From 5a256ea7b50f0e9dba18211edd99da3b1bbe22d7 Mon Sep 17 00:00:00 2001 From: Rob Davis Date: Mon, 24 Oct 2022 16:09:58 +0100 Subject: [PATCH 01/22] #6550 enables tagging of words that are terms to work. looking for the processing object property seems to be unnecessary --- sites/all/modules/contrib/lexicon/lexicon.module | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/sites/all/modules/contrib/lexicon/lexicon.module b/sites/all/modules/contrib/lexicon/lexicon.module index 3b39227264..67c5440b53 100644 --- a/sites/all/modules/contrib/lexicon/lexicon.module +++ b/sites/all/modules/contrib/lexicon/lexicon.module @@ -1086,10 +1086,7 @@ function _lexicon_get_terms(&$vids, $langcode = LANGUAGE_NONE) { } // Add extra information to each term in the tree. - foreach ($tree as $term) { - if (!$term->processing){ - continue; - } + foreach ($tree as $term) { _lexicon_term_add_info($term, TRUE); $terms[$langcode][] = $term; } From 1452488a0c84825abc467e241d364876a96977f6 Mon Sep 17 00:00:00 2001 From: Rob Davis Date: Tue, 25 Oct 2022 17:22:34 +0100 Subject: [PATCH 02/22] #6519 remove protocol and domain so that all cached css js is consistent, to fix unstyled issue --- .../scratchpads_backend.module | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/sites/all/modules/custom/scratchpads/scratchpads_backend/scratchpads_backend.module b/sites/all/modules/custom/scratchpads/scratchpads_backend/scratchpads_backend.module index 90d8374692..b1cc124089 100644 --- a/sites/all/modules/custom/scratchpads/scratchpads_backend/scratchpads_backend.module +++ b/sites/all/modules/custom/scratchpads/scratchpads_backend/scratchpads_backend.module @@ -313,4 +313,22 @@ function scratchpads_backend_ckeditor_plugin(){ 'path' => drupal_get_path('module', 'scratchpads_backend') . '/ckeditor/' ) ); +} + + + +/** + * Implements hook_advagg_get_css_aggregate_contents_alter() + * + * This function to solve problem caused by www prefix in + * requests to the site - the cause of unstyled site content + * due to cached CSS/JS files containing the www prefix as a result. + * + * credit: https://www.drupal.org/project/advagg/issues/2353811#comment-9268783 + */ +function scratchpads_backend_advagg_get_css_aggregate_contents_alter(&$data, $files, $aggregate_settings) { + global $base_url, $base_path; + + // Remove protocol and hostname so aggregation can be used cross-protocol and cross-domain + $data = str_replace($base_url, rtrim($base_path, '/'), $data); } \ No newline at end of file From 57d56003e24badd79990a80f7b95f43dd65bae0e Mon Sep 17 00:00:00 2001 From: Rob Davis Date: Tue, 8 Nov 2022 10:41:28 +0000 Subject: [PATCH 03/22] #6519 remove this fix that didnt execute, according to xdebug https://github.com/NaturalHistoryMuseum/scratchpads2/issues/6519#issuecomment-1292302562 instead going to update the module advagg module to 2.8 to 2.35 and regression test --- .../scratchpads_backend.module | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/sites/all/modules/custom/scratchpads/scratchpads_backend/scratchpads_backend.module b/sites/all/modules/custom/scratchpads/scratchpads_backend/scratchpads_backend.module index b1cc124089..0ac274d40b 100644 --- a/sites/all/modules/custom/scratchpads/scratchpads_backend/scratchpads_backend.module +++ b/sites/all/modules/custom/scratchpads/scratchpads_backend/scratchpads_backend.module @@ -317,18 +317,3 @@ function scratchpads_backend_ckeditor_plugin(){ -/** - * Implements hook_advagg_get_css_aggregate_contents_alter() - * - * This function to solve problem caused by www prefix in - * requests to the site - the cause of unstyled site content - * due to cached CSS/JS files containing the www prefix as a result. - * - * credit: https://www.drupal.org/project/advagg/issues/2353811#comment-9268783 - */ -function scratchpads_backend_advagg_get_css_aggregate_contents_alter(&$data, $files, $aggregate_settings) { - global $base_url, $base_path; - - // Remove protocol and hostname so aggregation can be used cross-protocol and cross-domain - $data = str_replace($base_url, rtrim($base_path, '/'), $data); -} \ No newline at end of file From dd9d202327542335a3f889fe8c594fe42f50ce38 Mon Sep 17 00:00:00 2001 From: Rob Davis Date: Tue, 8 Nov 2022 10:42:49 +0000 Subject: [PATCH 04/22] #6519 update advagg from 2.8 to 2.35 --- .../contrib/advagg/{README.txt => README.md} | 786 ++-- .../modules/contrib/advagg/advagg.admin.inc | 1126 ++++- .../modules/contrib/advagg/advagg.admin.js | 72 +- .../modules/contrib/advagg/advagg.advagg.inc | 332 +- .../all/modules/contrib/advagg/advagg.api.php | 46 +- .../modules/contrib/advagg/advagg.cache.inc | 392 +- .../advagg/advagg.developer-documentation.php | 41 + .../modules/contrib/advagg/advagg.drush.inc | 84 +- sites/all/modules/contrib/advagg/advagg.inc | 910 +++- sites/all/modules/contrib/advagg/advagg.info | 9 +- .../all/modules/contrib/advagg/advagg.install | 2169 +++++++-- .../contrib/advagg/advagg.make.example | 50 + .../modules/contrib/advagg/advagg.missing.inc | 906 +++- .../all/modules/contrib/advagg/advagg.module | 4098 ++++++++++++++--- .../advagg_bundler/advagg_bundler.admin.inc | 158 +- .../advagg_bundler/advagg_bundler.advagg.inc | 50 +- .../advagg/advagg_bundler/advagg_bundler.info | 7 +- .../advagg_bundler/advagg_bundler.install | 37 + .../advagg_bundler/advagg_bundler.module | 329 +- .../advagg_critical_css.admin.inc | 381 ++ .../advagg_critical_css.info | 14 + .../advagg_critical_css.install | 84 + .../advagg_critical_css.module | 239 + .../advagg/advagg_css_cdn/advagg_css_cdn.info | 7 +- .../advagg_css_cdn/advagg_css_cdn.install | 9 + .../advagg_css_cdn/advagg_css_cdn.module | 80 +- .../advagg_css_compress.admin.inc | 52 +- .../advagg_css_compress.advagg.inc | 71 +- .../advagg_css_compress.info | 8 +- .../advagg_css_compress.install | 42 +- .../advagg_css_compress.module | 145 +- .../advagg/advagg_css_compress/yui/CSSMin.inc | 1144 +++-- .../advagg_ext_compress.admin.inc | 83 + .../advagg_ext_compress.info | 13 + .../advagg_ext_compress.module | 241 + .../advagg/advagg_font/advagg_font.admin.inc | 209 + .../advagg/advagg_font/advagg_font.advagg.inc | 63 + .../advagg/advagg_font/advagg_font.info | 14 + .../advagg/advagg_font/advagg_font.inline.js | 39 + .../advagg/advagg_font/advagg_font.install | 61 + .../contrib/advagg/advagg_font/advagg_font.js | 110 + .../advagg/advagg_font/advagg_font.module | 542 +++ .../advagg/advagg_js_cdn/advagg_js_cdn.info | 7 +- .../advagg_js_cdn/advagg_js_cdn.install | 11 +- .../advagg/advagg_js_cdn/advagg_js_cdn.module | 28 +- .../advagg/advagg_js_cdn/js/jquery-ui.js | 41 +- .../advagg_js_compress.admin.inc | 135 +- .../advagg_js_compress.advagg.inc | 626 ++- .../advagg_js_compress.drush.inc | 111 + .../advagg_js_compress.info | 10 +- .../advagg_js_compress.install | 38 +- .../advagg_js_compress.module | 581 ++- .../advagg_js_compress.php53.inc | 138 + .../advagg/advagg_js_compress/jshrink.inc | 93 +- .../advagg/advagg_js_compress/jsminplus.inc | 76 +- .../advagg/advagg_js_compress/jspacker.inc | 4 +- .../advagg/advagg_js_compress/jsqueeze.inc | 1010 ++-- .../advagg/advagg_mod/advagg_mod.admin.inc | 572 ++- .../advagg/advagg_mod/advagg_mod.advagg.inc | 83 +- .../contrib/advagg/advagg_mod/advagg_mod.info | 8 +- .../advagg/advagg_mod/advagg_mod.install | 87 + .../advagg/advagg_mod/advagg_mod.module | 3462 +++++++++++--- .../advagg/advagg_mod/advagg_mod_css_defer.js | 5 +- .../advagg/advagg_mod/cssrelpreload.js | 112 + .../advagg/advagg_mod/cssrelpreload.min.js | 3 + .../contrib/advagg/advagg_mod/loadCSS.js | 121 +- .../contrib/advagg/advagg_mod/loadCSS.min.js | 5 +- .../contrib/advagg/advagg_mod/onloadCSS.js | 37 + .../advagg/advagg_mod/onloadCSS.min.js | 3 + .../advagg_relocate/advagg_relocate.admin.inc | 532 +++ .../advagg_relocate.advagg.inc | 1104 +++++ .../advagg_relocate/advagg_relocate.info | 13 + .../advagg_relocate/advagg_relocate.install | 23 + .../advagg_relocate/advagg_relocate.module | 1350 ++++++ .../advagg/advagg_sri/advagg_sri.admin.inc | 55 + .../advagg/advagg_sri/advagg_sri.advagg.inc | 240 + .../contrib/advagg/advagg_sri/advagg_sri.info | 13 + .../advagg/advagg_sri/advagg_sri.install | 75 + .../advagg/advagg_sri/advagg_sri.module | 80 + .../advagg_validator.admin.inc | 101 +- .../advagg_validator.advagg.inc | 31 + .../advagg_validator/advagg_validator.inc | 14 +- .../advagg_validator/advagg_validator.info | 10 +- .../advagg_validator/advagg_validator.install | 102 +- .../advagg_validator/advagg_validator.js | 73 +- .../advagg_validator/advagg_validator.module | 154 + .../all/modules/contrib/advagg/composer.json | 19 + .../modules/contrib/advagg/tests/advagg.test | 534 ++- .../advagg/tests/css_test_files/advagg.css | 48 + .../css_test_files/advagg.css.optimized.css | 13 +- .../advagg/tests/css_test_files/charset.css | 6 + .../css_test_files/charset.css.optimized.css | 3 +- .../tests/css_test_files/charset_newline.css | 1 + .../charset_newline.css.optimized.css | 1 + .../tests/css_test_files/charset_sameline.css | 1 + .../charset_sameline.css.optimized.css | 1 + .../tests/css_test_files/comment_hacks.css | 7 + .../comment_hacks.css.optimized.css | 3 +- .../comment_hacks.css.unoptimized.css | 7 + .../css_test_files/css_input_with_import.css | 3 +- .../css_input_with_import.css.optimized.css | 3 +- .../css_input_with_import.css.unoptimized.css | 10 +- .../css_input_without_import.css | 2 +- ...css_input_without_import.css.optimized.css | 1 + ...s_input_without_import.css.unoptimized.css | 2 +- .../css_subfolder/css_input_with_import.css | 2 +- .../css_input_with_import.css.optimized.css | 3 +- .../css_input_with_import.css.unoptimized.css | 9 +- .../advagg/tests/css_test_files/import1.css | 3 +- .../advagg/tests/css_test_files/import2.css | 4 +- .../contrib/advagg/tpl/imce-page.tpl.php | 41 + 111 files changed, 23264 insertions(+), 4093 deletions(-) rename sites/all/modules/contrib/advagg/{README.txt => README.md} (53%) create mode 100644 sites/all/modules/contrib/advagg/advagg.developer-documentation.php create mode 100644 sites/all/modules/contrib/advagg/advagg.make.example create mode 100644 sites/all/modules/contrib/advagg/advagg_bundler/advagg_bundler.install create mode 100644 sites/all/modules/contrib/advagg/advagg_critical_css/advagg_critical_css.admin.inc create mode 100644 sites/all/modules/contrib/advagg/advagg_critical_css/advagg_critical_css.info create mode 100644 sites/all/modules/contrib/advagg/advagg_critical_css/advagg_critical_css.install create mode 100644 sites/all/modules/contrib/advagg/advagg_critical_css/advagg_critical_css.module create mode 100644 sites/all/modules/contrib/advagg/advagg_ext_compress/advagg_ext_compress.admin.inc create mode 100644 sites/all/modules/contrib/advagg/advagg_ext_compress/advagg_ext_compress.info create mode 100644 sites/all/modules/contrib/advagg/advagg_ext_compress/advagg_ext_compress.module create mode 100644 sites/all/modules/contrib/advagg/advagg_font/advagg_font.admin.inc create mode 100644 sites/all/modules/contrib/advagg/advagg_font/advagg_font.advagg.inc create mode 100644 sites/all/modules/contrib/advagg/advagg_font/advagg_font.info create mode 100644 sites/all/modules/contrib/advagg/advagg_font/advagg_font.inline.js create mode 100644 sites/all/modules/contrib/advagg/advagg_font/advagg_font.install create mode 100644 sites/all/modules/contrib/advagg/advagg_font/advagg_font.js create mode 100644 sites/all/modules/contrib/advagg/advagg_font/advagg_font.module create mode 100644 sites/all/modules/contrib/advagg/advagg_js_compress/advagg_js_compress.drush.inc create mode 100644 sites/all/modules/contrib/advagg/advagg_js_compress/advagg_js_compress.php53.inc create mode 100644 sites/all/modules/contrib/advagg/advagg_mod/advagg_mod.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_mod/onloadCSS.js create mode 100644 sites/all/modules/contrib/advagg/advagg_mod/onloadCSS.min.js create mode 100644 sites/all/modules/contrib/advagg/advagg_relocate/advagg_relocate.admin.inc create mode 100644 sites/all/modules/contrib/advagg/advagg_relocate/advagg_relocate.advagg.inc create mode 100644 sites/all/modules/contrib/advagg/advagg_relocate/advagg_relocate.info create mode 100644 sites/all/modules/contrib/advagg/advagg_relocate/advagg_relocate.install create mode 100644 sites/all/modules/contrib/advagg/advagg_relocate/advagg_relocate.module 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 create mode 100644 sites/all/modules/contrib/advagg/advagg_validator/advagg_validator.advagg.inc create mode 100644 sites/all/modules/contrib/advagg/composer.json create mode 100644 sites/all/modules/contrib/advagg/tpl/imce-page.tpl.php diff --git a/sites/all/modules/contrib/advagg/README.txt b/sites/all/modules/contrib/advagg/README.md similarity index 53% rename from sites/all/modules/contrib/advagg/README.txt rename to sites/all/modules/contrib/advagg/README.md index 40a9627592..8740c676cb 100644 --- a/sites/all/modules/contrib/advagg/README.txt +++ b/sites/all/modules/contrib/advagg/README.md @@ -1,20 +1,356 @@ ----------------------------------- ADVANCED CSS/JS AGGREGATION MODULE ----------------------------------- +================================== CONTENTS OF THIS FILE --------------------- - - Features & benefits - - Configuration - - JSMin PHP Extension - - JavaScript Bookmarklet - - Technical Details & Hooks + - Introduction + - Requirements + - Recommended modules + - Installation - How to get a high PageSpeed score + - JSMin PHP Extension + - Brotli PHP Extension + - Zopfli PHP Extension - nginx Configuration + - JavaScript Bookmarklet - Troubleshooting + - Features & benefits + - Configuration + - Additional options for `drupal_add_css/js` functions + - Technical Details & Hooks + + +INTRODUCTION +------------ + +The Advanced CSS/JS Aggregation allows you to improve the frontend performance +of your site. Be sure to do a before and after comparison by using Google's +PageSpeed Insights and WebPagetest.org. +https://developers.google.com/speed/pagespeed/insights/ +http://www.webpagetest.org/easy + + +REQUIREMENTS +------------ + +No special requirements. + + +RECOMMENDED MODULES +------------------- + + - Libraries (https://www.drupal.org/project/libraries) + Allows for 3rd party code for minification to be used by AdvAgg. + + +INSTALLATION +------------ + + - Install as you would normally install a contributed Drupal module. Visit: + https://drupal.org/documentation/install/modules-themes/modules-7 + for further information. + + +HOW TO GET A HIGH PAGESPEED SCORE +--------------------------------- + +Be sure to check the site after every section to make sure the change didn't +mess up your site. The changes under AdvAgg Modifier are usually the most +problematic but they offer the biggest improvements. + +#### Advanced CSS/JS Aggregation #### +Go to `admin/config/development/performance/advagg` + +Select "Use recommended (optimized) settings" + +#### AdvAgg Compress Javascript #### +Install AdvAgg Compress Javascript if not enabled and go to +`admin/config/development/performance/advagg/js-compress` + + - Select JSMin if available; otherwise select JSMin+ + - Select Strip everything (smallest files) + - Save configuration + - Click the batch compress link to process these files at the top. + +#### AdvAgg Async Font Loader #### +Install AdvAgg Async Font Loader if not enabled and go to +`admin/config/development/performance/advagg/font` + + - Select Local file included in aggregate (version: X.X.X). If this option is + not available follow the directions right below the options on how to install + it. + +Keep the 2 checkboxes checked. + +#### AdvAgg Bundler #### +Install AdvAgg Bundler if not enabled and go to +`admin/config/development/performance/advagg/bundler` + +If your server supports HTTP 2 then select "Use HTTP 2.0 settings"; otherwise +leave it at the "Use HTTP 1.1 settings". + +#### AdvAgg Relocate #### +Install AdvAgg Relocate if not enabled and go to +`admin/config/development/performance/advagg/relocate` + +Select "Use recommended (optimized) settings" + +#### AdvAgg Modifier #### +Install AdvAgg Modifier if not enabled and go to +`admin/config/development/performance/advagg/mod` + +Select "Use recommended (optimized) settings" + +#### AdvAgg Critical CSS module #### +Install AdvAgg Critical CSS if not enabled and go to +`admin/config/development/performance/advagg/critical-css` + +These are the directions for the front page of your site. + +Under Add Critical CSS +- Select the theme that is your front page; usually the default is correct. +- User type should be set to 'anonymous' under most circumstances. +- Type of lookup, select URL +- Value to lookup, type in `` +- Critical CSS, paste in the generated CSS from running your homepage url +through https://www.sitelocity.com/critical-path-css-generator which is inside +of the 'Critical Path CSS' textarea on the sitelocity page. +- Click Save Configuration. + +Other landing pages should have their critical CSS added as well. If you have +Google Analytics this will show you how to find your top landing pages +https://developers.google.com/analytics/devguides/reporting/core/v3/common-queries#top-landing-pages +or for Piwik https://piwik.org/faq/how-to/faq_160/. You can also use this +chrome browser plugin to generate critical CSS +https://chrome.google.com/webstore/detail/critical-style-snapshot/gkoeffcejdhhojognlonafnijfkcepob?hl=en + + +JSMIN PHP EXTENSION +------------------- + +The AdvAgg JS Compress module can take advantage of jsmin.c. JavaScript parsing +and minimizing will be done in C instead of PHP dramatically speeding up the +process. If using PHP 5.3.10 or higher https://github.com/sqmk/pecl-jsmin is +recommended. If using PHP 5.3.9 or lower +http://www.ypass.net/software/php_jsmin/ is recommended. + + +BROTLI PHP EXTENSION +-------------------- + +The AdvAgg module can take advantage of Brotli compression. Install this +extension to take advantage of it. Should reduce CSS/JS files by 20%. +https://github.com/kjdev/php-ext-brotli + + +ZOPFLI PHP EXTENSION +-------------------- + +The AdvAgg module can take advantage of the Zopfli compression algorithm. +Install this extension to take advantage of it. This gives higher gzip +compression ratios compared to stock PHP. +https://github.com/kjdev/php-ext-zopfli + + +NGINX CONFIGURATION +------------------- + +https://drupal.org/node/1116618 +Note that @drupal (last line of code below) might be @rewrite or @rewrites +depending on your servers configuration. If there are image style rules in your +Nginx configuration add this right below that. If you want to have brotli +support https://github.com/google/ngx_brotli is how to install that; add +`brotli_static on;` right above `gzip_static on;` in the configuration below. + + ### + ### 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 $uri/ @drupal; + } + +Also noted that some ready made nginx configurations add in a Last-Modified +header inside the advagg directories. These should be removed. + + +JAVASCRIPT BOOKMARKLET +---------------------- + +You can use this JS code as a bookmarklet for toggling the AdvAgg URL parameter. +See http://en.wikipedia.org/wiki/Bookmarklet for more details. + + javascript:(function(){var loc = document.location.href,qs = document.location.search,regex_off = /\&?advagg=-1/,goto = loc;if(qs.match(regex_off)) {goto = loc.replace(regex_off, '');} else {qs = qs ? qs + '&advagg=-1' : '?advagg=-1';goto = document.location.pathname + qs;}window.location = goto;})(); + + +TROUBLESHOOTING +--------------- + +If the core Fast 404 Pages functionality is enabled via settings.php, the +settings must be changed in order for the on-demand file compilation to work. +Change this: + + $conf['404_fast_paths_exclude'] = '/\/(?:styles)\//'; + +to this: + + $conf['404_fast_paths_exclude'] = '/\/(?:styles|advagg_(cs|j)s)\//'; + +Similarly, if the Fast_404 module is enabled, the 'fast_404_string_whitelisting' +variable must be set inside of settings.php. Add this to your settings.php file: + + $conf['fast_404_string_whitelisting'][] = '/advagg_'; + + +Modules like the Central Authentication Services https://drupal.org/project/cas +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. + + services/* + sites/default/files/advagg_css/* + sites/default/files/advagg_js/* + httprl_async_function_callback + +In the example of CAS this setting can be found on the `admin/config/people/cas` +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). + + + # No mod_headers. Apache module headers is not enabled. + + # No mod_expires. Apache module expires is not enabled. + + # Use ETags. + FileETag MTime Size + + + + # Use Expires Directive if apache module expires is enabled. + + # Do not use ETags. + FileETag None + # Enable expirations. + ExpiresActive On + # Cache all aggregated css/js files for 52 weeks after access (A). + ExpiresDefault A31449600 + + + # Use Headers Directive if apache module headers is enabled. + + # Do not use etags for cache validation. + Header unset ETag + + # Set a far future Cache-Control header to 52 weeks. + Header set Cache-Control "max-age=31449600, no-transform, public" + + + Header append Cache-Control "no-transform, public" + + + + # Force advagg .js file to have the type of application/javascript. + + ForceType application/javascript + + + +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 - +something as simple as an errant unfinished comment in one file may cause entire +aggregates of files to be ignored. + + +If AdvAgg was installed via drush sometimes directory permissions need to be +fixed. Using `chown -R` on the advagg directories usually solves this issue. + + +If hosting on Pantheon, you might need to add this to your settings.php file if +you get Numerous login prompts after enabling Adv Agg module on Pantheon Test +and Live instances. + + if (isset($_SERVER['PANTHEON_ENVIRONMENT'])) { + // NO trailing slash when setting the $base_url variable. + switch ($_SERVER['PANTHEON_ENVIRONMENT']) { + case 'dev': + $base_url = 'http://dev-sitename.gotpantheon.com'; + break; + + case 'test': + $base_url = 'http://test-sitename.gotpantheon.com'; + break; + + case 'live': + $base_url = 'http://www.domain.tld'; + break; + } + // Remove a trailing slash if one was added. + if (!empty($base_url)) { + $base_url = rtrim($base_url, '/'); + } + } + + +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 +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] + + +If brotli compression is not working 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 right above this line `# Rules to +correctly serve gzip compressed CSS and JS files.` + + + # Serve brotli compressed CSS if they exist and the client accepts br. + RewriteCond %{HTTP:Accept-encoding} br + RewriteCond %{REQUEST_FILENAME}\.br -s + RewriteRule ^(.*)\.css $1\.css\.br [QSA] + RewriteRule \.css\.br$ - [T=text/css,E=no-gzip:1] + + # Serve brotli compressed JS if they exist and the client accepts br. + RewriteCond %{HTTP:Accept-encoding} br + RewriteCond %{REQUEST_FILENAME}\.br -s + RewriteRule ^(.*)\.js $1\.js\.br [QSA] + RewriteRule \.js\.br$ - [T=application/javascript,E=no-gzip:1] + + + # Serve correct encoding type. + Header set Content-Encoding br + # Force proxies to cache compressed and non-compressed css/js files + # separately. + Header append Vary Accept-Encoding + + + + +If you are having 5 minute or longer timeouts on the admin/reports/status page +then you might need to use an alternative to drupal_httP_request(). The cURL +HTTP Request module https://www.drupal.org/project/chr might fix this issue. FEATURES & BENEFITS @@ -34,10 +370,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 +382,29 @@ 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. + - Defer loading of CSS. - 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,11 +419,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. - - 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. - Enabled by default. + effect as placing `?advagg=-1` in the URL. Disabled by default. - Use Cores Grouping Logic: Leave this checkbox enabled until you are ready to begin exploring the AdvAgg Bundler sub-module which overrides Core's functionality. This groups files just like Core does so should just work. @@ -105,6 +438,49 @@ Settings page is located at: cache hit ratio will be low; if that is the case consider using Drupal.settings for a better cache hit ratio. +**Resource Hints** + +Preemptively get resources (CSS/JS & sub requests). This will set tags in the +document head telling the browser to open up connections before they are needed. + + - DNS Prefetch: Start the DNS lookup for external CSS and JavaScript files as + soon as possible. + - Preconnect: Start the connection to external resources before an HTTP request + is actually sent to the server. On HTTPS this can have a dramatic effect. + - Location of resource hints: This only needs to be changed if the above + settings are not working. + - Preload link http headers: If your server supports HTTP/2 push then this + allows for resources to be sent before the browser knows it needs it. + +**Cron Options** + +Adjusting the frequency of how often something happens on cron. + +**Obscure Options** + + - 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. + Enabled by default. + - Create .br files: Check this by default as it will improve your performance. + For every Aggregated file generated, this will create a brotli version of + file and then only serve it out if the browser accepts gzip files + compression. Enabled by default IF the Brotli Extension for PHP is installed. + See https://github.com/kjdev/php-ext-brotli + - Run advagg_ajax_render_alter(): Turn this off if you're having issues with + ajax. Also keep in mind that the max_input_vars setting can cause issues if + you are submitting a lot of data. + - Include the base_url variable in the hooks hash array: Enabled only if you + know you need it. + - Convert absolute paths to be self references: Turn on unless pages are used + inside of an iframe. + - Convert absolute paths to be protocol relative paths: Safe to use unless you + need to support IE6. + - Convert http:// to https://: Usually not needed, but here in case you do. + - Do not run CSS url() values through file_create_url(): Usually not needed, + but here in case you do. + + **CSS Options & JS Options** - Combine CSS files by using media queries: "Use cores grouping logic" needs to @@ -112,9 +488,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 +508,10 @@ 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. + - Hook Render Info: Displays the scripts and styles render callbakcs. 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 +530,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,23 +539,38 @@ 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 the ones that have not been accessed in the last 30 days. + - Clear 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. + - Delete Orphaned Aggregates: Scan CSS/JS advagg dir and remove file if + there is no associated db record. - Clear Missing Files From the Database: Scan for missing files and remove the associated entries in the database. - Delete Unused Aggregates from Database: Delete aggregates that have not been accessed in the last 6 weeks. - Delete orphaned/expired advagg locks from the semaphore database table. + - Delete leftover temporary files: Delete old temporary files from the + filesystem. - 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 - directories. - - Increment Global Counter: Force the creation of all new aggregates by - incrementing a global counter. + - Clear All Files: Remove All Generated Files. Remove all files in the + `advagg_(css|js)` directories. + - Force New Aggregates: Increment Global Counter. Force the creation of all + new aggregates by incrementing a global counter. + - Rescan All Files: Force rescan all files and clear the cache. Useful if a + css/js change from a deployment did not work. + - Remove deleted files in the advagg_files table: Remove entries in the + advagg_files table that have a filesize of 0 and delete the + javascript_parsed variable. This gets around the grace period that the + cron cleanup does. + - Reset the AdvAgg Files table: Truncate the advagg_files table and delete + the javascript_parsed variable. This may cause some 404s for CSS/JS assets + for a short amount of time (seconds). Useful if you really want to reset + some stuff. Might be best to put the site into maintenance mode before + doing this. **Hidden Settings** @@ -186,6 +581,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; @@ -243,24 +641,55 @@ current defaults are shown. // Value for the compression ratio test. $conf['advagg_js_compress_ratio'] = 0.1; + // Skip far future check on status page. + $conf['advagg_skip_far_future_check'] = FALSE; -JSMIN PHP EXTENSION -------------------- + // Skip preprocess and enabled checks. + $conf['advagg_skip_enabled_preprocess_check'] = FALSE; -The AdvAgg JS Compress module can take advantage of jsmin.c. JavaScript parsing -and minimizing will be done in C instead of PHP dramatically speeding up the -process. If using PHP 5.3.10 or higher https://github.com/sqmk/pecl-jsmin is -recommended. If using PHP 5.3.9 or lower -http://www.ypass.net/software/php_jsmin/ is recommended. + // 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; -JAVASCRIPT BOOKMARKLET ----------------------- + // If FALSE mtime of files will only trigger a change if they are in the + // future. + $conf['advagg_strict_mtime_check'] = TRUE; -You can use this JS code as a bookmarklet for toggling the AdvAgg URL parameter. -See http://en.wikipedia.org/wiki/Bookmarklet for more details. + // Skip 304 check on status page. + $conf['advagg_skip_304_check'] = FALSE; - javascript:(function(){var loc = document.location.href,qs = document.location.search,regex_off = /\&?advagg=-1/,goto = loc;if(qs.match(regex_off)) {goto = loc.replace(regex_off, '');} else {qs = qs ? qs + '&advagg=-1' : '?advagg=-1';goto = document.location.pathname + qs;}window.location = goto;})(); + // Control how many bytes can be inlined. + $conf['advagg_mod_css_defer_inline_size_limit'] = 12288; + + // Control how many bytes the preload header can use. + $conf['advagg_resource_hints_preload_max_size'] = 3072; + + // If TRUE, only verify 1st hash instead of all 3 of the filename. + $conf['advagg_weak_file_verification'] = FALSE; + + +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. + - `preprocess_lock`: TRUE - Make sure the preprocess key will not ever change. TECHNICAL DETAILS & HOOKS @@ -269,10 +698,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. @@ -288,23 +717,28 @@ 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)); + // CSS. + $hooks_hash = advagg_get_current_hooks_hash(); + $css_cache_id_full = + 'advagg:css:full:' . $hooks_hash . ':' . + drupal_hash_base64(serialize($full_css)); + // JS. + $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. @@ -316,235 +750,57 @@ 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. - - -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()" - - 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** - -On the `admin/config/development/performance/advagg/mod` page there is the -setting "Remove unused JavaScript tags if possible". This is a backport of D8 -where it will not add any JS to the page if it is not being used. -https://drupal.org/node/1279226 - -The AdvAgg Bundler module on the -`admin/config/development/performance/advagg/bundler` page. The bundler provides -intelligent bundling of CSS and JS files by grouping files that belong together. -This does what core tried to do; group CSS & JS files together that get used -together. Using this will make your pagespeed score go down as there will be -more css/js files to download but if different css/js files are used on -different pages of your site this will be a net win as a new full aggregate will -not have to be downloaded, instead a smaller aggregate can be downloaded, -ideally with only the css/js that is different on that page. You can select how -many bundles to create and the bundler will do it's best to meet that goal; if -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. - - -NGINX CONFIGURATION -------------------- - -http://drupal.org/node/1116618 -Note that @drupal (last line of code below) might be @rewrite or @rewrites -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; - add_header ETag ""; - add_header Cache-Control "max-age=31449600, no-transform, public"; - try_files $uri @drupal; - } - - -TROUBLESHOOTING ---------------- - -If the core Fast 404 Pages functionality is enabled via settings.php, the -settings must be changed in order for the on-demand file compilation to work. -Change this: - - $conf['404_fast_paths_exclude'] = '/\/(?:styles)\//'; - -to this: - - $conf['404_fast_paths_exclude'] = '/\/(?:styles|advagg_(cs|j)s)\//'; - -Similarly, if the Fast_404 module is enabled, the 'fast_404_string_whitelisting' -variable must be set inside of settings.php. Add this to your settings.php file: - - $conf['fast_404_string_whitelisting'][] = '/advagg_'; - - -Modules like the Central Authentication Services https://drupal.org/project/cas -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. - - services/* - sites/default/files/advagg_css/* - sites/default/files/advagg_js/* - httprl_async_function_callback - -In the example of CAS this setting can be found on the `admin/config/people/cas` -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). - - - # No mod_headers - - # No mod_expires - - # Use ETags. - FileETag MTime Size - - - - # Use Expires Directive. - - # Do not use ETags. - FileETag None - # Enable expirations. - ExpiresActive On - # Cache all aggregated css/js files for 52 weeks after access (A). - ExpiresDefault A31449600 - - - - # Do not use etags for cache validation. - Header unset ETag - - # Set a far future Cache-Control header to 52 weeks. - Header set Cache-Control "max-age=31449600, no-transform, public" - - - Header append Cache-Control "no-transform, public" - - - - # Force advagg .js file to have the type of application/javascript. - - ForceType application/javascript - - - -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 - -something as simple as an errant unfinished comment in one file may cause entire -aggregates of files to be ignored. - - -If AdvAgg was installed via drush sometimes directory permissions need to be -fixed. Using `chown -R` on the advagg directories usually solves this issue. - - -If hosting on Pantheon, you might need to add this to your settings.php file if -you get Numerous login prompts after enabling Adv Agg module on Pantheon Test -and Live instances. - - if (isset($_SERVER['PANTHEON_ENVIRONMENT'])) { - // NO trailing slash when setting the $base_url variable. - switch ($_SERVER['PANTHEON_ENVIRONMENT']) { - case 'dev': - $base_url = 'http://dev-sitename.gotpantheon.com'; - break; - - case 'test': - $base_url = 'http://test-sitename.gotpantheon.com'; - break; - - case 'live': - $base_url = 'http://www.domain.tld'; - break; - } - // Remove a trailing slash if one was added. - if (!empty($base_url)) { - $base_url = rtrim($base_url, '/'); - } - } - - -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 -production and not production environments. diff --git a/sites/all/modules/contrib/advagg/advagg.admin.inc b/sites/all/modules/contrib/advagg/advagg.admin.inc index 5eff78b5e6..80601001e0 100644 --- a/sites/all/modules/contrib/advagg/advagg.admin.inc +++ b/sites/all/modules/contrib/advagg/advagg.admin.inc @@ -5,33 +5,69 @@ * Admin page callbacks for the advanced CSS/JS aggregation module. */ +/** + * @defgroup advagg_forms Advanced Aggregates Forms + * @{ + * Advanced Aggregates administration forms. + */ + /** * Form builder; Configure advagg settings. * - * @ingroup forms - * * @see system_settings_form() */ function advagg_admin_settings_form($form, $form_state) { drupal_set_title(t('AdvAgg: Configuration')); + advagg_display_message_if_requirements_not_met(); $config_path = advagg_admin_config_root_path(); + $options = array( + 0 => t('Use default (safe) settings'), + 2 => t('Use recommended (optimized) settings'), + 4 => t('Use customized settings'), + ); + $form['advagg_admin_mode'] = array( + '#type' => 'radios', + '#title' => t('AdvAgg Settings'), + '#default_value' => variable_get('advagg_admin_mode', ADVAGG_ADMIN_MODE), + '#options' => $options, + '#description' => t("Default settings will mirror core as closely as possible.
Recommended settings are optimized for speed."), + ); + + $form['global_container'] = array( + '#type' => 'container', + '#hidden' => TRUE, + '#states' => array( + 'visible' => array( + ':input[name="advagg_admin_mode"]' => array('value' => '4'), + ), + ), + ); + // Simple checkbox settings. - $form['global'] = array( + $form['global_container']['global'] = array( '#type' => 'fieldset', '#title' => t('Global Options'), ); - $form['global']['advagg_enabled'] = array( + $form['global_container']['global']['advagg_enabled'] = array( '#type' => 'checkbox', - '#title' => t('Enable advanced aggregation'), + '#title' => t('Enable advanced aggregation (recommended)'), '#default_value' => variable_get('advagg_enabled', ADVAGG_ENABLED), '#description' => t('Uncheck this box to completely disable AdvAgg functionality.'), + '#recommended_value' => TRUE, ); - $form['global']['advagg_core_groups'] = array( + $advagg_core_groups_default = variable_get('advagg_core_groups', ADVAGG_CORE_GROUPS); + if (variable_get('advagg_combine_css_media', ADVAGG_COMBINE_CSS_MEDIA) + || variable_get('advagg_ie_css_selector_limiter', ADVAGG_IE_CSS_SELECTOR_LIMITER) + || (module_exists('advagg_bundler') && variable_get('advagg_bundler_active', ADVAGG_BUNDLER_ACTIVE))) { + $advagg_core_groups_default = FALSE; + } + $form['global_container']['global']['advagg_core_groups'] = array( '#type' => 'checkbox', '#title' => t('Use cores grouping logic'), - '#default_value' => variable_get('advagg_combine_css_media', ADVAGG_COMBINE_CSS_MEDIA) || variable_get('advagg_ie_css_selector_limiter', ADVAGG_IE_CSS_SELECTOR_LIMITER) ? FALSE : variable_get('advagg_core_groups', ADVAGG_CORE_GROUPS), + '#default_value' => $advagg_core_groups_default, '#description' => t('Will group files just like core does.'), + '#recommended_value' => FALSE, '#states' => array( 'enabled' => array( '#edit-advagg-combine-css-media' => array('checked' => FALSE), @@ -39,12 +75,13 @@ function advagg_admin_settings_form($form, $form_state) { ), ), ); - $form['global']['advagg_use_httprl'] = array( + $form['global_container']['global']['advagg_use_httprl'] = array( '#type' => 'checkbox', - '#title' => t('Use HTTPRL to generate aggregates.'), + '#title' => t('Use HTTPRL to generate aggregates (recommended)'), '#default_value' => module_exists('httprl') ? variable_get('advagg_use_httprl', ADVAGG_USE_HTTPRL) : FALSE, '#disabled' => module_exists('httprl') ? FALSE : TRUE, '#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')), + '#recommended_value' => TRUE, ); $aggressive_cache_conflicts = advagg_aggressive_cache_conflicts(); @@ -56,22 +93,50 @@ function advagg_admin_settings_form($form, $form_state) { } $options = array( -1 => t('Development ~ 300ms'), - 3 => t('Normal ~ 30ms'), - 5 => t('Aggressive ~ 10ms'), + 1 => t('Normal ~ 60ms'), + 3 => t('Render Cache ~ 30ms (recommended)'), + 5 => t('Aggressive Render Cache ~ 10ms'), ); - $form['global']['advagg_cache_level'] = array( + $form['global_container']['global']['advagg_cache_level'] = array( '#type' => 'radios', '#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 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.", array( + '#recommended_value' => 3, + '#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, + )), ); + $stream_wrappers = file_get_stream_wrappers(); + $prefix = variable_get('advagg_root_dir_prefix', ADVAGG_ROOT_DIR_PREFIX); + if ($prefix !== 'public://' + || $stream_wrappers['public']['class'] !== 'DrupalPublicStreamWrapper' + || count($stream_wrappers) > 2 + || file_default_scheme() !== 'public' + ) { + $stream_wrappers_string = array(); + foreach ($stream_wrappers as $key => $values) { + $stream_wrappers_string[] = t("@key:// uses @class; @description", array( + '@key' => $key, + '@class' => $values['class'], + '@description' => $values['description'], + )); + } + $stream_wrappers_string = implode("
\n", $stream_wrappers_string); + $form['global_container']['global']['advagg_root_dir_prefix'] = array( + '#type' => 'textfield', + '#title' => t('Stream wrapper for AdvAgg.'), + '#default_value' => $prefix, + '#description' => t("Options:
\n!stream_wrappers", array( + '!stream_wrappers' => $stream_wrappers_string, + )), + ); + } - $form['global']['dev_container'] = array( + $form['global_container']['global']['dev_container'] = array( '#type' => 'container', '#states' => array( 'visible' => array( @@ -80,14 +145,14 @@ function advagg_admin_settings_form($form, $form_state) { ), ); // Show msg about advagg css compress. - if (module_exists('advagg_css_compress') && (variable_get('advagg_css_compressor', ADVAGG_CSS_COMPRESSOR) || variable_get('advagg_css_compress_inline', ADVAGG_CSS_COMPRESS_INLINE))) { - $form['global']['dev_container']['advagg_css_compress_msg'] = array( + if (module_exists('advagg_css_compress') && (variable_get('advagg_css_compressor', ADVAGG_CSS_COMPRESSOR) > 0 || variable_get('advagg_css_compress_inline', ADVAGG_CSS_COMPRESS_INLINE))) { + $form['global_container']['global']['dev_container']['advagg_css_compress_msg'] = array( '#markup' => '

' . t('The AdvAgg CSS Compression module is disabled when in development mode.', array('@css' => url($config_path . '/advagg/css-compress'))) . '

', ); } // Show msg about advagg js compress. if (module_exists('advagg_js_compress') && (variable_get('advagg_js_compressor', ADVAGG_JS_COMPRESSOR) || variable_get('advagg_js_compress_inline', ADVAGG_JS_COMPRESS_INLINE))) { - $form['global']['dev_container']['advagg_js_compress_msg'] = array( + $form['global_container']['global']['dev_container']['advagg_js_compress_msg'] = array( '#markup' => '

' . t('The AdvAgg JS Compression module is disabled when in development mode.', array('@js' => url($config_path . '/advagg/js-compress'))) . '

', ); } @@ -95,14 +160,14 @@ function advagg_admin_settings_form($form, $form_state) { // Show msg about the jquery update compression setting. if (module_exists('jquery_update')) { if (variable_get('jquery_update_compression_type', 'min') === 'min') { - $form['global']['dev_container']['advagg_jquery_update_development'] = array( + $form['global_container']['global']['dev_container']['advagg_jquery_update_development'] = array( '#markup' => '

' . t('You might want to change the jQuery update compression level to "Development" as well.', array( '!url' => url('admin/config/development/jquery_update'), )) . '

', ); } else { - $form['global']['prod_container'] = array( + $form['global_container']['global']['prod_container'] = array( '#type' => 'container', '#states' => array( 'visible' => array( @@ -110,7 +175,7 @@ function advagg_admin_settings_form($form, $form_state) { ), ), ); - $form['global']['prod_container']['advagg_jquery_update_development'] = array( + $form['global_container']['global']['prod_container']['advagg_jquery_update_development'] = array( '#markup' => '

' . t('You might want to change the jQuery update compression level to "Production" as well.', array( '!url' => url('admin/config/development/jquery_update'), )) . '

', @@ -118,14 +183,205 @@ function advagg_admin_settings_form($form, $form_state) { } } - $form['global']['cron'] = array( + $advagg_resource_hints_dns_prefetch = variable_get('advagg_resource_hints_dns_prefetch', ADVAGG_RESOURCE_HINTS_DNS_PREFETCH); + $advagg_resource_hints_preconnect = variable_get('advagg_resource_hints_preconnect', ADVAGG_RESOURCE_HINTS_PRECONNECT); + $advagg_resource_hints_preload = variable_get('advagg_resource_hints_preload', ADVAGG_RESOURCE_HINTS_PRELOAD); + $form['global_container']['global']['resource_hints'] = array( + '#type' => 'fieldset', + '#title' => t('Resource Hints'), + '#collapsible' => TRUE, + '#collapsed' => ($advagg_resource_hints_dns_prefetch || $advagg_resource_hints_preconnect || $advagg_resource_hints_preload) ? FALSE : TRUE, + '#description' => t('Preemptively get resources (CSS/JS & sub requests).'), + ); + $form['global_container']['global']['resource_hints']['advagg_resource_hints_dns_prefetch'] = array( + '#type' => 'checkbox', + '#title' => t('DNS Prefetch (recommended).'), + '#default_value' => $advagg_resource_hints_dns_prefetch, + '#disabled' => '', + '#description' => t('Start the DNS lookup for external CSS and JavaScript files as soon as possible.'), + '#recommended_value' => TRUE, + ); + $form['global_container']['global']['resource_hints']['advagg_resource_hints_preconnect'] = array( + '#type' => 'checkbox', + '#title' => t('Preconnect (recommended).'), + '#default_value' => $advagg_resource_hints_preconnect, + '#disabled' => '', + '#description' => t('Start the connection to external resources before an HTTP request is actually sent to the server.'), + '#recommended_value' => TRUE, + ); + $form['global_container']['global']['resource_hints']['advagg_resource_hints_location'] = array( + '#type' => 'radios', + '#title' => t('Location of resource hints.'), + '#default_value' => variable_get('advagg_resource_hints_location', ADVAGG_RESOURCE_HINTS_LOCATION), + '#description' => t('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 some of the resource hints to work; Optimizely is an example that would need this other option. Link headers are not supported in every browser.'), + '#recommended_value' => 1, + '#options' => array( + 1 => t('Below charset=utf-8 (recommended)'), + 3 => t('Above charset=utf-8'), + ), + '#states' => array( + 'disabled' => array( + '#edit-advagg-resource-hints-dns-prefetch' => array('checked' => FALSE), + '#edit-advagg-resource-hints-preconnect' => array('checked' => FALSE), + ), + ), + ); + + // Preload Section. + $form['global_container']['global']['resource_hints']['preload']['advagg_resource_hints_preload'] = array( + '#type' => 'checkbox', + '#title' => t('Preload link http headers.'), + '#default_value' => $advagg_resource_hints_preload, + '#disabled' => '', + '#description' => t('Use link http headers to send out preload hints for local and external resources.'), + '#recommended_value' => FALSE, + ); + $advagg_resource_hints_preload_settings = advagg_get_resource_hints_preload_settings(); + $form['global_container']['global']['resource_hints']['preload']['advagg_resource_hints_preload_settings'] = array( + '#type' => 'fieldset', + '#title' => t('Preload Ordering and Settings'), + '#collapsible' => TRUE, + '#collapsed' => TRUE, + '#description' => t('Change the order of what preload items get sent first. Can also control what will be sent and if HTTP/2 push should be used (if your server supports it). If your webserver supports HTTP/2 Push, and it is enabled, the server will send every asset flagged to be pushed to the browser on every request, ignoring the browser cache (not a good thing for repeat visits). More complex server configuration will be needed to make this efficient; no solutions are currently available.'), + '#states' => array( + 'invisible' => array( + '#edit-advagg-resource-hints-preload' => array('checked' => FALSE), + ), + ), + ); + // Build the rows. + foreach ($advagg_resource_hints_preload_settings as $id => $entry) { + // Build the table rows. + $rows[$id] = array( + 'data' => array( + // Cell for the cross drag and drop element. + array('class' => array('entry-cross')), + // Weight item for the tabledrag. + array( + 'data' => array( + '#type' => 'weight', + '#title' => t('Weight'), + '#title_display' => 'invisible', + '#default_value' => $entry['#weight'], + '#parents' => array( + 'advagg_resource_hints_preload_settings', + $id, + 'weight', + ), + '#attributes' => array( + 'class' => array('entry-order-weight'), + ), + ), + ), + check_plain($entry['title']), + array( + 'data' => array( + '#type' => 'checkbox', + '#title' => t('Enable'), + '#title_display' => 'invisible', + '#default_value' => $entry['enabled'], + '#parents' => array( + 'advagg_resource_hints_preload_settings', + $id, + 'enabled', + ), + ), + ), + array( + 'data' => array( + '#type' => 'checkbox', + '#title' => t('HTTP/2 Push'), + '#title_display' => 'invisible', + '#default_value' => $entry['push'], + '#parents' => array( + 'advagg_resource_hints_preload_settings', + $id, + 'push', + ), + ), + ), + array( + 'data' => array( + '#type' => 'checkbox', + '#title' => t('Local'), + '#title_display' => 'invisible', + '#default_value' => $entry['local'], + '#parents' => array( + 'advagg_resource_hints_preload_settings', + $id, + 'local', + ), + ), + ), + array( + 'data' => array( + '#type' => 'checkbox', + '#title' => t('External'), + '#title_display' => 'invisible', + '#default_value' => $entry['external'], + '#parents' => array( + 'advagg_resource_hints_preload_settings', + $id, + 'external', + ), + ), + ), + ), + 'class' => array('draggable'), + ); + // Build rows of the form elements in the table. + $row_elements[$id] = array( + 'weight' => &$rows[$id]['data'][1]['data'], + 'enabled' => &$rows[$id]['data'][3]['data'], + 'push' => &$rows[$id]['data'][4]['data'], + 'local' => &$rows[$id]['data'][5]['data'], + 'external' => &$rows[$id]['data'][6]['data'], + ); + } + + // Add the table to the form. + $form['global_container']['global']['resource_hints']['preload']['advagg_resource_hints_preload_settings']['advagg_table'] = array( + '#theme' => 'table', + // The row form elements need to be processed and build, + // therefore pass them as element children. + 'elements' => $row_elements, + '#header' => array( + // We need two empty columns for the weight field and the cross. + array('data' => NULL, 'colspan' => 2), + t('Name'), + t('Enabled'), + t('HTTP/2 Push'), + t('Local'), + t('External'), + ), + '#rows' => $rows, + '#attributes' => array('id' => 'entry-order'), + ); + drupal_add_tabledrag('entry-order', 'order', 'sibling', 'entry-order-weight'); + $form['global_container']['global']['resource_hints']['preload']['advagg_resource_hints_preload_settings']['advagg_resource_hints_preload_reset'] = array( + '#type' => 'submit', + '#value' => t('Reset'), + '#submit' => array('advagg_admin_resource_hints_preload_reset'), + ); + $form['global_container']['global']['resource_hints']['advagg_resource_hints_use_immutable'] = array( + '#type' => 'checkbox', + '#title' => t('Send immutable header for all aggregated files (recommended).'), + '#default_value' => variable_get('advagg_resource_hints_use_immutable', ADVAGG_RESOURCE_HINTS_USE_IMMUTABLE), + '#description' => t('Have Apache send Cache-Control: immutable for all aggregated files. Current browser support', array( + '@url1' => 'http://bitsup.blogspot.de/2016/05/cache-control-immutable.html', + '@url2' => 'https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control#Browser_compatibility', + )), + '#recommended_value' => TRUE, + ); + + $form['global_container']['global']['cron'] = array( '#type' => 'fieldset', '#title' => t('Cron Options'), '#collapsible' => TRUE, '#collapsed' => TRUE, '#description' => t('Unless you have a good reason to adjust these values you should leave them alone.'), ); - // @ignore sniffer_squiz_commenting_poststatementcomment_found:27 + // @codingStandardsIgnoreStart $short_times = drupal_map_assoc(array( 60 * 15, // 15 min. 60 * 30, // 30 min. @@ -138,6 +394,7 @@ function advagg_admin_settings_form($form, $form_state) { 60 * 60 * 10, // 10 hours. 60 * 60 * 12, // 12 hours. 60 * 60 * 18, // 18 hours. + 60 * 60 * 23, // 23 hours. 60 * 60 * 24, // 1 day. 60 * 60 * 24 * 2, // 2 days. ), 'format_interval'); @@ -154,6 +411,7 @@ function advagg_admin_settings_form($form, $form_state) { 60 * 60 * 24 * 45, // 1 month 2 weeks. 60 * 60 * 24 * 60, // 2 months. ), 'format_interval'); + // @codingStandardsIgnoreEnd $last_ran = variable_get('advagg_cron_timestamp', 0); if (!empty($last_ran)) { $last_ran = t('@time ago', array('@time' => format_interval(REQUEST_TIME - $last_ran))); @@ -161,7 +419,8 @@ function advagg_admin_settings_form($form, $form_state) { else { $last_ran = t('never'); } - $form['global']['cron']['advagg_cron_frequency'] = array( + + $form['global_container']['global']['cron']['advagg_cron_frequency'] = array( '#type' => 'select', '#options' => $short_times, '#title' => 'Minimum amount of time between advagg_cron() runs.', @@ -171,21 +430,22 @@ function advagg_admin_settings_form($form, $form_state) { '%time' => $last_ran, )), ); - $form['global']['cron']['drupal_stale_file_threshold'] = array( + $form['global_container']['global']['cron']['drupal_stale_file_threshold'] = array( '#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))), ); - $form['global']['cron']['advagg_remove_missing_files_from_db_time'] = array( + $form['global_container']['global']['cron']['advagg_remove_missing_files_from_db_time'] = array( '#type' => 'select', '#options' => $long_times, '#title' => 'How long to wait until unaccessed aggregates with missing files are removed from the database.', '#default_value' => variable_get('advagg_remove_missing_files_from_db_time', ADVAGG_REMOVE_MISSING_FILES_FROM_DB_TIME), '#description' => t('The default value for this is %value.', array('%value' => format_interval(ADVAGG_REMOVE_MISSING_FILES_FROM_DB_TIME))), ); - $form['global']['cron']['advagg_remove_old_unused_aggregates_time'] = array( + $form['global_container']['global']['cron']['advagg_remove_old_unused_aggregates_time'] = array( '#type' => 'select', '#options' => $long_times, '#title' => 'How long to wait until unaccessed aggregates are removed from the database.', @@ -193,27 +453,45 @@ function advagg_admin_settings_form($form, $form_state) { '#description' => t('The default value for this is %value.', array('%value' => format_interval(ADVAGG_REMOVE_OLD_UNUSED_AGGREGATES_TIME))), ); - $form['global']['obscure'] = array( + $form['global_container']['global']['obscure'] = array( '#type' => 'fieldset', '#title' => t('Obscure Options'), '#collapsible' => TRUE, '#collapsed' => TRUE, '#description' => t('Some of the more obscure AdvAgg settings. Odds are you do not need to change anything in here.'), ); - $form['global']['obscure']['advagg_gzip'] = array( + $form['global_container']['global']['obscure']['advagg_gzip'] = array( '#type' => 'checkbox', '#title' => t('Create .gz files'), '#default_value' => variable_get('advagg_gzip', ADVAGG_GZIP), '#description' => t('All aggregated files can be pre-compressed into a .gz file and served from Apache. This is faster then gzipping the file on each request.'), ); - $form['global']['obscure']['advagg_ajax_render_alter'] = array( + $form['global_container']['global']['obscure']['advagg_brotli'] = array( + '#type' => 'checkbox', + '#title' => t('Create .br files'), + '#default_value' => variable_get('advagg_brotli', ADVAGG_BROTLI), + '#description' => t('All aggregated files can be pre-compressed into a .br file and + served from Apache. This is faster then brotli compressing the file on each request.'), + ); + if (!function_exists('brotli_compress') || !defined('BROTLI_TEXT')) { + $form['global_container']['global']['obscure']['advagg_brotli']['#default_value'] = FALSE; + $form['global_container']['global']['obscure']['advagg_brotli']['#disabled'] = TRUE; + $form['global_container']['global']['obscure']['advagg_brotli']['#description'] .= ' ' . t('The PHP brotli extension is needed in order to enable this feature.', array('@url' => 'https://github.com/kjdev/php-ext-brotli')); + if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') { + $form['global_container']['global']['obscure']['advagg_brotli']['#description'] .= ' ' . t('If running on windows this is a helpful guide on how to compile PHP extensions. Also noted that IIS has a Brotli extension,', array( + '@url' => 'https://wiki.php.net/internals/windows/stepbystepbuild', + '@iis' => 'https://www.iis.net/downloads/community/2016/03/iis-brotli', + )); + } + } + $form['global_container']['global']['obscure']['advagg_ajax_render_alter'] = array( '#type' => 'checkbox', '#title' => t('Run advagg_ajax_render_alter()'), '#default_value' => variable_get('advagg_ajax_render_alter', ADVAGG_AJAX_RENDER_ALTER), '#description' => t('If disabled, AdvAgg will not alter the aggregates returned by ajax requests.'), ); - $form['global']['obscure']['advagg_include_base_url'] = array( + $form['global_container']['global']['obscure']['advagg_include_base_url'] = array( '#type' => 'checkbox', '#title' => t('Include the base_url variable in the hooks hash array.'), '#default_value' => variable_get('advagg_include_base_url', ADVAGG_INCLUDE_BASE_URL), @@ -222,17 +500,17 @@ function advagg_admin_settings_form($form, $form_state) { '@issue' => 'https://www.drupal.org/node/2353811', )), ); - $form['global']['obscure']['advagg_convert_absolute_to_relative_path'] = array( + $form['global_container']['global']['obscure']['advagg_convert_absolute_to_relative_path'] = array( '#type' => 'checkbox', '#title' => t('Convert absolute paths to be self references.'), '#default_value' => variable_get('advagg_convert_absolute_to_relative_path', ADVAGG_CONVERT_ABSOLUTE_TO_RELATIVE_PATH), '#description' => t('If the src to a CSS/JS file points to this domain, convert the absolute path to a relative path. Will also convert url() references inside of css files. This is generally safe unless the drupal page will be embedded, like it is when used as an iframe.'), ); - $form['global']['obscure']['advagg_convert_absolute_to_protocol_relative_path'] = array( + $form['global_container']['global']['obscure']['advagg_convert_absolute_to_protocol_relative_path'] = array( '#type' => 'checkbox', '#title' => t('Convert absolute paths to be protocol relative paths.'), '#default_value' => variable_get('advagg_convert_absolute_to_protocol_relative_path', ADVAGG_CONVERT_ABSOLUTE_TO_PROTOCOL_RELATIVE_PATH), - '#description' => t('If the src to a CSS/JS file points starts with http:// or https://, convert it to use a protocol relative path //. Will also convert url() references inside of css files. This is incompatible with IE6 and might cause extra bandwidth usage in IE7 & IE8 in regards to url() statements inside css files. According to current web browser market share IE 6 is less than 0.2% of the total number of browsers in use & IE 7+8 make up under 5% of browsers in use.', array( + '#description' => t('If the src to a CSS/JS file points starts with http:// or https://, convert it to use a protocol relative path //. Will also convert url() references inside of css files. This is incompatible with IE6 and might cause extra bandwidth usage in IE7 and IE8 in regards to url() statements inside css files. According to current web browser market share IE 6 is less than 0.2% of the total number of browsers in use and IE 7+8 make up under 5% of browsers in use.', array( '@url' => 'http://www.w3counter.com/trends', )), '#states' => array( @@ -241,7 +519,7 @@ function advagg_admin_settings_form($form, $form_state) { ), ), ); - $form['global']['obscure']['advagg_force_https_path'] = array( + $form['global_container']['global']['obscure']['advagg_force_https_path'] = array( '#type' => 'checkbox', '#title' => t('Convert http:// to https://.'), '#default_value' => variable_get('advagg_force_https_path', ADVAGG_FORCE_HTTPS_PATH), @@ -252,14 +530,88 @@ function advagg_admin_settings_form($form, $form_state) { ), ), ); + $form['global_container']['global']['obscure']['advagg_css_absolute_path'] = array( + '#type' => 'checkbox', + '#title' => t('Convert CSS url() to absolute paths if file is outside of the public:// dir.'), + '#default_value' => variable_get('advagg_css_absolute_path', ADVAGG_CSS_ABSOLUTE_PATH), + '#description' => t('Useful for CSS files hosted on S3 where the referenced files inside of url() is not in the same location as the aggregated CSS file.'), + ); + $form['global_container']['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 will not convert relative paths in your source CSS to absolute paths in the aggregated CSS.'), + ); + $form['global_container']['global']['obscure']['advagg_htaccess_symlinksifownermatch'] = array( + '#type' => 'checkbox', + '#title' => t('Use "Options +SymLinksIfOwnerMatch"'), + '#default_value' => variable_get('advagg_htaccess_symlinksifownermatch', ADVAGG_HTACCESS_SYMLINKSIFOWNERMATCH), + '#description' => t('By default the custom .htaccess files are configured to use "Options +FollowSymLinks". Some hosting companies do not support this so "Options +SymLinksIfOwnerMatch" must be used instead.'), + ); + $form['global_container']['global']['obscure']['advagg_disable_on_admin'] = array( + '#type' => 'checkbox', + '#title' => t('Do not use AdvAgg or any aggregation in the admin section.'), + '#default_value' => variable_get('advagg_disable_on_admin', ADVAGG_DISABLE_ON_ADMIN), + '#description' => t('If checked, AdvAgg will not be used in the /admin section as well as any place the admin theme is used.'), + ); + $form['global_container']['global']['obscure']['advagg_disable_on_listed_pages'] = array( + '#type' => 'textarea', + '#title' => t('Do not use AdvAgg or any aggregation in the following pages'), + '#default_value' => variable_get('advagg_disable_on_listed_pages', ADVAGG_DISABLE_ON_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.", array( + '%blog' => 'blog', + '%blog-wildcard' => 'blog/*', + )), + ); + // Get core htaccess files. + $core_htaccess = advagg_htaccess_rewritebase(); + $advagg_htaccess_rewritebase = variable_get('advagg_htaccess_rewritebase', ADVAGG_HTACCESS_REWRITEBASE); + if (!empty($core_htaccess)) { + // Get advagg htaccess files. + list($css_path, $js_path) = advagg_get_root_files_dir(); + $advagg_css_htaccess = advagg_htaccess_rewritebase($css_path[1]); + $advagg_js_htaccess = advagg_htaccess_rewritebase($js_path[1]); + // Build best guess dir name. + $core_htaccess_directive = trim(str_ireplace('RewriteBase ', '', $core_htaccess)); + $new_dir = str_replace('//', '/', $core_htaccess_directive . '/' . dirname($css_path[1])); - $form['css'] = array( + $form['global_container']['global']['obscure']['advagg_htaccess_rewritebase'] = array( + '#type' => 'textfield', + '#title' => t('AdvAgg RewriteBase Directive in .htaccess files.'), + '#default_value' => $advagg_htaccess_rewritebase, + '#description' => t('If gzip and/or brotli files are getting a 307 instead of a 200, this might fix the issue. This Drupal install is using "@core_htaccess" in the webroot .htaccess file. Based off of this and the current location of the advagg css/js directories (@css_path, @js_path) this is the recommended value for this field:

@best_guess

Current advagg htaccess values:

@advagg_css_htaccess
@advagg_js_htaccess

Note that the advagg CSS/JS directories are automatically added onto the end of the given input value.', array( + '@core_htaccess' => $core_htaccess, + '@best_guess' => $new_dir, + '@css_path' => $css_path[1], + '@js_path' => $js_path[1], + '@advagg_css_htaccess' => $advagg_css_htaccess, + '@advagg_js_htaccess' => $advagg_js_htaccess, + )), + ); + } + elseif (!empty($advagg_htaccess_rewritebase)) { + list($css_path, $js_path) = advagg_get_root_files_dir(); + $advagg_css_htaccess = advagg_htaccess_rewritebase($css_path[1]); + $advagg_js_htaccess = advagg_htaccess_rewritebase($js_path[1]); + // Offer advice if this setting should be disabled. + $form['global_container']['global']['obscure']['advagg_htaccess_rewritebase'] = array( + '#type' => 'textfield', + '#title' => t('AdvAgg RewriteBase Directive in .htaccess files.'), + '#default_value' => $advagg_htaccess_rewritebase, + '#description' => t('This Drupal install is no longer using RewriteBase in the webroot .htaccess file. The recommended value for this field is a blank string (empty)
Current advagg htaccess values:

@advagg_css_htaccess
@advagg_js_htaccess

', array( + '@advagg_css_htaccess' => $advagg_css_htaccess, + '@advagg_js_htaccess' => $advagg_js_htaccess, + )), + ); + } + + $form['global_container']['css'] = array( '#type' => 'fieldset', '#title' => t('CSS Options'), ); - $form['css']['advagg_combine_css_media'] = array( + $form['global_container']['css']['advagg_combine_css_media'] = array( '#type' => 'checkbox', - '#title' => t('Combine CSS files by using media queries'), + '#title' => t('Combine CSS files by using media queries (recommended)'), '#default_value' => variable_get('advagg_combine_css_media', ADVAGG_COMBINE_CSS_MEDIA), '#description' => t('Will combine more CSS files together because different CSS media types can be used in the same file by using media queries. Use cores grouping logic needs to be unchecked in order for this to work. Also noted is that due to an issue with IE9, compatibility mode is forced off if this is enabled.'), '#states' => array( @@ -267,23 +619,25 @@ function advagg_admin_settings_form($form, $form_state) { '#edit-advagg-core-groups' => array('checked' => TRUE), ), ), + '#recommended_value' => TRUE, ); - $form['css']['advagg_ie_css_selector_limiter'] = array( + $form['global_container']['css']['advagg_ie_css_selector_limiter'] = array( '#type' => 'checkbox', '#title' => t('Prevent more than %limit CSS selectors in an aggregated CSS file', array('%limit' => variable_get('advagg_ie_css_selector_limiter_value', ADVAGG_IE_CSS_SELECTOR_LIMITER_VALUE))), '#default_value' => variable_get('advagg_ie_css_selector_limiter', ADVAGG_IE_CSS_SELECTOR_LIMITER), - '#description' => t('Internet Explorer before version 10; IE9, IE8, IE7, & IE6 all have 4095 as the limit for the maximum number of css selectors that can be in a file. Enabling this will prevent CSS aggregates from being created that exceed this limit. More info. Use cores grouping logic needs to be unchecked in order for this to work.', array('@link' => 'http://blogs.msdn.com/b/ieinternals/archive/2011/05/14/10164546.aspx')), + '#description' => t('Internet Explorer before version 10; IE9, IE8, IE7, and IE6 all have 4095 as the limit for the maximum number of css selectors that can be in a file. Enabling this will prevent CSS aggregates from being created that exceed this limit. More info. Use cores grouping logic needs to be unchecked in order for this to work.', array('@link' => 'http://blogs.msdn.com/b/ieinternals/archive/2011/05/14/10164546.aspx')), '#states' => array( 'disabled' => array( '#edit-advagg-core-groups' => array('checked' => TRUE), ), ), + '#recommended_value' => FALSE, ); - $form['css']['advagg_ie_css_selector_limiter_value'] = array( + $form['global_container']['css']['advagg_ie_css_selector_limiter_value'] = array( '#type' => 'textfield', '#title' => t('The selector count the IE CSS limiter should use'), '#default_value' => variable_get('advagg_ie_css_selector_limiter_value', ADVAGG_IE_CSS_SELECTOR_LIMITER_VALUE), - '#description' => t('Internet Explorer before version 10; IE9, IE8, IE7, & IE6 all have 4095 as the limit for the maximum number of css selectors that can be in a file. Use this field to modify the value used; 4095 sometimes may be still be too many with media queries.'), + '#description' => t('Internet Explorer before version 10; IE9, IE8, IE7, and IE6 all have 4095 as the limit for the maximum number of css selectors that can be in a file. Use this field to modify the value used; 4095 sometimes may be still be too many with media queries.'), '#states' => array( 'visible' => array( '#edit-advagg-ie-css-selector-limiter' => array('checked' => TRUE), @@ -293,26 +647,52 @@ function advagg_admin_settings_form($form, $form_state) { ), ), ); - $form['css']['advagg_css_fix_type'] = array( + $form['global_container']['css']['advagg_css_fix_type'] = array( '#type' => 'checkbox', - '#title' => t('Fix improperly set type'), + '#title' => t('Fix improperly set type (recommended)'), '#default_value' => variable_get('advagg_css_fix_type', ADVAGG_CSS_FIX_TYPE), '#description' => t('If type is external but does not start with http, https, or // change it to be type file. If type is file but it starts with http, https, or // change type to be external. Note that if this is causing issues, odds are you have a double slash when there should be a single; see this issue', array( '@link' => 'https://www.drupal.org/node/2336217', )), + '#recommended_value' => TRUE, + ); + $form['global_container']['css']['advagg_css_remove_empty_files'] = array( + '#type' => 'checkbox', + '#title' => t('Remove empty CSS files from aggregates (recommended)'), + '#default_value' => variable_get('advagg_css_remove_empty_files', ADVAGG_CSS_REMOVE_EMPTY_FILES), + '#description' => t('If an empty CSS file ends up being in its own aggregate, that aggregate can end up as a 404.'), + '#recommended_value' => TRUE, ); - $form['js'] = array( + $form['global_container']['js'] = array( '#type' => 'fieldset', '#title' => t('JS Options'), ); - $form['js']['advagg_js_fix_type'] = array( + $form['global_container']['js']['advagg_js_fix_type'] = array( '#type' => 'checkbox', - '#title' => t('Fix improperly set type'), + '#title' => t('Fix improperly set type (recommended)'), '#default_value' => variable_get('advagg_js_fix_type', ADVAGG_JS_FIX_TYPE), '#description' => t('If type is external but does not start with http, https, or // change it to be type file. If type is file but it starts with http, https, or // change type to be external. Note that if this is causing issues, odds are you have a double slash when there should be a single; see this issue', array( '@link' => 'https://www.drupal.org/node/2336217', )), + '#recommended_value' => TRUE, + ); + $form['global_container']['js']['advagg_js_remove_empty_files'] = array( + '#type' => 'checkbox', + '#title' => t('Remove empty JS files from aggregates (recommended)'), + '#default_value' => variable_get('advagg_js_remove_empty_files', ADVAGG_JS_REMOVE_EMPTY_FILES), + '#description' => t('If an empty JS file ends up being in its own aggregate, that aggregate can end up as a 404.'), + '#recommended_value' => TRUE, + ); + $form['global_container']['js']['advagg_scripts_scope_anywhere'] = array( + '#type' => 'checkbox', + '#title' => t('Allow for JS to be added to blocks and views'), + '#default_value' => variable_get('advagg_scripts_scope_anywhere', ADVAGG_SCRIPTS_SCOPE_ANYWHERE), + '#description' => t('The scope key when using drupal_add_js and friends can be used to insert script tags throughout the html body. To see possible insertion points set @theme_debug in your settings.php file and view the page source while looking for @comment html comments. Some example scopes:

!example

', array( + '@theme_debug' => '$conf[\'theme_debug\'] = TRUE;', + '@comment' => '', + '!example' => "page_top:prefix
block:prefix:system:powered-by
view:suffix:frontpage:page", + )), ); // Clear the cache bins on submit. @@ -323,11 +703,10 @@ function advagg_admin_settings_form($form, $form_state) { /** * Form builder; Do advagg operations. - * - * @ingroup forms */ function advagg_admin_operations_form($form, $form_state) { drupal_set_title(t('AdvAgg: Operations')); + advagg_display_message_if_requirements_not_met(); // Explain what can be done on this page. $form['tip'] = array( @@ -360,6 +739,7 @@ function advagg_admin_operations_form($form, $form_state) { 60 * 60 * 24 * 2, 60 * 60 * 24 * 7, 60 * 60 * 24 * 30, + 60 * 60 * 24 * 365, ), 'format_interval'); $form['bypass']['timespan'] = array( '#type' => 'select', @@ -374,16 +754,38 @@ function advagg_admin_operations_form($form, $form_state) { ); // Add in aggregation bypass cookie javascript. $form['#attached']['js'][] = array( - 'data' => array('advagg' => array('key' => drupal_hash_base64(drupal_get_private_key()))), + 'data' => array( + 'advagg' => array( + 'key' => drupal_hmac_base64('advagg_cookie', drupal_get_private_key() . drupal_get_hash_salt() . variable_get('cron_key', 'drupal')), + ), + ), 'type' => 'setting', ); $form['#attached']['js'][] = drupal_get_path('module', 'advagg') . '/advagg.admin.js'; + // Regenerate .htaccess files. + if (variable_get('advagg_htaccess_check_generate', ADVAGG_HTACCESS_CHECK_GENERATE)) { + list($css_path, $js_path) = advagg_get_root_files_dir(); + $form['htaccess'] = array( + '#type' => 'fieldset', + '#title' => t('Regenerate .htaccess files'), + '#description' => t('@advagg_css_htaccess
@advagg_js_htaccess', array( + '@advagg_css_htaccess' => $css_path[1] . '/.htaccess', + '@advagg_js_htaccess' => $js_path[1] . '/.htaccess', + )), + ); + $form['htaccess']['advagg_regenerate_htaccess'] = array( + '#type' => 'submit', + '#value' => t('Recreate htaccess files'), + '#submit' => array('advagg_admin_regenerate_htaccess_button'), + ); + } + // Tasks run by cron. $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 7 operations are ran on cron but you can run them manually here.'), ); $form['cron']['smart_file_flush'] = array( '#type' => 'fieldset', @@ -397,6 +799,33 @@ 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_empty_aggregates'] = array( + '#type' => 'fieldset', + '#collapsible' => TRUE, + '#collapsed' => TRUE, + '#title' => t('Delete empty aggregates'), + '#description' => t('Delete all aggregates that have no content so that they can be regenerated.'), + ); + $form['cron']['delete_empty_aggregates']['advagg_delete_empty_aggregates'] = array( + '#type' => 'submit', + '#value' => t('Delete empty aggregates'), + '#submit' => array('advagg_delete_empty_aggregates_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, @@ -409,6 +838,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, @@ -421,6 +851,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, @@ -430,10 +861,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', @@ -478,6 +937,98 @@ function advagg_admin_operations_form($form, $form_state) { '#value' => t('Increment Global Counter'), '#submit' => array('advagg_admin_increment_global_counter'), ); + $form['drastic_measures']['rescan_files'] = array( + '#type' => 'fieldset', + '#collapsible' => TRUE, + '#collapsed' => TRUE, + '#title' => t('Rescan all files'), + '#description' => t('Force rescan all files and clear the cache. Useful if a css/js change from a deployment did not work.'), + ); + $form['drastic_measures']['rescan_files']['reset_mtime'] = array( + '#type' => 'submit', + '#value' => t('Reset All mtime Values'), + '#submit' => array('advagg_admin_reset_mtime'), + ); + $form['drastic_measures']['remove_empty_advagg_files'] = array( + '#type' => 'fieldset', + '#collapsible' => TRUE, + '#collapsed' => TRUE, + '#title' => t('Remove deleted files in the advagg_files table'), + '#description' => t('Remove entries in the advagg_files table that have a filesize of 0 and delete the javascript_parsed variable. This gets around the grace period that the cron cleanup does.'), + ); + $form['drastic_measures']['remove_empty_advagg_files']['advagg_admin_remove_empty_advagg_files'] = array( + '#type' => 'submit', + '#value' => t('Remove deleted files from advagg_files'), + '#submit' => array('advagg_admin_remove_empty_advagg_files'), + ); + $form['drastic_measures']['reset_advagg_files'] = array( + '#type' => 'fieldset', + '#collapsible' => TRUE, + '#collapsed' => TRUE, + '#title' => t('Reset the AdvAgg Files table'), + '#description' => t('Truncate the advagg_files table and delete the javascript_parsed variable. This may cause some 404s for CSS/JS assets for a short amount of time (seconds). Useful if you really want to reset some stuff. Might be best to put the site into maintenance mode before doing this.'), + ); + $form['drastic_measures']['reset_advagg_files']['truncate_advagg_files'] = array( + '#type' => 'submit', + '#value' => t('Truncate advagg_files'), + '#submit' => array('advagg_admin_truncate_advagg_files'), + ); + + $form['drastic_measures']['clear_base_file'] = array( + '#type' => 'fieldset', + '#collapsible' => TRUE, + '#collapsed' => TRUE, + '#title' => t('Delete all aggregates containing this base file.'), + '#description' => t('Pinpoint clearing of all aggregates that contain a file.'), + ); + $form['drastic_measures']['clear_base_file']['filename'] = array( + '#type' => 'textfield', + '#size' => 170, + '#maxlength' => 256, + '#default_value' => '', + '#title' => t('Filename'), + ); + $form['drastic_measures']['clear_base_file']['submit'] = array( + '#type' => 'submit', + '#value' => t('Delete Select Aggregates'), + '#validate' => array('advagg_admin_clear_file_aggregate_validate'), + '#submit' => array('advagg_admin_clear_file_aggregate_submit'), + '#ajax' => array( + 'callback' => 'advagg_admin_clear_file_aggregate_callback', + 'wrapper' => 'advagg-clear-file-aggregate-ajax', + 'effect' => 'fade', + ), + ); + $types = array('css', 'js'); + $css_file = ''; + $js_file = ''; + foreach ($types as $type) { + // Get valid filename. + $results = db_select('advagg_files', 'af') + ->fields('af', array('filename')) + ->condition('filetype', $type) + ->orderBy('filename', 'ASC') + ->execute(); + while ($row = $results->fetchAssoc()) { + if (empty($css_file) && $type === 'css') { + $css_file = $row['filename']; + break; + } + if (empty($js_file) && $type === 'js') { + $js_file = $row['filename']; + break; + } + } + } + $form['drastic_measures']['clear_base_file']['tip'] = array( + '#markup' => '

' . t('Takes input like "@css_file" or "@advagg_js"', array( + '@css_file' => $css_file, + '@advagg_js' => $js_file, + )) . '

', + ); + $form['drastic_measures']['clear_base_file']['wrapper'] = array( + '#markup' => "
", + ); return $form; } @@ -485,36 +1036,66 @@ function advagg_admin_operations_form($form, $form_state) { /** * Form builder; Show info about advagg and advagg settings. * - * @ingroup forms - * * @see system_settings_form() */ function advagg_admin_info_form($form, $form_state) { drupal_set_title(t('AdvAgg: Information')); + advagg_display_message_if_requirements_not_met(); // Explain what can be done on this page. $form['tip'] = array( '#markup' => '

' . t('This page provides debugging information. There are no configuration options here.') . '

', ); - // Get all hooks & variables. + // Get all hooks and variables. drupal_theme_initialize(); $core_hooks = theme_get_registry(); $advagg_hooks = advagg_hooks_implemented(); list(, $js_path) = advagg_get_root_files_dir(); // Output html process functions hooks. - $form['info'] = array( + $form['theme_info'] = array( '#type' => 'fieldset', '#collapsible' => TRUE, '#collapsed' => TRUE, '#title' => t('Hook Theme Info'), ); $data = implode("\n", $core_hooks['html']['process functions']); - $form['info']['advagg_debug_info'] = array( + $form['theme_info']['advagg_theme_info'] = array( '#markup' => '
' . $data . '
', ); + $form['render_info'] = array( + '#type' => 'fieldset', + '#collapsible' => TRUE, + '#collapsed' => TRUE, + '#title' => t('Hook Render Info'), + ); + $element_info = array(); + $element_info['scripts'] = element_info('scripts'); + $element_info['styles'] = element_info('styles'); + $output = ''; + foreach ($element_info as $type => &$values) { + $output .= $type . ":\n"; + foreach ($values as $key => &$value) { + if ($key === '#items' || $key === '#type') { + continue; + } + if (empty($value)) { + $output .= ' ' . $key . ' - ' . str_replace(array("\r", "\n"), '', (string) var_export($value, TRUE)) . "\n"; + } + elseif (is_array($value)) { + $output .= ' ' . $key . ' - ' . implode(', ', $value) . "\n"; + } + else { + $output .= ' ' . $key . ' - ' . print_r($value, TRUE) . "\n"; + } + } + } + $form['render_info']['advagg_render_info'] = array( + '#markup' => '
' . $output . '
', + ); + // Get all parent css and js files. $types = array('css', 'js'); $css_file = ''; @@ -529,6 +1110,7 @@ function advagg_admin_info_form($form, $form_state) { $results = db_select('advagg_files', 'af') ->fields('af', array('filename', 'filename_hash', 'changes')) ->condition('filetype', $type) + ->orderBy('filename', 'ASC') ->execute(); while ($row = $results->fetchAssoc()) { if (empty($css_file) && $type === 'css') { @@ -643,10 +1225,11 @@ function advagg_admin_info_form($form, $form_state) { ), ); module_load_include('install', 'advagg', 'advagg'); + $first_file_path = (advagg_s3fs_evaluate_no_rewrite_cssjs(FALSE)) ? $js_path[0] : $js_path[1]; $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($first_file_path, 'js'), )) . '

', ); $form['get_info_about_agg']['wrapper'] = array( @@ -656,24 +1239,144 @@ function advagg_admin_info_form($form, $form_state) { return $form; } -// Submit callback. +/** + * 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'), + )); + $msg .= ' '; + 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', + )); + } + if (is_callable('omega_extension_enabled') + && is_callable('omega_theme_get_setting') + && omega_extension_enabled('development') + && omega_theme_get_setting('omega_livereload', TRUE) + ) { + $msg .= ' ' . t('The omega theme is in development mode and livereload is also enabled. This setting combination disables CSS aggregation. If you wish to have CSS aggregation turned on, go to the theme settings page, select the Omega theme/subtheme and turn off LiveReload and/or turn off the Development extension.', array('@url' => url('admin/appearance/settings'))); + } + + $form['bandwidth_optimization']['advagg_note'] = array( + '#markup' => $msg, + ); +} + +/** + * @} End of "defgroup advagg_forms". + */ + +/** + * @defgroup advagg_forms_callback AdvAgg forms callbacks and validation. + * @{ + * Functions that handle user input from forms. + */ + /** * 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); + // Remove non variables. + if (isset($form_state['values']['advagg_resource_hints_preload_reset'])) { + unset($form_state['values']['advagg_resource_hints_preload_reset']); } + + // Reset this form to defaults or recommended values; also show what changed. + advagg_set_admin_form_defaults_recommended($form_state, 'advagg_admin_mode'); + + // Sort and fix values before saving to the db. + foreach ($form_state['values']['advagg_resource_hints_preload_settings'] as &$entry) { + if (isset($entry['weight'])) { + $entry['#weight'] = $entry['weight']; + unset($entry['weight']); + } + ksort($entry); + } + uasort($form_state['values']['advagg_resource_hints_preload_settings'], 'element_sort'); + + // Make sure .htaccess file exists in the advagg dir. + if (isset($form_state['values']['advagg_resource_hints_use_immutable']) + && variable_get('advagg_resource_hints_use_immutable', ADVAGG_RESOURCE_HINTS_USE_IMMUTABLE) != $form_state['values']['advagg_resource_hints_use_immutable'] + && variable_get('advagg_htaccess_check_generate', ADVAGG_HTACCESS_CHECK_GENERATE) + ) { + // Set variable. + variable_set('advagg_resource_hints_use_immutable', $form_state['values']['advagg_resource_hints_use_immutable']); + unset($form_state['values']['advagg_resource_hints_use_immutable']); + + // Get paths to .htaccess file. + list($css_path, $js_path) = advagg_get_root_files_dir(); + $files['css'] = $css_path[0] . '/.htaccess'; + $files['js'] = $js_path[0] . '/.htaccess'; + + // Make the advagg_htaccess_check_generate() function available. + module_load_include('inc', 'advagg', 'advagg.missing'); + + // Generate new .htaccess files in advagg dirs. + foreach ($files as $type => $uri) { + advagg_htaccess_check_generate(array($uri => $type), $type, TRUE); + } + } + + // Alter htaccess if needed. + if (isset($form_state['values']['advagg_htaccess_rewritebase']) + && variable_get('advagg_htaccess_rewritebase', ADVAGG_HTACCESS_REWRITEBASE) != $form_state['values']['advagg_htaccess_rewritebase'] + && variable_get('advagg_htaccess_check_generate', ADVAGG_HTACCESS_CHECK_GENERATE) + ) { + // Set variable. + variable_set('advagg_htaccess_rewritebase', trim($form_state['values']['advagg_htaccess_rewritebase'])); + unset($form_state['values']['advagg_htaccess_rewritebase']); + + // Get paths to .htaccess file. + list($css_path, $js_path) = advagg_get_root_files_dir(); + $files['css'] = $css_path[0] . '/.htaccess'; + $files['js'] = $js_path[0] . '/.htaccess'; + + // Make the advagg_htaccess_check_generate() function available. + module_load_include('inc', 'advagg', 'advagg.missing'); + + // Generate new .htaccess files in advagg dirs. + foreach ($files as $type => $uri) { + advagg_htaccess_check_generate(array($uri => $type), $type, TRUE); + } + } + + if (variable_get('advagg_htaccess_symlinksifownermatch', ADVAGG_HTACCESS_SYMLINKSIFOWNERMATCH) != $form_state['values']['advagg_htaccess_symlinksifownermatch'] + && variable_get('advagg_htaccess_check_generate', ADVAGG_HTACCESS_CHECK_GENERATE) + ) { + list($css_path, $js_path) = advagg_get_root_files_dir(); + $files['css'] = $css_path[0] . '/.htaccess'; + $files['js'] = $js_path[0] . '/.htaccess'; + + // Make the advagg_htaccess_check_generate() function available. + module_load_include('inc', 'advagg', 'advagg.missing'); + + // Push out new setting. + variable_set('advagg_htaccess_symlinksifownermatch', $form_state['values']['advagg_htaccess_symlinksifownermatch']); + unset($form_state['values']['advagg_htaccess_symlinksifownermatch']); + foreach ($files as $type => $uri) { + advagg_htaccess_check_generate(array($uri => $type), $type, TRUE); + } + } + + // Clear caches. + advagg_cache_clear_admin_submit(); } -// Callbacks for buttons. /** * Set or remove the AdvAggDisabled cookie. */ function advagg_admin_toggle_bypass_cookie($form, &$form_state) { $cookie_name = 'AdvAggDisabled'; - $key = drupal_hash_base64(drupal_get_private_key()); + $key = drupal_hmac_base64('advagg_cookie', drupal_get_private_key() . drupal_get_hash_salt() . variable_get('cron_key', 'drupal')); // If the cookie does exist then remove it. if (!empty($_COOKIE[$cookie_name]) && $_COOKIE[$cookie_name] == $key) { @@ -694,6 +1397,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; } @@ -825,10 +1529,50 @@ function advagg_admin_get_file_info($filename) { return $output; } +/** + * Reset the advagg_resource_hints_preload_settings variable. + */ +function advagg_admin_resource_hints_preload_reset() { + variable_del('advagg_resource_hints_preload_settings'); +} + +/** + * Recreate the advagg htaccess files. + */ +function advagg_admin_regenerate_htaccess_button() { + // Get paths to .htaccess file. + list($css_path, $js_path) = advagg_get_root_files_dir(); + $files['css'] = $css_path[0] . '/.htaccess'; + $files['js'] = $js_path[0] . '/.htaccess'; + + // Make the advagg_htaccess_check_generate() function available. + module_load_include('inc', 'advagg', 'advagg.missing'); + + // Generate new .htaccess files in advagg dirs. + $errors = array(); + foreach ($files as $type => $uri) { + $errors += advagg_htaccess_check_generate(array($uri => $type), $type, TRUE); + } + if (empty($errors)) { + drupal_set_message(t('The .htaccess files have been regenerated.')); + } + else { + drupal_set_message(t('The .htaccess files failed to be regenerated. @errors', array('@errors' => implode(', ', $errors)))); + } +} + /** * 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(); @@ -849,10 +1593,11 @@ function advagg_admin_flush_cache_button() { 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( + 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. Trigger: @changes', array( '%filename' => $filename, '%db_usage' => $data[0], '%db_count' => $data[1], + '@changes' => print_r($data[2], TRUE), '%type' => $ext, ))); } @@ -868,6 +1613,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(); @@ -880,12 +1633,9 @@ function advagg_admin_clear_all_caches_button() { } /** - * Clear out all advagg cache bins and clear out all advagg aggregated files. + * Clear out all advagg aggregated files. */ function advagg_admin_clear_all_files_button() { - // Clear out the cache. - advagg_admin_clear_all_caches_button(); - // Run the command. module_load_include('inc', 'advagg', 'advagg.cache'); list($css_files, $js_files) = advagg_remove_all_aggregated_files(); @@ -917,6 +1667,26 @@ function advagg_admin_flush_stale_files_button() { } } +/** + * Delete all empty advagg aggregated files. + */ +function advagg_delete_empty_aggregates_button() { + // Run the command. + module_load_include('inc', 'advagg', 'advagg.cache'); + list($css_files, $js_files) = advagg_delete_empty_aggregates(); + + // Report back the results. + if (count($css_files) > 0 || count($js_files) > 0) { + drupal_set_message(t('All empty aggregates have been deleted. %css_count CSS files and %js_count JS files have been removed.', array( + '%css_count' => count($css_files), + '%js_count' => count($js_files), + ))); + } + else { + drupal_set_message(t('No empty aggregates found. Nothing was deleted.')); + } +} + /** * Clear out all advagg cache bins and increment the counter. */ @@ -930,6 +1700,69 @@ function advagg_admin_increment_global_counter() { drupal_set_message(t('Global counter is now set to %new_value', array('%new_value' => $new_value))); } +/** + * Clear out all advagg cache bins and increment the counter. + */ +function advagg_admin_reset_mtime() { + // Reset mtime. + db_update('advagg_files') + ->fields(array( + 'mtime' => 0, + )) + ->execute(); + drupal_set_message(t('All files have been rescanned.')); + + // Smart cache clear. + advagg_admin_flush_cache_button(); + + // Clear out the cache. + advagg_admin_clear_all_caches_button(); +} + +/** + * Remove filesize zero files from the advagg_files table and clear caches. + */ +function advagg_admin_remove_empty_advagg_files() { + // Remove dead files from the advagg_files table. + db_delete('advagg_files') + ->condition('filesize', 0) + ->execute(); + variable_del('javascript_parsed'); + drupal_set_message(t('All empty files in the advagg_files table have been removed.')); + + // Clear out the cache. + advagg_admin_clear_all_caches_button(); +} + +/** + * Truncate the advagg_files table and clear caches. + */ +function advagg_admin_truncate_advagg_files() { + // Truncate advagg_files table. + db_truncate('advagg_files')->execute(); + variable_del('javascript_parsed'); + drupal_set_message(t('All files from the advagg_files table have been removed.')); + + // Clear out the cache. + advagg_admin_clear_all_caches_button(); +} + +/** + * 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. */ @@ -974,6 +1807,159 @@ 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.')); + } +} + +/** + * Verify that the filename is correct. + */ +function advagg_admin_clear_file_aggregate_validate($form, &$form_state) { + if (empty($form_state['values']['filename'])) { + form_set_error('filename', t('Please input a filename.')); + $form_state['values']['error'] = TRUE; + } +} + +/** + * Display what files where deleted in a drupal message. + */ +function advagg_admin_clear_file_aggregate_submit($form, &$form_state) { + // @codingStandardsIgnoreLine + if (!empty($form_state['input']['ajax_page_state'])) { + return; + } + $info = advagg_admin_clear_file_aggregates($form_state['values']['filename']); + if (module_exists('httprl')) { + $output = httprl_pr($info); + } + else { + $output = '
' . check_plain(print_r($info, TRUE)) . '
'; + } + // @ignore security_dsm + drupal_set_message($output); +} + +/** + * Display what files where deleted via ajax callback. + */ +function advagg_admin_clear_file_aggregate_callback($form, &$form_state) { + if (!empty($form_state['values']['error'])) { + return '
'; + } + $info = advagg_admin_clear_file_aggregates($form_state['values']['filename']); + if (empty($info)) { + form_set_error('filename', t('Please input a valid filename.')); + return '
'; + } + else { + if (module_exists('httprl')) { + $output = httprl_pr($info); + } + else { + $output = '
' . print_r($info, TRUE) . '
'; + } + return '
' . $output . '
'; + } +} + +/** + * Remove the aggregates that contain the given filename. + * + * @param string $filename + * Name of file to lookup. Can be a comma separated list. + * @param bool $dry_run + * If TRUE, return the regex search string. + * + * @return array + * Returns an array of the parent file and what children where deleted. + */ +function advagg_admin_clear_file_aggregates($filename, $dry_run = FALSE) { + module_load_include('inc', 'advagg', 'advagg.cache'); + list($css_path, $js_path) = advagg_get_root_files_dir(); + $space = ADVAGG_SPACE; + $output = array(); + $options = array('callback' => 'file_unmanaged_delete'); + if ($dry_run) { + $options = array(); + } + + // Strip quotes and trim. + $filenames = array_map('trim', explode(',', trim(str_replace(array('"', "'"), '', $filename)))); + + foreach ($filenames as $filename) { + $results = db_select('advagg_files', 'af') + ->fields('af') + ->condition('filename', $filename) + ->execute(); + while ($row = $results->fetchAssoc()) { + // Get aggregates that use this file. + $row['aggregates_using_file'] = advagg_get_aggregates_using_file($row['filename_hash']); + // Get dir and other info from file. + if ($row['filetype'] === 'css') { + $dirname = $css_path[0]; + $basename_prefix = "{$row['filetype']}"; + } + if ($row['filetype'] === 'js') { + $dirname = $js_path[0]; + $basename_prefix = "{$row['filetype']}"; + } + + // Build regex search string for file_scan_directory(). + $regex_search = array(); + foreach ($row['aggregates_using_file'] as $values) { + $regex_search[] = preg_quote("{$basename_prefix}{$space}{$values['aggregate_filenames_hash']}{$space}") . '.*'; + } + $regex_search = array_unique($regex_search); + $regex_search_string = '/(' . implode('|', $regex_search) . ')/'; + $files = file_scan_directory($dirname, $regex_search_string, $options); + + // List what files were deleted. + $row['aggregates_deleted'] = array(); + $files_deleted = array_keys($files); + if (!empty($files_deleted)) { + $row['aggregates_deleted'][] = $files_deleted; + } + + $output[$filename] = $row['aggregates_deleted']; + } + } + + return $output; +} + +/** + * @} End of "defgroup advagg_forms_callback". + */ diff --git a/sites/all/modules/contrib/advagg/advagg.admin.js b/sites/all/modules/contrib/advagg/advagg.admin.js index 8fb0fe7826..5a4e29ec2e 100644 --- a/sites/all/modules/contrib/advagg/advagg.admin.js +++ b/sites/all/modules/contrib/advagg/advagg.admin.js @@ -1,80 +1,83 @@ -/** global Drupal:false */ - /** * @file * Used to toggle the AdvAgg Bypass Cookie client side. */ +/* global Drupal:false */ +/* eslint-disable no-unused-vars */ + /** * 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) { - "use strict"; +Drupal.formatInterval = function (interval, granularity, langcode) { + 'use strict'; granularity = typeof granularity !== 'undefined' ? granularity : 2; langcode = typeof langcode !== 'undefined' ? langcode : null; var output = ''; + /* eslint-disable key-spacing */ while (granularity > 0) { var value = 0; if (interval >= 31536000) { value = 31536000; - output += (output.length ? ' ' : '') + Drupal.formatPlural(Math.floor(interval / value), '1 year', '@count years', { langcode : langcode }); + output += (output.length ? ' ' : '') + Drupal.formatPlural(Math.floor(interval / value), '1 year', '@count years', {langcode : langcode}); } else if (interval >= 2592000) { value = 2592000; - output += (output.length ? ' ' : '') + Drupal.formatPlural(Math.floor(interval / value), '1 month', '@count months', { langcode : langcode }); + output += (output.length ? ' ' : '') + Drupal.formatPlural(Math.floor(interval / value), '1 month', '@count months', {langcode : langcode}); } else if (interval >= 604800) { value = 604800; - output += (output.length ? ' ' : '') + Drupal.formatPlural(Math.floor(interval / value), '1 week', '@count weeks', { langcode : langcode }); + output += (output.length ? ' ' : '') + Drupal.formatPlural(Math.floor(interval / value), '1 week', '@count weeks', {langcode : langcode}); } else if (interval >= 86400) { value = 86400; - output += (output.length ? ' ' : '') + Drupal.formatPlural(Math.floor(interval / value), '1 day', '@count days', { langcode : langcode }); + output += (output.length ? ' ' : '') + Drupal.formatPlural(Math.floor(interval / value), '1 day', '@count days', {langcode : langcode}); } else if (interval >= 3600) { value = 3600; - output += (output.length ? ' ' : '') + Drupal.formatPlural(Math.floor(interval / value), '1 hour', '@count hours', { langcode : langcode }); + output += (output.length ? ' ' : '') + Drupal.formatPlural(Math.floor(interval / value), '1 hour', '@count hours', {langcode : langcode}); } else if (interval >= 60) { value = 60; - output += (output.length ? ' ' : '') + Drupal.formatPlural(Math.floor(interval / value), '1 min', '@count min', { langcode : langcode }); + output += (output.length ? ' ' : '') + Drupal.formatPlural(Math.floor(interval / value), '1 min', '@count min', {langcode : langcode}); } else if (interval >= 1) { value = 1; - output += (output.length ? ' ' : '') + Drupal.formatPlural(Math.floor(interval / value), '1 sec', '@count sec', { langcode : langcode }); + output += (output.length ? ' ' : '') + Drupal.formatPlural(Math.floor(interval / value), '1 sec', '@count sec', {langcode : langcode}); } interval %= value; granularity--; } - return output.length ? output : Drupal.t('0 sec', {}, { langcode : langcode }); -} + return output.length ? output : Drupal.t('0 sec', {}, {langcode : langcode}); + /* eslint-enable key-spacing */ +}; /** * 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){ - "use strict"; +function advagg_is_unicode(str) { + 'use strict'; for (var i = 0, n = str.length; i < n; i++) { if (str.charCodeAt(i) > 255) { return true; @@ -86,12 +89,12 @@ 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. */ function advagg_toggle_cookie() { - "use strict"; + 'use strict'; // Fallback to submitting the form for Unicode domains like ".рф". if (advagg_is_unicode(document.location.hostname)) { return true; @@ -104,21 +107,24 @@ function advagg_toggle_cookie() { // If the cookie does exist then remove it. if (cookie_pos !== -1) { - document.cookie = cookie_name + '=;' - + 'expires=Thu, 01 Jan 1970 00:00:00 GMT;' - + ' path=' + Drupal.settings.basePath + ';' - + ' domain=.' + document.location.hostname + ';'; + document.cookie = + cookie_name + '=' + + '; expires=Thu, 01 Jan 1970 00:00:00 GMT' + + '; path=' + Drupal.settings.basePath + + '; domain=.' + document.location.hostname + ';'; alert(Drupal.t('AdvAgg Bypass Cookie Removed')); } // If the cookie does not exist then set it. else { - var bypass_length = document.getElementById('edit-timespan').value, expire_date = new Date(new Date().getTime() + bypass_length * 1000); + var bypass_length = document.getElementById('edit-timespan').value; + var expire_date = new Date(new Date().getTime() + bypass_length * 1000); - document.cookie = cookie_name + '=' + Drupal.settings.advagg.key + ';' - + ' expires=' + expire_date.toGMTString() + ';' - + ' path=' + Drupal.settings.basePath + ';' - + ' domain=.' + document.location.hostname + ';'; - alert(Drupal.t('AdvAgg Bypass Cookie Set for @time.', {'@time' : Drupal.formatInterval(bypass_length)})); + document.cookie = + cookie_name + '=' + Drupal.settings.advagg.key + + '; expires=' + expire_date.toGMTString() + + '; path=' + Drupal.settings.basePath + + '; domain=.' + document.location.hostname + ';'; + alert(Drupal.t('AdvAgg Bypass Cookie Set for @time.', {'@time': Drupal.formatInterval(bypass_length)})); } // Must return false, if returning true then form gets submitted. diff --git a/sites/all/modules/contrib/advagg/advagg.advagg.inc b/sites/all/modules/contrib/advagg/advagg.advagg.inc index feab39ac72..bd6aba3802 100644 --- a/sites/all/modules/contrib/advagg/advagg.advagg.inc +++ b/sites/all/modules/contrib/advagg/advagg.advagg.inc @@ -7,65 +7,122 @@ * File used to store hook_advagg_* hooks. */ -// @ignore sniffer_commenting_functioncomment_hookparamdoc:11 +/** + * @addtogroup advagg_hooks + * @{ + */ + /** * Implements hook_advagg_save_aggregate_alter(). * * Used to add in a .gz file if none exits. - * - * @param array $files_to_save - * Array($uri => $contents). - * @param array $aggregate_settings - * Array of settings. - * @param array $other_parameters - * Array of containing $files & $type. */ function advagg_advagg_save_aggregate_alter(array &$files_to_save, array $aggregate_settings, array $other_parameters) { - // Return if gzip is disabled. - if (empty($aggregate_settings['variables']['advagg_gzip'])) { + // * @param array $files_to_save + // * Array($uri => $contents). + // * @param array $aggregate_settings + // * Array of settings. + // * @param array $other_parameters + // * Array of containing $files and $type. + $file_types = array(); + // Handle gzip. + if (!empty($aggregate_settings['variables']['advagg_gzip'])) { + // Use zopfli_encode if it exists. + // See https://github.com/kjdev/php-ext-zopfli + if (function_exists('zopfli_encode') + && defined('ZOPFLI_GZIP') + && empty($aggregate_settings['variables']['advagg_no_zopfli']) + ) { + $file_types['.gz'] = array('zopfli_encode', 15, constant('ZOPFLI_GZIP')); + } + else { + $file_types['.gz'] = array('gzencode', 9, FORCE_GZIP); + } + } + // Handle brotli. + // See https://github.com/kjdev/php-ext-brotli + if (!empty($aggregate_settings['variables']['advagg_brotli']) + && defined('BROTLI_TEXT') + && function_exists('brotli_compress') + ) { + $file_types['.br'] = array('brotli_compress', 11, constant('BROTLI_TEXT')); + } + + if (empty($file_types)) { return; } - // See if a .gz file already exists. - $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 (!empty($pos)) { - $len = strlen($uri); - // .gz file exists, exit loop. - if ($pos == $len - 3) { - $gzip_exists = TRUE; - break; - } + // Special S3 handling. + $s3fs = FALSE; + $files_to_save_keys = array_keys($files_to_save); + foreach ($files_to_save_keys as $uri) { + $wrapper = file_stream_wrapper_get_instance_by_uri($uri); + if ($wrapper && get_class($wrapper) === 'S3fsStreamWrapper') { + $s3fs = TRUE; + break; } } - // If a .gz file does not exist, create one. - if (!$gzip_exists) { - // Use the first file in the array. - $data = reset($files_to_save); - $uri = key($files_to_save); - // Compress it and add it to the $files_to_save array. - $compressed = gzencode($data, 9, FORCE_GZIP); - $files_to_save[$uri . '.gz'] = $compressed; + foreach ($file_types as $ext => $settings) { + // See if a file already exists with this extension. + $ext_exists = FALSE; + foreach ($files_to_save as $uri => $contents) { + // See if this uri contains $ext near the end of it. + if (strlen($uri) > 91 + strlen(ADVAGG_SPACE) * 3) { + $pos = strripos($uri, $ext, 91 + strlen(ADVAGG_SPACE) * 3); + } + else { + $pos = strripos($uri, $ext); + } + if (!empty($pos)) { + $len = strlen($uri); + // $ext file exists, exit loop. + if ($pos == $len - 3) { + $ext_exists = TRUE; + break; + } + } + } + + // If a $ext file does not exist, create one. + if (!$ext_exists) { + // Use the first file in the array. + $data = reset($files_to_save); + $uri = key($files_to_save); + // Compress it and add it to the $files_to_save array. + $callback = $settings[0]; + $settings[0] = $data; + $compressed = call_user_func_array($callback, $settings); + if (!empty($compressed)) { + if ($s3fs && $ext === '.gz') { + // Only serve gzip files from S3. + $files_to_save[$uri] = $compressed; + } + elseif ($s3fs && $ext === '.br' && !isset($file_types['.gz'])) { + // Only serve br files from S3. + $files_to_save[$uri] = $compressed; + } + else { + $files_to_save[$uri . $ext] = $compressed; + } + } + } } } -// @ignore sniffer_commenting_functioncomment_hookparamdoc:10 /** * Implements hook_advagg_build_aggregate_plans_alter(). * * Used to alter the plan so it has the same grouping as cores. - * - * @param array $files - * List of files in the aggregate as well as the aggregate name. - * @param bool $modified - * Change this to TRUE if $files has been changed. - * @param string $type - * String containing css or js. */ function advagg_advagg_build_aggregate_plans_alter(array &$files, &$modified, $type) { + // * @param array $files + // * List of files in the aggregate as well as the aggregate name. + // * @param bool $modified + // * Change this to TRUE if $files has been changed. + // * @param string $type + // * String containing css or js. + // // Do nothing if core grouping is disabled. if (!variable_get('advagg_core_groups', ADVAGG_CORE_GROUPS)) { return; @@ -77,7 +134,7 @@ function advagg_advagg_build_aggregate_plans_alter(array &$files, &$modified, $t $group = NULL; $every_page = NULL; foreach ($data['files'] as $fileinfo) { - // Grouped by group & every_page variables. + // Grouped by group and every_page variables. if (is_null($group)) { $group = $fileinfo['group']; } @@ -101,7 +158,6 @@ function advagg_advagg_build_aggregate_plans_alter(array &$files, &$modified, $t $files = advagg_generate_filenames(array($temp_new_files), $type); } - /** * Implements hook_advagg_context_alter(). */ @@ -117,13 +173,12 @@ function advagg_advagg_context_alter(&$original, $aggregate_settings, $mode) { $GLOBALS['is_https'] = $aggregate_settings['variables']['is_https']; if ($aggregate_settings['variables']['is_https']) { $GLOBALS['base_root'] = str_replace('http://', 'https://', $GLOBALS['base_root']); - $GLOBALS['base_url'] = str_replace('http://', 'https://', $GLOBALS['base_url']); } else { $GLOBALS['base_root'] = str_replace('https://', 'http://', $GLOBALS['base_root']); - $GLOBALS['base_url'] = str_replace('https://', 'http://', $GLOBALS['base_url']); } $GLOBALS['base_path'] = $aggregate_settings['variables']['base_path']; + $GLOBALS['base_url'] = rtrim($GLOBALS['base_root'] . $GLOBALS['base_path'], '/'); if (isset($aggregate_settings['variables']['language'])) { $languages = language_list(); @@ -214,32 +269,103 @@ function advagg_advagg_context_alter(&$original, $aggregate_settings, $mode) { * Used to make sure the info is up to date in the cache. */ function advagg_advagg_get_info_on_files_alter(&$return, $cached_data, $bypass_cache) { - // Do nothing if the ie_css_selector_limiter is disabled. - if (!variable_get('advagg_ie_css_selector_limiter', ADVAGG_IE_CSS_SELECTOR_LIMITER)) { - return; - } - $limit_value = variable_get('advagg_ie_css_selector_limiter_value', ADVAGG_IE_CSS_SELECTOR_LIMITER_VALUE); - - // Get the css path. - list($css_path) = advagg_get_root_files_dir(); - $parts_path = $css_path[1] . '/parts'; + // Check for the ie_css_selector_limiter. + if (variable_get('advagg_ie_css_selector_limiter', ADVAGG_IE_CSS_SELECTOR_LIMITER)) { + $limit_value = variable_get('advagg_ie_css_selector_limiter_value', ADVAGG_IE_CSS_SELECTOR_LIMITER_VALUE); + + // Get the css path. + list($css_path) = advagg_get_root_files_dir(); + $css_parts_path = (advagg_s3fs_evaluate_no_rewrite_cssjs(FALSE)) ? $css_path[0] : $css_path[1]; + $parts_path = $css_parts_path . '/parts/'; + + foreach ($return as &$info) { + // Skip if not a css file. + if (empty($info['fileext']) || $info['fileext'] !== 'css') { + continue; + } - foreach ($return as &$info) { - // Skip if not a css file. - if (empty($info['fileext']) || $info['fileext'] !== 'css') { - continue; + // Check if this is a split css file. + if (strpos($info['data'], $parts_path) !== FALSE) { + $info['split'] = TRUE; + } + // Break large file into multiple small files if needed. + elseif ($info['linecount'] > $limit_value) { + advagg_split_css_file($info); + } } + unset($info); + } - // Check if this is a split css file. - if (strpos($info['data'], $parts_path) === 0) { - $info['split'] = TRUE; - } - // Break large file into multiple small files if needed. - elseif ($info['linecount'] > $limit_value) { - advagg_split_css_file($info); + // Capture resource_hints. + if (variable_get('advagg_resource_hints_dns_prefetch', ADVAGG_RESOURCE_HINTS_DNS_PREFETCH) + || variable_get('advagg_resource_hints_preconnect', ADVAGG_RESOURCE_HINTS_PRECONNECT) + || variable_get('advagg_resource_hints_preload', ADVAGG_RESOURCE_HINTS_PRELOAD) + ) { + $aggregate_settings = advagg_current_hooks_hash_array(); + foreach ($return as &$info) { + // Skip if not a css file. + if (empty($info['fileext']) || $info['fileext'] !== 'css') { + continue; + } + // Get the file contents. + $file_contents = (string) @advagg_file_get_contents($info['data']); + $file_contents = advagg_load_css_stylesheet($info['data'], FALSE, $aggregate_settings, $file_contents); + + // Get domain names and external assets in this css file. + $hosts = array(); + $urls = array(); + $matches = array(); + $pattern = '%url\(\s*+[\'"]?+(http:\/\/|https:\/\/|\/\/)([^\'"()\s]++)[\'"]?+\s*+\)%i'; + preg_match_all($pattern, $file_contents, $matches); + if (!empty($matches[1])) { + foreach ($matches[1] as $key => $match) { + $url = $match . $matches[2][$key]; + $parse = @parse_url($url); + if (!empty($parse['host'])) { + $extra = ''; + $ext = strtolower(pathinfo($url, PATHINFO_EXTENSION)); + $supported = array( + 'eot', + 'woff2', + 'woff', + 'ttf', + 'otf', + ); + if (in_array($ext, $supported)) { + $extra .= '#crossorigin'; + } + $hosts[$parse['host'] . $extra] = $match . $parse['host'] . '/' . $extra; + $urls[$url] = $url; + } + } + } + if (!empty($hosts) && (variable_get('advagg_resource_hints_dns_prefetch', ADVAGG_RESOURCE_HINTS_DNS_PREFETCH) + || variable_get('advagg_resource_hints_preconnect', ADVAGG_RESOURCE_HINTS_PRECONNECT) + )) { + $info['dns_prefetch'] = array_values($hosts); + } + + // Get local files to preload in this css file. + if (variable_get('advagg_resource_hints_preload', ADVAGG_RESOURCE_HINTS_PRELOAD)) { + $matches = array(); + $pattern = '/url\(\s*+[\'"]?(.*?)[\'"\s]?+\)/i'; + preg_match_all($pattern, $file_contents, $matches); + if (!empty($matches[1])) { + foreach ($matches[1] as $key => $match) { + $url = advagg_convert_abs_to_rel($match); + $parse = @parse_url($url); + if (empty($parse['host'])) { + $urls[$url] = $url; + } + } + } + if (!empty($urls)) { + $info['preload'] = array_values($urls); + } + } } + unset($info); } - unset($info); } /** @@ -247,13 +373,16 @@ function advagg_advagg_get_info_on_files_alter(&$return, $cached_data, $bypass_c * * If the color module is enabled regenerate color module css when it changes. * If a responsive file inside an adaptive theme has changed, regenerate it. - * - * @param array $files - * List of files that have changed. - * @param array $types - * Array with the css and or the js key. */ function advagg_advagg_changed_files(array $files, array $types) { + // * @param array $files + // * List of files that have changed. + // * @param array $types + // * Array with the css and or the js key. + if (module_exists('locale')) { + _advagg_locale_changed_files($files, $types); + } + // Keep track of what themes have been done. static $themes_done; if (!isset($themes_done)) { @@ -265,17 +394,16 @@ function advagg_advagg_changed_files(array $files, array $types) { return; } - $return = array(); foreach ($files as $filename => $meta_data) { // Only care about css files. - $ext = pathinfo($filename, PATHINFO_EXTENSION); - if ($ext != 'css') { + $ext = strtolower(pathinfo($filename, PATHINFO_EXTENSION)); + if ($ext !== 'css') { continue; } - $return[$filename] = advagg_advagg_scan_for_changes($filename, TRUE); + advagg_advagg_scan_for_changes($filename, TRUE); } - // Save error states & clear them. + // Save error states and clear them. $errors_before = drupal_static('form_set_error', array()); form_clear_error(); @@ -294,7 +422,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; @@ -396,7 +524,7 @@ function advagg_advagg_changed_files(array $files, array $types) { */ function advagg_advagg_scan_for_changes($filename, $save_changes = FALSE) { // Skip if this file is not a css file. - $ext = pathinfo($filename, PATHINFO_EXTENSION); + $ext = strtolower(pathinfo($filename, PATHINFO_EXTENSION)); if ($ext !== 'css') { return FALSE; } @@ -414,7 +542,7 @@ function advagg_advagg_scan_for_changes($filename, $save_changes = FALSE) { return; } - $this_file_changed = FALSE; + $file_changed = array(); foreach ($adaptivethemes as $theme_name => $path) { // Set up some paths we use to get and save files. $path_to_responsive_css = drupal_get_path('theme', $theme_name) . '/css/'; @@ -441,12 +569,58 @@ function advagg_advagg_scan_for_changes($filename, $save_changes = FALSE) { } // See if anything has changed. - if (advagg_detect_subfile_changes($filename, $file_map[$filename], 'adaptivetheme', $save_changes)) { - $this_file_changed = TRUE; + $changes = advagg_detect_subfile_changes($filename, $file_map[$filename], 'adaptivetheme', $save_changes); + if (!empty($changes)) { + $file_changed[$path] = $changes; } } - return $this_file_changed; + return $file_changed; +} + +/** + * @} End of "addtogroup advagg_hooks". + */ + +/** + * 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 _advagg_locale_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 = strtolower(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; + _advagg_locale_js_alter($javascript); + } + } + $GLOBALS['language'] = $language_before; + } } /** diff --git a/sites/all/modules/contrib/advagg/advagg.api.php b/sites/all/modules/contrib/advagg/advagg.api.php index f081ffe213..b0e68d0d91 100644 --- a/sites/all/modules/contrib/advagg/advagg.api.php +++ b/sites/all/modules/contrib/advagg/advagg.api.php @@ -6,8 +6,14 @@ */ /** - * @addtogroup hooks + * @defgroup advagg_hooks Advanced Aggregates Hooks + * * @{ + * Hooks for modules to implement to extend or modify Advanced Aggregates. + * + * For more examples of use see most of the Advanced Agregrates sub modules. + * + * @see https://api.drupal.org/api/drupal/includes%21module.inc/group/hooks/7.x */ /** @@ -33,10 +39,13 @@ function hook_advagg_build_aggregate_plans_alter(array &$files, &$modified, $typ $temp_new_files = array(); $counter = 0; foreach ($files as $filename => $data) { + if ($filename) { + // This is the filename. + } $group = NULL; $every_page = NULL; foreach ($data['files'] as $fileinfo) { - // Grouped by group & every_page variables. + // Grouped by group and every_page variables. if (is_null($group)) { $group = $fileinfo['group']; } @@ -83,7 +92,7 @@ function hook_advagg_changed_files(array $files, array $types) { $return = array(); foreach ($files as $filename => $meta_data) { // Only care about js files. - $ext = pathinfo($filename, PATHINFO_EXTENSION); + $ext = strtolower(pathinfo($filename, PATHINFO_EXTENSION)); if ($ext !== 'js') { continue; } @@ -116,7 +125,7 @@ function hook_advagg_current_hooks_hash_array_alter(array &$aggregate_settings) * @param array $aggregate_settings * Array of settings. * @param array $other_parameters - * Array of containing $files & $type. + * Array of containing $files and $type. * * @see advagg_save_aggregate() * @see advagg_advagg_save_aggregate_alter() @@ -340,8 +349,7 @@ function hook_advagg_css_groups_alter(array &$css_groups, $preprocess_css) { } else { $diff = array_merge(array_diff_assoc($group['browsers'], $target['browsers']), array_diff_assoc($target['browsers'], $group['browsers'])); - // @ignore sniffer_whitespace_openbracketspacing_openingwhitespace - if ( $group['type'] != $target['type'] + if ($group['type'] != $target['type'] || $group['group'] != $target['group'] || $group['every_page'] != $target['every_page'] || $group['media'] != $target['media'] @@ -351,8 +359,7 @@ function hook_advagg_css_groups_alter(array &$css_groups, $preprocess_css) { ) { if (!empty($last_group)) { $diff = array_merge(array_diff_assoc($last_group['browsers'], $target['browsers']), array_diff_assoc($target['browsers'], $last_group['browsers'])); - // @ignore sniffer_whitespace_openbracketspacing_openingwhitespace - if ( $last_group['type'] != $target['type'] + if ($last_group['type'] != $target['type'] || $last_group['group'] != $target['group'] || $last_group['every_page'] != $target['every_page'] || $last_group['media'] != $target['media'] @@ -427,7 +434,7 @@ function hook_advagg_js_groups_alter(array &$js_groups, $preprocess_js) { } /** - * Allow other modules to modify $children & $elements before they are rendered. + * Allow other modules to modify $children and $elements before rendering. * * @param array $children * An array of children elements. @@ -462,7 +469,7 @@ function hook_advagg_modify_css_pre_render_alter(array &$children, array &$eleme module_load_include('inc', 'advagg_css_compress', 'advagg_css_compress.advagg'); if ($compressor == 2) { // Compress any inline CSS with YUI. - foreach ($children as $key => &$values) { + foreach ($children as &$values) { if (!empty($values['#value'])) { advagg_css_compress_yui_cssmin($values['#value']); } @@ -472,7 +479,7 @@ function hook_advagg_modify_css_pre_render_alter(array &$children, array &$eleme } /** - * Allow other modules to modify $children & $elements before they are rendered. + * Allow other modules to modify $children and $elements before rendering. * * @param array $children * An array of children elements. @@ -507,7 +514,7 @@ function hook_advagg_modify_js_pre_render_alter(array &$children, array &$elemen // Compress any inline JS. module_load_include('inc', 'advagg_js_compress', 'advagg_js_compress.advagg'); - foreach ($children as $key => &$values) { + foreach ($children as &$values) { if (!empty($values['#value'])) { $contents = $values['#value']; $filename = drupal_hash_base64($contents); @@ -577,6 +584,9 @@ function hook_advagg_context_alter(array &$original, array $aggregate_settings, */ function hook_advagg_removed_aggregates(array $kill_list) { foreach ($kill_list as $uri) { + if ($uri) { + // This is the uri. + } // Do something else. } } @@ -621,9 +631,15 @@ function hook_advagg_get_info_on_files_alter(array &$return, array $cached_data, } $limit_value = variable_get('advagg_ie_css_selector_limiter_value', ADVAGG_IE_CSS_SELECTOR_LIMITER_VALUE); list($css_path, $js_path) = advagg_get_root_files_dir(); + if ($js_path) { + // This is the js_path array. + } $parts_path = $css_path[1] . '/parts'; foreach ($return as $filename => &$info) { + if ($filename) { + // This is the filename. + } if (empty($info['fileext']) || $info['fileext'] !== 'css') { continue; } @@ -666,6 +682,10 @@ function hook_advagg_hooks_implemented_alter(array &$hooks, $all) { */ function hook_advagg_bundler_analysis_alter(array &$analysis) { foreach ($analysis as $filename => &$data) { + if ($filename) { + // This is the filename. + } + // This changes often; 604800 is 1 week. if ($data['changes'] > 10 && $data['mtime'] >= REQUEST_TIME - 604800) { // Modify the group hash so this doesn't end up in a big aggregate. @@ -676,5 +696,5 @@ function hook_advagg_bundler_analysis_alter(array &$analysis) { } /** - * @} End of "addtogroup hooks". + * @} End of "defgroup advagg_hooks". */ diff --git a/sites/all/modules/contrib/advagg/advagg.cache.inc b/sites/all/modules/contrib/advagg/advagg.cache.inc index 90827684c6..8d1d1586c0 100644 --- a/sites/all/modules/contrib/advagg/advagg.cache.inc +++ b/sites/all/modules/contrib/advagg/advagg.cache.inc @@ -7,7 +7,6 @@ * Functions used for clearing caches and killing files. */ -// Cache and file flushing. /** * Uses the database to scan CSS/JS files for changes. * @@ -44,25 +43,30 @@ function advagg_scan_for_changes() { 'content_hash', 'linecount', ); - $changed = FALSE; + $changed = array(); foreach ($keys_to_compare as $key) { if ($row[$key] != $info[$key]) { - $changed = TRUE; + $changed[] = $key . ' db:' . $row[$key] . ' file:' . $info[$key]; break; } } // Compare mtime if it is not zero. - if (!$changed && empty($info['split']) && !empty($info['mtime']) && $row['mtime'] != $info['mtime']) { - $changed = TRUE; + if (empty($info['split']) && !empty($info['mtime'])) { + if (variable_get('advagg_strict_mtime_check', ADVAGG_STRICT_MTIME_CHECK) && $row['mtime'] != $info['mtime']) { + $changed[] = 'mtime db:' . $row['mtime'] . ' file:' . $info['mtime']; + } + elseif ($row['mtime'] < $info['mtime']) { + $changed[] = 'mtime db:' . $row['mtime'] . ' file:' . $info['mtime']; + } } - if (!$changed) { + if (empty($changed)) { // Call hook_advagg_scan_for_changes(). $changes_array = module_invoke_all('advagg_scan_for_changes', $row['filename']); if (is_array($changes_array)) { foreach ($changes_array as $value) { if (!empty($value)) { - $changed = TRUE; + $changed[] = $value; break; } } @@ -70,7 +74,8 @@ function advagg_scan_for_changes() { } // If file has changed, add it to the array. - if ($changed) { + if (!empty($changed)) { + $info['changes'] = $changed; $files_that_have_changed[$row['filename']] = $info; } } @@ -85,16 +90,27 @@ function advagg_scan_for_changes() { * @return array * Array of files that have changed and caches flushed. */ -function advagg_push_new_changes() { +function advagg_push_new_changes(array $files = array()) { + $results = array(); + // Scan the file system for changes to CSS/JS files. + if (empty($files)) { + $files = advagg_scan_for_changes(); + if (variable_get('advagg_debug', ADVAGG_DEBUG) >= 2) { + watchdog('advagg-debug', 'Changes detected in
@files
.', array('@files' => print_r($files, TRUE)), WATCHDOG_DEBUG); + } + } + // Clear some static caches. drupal_static_reset('advagg_get_info_on_file'); drupal_static_reset('advagg_drupal_hash_base64'); drupal_static_reset('advagg_current_hooks_hash_array'); drupal_static_reset('advagg_get_current_hooks_hash'); - // Scan the file system for changes to CSS/JS files. - $files = advagg_scan_for_changes(); - $results = array(); + if (variable_get('advagg_debug', ADVAGG_DEBUG) >= 2) { + // Exception used to get a compact stack trace. + $e = new Exception(); + watchdog('advagg-debug', 'New changes called by:
@changes
', array('@changes' => print_r($e->getTraceAsString(), TRUE)), WATCHDOG_DEBUG); + } // If something changed, flush the correct caches so that change goes out. if (!empty($files)) { @@ -104,17 +120,31 @@ function advagg_push_new_changes() { // Lookup the aggregates/cache ids that use this file. $cache_ids = advagg_get_aggregates_using_file($meta_data['filename_hash'], TRUE); $cache_hits = array(); - $ext = pathinfo($filename, PATHINFO_EXTENSION); + $ext = strtolower(pathinfo($filename, PATHINFO_EXTENSION)); $types[$ext] = TRUE; if (!empty($cache_ids)) { $cache_hits = cache_get_multiple($cache_ids, 'cache_advagg_info'); foreach ($cache_hits as $cid => $data) { + if (variable_get('advagg_debug', ADVAGG_DEBUG) >= 2) { + watchdog('advagg-debug', 'Clearing cache @cid.', array('@cid' => $cid), WATCHDOG_DEBUG); + } cache_clear_all($cid, 'cache_advagg_info', FALSE); } } - $results[$filename] = array(count($cache_ids), count($cache_hits)); + $changes = array(); + if (!empty($meta_data['changes'])) { + $changes = $meta_data['changes']; + unset($meta_data['changes']); + } + + $results[$filename] = array( + count($cache_ids), + count($cache_hits), + $changes, + ); + // Update database. advagg_insert_update_files(array($filename => $meta_data), $ext); } @@ -129,6 +159,9 @@ function advagg_push_new_changes() { // Clear out the full aggregates cache. foreach ($types as $ext => $bool) { + if (variable_get('advagg_debug', ADVAGG_DEBUG) >= 2) { + watchdog('advagg-debug', 'Clearing cache advagg:@ext: in cache_advagg_aggregates.', array('@ext' => print_r($ext, TRUE)), WATCHDOG_DEBUG); + } cache_clear_all('advagg:' . $ext . ':', 'cache_advagg_aggregates', TRUE); } } @@ -184,7 +217,7 @@ function advagg_get_aggregates_using_file($filename_hash, $cid_only = FALSE) { */ function advagg_get_all_files(array $options = array()) { list($css_path, $js_path) = advagg_get_root_files_dir(); - $options += array('nomask' => '/(\.\.?|CVS|\.gz)$/'); + $options += array('nomask' => '/(\.\.?|CVS|\.gz|\.br)$/'); // Get a list of files. $css_files = file_scan_directory($css_path[0], '/.*/', $options); @@ -193,7 +226,7 @@ function advagg_get_all_files(array $options = array()) { } /** - * Scan CSS/JS advagg dir & remove that file if atime is grater than 30 days. + * Scan CSS/JS advagg dir and remove that file if atime is grater than 30 days. * * @return array * Array of files that got removed. @@ -230,32 +263,65 @@ function advagg_delete_files_if_stale(array $files) { } else { // Can not get data on file, remove it. - file_unmanaged_delete($uri); - if (file_exists($uri . '.gz')) { - file_unmanaged_delete($uri . '.gz'); - } - $kill_list[] = $uri; + $kill_list[] = advagg_delete_file_by_uri($uri); continue; } // Get atime of file. $atime = advagg_get_atime($aggregate_filenames_hash, $aggregate_contents_hash, $uri); if (empty($atime)) { - file_unmanaged_delete($uri); - if (file_exists($uri . '.gz')) { - file_unmanaged_delete($uri . '.gz'); - } - $kill_list[] = $uri; + $kill_list[] = advagg_delete_file_by_uri($uri); continue; } // Default stale file threshold is 30 days. if (REQUEST_TIME - $atime > variable_get('drupal_stale_file_threshold', 2592000)) { - file_unmanaged_delete($uri); - if (file_exists($uri . '.gz')) { - file_unmanaged_delete($uri . '.gz'); - } - $kill_list[] = $uri; + $kill_list[] = advagg_delete_file_by_uri($uri); + continue; + } + + } + // Let other modules know about the removed files. + // Call hook_advagg_removed_aggregates(). + module_invoke_all('advagg_removed_aggregates', $kill_list); + return $kill_list; +} + +/** + * Scan CSS/JS advagg dir and remove that file if it is empty. + * + * @return array + * Array of files that got removed. + */ +function advagg_delete_empty_aggregates() { + list($css_files, $js_files) = advagg_get_all_files(); + $css_files = advagg_delete_files_if_empty($css_files); + $js_files = advagg_delete_files_if_empty($js_files); + return array($css_files, $js_files); +} + +/** + * Given an array of files remove that file if it is empty. + * + * @param array $files + * Array of files returned by file_scan_directory. + * + * @return array + * Array of files that got removed. + */ +function advagg_delete_files_if_empty(array $files) { + // Array used to record what files were deleted. + $kill_list = array(); + + foreach ($files as $uri => $file) { + // Ignore temp files. There's a separate process for cleaning those up. + if (strpos($uri, '/advagg_file_') !== FALSE) { + continue; + } + $size = filesize($uri); + if ($size === 0) { + $kill_list[] = advagg_delete_file_by_uri($uri); + continue; } } // Let other modules know about the removed files. @@ -264,6 +330,28 @@ function advagg_delete_files_if_stale(array $files) { return $kill_list; } +/** + * Delete a file, and any compressed versions. + * + * @param string $uri + * URI of the file to delete. + * + * @return string + * The given URI. + */ +function advagg_delete_file_by_uri($uri) { + if (file_exists($uri)) { + file_unmanaged_delete($uri); + } + if (file_exists($uri . '.gz')) { + file_unmanaged_delete($uri . '.gz'); + } + if (file_exists($uri . '.br')) { + file_unmanaged_delete($uri . '.br'); + } + return $uri; +} + /** * Perform a cache_clear_all on all bins returned by advagg_flush_caches(TRUE). * @@ -292,6 +380,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) { @@ -361,7 +453,7 @@ function advagg_remove_missing_files_from_db() { $types = array(); foreach ($missing_files as $filename => $data) { // Setup this run. - $ext = pathinfo($filename, PATHINFO_EXTENSION); + $ext = strtolower(pathinfo($filename, PATHINFO_EXTENSION)); $advagg_files_del = 0; $advagg_aggregates_del = 0; $advagg_aggregates_versions_del = 0; @@ -402,12 +494,17 @@ function advagg_remove_missing_files_from_db() { ->execute(); // Add info to array. - $types[$ext] = TRUE; - $deleted[$filename] = array( - 'advagg_files' => $advagg_files_del, - 'advagg_aggregates_versions' => $advagg_aggregates_versions_del, - 'advagg_aggregates' => $advagg_aggregates_del, - ); + if (!empty($advagg_files_del) + || !empty($advagg_aggregates_versions_del) + || !empty($advagg_aggregates_del) + ) { + $types[$ext] = TRUE; + $deleted[$filename] = array( + 'advagg_files' => $advagg_files_del, + 'advagg_aggregates_versions' => $advagg_aggregates_versions_del, + 'advagg_aggregates' => $advagg_aggregates_del, + ); + } } } @@ -423,7 +520,7 @@ function advagg_remove_missing_files_from_db() { } /** - * Scan CSS/JS advagg dir & remove file if there is no associated db record. + * Scan CSS/JS advagg dir and remove file if there is no associated db record. * * @return array * Array of files that got removed. @@ -450,7 +547,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 +566,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. @@ -505,14 +602,13 @@ function advagg_delete_files_if_orphaned(array $files) { if (!empty($kill_list)) { foreach ($kill_list as $uri) { - if (file_exists($uri)) { - file_unmanaged_delete($uri); - if (file_exists($uri . '.gz')) { - file_unmanaged_delete($uri . '.gz'); - } - } + advagg_delete_file_by_uri($uri); } } + + // Let other modules know about the removed files. + // Call hook_advagg_removed_aggregates(). + module_invoke_all('advagg_removed_aggregates', $kill_list); return $kill_list; } @@ -531,7 +627,7 @@ function advagg_remove_old_unused_aggregates() { $query = db_select('advagg_aggregates_versions', 'aav') ->fields('aav', array('aggregate_filenames_hash')) ->groupBy('aav.aggregate_filenames_hash'); - // Create join & add in query comment. + // Create join and add in query comment. $query->leftjoin('advagg_aggregates', 'aa', 'aa.aggregate_filenames_hash=aav.aggregate_filenames_hash'); $query->isNull('aa.aggregate_filenames_hash'); $query->comment('Query called from ' . __FUNCTION__ . '()'); @@ -555,7 +651,7 @@ function advagg_remove_old_unused_aggregates() { $query = db_select('advagg_aggregates', 'aa') ->fields('aa', array('aggregate_filenames_hash')) ->groupBy('aa.aggregate_filenames_hash'); - // Create join & add in query comment. + // Create join and add in query comment. $query->leftjoin('advagg_aggregates_versions', 'aav', 'aa.aggregate_filenames_hash=aav.aggregate_filenames_hash'); $query->isNull('aav.aggregate_filenames_hash'); $query->comment('Query called from ' . __FUNCTION__ . '()'); @@ -590,11 +686,197 @@ 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') + ->condition('af.filesize', 0, '>') + ->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. * * @param string $filename - * Name of the file that is related to teh subfiles. + * Name of the file that is related to the subfiles. * @param array $subfiles * An array of files to check for changes. * @param string $keyname @@ -615,7 +897,7 @@ function advagg_detect_subfile_changes($filename, array $subfiles, $keyname, $sa $info[$keyname] = advagg_get_hash_settings($hash_id); } - $subfile_changed = FALSE; + $subfile_changed = array(); // Check every subfile seeing if they have changed. foreach ($subfiles as $subfile) { $current_file_info = $defaults = array( @@ -631,22 +913,22 @@ function advagg_detect_subfile_changes($filename, array $subfiles, $keyname, $sa // Get the current info on the file. if (file_exists($subfile)) { $current_file_info = array( - 'hash' => drupal_hash_base64(file_get_contents($subfile)), + 'hash' => drupal_hash_base64((string) @advagg_file_get_contents($subfile)), 'size' => filesize($subfile), 'mtime' => filemtime($subfile), ); } - // Set the info incase a save happens. + // Set the info in case a save happens. $info[$keyname][$subfile] = $current_file_info; // Check for any differences. $diff = array_diff_assoc($saved_file_info, $current_file_info); if (!empty($diff)) { - $subfile_changed = TRUE; + $subfile_changed[$subfile] = $diff; } } - if ($subfile_changed && $save_changes) { + if (!empty($subfile_changed) && $save_changes) { $cache_id = 'advagg:file:' . $info['filename_hash']; // Set static cache. diff --git a/sites/all/modules/contrib/advagg/advagg.developer-documentation.php b/sites/all/modules/contrib/advagg/advagg.developer-documentation.php new file mode 100644 index 0000000000..f49d8ac0fb --- /dev/null +++ b/sites/all/modules/contrib/advagg/advagg.developer-documentation.php @@ -0,0 +1,41 @@ + advagg_get_global_counter())); + return dt('Force the creation of all new aggregates by incrementing a global counter. Current value of counter: @value. This is useful if a CDN has cached an aggregate incorrectly as it will force new ones to be used even if nothing else has changed.', array('@value' => advagg_get_global_counter())); } } @@ -68,6 +73,10 @@ function advagg_drush_command() { return $items; } +/** + * @} End of "addtogroup 3rd_party_hooks". + */ + /** * Callback function for drush advagg-force-new-aggregates. * @@ -137,29 +146,65 @@ function drush_advagg_cron() { // Output results from running advagg_delete_stale_aggregates(). list($css_files, $js_files) = $output[0]; if (count($css_files) > 0 || count($js_files) > 0) { - drush_log(dt('All stale aggregates have been deleted. %css_count CSS files and %js_count JS files have been removed.', array( - '%css_count' => count($css_files), - '%js_count' => count($js_files), + drush_log(dt('All stale aggregates have been deleted. @css_count CSS files and @js_count JS files have been removed.', array( + '@css_count' => count($css_files), + '@js_count' => count($js_files), )), 'ok'); } else { 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 +212,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(); @@ -187,11 +240,12 @@ function drush_advagg_smart_cache_flush() { continue; } $ext = pathinfo($filename, PATHINFO_EXTENSION); - drush_log(dt('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' => $data[0], - '%db_count' => $data[1], - '%type' => $ext, + drush_log(dt('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. Trigger: @changes', array( + '@filename' => $filename, + '@db_usage' => $data[0], + '@db_count' => $data[1], + '@changes' => print_r($data[2], TRUE), + '@type' => $ext, )), 'ok'); } diff --git a/sites/all/modules/contrib/advagg/advagg.inc b/sites/all/modules/contrib/advagg/advagg.inc index 1707d2a5f7..7db438b363 100644 --- a/sites/all/modules/contrib/advagg/advagg.inc +++ b/sites/all/modules/contrib/advagg/advagg.inc @@ -7,11 +7,10 @@ * These functions are needed for cache misses. */ -// Database operations. /** * Insert/Update data in advagg tables. * - * Tables: advagg_files, advagg_aggregates, & advagg_aggregates_versions. + * Tables: advagg_files, advagg_aggregates, advagg_aggregates_versions. * * @param array $files * List of files in the aggregate as well as the aggregate name. @@ -75,7 +74,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 +132,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 +174,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( @@ -186,50 +187,246 @@ function advagg_insert_update_files(array $files, $type) { 'mtime' => $file_meta_data['mtime'], 'linecount' => $file_meta_data['linecount'], ); + try { + // Check the file in the database. + if (empty($files_in_db[$filename])) { + // 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); + } + } - // Check the file in the database. - if (empty($files_in_db[$filename])) { - // Insert into database. - $record['changes'] = 1; + // Insert into database. + $record['changes'] = 1; - $return = db_merge('advagg_files') - ->key(array( - 'filename_hash' => $record['filename_hash'], + $return = db_merge('advagg_files') + ->key(array( + 'filename_hash' => $record['filename_hash'], )) - ->insertFields($record) - ->execute(); + ->insertFields($record) + ->execute(); + if ($return) { + if (variable_get('advagg_debug', ADVAGG_DEBUG) >= 2) { + watchdog('advagg-debug', 'Inserting into db
@record
.', array('@record' => print_r($record, TRUE)), WATCHDOG_DEBUG); + } + $write_done = TRUE; + } + } + else { + // Take changes counter out of the diff equation. + $changes = $files_in_db[$filename]['changes']; + unset($files_in_db[$filename]['changes']); + // If not in strict mode, only use mtime if newer than the existing one. + if (!variable_get('advagg_strict_mtime_check', ADVAGG_STRICT_MTIME_CHECK)) { + // Make sure mtime only moves forward. + if ($record['mtime'] <= $files_in_db[$filename]['mtime']) { + $record['mtime'] = $files_in_db[$filename]['mtime']; + } + } + + // If something is different, update. + $diff = array_diff_assoc($record, $files_in_db[$filename]); + if (!empty($diff)) { + $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; + } + } + } - if ($return) { - $write_done = TRUE; + $return = db_merge('advagg_files') + ->key(array( + 'filename_hash' => $diff['filename_hash'], + )) + ->fields($diff) + ->execute(); + if ($return) { + if (variable_get('advagg_debug', ADVAGG_DEBUG) >= 2) { + watchdog('advagg-debug', 'Updating db
@diff
.', array('@diff' => print_r($diff, TRUE)), WATCHDOG_DEBUG); + } + $write_done = TRUE; + } + } } } + 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)); + } + } + } + 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)) { + $files[$type][$filename] = strlen(gzencode($contents, 9, FORCE_GZIP)); + } else { - // Take changes counter out of the diff equation. - $changes = $files_in_db[$filename]['changes']; - unset($files_in_db[$filename]['changes']); + $files[$type][$filename] = 0; + } + } + return $files[$type][$filename]; +} - // If something is different, update. - $diff = array_diff_assoc($record, $files_in_db[$filename]); - if (!empty($diff)) { - $diff['changes'] = $changes + 1; - $diff['filename_hash'] = $record['filename_hash']; +/** + * 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(); - $return = db_merge('advagg_files') - ->key(array( - 'filename_hash' => $diff['filename_hash'], - )) - ->updateFields($diff) - ->execute(); - if ($return) { - $write_done = TRUE; + $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";. + // Trim the JS down to 24kb. + $length = variable_get('advagg_js_header_length', ADVAGG_JS_HEADER_LENGTH); + $header = advagg_get_js_header($contents, $length); + + // Look for the string. + $use_strict = stripos($header, '"use strict";'); + $strict_js = FALSE; + if ($use_strict === FALSE) { + $use_strict = stripos($header, "'use strict';"); + } + if ($use_strict !== FALSE) { + if ($use_strict == 0) { + $strict_js = TRUE; + } + else { + // Get all text before "use strict";. + $substr = substr($header, 0, $use_strict); + // Check if there are any comments. + $single_line_comment = strpos($substr, '//'); + $multi_line_comment = strpos($substr, '/*'); + $in_function = strpos($substr, '{'); + if ($single_line_comment !== FALSE || $multi_line_comment !== FALSE) { + // Remove js comments and try again. + advagg_remove_js_comments($header); + + // Look for the string. + $use_strict = stripos($header, '"use strict";'); + if ($use_strict === FALSE) { + $use_strict = stripos($header, "'use strict';"); + } + // Get all text before "use strict"; with comments removed. + $substr = substr($header, 0, $use_strict); + // Check if there is a function before use strict. + $in_function = strpos($substr, '{'); + } + if ($in_function === FALSE) { + $strict_js = TRUE; } } } + + $files[$filename] = $strict_js; } - return $write_done; + return $files[$filename]; +} + +/** + * Read only the first 8192 bytes to get the file header. + * + * @param string $content + * JS string to cut. + * @param int $length + * The number of bytes to grab. See advagg_js_header_length variable. + * + * @return string + * The shortened JS string. + */ +function advagg_get_js_header($content, $length) { + $content = trim($content); + // Only grab the first X bytes. + if (function_exists('mb_strcut')) { + $header = mb_strcut($content, 0, $length); + } + else { + $header = substr($content, 0, $length); + } + + return $header; +} + +/** + * Remove comments from JavaScript. + * + * @param string $content + * JS string to minify. + */ +function advagg_remove_js_comments(&$content) { + // Remove comments. + $content = preg_replace('/(?:(?:\/\*(?:[^*]|(?:\*+[^*\/]))*\*+\/)|(?:(?@finfo', array( + '@finfo' => var_export($info, TRUE), + )); + } } // Get filesystem data. $files_info = advagg_get_info_on_files($files_info_filenames); foreach ($files_with_meta_data as $info) { + // Skip if not a string or key doesn't exist. + if (!is_string($info['data']) || !array_key_exists($info['data'], $files_info)) { + continue; + } + $filename = $info['data']; $info += $files_info[$filename]; // Skip if file doesn't exist. @@ -333,12 +542,11 @@ function &advagg_load_files_info_into_static_cache(array $files) { // Get the static cache of this data. $cached_data = &drupal_static('advagg_get_info_on_file'); - // Get that staticly cached data for all the given files. + // Get the statically cached data for all the given files. $cache_ids = array(); foreach ($files as $file) { $cache_id = 'advagg:file:' . advagg_drupal_hash_base64($file); - // @ignore sniffer_whitespace_openbracketspacing_openingwhitespace - if ( !empty($cached_data) + if (!empty($cached_data) && !empty($cached_data[$cache_id]) ) { // Make sure the cache_id is included. @@ -377,8 +585,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]; @@ -415,8 +623,7 @@ function advagg_get_info_on_files(array $files, $bypass_cache = FALSE, $run_alte $filename_hash = advagg_drupal_hash_base64($file); $cache_id = 'advagg:file:' . $filename_hash; // If we are not bypassing the cache add cached data. - // @ignore sniffer_whitespace_openbracketspacing_openingwhitespace - if ( $bypass_cache == FALSE + if ($bypass_cache == FALSE && is_array($cached_data) && array_key_exists($cache_id, $cached_data) ) { @@ -428,11 +635,11 @@ 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); } - // Return filename_hash & data. Empty values for the other keys. + // Return filename_hash and data. Empty values for the other keys. $return[$file] = array( 'filesize' => 0, 'mtime' => 0, @@ -447,11 +654,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) @advagg_file_get_contents($file); - $ext = pathinfo($file, PATHINFO_EXTENSION); + $ext = strtolower(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) @@ -466,8 +673,7 @@ function advagg_get_info_on_files(array $files, $bypass_cache = FALSE, $run_alte if ($ext === 'css') { // Get the number of selectors. - // http://stackoverflow.com/a/12567381/125684 - $linecount = preg_match_all('/\{.+?\}|,/s', $file_contents, $matched); + $linecount = advagg_count_css_selectors($file_contents); } else { // Get the number of lines. @@ -476,7 +682,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), @@ -499,8 +705,7 @@ function advagg_get_info_on_files(array $files, $bypass_cache = FALSE, $run_alte foreach ($return as $info) { // If no cache is empty add/update the cached entry. // Update the cache if it is new or something changed. - // @ignore sniffer_whitespace_openbracketspacing_openingwhitespace - if ( empty($info['#no_cache']) + if (empty($info['#no_cache']) && !empty($info['cache_id']) && (empty($cached_data[$info['cache_id']]) || $info !== $cached_data[$info['cache_id']]) ) { @@ -593,17 +798,18 @@ function advagg_clearstatcache($filename = NULL) { } } -// Modify CSS/JS arrays. /** * Group the CSS/JS into the biggest buckets possible. * * @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; @@ -613,16 +819,30 @@ function advagg_generate_groups(array $files_to_aggregate) { $async = ''; $cache = ''; $scope = ''; + $use_strict = 0; $browsers = array(); $selector_count = 0; // Get CSS limit value. $limit_value = variable_get('advagg_ie_css_selector_limiter_value', ADVAGG_IE_CSS_SELECTOR_LIMITER_VALUE); - if (variable_get('advagg_ie_css_selector_limiter', ADVAGG_IE_CSS_SELECTOR_LIMITER)) { + if (variable_get('advagg_ie_css_selector_limiter', ADVAGG_IE_CSS_SELECTOR_LIMITER) + || variable_get('advagg_resource_hints_dns_prefetch', ADVAGG_RESOURCE_HINTS_DNS_PREFETCH) + || variable_get('advagg_resource_hints_preconnect', ADVAGG_RESOURCE_HINTS_PRECONNECT) + || variable_get('advagg_resource_hints_preload', ADVAGG_RESOURCE_HINTS_PRELOAD) + ) { + $filenames = array(); foreach ($files_to_aggregate as $data) { foreach ($data as $values) { foreach ($values['items'] as $file_info) { - $filenames[] = $file_info['data']; + if (!empty($file_info['data']) && is_string($file_info['data'])) { + $filenames[] = $file_info['data']; + } + else { + watchdog('advagg', 'Bad data key. File info: @finfo Group info: @ginfo', array( + '@finfo' => var_export($file_info, TRUE), + '@ginfo' => var_export($values, TRUE), + )); + } } } } @@ -631,6 +851,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) { @@ -642,9 +877,10 @@ function advagg_generate_groups(array $files_to_aggregate) { // changed from the previous run of this loop. $changed = FALSE; $ext = isset($file_info['fileext']) ? $file_info['fileext'] : pathinfo($file_info['data'], PATHINFO_EXTENSION); + $ext = strtolower($ext); 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']) @@ -673,7 +909,6 @@ function advagg_generate_groups(array $files_to_aggregate) { $media = ''; } } - if (isset($file_info['browsers'])) { // Browsers changed. $diff = array_merge(array_diff_assoc($file_info['browsers'], $browsers), array_diff_assoc($browsers, $file_info['browsers'])); @@ -688,6 +923,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; @@ -729,7 +974,11 @@ function advagg_generate_groups(array $files_to_aggregate) { $scope = ''; } - if (variable_get('advagg_ie_css_selector_limiter', ADVAGG_IE_CSS_SELECTOR_LIMITER)) { + if (variable_get('advagg_ie_css_selector_limiter', ADVAGG_IE_CSS_SELECTOR_LIMITER) + && array_key_exists('data', $file_info) + && is_string($file_info['data']) + && array_key_exists($file_info['data'], $files_info) + ) { $file_info += $files_info[$file_info['data']]; // Prevent CSS rules exceeding 4095 due to limits with IE9 and below. if ($ext === 'css') { @@ -746,6 +995,37 @@ function advagg_generate_groups(array $files_to_aggregate) { } } + // Merge in dns_prefetch. + if ((variable_get('advagg_resource_hints_dns_prefetch', ADVAGG_RESOURCE_HINTS_DNS_PREFETCH) + || variable_get('advagg_resource_hints_preconnect', ADVAGG_RESOURCE_HINTS_PRECONNECT)) + && isset($files_info[$file_info['data']]['dns_prefetch']) + ) { + if (!isset($file_info['dns_prefetch'])) { + $file_info['dns_prefetch'] = array(); + } + if (!empty($file_info['dns_prefetch']) && is_string($file_info['dns_prefetch'])) { + $temp = $file_info['dns_prefetch']; + unset($file_info['dns_prefetch']); + $file_info['dns_prefetch'] = array($temp); + } + $file_info['dns_prefetch'] = array_filter(array_unique(array_merge($file_info['dns_prefetch'], $files_info[$file_info['data']]['dns_prefetch']))); + } + + // Merge in preload. + if (variable_get('advagg_resource_hints_preload', ADVAGG_RESOURCE_HINTS_PRELOAD) + && isset($files_info[$file_info['data']]['preload']) + ) { + if (!isset($file_info['preload'])) { + $file_info['preload'] = array(); + } + if (!empty($file_info['preload']) && is_string($file_info['preload'])) { + $temp = $file_info['preload']; + unset($file_info['preload']); + $file_info['preload'] = array($temp); + } + $file_info['preload'] = array_filter(array_unique(array_merge($file_info['preload'], $files_info[$file_info['data']]['preload']))); + } + // If one of the above options changed, it needs to be in a different // aggregate. if (!empty($parts)) { @@ -774,193 +1054,278 @@ function advagg_generate_groups(array $files_to_aggregate) { * * @param array $file_info * File info array from advagg_get_info_on_file(). + * @param string $file_contents + * CSS file contents. * * @return array - * array array with advagg_get_info_on_file data & split data. + * Array with advagg_get_info_on_file data and split data. */ -function advagg_split_css_file(array $file_info) { +function advagg_split_css_file(array $file_info, $file_contents = '') { // Make advagg_parse_media_blocks() available. module_load_include('inc', 'advagg', 'advagg.missing'); // Get the CSS file and break up by media queries. - $file_contents = file_get_contents($file_info['data']); + if (empty($file_contents)) { + $file_contents = (string) @advagg_file_get_contents($file_info['data']); + } $media_blocks = advagg_parse_media_blocks($file_contents); - // Get 98% of the advagg_ie_css_selector_limiter_value; usually 4013. - $selector_split_value = (int) max(floor(variable_get('advagg_ie_css_selector_limiter_value', ADVAGG_IE_CSS_SELECTOR_LIMITER_VALUE) * 0.98), 100); + // Get the advagg_ie_css_selector_limiter_value. + $selector_limit = (int) max(variable_get('advagg_ie_css_selector_limiter_value', ADVAGG_IE_CSS_SELECTOR_LIMITER_VALUE), 100); + + // Group media queries together. $part_selector_count = 0; - $major_chunks = array(); $counter = 0; - // Group media queries together. + $values = array(); foreach ($media_blocks as $media_block) { - $matched = array(); // Get the number of selectors. - // http://stackoverflow.com/a/12567381/125684 - $selector_count = preg_match_all('/\{.+?\}|,/s', $media_block, $matched); - $part_selector_count += $selector_count; + $selector_count = advagg_count_css_selectors($media_block); + + // This chunk is bigger than $selector_limit. It needs to be split. + if ($selector_count > $selector_limit) { + $inner_selector_count = 0; + // Split css string. + list($media_query, $split_css_strings) = advagg_split_css_string($media_block, $selector_limit); + foreach ($split_css_strings as $split_css_strings) { + $counter_changed = FALSE; + if (empty($split_css_strings)) { + continue; + } - if ($part_selector_count > $selector_split_value) { - if (isset($major_chunks[$counter])) { - ++$counter; - $major_chunks[$counter] = $media_block; + // Make sure selector count doesn't go over selector limit. + $inner_selector_count = advagg_count_css_selectors($split_css_strings); + $part_selector_count += $inner_selector_count; + if ($part_selector_count > $selector_limit) { + if (!empty($values[$counter])) { + ++$counter; + } + $counter_changed = TRUE; + $part_selector_count = $inner_selector_count; + } + + // Add to output array. + if (isset($values[$counter])) { + if (!empty($media_query)) { + $values[$counter] .= "\n$media_query { $split_css_strings } "; + } + else { + $values[$counter] .= "$split_css_strings"; + } + } + else { + if (!empty($media_query)) { + $values[$counter] = "$media_query { $split_css_strings } "; + } + else { + $values[$counter] = $split_css_strings; + } + } } - else { - $major_chunks[$counter] = $media_block; + // Add to current selector counter and go to the next value. + if (!$counter_changed) { + $part_selector_count += $inner_selector_count; + } + continue; + } + + $part_selector_count += $selector_count; + if ($part_selector_count > $selector_limit) { + if (!empty($values[$counter])) { + ++$counter; } - ++$counter; - $part_selector_count = 0; + $values[$counter] = $media_block; + $part_selector_count = $selector_count; } else { - if (isset($major_chunks[$counter])) { - $major_chunks[$counter] .= "\n" . $media_block; + if (isset($values[$counter])) { + $values[$counter] .= "\n$media_block"; } else { - $major_chunks[$counter] = $media_block; + $values[$counter] = $media_block; } } } + // Save data. $parts = array(); - $overall_split = 0; - $split_at = $selector_split_value; - $chunk_split_value = (int) variable_get('advagg_ie_css_selector_limiter_value', ADVAGG_IE_CSS_SELECTOR_LIMITER_VALUE) - $selector_split_value - 1; - foreach ($major_chunks as $chunk_key => $chunks) { + $overall_counter = 0; + foreach ($values as $key => $value) { $last_chunk = FALSE; $file_info['split_last_part'] = FALSE; - if (count($major_chunks) - 1 == $chunk_key) { + if (count($values) - 1 == $key) { $last_chunk = TRUE; } + if ($last_chunk) { + $file_info['split_last_part'] = TRUE; + } // Get the number of selectors. - $matches = array(); - $selector_count = preg_match_all('/\{.+?\}|,/s', $chunks, $matches); - - // Pass through if selector count is low. - if ($selector_count < $selector_split_value) { - $overall_split += $selector_count; - if ($last_chunk) { - $file_info['split_last_part'] = TRUE; - } - $parts[] = advagg_create_subfile($chunks, $overall_split, $file_info); - continue; + $selector_count = advagg_count_css_selectors($value); + $overall_counter += $selector_count; + + // Save file. + $subfile = advagg_create_subfile($value, $overall_counter, $file_info); + if (empty($subfile)) { + // Something 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; + } + return $parts; +} - $media_query = ''; - if (strpos($chunks, '@media') !== FALSE) { - $media_query_pos = strpos($chunks, '{'); - $media_query = substr($chunks, 0, $media_query_pos); - $chunks = substr($chunks, $media_query_pos + 1); +/** + * Count the number of selectors inside of a CSS string. + * + * @param string $css_string + * CSS string. + * + * @return int + * The number of CSS selectors. + */ +function advagg_count_css_selectors($css_string) { + return substr_count($css_string, ',') + substr_count($css_string, '{') - substr_count($css_string, '@media'); +} + +/** + * Given a css string it will split it if it's over the selector limit. + * + * @param string $css_string + * CSS string. + * @param int $selector_limit + * How many selectors can be grouped together. + * + * @return array + * Array that contains the $media_query and the $css_array. + */ +function advagg_split_css_string($css_string, $selector_limit) { + // See if this css string is wrapped in a @media statement. + $media_query = ''; + $media_query_pos = strpos($css_string, '@media'); + if ($media_query_pos !== FALSE) { + // Get the opening bracket. + $open_bracket_pos = strpos($css_string, "{", $media_query_pos); + // Skip if there is a syntax error. + if ($open_bracket_pos === FALSE) { + return array(); } + $media_query = substr($css_string, $media_query_pos, $open_bracket_pos - $media_query_pos); + $css_string_inside = substr($css_string, $open_bracket_pos + 1); + } + else { + $css_string_inside = $css_string; + } - // Split CSS into selector chunks. - $split = preg_split('/(\{.+?\}|,)/si', $chunks, -1, PREG_SPLIT_DELIM_CAPTURE); + // Split CSS into selector chunks. + $split = preg_split('/(\{.+?\}|,)/si', $css_string_inside, -1, PREG_SPLIT_DELIM_CAPTURE); - // Setup and handle media queries. - $new_css_chunk = array(0 => ''); - $selector_chunk_counter = 0; - $counter = 0; - if (!empty($media_query)) { - $new_css_chunk[0] = $media_query . '{'; - $new_css_chunk[1] = ''; + $new_css_chunk = array(0 => ''); + $selector_chunk_counter = 0; + $counter = 0; + // Have the key value be the running selector count and put split array semi + // back together. + foreach ($split as $value) { + $new_css_chunk[$counter] .= $value; + if (strpos($value, '}') === FALSE) { ++$selector_chunk_counter; - ++$counter; } - // Have the key value be the running selector count and put split array semi - // back together. - foreach ($split as $value) { - $new_css_chunk[$counter] .= $value; - if (strpos($value, '}') === FALSE) { - ++$selector_chunk_counter; + else { + if ($counter + 1 < $selector_chunk_counter) { + $selector_chunk_counter += ($counter - $selector_chunk_counter + 1) / 2; } - else { - if ($counter + 1 < $selector_chunk_counter) { - $selector_chunk_counter += ($counter - $selector_chunk_counter + 1) / 2; - } - $counter = $selector_chunk_counter; - if (!isset($new_css_chunk[$counter])) { - $new_css_chunk[$counter] = ''; - } + $counter = $selector_chunk_counter; + if (!isset($new_css_chunk[$counter])) { + $new_css_chunk[$counter] = ''; } } + } - // Group selectors. - $first = TRUE; - while (!empty($new_css_chunk)) { - // Find where to split the array. - $string_to_write = ''; - while (array_key_exists($split_at, $new_css_chunk) === FALSE) { - --$split_at; - } + // Generate output array in this function. + $css_array = array(); + $keys = array_keys($new_css_chunk); + $counter = 0; + $chunk_counter = 0; + foreach (array_keys($keys) as $key) { + // Get out of loop if at the end of the array. + if (!isset($keys[$key + 1])) { + break; + } - // Combine parts of the css so that it can be saved to disk. - foreach ($new_css_chunk as $key => $value) { - if ($key !== $split_at) { - // Move this css row to the $string_to_write variable. - $string_to_write .= $value; - unset($new_css_chunk[$key]); + // Get values, keys and counts. + $this_value = $new_css_chunk[$keys[$key]]; + $this_key = $keys[$key]; + $next_key = $keys[$key + 1]; + $this_selector_count = $next_key - $this_key; + + // Single rule is bigger than the selector limit. + if ($this_selector_count > $selector_limit) { + // Get css rules for these selectors. + $open_bracket_pos = strpos($this_value, "{"); + $css_rule = ' ' . substr($this_value, $open_bracket_pos); + + // Split on selectors. + $split = preg_split('/(\,)/si', $this_value, NULL, PREG_SPLIT_OFFSET_CAPTURE); + $index = 0; + $counter = 0; + while (isset($split[$index][1])) { + // Get starting and ending positions of the selectors given the selector + // limit. + $next_index = $index + $selector_limit - 1; + $start = $split[$index][1]; + if (isset($split[$next_index][1])) { + $end = $split[$next_index][1]; } - // We are at the split point. else { - // Get the number of selectors in this chunk. - $matched = array(); - $chunk_selector_count = preg_match_all('/\{.+?\}|,/s', $new_css_chunk[$key], $matched); - if ($chunk_selector_count < $chunk_split_value) { - // The number of selectors at this point is below the threshold; - // move this chunk to the write variable and break out of the loop. - $string_to_write .= $value; - unset($new_css_chunk[$key]); - $overall_split = $split_at; - $split_at += $selector_split_value; - } - else { - // The number of selectors with this chunk included is over the - // threshold; do not move it. Change split position so the next - // iteration of the while loop ends at the correct spot. Because we - // skip unset here, this chunk will start the next part file. - $overall_split = $split_at; - $split_at += $selector_split_value - $chunk_selector_count; - } - break; + // Last one. + $temp = end($split); + $split_key = key($split); + $counter = $split_key % $selector_limit; + $end_open_bracket_pos = (int) strpos($temp[0], "{"); + $end = $temp[1] + $end_open_bracket_pos; } - } - // Handle media queries. - if (!empty($media_query)) { - // See if brackets need a new line. - if (strpos($string_to_write, "\n") === 0) { - $open_bracket = '{'; - } - else { - $open_bracket = "{\n"; - } - if (strrpos($string_to_write, "\n") === strlen($string_to_write)) { - $close_bracket = '}'; - } - else { - $close_bracket = "\n}"; - } + // Extract substr. + $sub_this_value = substr($this_value, $start, $end - $start - 1) . $css_rule; - // Fix syntax around media queries. - if ($first) { - $string_to_write .= $close_bracket; + // Save substr. + ++$chunk_counter; + $key_output = $selector_limit; + if (!empty($counter)) { + $key_output = $selector_limit - $counter; } - elseif (empty($new_css_chunk)) { - $string_to_write = $media_query . $open_bracket . $string_to_write; + $css_array["$chunk_counter $key_output"] = ''; + + if (!isset($css_array[$chunk_counter])) { + $css_array[$chunk_counter] = $sub_this_value; } else { - $string_to_write = $media_query . $open_bracket . $string_to_write . $close_bracket; + $css_array[$chunk_counter] .= $sub_this_value; } + + // Move counter. + $index = $next_index; } - // Handle the last split part. - if (empty($new_css_chunk) && $last_chunk) { - $file_info['split_last_part'] = TRUE; - } - // Write the data. - $parts[] = advagg_create_subfile($string_to_write, $overall_split, $file_info); - $first = FALSE; + continue; + } + + $counter += $this_selector_count; + if ($counter > $selector_limit) { + $key_output = $counter - $this_selector_count; + $css_array["$chunk_counter $key_output"] = ''; + $counter = $next_key - $this_key; + ++$chunk_counter; + } + if (!isset($css_array[$chunk_counter])) { + $css_array[$chunk_counter] = $this_value; + } + else { + $css_array[$chunk_counter] .= $this_value; } } - return $parts; + + // Group into sets smaller than $selector_limit. + return array($media_query, $css_array); } /** @@ -974,7 +1339,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 & 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; @@ -993,6 +1358,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); @@ -1001,7 +1369,7 @@ function advagg_create_subfile($css, $overall_split, array $file_info) { $scheme = file_uri_scheme($new_filename); if ($scheme) { $wrapper = file_stream_wrapper_get_instance_by_scheme($scheme); - if ($wrapper) { + if ($wrapper && method_exists($wrapper, 'getDirectoryPath')) { // Use the wrappers directory path. $new_filename = $wrapper->getDirectoryPath() . '/' . file_uri_target($new_filename); } @@ -1014,18 +1382,19 @@ function advagg_create_subfile($css, $overall_split, array $file_info) { $part_uri = $parts_uri . '/' . $new_filename; $dirname = drupal_dirname($part_uri); file_prepare_directory($dirname, FILE_CREATE_DIRECTORY); + $filename_path = (advagg_s3fs_evaluate_no_rewrite_cssjs(FALSE)) ? $parts_uri : $parts_path; // Get info on the file that was just created. - $part = advagg_get_info_on_file($parts_path . '/' . $new_filename) + $file_info; + $part = advagg_get_info_on_file($filename_path . '/' . $new_filename, TRUE) + $file_info; $part['split'] = TRUE; $part['split_location'] = $overall_split; $part['split_original'] = $file_info['data']; // Overwrite/create file if hash doesn't match. $hash = drupal_hash_base64($css); - if ($part['content_hash'] != $hash) { + if ($part['content_hash'] !== $hash) { advagg_save_data($part_uri, $css, TRUE); - $part = advagg_get_info_on_file($parts_path . '/' . $new_filename, TRUE) + $file_info; + $part = advagg_get_info_on_file($filename_path . '/' . $new_filename, TRUE) + $file_info; $part['split'] = TRUE; $part['split_location'] = $overall_split; $part['split_original'] = $file_info['data']; @@ -1051,7 +1420,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); @@ -1097,23 +1466,46 @@ function advagg_build_aggregate_plans(array $files_to_aggregate, $type) { } } - // Get onload. $onload = array(); - foreach ($values['files'] as $items) { + $onerror = array(); + $attributes = array(); + $onloadcss = array(); + foreach ($values['files'] as &$items) { + // Get onload. if (!empty($items['onload'])) { $onload[] = $items['onload']; } - } - $onload = implode(';', array_unique(array_filter($onload))); - - // Get onerror. - $onerror = array(); - foreach ($values['files'] as $items) { + // Get attributes onload. + if (!empty($items['attributes']['onload'])) { + $onload[] = $items['attributes']['onload']; + unset($items['attributes']['onload']); + } + // Get onerror. if (!empty($items['onerror'])) { $onload[] = $items['onerror']; } + // Get attributes onerror. + if (!empty($items['attributes']['onerror'])) { + $onload[] = $items['attributes']['onerror']; + unset($items['attributes']['onerror']); + } + // Get attributes onloadCSS. + if (!empty($items['onloadCSS'])) { + $onloadcss[] = $items['onloadCSS']; + } + // Get attributes onloadCSS. + if (!empty($items['attributes']['onloadCSS'])) { + $onloadcss[] = $items['attributes']['onloadCSS']; + unset($items['attributes']['onloadCSS']); + } + // Get attributes. + if (!empty($items['attributes'])) { + $attributes += $items['attributes']; + } } + $onload = implode(';', array_unique(array_filter($onload))); $onerror = implode(';', array_unique(array_filter($onerror))); + $onloadcss = implode(';', array_unique(array_filter($onloadcss))); $first = reset($values['files']); if (!empty($mixed_media)) { @@ -1121,7 +1513,7 @@ function advagg_build_aggregate_plans(array $files_to_aggregate, $type) { } $url = ($type === 'css') ? $css_path[0] : $js_path[0]; $path = ($type === 'css') ? $css_path[1] : $js_path[1]; - $plans[] = array( + $plans[$agg_filename] = array( 'data' => $url . '/' . $agg_filename, 'media' => isset($first['media']) ? $first['media'] : '', 'defer' => isset($first['defer']) ? $first['defer'] : '', @@ -1134,14 +1526,23 @@ function advagg_build_aggregate_plans(array $files_to_aggregate, $type) { 'items' => $values, 'filepath' => $path . '/' . $agg_filename, 'filename' => $agg_filename, + 'attributes' => $attributes, ); + if (!empty($onloadcss)) { + $plans[$agg_filename]['attributes']['onloadCSS'] = $onloadcss; + } } + $plans = array_values($plans); // Create the aggregate files. if (variable_get('advagg_pregenerate_aggregate_files', ADVAGG_PREGENERATE_AGGREGATE_FILES)) { 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, $type); + return $plans; } @@ -1164,10 +1565,9 @@ function advagg_create_aggregate_files(array $plans, $type) { } // If the httprl module exists and we want to use it. - // @ignore sniffer_whitespace_openbracketspacing_openingwhitespace - if ( module_exists('httprl') + if (module_exists('httprl') && variable_get('advagg_use_httprl', ADVAGG_USE_HTTPRL) - && ( is_callable('httprl_is_background_callback_capable') + && (is_callable('httprl_is_background_callback_capable') && httprl_is_background_callback_capable() || !is_callable('httprl_is_background_callback_capable') ) @@ -1227,27 +1627,64 @@ 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); + + // Resolve public:// if needed. + if (!advagg_is_external($file) && file_uri_scheme($file)) { + $file = advagg_get_relative_path($file); + } // 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 = ((advagg_s3fs_evaluate_no_rewrite_cssjs(FALSE)) ? $css_path[0] : $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]; + // 'url(...)' bits. + if ($url_parts !== FALSE) { + $css_base_url = substr($css_base_url, $url_parts + strlen($parts_path)); } - _advagg_build_css_path(array(), $css_base_url . '/', $aggregate_settings); + // Replace the old base path with the one that was passed in. + if (advagg_is_external($css_base_url) || variable_get('advagg_skip_file_create_url_inside_css', ADVAGG_SKIP_FILE_CREATE_URL_INSIDE_CSS)) { + $pos = strpos($css_base_url, $old_base_path); + if ($pos !== FALSE) { + $parsed_url = parse_url($css_base_url); + if (!empty($parsed_url['path'])) { + // Remove any double slash in path. + $parsed_url['path'] = str_replace('//', '/', $parsed_url['path']); + + // Get newly recalculated position. + $pos = strpos($parsed_url['path'], $old_base_path); + + // Replace. + if (strpos($parsed_url['path'], '/') !== 0 && $old_base_path === '/') { + // Special case if going to a subdir. + $parsed_url['path'] = $GLOBALS['base_path'] . $parsed_url['path']; + } + else { + $parsed_url['path'] = substr_replace($parsed_url['path'], $GLOBALS['base_path'], $pos, strlen($old_base_path)); + } + + $css_base_url = advagg_glue_url($parsed_url); + } + } + } + + _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); @@ -1309,8 +1746,31 @@ function _advagg_build_css_path(array $matches, $base = '', array $aggregate_set return ''; } - // Prefix with base and remove '../' segments where possible. + // Prefix with base. $url = $_base . $matches[1]; + + // If advagg_file_create_url() is not being used and the $url is local, redo + // the $url taking the base_path into account. + if (!advagg_is_external($url) && variable_get('advagg_skip_file_create_url_inside_css', ADVAGG_SKIP_FILE_CREATE_URL_INSIDE_CSS)) { + $new_base_path = $GLOBALS['base_path']; + if (isset($_aggregate_settings['variables']['base_path'])) { + $new_base_path = $_aggregate_settings['variables']['base_path']; + } + // Remove first /. + $new_base_path = ltrim($new_base_path, '/'); + $pos = FALSE; + // See if base_path is in the passed in $_base. + if (!empty($new_base_path)) { + $pos = strpos($_base, $new_base_path); + } + if ($pos !== FALSE) { + $url = substr($_base, $pos) . $matches[1]; + } + else { + $url = $new_base_path . $_base . $matches[1]; + } + } + // Remove '../' segments where possible. $last = ''; while ($url != $last) { $last = $url; @@ -1319,7 +1779,7 @@ function _advagg_build_css_path(array $matches, $base = '', array $aggregate_set // Parse and build back the url without the query and fragment parts. $parsed_url = parse_url($url); - $base_url = advagg_glue_url($parsed_url, TRUE); + $base_url = advagg_glue_url($parsed_url, TRUE); $query = isset($parsed_url['query']) ? $parsed_url['query'] : ''; // In the case of certain URLs, we may have simply a '?' character without @@ -1332,6 +1792,14 @@ 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); - return 'url(' . $url . $query . $fragment . ')'; + $run_file_create_url = FALSE; + if (!variable_get('advagg_skip_file_create_url_inside_css', ADVAGG_SKIP_FILE_CREATE_URL_INSIDE_CSS)) { + $run_file_create_url = TRUE; + } + if (empty($parsed_url['host'])) { + $base_url = ltrim($base_url, '/'); + } + $base_url = advagg_file_create_url($base_url, $_aggregate_settings, $run_file_create_url, 'css'); + + return 'url(' . $base_url . $query . $fragment . ')'; } diff --git a/sites/all/modules/contrib/advagg/advagg.info b/sites/all/modules/contrib/advagg/advagg.info index cfeccf296c..f2ece07431 100644 --- a/sites/all/modules/contrib/advagg/advagg.info +++ b/sites/all/modules/contrib/advagg/advagg.info @@ -1,4 +1,4 @@ -name = Advanced CSS/JS Aggregation +name = Advanced CSS/JS Aggregation (AdvAgg) description = Aggregates multiple CSS/JS files in a way that prevents 404 from happening when accessing a CSS or JS file. package = Advanced CSS/JS Aggregation core = 7.x @@ -6,9 +6,8 @@ files[] = tests/advagg.test configure = admin/config/development/performance/advagg -; Information added by Drupal.org packaging script on 2015-04-14 -version = "7.x-2.8" +; Information added by Drupal.org packaging script on 2020-11-19 +version = "7.x-2.35" core = "7.x" project = "advagg" -datestamp = "1429049283" - +datestamp = "1605792717" diff --git a/sites/all/modules/contrib/advagg/advagg.install b/sites/all/modules/contrib/advagg/advagg.install index 4137e77b22..cad60a2e88 100644 --- a/sites/all/modules/contrib/advagg/advagg.install +++ b/sites/all/modules/contrib/advagg/advagg.install @@ -5,6 +5,39 @@ * Handles Advanced Aggregation installation and upgrade tasks. */ +/** + * @addtogroup hooks + * @{ + */ + +/** + * Implements hook_install(). + */ +function advagg_install() { + $tables = array( + 'advagg_aggregates' => array( + 'aggregate_filenames_hash', + 'filename_hash', + ), + 'advagg_aggregates_versions' => array( + 'aggregate_filenames_hash', + 'aggregate_contents_hash', + ), + 'advagg_files' => array( + 'filename_hash', + 'content_hash', + ), + ); + + $schema = advagg_schema(); + foreach ($tables as $table => $fields) { + // Change utf8_bin to ascii_bin. + advagg_install_change_table_collation($table, $fields, 'ascii_bin', $schema[$table]['fields']); + } + // New install gets a locked admin section. + variable_set('advagg_admin_mode', 0); +} + /** * Implements hook_enable(). */ @@ -33,6 +66,22 @@ function advagg_enable() { @chgrp($stat_js[0], $stat_public['gid']); } } + if (drupal_is_cli()) { + // Remove advagg and public dirs if empty and running from command line. + list($css_path, $js_path) = advagg_get_root_files_dir(); + $files = file_scan_directory($css_path[1], '/.*/'); + if (empty($files)) { + rmdir($css_path[1]); + } + $files = file_scan_directory($js_path[1], '/.*/'); + if (empty($files)) { + rmdir($js_path[1]); + } + $files = file_scan_directory('public://', '/.*/'); + if (empty($files)) { + rmdir('public://'); + } + } // Make sure the advagg_flush_all_cache_bins() function is available. module_load_include('inc', 'advagg', 'advagg'); @@ -66,6 +115,13 @@ function advagg_disable() { // Flush caches. advagg_flush_all_cache_bins(); + _drupal_flush_css_js(); + drupal_clear_css_cache(); + drupal_clear_js_cache(); + cache_clear_all('*', 'cache_page', TRUE); + + // Make sure the theme_registry: cid is cleared. + register_shutdown_function('cache_clear_all', 'theme_registry:', 'cache', TRUE); } /** @@ -74,13 +130,10 @@ function advagg_disable() { function advagg_uninstall() { // Make sure the advagg_get_root_files_dir() function is available. drupal_load('module', 'advagg'); + list($css_path, $js_path) = advagg_get_root_files_dir(); - // Make sure the advagg_remove_all_aggregated_files() function is available. - module_load_include('inc', 'advagg', 'advagg'); + // Make sure the advagg_flush_all_cache_bins() function is available. module_load_include('inc', 'advagg', 'advagg.cache'); - - // Remove files. - advagg_remove_all_aggregated_files(TRUE); // Flush caches. advagg_flush_all_cache_bins(); @@ -89,10 +142,12 @@ function advagg_uninstall() { ->condition('name', 'advagg%', 'LIKE') ->execute(); - // Remove Directories. - list($css_path, $js_path) = advagg_get_root_files_dir(); - drupal_rmdir($css_path[0]); - drupal_rmdir($js_path[0]); + // Remove all files and directories. + file_unmanaged_delete_recursive($css_path[0]); + file_unmanaged_delete_recursive($js_path[0]); + + // Make sure the theme_registry: cid is cleared. + register_shutdown_function('cache_clear_all', 'theme_registry:', 'cache', TRUE); } /** @@ -120,19 +175,25 @@ function advagg_schema() { ), 'filename_hash' => array( 'description' => 'Hash of path and filename. Used to join tables.', - 'type' => 'varchar', + 'type' => 'char', 'length' => 43, 'not null' => TRUE, 'default' => '', 'binary' => TRUE, + 'collation' => 'ascii_bin', + 'charset' => 'ascii', + 'mysql_character_set' => 'ascii', ), 'content_hash' => array( 'description' => 'Hash of the file content. Used to see if the file has changed.', - 'type' => 'varchar', + 'type' => 'char', 'length' => 43, - 'not null' => TRUE, + 'not null' => FALSE, 'default' => '', 'binary' => TRUE, + 'collation' => 'ascii_bin', + 'charset' => 'ascii', + 'mysql_character_set' => 'ascii', ), 'filetype' => array( 'description' => 'Filetype.', @@ -166,11 +227,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'), ); @@ -180,19 +254,25 @@ function advagg_schema() { 'fields' => array( 'aggregate_filenames_hash' => array( 'description' => 'Hash of the aggregates list of files. Keep track of what files are in the aggregate.', - 'type' => 'varchar', + 'type' => 'char', 'length' => 43, 'not null' => TRUE, 'default' => '', 'binary' => TRUE, + 'collation' => 'ascii_bin', + 'charset' => 'ascii', + 'mysql_character_set' => 'ascii', ), 'filename_hash' => array( 'description' => 'Hash of path and filename.', - 'type' => 'varchar', + 'type' => 'char', 'length' => 43, 'not null' => TRUE, 'default' => '', 'binary' => TRUE, + 'collation' => 'ascii_bin', + 'charset' => 'ascii', + 'mysql_character_set' => 'ascii', ), 'porder' => array( 'description' => 'Processing order.', @@ -211,6 +291,8 @@ function advagg_schema() { ), 'indexes' => array( 'porder' => array('porder'), + 'filename_hash' => array('filename_hash'), + 'aggregate_filenames_hash_porder' => array('aggregate_filenames_hash', 'porder'), ), 'primary key' => array('aggregate_filenames_hash', 'filename_hash'), ); @@ -220,19 +302,25 @@ function advagg_schema() { 'fields' => array( 'aggregate_filenames_hash' => array( 'description' => 'Hash of the aggregates list of files. Keep track of what files are in the aggregate.', - 'type' => 'varchar', + 'type' => 'char', 'length' => 43, 'not null' => TRUE, 'default' => '', 'binary' => TRUE, + 'collation' => 'ascii_bin', + 'charset' => 'ascii', + 'mysql_character_set' => 'ascii', ), 'aggregate_contents_hash' => array( 'description' => 'Hash of all content_hashes in this aggregate. Simple Version control of the aggregate.', - 'type' => 'varchar', + 'type' => 'char', 'length' => 43, 'not null' => TRUE, 'default' => '', 'binary' => TRUE, + 'collation' => 'ascii_bin', + 'charset' => 'ascii', + 'mysql_character_set' => 'ascii', ), 'atime' => array( 'description' => 'Last access time for this version of the aggregate. Updated every 12 hours.', @@ -258,7 +346,7 @@ function advagg_schema() { 'primary key' => array('aggregate_filenames_hash', 'aggregate_contents_hash'), ); - // Copy the variable table & change a couple of things. + // Copy the variable table and change a couple of things. $schema['advagg_aggregates_hashes'] = drupal_get_schema_unprocessed('system', 'variable'); $schema['advagg_aggregates_hashes']['fields']['hash'] = $schema['advagg_aggregates_hashes']['fields']['name']; $schema['advagg_aggregates_hashes']['fields']['hash']['length'] = 255; @@ -274,9 +362,7 @@ function advagg_schema() { } /** - * Upgrade AdvAgg previous versions (6.x-1.x & 7.x-1.x) to 7.x-2.x. - * - * Implements hook_update_N(). + * Upgrade AdvAgg previous versions (6.x-1.x and 7.x-1.x) to 7.x-2.x. */ function advagg_update_7200(&$sandbox) { // Check and see if new tables exist. @@ -318,38 +404,27 @@ function advagg_update_7200(&$sandbox) { } /** - * Implements hook_update_N(). - * - * Update the .htaccess file in the advagg directories by removing the - * Last-Modified Header so far future 304's work correctly. + * Remove Last-Modified Header from .htaccess to fix far future 304's. */ function advagg_update_7201(&$sandbox) { return advagg_install_update_htaccess('Header set Last-Modified'); } /** - * Implements hook_update_N(). - * - * Update the .htaccess file in the advagg directories by removing the 480 week - * Far-Future code as it does not follow RFC 2616 14.21. + * Remove the 480 week Far-Future code from .htaccess (violates RFC 2616 14.21). */ function advagg_update_7202(&$sandbox) { return advagg_install_update_htaccess('290304000'); } /** - * Implements hook_update_N(). - * - * Update the .htaccess file in the advagg directories by forcing .js files to - * be application/javascript so to follow RFC 4329 7.1 + * Add forcing of .js files to be application/javascript to follow RFC 4329 7.1. */ function advagg_update_7203(&$sandbox) { return advagg_install_update_htaccess('', 'ForceType'); } /** - * Implements hook_update_N(). - * * Remove empty temporary files left behind by AdvAgg. */ function advagg_update_7204(&$sandbox) { @@ -388,20 +463,16 @@ function advagg_update_7204(&$sandbox) { } /** - * Implements hook_update_N(). - * - * Update the .htaccess file in the advagg directories in order to fix incorrect - * usage of ForceType from update 7203. + * Fix incorrect usage of ForceType in .htaccess 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'), + )); } /** - * Implements hook_update_N(). - * * Update the schema making the varchar columns utf8_bin in MySQL. */ function advagg_update_7206(&$sandbox) { @@ -431,88 +502,356 @@ function advagg_update_7206(&$sandbox) { } /** - * Callback to delete files if size == 0 & modified more than 60 seconds ago. - * - * @param string $uri - * Location of the file to check. + * Update schema making the varchar columns char. Change utf8_bin to ascii_bin. */ -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) { - file_unmanaged_delete($uri); +function advagg_update_7207(&$sandbox) { + $tables = array( + 'advagg_aggregates' => array( + 'aggregate_filenames_hash', + 'filename_hash', + ), + 'advagg_aggregates_versions' => array( + 'aggregate_filenames_hash', + 'aggregate_contents_hash', + ), + 'advagg_files' => array( + 'filename_hash', + 'content_hash', + ), + ); + + $schema = advagg_schema(); + foreach ($tables as $table => $fields) { + foreach ($fields as $field) { + // Change varchar to char. + db_change_field($table, $field, $field, $schema[$table]['fields'][$field]); + } + // Change utf8_bin to ascii_bin. + advagg_install_change_table_collation($table, $fields, 'ascii_bin', $schema[$table]['fields']); } + return t('AdvAgg Tables converted from varchar to char and utf8_bin to ascii_bin.'); } /** - * Callback to delete files if size == 0 & modified more than 60 seconds ago. - * - * @param string $has_string - * If the .htaccess file contains this string it will be removed & recreated. - * @param string $does_not_have_string - * If the .htaccess file does not contains this string it will be removed & - * recreated. - * - * @return string - * Translated string indicating what was done. + * Add an index to the filename_hash column in the advagg_aggregates table. */ -function advagg_install_update_htaccess($has_string = '', $does_not_have_string = '') { - // Make sure the advagg_get_root_files_dir() function is available. - drupal_load('module', 'advagg'); +function advagg_update_7208(&$sandbox) { + if (!db_index_exists('advagg_aggregates', 'filename_hash')) { + db_add_index('advagg_aggregates', 'filename_hash', array('filename_hash')); + return t('Database index added to the filename_hash column of the advagg_aggregates table.'); + } + return t('Nothing needed to be done.'); +} - // Get paths to .htaccess file. - list($css_path, $js_path) = advagg_get_root_files_dir(); - $files['css'] = $css_path[0] . '/.htaccess'; - $files['js'] = $js_path[0] . '/.htaccess'; +/** + * Update schema making it match the definition. + */ +function advagg_update_7209(&$sandbox) { + $tables = array( + 'advagg_aggregates' => array( + 'aggregate_filenames_hash', + 'filename_hash', + ), + 'advagg_aggregates_versions' => array( + 'aggregate_filenames_hash', + 'aggregate_contents_hash', + ), + 'advagg_files' => array( + 'filename_hash', + 'content_hash', + ), + ); - // Check for old .htaccess files. - $something_done = FALSE; - foreach ($files as $type => $uri) { - if (!file_exists($uri)) { - unset($files[$type]); - continue; - } - $contents = file_get_contents($uri); - // Remove old .htaccess file if it has this string. - if (!empty($has_string) && strpos($contents, $has_string) !== FALSE) { - drupal_unlink($uri); - $something_done = TRUE; - } - // Remove old .htaccess file if it does not have this string. - if (!empty($does_not_have_string) && strpos($contents, $does_not_have_string) === FALSE) { - drupal_unlink($uri); - $something_done = TRUE; + $schema = advagg_schema(); + foreach ($tables as $table => $fields) { + foreach ($fields as $field) { + // Change varchar to char. + db_change_field($table, $field, $field, $schema[$table]['fields'][$field]); } + // Change utf8_bin to ascii_bin. + advagg_install_change_table_collation($table, $fields, 'ascii_bin', $schema[$table]['fields']); } + return t('Database schema was adjusted to match what is listed in advagg_schema.'); +} - // Create the new .htaccess file. - $new_htaccess = FALSE; - if (!empty($files) && $something_done && variable_get('advagg_htaccess_check_generate', ADVAGG_HTACCESS_CHECK_GENERATE)) { - // Make the advagg_htaccess_check_generate() function available. - module_load_include('inc', 'advagg', 'advagg.missing'); - foreach ($files as $type => $uri) { - advagg_htaccess_check_generate(array($uri => $type), $type, TRUE); - $new_htaccess = TRUE; - } +/** + * 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.'); +} + +/** + * Populate the filesize_processed field in the advagg_files table. + */ +function advagg_update_7211(&$sandbox) { + drupal_load('module', 'advagg'); + module_load_include('inc', 'advagg', 'advagg'); + $types = array('css', 'js'); + + // If first run of this update function then set progress variables. + if (!isset($sandbox['progress'])) { + $count = db_select('advagg_files', 'af') + ->fields('af') + ->condition('filesize_processed', 0) + ->countQuery() + ->execute() + ->fetchField(); + $sandbox['progress'] = 0; + $sandbox['max'] = $count; } - // Output info. - if ($something_done) { - if ($new_htaccess) { - return t('Removed the old .htaccess file and put in a new one.'); + // How many items should be processed per pass. + $limit = 20; + + foreach ($types as $type) { + $query = db_select('advagg_files', 'af') + ->fields('af') + ->condition('filesize_processed', 0) + ->condition('filetype', $type) + ->range($sandbox['progress'], $limit) + ->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(); + } } - else { - return t('Removed the old .htaccess file.'); + } + + // Update our progress information. + $sandbox['progress'] += $limit; + // Set the value for finished. + $sandbox['#finished'] = ($sandbox['progress'] >= $sandbox['max']) ? TRUE : ($sandbox['progress'] / $sandbox['max']); + + if ($sandbox['#finished']) { + return t('The filesize_processed field has been populated inside the advagg_files table.'); + } +} + +/** + * 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.'); +} + +/** + * Populate the use_strict field in the advagg_files table. + */ +function advagg_update_7213(&$sandbox) { + drupal_load('module', 'advagg'); + module_load_include('inc', 'advagg', 'advagg'); + + // If first run of this update function then set progress variables. + if (!isset($sandbox['progress'])) { + $count = db_select('advagg_files', 'af') + ->fields('af') + ->condition('filetype', 'js') + ->countQuery() + ->execute() + ->fetchField(); + $sandbox['progress'] = 0; + $sandbox['max'] = $count; + } + + // How many items should be processed per pass. + $limit = 10; + + $query = db_select('advagg_files', 'af') + ->fields('af') + ->condition('filetype', 'js') + ->range($sandbox['progress'], $limit) + ->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(); } } - else { + + // Update our progress information. + $sandbox['progress'] += $limit; + // Set the value for finished. + $sandbox['#finished'] = ($sandbox['progress'] >= $sandbox['max']) ? TRUE : ($sandbox['progress'] / $sandbox['max']); + + if ($sandbox['#finished']) { + return t('The use_strict field has been populated inside the advagg_files table.'); + } +} + +/** + * Update .htaccess to support brotli compression (br). + */ +function advagg_update_7214(&$sandbox) { + return advagg_install_update_htaccess('', 'brotli'); +} + +/** + * Update .htaccess to support brotli compression (br). + */ +function advagg_update_7215(&$sandbox) { + return advagg_install_update_htaccess('', '%{HTTP:Accept-encoding} gzip'); +} + +/** + * Update .htaccess to support brotli compression (br). + */ +function advagg_update_7216(&$sandbox) { + if ($GLOBALS['base_path'] !== '/') { + return advagg_install_update_htaccess('', 'ErrorDocument 404'); + } +} + +/** + * Update migrate the advagg_browser_dns_prefetch variable. + */ +function advagg_update_7217(&$sandbox) { + $advagg_browser_dns_prefetch = variable_get('advagg_browser_dns_prefetch', NULL); + $advagg_resource_hints_dns_prefetch = variable_get('advagg_resource_hints_dns_prefetch', NULL); + $advagg_resource_hints_location = variable_get('advagg_resource_hints_location', NULL); + variable_del('advagg_browser_dns_prefetch'); + if (empty($advagg_browser_dns_prefetch) + || !is_null($advagg_resource_hints_dns_prefetch) + || !is_null($advagg_resource_hints_location) + ) { return t('Nothing needed to be done.'); } + drupal_load('module', 'advagg'); + $config_path = advagg_admin_config_root_path(); + + if ($advagg_browser_dns_prefetch == 1) { + variable_set('advagg_resource_hints_location', 1); + variable_set('advagg_resource_hints_dns_prefetch', TRUE); + } + elseif ($advagg_browser_dns_prefetch == 2) { + variable_set('advagg_resource_hints_location', 3); + variable_set('advagg_resource_hints_dns_prefetch', TRUE); + } + else { + return t('Nothing happened.'); + } + return t('Old DNS Prefetch variable transferred to the new variable. Other options are under Resource Hints on the configuration page', array('@url' => url($config_path . '/advagg', array('fragment' => 'edit-resource-hints')))); } /** - * Implements hook_requirements(). + * Update the advagg .htaccess file fixing edge cases with the new rules. */ -function advagg_requirements($phase) { +function advagg_update_7218(&$sandbox) { + return advagg_install_update_htaccess('', 'Options +FollowSymLinks'); +} + +/** + * Update the .htaccess file in the advagg directories adding immutable header. + */ +function advagg_update_7219(&$sandbox) { + return advagg_install_update_htaccess('', 'immutable'); +} + +/** + * Update the advagg_files table; use_strict column might have been incorrect. + */ +function advagg_update_7220() { + // Get all files that have use_strict marked. + $filenames = array(); + $query = db_select('advagg_files', 'af') + ->fields('af', array('filename', 'use_strict')) + ->condition('use_strict', 1) + ->execute(); + foreach ($query as $row) { + $filenames[] = $row->filename; + } + if (empty($filenames)) { + return t('Nothing needed to happen. Good to go!'); + } + + drupal_load('module', 'advagg'); + module_load_include('inc', 'advagg', 'advagg'); + + // Force change. + $info = advagg_get_info_on_files($filenames); + foreach ($info as &$value) { + $value['mtime']++; + } + advagg_insert_update_files($info, 'js'); + + // Fix changed record. + advagg_get_info_on_files($filenames); + advagg_insert_update_files($info, 'js'); + + // Detect changes. + $filenames_new = array(); + $query = db_select('advagg_files', 'af') + ->fields('af', array('filename', 'use_strict')) + ->condition('use_strict', 1) + ->execute(); + foreach ($query as $row) { + $filenames_new[] = $row->filename; + } + + // Output results. + if (count($filenames_new) == count($filenames_new)) { + return t('Nothing needed to happen. Good to go!'); + } + else { + return t('The advagg_files table has been updated; use_strict column has been updated.'); + } +} + +/** + * Add index to aggregate_filenames_hash and porder in advagg_aggregates table. + */ +function advagg_update_7221(&$sandbox) { + if (!db_index_exists('advagg_aggregates', 'aggregate_filenames_hash_porder')) { + db_add_index('advagg_aggregates', 'aggregate_filenames_hash_porder', array('aggregate_filenames_hash', 'porder')); + return t('Database index added to the aggregate_filenames_hash and porder column of the advagg_aggregates table.'); + } + return t('Nothing needed to be done.'); +} + +/** + * Run various checks that are fast. + * + * @param string $phase + * Can be install, update, or runtime. + * + * @return array + * An associative array. + */ +function advagg_install_fast_checks($phase = 'runtime') { $requirements = array(); // Ensure translations don't break at install time. $t = get_t(); @@ -565,71 +904,169 @@ function advagg_requirements($phase) { if ($phase !== 'runtime') { return $requirements; } + // Make sure the advagg default values for variable_get are available. + drupal_load('module', 'advagg'); // Do the following checks only at runtime. list($css_path, $js_path) = advagg_get_root_files_dir(); $config_path = advagg_admin_config_root_path(); // Make sure directories are writable. - if (!file_prepare_directory($css_path[0], FILE_CREATE_DIRECTORY)) { - $requirements['advagg_css_path'] = array( + if (!file_prepare_directory($css_path[0], FILE_CREATE_DIRECTORY + FILE_MODIFY_PERMISSIONS)) { + $requirements['advagg_css_path_0_prepare_dir'] = array( 'title' => $t('Adv CSS/JS Agg - CSS Path'), 'severity' => REQUIREMENT_ERROR, 'value' => $t('CSS directory is not created or writable.'), 'description' => $t('%path is not setup correctly.', array('%path' => $css_path[0])), ); } - if (!file_prepare_directory($js_path[0], FILE_CREATE_DIRECTORY)) { - $requirements['advagg_js_path'] = array( + if (!is_writable($css_path[0])) { + $requirements['advagg_css_path_0_write'] = 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])), + ); + } + if (!is_readable($css_path[0])) { + $requirements['advagg_css_path_0_read'] = 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])), + ); + } + $css_wrapper = file_stream_wrapper_get_instance_by_uri($css_path[0]); + if ($css_wrapper instanceof DrupalLocalStreamWrapper) { + if (!is_writable($css_path[1])) { + $requirements['advagg_css_path_1_write'] = 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])), + ); + } + if (!is_readable($css_path[1])) { + $requirements['advagg_css_path_1_read'] = 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_0_prepare_dir'] = array( 'title' => $t('Adv CSS/JS Agg - JS Path'), 'severity' => REQUIREMENT_ERROR, 'value' => $t('JS directory is not created or writable.'), 'description' => $t('%path is not setup correctly.', array('%path' => $js_path[0])), ); } - - // Make sure variables are set correctly. - if (variable_get('advagg_enabled', ADVAGG_ENABLED) == FALSE) { - $requirements['advagg_not_on'] = array( - 'title' => $t('Adv CSS/JS Agg - Enabled'), - 'severity' => REQUIREMENT_WARNING, - 'value' => $t('Advanced CSS/JS aggregation is disabled.'), - 'description' => $t('Go to the Advanced CSS/JS aggregation settings page and enable it.', array('@settings' => url($config_path . '/advagg'))), + if (!is_writable($js_path[0])) { + $requirements['advagg_js_path_0_write'] = 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 (!variable_get('preprocess_css', FALSE) || !variable_get('preprocess_js', FALSE)) { - $requirements['advagg_core_off'] = array( - 'title' => $t('Adv CSS/JS Agg - Core Variables'), + if (!is_readable($js_path[0])) { + $requirements['advagg_js_path_0_read'] = array( + 'title' => $t('Adv CSS/JS Agg - JS Path'), 'severity' => REQUIREMENT_ERROR, - 'value' => $t('Cores 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'))), + 'value' => $t('JS directory is not readable.'), + 'description' => $t('%path is not setup correctly.', array('%path' => $js_path[0])), ); } + $js_wrapper = file_stream_wrapper_get_instance_by_uri($js_path[0]); + if ($js_wrapper instanceof DrupalLocalStreamWrapper) { + if (!is_writable($js_path[1])) { + $requirements['advagg_js_path_1_write'] = 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])), + ); + } + if (!is_readable($js_path[1])) { + $requirements['advagg_js_path_1_read'] = 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. + if (!variable_get('advagg_enabled', ADVAGG_ENABLED)) { + $requirements['advagg_not_on'] = array( + 'title' => $t('Adv CSS/JS Agg - Enabled'), + 'severity' => REQUIREMENT_WARNING, + 'value' => $t('Advanced CSS/JS aggregation is disabled.'), + 'description' => $t('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)) { + $requirements['advagg_core_off'] = array( + 'title' => $t('Adv CSS/JS Agg - Core Variables'), + 'severity' => REQUIREMENT_ERROR, + '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', array('fragment' => 'edit-bandwidth-optimization')))), + ); + } + } // 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)))); - $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, - )), - ); + // Paths will vary based on s3fs no_rewrite_cssjs setting. + if (advagg_s3fs_evaluate_no_rewrite_cssjs(FALSE)) { + // If using s3fs and no_rewrite_cssjs is not set, external paths are needed. + // Use $css_path[0] and $js_path[0] since they contain the scheme. + $menu_path_key = 0; + $menu_css_path = trim(parse_url(file_create_url($css_path[0] . '/test.css'), PHP_URL_PATH)); + if (strpos($menu_css_path, $GLOBALS['base_path']) === 0) { + $menu_css_path = substr($menu_css_path, strlen($GLOBALS['base_path'])); + } + $menu_js_path = trim(parse_url(file_create_url($js_path[0] . '/test.js'), PHP_URL_PATH)); + if (strpos($menu_js_path, $GLOBALS['base_path']) === 0) { + $menu_js_path = substr($menu_js_path, strlen($GLOBALS['base_path'])); + } } - $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( + else { + // Determine paths if not using s3fs, or no_rewrite_cssjs is set. + // Use $css_path[1] and $js_path[1] since they are without schemes. + $menu_path_key = 1; + $menu_css_path = $css_path[1] . '/test.css'; + $menu_js_path = $js_path[1] . '/test.js'; + } + + // Use the paths set above to check menu router handler. + $advagg_async_generation_menu_issue = FALSE; + if (!file_uri_scheme($css_path[$menu_path_key])) { + $item_css = menu_get_item($menu_css_path); + if (empty($item_css['page_callback']) + || strpos($item_css['page_callback'], 'advagg') === FALSE + ) { + $advagg_async_generation_menu_issue = TRUE; + } + } + if (!file_uri_scheme($js_path[$menu_path_key])) { + $item_js = menu_get_item($menu_js_path); + if (empty($item_js['page_callback']) + || strpos($item_js['page_callback'], 'advagg') === FALSE + ) { + $advagg_async_generation_menu_issue = TRUE; + } + } + if ($advagg_async_generation_menu_issue) { + $requirements['advagg_async_generation_menu_issue'] = 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, )), ); } @@ -637,22 +1074,36 @@ function advagg_requirements($phase) { // Make hook_element_info_alter worked. $styles_info = element_info('styles'); $scripts_info = element_info('scripts'); - // @ignore sniffer_whitespace_openbracketspacing_openingwhitespace - if ( empty($styles_info['#pre_render']) + if (empty($styles_info['#pre_render']) || !is_array($styles_info['#pre_render']) || !in_array('advagg_modify_css_pre_render', $styles_info['#pre_render']) || empty($scripts_info['#pre_render']) || !is_array($scripts_info['#pre_render']) || !in_array('advagg_modify_js_pre_render', $scripts_info['#pre_render']) ) { - $requirements['advagg_hook_element_info_alter'] = array( - 'title' => $t('Adv CSS/JS Agg - element_info'), - 'severity' => REQUIREMENT_WARNING, - 'value' => $t('Flush your caches.'), - 'description' => $t('You need to flush your cache_bootstrap cache bin as advagg_hook_element_info_alter() is not working correctly. This can be done near the top of the performance page under Clear cache.', array( - '@performance' => url('admin/config/development/performance'), - )), - ); + if (!empty($scripts_info['#group_callback']) && $scripts_info['#group_callback'] === 'omega_group_js') { + $requirements['advagg_hook_element_info_alter_omega'] = array( + 'title' => $t('Adv CSS/JS Agg - omega theme patch'), + 'severity' => REQUIREMENT_WARNING, + 'value' => $t('Omega theme needs a patch.'), + 'description' => $t('The patch can be found in this issue', array( + '@patch' => 'https://www.drupal.org/files/issues/omega-2492461-1-smarter-element-info-alter.patch', + '@issue' => 'https://www.drupal.org/node/2492461', + )), + ); + } + else { + $requirements['advagg_hook_element_info_alter'] = array( + 'title' => $t('Adv CSS/JS Agg - element_info'), + 'severity' => REQUIREMENT_WARNING, + 'value' => $t('Flush your caches.'), + 'description' => $t('You need to flush your cache_bootstrap cache bin as advagg_hook_element_info_alter() is not working correctly. This can be done near the top of the performance page under Clear cache.
Styles:

@styles

Scripts:

@scripts

', array( + '@performance' => url('admin/config/development/performance'), + '@styles' => print_r($styles_info, TRUE), + '@scripts' => print_r($scripts_info, TRUE), + )), + ); + } } // Make sure some modules have the correct patches installed. @@ -679,10 +1130,9 @@ function advagg_requirements($phase) { } // Adjust some modules settings. - // @ignore sniffer_whitespace_openbracketspacing_openingwhitespace $search404_ignore_query = variable_get('search404_ignore_query', 'gif jpg jpeg bmp png'); - if ( module_exists('search404') && - ( strpos($search404_ignore_query, 'css') === FALSE + if (module_exists('search404') && + (strpos($search404_ignore_query, 'css') === FALSE || strpos($search404_ignore_query, 'js') === FALSE ) ) { @@ -706,10 +1156,44 @@ function advagg_requirements($phase) { ); } + if (module_exists('securepages') && variable_get('securepages_enable', 0) && function_exists('securepages_match')) { + $test_css = securepages_match($css_path[1] . '/test.css'); + $test_js = securepages_match($js_path[1] . '/test.js'); + if ($test_css === 0 || $test_js === 0) { + $added_paths = array(); + $securepages_ignore = variable_get('securepages_ignore', ''); + if (strpos($securepages_ignore, $css_path[1]) === FALSE) { + $added_paths[] = $css_path[1] . '/*'; + } + if (strpos($securepages_ignore, $js_path[1]) === FALSE) { + $added_paths[] = $js_path[1] . '/*'; + } + if (!empty($added_paths)) { + $requirements['advagg_securepages_module'] = array( + 'title' => $t('Adv CSS/JS Agg - Secure Pages Settings'), + 'severity' => REQUIREMENT_ERROR, + 'value' => $t('Requests to advagg for css/js files may be getting redirected to http on a https page.'), + ); + if (!empty($securepages_ignore)) { + $requirements['advagg_securepages_module']['description'] = $t('The Secure Pages module is enabled. You need to change the securepages_ignore setting, also known as "Ignore pages" so advagg will work. Go to the Secure Pages settings page and under the "Ignore pages" setting add

!code

to the string that looks like this:

@old

so it will then look like this:

@old
!code

', array( + '@config' => url('admin/config/system/securepages', array('fragment' => 'edit-securepages-ignore')), + '!code' => implode("\n
", $added_paths), + '@old' => trim($securepages_ignore), + )); + } + else { + $requirements['advagg_securepages_module']['description'] = $t('The Secure Pages module is enabled. You need to change the securepages_ignore setting, also known as "Ignore pages" so advagg will work. Go to the Secure Pages settings page and under the "Ignore pages" setting add

!code

to that section.', array( + '@config' => url('admin/config/system/securepages', array('fragment' => 'edit-securepages-ignore')), + '!code' => implode("\n
", $added_paths), + )); + } + } + } + } + // Check that https is correct. - // @ignore sniffer_whitespace_openbracketspacing_openingwhitespace:2 - if ( empty($GLOBALS['is_https']) && - ( (isset($_SERVER['HTTPS']) && strtolower($_SERVER['HTTPS']) === 'on') + if (empty($GLOBALS['is_https']) && + ((isset($_SERVER['HTTPS']) && strtolower($_SERVER['HTTPS']) === 'on') || (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https') || (isset($_SERVER['HTTP_HTTPS']) && $_SERVER['HTTP_HTTPS'] === 'on') ) @@ -719,33 +1203,91 @@ function advagg_requirements($phase) { 'severity' => REQUIREMENT_WARNING, 'value' => $t('The core global $is_https is not TRUE.'), 'description' => $t('You need to add in this logic near the top your settings.php file:
@code
', array( - '@code' => 'if ( (isset($_SERVER[\'HTTPS\']) && strtolower($_SERVER[\'HTTPS\']) == \'on\') + '@code' => 'if ((isset($_SERVER[\'HTTPS\']) && strtolower($_SERVER[\'HTTPS\']) == \'on\') || (isset($_SERVER[\'HTTP_X_FORWARDED_PROTO\']) && $_SERVER[\'HTTP_X_FORWARDED_PROTO\'] == \'https\') || (isset($_SERVER[\'HTTP_HTTPS\']) && $_SERVER[\'HTTP_HTTPS\'] == \'on\') ) { $_SERVER[\'HTTPS\'] = \'on\'; -}'))); +}', + )), + ); } // Make sure $base_url is correct. - if (!empty($GLOBALS['is_https']) && strpos($GLOBALS['base_url'], 'https://') !== 0) { + // Site is https but $base_url starts with http://. + if (!empty($GLOBALS['is_https']) + && strpos($GLOBALS['base_url'], 'http://') === 0 + ) { $requirements['advagg_is_https_check'] = array( - 'title' => $t('Adv CSS/JS Agg - HTTPS'), + 'title' => $t('Adv CSS/JS Agg - $base_url'), 'severity' => REQUIREMENT_WARNING, 'value' => $t('The core global $base_url\'s scheme is incorrect.'), - 'description' => $t('You need to add in this logic near the bottom of your settings.php file:
@code
', array( + 'description' => $t('You need to add in this logic near the bottom of your settings.php file:

@code

', array( '@code' => 'if (isset($_SERVER["HTTPS"]) && strtolower($_SERVER["HTTPS"]) == "on" && isset($base_url)) { -$base_url = str_replace("http://", "https://", $base_url); + $base_url = str_replace("http://", "https://", $base_url); +}', + )), + ); + } + + return $requirements; } -'))); + +/** + * Implements hook_requirements(). + */ +function advagg_requirements($phase) { + $t = get_t(); + $requirements = advagg_install_fast_checks($phase); + // If not at runtime, return here. + if ($phase !== 'runtime') { + return $requirements; + } + + // Make sure outbound http requests will work. + $request = drupal_http_request('https://www.google.com/robots.txt', array('timeout' => 8)); + if (empty($request->data) || $request->code != 200) { + $requirements['advagg_drupal_http_request_failure'] = array( + 'title' => $t('Adv CSS/JS Agg - drupal_http_request test'), + 'severity' => REQUIREMENT_WARNING, + 'value' => $t('An external request for https://www.google.com/robots.txt could not be fulfilled.'), + 'description' => $t('If drupal_http_request does not work, the tests that AdvAgg performs may not be accurate.'), + ); } - // Make sure http requests will work correctly. + // Make sure http requests to advagg will work. 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 = ''; + if (variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL) < 0) { $description .= ' ' . $t('Currently running in development mode.'); } @@ -771,6 +1313,10 @@ $base_url = str_replace("http://", "https://", $base_url); return $requirements; } +/** + * @} End of "addtogroup hooks". + */ + /** * Make sure http requests to css/js files work correctly. * @@ -789,21 +1335,47 @@ function advagg_install_check_via_http(array &$requirements) { // Setup some variables. list($css_path, $js_path) = advagg_get_root_files_dir(); $types = array('css', 'js'); + $config_path = advagg_admin_config_root_path(); + + // Get s3fs no_rewrite_cssjs setting. + $s3fs_no_rewrite_cssjs = advagg_get_s3fs_config('no_rewrite_cssjs'); // Make sure we get an advagg fast 404. $mod_url = FALSE; if (!variable_get('maintenance_mode', FALSE) && !variable_get('advagg_skip_404_check', FALSE)) { foreach ($types as $type) { if ($type === 'css') { - $path = $css_path[0]; + $url_path = $css_path[0]; + $file_path = $css_path[1]; } elseif ($type === 'js') { - $path = $js_path[0]; + $url_path = $js_path[0]; + $file_path = $js_path[1]; } + // Set arguments for drupal_http_request(). // Make a 404 request to the advagg menu callback. - $url = file_create_url($path . '/' . $type . ADVAGG_SPACE . REQUEST_TIME . '.' . $type); - $options = array(); + $url = file_create_url($url_path . '/' . $type . ADVAGG_SPACE . REQUEST_TIME . '.' . $type); + $options = array('timeout' => 8); + + if (empty($url)) { + $filename_path = (!is_null($s3fs_no_rewrite_cssjs) && empty($s3fs_no_rewrite_cssjs)) ? $url_path : $file_path; + $filename = advagg_install_get_first_advagg_file($filename_path, $type); + $url = file_create_url($url_path . '/' . $filename); + $end = strpos($url, $filename); + if ($end !== FALSE) { + $url = substr($url, 0, $end) . $type . ADVAGG_SPACE . REQUEST_TIME . '.' . $type; + } + else { + $requirements['advagg_self_request'] = array( + 'title' => $t('Adv CSS/JS Agg - Self Request Failure'), + 'severity' => REQUIREMENT_ERROR, + 'value' => $t('The uri: %url can not be converted to a url.', array('%url' => $url_path . '/' . $type . ADVAGG_SPACE . REQUEST_TIME . '.' . $type)), + 'description' => $t('If you are using a non default stream wrapper this might be the issue.'), + ); + continue; + } + } // Send request. advagg_install_url_mod($url, $options, $mod_url); @@ -818,13 +1390,13 @@ function advagg_install_check_via_http(array &$requirements) { if ($new_request->code < 0) { $description = ''; if (!module_exists('httprl')) { - $description = t('Enabling the HTTP Parallel Request & Threading Library module might be able to fix this as AdvAgg will use HTTPRL to build the URL if it is enabled.', array('!httprl' => 'https://drupal.org/project/httprl')); + $description = t('Enabling the HTTP Parallel Request and Threading Library module might be able to fix this as AdvAgg will use HTTPRL to build the URL if it is enabled.', array('!httprl' => 'https://drupal.org/project/httprl')); } $requirements['advagg_self_request'] = array( 'title' => $t('Adv CSS/JS Agg - Self Request Failure'), 'severity' => REQUIREMENT_ERROR, 'value' => $t('HTTP loopback requests to this server are returning a non positive response code of %code', array('%code' => $new_request->code)), - 'description' => $t('If you have manually verified that AdvAgg is working correctly you can set the advagg_skip_404_check variable to TRUE in your settings.php. Editing the servers hosts file so the host name points to the localhost might also fix this (127.0.0.1 !hostname). To manually check go to @url, view the source and check for this string @string. If that string is in the source, you can safely add this to your settings.php file @code', array( + 'description' => $t('If you have manually verified that AdvAgg is working correctly you can set the advagg_skip_404_check variable to TRUE in your settings.php. Editing the servers hosts file so the host name points to the localhost might also fix this (127.0.0.1 !hostname). To manually check go to @url, view the source (press ctrl+u on your keyboard) and check for this string @string. If that string is in the source, you can safely add this to your settings.php file @code', array( '!hostname' => $_SERVER['HTTP_HOST'], '@url' => $url, '@string' => '', @@ -841,23 +1413,32 @@ 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 + // - Not a 404 OR + // - No data returned OR + // - Headers do not contain "x-advagg" AND + // - Body does not contain "advagg_missing_fast404". + if ($request->code != 404 || empty($request->data) - || ( empty($request->headers['x-advagg']) + || (empty($request->headers['x-advagg']) && strpos($request->data, '') === FALSE - ) + ) ) { // Fast 404 check. $url_path_404 = parse_url($url, PHP_URL_PATH); @@ -865,16 +1446,34 @@ function advagg_install_check_via_http(array &$requirements) { $fast_404_html = variable_get('404_fast_html', '404 Not Found

Not Found

The requested URL "@path" was not found on this server.

'); // Replace @path in the variable with the page path. $fast_404_html = trim(strtr($fast_404_html, array('@path' => check_plain($url_path_404)))); - if ( !empty($request->data) + if (!empty($request->data) && $fast_404_html == trim($request->data) && !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'), @@ -883,8 +1482,7 @@ function advagg_install_check_via_http(array &$requirements) { 'description' => $t('If you have fast 404 enabled in your settings.php file, you need to change the 404_fast_paths_exclude setting so advagg will work.') . ' ' . $description, ); } - // @ignore sniffer_whitespace_openbracketspacing_openingwhitespace - elseif ( module_exists('fast_404') + elseif (module_exists('fast_404') && defined('FAST_404_EXT_CHECKED') && !in_array('/advagg_', variable_get('fast_404_string_whitelisting', array())) && strpos(variable_get('fast_404_exts', '/^(?!robots).*\.(txt|png|gif|jpe?g|css|js|ico|swf|flv|cgi|bat|pl|dll|exe|asp)$/i'), $type) !== FALSE @@ -897,10 +1495,9 @@ function advagg_install_check_via_http(array &$requirements) { array('@code' => '$conf[\'fast_404_string_whitelisting\'][] = \'/advagg_\';')), ); } - // @ignore sniffer_whitespace_openbracketspacing_openingwhitespace - elseif ( module_exists('stage_file_proxy') + elseif (module_exists('stage_file_proxy') && variable_get('stage_file_proxy_origin', NULL) - && strpos(file_get_contents(drupal_get_path('module', 'stage_file_proxy') . '/stage_file_proxy.module'), 'advagg') === FALSE + && strpos(advagg_file_get_contents(drupal_get_path('module', 'stage_file_proxy') . '/stage_file_proxy.module'), 'advagg') === FALSE ) { // Stage File Proxy patch is missing. $requirements['advagg_stage_file_proxy_' . $type . '_generation'] = array( @@ -935,13 +1532,100 @@ 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. If there are image style rules in your Nginx configuration add this right below that. !config_location Raw request info:
@request
', array( + '@request' => var_export($request, TRUE), + '@code' => ' +### +### 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 $uri/ @drupal; +}', + '!config_location' => $config_location, + )), + ); + } + elseif (!advagg_install_htaccess_errordocument($type)) { + $parsed_base_url = parse_url($GLOBALS['base_url']); + if (isset($parsed_base_url['scheme'])) { + unset($parsed_base_url['scheme']); + } + if ($type === 'css') { + $location = $css_path[1] . '/.htaccess'; + } + if ($type === 'js') { + $location = $js_path[1] . '/.htaccess'; + } + $requirements['advagg_' . $type . '_errordoc_404'] = array( + 'title' => $t('Adv CSS/JS Agg - HTTP Request'), + 'severity' => REQUIREMENT_WARNING, + 'value' => $t('HTTP requests to advagg for ' . $type . ' files are not getting through. The .htaccess needs to be rebuilt.'), + 'description' => $t('The .htaccess file generated by AdvAgg has the incorrect errordoc location. This can happen if Drush is used incorrectly or if the site has been moved to a different directory structure. If you are currently using drush this is how to access it correctly:

@drush

Odds are you will need to fix the errordoc location. Go to the AdvAgg: Operations page and under Regenerate .htaccess files press the Recreate htaccess files button. If you wish to manually edit the file go to the @htaccess_loc file and make sure the following line is in there near the top and any other ErrorDocument 404 statements have been removed.

@code

', array( + '@drush' => 'drush --root=' . DRUPAL_ROOT . '/ --uri=' . advagg_glue_url($parsed_base_url) . ' ', + '@url' => url($config_path . '/advagg/operations', array('fragment' => 'edit-htaccess')), + '@htaccess_loc' => $location, + '@code' => "ErrorDocument 404 {$GLOBALS['base_path']}index.php", + )), + ); + } + elseif (!is_null($s3fs_no_rewrite_cssjs) + && !empty($s3fs_no_rewrite_cssjs) + && !empty($request->headers['server']) + && $request->headers['server'] === 'AmazonS3' + ) { + $severity = REQUIREMENT_WARNING; + if (module_exists('httprl') && variable_get('advagg_use_httprl', ADVAGG_USE_HTTPRL)) { + $severity = REQUIREMENT_ERROR; + } + // S3 doesn't do origin pull. + $requirements['advagg_' . $type . '_generation'] = array( + 'title' => $t('Adv CSS/JS Agg - HTTP Request'), + 'severity' => $severity, + '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. In this case the s3fs Advanced Configuration Option "Don\'t render proxied CSS/JS file paths" should be disabled. Raw request info:
@request
', array( + '@request' => var_export($request, TRUE), + '@url' => url('admin/config/media/s3fs', array('fragment' => 'edit-s3fs-no-rewrite-cssjs')), + )), + ); + } 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. In some cases this can sometimes be a false report; go here: @url and check if the source (press ctrl+u on your keyboard) has an html comment that says "advagg_missing_fast404"; if it does, this is a false report, add this $conf[\'advagg_skip_404_check\'] = TRUE; to your settings.php file. Raw request info:
@request
', array( + '@request' => var_export($request, TRUE), + '@url' => $url, + )), ); } } @@ -967,7 +1651,8 @@ 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_path = (!is_null($s3fs_no_rewrite_cssjs) && empty($s3fs_no_rewrite_cssjs)) ? $url_path : $file_path; + $filename = advagg_install_get_first_advagg_file($filename_path, $type); // Skip if filename is empty. if (empty($filename)) { @@ -975,7 +1660,11 @@ function advagg_install_check_via_http(array &$requirements) { } $urls = array(); - $urls[] = file_create_url($url_path . '/' . $filename); + $url = file_create_url($url_path . '/' . $filename); + if (empty($url)) { + continue; + } + $urls[] = $url; if (module_exists('cdn')) { // Get CDN defaults. $blacklist = variable_get(CDN_EXCEPTION_DRUPAL_PATH_BLACKLIST_VARIABLE, CDN_EXCEPTION_DRUPAL_PATH_BLACKLIST_DEFAULT); @@ -997,8 +1686,11 @@ function advagg_install_check_via_http(array &$requirements) { 'Accept-Encoding' => 'gzip, deflate', ), 'version' => '1.0', + '#advagg_path' => "{$file_path}/{$filename}", + 'timeout' => 8, ); - // 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); // Test http 1.1 @@ -1007,11 +1699,46 @@ function advagg_install_check_via_http(array &$requirements) { $old = variable_get('drupal_http_request_function', FALSE); $GLOBALS['conf']['drupal_http_request_function'] = 'httprl_override_core'; + // Only test 1.1; 1.0 is rarely used these days. + $requirements = $old_requirements; + $options['version'] = '1.1'; advagg_install_chk_urls($requirements, $urls, $options, $mod_url, $type, $url_path, $file_path, $filename); $GLOBALS['conf']['drupal_http_request_function'] = $old; } + + if (function_exists('brotli_compress') + && defined('BROTLI_TEXT') + && variable_get('advagg_brotli', ADVAGG_BROTLI) + ) { + // Set arguments for drupal_http_request(). + $options = array( + 'headers' => array( + 'Accept-Encoding' => 'br', + ), + 'version' => '1.0', + 'timeout' => 8, + ); + // Test http 1.0. + $old_requirements = $requirements; + advagg_install_chk_urls($requirements, $urls, $options, $mod_url, $type, $url_path, $file_path, $filename); + + // Test http 1.1 + // If Drupal version is >= 7.22 and httprl_override_core exists. + if (defined('VERSION') && floatval(VERSION) >= 7.22 && is_callable('httprl_override_core')) { + $old = variable_get('drupal_http_request_function', FALSE); + $GLOBALS['conf']['drupal_http_request_function'] = 'httprl_override_core'; + + // Only test 1.1; 1.0 is rarely used these days. + $requirements = $old_requirements; + + $options['version'] = '1.1'; + advagg_install_chk_urls($requirements, $urls, $options, $mod_url, $type, $url_path, $file_path, $filename); + + $GLOBALS['conf']['drupal_http_request_function'] = $old; + } + } } } @@ -1039,7 +1766,22 @@ 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) { + list($css_path, $js_path) = advagg_get_root_files_dir(); + $config_path = advagg_admin_config_root_path(); + + $options += array( + 'timeout' => 8, + ); + + $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 = strtolower(pathinfo($url, PATHINFO_EXTENSION)); // Make sure the URL contains a schema. if (strpos($url, 'http') !== 0) { if ($GLOBALS['is_https']) { @@ -1050,77 +1792,319 @@ function advagg_install_chk_urls(array &$requirements, array $urls, array $optio } } + // Before sending the request when using s3fs, check if the file exists. + if (module_exists('s3fs') && !file_exists($url_path . '/' . $filename)) { + if (module_exists('httprl') && variable_get('advagg_use_httprl', ADVAGG_USE_HTTPRL)) { + $httprl_message = 'This may be due to an issue with the HTTPRL module or its configuration. '; + } + else { + $httprl_message = ''; + } + $requirements['advagg_' . $type . '_missing' . $key] = array( + 'title' => $t('Adv CSS/JS Agg - file does not exist'), + 'severity' => REQUIREMENT_WARNING, + 'value' => $t('Unable to find %type files.', array('%type' => $type)), + 'description' => $t('The AdvAgg database records and S3 files are not in sync. The file referenced in the database to perform a test cannot be found in the S3 file system. @httprl_messageFile URL:
@file
', array( + '@httprl_message' => $httprl_message, + '@file' => $url, + )), + ); + continue; + } + // Send request. advagg_install_url_mod($url, $options, $mod_url); $request = drupal_http_request($url, $options); + $encoding_type = 'gzip'; + if (!empty($request->options['headers']['Accept-Encoding']) && strpos($request->options['headers']['Accept-Encoding'], 'br') !== FALSE) { + $encoding_type = 'br'; + } - // @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 (!variable_get('advagg_skip_gzip_check', ADVAGG_SKIP_GZIP_CHECK)) { + // Check response. Report an error if + // - Not a 200. + // - Headers do not contain "content-encoding". + // - content-encoding is not gzip, deflate or br. + if ($request->code != 200 + || empty($request->headers['content-encoding']) + || ($request->headers['content-encoding'] !== 'gzip' + && $request->headers['content-encoding'] !== 'deflate' + && $request->headers['content-encoding'] !== 'br' + ) + ) { + // Gzip failed. + if (!variable_get('advagg_gzip', ADVAGG_GZIP) + && $encoding_type === '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, + )), + ); + } + elseif (function_exists('brotli_compress') + && defined('BROTLI_TEXT') + && !variable_get('advagg_brotli', ADVAGG_BROTLI) + && $encoding_type === 'br' + ) { + // Recommend that br be turned on. + $requirements['advagg_' . $type . '_br' . $key] = array( + 'title' => $t('Adv CSS/JS Agg - brotli'), + 'severity' => REQUIREMENT_WARNING, + 'value' => $t('Brotli is failing for %type files.', array('%type' => $type)), + 'description' => $t('Try enabling on the "Create .br 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 . '_' . $encoding_type] = 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 @encoding to fail.', array( + '!link' => 'http://httpd.apache.org/docs/current/mod/mod_headers.html', + '@encoding' => $encoding_type, + )), + 'severity' => REQUIREMENT_WARNING, + 'value' => $t('Apache module "mod_headers" is not installed.'), + ); + } + if ($mod_rewrite === FALSE) { + $requirements['advagg_mod_rewrite' . $key . '_' . $encoding_type] = 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 @encoding to fail.', array( + '!link' => 'http://httpd.apache.org/docs/current/mod/mod_rewrite.html', + '@encoding' => $encoding_type, + )), + 'severity' => REQUIREMENT_ERROR, + 'value' => $t('Apache module "mod_rewrite" 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) { + // Check via external service. + $ext_url = 'http://checkgzipcompression.com/?url=' . urlencode($url); + if ($encoding_type === 'br') { + $ext_url = 'https://tools.keycdn.com/brotli-query.php?url=' . urlencode($url) . '&public=0'; + } + $external_compression_request = drupal_http_request($ext_url, array( + 'timeout' => 7, + 'headers' => array('Connection' => 'close'), + )); + if (!empty($external_compression_request->data)) { + if ($encoding_type === 'br') { + if (stripos($external_compression_request->data, '') !== FALSE) { + preg_match("/(.*)<\/strong>/siU", $external_compression_request->data, $title_matches); + if (stripos($title_matches[1], 'Negative') === FALSE) { + $external_test_results = 1; + } + else { + $external_test_results = -1; + } + } + else { + $external_test_results = 0; + } + } + elseif (stripos($external_compression_request->data, '') !== FALSE) { + preg_match("/<title>(.*)<\/title>/siU", $external_compression_request->data, $title_matches); + if (stripos($title_matches[1], 'gzip') === FALSE) { + $external_test_results = 0; + } + elseif (stripos($title_matches[1], 'not gzip') === FALSE) { + $external_test_results = 1; + } + else { + $external_test_results = -1; + } + } + } + if (!isset($external_test_results) || $external_test_results !== 1) { + if ($request->code != 200) { + $rewritebase = advagg_htaccess_rewritebase(); + if (!empty($rewritebase)) { + if ($type === 'css') { + $rewritebase_advagg = advagg_htaccess_rewritebase($css_path[1]); + } + if ($type === 'js') { + $rewritebase_advagg = advagg_htaccess_rewritebase($js_path[1]); + } + } + $advagg_htaccess_rewritebase = variable_get('advagg_htaccess_rewritebase', ADVAGG_HTACCESS_REWRITEBASE); + if ($request->code == 307 && !empty($rewritebase) && empty($rewritebase_advagg) && empty($advagg_htaccess_rewritebase)) { + $requirements['advagg_' . $type . $encoding_type . $key . $options['version']] = array( + 'title' => $t('Adv CSS/JS Agg - @encoding', array('@encoding' => $encoding_type)), + 'severity' => REQUIREMENT_WARNING, + 'value' => $t('@encoding is failing for %type files.', array( + '%type' => $type, + '@encoding' => $encoding_type, + )), + 'description' => $t('The web server is not returning a 200, instead a @code is being returned. The RewriteBase option should be set on the <a href="@url">configuration page</a> under "Obscure Options" look for "AdvAgg RewriteBase Directive in .htaccess files". Raw request info: <pre>@request</pre>', array( + '@code' => $request->code, + '@encoding' => $encoding_type, + '@request' => print_r($request, TRUE), + '@url' => url($config_path . '/advagg', array('fragment' => 'edit-advagg-htaccess-rewritebase')), + )), + ); + } + else { + if (module_exists('s3fs') && ($request->code == 307 || $request->code == -2)) { + $requirements['advagg_' . $type . $encoding_type . $key . $options['version']] = array( + 'title' => $t('Adv CSS/JS Agg - Redirect Loop'), + 'severity' => REQUIREMENT_WARNING, + 'value' => $t('S3fs issue. Proxy is not setup correctly for %type.', array( + '%type' => $type, + )), + 'description' => $t('The web server is not returning a 200, instead a @code is being returned. Apache proxy settings for httpd.conf: <p><code>!httpd</code></p>. Raw request info: <pre>@request</pre>', array( + '!httpd' => nl2br(str_replace(' ', '  ', htmlentities(advagg_install_s3fs_proxy_settings($type)))), + '@code' => $request->code, + '@encoding' => $encoding_type, + '@request' => print_r($request, TRUE), + )), + ); + } + else { + $requirements['advagg_' . $type . $encoding_type . $key . $options['version']] = array( + 'title' => $t('Adv CSS/JS Agg - @encoding', array('@encoding' => $encoding_type)), + 'severity' => REQUIREMENT_WARNING, + 'value' => $t('@encoding is failing for %type files.', array( + '%type' => $type, + '@encoding' => $encoding_type, + )), + 'description' => $t('The web server is not returning a 200, instead a @code is being returned. @encoding can not be tested. Raw request info: <pre>@request</pre>', array( + '@code' => $request->code, + '@encoding' => $encoding_type, + '@request' => print_r($request, TRUE), + )), + ); + } + } + } + elseif (empty($request->data)) { + $url = 'http://checkgzipcompression.com/?url=' . urlencode($url); + if ($encoding_type === 'br') { + $url = 'https://tools.keycdn.com/brotli-query.php?url=' . urlencode($url) . '&public=0'; + } + $requirements['advagg_' . $type . $encoding_type . $key . $options['version']] = array( + 'title' => $t('Adv CSS/JS Agg - @encoding', array('@encoding' => $encoding_type)), + 'severity' => REQUIREMENT_WARNING, + 'value' => $t('@encoding is failing for %type files.', array( + '%type' => $type, + '@encoding' => $encoding_type, + )), + 'description' => $t('No data was returned from your server; this @encoding test can not be done locally. Error: @error - @message You can manually test if @encoding is working by <a href="@urlGZ">going here</a> and seeing if @encoding is enabled. You can turn this warning off by adding this to your settings.php file: <code>@skipcode</code>', array( + '@error' => $request->code, + '@message' => isset($request->error) ? $request->error : '', + '@url' => $url, + '@skipcode' => '$conf[\'advagg_skip_gzip_check\'] = TRUE;', + '@encoding' => $encoding_type, + )), + ); + } + else { + // Recommend servers configuration be adjusted. + $request->data = '...'; + $requirements['advagg_' . $type . $encoding_type . $key . $options['version']] = array( + 'title' => $t('Adv CSS/JS Agg - @encoding', array('@encoding' => $encoding_type)), + 'severity' => REQUIREMENT_WARNING, + 'value' => $t('@encoding is failing for %type files.', array( + '%type' => $type, + '@encoding' => $encoding_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". Also check in the <a href="@readme">readme</a>, under Troubleshooting. Certain default web server configurations (<a href="!nginx">nginx</a>) do not gzip HTTP/1.0 requests. If you are using cloudfront you will have to <a href="!cloudfront">add metadata</a> to the .gz files. There are some other options if using <a href="!so">cloudfront</a>. Raw request info: <pre>@request</pre>', 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', + '@readme' => url(drupal_get_path('module', 'advagg') . '/README.txt'), + )), + ); + } } } } - if (!$apache_module_missing) { - // Recommend servers configuration be adjusted. - $request->data = '...'; + } + elseif ($request->code == 200 + && !empty($request->headers['content-encoding']) + && !empty($request->data) + && ($request->headers['content-encoding'] === 'gzip' + || $request->headers['content-encoding'] === 'deflate' + || $request->headers['content-encoding'] === 'br' + )) { + // Do the first level of decoding if not already done. + if (!isset($request->chunk_size)) { + if ($request->headers['content-encoding'] === 'gzip') { + $request->data = @gzinflate(substr($request->data, 10)); + } + elseif ($request->headers['content-encoding'] === 'deflate') { + $request->data = @gzinflate($request->data); + } + elseif ($request->headers['content-encoding'] === 'br' && is_callable('brotli_uncompress')) { + $request->data = @brotli_uncompress($request->data); + } + } + // Check for double gzip compression. + $contents = @file_get_contents($options['#advagg_path']); + if ($contents !== $request->data && (@gzinflate(substr($request->data, 10)) !== FALSE || @gzinflate($request->data) !== FALSE)) { + $config_path = advagg_admin_config_root_path(); + $description = ''; + if (variable_get('advagg_gzip', ADVAGG_GZIP)) { + $description .= $t('Go to the Advanced CSS/JS aggregation <a href="@settings">settings page</a>, under Obsucre Options uncheck the Create .gz files setting.', array( + '@settings' => url($config_path . '/advagg', array('fragment' => 'edit-obscure')), + )); + } + else { + $description .= $t('Your webserver configuration needs to be changed so that %type files are not being double compressed.', array( + '%type' => $type, + )); + if (isset($request->headers['content-type'])) { + $description .= ' ' . $t('The content type is: %type.', array( + '%type' => $request->headers['content-type'], + )); + } + } $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 (<a href="!nginx">nginx</a>) do not gzip HTTP/1.0 requests. If you are using cloudfront you will have to <a href="!cloudfront">add metadata</a> to the .gz files. There are some other options if using <a href="!so">cloudfront</a>. Raw request info: <pre>@request</pre>', 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', - )), + 'severity' => REQUIREMENT_ERROR, + 'value' => $t('Double gzip encoding detected for %type files.', array('%type' => $type)), + 'description' => $description, + ); + } + if ($contents !== $request->data && (is_callable('brotli_uncompress') && @brotli_uncompress($request->data) !== FALSE)) { + $config_path = advagg_admin_config_root_path(); + $description = ''; + if (variable_get('advagg_brotli', ADVAGG_BROTLI)) { + $description .= $t('Go to the Advanced CSS/JS aggregation <a href="@settings">settings page</a>, under Obsucre Options uncheck the Create .br files setting.', array( + '@settings' => url($config_path . '/advagg', array('fragment' => 'edit-obscure')), + )); + } + else { + $description .= $t('Your webserver configuration needs to be changed so that %type files are not being double compressed.', array( + '%type' => $type, + )); + if (isset($request->headers['content-type'])) { + $description .= ' ' . $t('The content type is: %type.', array( + '%type' => $request->headers['content-type'], + )); + } + } + $requirements['advagg_' . $type . '_br' . $key . $options['version']] = array( + 'title' => $t('Adv CSS/JS Agg - br'), + 'severity' => REQUIREMENT_ERROR, + 'value' => $t('Double br encoding detected for %type files.', array('%type' => $type)), + 'description' => $description, ); } } @@ -1129,31 +2113,35 @@ function advagg_install_chk_urls(array &$requirements, array $urls, array $optio 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']) + 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, & If-Modified-Since might be incorrect. In the <a href="@readme">readme</a>, 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 <a href="@readme">readme</a>, under Troubleshooting try the Far-Future recommendations.', array('@readme' => url(drupal_get_path('module', 'advagg') . '/README.txt'))), + ); + } } - if ( $type === 'css' - && ( empty($request->headers['content-type']) + if ($type === 'css' + && (empty($request->headers['content-type']) || strpos($request->headers['content-type'], 'text/' . $content_type) === FALSE - ) + ) ) { // Recommend servers configuration be adjusted. $requirements['advagg_' . $type . '_type' . $key] = array( @@ -1167,12 +2155,12 @@ function advagg_install_chk_urls(array &$requirements, array $urls, array $optio ); } - if ( $type === 'js' - && ( empty($request->headers['content-type']) - || ( strpos($request->headers['content-type'], 'application/' . $content_type) === FALSE + if ($type === 'js' + && (empty($request->headers['content-type']) + || (strpos($request->headers['content-type'], 'application/' . $content_type) === FALSE && strpos($request->headers['content-type'], 'application/x-' . $content_type) === FALSE - ) ) + ) ) { // Recommend servers configuration be adjusted. $requirements['advagg_' . $type . '_type' . $key] = array( @@ -1189,115 +2177,191 @@ function advagg_install_chk_urls(array &$requirements, array $urls, array $optio // Test far future headers. $apache_module_missing = FALSE; - // Make sure the expires header is at least set to 1 month - // (2628000 seconds) in the future. - if ( !empty($request->headers['expires']) - && strtotime($request->headers['expires']) < time() + 2628000 + if (!variable_get('advagg_skip_far_future_check', ADVAGG_SKIP_FAR_FUTURE_CHECK) + && empty($_SERVER['PANTHEON_ENVIRONMENT']) + && empty($_SERVER['PANTHEON_SITE_NAME']) ) { - // Recommend servers configuration be adjusted. - if (function_exists('apache_get_modules') && !in_array('mod_headers', $modules)) { - $apache_module_missing = TRUE; - } - else { - $requirements['advagg_' . $type . '_expires' . $key] = array( - '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 <code>@received</code>.', array( - '@received' => isset($request->headers['expires']) ? number_format(strtotime($request->headers['expires'])) : 'NULL', - )), - ); - } - } - // Make sure the cache-control header max age value is at least set to - // 1 month (2628000 seconds) in the future. - $matches = array(); - if ( empty($request->headers['cache-control']) - || !preg_match('/max-age=(\\d+)/is', $request->headers['cache-control'], $matches) - || $matches[1] < 2628000 - ) { - // Recommend servers configuration be adjusted. - if (function_exists('apache_get_modules') && (!in_array('mod_headers', $modules))) { - $apache_module_missing = TRUE; + // Make sure the expires header is at least set to 1 month + // (2628000 seconds) in the future. + if (!empty($request->headers['expires']) + && strtotime($request->headers['expires']) < time() + 2628000 + ) { + // Recommend servers configuration be adjusted. + if ($is_apache && $mod_headers === FALSE) { + $apache_module_missing = TRUE; + } + else { + $requirements['advagg_' . $type . '_expires' . $key] = array( + '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.'), + '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 <code>@received</code> (@expires). You can turn this warning off if you can not adjust this value (Pantheon) by adding this to your settings.php file: <code>@skipcode</code>', 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;', + )), + ); + } } - else { - $requirements['advagg_' . $type . '_cache_control' . $key] = array( - 'title' => $t('Adv CSS/JS Agg - Cache-Control'), - 'severity' => REQUIREMENT_WARNING, - '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 <code>@received</code>.', array( - '@received' => isset($request->headers['cache-control']) ? $request->headers['cache-control'] : 'NULL', - )), - ); + // Make sure the cache-control header max age value is at least set to + // 1 month (2628000 seconds) in the future. + $matches = array(); + if (empty($request->headers['cache-control']) + || !preg_match('/\s*max-age\s*=\s*(\\d+)\s*/is', $request->headers['cache-control'], $matches) + || $matches[1] < 2628000 + ) { + // Recommend servers configuration be adjusted. + if ($is_apache && $mod_headers === FALSE) { + $apache_module_missing = TRUE; + } + else { + $requirements['advagg_' . $type . '_cache_control' . $key] = array( + 'title' => $t('Adv CSS/JS Agg - Cache-Control'), + 'severity' => REQUIREMENT_WARNING, + 'value' => $t("The cache-control's max-age header being sent by your web server is not at least 1 month in the future."), + '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 <code>@received</code>. You can turn this warning off if you can not adjust this value (Pantheon) by adding this to your settings.php file: <code>@skipcode</code>', array( + '@received' => isset($request->headers['cache-control']) ? $request->headers['cache-control'] : 'NULL', + '@skipcode' => '$conf[\'advagg_skip_far_future_check\'] = TRUE;', + )), + ); + } } } + // 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 <a href="!link">mod_headers</a> 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 <a href="!link">mod_expires</a> 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. - if (isset($request->headers['last-modified'])) { - // Send a If-Modified-Since header and see if the web server returns - // 304. - $url = file_create_url($url_path . '/' . $filename); - $if_modified_options = $options; - $if_modified_options['headers'] = array( - 'Accept-Encoding' => 'gzip, deflate', - 'If-Modified-Since' => $request->headers['last-modified'], - ); + if (!variable_get('advagg_skip_304_check', ADVAGG_SKIP_304_CHECK) + && empty($_SERVER['PANTHEON_ENVIRONMENT']) + && empty($_SERVER['PANTHEON_SITE_NAME']) + ) { + $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); + $if_modified_options = $options; + $if_modified_options['headers'] = array( + 'Accept-Encoding' => 'gzip, deflate, br', + 'If-None-Match' => $request->headers['etag'], + ); + if (!empty($request->options['headers']['Accept-Encoding'])) { + $if_modified_options['headers']['Accept-Encoding'] = $request->options['headers']['Accept-Encoding']; + } - // Send request. - advagg_install_url_mod($url, $if_modified_options, $mod_url); - $request = drupal_http_request($url, $if_modified_options); - if ($request->code != 304) { - // Recommend servers configuration be adjusted. - $requirements['advagg_' . $type . '_304' . $key] = array( - 'title' => $t('Adv CSS/JS Agg - If-Modified-Since'), - 'severity' => REQUIREMENT_WARNING, - 'value' => $t('The If-Modified-Since header is being ignored by your web server.'), - 'description' => $t('The web servers configuration will need to be adjusted. The server should have responded with a 304, instead a @code was returned.', array('@code' => $request->code)), + // Send request. + advagg_install_url_mod($url, $if_modified_options, $mod_url); + $request_304 = drupal_http_request($url, $if_modified_options); + if ($request_304->code != 304) { + // Recommend servers configuration be adjusted. + $requirements['advagg_' . $type . '_304' . $key . '_etag'] = array( + 'title' => $t('Adv CSS/JS Agg - Etag'), + 'severity' => REQUIREMENT_WARNING, + 'value' => $t('The If-None-Match (Etag) header is being ignored by your web server.'), + 'description' => $t('The web servers configuration will need to be adjusted. The server should have responded with a 304, instead a @code was returned.', array( + '@code' => $request_304->code, + )), + ); + } + else { + $etag_works = TRUE; + } + } + if (isset($request->headers['last-modified'])) { + // Send a If-Modified-Since header and see if the web server returns a + // 304. + $url = file_create_url($url_path . '/' . $filename); + $if_modified_options = $options; + $if_modified_options['headers'] = array( + 'Accept-Encoding' => 'gzip, deflate, br', + 'If-Modified-Since' => $request->headers['last-modified'], ); + if (!empty($request->options['headers']['Accept-Encoding'])) { + $if_modified_options['headers']['Accept-Encoding'] = $request->options['headers']['Accept-Encoding']; + } + + // Send request. + advagg_install_url_mod($url, $if_modified_options, $mod_url); + $request_304 = drupal_http_request($url, $if_modified_options); + if ($request_304->code != 304) { + // Recommend servers configuration be adjusted. + $requirements['advagg_' . $type . '_304' . $key . '_last_modified'] = array( + 'title' => $t('Adv CSS/JS Agg - If-Modified-Since'), + 'severity' => REQUIREMENT_WARNING, + 'value' => $t('The If-Modified-Since (Last-Modified) header is being ignored by your web server.'), + 'description' => $t('The web servers configuration will need to be adjusted. The server should have responded with a 304, instead a @code was returned.', array( + '@code' => $request_304->code, + )), + ); + } + elseif (isset($requirements['advagg_' . $type . '_304' . $key . '_etag'])) { + // Last-Modified works, Etag is broken. 304s are working. Don't warn + // user. + unset($requirements['advagg_' . $type . '_304' . $key . '_etag']); + } } - } - else { - // Get path to advagg .htaccess file. - $files = array(); - $files[] = $file_path . '/.htaccess'; - $files[] = DRUPAL_ROOT . '/.htaccess'; - - // Check for bad .htaccess files. - $bad_config_found = FALSE; - foreach ($files as $file) { - if (!file_exists($file)) { - continue; + 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'])) { + // Get path to advagg .htaccess file. + $files = array( + $file_path . '/.htaccess', + DRUPAL_ROOT . '/.htaccess', + ); + + // Check for bad .htaccess files. + $bad_config_found = FALSE; + foreach ($files as $count => $file) { + if (!file_exists($file)) { + continue; + } + $contents = advagg_file_get_contents($file); + if (strpos($contents, 'Header unset Last-Modified') !== FALSE) { + $bad_config_found = TRUE; + $requirements['advagg_' . $type . '_last-modified_' . $key . $count] = array( + 'title' => $t('Adv CSS/JS Agg - Last-Modified'), + 'severity' => REQUIREMENT_WARNING, + 'value' => $t('The Last-Modified header is not being sent out by your web server.'), + 'description' => $t('The web servers configuration will need to be adjusted. The server should have sent out a Last-Modified header. Remove "Header unset Last-Modified" inside this file to fix the issue: @file', array('@file' => $file)), + ); + } } - $contents = file_get_contents($file); - if (strpos($contents, 'Header unset Last-Modified') !== FALSE) { - $bad_config_found = TRUE; + + // Recommend servers configuration be adjusted. + if (!$bad_config_found) { $requirements['advagg_' . $type . '_last-modified_' . $key] = array( 'title' => $t('Adv CSS/JS Agg - Last-Modified'), 'severity' => REQUIREMENT_WARNING, 'value' => $t('The Last-Modified header is not being sent out by your web server.'), - 'description' => $t('The web servers configuration will need to be adjusted. The server should have sent out a Last-Modified header. Remove "Header unset Last-Modified" inside this file to fix the issue: @file', array('@file' => $file)), + 'description' => $t('The web servers configuration will need to be adjusted. The server should have sent out a Last-Modified header and/or an Etag header. If you can not get the Last-Modified header to work, you can try using ETags. Inside <code>@htaccess</code> right before <code>@line1</code> at the bottom of the file add in <code>@line2</code>. You will also need to remove the <code>@line3</code> line further up.', array( + '@htaccess' => $file_path . '/.htaccess', + '@line1' => '</FilesMatch>', + '@line2' => 'FileETag MTime Size', + '@line3' => 'Header unset ETag', + )), ); } } - - // Recommend servers configuration be adjusted. - if (!$bad_config_found) { - $requirements['advagg_' . $type . '_last-modified_' . $key] = array( - 'title' => $t('Adv CSS/JS Agg - Last-Modified'), - 'severity' => REQUIREMENT_WARNING, - 'value' => $t('The Last-Modified header is not being sent out by your web server.'), - 'description' => $t('The web servers configuration will need to be adjusted. The server should have sent out a Last-Modified header.'), - ); - } } } } @@ -1308,11 +2372,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 = ''; @@ -1320,7 +2386,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_generate_advagg_filename_from_db($type); } // Filter list. $blacklist = array('..', '.', '.htaccess', 'parts'); @@ -1330,7 +2397,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; @@ -1345,10 +2412,42 @@ function advagg_install_get_first_advagg_file($directory) { } } - $gzip_filename = $scanned_directory[$key+1]; + $gzip_filename = $scanned_directory[$key + 1]; + $br_filename = $scanned_directory[$key + 1]; + if (function_exists('brotli_compress') + && defined('BROTLI_TEXT') + && variable_get('advagg_brotli', ADVAGG_BROTLI) + ) { + $gzip_filename = $scanned_directory[$key + 2]; + // Skip if the next file does not have a .br extension. + // This can occur if: + // - File is not .br compressed, or, + // - Using s3fs module and only .br compression is set. In + // this case, the advagg_advadgg_save_aggregate_alter() + // function will not add a file extension. + if (strcmp($filename . '.br', $br_filename) !== 0 + && (!module_exists('s3fs') + || (module_exists('s3fs') && variable_get('advagg_gzip', ADVAGG_GZIP)))) { + continue; + } + } + else { + // Skip if the next file is a .br file. + if (strcmp($filename . '.br', $br_filename) === 0) { + continue; + } + } + if (variable_get('advagg_gzip', ADVAGG_GZIP)) { - // Skip if the next file is not a .gz file. - if (strcmp($filename . '.gz', $gzip_filename) !== 0) { + // Skip if the next file does not have a .gz extension. + // This can occur if: + // - File is not .gz compressed, or, + // - Using s3fs module and either: + // - Only .gz compression option is set or, + // - Both .gz and .br compression options are set. In + // this case, the advagg_advagg_save_aggregate_alter() + // function creates a .gz file by default. + if (strcmp($filename . '.gz', $gzip_filename) !== 0 && !module_exists('s3fs')) { continue; } } @@ -1373,6 +2472,9 @@ function advagg_install_get_first_advagg_file($directory) { } } + if (empty($filename)) { + return advagg_generate_advagg_filename_from_db($type); + } return $filename; } @@ -1395,12 +2497,6 @@ function advagg_install_url_mod(&$url, array &$options, $mod_url = FALSE) { 'connect_timeout' => 8, 'ttfb_timeout' => 8, ); - if (!empty($_SERVER['HTTP_HOST'])) { - $options['headers']['Host'] = $_SERVER['HTTP_HOST']; - } - elseif (!empty($_SERVER['SERVER_NAME'])) { - $options['headers']['Host'] = $_SERVER['SERVER_NAME']; - } // Set connection to closed to prevent keep-alive from causing a timeout. $options['headers']['Connection'] = 'close'; // Set referrer to current page. @@ -1411,16 +2507,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); @@ -1433,14 +2552,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'; @@ -1453,3 +2578,291 @@ function advagg_install_url_mod(&$url, array &$options, $mod_url = FALSE) { } $url = $new_url; } + +/** + * 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 multiple 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; +} + +/** + * Convert the table to the specified collation. + * + * @param string $table_name + * Perform the operation on this table. + * @param array $fields + * An array of field names. + * @param string $collation + * The db collation to change to table columns to. + * @param array $schema_fields + * An array of field definitions. + * + * @return array + * Returns an array of tables and column names. + */ +function advagg_install_change_table_collation($table_name, array $fields, $collation, array $schema_fields) { + $db_type = Database::getConnection()->databaseType(); + // Skip if not MySQL. + if ($db_type !== 'mysql') { + return FALSE; + } + $table_name = Database::getConnection()->prefixTables('{' . db_escape_table($table_name) . '}'); + $results = db_query("SHOW FULL FIELDS FROM $table_name")->fetchAllAssoc('Field'); + $db_schema = Database::getConnection()->schema(); + foreach ($results as $row) { + if (!in_array($row->Field, $fields)) { + continue; + } + + $charset = strtolower(substr($collation, 0, strpos($collation, '_'))); + $query = "ALTER TABLE $table_name CHANGE `{$row->Field}` `{$row->Field}` {$row->Type}"; + $query .= " CHARACTER SET $charset COLLATE $collation"; + + if (isset($schema_fields[$row->Field]['not null'])) { + if ($schema_fields[$row->Field]['not null']) { + $query .= ' NOT NULL'; + } + else { + $query .= ' NULL'; + } + } + + // $schema_fields[$row->Field]['default'] can be NULL, so we explicitly + // check for the key here. + 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 . "'"; + } + elseif (!isset($default)) { + $default = 'NULL'; + } + $query .= ' DEFAULT ' . $default; + } + + if (empty($schema_fields[$row->Field]['not null']) && !isset($schema_fields[$row->Field]['default'])) { + $query .= ' DEFAULT NULL'; + } + + // Add column comment. + if (!empty($schema_fields[$row->Field]['description'])) { + $query .= ' COMMENT ' . $db_schema->prepareComment($schema_fields[$row->Field]['description'], 255); + } + + db_query($query); + } +} + +/** + * Callback to delete files if size == 0 and modified more than 60 seconds ago. + * + * @param string $uri + * Location of the file to check. + */ +function advagg_install_delete_empty_file_if_stale($uri) { + // Set stale file threshold to 60 seconds. + if (filesize($uri) < 3 && REQUEST_TIME - filemtime($uri) > 60) { + file_unmanaged_delete($uri); + } +} + +/** + * Callback to delete files if size == 0 and modified more than 60 seconds ago. + * + * @param string $has_string + * If the .htaccess file contains this string it will be removed and + * recreated. + * @param string $does_not_have_string + * If the .htaccess file does not contains this string it will be removed & + * recreated. + * + * @return string + * Translated string indicating what was done. + */ +function advagg_install_update_htaccess($has_string = '', $does_not_have_string = '') { + // Make sure the advagg_get_root_files_dir() function is available. + drupal_load('module', 'advagg'); + + // Get paths to .htaccess file. + list($css_path, $js_path) = advagg_get_root_files_dir(); + $files['css'] = $css_path[0] . '/.htaccess'; + $files['js'] = $js_path[0] . '/.htaccess'; + + // Check for old .htaccess files. + $something_done = FALSE; + foreach ($files as $type => $uri) { + if (!file_exists($uri)) { + unset($files[$type]); + continue; + } + $contents = advagg_file_get_contents($uri); + // Remove old .htaccess file if it has this string. + if (!empty($has_string) && strpos($contents, $has_string) !== FALSE) { + drupal_unlink($uri); + $something_done = TRUE; + } + // Remove old .htaccess file if it does not have this string. + if (!empty($does_not_have_string) && strpos($contents, $does_not_have_string) === FALSE) { + drupal_unlink($uri); + $something_done = TRUE; + } + } + + // Create the new .htaccess file. + $new_htaccess = FALSE; + if (!empty($files) && $something_done && variable_get('advagg_htaccess_check_generate', ADVAGG_HTACCESS_CHECK_GENERATE)) { + // Make the advagg_htaccess_check_generate() function available. + module_load_include('inc', 'advagg', 'advagg.missing'); + foreach ($files as $type => $uri) { + advagg_htaccess_check_generate(array($uri => $type), $type, TRUE); + $new_htaccess = TRUE; + } + } + + // Output info. + if ($something_done) { + if ($new_htaccess) { + return t('Removed the old .htaccess file and put in a new one.'); + } + else { + return t('Removed the old .htaccess file.'); + } + } + else { + return t('Nothing needed to be done.'); + } +} + +/** + * See if the .htaccess file uses the RewriteBase directive. + * + * @param string $type + * Either css or js. + * + * @return bool + * FALSE if the ErrorDocument 404 statement is incorrect. + */ +function advagg_install_htaccess_errordocument($type) { + list($css_path, $js_path) = advagg_get_root_files_dir(); + if ($type === 'css') { + $location = $css_path[1] . '/.htaccess'; + } + if ($type === 'js') { + $location = $js_path[1] . '/.htaccess'; + } + $good = TRUE; + + // Get the location of the 404 error doc. + if (is_readable($location)) { + $htaccess = advagg_file_get_contents($location); + $matches = array(); + $found = preg_match_all('/\\n\s*ErrorDocument\s*404\s*(.*)/i', $htaccess, $matches); + if ($found && !empty($matches[0])) { + $matches[1] = array_map('trim', $matches[1]); + $location = array_pop($matches[1]); + } + } + else { + return $good; + } + // If it's pointing to the wrong place or doesn't exist return FALSE. + if (!empty($location) && $location !== "{$GLOBALS['base_path']}index.php") { + $good = FALSE; + } + if (empty($location) && $GLOBALS['base_path'] !== '/') { + $good = FALSE; + } + + return $good; +} + +/** + * Create proxy settings. + * + * @return string + * Apache httpd.conf settings for the s3fs module. + */ +function advagg_install_s3fs_proxy_settings() { + list($css_path, $js_path) = advagg_get_root_files_dir(); + + $position = strpos($css_path[0], '://'); + $dir = ''; + if ($position !== FALSE) { + $dir = substr($css_path[0], $position + 3); + } + $css_target = str_replace($dir, '', file_create_url($css_path[0])); + $position = strpos($js_path[0], '://'); + $dir = ''; + if ($position !== FALSE) { + $dir = substr($js_path[0], $position + 3); + } + $js_target = str_replace($dir, '', file_create_url($js_path[0])); + $scheme = parse_url($js_target, PHP_URL_SCHEME); + + $config = ''; + $extra = ''; + if ($scheme === 'http') { + $port = '80'; + } + elseif ($scheme === 'https') { + $port = '443'; + $extra = " SSLProxyEngine on\n"; + } + $config .= "<VirtualHost *:$port>\n"; + $config .= " ProxyRequests Off\n"; + $config .= $extra; + $config .= " <Proxy *>\n"; + $config .= " Order deny,allow\n"; + $config .= " Allow from all\n"; + $config .= " </Proxy>\n"; + $config .= " ProxyTimeout 4\n"; + $config .= " ProxyPass {$GLOBALS['base_path']}s3fs-css/ $css_target\n"; + $config .= " ProxyPassReverse {$GLOBALS['base_path']}s3fs-css/ $css_target\n"; + $config .= " ProxyPass {$GLOBALS['base_path']}s3fs-js/ $js_target\n"; + $config .= " ProxyPassReverse {$GLOBALS['base_path']}s3fs-js/ $js_target\n"; + $config .= " ProxyErrorOverride On\n"; + $config .= " ErrorDocument 404 {$GLOBALS['base_path']}index.php\n"; + $config .= "</VirtualHost>\n"; + + return $config; +} diff --git a/sites/all/modules/contrib/advagg/advagg.make.example b/sites/all/modules/contrib/advagg/advagg.make.example new file mode 100644 index 0000000000..1971310e1e --- /dev/null +++ b/sites/all/modules/contrib/advagg/advagg.make.example @@ -0,0 +1,50 @@ +; Rename this file to advagg.make to have it included within a drush make +; command. + +api = 2 +core = 7.x + +; Contrib module: libraries +; --------------------------------------- +projects[libraries][version] = "2" + +; JavaScript libraries +; --------------------------------------- + +; Library: CSSLint. +libraries[csslint][download][type] = git +libraries[csslint][download][url] = https://github.com/CSSLint/csslint + +; Library: fontfaceobserver. +libraries[fontfaceobserver][download][type] = git +libraries[fontfaceobserver][download][url] = https://github.com/bramstein/fontfaceobserver.git + +; Library: jshint. +libraries[jshint][download][type] = git +libraries[jshint][download][url] = https://github.com/jshint/jshint + +; Library: JShrink. +libraries[JShrink][download][type] = git +libraries[JShrink][download][url] = https://github.com/tedious/JShrink + +; Library: JSMinPlus. +libraries[jsminplus][download][type] = git +libraries[jsminplus][download][url] = https://github.com/JSMinPlus/JSMinPlus +libraries[jsminplus][directory_name] = "jsminplus" + +; Library: jspacker. +libraries[jspacker][download][type] = git +libraries[jspacker][download][url] = https://github.com/tholu/php-packer +libraries[jspacker][directory_name] = "jspacker" + +; Library: jsqueeze. +libraries[jsqueeze][download][type] = git +libraries[jsqueeze][download][url] = https://github.com/tchwork/jsqueeze + +; Library: loadCSS. +libraries[loadCSS][download][type] = git +libraries[loadCSS][download][url] = https://github.com/filamentgroup/loadCSS + +; Library: YUI-CSS-compressor-PHP-port. +libraries[YUI-CSS-compressor-PHP-port][download][type] = git +libraries[YUI-CSS-compressor-PHP-port][download][url] = https://github.com/tubalmartin/YUI-CSS-compressor-PHP-port diff --git a/sites/all/modules/contrib/advagg/advagg.missing.inc b/sites/all/modules/contrib/advagg/advagg.missing.inc index 4ab239eb49..2bd1dddbdf 100644 --- a/sites/all/modules/contrib/advagg/advagg.missing.inc +++ b/sites/all/modules/contrib/advagg/advagg.missing.inc @@ -17,6 +17,40 @@ function advagg_missing_aggregate($input = '') { // Generate missing file. $msg = advagg_missing_generate($input); + if (module_exists('jquery_update')) { + $arg = arg(); + $filename = array_pop($arg); + $filename = explode('?', $filename); + $filename = array_shift($filename); + if (strpos($filename, 'min.map') !== FALSE && strpos($filename, 'jquery') !== FALSE) { + // Get filename from request. + $wrong_pattern = t('Wrong pattern.'); + if ($msg === $wrong_pattern) { + $version = variable_get('jquery_update_jquery_version', '1.10'); + $trueversion = '1.9.1'; + switch ($version) { + case '1.9': + $trueversion = '1.9.1'; + break; + + case '1.10': + $trueversion = '1.10.2'; + break; + + case '1.11': + $trueversion = '1.11.2'; + break; + + case '2.1': + $trueversion = '2.1.4'; + break; + } + $url = "https://cdn.jsdelivr.net/gh/jquery/jquery@$trueversion/jquery.min.map"; + drupal_goto($url, array('external' => TRUE), 301); + } + } + } + // If here send out fast 404. advagg_missing_fast404($msg); } @@ -46,9 +80,12 @@ function advagg_missing_generate($input = '') { if (is_string($data) || !is_array($data)) { // Try again with the function input. $filename = $input; - $data = advagg_get_hashes_from_filename($filename); + $data1 = advagg_get_hashes_from_filename($filename); if (is_string($data) || !is_array($data)) { - return $data; + return "$data $data1"; + } + else { + $data = $data1; } } @@ -75,7 +112,17 @@ function advagg_missing_generate($input = '') { $uri = $GLOBALS['base_path'] . $_GET['q']; $created = FALSE; $files_to_save = array(); - if (lock_acquire($lock_name, 10) || $redirect_counter > 4) { + if (variable_get('advagg_no_locks', ADVAGG_NO_LOCKS)) { + $return = advagg_missing_create_file($filename, FALSE, $data); + if (!is_array($return)) { + return $return; + } + else { + list($files_to_save, $type) = $return; + $created = TRUE; + } + } + elseif (lock_acquire($lock_name, 10) || $redirect_counter > 4) { if ($redirect_counter > 4) { $return = advagg_missing_create_file($filename, TRUE, $data); } @@ -121,7 +168,7 @@ function advagg_missing_generate($input = '') { } /** - * Given the filename, type, & settings, create absolute URL for 307 redirect. + * Given the filename, type, and settings, create absolute URL for 307 redirect. * * Due to url inbound alter we can not trust that the redirect will work if * using $GLOBALS['base_path'] . $_GET['q']. Generate the uri as if it was @@ -146,6 +193,10 @@ function advagg_generate_location_uri($filename, $type, array $aggregate_setting $uri_307 = $js_path[0] . '/' . $filename; } + if (empty($aggregate_settings)) { + $aggregate_settings = advagg_current_hooks_hash_array(); + } + // 307s need to be absolute. RFC 2616 14.30. $aggregate_settings['variables']['advagg_convert_absolute_to_relative_path'] = FALSE; $aggregate_settings['variables']['advagg_convert_absolute_to_protocol_relative_path'] = FALSE; @@ -176,16 +227,35 @@ 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(); + } + + $return_compressed_br = FALSE; + $return_compressed_gz = FALSE; // Negotiate whether to use gzip compression. - $return_compressed = isset($_SERVER['HTTP_ACCEPT_ENCODING']) && strpos($_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip') !== FALSE; + if (!empty($_SERVER['HTTP_ACCEPT_ENCODING'])) { + if (strpos($_SERVER['HTTP_ACCEPT_ENCODING'], 'br') !== FALSE) { + $return_compressed_br = TRUE; + } + if (strpos($_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip') !== FALSE) { + $return_compressed_gz = TRUE; + } + } header('Vary: Accept-Encoding', FALSE); if (!empty($created)) { - if ($return_compressed && file_exists($uri . '.gz') && filesize($uri . '.gz') > 0) { + if ($return_compressed_br && file_exists($uri . '.br') && filesize($uri . '.br') > 0) { + $uri .= '.br'; + } + elseif ($return_compressed_gz && file_exists($uri . '.gz') && filesize($uri . '.gz') > 0) { $uri .= '.gz'; } if (!isset($files_to_save[$uri]) && file_exists($uri) && filesize($uri)) { - $files_to_save[$uri] = file_get_contents($uri); + // Do not use advagg_file_get_contents here. + $files_to_save[$uri] = (string) @file_get_contents($uri); } } @@ -197,10 +267,23 @@ function advagg_missing_send_saved_file(array $files_to_save, $uri, $created, $f ob_end_clean(); } // Set generic far future headers. - advagg_missing_set_farfuture_headers(); + if (variable_get('advagg_farfuture_php', ADVAGG_FARFUTURE_PHP)) { + advagg_missing_set_farfuture_headers(); + } // Return compressed content if we can. - if ($return_compressed) { + if ($return_compressed_br || $return_compressed_gz) { foreach ($files_to_save as $uri => $data) { + // See if this uri contains .br near the end of it. + $pos = strripos($uri, '.br', 91 + strlen(ADVAGG_SPACE) * 3); + if (!empty($pos)) { + $len = strlen($uri); + if ($pos == $len - 3) { + // .br file exists, send it out. + header('Content-Encoding: br'); + break; + } + } + // See if this uri contains .gz near the end of it. $pos = strripos($uri, '.gz', 91 + strlen(ADVAGG_SPACE) * 3); if (!empty($pos)) { @@ -219,14 +302,49 @@ 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"); + $default = ADVAGG_HTTP_200_CODE; + if (module_exists('httprl') + && variable_get('advagg_use_httprl', ADVAGG_USE_HTTPRL) + && (is_callable('httprl_is_background_callback_capable') + && httprl_is_background_callback_capable() + || !is_callable('httprl_is_background_callback_capable') + ) + ) { + // Use 203 instead of 200 if HTTPRL is being used. + $default = 203; + } + $number = variable_get('advagg_http_200_code', $default); + header("{$_SERVER['SERVER_PROTOCOL']} $number OK"); // Insure the Last-Modified header is set so 304's work correctly. - // @ignore druplart_andor_assignment if (file_exists($uri) && $filemtime = @filemtime($uri)) { header('Last-Modified: ' . gmdate('D, d M Y H:i:s \G\M\T', $filemtime)); + // 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. + if (variable_get('advagg_farfuture_php', ADVAGG_FARFUTURE_PHP)) { + 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"); @@ -258,7 +376,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, @@ -280,7 +398,12 @@ function advagg_missing_set_farfuture_headers() { // Set a far future Cache-Control header (52 weeks), which prevents // intermediate caches from transforming the data and allows any // intermediate cache to cache it, since it's marked as a public resource. - header('Cache-Control: max-age=31449600, no-transform, public'); + if (variable_get('advagg_resource_hints_use_immutable', ADVAGG_RESOURCE_HINTS_USE_IMMUTABLE)) { + header('Cache-Control: max-age=31449600, no-transform, public, immutable'); + } + else { + header('Cache-Control: max-age=31449600, no-transform, public'); + } } /** @@ -290,7 +413,7 @@ function advagg_missing_set_farfuture_headers() { * Just the filename no path information. * @param bool $no_alters * (optional) Set to TRUE to do the bare amount of processing on the file. - * @param array $data + * @param mixed $data * (optional) Output from advagg_get_hashes_from_filename(). * * @return mixed @@ -298,6 +421,9 @@ function advagg_missing_set_farfuture_headers() { * On success the $files_to_save array. */ function advagg_missing_create_file($filename, $no_alters = FALSE, $data = array()) { + // Option to still delever the file if fatal error. + register_shutdown_function("advagg_missing_fatal_handler", $filename); + if (empty($data)) { $data = advagg_get_hashes_from_filename($filename); } @@ -308,6 +434,10 @@ function advagg_missing_create_file($filename, $no_alters = FALSE, $data = array return $data; } + if (empty($aggregate_settings)) { + $aggregate_settings = advagg_current_hooks_hash_array(); + } + // Set no alters if this is the last chance of generating the aggregate. if ($no_alters) { $aggregate_settings['settings']['no_alters'] = TRUE; @@ -320,12 +450,14 @@ 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( - 'aggregate_filenames_hash' => $aggregate_filenames_hash, - 'aggregate_contents_hash' => $aggregate_contents_hash, - ))); + advagg_multi_update_atime(array( + array( + 'aggregate_filenames_hash' => $aggregate_filenames_hash, + 'aggregate_contents_hash' => $aggregate_contents_hash, + ), + )); // Make sure .htaccess file exists in the advagg dir. if (variable_get('advagg_htaccess_check_generate', ADVAGG_HTACCESS_CHECK_GENERATE)) { advagg_htaccess_check_generate($files_to_save, $type); @@ -339,6 +471,7 @@ function advagg_missing_create_file($filename, $no_alters = FALSE, $data = array $aggregate_contents_hash, $aggregate_settings, $files, + $errors, ); } @@ -351,26 +484,96 @@ 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) { + list($css_path, $js_path) = advagg_get_root_files_dir(); + $content_type = $type; if ($content_type === 'js') { $content_type = 'javascript'; + $advagg_dir = basename($js_path[1]); } + elseif ($content_type === 'css') { + $advagg_dir = basename($css_path[1]); + } + $type_upper = strtoupper($type); + $data = "\n"; - // @ignore style_lowercase_html:45 - $data = "\n"; - $data .= "<FilesMatch \"^${type}" . ADVAGG_SPACE . "[A-Za-z0-9-_]{43}" . ADVAGG_SPACE . "[A-Za-z0-9-_]{43}" . ADVAGG_SPACE . "[A-Za-z0-9-_]{43}.${type}(\.gz)?\">\n"; - $data .= " # No mod_headers\n"; + // Some hosting companies do not allow "FollowSymLinks" but will support + // "SymLinksIfOwnerMatch". + if (variable_get('advagg_htaccess_symlinksifownermatch', ADVAGG_HTACCESS_SYMLINKSIFOWNERMATCH)) { + $data .= "Options +SymLinksIfOwnerMatch\n"; + } + else { + $data .= "Options +FollowSymLinks\n"; + } + if ($GLOBALS['base_path'] !== '/') { + $data .= "ErrorDocument 404 {$GLOBALS['base_path']}index.php\n"; + } + // See if RewriteBase is needed. + $advagg_htaccess_rewritebase = trim(variable_get('advagg_htaccess_rewritebase', ADVAGG_HTACCESS_REWRITEBASE)); + if (!empty($advagg_htaccess_rewritebase) && !empty($advagg_dir)) { + $rewrite_base_rule = str_replace('//', '/', $advagg_htaccess_rewritebase . '/' . $advagg_dir); + $data .= "RewriteBase $rewrite_base_rule\n"; + } + + $data .= "\n"; + $data .= "<IfModule mod_rewrite.c>\n"; + $data .= " RewriteEngine on\n"; + $data .= " <IfModule mod_headers.c>\n"; + $data .= " # Serve brotli compressed ${type_upper} files if they exist and the client accepts br.\n"; + $data .= " RewriteCond %{HTTP:Accept-encoding} br\n"; + $data .= " RewriteCond %{REQUEST_FILENAME}\.br -s\n"; + $data .= " RewriteRule ^(.*)\.${type} " . '$1' . "\.${type}\.br [QSA]\n"; + if ($type === 'css') { + $data .= " RewriteRule \.${type}\.br$ - [T=text/${content_type},E=no-gzip:1]\n"; + } + else { + $data .= " RewriteRule \.${type}\.br$ - [T=application/${content_type},E=no-gzip:1]\n"; + } + $data .= "\n"; + $data .= " <FilesMatch \"\.${type}\.br$\">\n"; + $data .= " # Serve correct encoding type.\n"; + $data .= " Header set Content-Encoding br\n"; + $data .= " # Force proxies to cache gzipped & non-gzipped css/js files separately.\n"; + $data .= " Header append Vary Accept-Encoding\n"; + $data .= " </FilesMatch>\n"; + $data .= "\n"; + $data .= " # Serve gzip compressed ${type_upper} files if they exist and the client accepts gzip.\n"; + $data .= " RewriteCond %{HTTP:Accept-encoding} gzip\n"; + $data .= " RewriteCond %{REQUEST_FILENAME}\.gz -s\n"; + $data .= " RewriteRule ^(.*)\.${type} " . '$1' . "\.${type}\.gz [QSA]\n"; + if ($type === 'css') { + $data .= " RewriteRule \.${type}\.gz$ - [T=text/${content_type},E=no-gzip:1]\n"; + } + else { + $data .= " RewriteRule \.${type}\.gz$ - [T=application/${content_type},E=no-gzip:1]\n"; + } + $data .= "\n"; + $data .= " <FilesMatch \"\.${type}\.gz$\">\n"; + $data .= " # Serve correct encoding type.\n"; + $data .= " Header set Content-Encoding gzip\n"; + $data .= " # Force proxies to cache gzipped & non-gzipped css/js files separately.\n"; + $data .= " Header append Vary Accept-Encoding\n"; + $data .= " </FilesMatch>\n"; + $data .= " </IfModule>\n"; + $data .= "</IfModule>\n"; + $data .= "\n"; + $data .= "<FilesMatch \"^${type}" . ADVAGG_SPACE . "[A-Za-z0-9-_]{43}" . ADVAGG_SPACE . "[A-Za-z0-9-_]{43}" . ADVAGG_SPACE . "[A-Za-z0-9-_]{43}.${type}(\.gz|\.br)?\">\n"; + $data .= " # No mod_headers. Apache module headers is not enabled.\n"; $data .= " <IfModule !mod_headers.c>\n"; - $data .= " # No mod_expires\n"; + $data .= " # No mod_expires. Apache module expires is not enabled.\n"; $data .= " <IfModule !mod_expires.c>\n"; $data .= " # Use ETags.\n"; $data .= " FileETag MTime Size\n"; $data .= " </IfModule>\n"; $data .= " </IfModule>\n"; $data .= "\n"; - $data .= " # Use Expires Directive.\n"; + $data .= " # Use Expires Directive if apache module expires is enabled.\n"; $data .= " <IfModule mod_expires.c>\n"; $data .= " # Do not use ETags.\n"; $data .= " FileETag None\n"; @@ -380,6 +583,7 @@ function advagg_htaccess_check_generate(array $files_to_save, $type, $force = FA $data .= " ExpiresDefault A31449600\n"; $data .= " </IfModule>\n"; $data .= "\n"; + $data .= " # Use Headers Directive if apache module headers is enabled.\n"; $data .= " <IfModule mod_headers.c>\n"; $data .= " # Do not use etags for cache validation.\n"; $data .= " Header unset ETag\n"; @@ -392,10 +596,20 @@ function advagg_htaccess_check_generate(array $files_to_save, $type, $force = FA } $data .= " <IfModule !mod_expires.c>\n"; $data .= " # Set a far future Cache-Control header to 52 weeks.\n"; - $data .= " Header set Cache-Control \"max-age=31449600, no-transform, public\"\n"; + if (variable_get('advagg_resource_hints_use_immutable', ADVAGG_RESOURCE_HINTS_USE_IMMUTABLE)) { + $data .= " Header set Cache-Control \"max-age=31449600, no-transform, public, immutable\"\n"; + } + else { + $data .= " Header set Cache-Control \"max-age=31449600, no-transform, public\"\n"; + } $data .= " </IfModule>\n"; $data .= " <IfModule mod_expires.c>\n"; - $data .= " Header append Cache-Control \"no-transform, public\"\n"; + if (variable_get('advagg_resource_hints_use_immutable', ADVAGG_RESOURCE_HINTS_USE_IMMUTABLE)) { + $data .= " Header append Cache-Control \"no-transform, public, immutable\"\n"; + } + else { + $data .= " Header append Cache-Control \"no-transform, public\"\n"; + } $data .= " </IfModule>\n"; $data .= " </IfModule>\n"; if ($type === 'css') { @@ -406,6 +620,7 @@ function advagg_htaccess_check_generate(array $files_to_save, $type, $force = FA } $data .= "</FilesMatch>\n"; + $errors = array(); foreach (array_keys($files_to_save) as $uri) { $dir = dirname($uri); $htaccess_file = $dir . '/.htaccess'; @@ -413,11 +628,11 @@ 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. /** * Given a filename return the type and 2 hashes. * @@ -464,7 +679,12 @@ function advagg_get_hashes_from_filename($filename, $skip_hash_settings = FALSE) // Verify that the hooks hashes is valid. $aggregate_settings = advagg_get_hash_settings($hooks_hashes_value); if (empty($aggregate_settings)) { - return t('Bad hooks hashes value.'); + if (!variable_get('advagg_weak_file_verification', ADVAGG_WEAK_FILE_VERIFICATION)) { + return t('Bad hooks hashes value.'); + } + elseif (variable_get('advagg_debug', ADVAGG_DEBUG) >= 2) { + watchdog('advagg-debug', 'File @filename has an empty aggregate_settings variable; the 3rd hash is incorrect.', array('@filename' => $filename), WATCHDOG_DEBUG); + } } } @@ -500,7 +720,7 @@ function advagg_get_files_from_hashes($type, $aggregate_filenames_hash, $aggrega array(':aggregate_filenames_hash' => $aggregate_filenames_hash)); // Create join query for the advagg_files table. $query->join('advagg_files', 'af', 'af.filename_hash = aa.filename_hash AND - af.filetype = :type', array(':type' => $type)); + af.filetype = :type AND af.filesize > 0', array(':type' => $type)); // Select fields and ordering of the query; add in query comment as well. $query = $query->fields('af', array('filename')) ->fields($subquery_aggregates, array('settings')) @@ -513,10 +733,42 @@ function advagg_get_files_from_hashes($type, $aggregate_filenames_hash, $aggrega foreach ($results as $value) { $files[$value->filename] = unserialize($value->settings); } + + // Try again with weak file verification. + if (empty($files) && variable_get('advagg_weak_file_verification', ADVAGG_WEAK_FILE_VERIFICATION)) { + if (variable_get('advagg_debug', ADVAGG_DEBUG) >= 2) { + watchdog('advagg-debug', 'Filehash @filename of type @type has an aggregate_contents_hash variable; the 2rd hash is incorrect.', array( + '@filename' => $aggregate_filenames_hash, + '@type' => $type, + ), WATCHDOG_DEBUG); + } + + // Create main query for the advagg_aggregates_versions table. + $query = db_select('advagg_aggregates_versions', 'aav') + ->condition('aav.aggregate_filenames_hash', $aggregate_filenames_hash); + // Create join query for the advagg_aggregates table. + $subquery_aggregates = $query->join('advagg_aggregates', 'aa', 'aa.aggregate_filenames_hash = + aav.aggregate_filenames_hash AND aa.aggregate_filenames_hash = :aggregate_filenames_hash', + array(':aggregate_filenames_hash' => $aggregate_filenames_hash)); + // Create join query for the advagg_files table. + $query->join('advagg_files', 'af', 'af.filename_hash = aa.filename_hash AND + af.filetype = :type AND af.filesize > 0', array(':type' => $type)); + // Select fields and ordering of the query; add in query comment as well. + $query = $query->fields('af', array('filename')) + ->fields($subquery_aggregates, array('settings')) + ->orderBy('porder', 'ASC'); + $query->comment('Query called from ' . __FUNCTION__ . '()'); + $results = $query->execute(); + + // Add in files that are included in this aggregate. + $files = array(); + foreach ($results as $value) { + $files[$value->filename] = unserialize($value->settings); + } + } return $files; } -// Read CSS/JS files. /** * Given a list of files, grab their contents and glue it into one big string. * @@ -524,11 +776,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'])) { @@ -538,6 +793,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; @@ -550,16 +808,77 @@ function advagg_get_css_aggregate_contents(array $files, array $aggregate_settin $last_media = $settings['media']; continue; } - if ($settings['media'] != $last_media) { + if ($settings['media'] !== $last_media) { $media_changes = TRUE; break; } } - $last_media = NULL; + if ($media_changes) { + $global_file_media = 'all'; + } + else { + $global_file_media = $last_media; + } + + // https://developer.mozilla.org/en-US/docs/Web/Guide/CSS/Media_queries + $media_types = array( + 'all', + 'aural', + 'braille', + 'handheld', + 'print', + 'projection', + 'screen', + 'tty', + 'tv', + 'embossed', + ); + $import_statements = array(); module_load_include('inc', 'advagg', 'advagg'); + $original_settings = array($optimize, $aggregate_settings); foreach ($files as $file => $settings) { - $contents = advagg_load_css_stylesheet($file, $optimize, $aggregate_settings); + $media_changes = FALSE; + if (!isset($settings['media'])) { + $settings['media'] = ''; + } + + if ($settings['media'] !== $global_file_media) { + $media_changes = TRUE; + } + + list($optimize, $aggregate_settings) = $original_settings; + // Allow other modules to modify aggregate_settings optimize. + // Call hook_advagg_get_css_file_contents_pre_alter(). + if (empty($aggregate_settings['settings']['no_alters'])) { + drupal_alter('advagg_get_css_file_contents_pre', $file, $optimize, $aggregate_settings); + } + if (is_readable($file)) { + // Get the files contents. + $file_contents = (string) @advagg_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 (isset($info_on_files[$cid]) == FALSE || $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 { + // File is not readable. + $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(). @@ -571,6 +890,16 @@ function advagg_get_css_aggregate_contents(array $files, array $aggregate_settin $media_blocks = advagg_parse_media_blocks($contents); $contents = ''; + $file_has_type = FALSE; + if (!empty($settings['media'])) { + foreach ($media_types as $media_type) { + if (stripos($settings['media'], $media_type) !== FALSE) { + $file_has_type = TRUE; + break; + } + } + } + foreach ($media_blocks as $css_rules) { if (strpos($css_rules, '@media') !== FALSE) { // Get start and end of the rules for this media query block. @@ -589,47 +918,55 @@ function advagg_get_css_aggregate_contents(array $files, array $aggregate_settin $css_selectors_rules = substr($css_rules, $start + 1, $end - ($start + 1)); // Add in main media rule if needed. - if (strpos($media_rules, $settings['media']) === FALSE) { - $media_rules = $settings['media'] . ' ' . $media_rules; + if (!empty($settings['media']) + && strpos($media_rules, $settings['media']) === FALSE + && $settings['media'] !== $global_file_media + ) { + $rule_has_type = FALSE; + if ($file_has_type) { + foreach ($media_types as $media_type) { + if (stripos($media_rules, $media_type) !== FALSE) { + $rule_has_type = TRUE; + break; + } + } + } + if (!$rule_has_type) { + $media_rules = $settings['media'] . ' and ' . $media_rules; + } } } else { $media_rules = $settings['media']; $css_selectors_rules = $css_rules; } - // Remove the all rule. - $media_rules = str_replace('all', '', $media_rules); $media_rules = trim($media_rules); - $css_selectors_rules = trim($css_selectors_rules); - // Start of stylesheet. - if (is_null($last_media)) { - if (!empty($media_rules)) { - $output = '@media ' . $media_rules . ' {' . $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 { - $output = $css_selectors_rules; + $css_selectors_rules .= $rules; } } - elseif ($media_rules != $last_media) { - if (!empty($media_rules)) { - if (!empty($last_media)) { - $output = "} \n@media " . $media_rules . ' {' . $css_selectors_rules; - } - else { - $output = "\n@media " . $media_rules . ' {' . $css_selectors_rules; - } - } - else { - $output = "} \n " . $css_selectors_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 = "{$font_face_string} \n@media {$media_rules} {\n {$css_selectors_rules} \n}"; } else { - $output = ' ' . $css_selectors_rules; + $output = "{$font_face_string} \n {$css_selectors_rules}"; } - $last_media = $media_rules; - $contents .= $output; + $contents .= trim($output); } } @@ -641,11 +978,11 @@ function advagg_get_css_aggregate_contents(array $files, array $aggregate_settin $contents = preg_replace($regexp, '', $contents); // Add the import statements with the media query of the current file. $import_media = isset($settings['media']) ? $settings['media'] : ''; - $import_media = trim(str_replace('all', '', $import_media)); + $import_media = trim($import_media); $import_statements[] = array($import_media, $matches[0]); // Close any open comment blocks. - $contents .= '/**/'; + $contents .= "\n/*})'\"*/\n"; if (variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL) < 0) { $contents .= "\n/* Above code came from $file */\n\n"; } @@ -653,11 +990,6 @@ function advagg_get_css_aggregate_contents(array $files, array $aggregate_settin $data .= $contents; } - // Close the final media bracket. - if ($media_changes && !empty($last_media)) { - $data .= '}'; - } - // Add import statements to the top of the stylesheet. $import_string = ''; foreach ($import_statements as $values) { @@ -678,7 +1010,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); } /** @@ -688,30 +1020,63 @@ 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) @advagg_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 (isset($info_on_files[$cid]['content_hash']) && $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); + } + + // Make sure that the file is ended properly. + $file_contents .= "\n;/*})'\"*/\n"; if (variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL) < 0) { - $contents .= "/* Above code came from $filename */\n\n"; + $file_contents .= "/* Above code came from $filename */\n\n"; } + $contents .= $file_contents; + } + else { + // File is not readable. + $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'])) { drupal_alter('advagg_get_js_file_contents', $contents, $filename, $aggregate_settings); } + // Make sure that the file is ended properly. + if (!empty($contents)) { + $contents .= ";/*})'\"*/\n"; + } $data .= $contents; } } @@ -721,10 +1086,64 @@ 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 file couldn't be found. + * + * @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(); + list($css_path, $js_path) = advagg_get_root_files_dir(); + + // 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 does not start in the + // public dir and the advagg dirs. + if (empty($cache) + && strpos($filename, 'public://') !== 0 + && strpos($filename, $css_path[1]) !== 0 + && strpos($filename, $js_path[1]) !== 0 + ) { + if ($fs_read_failure) { + watchdog('advagg', 'Reading from the file system failed. This can sometimes happen during a deployment and/or a clear cache operation. Filename: %file Aggregate Filename: %aggregate. If this continues to happen go to the <a href="@operations">Operations page</a> and under Drastic Measures - Reset the AdvAgg Files table click the Truncate advagg_files button.', array( + '%file' => $filename, + '%aggregate' => $aggregate_filename, + '@operations' => url('admin/config/development/performance/advagg/operations', array('fragment' => 'edit-reset-advagg-files')), + ), WATCHDOG_WARNING); + } + else { + watchdog('advagg', 'The content hash for %file does not match the stored content hash from the database. Please <a href="@url">flush the advagg cache</a> under Smart Cache Flush. This can sometimes happen during a deployment. Filename: %file Aggregate Filename: %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 30 minutes. + $write_aggregate = TRUE; + } + return $write_aggregate; } -// File save functions. /** * Save an aggregate given a filename, the files included in it, and the type. * @@ -738,20 +1157,35 @@ 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) { +function advagg_save_aggregate($filename, array $files, $type, array $aggregate_settings = array()) { list($css_path, $js_path) = advagg_get_root_files_dir(); + $uri = ''; + if ($type === 'css') { + $uri = $css_path[0] . '/' . $filename; + } + elseif ($type === 'js') { + $uri = $js_path[0] . '/' . $filename; + } + + if (empty($aggregate_settings)) { + $aggregate_settings = advagg_current_hooks_hash_array(); + } + + // Allow other modules to alter the location, files included, and settings. + if (empty($aggregate_settings['settings']['no_alters'])) { + // Call hook_advagg_save_aggregate_pre_alter(). + drupal_alter('advagg_save_aggregate_pre', $uri, $files, $aggregate_settings); + } // Build the aggregates contents. $contents = ''; if ($type === 'css') { - $contents = advagg_get_css_aggregate_contents($files, $aggregate_settings); - $uri = $css_path[0] . '/' . $filename; + 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); - $uri = $js_path[0] . '/' . $filename; + 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; @@ -762,40 +1196,55 @@ function advagg_save_aggregate($filename, array $files, $type, array $aggregate_ $uri => $contents, ); - // Allow other modules to alter the contents and add new files to save (.gz). + // Allow other modules to alter the contents and add new files to save. // Call hook_advagg_save_aggregate_alter(). $other_parameters = array($files, $type); if (empty($aggregate_settings['settings']['no_alters'])) { 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. !errors. %full_dir.', array( + '%uri' => $uri, + '!errors' => print_r($errors, TRUE), + '%full_dir' => $full_dir, + ), WATCHDOG_ALERT); + } + elseif (!is_writable($full_dir)) { + watchdog('advagg', 'Write to file system failed. Check directory permissions. %uri. !errors. %full_dir.', array( + '%uri' => $uri, + '!errors' => print_r($errors, TRUE), + '%full_dir' => $full_dir, + ), WATCHDOG_ERROR); + } + else { + watchdog('advagg', 'Write to file system failed. %uri. !errors. %full_dir.', array( + '%uri' => $uri, + '!errors' => print_r($errors, TRUE), + '%full_dir' => $full_dir, + ), 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); } /** @@ -810,14 +1259,33 @@ 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); + + // Prepare dir if needed. + $dir = dirname($uri); + $dir_good = file_prepare_directory($dir, FILE_CREATE_DIRECTORY); + if (!$dir_good) { + $errors[1] = $t('The directory for @file can not be created or is not writable.', array('@file' => $uri)); + return $errors; + } + // File already exists. if (!$overwrite && file_exists($uri) && filesize($uri) > 0) { - return; + if (variable_get('advagg_debug', ADVAGG_DEBUG) >= 2) { + watchdog('advagg-debug', 'File @uri exists and overwrite is false.', array('@uri' => $uri), WATCHDOG_DEBUG); + } + $errors[2] = $t('File (@file) already exits.', array('@file' => $uri)); + return $errors; } // If data is empty, write a space. @@ -829,51 +1297,163 @@ function advagg_save_data($uri, $data, $overwrite = FALSE) { // writing to the same file, the best option is to create a temporary file in // the same directory and then rename it to the destination. A temporary file // is needed if the directory is mounted on a separate machine; thus ensuring - // the rename command stays local. + // the rename command stays local and atomic. // // Get a temporary filename in the destination directory. - $dir = drupal_dirname($uri) . '/'; - $temporary_file = drupal_tempnam($dir, 'file_advagg_'); - $temporary_file_copy = $temporary_file; + $dir = $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); + if ($wrapper && method_exists($wrapper, 'getDirectoryPath')) { + $wrapper_dir_path = $wrapper->getDirectoryPath(); + if (!empty($wrapper_dir_path)) { + $dir = $wrapper_dir_path . '/' . substr($uri_dir, strlen($scheme . '://')); + $uri = $dir . substr($uri, strlen($uri_dir)); + } + } + } // Get the extension of the original filename and append it to the temp file // name. Preserves the mime type in different stream wrapper implementations. $parts = pathinfo($uri); + if (variable_get('advagg_debug', ADVAGG_DEBUG) >= 2) { + watchdog('advagg-debug', 'Creating URI @uri', array('@uri' => $uri), WATCHDOG_DEBUG); + watchdog('advagg-debug', 'File Parts <pre>@parts</pre>', array('@parts' => print_r($parts, TRUE)), WATCHDOG_DEBUG); + } $extension = '.' . $parts['extension']; - if ($extension === '.gz') { + if ($extension === '.gz' || $extension === '.br') { $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; - // Preform the rename, adding the extension to the temp file. - if (!@rename($temporary_file_copy, $temporary_file)) { - // Remove if rename failed. - @unlink($temporary_file_copy); - } + // Create temp filename. + $temporary_file = $dir . 'advagg_file_' . drupal_hash_base64(microtime(TRUE) . mt_rand()) . $extension; // Save to temporary filename in the destination directory. $filepath = file_unmanaged_save_data($data, $temporary_file, FILE_EXISTS_REPLACE); if ($filepath) { // Perform the rename operation. - $result = @rename($filepath, $uri); - if (!$result) { + if (!advagg_rename($filepath, $uri)) { // Unlink and try again for windows. Rename on windows does not replace // the file if it already exists. + if (variable_get('advagg_debug', ADVAGG_DEBUG) >= 2) { + watchdog('advagg-debug', 'Rename failed. @to', array('@to' => $uri), WATCHDOG_WARNING); + } @unlink($uri); - $result = @rename($filepath, $uri); // Remove temporary_file if rename failed. - if (!$result) { + if (!advagg_rename($filepath, $uri)) { + $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); + } + } + + // Check the filesize. + $file_size = @filesize($uri); + $expected_size = _advagg_string_size_in_bytes($data); + if ($file_size === 0) { + // Zero byte file. + $errors[26] = $t('Write successful, but the file is empty. @file', array('@file' => $filepath)); + watchdog('advagg', 'Write successful, but the file is empty. Target: target. The empty file has been removed. If this error continues, performance will be greatly degraded.', array( + '%target' => $uri, + ), WATCHDOG_ERROR); + // Better to serve straight from Drupal than have a broken file. + @unlink($uri); + } + elseif ($file_size > 0 && $file_size != $expected_size) { + // Data written to disk doesn't match. + $errors[28] = $t('Write successful, but the file is the wrong size. @file Expected size is @expected_size, actual size is @file_size', array( + '@file' => $uri, + '@expected_size' => $expected_size, + '@file_size' => $file_size, + )); + watchdog('advagg', 'Write successful, but the file is the wrong size. %file Expected size is %expected_size, actual size is %file_size. The broken file has been removed. If this error continues, performance will be greatly degraded.', array( + '%file' => $uri, + '%expected_size' => $expected_size, + '%file_size' => $file_size, + ), WATCHDOG_ERROR); + // Better to serve straight from Drupal than have a broken file. + @unlink($uri); + } + } + else { + $errors[24] = $t('Write failed. @file', array('@file' => $temporary_file)); + watchdog('advagg', 'Write failed. Target: %target', array( + '%target' => $temporary_file, + ), WATCHDOG_ERROR); + } + // Cleanup leftover files. + if (file_exists($temporary_file)) { + @unlink($temporary_file); + } + if (file_exists($filepath)) { + @unlink($filepath); + } + return $errors; +} + +/** + * Given a string, what is the size that it should be as a file? + * + * @param string $string + * Input data to be sized in bytes. + * @link http://stackoverflow.com/a/3511239/231914. + * + * @return int + * Number of bytes this string uses. + */ +function _advagg_string_size_in_bytes($string) { + if (function_exists('mb_strlen')) { + return mb_strlen($string, '8bit'); + } + else { + return strlen($string); + } +} + +/** + * Rename; fallback to copy delete if this fails. + * + * @param string $source + * A string containing the source location. + * @param string $destination + * A string containing the destination location. + * + * @return mixed + * Destination string on success, FALSE on failure. + */ +function advagg_rename($source, $destination) { + $real_source = drupal_realpath($source); + $real_source = $real_source ? $real_source : $source; + $real_destination = drupal_realpath($destination); + $real_destination = $real_destination ? $real_destination : $destination; + + // Try php rename. + if (!@rename($real_source, $real_destination)) { + // Try drupal move. + if (!file_unmanaged_move($source, $destination)) { + // Try file scheme's rename method if it exists. + $fs_wrapper = file_stream_wrapper_get_instance_by_scheme(file_uri_scheme($source)); + if (!$fs_wrapper || !method_exists($fs_wrapper, 'rename') || !$fs_wrapper->rename($source, $destination)) { + return FALSE; } } } + + return $destination; } -// Helper functions. /** * Send out a fast 404 and exit. * @@ -881,8 +1461,10 @@ function advagg_save_data($uri, $data, $overwrite = FALSE) { * (optional) Small message reporting why the file didn't get created. */ function advagg_missing_fast404($msg = '') { - // Strip new lines and limit header message to 512 characters. - $msg = substr(str_replace(array("\n", "\r"), '', $msg), 0, 512); + drupal_page_is_cacheable(FALSE); + + // Strip new lines & separators and limit header message to 512 characters. + $msg = substr(preg_replace("/[^\w\. ]+/", "", $msg), 0, 512); // Add in headers if possible. if (!headers_sent()) { @@ -950,20 +1532,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 + * @see http://stackoverflow.com/a/14145856/125684 */ -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(); @@ -980,14 +1564,15 @@ function advagg_parse_media_blocks($css) { // Move past first bracket. ++$i; - // Find the closing bracket for the @media statement. - while (!empty($s)) { + // Find the closing bracket for the @media statement. But ensure we don't + // overflow if there's an error. + while (!empty($s) && isset($css[$i])) { // If the character is an opening bracket, push it onto the stack, // otherwise pop the stack. - if ($css[$i] == "{") { + if ($css[$i] === "{") { array_push($s, "{"); } - elseif ($css[$i] == "}") { + elseif ($css[$i] === "}") { array_pop($s); } ++$i; @@ -1017,3 +1602,56 @@ function advagg_parse_media_blocks($css) { return $media_blocks; } + +/** + * Given a filename create that file; usually works if PHP goes fatal. + * + * @param string $filename + * Just the filename no path information. + * + * @return mixed + * On failure a string saying why it failed. + * On success the $files_to_save array. + */ +function advagg_missing_fatal_handler($filename) { + static $counter = 0; + // Bail out if there is no error. + $error = error_get_last(); + if ($error === NULL) { + return; + } + + $counter++; + // Bail out if this is still in a loop. + if ($counter > 2) { + return; + } + + // Bail out if the file already exists. + $data = advagg_get_hashes_from_filename($filename); + $type = $data[0]; + list($css_path, $js_path) = advagg_get_root_files_dir(); + $uri = ''; + if ($type === 'css') { + $uri = $css_path[0] . '/' . $filename; + } + elseif ($type === 'js') { + $uri = $js_path[0] . '/' . $filename; + } + if (file_exists($uri)) { + return; + } + + // Generate the file with no alters. + set_time_limit(0); + $return = advagg_missing_create_file($filename, TRUE); + if (is_array($return) && !headers_sent()) { + $redirect_counter = isset($_GET['redirect_counter']) ? intval($_GET['redirect_counter']) : 0; + // 307 if headers have not been sent yet. + $uri = advagg_generate_location_uri($filename, $data[0], $data[3]); + ++$redirect_counter; + $uri .= '?redirect_counter=' . $redirect_counter; + header('Location: ' . $uri, TRUE, 307); + exit(); + } +} diff --git a/sites/all/modules/contrib/advagg/advagg.module b/sites/all/modules/contrib/advagg/advagg.module index 7b5d6233a2..2b497ef05c 100644 --- a/sites/all/modules/contrib/advagg/advagg.module +++ b/sites/all/modules/contrib/advagg/advagg.module @@ -5,7 +5,12 @@ * Advanced CSS/JS aggregation module. */ -// Define default variables. +/** + * @defgroup default_variables default values for variables + * @{ + * Default values for various variables are defined here. + */ + /** * Default space characters. */ @@ -24,7 +29,12 @@ define('ADVAGG_GZIP', TRUE); /** * Default value to see we use core's default grouping of CSS/JS files. */ -define('ADVAGG_CORE_GROUPS', TRUE); +if (module_exists('advagg_bundler') && variable_get('advagg_bundler_active', TRUE)) { + define('ADVAGG_CORE_GROUPS', FALSE); +} +else { + define('ADVAGG_CORE_GROUPS', TRUE); +} /** * Default value to see if we cache the full CSS/JS structure. @@ -39,7 +49,7 @@ define('ADVAGG_GLOBAL_COUNTER', 0); /** * Send non blocking requests in order to generate aggregated files via HTTPRL. */ -define('ADVAGG_USE_HTTPRL', TRUE); +define('ADVAGG_USE_HTTPRL', FALSE); /** * Combine css files by using media queries instead of media attributes. @@ -107,9 +117,9 @@ define('ADVAGG_CLEAR_SCRIPTS', TRUE); define('ADVAGG_NEEDS_UPDATE', FALSE); /** - * How long to wait until advagg cron will run again. Default is 24 hours. + * How long to wait until advagg cron will run again. Default is 23 hours. */ -define('ADVAGG_CRON_FREQUENCY', 86400); +define('ADVAGG_CRON_FREQUENCY', 82800); /** * How long to wait until unaccessed aggregates are removed from the database. @@ -151,7 +161,7 @@ define('ADVAGG_INCLUDE_BASE_URL', FALSE); /** * Convert absolute path CSS/JS src/url() to be relative if self referencing. */ -define('ADVAGG_CONVERT_ABSOLUTE_TO_RELATIVE_PATH', FALSE); +define('ADVAGG_CONVERT_ABSOLUTE_TO_RELATIVE_PATH', TRUE); /** * Convert absolute path CSS/JS src/url() to be protocol relative. @@ -163,6 +173,11 @@ define('ADVAGG_CONVERT_ABSOLUTE_TO_PROTOCOL_RELATIVE_PATH', TRUE); */ define('ADVAGG_FORCE_HTTPS_PATH', FALSE); +/** + * Convert relative path CSS inside src() to be absolute. + */ +define('ADVAGG_CSS_ABSOLUTE_PATH', FALSE); + /** * If TRUE then the css is being rendered via javascript. */ @@ -183,11 +198,308 @@ define('ADVAGG_AUTH_BASIC_USER', ''); */ define('ADVAGG_AUTH_BASIC_PASS', ''); -// Core hook implementations. /** - * Inbound URL rewrite helper. + * Skip far future check on status page. + */ +define('ADVAGG_SKIP_FAR_FUTURE_CHECK', FALSE); + +/** + * Skip preprocess check on status page. + */ +define('ADVAGG_SKIP_ENABLED_PREPROCESS_CHECK', FALSE); + +/** + * Prefetch External domains for CSS/JS. + */ +define('ADVAGG_RESOURCE_HINTS_DNS_PREFETCH', FALSE); + +/** + * Preconnect External domains for CSS/JS. + */ +define('ADVAGG_RESOURCE_HINTS_PRECONNECT', FALSE); + +/** + * Preload CSS/JS and sub requests. + */ +define('ADVAGG_RESOURCE_HINTS_PRELOAD', FALSE); + +/** + * Location of CSS/JS and sub requests resource hints. + */ +define('ADVAGG_RESOURCE_HINTS_LOCATION', 1); + +/** + * 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. + */ +if (module_exists('cdn')) { + define('ADVAGG_SKIP_FILE_CREATE_URL_INSIDE_CSS', FALSE); +} +else { + define('ADVAGG_SKIP_FILE_CREATE_URL_INSIDE_CSS', TRUE); +} + +/** + * 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', 1800); + +/** + * See if the mtime != if TRUE < if FALSE. + */ +define('ADVAGG_STRICT_MTIME_CHECK', TRUE); + +/** + * Default value to see if .br files should be created as well. + */ +if (function_exists('brotli_compress')) { + define('ADVAGG_BROTLI', TRUE); +} +else { + define('ADVAGG_BROTLI', FALSE); +} + +/** + * Default value to see zopfli_encode should not be used. + */ +define('ADVAGG_NO_ZOPFLI', FALSE); + +/** + * If true do test for 304 files on the status report page. + */ +define('ADVAGG_SKIP_304_CHECK', FALSE); + +/** + * If false do not set the immutable header. + */ +define('ADVAGG_RESOURCE_HINTS_USE_IMMUTABLE', TRUE); + +/** + * If true chrome=1 will be added to the X-UA-Compatible header. + */ +define('ADVAGG_CHROME_HEADER_ENABLED', FALSE); + +/** + * If true advagg htaccess Options uses SymLinksIfOwnerMatch vs FollowSymLinks. + */ +define('ADVAGG_HTACCESS_SYMLINKSIFOWNERMATCH', FALSE); + +/** + * How far down a JS file to look for use strict. + */ +define('ADVAGG_JS_HEADER_LENGTH', 24576); + +/** + * If not empty advagg htaccess will include the given rewritebase. + */ +define('ADVAGG_HTACCESS_REWRITEBASE', ''); + +/** + * Preload CSS/JS header limit 3kb. + */ +define('ADVAGG_RESOURCE_HINTS_PRELOAD_MAX_SIZE', 3072); + +/** + * If TRUE advagg will search for and remove empty CSS files from aggregates. + */ +define('ADVAGG_CSS_REMOVE_EMPTY_FILES', FALSE); + +/** + * If TRUE advagg will search for and remove empty JS files from aggregates. + */ +define('ADVAGG_JS_REMOVE_EMPTY_FILES', FALSE); + +/** + * If TRUE advagg will be disabled on admin pages. + */ +define('ADVAGG_DISABLE_ON_ADMIN', FALSE); + +/** + * Default is 200; 203 has been requested in the past. + */ +define('ADVAGG_HTTP_200_CODE', 200); + +/** + * Verify all 3 hashes from the filename, if TRUE only verify 1st hash. + */ +define('ADVAGG_WEAK_FILE_VERIFICATION', FALSE); + +/** + * If FALSE lock_acquire is used before writing a file. + */ +define('ADVAGG_NO_LOCKS', FALSE); + +/** + * If TRUE drupal_get_html_head will be rendered at the top of the css section. + */ +define('ADVAGG_HTML_HEAD_IN_CSS_LOCATION', FALSE); + +/** + * If 4 the admin section gets unlocked. + */ +define('ADVAGG_ADMIN_MODE', 4); + +/** + * If TRUE farfuture headers will go out if the file is delivered by php. + */ +define('ADVAGG_FARFUTURE_PHP', FALSE); + +/** + * Internal urls set here will have advagg disabled on those pages. + */ +define('ADVAGG_DISABLE_ON_LISTED_PAGES', ''); + +/** + * @} End of "defgroup default_variables". + */ + +/** + * @addtogroup hooks + * @{ + */ + +/** + * Implements hook_help(). + */ +function advagg_help($path, $arg) { + switch ($path) { + case 'admin/help#advagg': + $filepath = dirname(__FILE__) . '/README.txt'; + if (file_exists($filepath)) { + $readme = file_get_contents($filepath); + } + if (!isset($readme)) { + return NULL; + } + if (module_exists('markdown')) { + $filters = module_invoke('markdown', 'filter_info'); + $info = $filters['filter_markdown']; + + if (function_exists($info['process callback'])) { + $output = $info['process callback']($readme, NULL); + } + else { + $output = '<pre>' . $readme . '</pre>'; + } + } + else { + $output = '<pre>' . $readme . '</pre>'; + } + return $output; + } +} + +/** + * Implements hook_block_view_alter(). + */ +function advagg_block_view_alter(&$data, $block) { + // Do not run hook if AdvAgg is disabled. + if (!advagg_enabled()) { + return; + } + // Do not run hook if setting is disabled. + if (!variable_get('advagg_scripts_scope_anywhere', ADVAGG_SCRIPTS_SCOPE_ANYWHERE)) { + return; + } + + if (empty($data) || empty($data['content'])) { + return; + } + + $block_info = $block->module . ':' . $block->delta; + $prefix = "<!-- AdvAgg block:prefix:$block_info tag -->"; + $suffix = "<!-- AdvAgg block:suffix:$block_info tag -->"; + if (is_string($data['content'])) { + $data['content'] = $prefix . $data['content'] . $suffix; + } + else { + if (!isset($data['content']['#prefix'])) { + $data['content']['#prefix'] = ''; + } + $data['content']['#prefix'] .= $prefix; + if (!isset($data['content']['#suffix'])) { + $data['content']['#suffix'] = ''; + } + $data['content']['#suffix'] .= $suffix; + } +} + +/** + * Implements hook_views_pre_render(). + */ +function advagg_views_pre_render(&$view) { + // Do not run hook if AdvAgg is disabled. + if (!advagg_enabled()) { + return; + } + // Do not run hook if setting is disabled. + if (!variable_get('advagg_scripts_scope_anywhere', ADVAGG_SCRIPTS_SCOPE_ANYWHERE)) { + return; + } + + $info = "{$view->name}:{$view->current_display}"; + $prefix = "<!-- AdvAgg view:prefix:$info tag -->"; + $suffix = "<!-- AdvAgg view:suffix:$info tag -->"; + if (!isset($view->attachment_before)) { + $view->attachment_before = ''; + } + $view->attachment_before .= $prefix; + if (!isset($view->attachment_after)) { + $view->attachment_after = ''; + } + $view->attachment_after .= $suffix; +} + +/** + * Implements hook_panels_pre_render(). + */ +function advagg_panels_pre_render($panels_display, &$renderer) { + // Do not run hook if AdvAgg is disabled. + if (!advagg_enabled()) { + return; + } + // Do not run hook if setting is disabled. + if (!variable_get('advagg_scripts_scope_anywhere', ADVAGG_SCRIPTS_SCOPE_ANYWHERE)) { + return; + } + + $info = "{$panels_display->layout}:{$panels_display->css_id}"; + $prefix = "<!-- AdvAgg panels:prefix:$info tag -->"; + $suffix = "<!-- AdvAgg panels:suffix:$info tag -->"; + if (!isset($renderer->prefix)) { + $renderer->prefix = ''; + } + $renderer->prefix .= $prefix; + if (!isset($renderer->suffix)) { + $renderer->suffix = ''; + } + $renderer->suffix .= $suffix; +} + +/** + * Implements hook_url_inbound_alter(). * - * If host includes subdomain, rewrite URI and internal path if necessary. + * Inbound URL rewrite helper. If host includes subdomain, rewrite URI and + * internal path if necessary. */ function advagg_url_inbound_alter(&$path, $original_path, $path_language) { // Do nothing if this has been disabled. @@ -210,8 +522,7 @@ function advagg_url_inbound_alter(&$path, $original_path, $path_language) { // If requested path was for an advagg file but now it is something else // switch is back to the advagg file. - // @ignore sniffer_whitespace_openbracketspacing_openingwhitespace:2 - if ( !empty($path) + if (!empty($path) && $path != $request_path && advagg_match_file_pattern($request_path) ) { @@ -233,7 +544,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; } } @@ -262,12 +573,15 @@ function advagg_hook_info() { // advagg_get_root_files_dir_alter // because these 3 hooks are used on most requests. $advagg_hooks = array( + 'advagg_get_css_file_contents_pre_alter', 'advagg_get_css_file_contents_alter', 'advagg_get_js_file_contents_alter', 'advagg_get_css_aggregate_contents_alter', 'advagg_get_js_aggregate_contents_alter', + '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', @@ -277,6 +591,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) { @@ -289,77 +604,75 @@ function advagg_hook_info() { * Implements hook_module_implements_alter(). */ function advagg_module_implements_alter(&$implementations, $hook) { - // Move advagg to the top. + // Move advagg_theme_registry_alter to the top. if ($hook === 'theme_registry_alter' && array_key_exists('advagg', $implementations)) { $item = array('advagg' => $implementations['advagg']); unset($implementations['advagg']); $implementations = array_merge($item, $implementations); } - // Move advagg to the top. + // Move advagg_ajax_render_alter to the top. if ($hook === 'ajax_render_alter' && array_key_exists('advagg', $implementations)) { $item = array('advagg' => $implementations['advagg']); unset($implementations['advagg']); $implementations = array_merge($item, $implementations); } - // Move advagg to the bottom. + // Move advagg_element_info_alter to the bottom. if ($hook === 'element_info_alter' && array_key_exists('advagg', $implementations)) { $item = $implementations['advagg']; unset($implementations['advagg']); $implementations['advagg'] = $item; } + // Replace locale_js_alter with _advagg_locale_js_alter. if ($hook === 'js_alter' && array_key_exists('locale', $implementations)) { - // Replace locale with _advagg_locale. unset($implementations['locale']); $implementations['_advagg_locale'] = FALSE; } - if ($hook === 'js_alter' && array_key_exists('advagg', $implementations)) { - if (variable_get('advagg_run_alter_after_theme', ADVAGG_RUN_ALTER_AFTER_THEME)) { - // Remove advagg & advagg_mod; runs after drupal_alter('js', $js). - unset($implementations['advagg']); - if (array_key_exists('advagg_mod', $implementations)) { - unset($implementations['advagg_mod']); - } - } - else { - // Move advagg & advagg_mod to the bottom, but advagg is above advagg_mod. - $item = $implementations['advagg']; - unset($implementations['advagg']); - $implementations['advagg'] = $item; - if (array_key_exists('advagg_mod', $implementations)) { - $item = $implementations['advagg_mod']; - unset($implementations['advagg_mod']); - $implementations['advagg_mod'] = $item; - } - } + // Move advagg_file_url_alter to the bottom. + if ($hook === 'file_url_alter' && array_key_exists('advagg', $implementations)) { + $item = $implementations['advagg']; + unset($implementations['advagg']); + $implementations['advagg'] = $item; } - if ($hook === 'css_alter' && array_key_exists('advagg', $implementations)) { - if (variable_get('advagg_run_alter_after_theme', ADVAGG_RUN_ALTER_AFTER_THEME)) { - // Remove advagg & advagg_mod; runs after drupal_alter('css', $css). - unset($implementations['advagg']); - if (array_key_exists('advagg_mod', $implementations)) { - unset($implementations['advagg_mod']); - } - } - else { - // Move advagg & advagg_mod to the bottom, but advagg is above advagg_mod. + if ($hook === 'requirements') { + // Move advagg_requirements to the bottom. + if (array_key_exists('advagg', $implementations)) { $item = $implementations['advagg']; unset($implementations['advagg']); $implementations['advagg'] = $item; - if (array_key_exists('advagg_mod', $implementations)) { - $item = $implementations['advagg_mod']; - unset($implementations['advagg_mod']); - $implementations['advagg_mod'] = $item; - } + } + // Move advagg_css_cdn to the bottom. + if (array_key_exists('advagg_css_cdn', $implementations)) { + $item = $implementations['advagg_css_cdn']; + unset($implementations['advagg_css_cdn']); + $implementations['advagg_css_cdn'] = $item; + } + // Move advagg_css_compress to the bottom. + if (array_key_exists('advagg_css_compress', $implementations)) { + $item = $implementations['advagg_css_compress']; + unset($implementations['advagg_css_compress']); + $implementations['advagg_css_compress'] = $item; + } + // Move advagg_js_cdn to the bottom. + if (array_key_exists('advagg_js_cdn', $implementations)) { + $item = $implementations['advagg_js_cdn']; + unset($implementations['advagg_js_cdn']); + $implementations['advagg_js_cdn'] = $item; + } + // Move advagg_js_compress to the bottom. + if (array_key_exists('advagg_js_compress', $implementations)) { + $item = $implementations['advagg_js_compress']; + unset($implementations['advagg_js_compress']); + $implementations['advagg_js_compress'] = $item; } } - // Move advagg to the bottom. - if ($hook === 'file_url_alter' && array_key_exists('advagg', $implementations)) { + // Move advagg_cron to the bottom. + if ($hook === 'cron' && array_key_exists('advagg', $implementations)) { $item = $implementations['advagg']; unset($implementations['advagg']); $implementations['advagg'] = $item; @@ -372,12 +685,57 @@ function advagg_module_implements_alter(&$implementations, $hook) { * This is a locking wrapper for locale_js_alter(). */ function _advagg_locale_js_alter(&$js) { + // If the variable is empty then get the latest variable from the database. + $name = 'javascript_parsed'; + $parsed = variable_get($name, array()); + if (empty($parsed)) { + $variables = array_map('unserialize', db_query('SELECT name, value FROM {variable} WHERE name = :name', array(':name' => $name))->fetchAllKeyed()); + if (!empty($variables[$name])) { + $GLOBALS['conf'][$name] = $variables[$name]; + } + } + + // See if locale_js_alter() needs to do anything. + $dir = 'public://' . variable_get('locale_js_directory', 'languages'); + $new_files = FALSE; + // See if a rebuild of the translation file for the current language is + // needed. + if (!empty($parsed['refresh:' . $GLOBALS['language']->language])) { + $new_files = TRUE; + } + // Check for new js source files. + if (empty($new_files)) { + foreach ($js as $item) { + if ($item['type'] === 'file' + && !in_array($item['data'], $parsed) + && substr($item['data'], 0, strlen($dir)) != $dir + ) { + $new_files = TRUE; + break; + } + } + } + if (empty($new_files)) { + // No new files to manage, just add in available i18n files. + advagg_locale_js_add_translations($js, $dir); + // Exit function. + return; + } + $count = 0; - while (!lock_acquire('locale_js_alter', 5)) { + while (!lock_acquire('locale_js_alter', 10)) { ++$count; - // If we've waited over 5 times then skip. - if ($count > 5) { + // If we've waited over 3 times then skip. + if ($count > 3) { lock_release('locale_js_alter'); + // Add in available i18n files. + advagg_locale_js_add_translations($js, $dir); + + // Disable saving to the cache as translations might be missing. + drupal_page_is_cacheable(FALSE); + if (variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL) > 1) { + $GLOBALS['conf']['advagg_cache_level'] = 0; + } return; } @@ -385,10 +743,19 @@ function _advagg_locale_js_alter(&$js) { lock_wait('locale_js_alter'); } - // Run the alter and return. - $return = locale_js_alter($js); + try { + // Run the alter. + 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: <code>@info</code>', array('@info' => $e)); + } + } lock_release('locale_js_alter'); - return $return; } /** @@ -402,9 +769,7 @@ function advagg_system_info_alter(&$info, $file, $type) { } // Replace advagg path. - // @ignore sniffer_whitespace_openbracketspacing_openingwhitespace:2 - // @ignore sniffer_whitespace_closebracketspacing_closingwhitespace:8 - if ( !empty($info['configure']) + if (!empty($info['configure']) && strpos($info['configure'], '/advagg') !== FALSE && ((!empty($info['dependencies']) && is_array($info['dependencies']) @@ -439,20 +804,14 @@ function advagg_file_url_alter(&$original_uri) { return; } - // Ignore coder warnings. - // @ignore sniffer_commenting_inlinecomment_spacingbefore:10 - // @ignore sniffer_whitespace_openbracketspacing_openingwhitespace:11 - // @ignore sniffer_whitespace_closebracketspacing_closingwhitespace:19 - // @ignore sniffer_commenting_inlinecomment_spacingafter - // // CDN fix. // Do nothing if // in maintenance_mode // CDN module does not exist // CDN far future is disabled // CDN mode is not basic - // URI does not contain cdn/farfuture/ - if ( variable_get('maintenance_mode', FALSE) + // 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) || variable_get(CDN_MODE_VARIABLE, CDN_MODE_BASIC) != CDN_MODE_BASIC @@ -473,22 +832,75 @@ function advagg_menu() { $file_path = drupal_get_path('module', 'advagg'); $config_path = advagg_admin_config_root_path(); - $items[$css_path[1] . '/%'] = array( - 'title' => "Generate CSS Aggregate", - 'page callback' => 'advagg_missing_aggregate', - 'type' => MENU_CALLBACK, - 'access callback' => TRUE, - 'file path' => $file_path, - 'file' => 'advagg.missing.inc', - ); - $items[$js_path[1] . '/%'] = array( - 'title' => "Generate JS Aggregate", - 'page callback' => 'advagg_missing_aggregate', - 'type' => MENU_CALLBACK, - 'access callback' => TRUE, - 'file path' => $file_path, - 'file' => 'advagg.missing.inc', - ); + $path_defined = FALSE; + if (advagg_s3fs_evaluate_no_rewrite_cssjs(FALSE)) { + $external_css = trim(parse_url(str_replace('/test.css', '/%', file_create_url($css_path[0] . '/test.css')), PHP_URL_PATH)); + if (strpos($external_css, $GLOBALS['base_path']) === 0) { + $external_css = substr($external_css, strlen($GLOBALS['base_path'])); + } + $external_js = trim(parse_url(str_replace('/test.js', '/%', file_create_url($js_path[0] . '/test.js')), PHP_URL_PATH)); + if (strpos($external_js, $GLOBALS['base_path']) === 0) { + $external_js = substr($external_js, strlen($GLOBALS['base_path'])); + } + $items[$external_css] = array( + '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', + ); + $items[$external_js] = array( + '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', + ); + $path_defined = TRUE; + } + + if (!$path_defined) { + $items[$css_path[1] . '/%'] = array( + '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', + ); + $items[$js_path[1] . '/%'] = array( + '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', + ); + } + + // If mutiple paths are symlinked to the same location; allow advagg to handle + // those addtional locations. + $advagg_additional_generate_paths = variable_get('advagg_additional_generate_paths', array()); + if (!empty($advagg_additional_generate_paths)) { + foreach ($advagg_additional_generate_paths as $path) { + $items[$path] = array( + 'title' => "Generate CSS/JS 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', + ); + } + } + $items[$config_path . '/default'] = array( 'title' => 'Performance', 'type' => MENU_DEFAULT_LOCAL_TASK, @@ -536,16 +948,15 @@ function advagg_menu() { return $items; } -// @ignore sniffer_commenting_functioncomment_hookparamdoc:7 /** * Implements hook_cron(). * * This will be ran once a day at most. - * - * @param bool $bypass_time_check - * Set to TRUE to skip the 24 hour check. */ function advagg_cron($bypass_time_check = FALSE) { + // @param bool $bypass_time_check + // Set to TRUE to skip the 24 hour check. + // // Execute once a day (24 hours). if (!$bypass_time_check && variable_get('advagg_cron_timestamp', 0) > (REQUEST_TIME - variable_get('advagg_cron_frequency', ADVAGG_CRON_FREQUENCY))) { return array(); @@ -560,7 +971,10 @@ function advagg_cron($bypass_time_check = FALSE) { module_load_include('inc', 'advagg', 'advagg.cache'); $return[] = advagg_delete_stale_aggregates(); - // Remove orphaned files. + // Delete all empty aggregated files. + $return[] = advagg_delete_empty_aggregates(); + + // Delete orphaned aggregates. $return[] = advagg_delete_orphaned_aggregates(); // Remove aggregates that include missing files. @@ -571,25 +985,34 @@ 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(); + + // Update libraries data. + advagg_get_remote_libraries_versions(TRUE); + return $return; } -// @ignore sniffer_commenting_functioncomment_hookparamdoc:5 /** * Implements hook_flush_caches(). - * - * @param bool $all_bins - * TRUE: Get all advagg cache bins. - * @param bool $push_new_changes - * FALSE: Do not scan for changes. */ function advagg_flush_caches($all_bins = FALSE, $push_new_changes = TRUE) { + // * @param bool $all_bins + // * TRUE: Get all advagg cache bins. + // * @param bool $push_new_changes + // * FALSE: Do not scan for changes. + // // Send back a blank array if aav table doesn't exist. if (!db_table_exists('advagg_aggregates_versions')) { return array(); } - // Scan for & push new changes. + // Scan for and push new changes. module_load_include('inc', 'advagg', 'advagg.cache'); if ($push_new_changes) { advagg_push_new_changes(); @@ -607,45 +1030,62 @@ function advagg_flush_caches($all_bins = FALSE, $push_new_changes = TRUE) { * Implements hook_element_info_alter(). */ function advagg_element_info_alter(&$type) { + // Replace drupal_pre_render_styles with advagg_pre_render_styles. + $type['styles']['#items'] = array(); + if (!isset($type['styles']['#pre_render'])) { + $type['styles']['#pre_render'] = array(); + } + $key = array_search('drupal_pre_render_styles', $type['styles']['#pre_render']); + if ($key !== FALSE) { + $type['styles']['#pre_render'][$key] = 'advagg_pre_render_styles'; + } + else { + $type['styles']['#pre_render'][] = 'advagg_pre_render_styles'; + } + // Allow for other code to easily change the render with alter hooks. + $type['styles']['#pre_render'][] = 'advagg_modify_css_pre_render'; + $type['styles']['#group_callback'] = 'drupal_group_css'; // Swap in our own aggregation callback. - if (isset($type['styles']['#aggregate_callback'])) { - $type['styles']['#aggregate_callback'] = '_advagg_aggregate_css'; - - // Replace drupal_pre_render_styles with advagg_pre_render_styles. - $key = array_search('drupal_pre_render_styles', $type['styles']['#pre_render']); - if ($key !== FALSE) { - $type['styles']['#pre_render'][$key] = 'advagg_pre_render_styles'; - } - else { - $type['styles']['#pre_render'][] = 'advagg_pre_render_styles'; - } + $type['styles']['#aggregate_callback'] = '_advagg_aggregate_css'; + $type['styles']['#type'] = 'styles'; - // Allow for other code to easily change the render with alter hooks. - $type['styles']['#pre_render'][] = 'advagg_modify_css_pre_render'; + // Replace drupal_pre_render_scripts with advagg_pre_render_scripts. + $type['scripts']['#items'] = array(); + if (!isset($type['scripts']['#pre_render'])) { + $type['scripts']['#pre_render'] = array(); } - - // Swap in our own aggregation callback. - if (isset($type['scripts']['#aggregate_callback'])) { - $type['scripts']['#aggregate_callback'] = '_advagg_aggregate_js'; + $key_drupal = array_search('drupal_pre_render_scripts', $type['scripts']['#pre_render']); + $key_omega = array_search('omega_pre_render_scripts', $type['scripts']['#pre_render']); + $key_aurora = array_search('aurora_pre_render_scripts', $type['scripts']['#pre_render']); + if ($key_drupal !== FALSE) { + $type['scripts']['#pre_render'][$key_drupal] = 'advagg_pre_render_scripts'; + } + elseif ($key_omega !== FALSE) { + $type['scripts']['#pre_render'][$key_omega] = 'advagg_pre_render_scripts'; + } + elseif ($key_aurora !== FALSE) { + $type['scripts']['#pre_render'][$key_aurora] = 'advagg_pre_render_scripts'; } else { - $type['scripts'] = array( - '#items' => array(), - '#pre_render' => array('advagg_pre_render_scripts'), - '#group_callback' => 'advagg_group_js', - '#aggregate_callback' => '_advagg_aggregate_js', - '#type' => 'scripts', - ); + $type['scripts']['#pre_render'][] = 'advagg_pre_render_scripts'; } - // Allow for other code to easily change the render with alter hooks. $type['scripts']['#pre_render'][] = 'advagg_modify_js_pre_render'; + $type['scripts']['#group_callback'] = 'advagg_group_js'; + // Swap in our own aggregation callback. + $type['scripts']['#aggregate_callback'] = '_advagg_aggregate_js'; + $type['scripts']['#type'] = 'scripts'; + + // Copy html_tag to html_script_tag. + $type['html_script_tag'] = $type['html_tag']; + $type['html_script_tag']['#theme'] = 'html_script_tag'; + $type['html_script_tag']['#type'] = 'html_script_tag'; } /** * Implements hook_theme_registry_alter(). * - * Replace template_process_html with _advagg_process_html + * Replace template_process_html with _advagg_process_html. */ function advagg_theme_registry_alter(&$theme_registry) { if (!isset($theme_registry['html'])) { @@ -657,7 +1097,24 @@ function advagg_theme_registry_alter(&$theme_registry) { if ($index !== FALSE) { $theme_registry['html']['process functions'][$index] = '_advagg_process_html'; } -} + else { + // Put AdvAgg at the bottom if we can't find the replacement. + $theme_registry['html']['process functions'][] = '_advagg_process_html'; + } + + // Copy html_tag to html_script_tag. + $theme_registry['html_script_tag'] = $theme_registry['html_tag']; + $theme_registry['html_script_tag']['function'] = 'theme_html_script_tag'; + + // Fix imce_page. + if (isset($theme_registry['imce_page'])) { + $advagg_path = drupal_get_path('module', 'advagg'); + $imce_path = drupal_get_path('module', 'imce'); + if (strpos($theme_registry['imce_page']['path'], $imce_path) !== FALSE) { + $theme_registry['imce_page']['path'] = $advagg_path . '/tpl'; + } + } +} /** * Implements hook_ajax_render_alter(). @@ -686,8 +1143,10 @@ function advagg_ajax_render_alter(&$commands) { $scripts_header = $scripts_footer = ''; if (!empty($items['js'])) { $scripts_footer_array = advagg_get_js('footer', $items['js'], TRUE); + // Function advagg_pre_render_scripts() gets called here. $scripts_footer = drupal_render($scripts_footer_array); $scripts_header_array = advagg_get_js('header', $items['js'], TRUE); + // Function advagg_pre_render_scripts() gets called here. $scripts_header = drupal_render($scripts_header_array); } @@ -698,11 +1157,7 @@ function advagg_ajax_render_alter(&$commands) { continue; } - // Ignore coder warnings. - // @ignore sniffer_whitespace_openbracketspacing_openingwhitespace:22 - // @ignore sniffer_whitespace_closebracketspacing_closingwhitespace:25 - // @ignore sniffer_commenting_inlinecomment_spacingafter - if ( $values['command'] === 'settings' + if ($values['command'] === 'settings' && is_array($values['settings']) && !empty($values['merge']) ) { @@ -710,7 +1165,7 @@ function advagg_ajax_render_alter(&$commands) { unset($commands[$key]); continue; } - if ( $values['command'] === 'insert' + if ($values['command'] === 'insert' && is_null($values['settings']) && $values['method'] === 'prepend' && $values['data'] == $core_scripts_header @@ -719,7 +1174,7 @@ function advagg_ajax_render_alter(&$commands) { unset($commands[$key]); continue; } - if ( $values['command'] === 'insert' + if ($values['command'] === 'insert' && is_null($values['settings']) && $values['method'] === 'append' && $values['data'] == $core_scripts_footer @@ -742,105 +1197,224 @@ 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(array_filter($settings['data'], 'is_array'))), TRUE)); } } /** - * Implements hook_js_alter(). + * Implements hook_preprocess_page(). */ -function advagg_js_alter(&$js) { - if (!advagg_enabled() || !variable_get('advagg_js_fix_type', ADVAGG_JS_FIX_TYPE)) { +function advagg_preprocess_page() { + // Scan for changes to any CSS/JS files if in development mode. + advagg_scan_filesystem_for_changes_live(); +} + +/** + * Implements hook_preprocess_html(). + * + * Add in rendering IE meta tag if "combine CSS" is enabled. + */ +function advagg_preprocess_html() { + // http://www.phpied.com/conditional-comments-block-downloads/#update + // Prevent conditional comments from stalling css downloads. + $fix_blocking_css_ie = array( + '#weight' => '-999999', + '#type' => 'markup', + '#markup' => "<!--[if IE]><![endif]-->\n", + ); + // Add markup for IE conditional comments to head. + drupal_add_html_head($fix_blocking_css_ie, 'fix_blocking_css_ie'); + + // Do not force IE rendering mode if "combine CSS" is disabled. + if (!variable_get('advagg_combine_css_media', ADVAGG_COMBINE_CSS_MEDIA)) { return; } - // Get hostname and base path. - $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); + // Send IE meta tag to force IE rendering mode header. + $x_ua_compatible = 'IE=edge'; + if (variable_get('advagg_chrome_header_enabled', ADVAGG_CHROME_HEADER_ENABLED)) { + $x_ua_compatible .= ',chrome=1'; + } + drupal_add_http_header('X-UA-Compatible', $x_ua_compatible); +} - // Fix type if it was incorrectly set. - foreach ($js as &$value) { - if (empty($value['data']) || !is_string($value['data'])) { - continue; - } +/** + * 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); +} - // If type is external but doesn't start with http, https, or // change it - // to file. - if ( $value['type'] === 'external' - && stripos($value['data'], 'http://') !== 0 - && stripos($value['data'], 'https://') !== 0 - && stripos($value['data'], '//') !== 0 - ) { - $value['type'] = 'file'; +/** + * Implements hook_js_alter(). + */ +function advagg_js_alter(&$js) { + if (module_exists('admin_menu')) { + // Fix for admin menu; put JS in footer. + $path = drupal_get_path('module', 'admin_menu'); + $filename = $path . '/admin_menu.js'; + if (isset($js[$filename])) { + $js[$filename]['scope'] = 'footer'; } + } +} - // If type is file but it starts with http, https, or // change it to - // external. - if ( $value['type'] === 'file' - && (stripos($value['data'], 'http://') === 0 - || stripos($value['data'], 'https://') === 0 - || (stripos($value['data'], '//') === 0 && stripos($value['data'], '///') === FALSE)) - ) { - $value['type'] = 'external'; - } +/** + * @} End of "addtogroup hooks". + */ - // If type is external & starts with http, https, or // but points to this - // host change it to file, but move it to the top of the aggregation stack. - if ( $value['type'] === 'external' - && stripos($value['data'], $mod_base_url) !== FALSE - && ( stripos($value['data'], 'http://') === 0 - || stripos($value['data'], 'https://') === 0 - || stripos($value['data'], '//') === 0 - ) - ) { - $value['type'] = 'file'; - $value['group'] = JS_LIBRARY; - $value['every_page'] = TRUE; - $value['weight'] = -40000; - $value['data'] = substr($value['data'], stripos($value['data'], $mod_base_url) + $mod_base_url_len); +/** + * @defgroup 3rd_party_hooks 3rd party hook implementations + * @{ + * Hooks that are not apart of core or AdvAgg. + */ + +/** + * Implements hook_cron_alter(). + */ +function advagg_cron_alter(&$data) { + // Run this cron job every 2 minutes. + if (isset($data['advagg_js_compress_cron'])) { + $data['advagg_js_compress_cron']['rule'] = '*/2 * * * *'; + } + // Run this cron job every 5 minutes. + if (isset($data['advagg_relocate_cron'])) { + $data['advagg_relocate_cron']['rule'] = '*/5 * * * *'; + } + // Run this cron job every day. + if (isset($data['advagg_cron'])) { + $data['advagg_cron']['rule'] = '0 0 * * *'; + } +} + +/** + * Implements hook_password_policy_force_change_allowed_paths_alter(). + */ +function advagg_password_policy_force_change_allowed_paths_alter(&$allowed_paths) { + $advagg_items = advagg_menu(); + foreach ($advagg_items as $path => $attributes) { + if (!empty($attributes['page callback']) && $attributes['page callback'] === 'advagg_missing_aggregate') { + $allowed_paths[] = str_replace('/%', '/*', $path); } } - unset($value); } /** - * Implements hook_css_alter(). + * Implements hook_s3fs_upload_params_alter(). + * + * Set headers for advagg files. */ -function advagg_css_alter(&$css) { - if (!advagg_enabled() || !variable_get('advagg_css_fix_type', ADVAGG_CSS_FIX_TYPE)) { - return; +function advagg_s3fs_upload_params_alter(&$upload_params) { + // Get advagg dir. + list($css_path, $js_path) = advagg_get_root_files_dir(); + $scheme = file_uri_scheme($css_path[1]); + if ($scheme) { + $css_path_dir = parse_url($css_path[1]); + $css_path_dir = str_replace("$scheme://", '', $css_path[1]); + } + else { + $css_path_dir = ltrim($css_path[1], '/'); + } + $scheme = file_uri_scheme($js_path[1]); + if ($scheme) { + $js_path_dir = parse_url($js_path[1]); + $js_path_dir = str_replace("$scheme://", '', $js_path_dir[1]); + } + else { + $js_path_dir = ltrim($js_path[1], '/'); } - // Fix type if it was incorrectly set. - foreach ($css as &$value) { - if (empty($value['data']) || !is_string($value['data'])) { - continue; + // Get file type in advagg dir, css or js. + $type = ''; + if (strpos($upload_params['Bucket'] . '/' . $upload_params['Key'], $css_path_dir) !== FALSE) { + $type = 'css'; + } + if (strpos($upload_params['Bucket'] . '/' . $upload_params['Key'], $js_path_dir) !== FALSE) { + $type = 'js'; + } + if ($js_path_dir === $css_path_dir && !empty($type)) { + $pathinfo = pathinfo($upload_params['Key']); + if ($pathinfo['extension'] === 'gz') { + $pathinfo = pathinfo($pathinfo['filename']); } + $type = $pathinfo['extension']; + } + if (empty($type)) { + // Only change advagg files. + return; + } - // If type is external but doesn't start with http, https, or // change it - // to file. - if ( $value['type'] === 'external' - && stripos($value['data'], 'http://') !== 0 - && stripos($value['data'], 'https://') !== 0 - && stripos($value['data'], '//') !== 0 - ) { - $value['type'] = 'file'; - } + // Cache control is 52 weeeks. + if (variable_get('advagg_resource_hints_use_immutable', ADVAGG_RESOURCE_HINTS_USE_IMMUTABLE)) { + $upload_params['CacheControl'] = 'max-age=31449600, no-transform, public, immutable'; + } + else { + $upload_params['CacheControl'] = 'max-age=31449600, no-transform, public'; + } + // Expires in 365 days. + $upload_params['Expires'] = gmdate('D, d M Y H:i:s \G\M\T', REQUEST_TIME + 365 * 24 * 60 * 60); - // If type is file but it starts with http, https, or // change it to - // external. - if ( $value['type'] === 'file' - && ( stripos($value['data'], 'http://') === 0 - || stripos($value['data'], 'https://') === 0 - || ( stripos($value['data'], '//') === 0 - && stripos($value['data'], '///') === FALSE - ) - ) - ) { - $value['type'] = 'external'; + // The extension is .css or .js. + $pathinfo = pathinfo($upload_params['Key']); + if ($pathinfo['extension'] === $type) { + if (variable_get('advagg_gzip', ADVAGG_GZIP)) { + // Set gzip. + $upload_params['ContentEncoding'] = 'gzip'; + } + elseif (variable_get('advagg_brotli', ADVAGG_BROTLI)) { + // Set br. + $upload_params['ContentEncoding'] = 'br'; } } - unset($value); +} + +/** + * Return s3fs configuration settings and values. + * + * @param string $key + * A specific key available in the s3fs configuration. NULL by default. + * + * @return array|string|null + * The full s3fs configuration settings, value of a specific key, + * or NULL if s3fs and the function do not exist. + */ +function advagg_get_s3fs_config($key = NULL) { + if (module_exists('s3fs') && is_callable('_s3fs_get_config')) { + $s3fs_config = _s3fs_get_config(); + return (empty($key)) ? $s3fs_config : $s3fs_config[$key]; + } + else { + return NULL; + } +} + +/** + * Shortcut to evaluate if s3fs no_rewrite_cssjs is set or empty. + * + * If this needs to be accessed in a loop, it is more efficient to call + * advagg_get_s3fs_config() once from outside of the loop. An example + * can be seen in the advagg_install_check_via_http function. + * + * @param bool $is_set + * Check if no_write_cssjs field is set (TRUE) or empty (FALSE). + * + * @return bool + * TRUE or FALSE is returned based on evaluating the field. If + * s3fs_config returns a NULL, evaluate the function to FALSE. + * + * @see advagg_get_s3fs_config() + */ +function advagg_s3fs_evaluate_no_rewrite_cssjs($is_set = TRUE) { + $s3fs_no_rewrite_cssjs = advagg_get_s3fs_config('no_rewrite_cssjs'); + if (!is_null($s3fs_no_rewrite_cssjs)) { + return ($is_set) ? !empty($s3fs_no_rewrite_cssjs) : empty($s3fs_no_rewrite_cssjs); + } + else { + return FALSE; + } } /** @@ -871,52 +1445,52 @@ function advagg_admin_menu_output_alter(array &$content) { } /** - * Implements hook_preprocess_page(). + * Implements hook_anonymous_login_paths_alter(). */ -function advagg_preprocess_page() { - // Scan for changes to any CSS/JS files if in development mode. - advagg_scan_filesystem_for_changes_live(); +function advagg_anonymous_login_paths_alter(&$paths) { + // Exclude advagg css/js paths. + list($css_path, $js_path) = advagg_get_root_files_dir(); + $paths['exclude'][] = $css_path[1] . '/*'; + $paths['exclude'][] = $js_path[1] . '/*'; } /** - * Implements hook_preprocess_html(). - * - * Add in rendering IE meta tag if "combine CSS" is enabled. + * Implements hook_pre_flush_all_caches(). */ -function advagg_preprocess_html() { - // http://www.phpied.com/conditional-comments-block-downloads/#update - // Prevent conditional comments from stalling css downloads. - $fix_blocking_css_ie = array( - '#weight' => '-999999', - '#type' => 'markup', - '#markup' => "<!--[if IE]><![endif]-->\n", - ); - // Add markup for IE conditional comments to head. - drupal_add_html_head($fix_blocking_css_ie, 'fix_blocking_css_ie'); +function advagg_pre_flush_all_caches() { + static $run_once; - // Do not force IE rendering mode if "combine CSS" is disabled. - if (!variable_get('advagg_combine_css_media', ADVAGG_COMBINE_CSS_MEDIA)) { - return; + if (!isset($run_once)) { + $run_once = TRUE; + // Only invoked by registry_rebuild. + module_load_include('admin.inc', 'advagg'); + // Truncate the advagg_files table. + advagg_admin_truncate_advagg_files(); } +} - // Setup IE meta tag to force IE rendering mode. - $meta_ie_render_engine = array( - '#type' => 'html_tag', - '#tag' => 'meta', - '#attributes' => array( - 'http-equiv' => 'X-UA-Compatible', - 'content' => 'IE=edge,chrome=1', - ), - '#weight' => '-99999', - '#prefix' => '<!--[if IE]>', - '#suffix' => '<![endif]-->', - ); +/** + * @} End of "defgroup 3rd_party_hooks". + */ - // Add meta tag for IE to head. - drupal_add_html_head($meta_ie_render_engine, 'meta_ie_render_engine'); +/** + * Only the alter part of locale_js_alter(), not the parsing part. + * + * @param array $javascript + * An array with all JavaScript code. Defaults to the default + * JavaScript array for the given scope. + * @param string $dir + * String pointing to the public locale_js_directory. + */ +function advagg_locale_js_add_translations(array &$javascript, $dir) { + // Add the translation JavaScript file to the page. + if (!empty($GLOBALS['language']->javascript)) { + // Add the translation JavaScript file to the page. + $file = $dir . '/' . $GLOBALS['language']->language . '_' . $GLOBALS['language']->javascript . '.js'; + $javascript[$file] = drupal_js_defaults($file); + } } -// Core CSS/JS override functions. /** * Callback for pre_render so elements can be modified before they are rendered. * @@ -934,20 +1508,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 & $elements before they are + // 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; } @@ -968,17 +1544,21 @@ function advagg_modify_js_pre_render(array $elements) { * A render array that will render to a string of JavaScript tags. */ function advagg_modify_css_pre_render(array $elements) { + if (!advagg_enabled()) { + return $elements; + } + // Put children elements into a reference array. $children = array(); foreach ($elements as $key => &$value) { - if ($key !== '' && $key[0] === '#') { + if ($key !== '' && is_string($key) && (0 === strpos($key, '#'))) { continue; } $children[$key] = &$value; } unset($value); - // Allow other modules to modify $children & $elements before they are + // Allow other modules to modify $children and $elements before they are // rendered. // Call hook_advagg_modify_css_pre_render_alter() drupal_alter('advagg_modify_css_pre_render', $children, $elements); @@ -1054,9 +1634,9 @@ function _advagg_aggregate_css(array &$css_groups) { if (!empty($files_to_aggregate)) { $hooks_hash = advagg_get_current_hooks_hash(); - $css_hash = drupal_hash_base64(serialize($files_to_aggregate)); + $serialize_function = variable_get('advagg_serialize', ADVAGG_SERIALIZE); + $css_hash = drupal_hash_base64($serialize_function($files_to_aggregate)); $cache_id = 'advagg:css:' . $hooks_hash . ':' . $css_hash; - // @ignore druplart_andor_assignment if (variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL) >= 1 && $cache = cache_get($cache_id, 'cache_advagg_aggregates')) { $plans = $cache->data; } @@ -1119,7 +1699,7 @@ function _advagg_aggregate_js(array &$js_groups) { if ($preprocess_js) { // Set boolean to TRUE if all JS in footer. $all_in_footer = FALSE; - if (module_exists('advagg_mod') && variable_get('advagg_mod_js_footer', ADVAGG_MOD_JS_FOOTER) == 2) { + if (module_exists('advagg_mod') && variable_get('advagg_mod_js_footer', ADVAGG_MOD_JS_FOOTER) >= 2) { $all_in_footer = TRUE; } foreach ($js_groups as $key => &$group) { @@ -1127,7 +1707,7 @@ function _advagg_aggregate_js(array &$js_groups) { // If a file group can be aggregated into a single file, do so, and set // the group's data property to the file path of the aggregate file. case 'file': - if ($group['preprocess']) { + if (!empty($group['preprocess'])) { // Special handing for when all JS is in the footer. if ($all_in_footer && $group['scope'] === 'footer' && $group['group'] > 9000) { ++$gap_counter; @@ -1156,9 +1736,9 @@ function _advagg_aggregate_js(array &$js_groups) { if (!empty($files_to_aggregate)) { $hooks_hash = advagg_get_current_hooks_hash(); - $js_hash = drupal_hash_base64(serialize($files_to_aggregate)); + $serialize_function = variable_get('advagg_serialize', ADVAGG_SERIALIZE); + $js_hash = drupal_hash_base64($serialize_function($files_to_aggregate)); $cache_id = 'advagg:js:' . $hooks_hash . ':' . $js_hash; - // @ignore druplart_andor_assignment if (variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL) >= 1 && $cache = cache_get($cache_id, 'cache_advagg_aggregates')) { $plans = $cache->data; } @@ -1189,12 +1769,14 @@ function _advagg_aggregate_js(array &$js_groups) { function _advagg_build_css_arrays_for_rendering($skip_alter = FALSE) { // Get the raw CSS variable. $raw_css = drupal_add_css(); - // Process & Sort css. + // Process and Sort css. $full_css = advagg_get_css($raw_css, $skip_alter); // Add attached js to drupal_add_js() function. - drupal_process_attached($full_css); - // Remove #attached since it's been added to the javascript array now. - unset($full_css['#attached']); + if (!empty($full_css['#attached'])) { + drupal_process_attached($full_css); + // Remove #attached since it's been added to the javascript array now. + unset($full_css['#attached']); + } return array($raw_css, $full_css); } @@ -1211,7 +1793,7 @@ function _advagg_build_css_arrays_for_rendering($skip_alter = FALSE) { function _advagg_build_js_arrays_for_rendering($skip_alter = FALSE) { // Get the raw JS variable. $javascript = drupal_add_js(); - // Process & Sort JS. + // Process and Sort JS. $full_javascript = advagg_get_full_js($javascript, $skip_alter); // Get scopes used in the js. $scopes = advagg_get_js_scopes($full_javascript); @@ -1230,29 +1812,60 @@ function _advagg_build_js_arrays_for_rendering($skip_alter = FALSE) { // Get the js settings. $js_scope_settings_array[$scope]['settings'] = $scripts['#items']['settings']; // Exclude JS Settings from the array; we'll add it back later. - unset($scripts['#items']['settings']); + $scripts['#items']['settings'] = array(); } $js_scope_array[$scope] = $scripts; } + + // Fix settings; if more than 1 is set, use the largest one. + if (count($js_scope_settings_array) > 1) { + $max = -1; + $max_scope = ''; + foreach ($js_scope_settings_array as $scope => $settings) { + $count = count($settings); + $max = max($max, $count); + if ($max == $count) { + $max_scope = $scope; + } + } + + foreach ($js_scope_settings_array as $scope => $settings) { + if ($scope !== $max_scope) { + unset($js_scope_settings_array[$scope]); + } + } + } return array($javascript, $js_scope_settings_array, $js_scope_array); } /** * Returns TRUE if the CSS is being loaded via JavaScript. * + * @param object $css_cache + * Cache object from cache_get(). + * * @return bool * TRUE if CSS loaded via JS. FALSE if not. */ -function advagg_css_in_js() { - if (( module_exists('advagg_mod') - && variable_get('advagg_mod_css_defer', ADVAGG_MOD_CSS_DEFER) - ) || ( - module_exists('css_delivery') - && css_delivery_enabled() - ) +function advagg_css_in_js($css_cache = NULL) { + if (module_exists('advagg_mod') + && variable_get('advagg_mod_css_defer', ADVAGG_MOD_CSS_DEFER) ) { return TRUE; } + if (module_exists('css_delivery') + && css_delivery_enabled() + ) { + return TRUE; + } + // Critical css added by another means. + if (!empty($css_cache->data[1]['#items'])) { + foreach ($css_cache->data[1]['#items'] as $values) { + if (!empty($values['critical-css'])) { + return TRUE; + } + } + } return variable_get('advagg_css_in_js', ADVAGG_CSS_IN_JS); } @@ -1267,7 +1880,7 @@ function advagg_css_in_js() { * _advagg_build_js_arrays_for_rendering(). * * @return array - * Array containing the $css_cache, $js_cache, $css_cache_id, & $js_cache_id. + * Array containing the $css_cache, $js_cache, $css_cache_id, $js_cache_id. */ function advagg_get_render_cache(array $full_css, array $js_scope_array) { $cids = array(); @@ -1276,20 +1889,21 @@ function advagg_get_render_cache(array $full_css, array $js_scope_array) { // Get advagg hash. $hooks_hash = advagg_get_current_hooks_hash(); + $serialize_function = variable_get('advagg_serialize', ADVAGG_SERIALIZE); if (advagg_file_aggregation_enabled('css')) { // Generate css cache id. - $cids[] = $css_cache_id = 'advagg:css:full:' . $hooks_hash . ':' . drupal_hash_base64(serialize($full_css)); + $cids[] = $css_cache_id = 'advagg:css:full:1.1:' . $hooks_hash . ':' . drupal_hash_base64($serialize_function($full_css)); } if (advagg_file_aggregation_enabled('js')) { // Generate js cache id. - $cids[] = $js_cache_id = 'advagg:js:full:' . $hooks_hash . ':' . drupal_hash_base64(serialize($js_scope_array)); + $cids[] = $js_cache_id = 'advagg:js:full:1.1:' . $hooks_hash . ':' . drupal_hash_base64($serialize_function($js_scope_array)); } if (!empty($cids)) { // 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]; } @@ -1299,10 +1913,10 @@ function advagg_get_render_cache(array $full_css, array $js_scope_array) { } // Special handling if the css is loaded via JS. - if ( advagg_css_in_js() - && !empty($css_cache) + if (!empty($css_cache) && empty($js_cache) - ) { + && advagg_css_in_js($css_cache) + ) { // If CSS is being loaded via JavaScript and the css cache is set but the // js cache is not set; then unset the css cache as well. unset($css_cache); @@ -1396,23 +2010,48 @@ 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']; } - $variables['head'] = drupal_get_html_head(); + $advagg_script_alt_scope_scripts = array(); + if (variable_get('advagg_scripts_scope_anywhere', ADVAGG_SCRIPTS_SCOPE_ANYWHERE)) { + $prefix = "<!-- AdvAgg page:prefix tag -->"; + $suffix = "<!-- AdvAgg page:suffix tag -->"; + $variables['page'] = $prefix . $variables['page'] . $suffix; + $prefix = "<!-- AdvAgg page_top:prefix tag -->"; + $suffix = "<!-- AdvAgg page_top:suffix tag -->"; + $variables['page_top'] = $prefix . $variables['page_top'] . $suffix; + $prefix = "<!-- AdvAgg page_bottom:prefix tag -->"; + $suffix = "<!-- AdvAgg page_bottom:suffix tag -->"; + $variables['page_bottom'] = $prefix . $variables['page_bottom'] . $suffix; + + $matches = array(); + preg_match_all('/<!-- AdvAgg (.*?) tag -->/', $variables['page_top'], $matches); + $advagg_script_alt_scope_scripts = array_merge($matches[1], $advagg_script_alt_scope_scripts); + preg_match_all('/<!-- AdvAgg (.*?) tag -->/', $variables['page'], $matches); + $advagg_script_alt_scope_scripts = array_merge($matches[1], $advagg_script_alt_scope_scripts); + preg_match_all('/<!-- AdvAgg (.*?) tag -->/', $variables['page_bottom'], $matches); + $advagg_script_alt_scope_scripts = array_merge($matches[1], $advagg_script_alt_scope_scripts); + } + + // Parts of drupal_get_html_head(). + $elements = drupal_add_html_head(); + if (is_callable('advagg_mod_html_head_post_alter')) { + advagg_mod_html_head_post_alter($elements); + } // Get default javascript. // @see http://drupal.org/node/1279226 @@ -1423,8 +2062,9 @@ function _advagg_process_html(&$variables) { // Try the render cache. if (!variable_get('advagg_debug', ADVAGG_DEBUG)) { - if (variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL) >= 5) { - // Get all CSS & JS variables needed; running no alters. + // No Alter. + if (variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL) >= 5 && !module_exists('advagg_relocate')) { + // Get all CSS and JS variables needed; running no alters. list($variables['css'], $full_css) = _advagg_build_css_arrays_for_rendering(TRUE); list($javascript, $js_scope_settings_array, $js_scope_array) = _advagg_build_js_arrays_for_rendering(TRUE); @@ -1432,8 +2072,9 @@ function _advagg_process_html(&$variables) { list($css_cache, $js_cache, $css_cache_id_no_alter, $js_cache_id_no_alter) = advagg_get_render_cache($full_css, $js_scope_array); } + // With Alter. if ((empty($css_cache->data) || empty($js_cache->data)) && variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL) >= 3) { - // Get all CSS & JS variables needed; running alters. + // Get all CSS and JS variables needed; running alters. list($variables['css'], $full_css) = _advagg_build_css_arrays_for_rendering(); list($javascript, $js_scope_settings_array, $js_scope_array) = _advagg_build_js_arrays_for_rendering(); @@ -1445,22 +2086,53 @@ function _advagg_process_html(&$variables) { // CSS has nice hooks so we don't need to work around it. if (!empty($css_cache->data)) { // Use render cache. - $variables['styles'] = $css_cache->data; + list($variables['styles'], $full_css) = $css_cache->data; } else { // Get the css if we have not done so. if (empty($full_css)) { list($variables['css'], $full_css) = _advagg_build_css_arrays_for_rendering(); } - // Render the CSS. + // Render the CSS; advagg_pre_render_styles() gets called here. $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, $variables['styles'], 'cache_advagg_aggregates', CACHE_TEMPORARY); + 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, $variables['styles'], 'cache_advagg_aggregates', CACHE_TEMPORARY); + cache_set($css_cache_id_no_alter, array($variables['styles'], $full_css), 'cache_advagg_aggregates', CACHE_TEMPORARY); + } + } + + if (module_exists('advagg_font') && variable_get('advagg_font_fontfaceobserver', ADVAGG_FONT_FONTFACEOBSERVER)) { + $fonts = array(); + foreach ($full_css['#groups'] as $groups) { + if (isset($groups['items']['files'])) { + foreach ($groups['items']['files'] as $file) { + if (isset($file['advagg_font'])) { + foreach ($file['advagg_font'] as $class => $name) { + $fonts[$class] = $name; + } + } + } + } + } + if (!empty($fonts)) { + if (isset($js_scope_settings_array)) { + $key = key($js_scope_settings_array); + $js_scope_settings_array[$key]['settings']['data'][] = array('advagg_font' => $fonts); + } + drupal_add_js(array('advagg_font' => $fonts), array('type' => 'setting')); + } + } + + if (variable_get('advagg_resource_hints_preload', ADVAGG_RESOURCE_HINTS_PRELOAD)) { + foreach ($full_css['#groups'] as $groups) { + if (empty($groups['data']) || $groups['type'] === 'inline') { + continue; + } + advagg_add_preload_header(advagg_convert_abs_to_rel(file_create_url($groups['data'])), 'style'); } } @@ -1484,14 +2156,17 @@ function _advagg_process_html(&$variables) { // Replace cached settings with current ones. $js_settings_used = array(); + $js_scope_settings_array_copy = $js_scope_settings_array; if (variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL) >= 5) { - if (!empty($js_scope_settings_array['header']) && empty($js_scope_settings_array['footer'])) { + if (!empty($js_scope_settings_array_copy['header']) && empty($js_scope_settings_array_copy['footer'])) { // Copy header settings into the footer. - $js_scope_settings_array['footer'] = $js_scope_settings_array['header']; + $js_scope_settings_array_copy['footer'] = $js_scope_settings_array_copy['header']; } } - foreach ($js_cache->data as $scope => $value) { + list($js_cache_data, $js_scope_array) = $js_cache->data; + + foreach ($js_cache_data as $scope => $value) { $scope_settings = $scope; if ($scope_settings === 'scripts') { $scope_settings = 'header'; @@ -1504,27 +2179,28 @@ function _advagg_process_html(&$variables) { if ($start !== FALSE) { // If the cache and current settings scope's do not match; do not use // the cached version. - if (!isset($js_scope_settings_array[$scope_settings]['settings'])) { + if (!isset($js_scope_settings_array_copy[$scope_settings]['settings'])) { $use_cache = FALSE; break; } // Replace cached Drupal.settings with current Drupal.settings for this // page. - $merged = drupal_array_merge_deep_array($js_scope_settings_array[$scope_settings]['settings']['data']); + $merged = advagg_cleanup_settings_array(drupal_array_merge_deep_array(array_filter($js_scope_settings_array_copy[$scope_settings]['settings']['data'], 'is_array'))); $json_data = advagg_json_encode($merged); if (!empty($json_data)) { // Record that this is being used. $js_settings_used[$scope_settings] = TRUE; - // Replace Drupal.settings json. - $value = substr($value, 0, $start + 30) . $json_data . substr($value, strpos($value, '});', $start) + 1); + + // Replace the drupal settings string. + $value = advagg_replace_drupal_settings_string($value, $json_data); } } $add_to_variables[$scope] = $value; } if ($use_cache) { - $all_used = array_diff(array_keys($js_scope_settings_array), array_keys($js_settings_used)); + $all_used = array_diff(array_keys($js_scope_settings_array_copy), array_keys($js_settings_used)); // Ignore this check if the cache level is less than 5. if (!empty($all_used) && variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL) < 5 && !empty($js_settings_used)) { // Some js settings did not make it into the output. Skip cache. @@ -1547,8 +2223,9 @@ function _advagg_process_html(&$variables) { // If the cache isn't used. if (!$use_cache) { - if (advagg_css_in_js() && !empty($js_cache->data) && !empty($css_cache->data)) { - // Render the css so it will be added to the js array. + if (!empty($js_cache->data) && !empty($css_cache->data) && advagg_css_in_js($css_cache)) { + // Render the css so it will be added to the js array; + // advagg_pre_render_styles() gets called here. $variables['styles'] = drupal_render($full_css); } @@ -1565,12 +2242,12 @@ function _advagg_process_html(&$variables) { $js_cache['scripts'] = ''; if (!empty($js_scope_array)) { // Add JS to the header and footer of the page. - foreach ($js_scope_array as $scope => $scripts_array) { + foreach ($js_scope_array as $scope => &$scripts_array) { // Add js settings. if (!empty($js_scope_settings_array[$scope]['settings'])) { $scripts_array['#items']['settings'] = $js_scope_settings_array[$scope]['settings']; } - // Render js. + // Render js; advagg_pre_render_scripts() gets called here. $scripts = drupal_render($scripts_array); if ($scope === 'header') { @@ -1584,9 +2261,15 @@ function _advagg_process_html(&$variables) { $variables['page_bottom'] .= $scripts; $js_cache['page_bottom'] = $scripts; } + // Above css scripts. + elseif ($scope === 'above_css') { + // Put in this new section. + $variables['above_css'] = $scripts; + $js_cache['above_css'] = $scripts; + } elseif (variable_get('advagg_scripts_scope_anywhere', ADVAGG_SCRIPTS_SCOPE_ANYWHERE)) { // Scripts in other places. - if ( isset($variables[$scope]) + if (isset($variables[$scope]) && is_string($variables[$scope]) && array_key_exists($scope, $GLOBALS['theme_info']->info['regions']) ) { @@ -1594,48 +2277,419 @@ function _advagg_process_html(&$variables) { $variables[$scope] .= $scripts; $js_cache[$scope] = $scripts; } + elseif (array_search($scope, $advagg_script_alt_scope_scripts, TRUE) !== FALSE) { + // Add to the inline html. + $pos_page_top = strpos($variables['page_top'], "<!-- AdvAgg $scope tag -->"); + $pos_page = strpos($variables['page'], "<!-- AdvAgg $scope tag -->"); + $pos_page_bottom = strpos($variables['page_bottom'], "<!-- AdvAgg $scope tag -->"); + if ($pos_page_top !== FALSE) { + $pos_page_top += strlen("<!-- AdvAgg $scope tag -->"); + $variables['page_top'] = substr_replace($variables['page_top'], "\n$scripts", $pos_page_top, 0); + $js_cache[$scope] = $scripts; + } + elseif ($pos_page !== FALSE) { + $pos_page += strlen("<!-- AdvAgg $scope tag -->"); + $variables['page'] = substr_replace($variables['page'], "\n$scripts", $pos_page, 0); + $js_cache[$scope] = $scripts; + } + elseif ($pos_page_bottom !== FALSE) { + $pos_page_bottom += strlen("<!-- AdvAgg $scope tag -->"); + $variables['page_bottom'] = substr_replace($variables['page_bottom'], "\n$scripts", $pos_page_bottom, 0); + $js_cache[$scope] = $scripts; + } + } // Add javascript to scripts if we can't find the region in the theme. - else { + elseif (strpos($scope, ':') === FALSE) { // Add to the bottom of this section. $variables['scripts'] .= $scripts; $js_cache['scripts'] .= $scripts; } } } + unset($scripts_array); + + // Clear drupal settings so cache is smaller. + foreach ($js_cache as &$string) { + $string = advagg_replace_drupal_settings_string($string, '{}'); + } + unset($string); + + // Clear drupal settings and not needed items from render cache. + $js_scope_array = array_intersect_key($js_scope_array, array_flip(element_children($js_scope_array))); + foreach ($js_scope_array as $scope => &$scripts_array) { + // Clear element children. + $scripts_array = array_diff_key($scripts_array, array_flip(element_children($scripts_array))); + if (isset($scripts_array['#children'])) { + unset($scripts_array['#children']); + } + // Clear drupal settings. + if (isset($scripts_array['#items']['settings']['data']) && is_array($scripts_array['#items']['settings']['data'])) { + $scripts_array['#items']['settings']['data'] = array(); + } + // Clear printed keys. + if (isset($scripts_array['#printed'])) { + unset($scripts_array['#printed']); + } + // Clear not used groups. + foreach ($scripts_array['#groups'] as $key => $groups) { + if (!isset($groups['items']['files'])) { + unset($scripts_array['#groups'][$key]); + } + } + } + unset($scripts_array); - if (!empty($js_cache_id) && !empty($js_cache)) { - cache_set($js_cache_id, $js_cache, 'cache_advagg_aggregates', CACHE_TEMPORARY); + 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)) { - cache_set($js_cache_id_no_alter, $js_cache, 'cache_advagg_aggregates', CACHE_TEMPORARY); + 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); } } } + if (!empty($variables['above_css'])) { + $variables['styles'] = $variables['above_css'] . $variables['styles']; + } - // Output debug info. - if (variable_get('advagg_debug', ADVAGG_DEBUG)) { - $debug = $GLOBALS['_advagg']['debug']; - if (module_exists('httprl')) { - $output = ' ' . httprl_pr($debug); - } - else { - $output = '<pre>' . str_replace(array('<', '>'), array('<', '>'), print_r($debug, TRUE)) . '</pre>'; + if (variable_get('advagg_resource_hints_preload', ADVAGG_RESOURCE_HINTS_PRELOAD)) { + foreach ($js_scope_array as $scope => &$scripts_array) { + if ($scope !== 'header' + && $scope !== 'footer' + && $scope !== 'above_css' + && !variable_get('advagg_scripts_scope_anywhere', ADVAGG_SCRIPTS_SCOPE_ANYWHERE) + ) { + continue; + } + + foreach ($scripts_array['#groups'] as $groups) { + if (empty($groups['data']) || $groups['type'] === 'inline') { + continue; + } + advagg_add_preload_header(advagg_convert_abs_to_rel(file_create_url($groups['data'])), 'script'); + } } - watchdog('advagg_debug', $output, array(), WATCHDOG_DEBUG); } -} -/** - * Returns a themed representation of all stylesheets to attach to the page. - * - * It loads the CSS in order, with 'module' first, then 'theme' afterwards. - * This ensures proper cascading of styles so themes can easily override - * module styles through CSS selectors. - * - * Themes may replace module-defined CSS files by adding a stylesheet with the - * same filename. For example, themes/bartik/system-menus.css would replace - * modules/system/system-menus.css. This allows themes to override complete - * CSS files, rather than specific selectors, when necessary. + $head_elements_before = drupal_add_html_head(); + if (variable_get('advagg_resource_hints_dns_prefetch', ADVAGG_RESOURCE_HINTS_DNS_PREFETCH) + || variable_get('advagg_resource_hints_preconnect', ADVAGG_RESOURCE_HINTS_PRECONNECT) + || variable_get('advagg_resource_hints_preload', ADVAGG_RESOURCE_HINTS_PRELOAD) + ) { + // Prefetch css domains. + foreach ($full_css['#items'] as $file) { + advagg_add_resource_hints_array($file); + } + foreach ($full_css['#groups'] as $groups) { + if (isset($groups['items']['files'])) { + foreach ($groups['items']['files'] as $file) { + advagg_add_resource_hints_array($file); + } + } + } + + // Prefetch js domains. + foreach ($js_scope_array as $scope_js) { + foreach ($scope_js['#items'] as $file) { + advagg_add_resource_hints_array($file); + } + if (isset($scope_js['#groups'])) { + foreach ($scope_js['#groups'] as $groups) { + if (isset($groups['items']['files'])) { + foreach ($groups['items']['files'] as $file) { + advagg_add_resource_hints_array($file); + } + } + } + } + } + } + + // Add in preload link headers. + advagg_add_preload_header(); + + // Add in the headers added by advagg. + $head_elements_after = drupal_add_html_head(); + $elements += array_diff_key($head_elements_after, $head_elements_before); + + // Parts of drupal_get_html_head(). + drupal_alter('html_head', $elements); + $head = drupal_render($elements); + if (variable_get('advagg_html_head_in_css_location', ADVAGG_HTML_HEAD_IN_CSS_LOCATION)) { + $variables['styles'] = $head . $variables['styles']; + $variables['head'] = ''; + } + else { + $variables['head'] = $head; + } + + // Remove AdvAgg comments. + if (variable_get('advagg_scripts_scope_anywhere', ADVAGG_SCRIPTS_SCOPE_ANYWHERE) + && !empty($advagg_script_alt_scope_scripts) + && !variable_get('theme_debug', FALSE) + ) { + $variables['page_top'] = preg_replace('/<!-- AdvAgg (.*?) tag -->/', '', $variables['page_top']); + $variables['page'] = preg_replace('/<!-- AdvAgg (.*?) tag -->/', '', $variables['page']); + $variables['page_bottom'] = preg_replace('/<!-- AdvAgg (.*?) tag -->/', '', $variables['page_bottom']); + } + + // Output debug info. + if (variable_get('advagg_debug', ADVAGG_DEBUG)) { + $debug = $GLOBALS['_advagg']['debug']; + if (is_callable('httprl_pr')) { + $output = ' ' . httprl_pr($debug); + } + else { + $output = '<pre>' . str_replace(array('<', '>'), array('<', '>'), print_r($debug, TRUE)) . '</pre>'; + } + watchdog('advagg_debug', $output, array(), WATCHDOG_DEBUG); + } +} + +/** + * Replace inline drupal settings script. + * + * @param string $subject + * Inline js. + * @param string $replace + * JS settings replacement. + * + * @return string + * Returns the subject with the replacement in place if this is a drupal + * settings json blob. + */ +function advagg_replace_drupal_settings_string($subject, $replace) { + $start = strpos($subject, 'jQuery.extend(Drupal.settings,'); + if ($start === FALSE) { + return $subject; + } + + // Find the end of the Drupal.settings. + $script_end = stripos($subject, '</script>', $start); + $settings_substring = substr($subject, $start, $script_end - $start); + $json_end = strripos($settings_substring, '});'); + + // Check if LABjs has added an additional wrapper around Drupal settings. + $script_tag_start = strripos(substr($subject, 0, $start), '<script'); + if (strpos(substr($subject, $script_tag_start, $start), '$L.wait(') !== FALSE) { + // Refine JSON end position. + $_json_end = strripos(substr($settings_substring, 0, $json_end), '});'); + if ($_json_end !== FALSE) { + $json_end = $_json_end; + } + } + + // Replace Drupal.settings json. + $subject = substr($subject, 0, $start + 30) . $replace . substr($subject, $json_end + $start + 1); + return $subject; +} + +/** + * 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(). + * + * @param array $values + * Attributes added via code for the file. + */ +function advagg_add_resource_hints_array(array $values) { + if (variable_get('advagg_resource_hints_dns_prefetch', ADVAGG_RESOURCE_HINTS_DNS_PREFETCH) + || variable_get('advagg_resource_hints_preconnect', ADVAGG_RESOURCE_HINTS_PRECONNECT)) { + if (!empty($values['type']) + && ($values['type'] === 'external' || $values['type'] === 'file') + ) { + // Get external domains. + advagg_add_dns_prefetch($values['data']); + } + if (!empty($values['dns_prefetch'])) { + // Grab domains that will be access when this file is loaded. + if (is_array($values['dns_prefetch'])) { + foreach ($values['dns_prefetch'] as $url) { + advagg_add_dns_prefetch($url); + } + } + else { + advagg_add_dns_prefetch($values['dns_prefetch']); + } + } + } + if (!empty($values['preload']) && variable_get('advagg_resource_hints_preload', ADVAGG_RESOURCE_HINTS_PRELOAD)) { + if (is_array($values['preload'])) { + foreach ($values['preload'] as $url) { + advagg_add_preload_header($url); + } + } + else { + advagg_add_preload_header($values['preload']); + } + } +} + +/** + * Add in the dns-prefetch header for CSS and JS external files. + * + * @param string $url + * The url of the external host. + * + * @return bool + * TRUE if it was added to the head. + */ +function advagg_add_dns_prefetch($url) { + // Keep the order. + $advagg_resource_hints_location = variable_get('advagg_resource_hints_location', ADVAGG_RESOURCE_HINTS_LOCATION); + static $weight = -1001; + if ($advagg_resource_hints_location == 3) { + $weight = -999.9; + } + $weight += 0.0001; + + // Get the host. + $parse = @parse_url($url); + if (empty($parse['host'])) { + // If just the hostname was given, build proper url. + if (strpos($url, '.') && strpos($url, '/') === FALSE) { + $parse['scheme'] = '//'; + $parse['host'] = $url; + // Check for fragment. + $pos = strpos($url, '#'); + if ($pos !== FALSE) { + $parse['fragment'] = substr($url, $pos + 1); + $parse['host'] = substr($url, 0, $pos); + } + // Put it back together and parse again. + $url = advagg_glue_url($parse); + $parse = @parse_url($url); + } + if (empty($parse['host'])) { + return FALSE; + } + } + + // Filter out wrong schemes. + if (!empty($parse['scheme']) + && $parse['scheme'] !== 'http' + && $parse['scheme'] !== 'https' + ) { + return FALSE; + } + + // Filter out local host. + $host = @parse_url($GLOBALS['base_root'], PHP_URL_HOST); + if ($parse['host'] === $host) { + return FALSE; + } + + // Add DNS information for more domains. + if (strpos($parse['host'], 'fonts.googleapis.com') !== FALSE) { + // Add fonts.gstatic.com when fonts.googleapis.com is added. + advagg_add_dns_prefetch('https://fonts.gstatic.com/#crossorigin'); + } + + // Build render array. + if (variable_get('advagg_resource_hints_dns_prefetch', ADVAGG_RESOURCE_HINTS_DNS_PREFETCH)) { + $element = array( + '#type' => 'html_tag', + '#tag' => 'link', + '#attributes' => array( + 'rel' => 'dns-prefetch', + 'href' => '//' . $parse['host'], + ), + '#weight' => $weight, + ); + // Add markup for dns-prefetch to html_head. + drupal_add_html_head($element, 'advagg_resource_hints_dns_prefetch:' . $parse['host']); + } + if (variable_get('advagg_resource_hints_preconnect', ADVAGG_RESOURCE_HINTS_PRECONNECT)) { + // HTTPS use Protocol Relative; HTTP and scheme defined use given scheme. + $href = '//' . $parse['host']; + if (!$GLOBALS['is_https'] && isset($parse['scheme'])) { + $href = "{$parse['scheme']}://{$parse['host']}"; + } + + $element = array( + '#type' => 'html_tag', + '#tag' => 'link', + '#attributes' => array( + 'rel' => 'preconnect', + 'href' => $href, + ), + '#weight' => $weight, + ); + if (!empty($parse['fragment']) && $parse['fragment'] === 'crossorigin') { + $element['#attributes']['crossorigin'] = ''; + } + // Add markup for dns-prefetch to html_head. + drupal_add_html_head($element, 'advagg_resource_hints_preconnect:' . $parse['host']); + } + + // Build render array. Goes after charset tag. + if (!empty($parse['fragment']) && $parse['fragment'] === 'prefetch') { + // Hacky way to open up a connection to the remote host. + $element = array( + '#type' => 'html_tag', + '#tag' => 'link', + '#attributes' => array( + 'rel' => 'prefetch', + 'href' => '//' . $parse['host'] . '/robots.txt', + ), + '#weight' => $weight, + ); + drupal_add_html_head($element, 'advagg_prefetch:' . $parse['host']); + } + return TRUE; +} + +/** + * Returns a themed representation of all stylesheets to attach to the page. + * + * It loads the CSS in order, with 'module' first, then 'theme' afterwards. + * This ensures proper cascading of styles so themes can easily override + * module styles through CSS selectors. + * + * Themes may replace module-defined CSS files by adding a stylesheet with the + * same filename. For example, themes/bartik/system-menus.css would replace + * modules/system/system-menus.css. This allows themes to override complete + * CSS files, rather than specific selectors, when necessary. * * If the original CSS file is being overridden by a theme, the theme is * responsible for supplying an accompanying RTL CSS file to replace the @@ -1661,14 +2715,13 @@ function advagg_get_css(array $css = array(), $skip_alter = FALSE) { // Allow modules and themes to alter the CSS items. if (!$skip_alter) { + advagg_add_default_dns_lookups($css, 'css'); + // Call hook_css_alter(). drupal_alter('css', $css); - if (variable_get('advagg_run_alter_after_theme', ADVAGG_RUN_ALTER_AFTER_THEME)) { - // Call these advagg css_alter hooks after the theme's hooks were called. - advagg_css_alter($css); - if (module_exists('advagg_mod')) { - advagg_mod_css_alter($css); - } - } + // Call hook_css_post_alter(). + drupal_alter('css_post', $css); + // Call these advagg functions after the hook_css_alter was called. + advagg_fix_type($css, 'css'); } // Sort CSS items, so that they appear in the correct order. @@ -1700,6 +2753,9 @@ function advagg_get_css(array $css = array(), $skip_alter = FALSE) { } } + // Remove empty files. + advagg_remove_empty_files($css); + // Render the HTML needed to load the CSS. $styles = array( '#type' => 'styles', @@ -1745,8 +2801,8 @@ 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 & scope is header, - if ( empty($javascript) + // only the settings array is used and scope is header. + if (empty($javascript) || (isset($javascript['settings']) && count($javascript) == 1) ) { return array(); @@ -1754,16 +2810,44 @@ function advagg_get_full_js(array $javascript = array(), $skip_alter = FALSE) { // Allow modules to alter the JavaScript. if (!$skip_alter) { + advagg_add_default_dns_lookups($javascript, 'js'); + if (is_callable('advagg_mod_js_pre_alter')) { + advagg_mod_js_pre_alter($javascript); + } // Call hook_js_alter(). drupal_alter('js', $javascript); - if (variable_get('advagg_run_alter_after_theme', ADVAGG_RUN_ALTER_AFTER_THEME)) { - // Call these advagg js_alter hooks after the theme's hooks were called. - advagg_js_alter($javascript); - if (module_exists('advagg_mod')) { - advagg_mod_js_alter($javascript); + // Call hook_js_post_alter(). + drupal_alter('js_post', $javascript); + // Call these advagg functions after the hook_js_alter was called. + advagg_fix_type($javascript, 'js'); + } + elseif (is_callable('advagg_mod_js_move_to_footer')) { + if (variable_get('advagg_mod_js_footer', ADVAGG_MOD_JS_FOOTER) == 3) { + 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); } } } + + // Remove empty files. + advagg_remove_empty_files($javascript); + return $javascript; } @@ -1800,30 +2884,23 @@ function advagg_get_full_js(array $javascript = array(), $skip_alter = FALSE) { * @see drupal_js_defaults() */ function advagg_get_js($scope = 'header', array $javascript = array(), $ajax = FALSE) { - $javascript_settings_data = &drupal_static(__FUNCTION__, array()); + // Keep track of js added for ajaxPageState. + $page_state = &drupal_static(__FUNCTION__, array()); // Add in javascript if none was passed in. if (empty($javascript) && !$ajax) { $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(); } - // Set the js_in_footer variable. - $js_in_footer = FALSE; - if ( module_exists('advagg_mod') - && variable_get('advagg_mod_js_footer', ADVAGG_MOD_JS_FOOTER) == 2 - ) { - $js_in_footer = TRUE; - } - // Filter out elements of the given scope. $items = array(); foreach ($javascript as $key => $item) { - if ($item['scope'] == $scope) { + if (!empty($item['scope']) && $item['scope'] === $scope) { $items[$key] = $item; } } @@ -1836,36 +2913,24 @@ function advagg_get_js($scope = 'header', array $javascript = array(), $ajax = F // without that group. We do not use the $key => $item type of iteration, // because PHP uses an internal array pointer for that, and we're modifying // the array order inside the loop. - if ($scope === 'footer' && $js_in_footer) { - // Get all settings from the items array. - $settings_js = array(); - foreach (array_keys($items) as $key) { - if ($items[$key]['type'] === 'setting') { - if (isset($settings_js[$key])) { - $settings_js[$key] += $items[$key]; - } - else { - $settings_js[$key] = $items[$key]; - } - unset($items[$key]); - } - } - - // Add settings array back in right before the footer shift happened. - if (!empty($settings_js)) { - $counter = 0; - foreach ($items as $key => $item) { - // Move $settings_js to the bottom of the js that was added to the - // header, but has now been moved to the footer via advagg_mod. - if ($item['group'] > 9000) { - advagg_array_splice_assoc($items, $counter, 0, $settings_js); - unset($settings_js); - break; - } - ++$counter; + if ($scope === 'footer' && !empty($items['settings'])) { + // Remove settings array from items. + $settings_js['settings'] = $items['settings']; + unset($items['settings']); + + // Move $settings_js to the bottom of the js that was added to the + // header, but has now been moved to the footer via advagg_mod. + $counter = 0; + foreach ($items as $key => $item) { + if ($item['group'] > 9000) { + advagg_array_splice_assoc($items, $counter, 0, $settings_js); + unset($settings_js); + break; } + ++$counter; } - if (!empty($settings_js)) { + // Nothing in the footer, add settings to the bottom of the array. + if (isset($settings_js)) { $items = array_merge($items, $settings_js); } } @@ -1881,21 +2946,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']); - drupal_add_js($setting, 'setting'); - - // Move 'settings' javascript to the header region. - if (!empty($javascript_settings_data) && (($scope === 'header' && !$js_in_footer) || ($scope === 'footer' && $js_in_footer))) { - foreach ($javascript_settings_data as $js_data) { - $items['settings']['data'][] = $js_data; - } - $javascript_settings_data = array(); - } - else { - $javascript_settings_data[] = $setting; - } + $page_state = array_merge($page_state, 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 @@ -1904,8 +2955,12 @@ function advagg_get_js($scope = 'header', array $javascript = array(), $ajax = F // stripped of settings, potentially in order to override how settings get // output, so in this case, do not add the setting to this output. // Also output the settings if we have pushed all javascript to the footer. - if (isset($items['settings']) && (($scope === 'header' && !$js_in_footer) || ($scope === 'footer' && $js_in_footer))) { - $items['settings']['data'][] = $setting; + if (isset($items['settings'])) { + $items['settings']['data'][] = array( + 'ajaxPageState' => array( + 'js' => $page_state, + ), + ); } // Do not include jQuery.extend(Drupal.settings) if the output is ajax. @@ -1938,7 +2993,7 @@ function advagg_get_js($scope = 'header', array $javascript = array(), $ajax = F '#items' => $items, ); - // Aurora & Omega themes uses alter without checking previous value. + // Aurora and Omega themes uses alter without checking previous value. if (variable_get('advagg_enforce_scripts_callback', TRUE)) { // Get the element_info for scripts. $scripts = element_info('scripts'); @@ -1946,9 +3001,18 @@ function advagg_get_js($scope = 'header', array $javascript = array(), $ajax = F // Directly alter the static. $element_info = &drupal_static('element_info'); advagg_element_info_alter($element_info); + if (function_exists('advagg_mod_element_info_alter')) { + advagg_mod_element_info_alter($element_info); + } } } + // Remove ajaxPageState CSS/JS from Drupal.settings if ajax.js is not used. + if (function_exists('advagg_mod_js_no_ajaxpagestate')) { + if (variable_get('advagg_mod_js_no_ajaxpagestate', ADVAGG_MOD_JS_NO_AJAXPAGESTATE)) { + advagg_mod_js_no_ajaxpagestate($elements); + } + } return $elements; } @@ -1995,18 +3059,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; } } @@ -2027,7 +3093,8 @@ function advagg_get_js_scopes(array $javascript) { // Filter out elements of the given scope. $scopes = array(); - foreach ($javascript as $item) { + $js_settings_in_footer = FALSE; + foreach ($javascript as $name => $item) { // Skip if the scope is not set. if (!is_array($item) || empty($item['scope'])) { continue; @@ -2035,6 +3102,9 @@ function advagg_get_js_scopes(array $javascript) { if (!isset($scopes[$item['scope']])) { $scopes[$item['scope']] = TRUE; } + if ($name === 'settings' && $item['scope'] === 'footer') { + $js_settings_in_footer = TRUE; + } } // Default to header if nothing found. @@ -2050,11 +3120,9 @@ function advagg_get_js_scopes(array $javascript) { } // Process footer last if everything has been moved to the footer. - // @ignore sniffer_whitespace_openbracketspacing_openingwhitespace - if ( isset($scopes['footer']) + if (isset($scopes['footer']) && count($scopes) > 1 - && module_exists('advagg_mod') - && variable_get('advagg_mod_js_footer', ADVAGG_MOD_JS_FOOTER) == 2 + && $js_settings_in_footer ) { $temp = $scopes['footer']; unset($scopes['footer']); @@ -2084,8 +3152,7 @@ function advagg_merge_plans(array $css_js_groups, array $plans) { // Remove files from the old css/js array. $file_removed = FALSE; foreach ($css_js_groups[$key]['items'] as $k => $values) { - // @ignore sniffer_whitespace_openbracketspacing_openingwhitespace - if ( is_array($values) + if (is_array($values) && array_key_exists('data', $values) && is_array($plan['items']['files']) && is_string($values['data']) @@ -2116,7 +3183,7 @@ function advagg_merge_plans(array $css_js_groups, array $plans) { $step = 0; do { ++$step; - $insert_key = '' . floatval($key) . '.' . $step; + $insert_key = '' . floatval($key) . '.' . sprintf('%03d', $step); } while (array_key_exists($insert_key, $css_js_groups)); $css_js_groups[(string) $insert_key] = $plan; $plan_added = TRUE; @@ -2132,8 +3199,7 @@ function advagg_merge_plans(array $css_js_groups, array $plans) { if (!$plan_added) { foreach ($css_js_groups as $key => $group) { - // @ignore sniffer_whitespace_openbracketspacing_openingwhitespace - if ( empty($group['items']['aggregate_filenames_hash']) + if (empty($group['items']['aggregate_filenames_hash']) || $group['items']['aggregate_filenames_hash'] != $plan['items']['aggregate_filenames_hash'] || empty($group['items']['aggregate_contents_hash']) || $group['items']['aggregate_contents_hash'] != $plan['items']['aggregate_contents_hash'] @@ -2143,7 +3209,7 @@ function advagg_merge_plans(array $css_js_groups, array $plans) { // Insert a unique key. do { - $key = '' . floatval($key) + 0.01; + $key = '' . (floatval($key) + 0.01); } while (array_key_exists((string) $key, $css_js_groups) || array_key_exists((string) $key, $used_keys)); $used_keys[(string) $key] = TRUE; $css_js_groups[(string) $key] = $plan; @@ -2160,7 +3226,6 @@ function advagg_merge_plans(array $css_js_groups, array $plans) { return $css_js_groups; } -// Helper functions. /** * Function used to see if aggregation is enabled. * @@ -2174,6 +3239,11 @@ function advagg_enabled() { return variable_get('advagg_enabled', ADVAGG_ENABLED); } + // Set base_path if not set. + if (empty($GLOBALS['base_path'])) { + $GLOBALS['base_path'] = rtrim(dirname($_SERVER['SCRIPT_NAME']), '\/') . '/'; + } + $init = TRUE; // Disable AdvAgg if module needs to be upgraded from 1.x to 2.x. if (variable_get('advagg_needs_update', ADVAGG_NEEDS_UPDATE)) { @@ -2188,12 +3258,60 @@ function advagg_enabled() { } } else { + // Get values and fill in defaults if needed. + $config_path = advagg_admin_config_root_path(); + $current_path = current_path(); + $arg = arg(); + $arg += array(1 => '', 2 => '', 3 => '', 4 => '', 5 => ''); + $admin_theme = variable_get('admin_theme'); + + // List of all the pages which will not have Advanced Aggregator enabled. + $list_of_pages = variable_get('advagg_disable_on_listed_pages'); + + $pages = trim(drupal_strtolower($list_of_pages)); + // Convert the Drupal path to lowercase. + $path = drupal_strtolower(drupal_get_path_alias(current_path())); + + // Compare the lowercase internal and lowercase path alias (if any). + $page_match = drupal_match_path($path, $pages); + if ($page_match) { + $GLOBALS['conf']['advagg_enabled'] = FALSE; + $GLOBALS['conf']['preprocess_css'] = FALSE; + $GLOBALS['conf']['preprocess_js'] = FALSE; + } + + // Disable advagg if on admin page and configured to do so. + // AND theme is admin theme + // AND NOT /admin/reports/status + // AND NOT /admin/config/development/performance/. + // AND NOT /admin/appearance/settings/*. + // AND NOT /admin/config/development/performance/advagg/*. + if (variable_get('advagg_disable_on_admin', ADVAGG_DISABLE_ON_ADMIN) + && $GLOBALS['theme'] === $admin_theme + && path_is_admin($current_path) + && !($arg[1] === 'reports' && $arg[2] === 'status') + && !($arg[2] === 'development' && $arg[3] === 'performance' && empty($arg[4])) + && !($arg[1] === 'appearance' && $arg[2] === 'settings' && !empty($arg[3])) + && stripos($current_path, $config_path . '/advagg') !== 0 + ) { + $GLOBALS['conf']['advagg_enabled'] = FALSE; + $GLOBALS['conf']['preprocess_css'] = FALSE; + $GLOBALS['conf']['preprocess_js'] = FALSE; + } + + // Check if the advagg cookie is set. + $cookie_name = 'AdvAggDisabled'; + $bypass_cookie = FALSE; + $key = drupal_hmac_base64('advagg_cookie', drupal_get_private_key() . drupal_get_hash_salt() . variable_get('cron_key', 'drupal')); + if (!empty($_COOKIE[$cookie_name]) && $_COOKIE[$cookie_name] == $key) { + $bypass_cookie = TRUE; + } + // Allow for AdvAgg to be enabled per request. - // @ignore sniffer_whitespace_openbracketspacing_openingwhitespace - if ( isset($_GET['advagg']) + if (isset($_GET['advagg']) && $_GET['advagg'] == 1 && !defined('MAINTENANCE_MODE') - && user_access('bypass advanced aggregation') + && (user_access('bypass advanced aggregation') || $bypass_cookie) ) { $GLOBALS['conf']['advagg_enabled'] = TRUE; $GLOBALS['conf']['preprocess_css'] = TRUE; @@ -2208,12 +3326,11 @@ function advagg_enabled() { if (variable_get('advagg_enabled', ADVAGG_ENABLED)) { // Do not use AdvAgg or preprocessing functions if the disable cookie is // set. - $cookie_name = 'AdvAggDisabled'; - $key = drupal_hash_base64(drupal_get_private_key()); - if (!empty($_COOKIE[$cookie_name]) && $_COOKIE[$cookie_name] == $key) { + if ($bypass_cookie && !isset($_GET['advagg'])) { $GLOBALS['conf']['advagg_enabled'] = FALSE; $GLOBALS['conf']['preprocess_css'] = FALSE; $GLOBALS['conf']['preprocess_js'] = FALSE; + $bypass_cookie = TRUE; // Let the user know that the AdvAgg bypass cookie is currently set. static $msg_set; @@ -2221,7 +3338,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 <a href="@advagg_operations">AdvAgg Operations</a> page and clicking the <em>Toggle the "aggregation bypass cookie" for this browser</em> 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 { @@ -2233,40 +3350,36 @@ function advagg_enabled() { } } // Disable advagg if requested. - // @ignore sniffer_whitespace_openbracketspacing_openingwhitespace - if ( isset($_GET['advagg']) + if (isset($_GET['advagg']) && $_GET['advagg'] == -1 - && user_access('bypass advanced aggregation') + && (user_access('bypass advanced aggregation') || $bypass_cookie) ) { $GLOBALS['conf']['advagg_enabled'] = FALSE; $GLOBALS['conf']['preprocess_css'] = FALSE; $GLOBALS['conf']['preprocess_js'] = FALSE; } // Disable core preprocessing if requested. - // @ignore sniffer_whitespace_openbracketspacing_openingwhitespace - if ( isset($_GET['advagg-core']) + if (isset($_GET['advagg-core']) && $_GET['advagg-core'] == 0 - && user_access('bypass advanced aggregation') + && (user_access('bypass advanced aggregation') || $bypass_cookie) ) { $GLOBALS['conf']['preprocess_css'] = FALSE; $GLOBALS['conf']['preprocess_js'] = FALSE; } // Enable core preprocessing if requested. - // @ignore sniffer_whitespace_openbracketspacing_openingwhitespace - if ( isset($_GET['advagg-core']) + if (isset($_GET['advagg-core']) && $_GET['advagg-core'] == 1 - && user_access('bypass advanced aggregation') + && (user_access('bypass advanced aggregation') || $bypass_cookie) ) { $GLOBALS['conf']['preprocess_css'] = TRUE; $GLOBALS['conf']['preprocess_js'] = TRUE; } // Enable debugging if requested. - // @ignore sniffer_whitespace_openbracketspacing_openingwhitespace - if ( isset($_GET['advagg-debug']) - && $_GET['advagg-debug'] == 1 - && user_access('bypass advanced aggregation') + if (isset($_GET['advagg-debug']) + && (user_access('bypass advanced aggregation') || $bypass_cookie) ) { - $GLOBALS['conf']['advagg_debug'] = TRUE; + // Cast to an int. + $GLOBALS['conf']['advagg_debug'] = (int) $_GET['advagg-debug']; } } } @@ -2292,14 +3405,17 @@ function advagg_admin_config_root_path() { */ function advagg_current_hooks_hash_array() { $aggregate_settings = &drupal_static(__FUNCTION__); - if (isset($aggregate_settings)) { + if (!empty($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( 'variables' => array( 'advagg_gzip' => variable_get('advagg_gzip', ADVAGG_GZIP), + 'advagg_brotli' => variable_get('advagg_brotli', ADVAGG_BROTLI), + 'advagg_no_zopfli' => variable_get('advagg_no_zopfli', ADVAGG_NO_ZOPFLI), 'is_https' => $GLOBALS['is_https'], 'advagg_global_counter' => advagg_get_global_counter(), 'base_path' => $GLOBALS['base_path'], @@ -2309,7 +3425,10 @@ function advagg_current_hooks_hash_array() { 'advagg_devel' => variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL) < 0 ? TRUE : FALSE, '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_css_absolute_path' => variable_get('advagg_css_absolute_path', ADVAGG_CSS_ABSOLUTE_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), ); @@ -2344,7 +3463,7 @@ function advagg_current_hooks_hash_array() { } /** - * Get the hash of all hooks & settings that affect aggregated files contents. + * Get the hash of all hooks and settings that affect aggregated files contents. * * @return string * hash value. @@ -2352,12 +3471,13 @@ function advagg_current_hooks_hash_array() { function advagg_get_current_hooks_hash() { $current_hash = &drupal_static(__FUNCTION__); - if (!isset($current_hash)) { + if (empty($current_hash)) { // Get all advagg hooks and variables in use. $aggregate_settings = advagg_current_hooks_hash_array(); // Generate the hash. - $current_hash = drupal_hash_base64(serialize($aggregate_settings)); + $serialize_function = variable_get('advagg_serialize', ADVAGG_SERIALIZE); + $current_hash = drupal_hash_base64($serialize_function($aggregate_settings)); // Save into variables for verification purposes later on if not found. $settings = advagg_get_hash_settings($current_hash); @@ -2373,13 +3493,18 @@ 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 */ -function advagg_set_hash_settings($hash, $settings) { +function advagg_set_hash_settings($hash, array $settings = array()) { return db_merge('advagg_aggregates_hashes') ->key(array('hash' => $hash)) - ->insertFields(array( + ->fields(array( 'hash' => $hash, 'settings' => serialize($settings), )) @@ -2399,10 +3524,12 @@ function advagg_set_hash_settings($hash, $settings) { function advagg_hooks_implemented($all = TRUE) { // Get hooks in use. $hooks = array( + 'advagg_get_css_file_contents_pre_alter' => array(), 'advagg_get_css_file_contents_alter' => array(), 'advagg_get_css_aggregate_contents_alter' => array(), 'advagg_get_js_file_contents_alter' => array(), 'advagg_get_js_aggregate_contents_alter' => array(), + 'advagg_save_aggregate_pre_alter' => array(), 'advagg_save_aggregate_alter' => array(), 'advagg_current_hooks_hash_array_alter' => array(), 'advagg_get_root_files_dir_alter' => array(), @@ -2411,6 +3538,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(), @@ -2420,6 +3548,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(), ); @@ -2428,7 +3557,8 @@ function advagg_hooks_implemented($all = TRUE) { drupal_alter('advagg_hooks_implemented', $hooks, $all); // Cache module_implements as this will load up .inc files. - $cid = 'advagg_hooks_implemented:' . (int) $all . ':' . drupal_hash_base64(serialize($hooks)); + $serialize_function = variable_get('advagg_serialize', ADVAGG_SERIALIZE); + $cid = 'advagg_hooks_implemented:' . (int) $all . ':' . drupal_hash_base64($serialize_function($hooks)); $cache = cache_get($cid, 'cache_bootstrap'); if (!empty($cache->data)) { $hooks = $cache->data; @@ -2473,9 +3603,8 @@ function advagg_get_hash_settings($hash) { return !empty($settings) ? unserialize($settings) : array(); } -// @ignore production_code:20 /** - * Get the CSS & JS path for advagg. + * Get the CSS and JS path for advagg. * * @param bool $reset * Set to TRUE to reset the static variables. @@ -2502,15 +3631,33 @@ 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); // Set the URI of the directory. - $css_paths[1] = advagg_get_relative_path($css_paths[0]); - $js_paths[1] = advagg_get_relative_path($js_paths[0]); + $css_paths[1] = advagg_get_relative_path($css_paths[0], 'css'); + $js_paths[1] = advagg_get_relative_path($js_paths[0], 'js'); + + // If the css or js got a path, use it for the other missing one. + if (empty($css_paths[1]) && !empty($js_paths[1])) { + $css_paths[1] = str_replace('/advagg_js', '/advagg_css', $js_paths[1]); + } + elseif (empty($js_paths[1]) && !empty($css_paths[1])) { + $js_paths[1] = str_replace('/advagg_css', '/advagg_js', $css_paths[1]); + } + + // Fix if empty. + if (empty($css_paths[1])) { + $css_paths[1] = $css_paths[0]; + } + if (empty($js_paths[1])) { + $js_paths[1] = $js_paths[0]; + } // Allow other modules to alter css and js paths. // Call hook_advagg_get_root_files_dir_alter() @@ -2525,22 +3672,33 @@ function advagg_get_root_files_dir($reset = FALSE) { * * @param string $uri * The uri for the stream wrapper. + * @param string $type + * (Optional) String: css or js. * * @return string * The relative path of the uri. * * @see https://www.drupal.org/node/837794#comment-9124435 */ -function advagg_get_relative_path($uri) { +function advagg_get_relative_path($uri, $type = '') { $wrapper = file_stream_wrapper_get_instance_by_uri($uri); if ($wrapper instanceof DrupalLocalStreamWrapper) { $relative_path = $wrapper->getDirectoryPath() . '/' . file_uri_target($uri); } else { $relative_path = parse_url(file_create_url($uri), PHP_URL_PATH); + if (empty($relative_path) && !empty($uri)) { + $filename = advagg_generate_advagg_filename_from_db($type); + $relative_path = parse_url(file_create_url("{$uri}/{$filename}"), PHP_URL_PATH); + $end = strpos($relative_path, "/{$filename}"); + if ($end !== FALSE) { + $relative_path = substr($relative_path, 0, $end); + } + } if (substr($relative_path, 0, strlen($GLOBALS['base_path'])) == $GLOBALS['base_path']) { $relative_path = substr($relative_path, strlen($GLOBALS['base_path'])); } + $relative_path = ltrim($relative_path, '/'); } return $relative_path; } @@ -2563,7 +3721,7 @@ function advagg_build_aggregates(array $filenames, $type) { } if (empty($type)) { $filename = reset($filenames); - $type = pathinfo($filename, PATHINFO_EXTENSION); + $type = strtolower(pathinfo($filename, PATHINFO_EXTENSION)); } // Call the file generation function directly. @@ -2584,7 +3742,10 @@ function advagg_build_aggregates(array $filenames, $type) { // Only create the file if we have a lock. $lock_name = 'advagg_' . $filename; - if (lock_acquire($lock_name, 10)) { + if (variable_get('advagg_no_locks', ADVAGG_NO_LOCKS)) { + $return[$filename] = advagg_missing_create_file($filename); + } + elseif (lock_acquire($lock_name, 10)) { $return[$filename] = advagg_missing_create_file($filename); lock_release($lock_name); } @@ -2624,6 +3785,7 @@ function advagg_build_ajax_js_css() { } else { $function = 'drupal_add_' . $type; + // Get the current css/js needed for this page. $items[$type] = $function(); // Call hook_js_alter() OR hook_css_alter(). drupal_alter($type, $items[$type]); @@ -2648,10 +3810,12 @@ function advagg_build_ajax_js_css() { // HTML in the page. We pass TRUE as the $skip_alter argument to prevent the // data from being altered again, as we already altered it above. Settings are // handled separately, afterwards. - if (isset($items['js']['settings'])) { - $settings = $items['js']['settings']; + $scripts = drupal_add_js(); + if (isset($scripts['settings'])) { + $settings = $scripts['settings']; unset($items['js']['settings']); } + $styles = drupal_get_css($items['css'], TRUE); $scripts_footer = drupal_get_js('footer', $items['js'], TRUE); $scripts_header = drupal_get_js('header', $items['js'], TRUE); @@ -2683,7 +3847,7 @@ function advagg_file_aggregation_enabled($type) { } /** - * Update atime advagg_aggregates_versions table & cache_advagg_info cache bin. + * Update atime inside advagg_aggregates_versions and cache_advagg_info. * * @param array $files * List of files in the aggregate as well as the aggregate name. @@ -2710,7 +3874,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]); @@ -2727,8 +3891,8 @@ function advagg_multi_update_atime(array $files) { ->key(array( 'aggregate_filenames_hash' => $record['aggregate_filenames_hash'], 'aggregate_contents_hash' => $record['aggregate_contents_hash'], - )) - ->updateFields(array('atime' => $record['atime'])) + )) + ->fields(array('atime' => $record['atime'])) ->execute(); if (!$write_done && $result) { $write_done = TRUE; @@ -2771,8 +3935,83 @@ function advagg_admin_flush_cache() { advagg_admin_flush_cache_button(); } +/** + * Returns HTML for a generic HTML tag with attributes. + * + * @param array $variables + * An associative array containing: + * - element: An associative array describing the tag: + * - #tag: The tag name to output. Typical tags added to the HTML HEAD: + * - meta: To provide meta information, such as a page refresh. + * - link: To refer to stylesheets and other contextual information. + * - script: To load JavaScript. + * - #attributes: (optional) An array of HTML attributes to apply to the + * tag. + * - #value: (optional) A string containing tag content, such as inline + * CSS. + * - #value_prefix: (optional) A string to prepend to #value, e.g. a CDATA + * wrapper prefix. + * - #value_suffix: (optional) A string to append to #value, e.g. a CDATA + * wrapper suffix. + */ +function theme_html_script_tag(array $variables) { + $element = $variables['element']; + $attributes = ''; + $onload = ''; + $onerror = ''; + if (isset($element['#attributes'])) { + // On Load. + if (!empty($element['#attributes']['onload'])) { + $onload = $element['#attributes']['onload']; + unset($element['#attributes']['onload']); + } + // On Error. + if (!empty($element['#attributes']['onerror'])) { + $onerror = $element['#attributes']['onerror']; + unset($element['#attributes']['onerror']); + } + $attributes = !empty($element['#attributes']) ? drupal_attributes($element['#attributes']) : ''; + if (!empty($onload)) { + $attributes .= ' onload="' . advagg_jsspecialchars($onload) . '"'; + } + if (!empty($onerror)) { + $attributes .= ' onerror="' . advagg_jsspecialchars($onerror) . '"'; + } + } + + if (!isset($element['#value'])) { + return '<' . $element['#tag'] . $attributes . " />\n"; + } + else { + $output = '<' . $element['#tag'] . $attributes . '>'; + if (isset($element['#value_prefix'])) { + $output .= $element['#value_prefix']; + } + $output .= $element['#value']; + if (isset($element['#value_suffix'])) { + $output .= $element['#value_suffix']; + } + $output .= '</' . $element['#tag'] . ">\n"; + return $output; + } +} + +/** + * Replace quotes with the html version of it. + * + * @param string $string + * Input string. Convert quotes to html chars. + * + * @return string + * Transformed string. + */ +function advagg_jsspecialchars($string = '') { + $string = str_replace('"', '"', $string); + $string = str_replace("'", ''', $string); + + return $string; +} -// Exact Copies of core functions from patches. /** * Callback for pre_render to add elements needed for JavaScript to be rendered. * @@ -2798,11 +4037,18 @@ function advagg_admin_flush_cache() { * @see drupal_get_js() */ function advagg_pre_render_scripts(array $elements) { + // Don't run it twice. + if (!empty($elements['#groups'])) { + return $elements; + } + // Group and aggregate the items. if (isset($elements['#group_callback'])) { + // Call advagg_group_js(). $elements['#groups'] = $elements['#group_callback']($elements['#items']); } if (isset($elements['#aggregate_callback'])) { + // Call _advagg_aggregate_js(). $elements['#aggregate_callback']($elements['#groups']); } @@ -2826,13 +4072,17 @@ function advagg_pre_render_scripts(array $elements) { // Defaults for each SCRIPT element. $element_defaults = array( - '#type' => 'html_tag', + '#type' => 'html_script_tag', '#tag' => 'script', '#value' => '', '#attributes' => array( 'type' => 'text/javascript', ), ); + $hooks = theme_get_registry(FALSE); + if (empty($hooks['html_script_tag'])) { + $element_defaults['#type'] = 'html_tag'; + } // Loop through each group. foreach ($elements['#groups'] as $group) { @@ -2861,6 +4111,9 @@ function advagg_pre_render_scripts(array $elements) { } $element['#attributes']['onerror'] .= $group['onerror']; } + if (!empty($group['attributes'])) { + $element['#attributes'] += $group['attributes']; + } $elements[] = $element; } // For non-file types, and non-aggregated files, add a script element per @@ -2892,6 +4145,9 @@ function advagg_pre_render_scripts(array $elements) { } $element['#attributes']['onerror'] .= $item['onerror']; } + if (!empty($group['attributes'])) { + $element['#attributes'] += $group['attributes']; + } $element['#browsers'] = isset($item['browsers']) ? $item['browsers'] : array(); // Crude type detection if needed. @@ -2912,7 +4168,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(array_filter($item['data'], 'is_array'))); $json_data = advagg_json_encode($data); $element['#value_prefix'] = $embed_prefix; $element['#value'] = 'jQuery.extend(Drupal.settings, ' . $json_data . ");"; @@ -2920,16 +4176,25 @@ function advagg_pre_render_scripts(array $elements) { break; case 'inline': + // If a BOM is found, convert the string to UTF-8. + $encoding = advagg_get_encoding_from_bom($item['data']); + if (!empty($encoding)) { + $item['data'] = advagg_convert_to_utf8($item['data'], $encoding); + } + $element['#value_prefix'] = $embed_prefix; $element['#value'] = $item['data']; $element['#value_suffix'] = $embed_suffix; break; case 'file': - $query_string = empty($item['version']) ? $default_query_string : $js_version_string . $item['version']; $query_string_separator = (strpos($item['data'], '?') !== FALSE) ? '&' : '?'; + $cache_validator = REQUEST_TIME; + if (!empty($item['cache'])) { + $cache_validator = empty($item['version']) ? $default_query_string : $js_version_string . $item['version'];; + } - $element['#attributes']['src'] = advagg_file_create_url($item['data']) . $query_string_separator . ($item['cache'] ? $query_string : REQUEST_TIME); + $element['#attributes']['src'] = advagg_file_create_url($item['data']) . $query_string_separator . $cache_validator; break; case 'external': @@ -3024,11 +4289,23 @@ function advagg_get_css_prefix_suffix() { * @see drupal_get_css() */ function advagg_pre_render_styles(array $elements) { + // Skip if advagg is disabled. + if (!advagg_enabled()) { + return drupal_pre_render_styles($elements); + } + + // Don't run it twice. + if (!empty($elements['#groups'])) { + return $elements; + } + // Group and aggregate the items. if (isset($elements['#group_callback'])) { + // Call drupal_group_css(). $elements['#groups'] = $elements['#group_callback']($elements['#items']); } if (isset($elements['#aggregate_callback'])) { + // Call _advagg_aggregate_css(). $elements['#aggregate_callback']($elements['#groups']); } @@ -3083,6 +4360,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 @@ -3104,7 +4384,7 @@ function advagg_pre_render_styles(array $elements) { if (file_exists($item['data'])) { // The dummy query string needs to be added to the URL to control // browser-caching. IE7 does not support a media type on the - // @import statement, so we instead specify the media for the + // "@import" statement, so we instead specify the media for the // group on the STYLE tag. $import[] = '@import url("' . check_plain(advagg_file_create_url($item['data']) . '?' . $query_string) . '");'; } @@ -3122,6 +4402,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; } } @@ -3144,6 +4427,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; } } @@ -3160,6 +4446,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 { @@ -3170,6 +4459,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; } } @@ -3188,6 +4480,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; @@ -3238,6 +4533,9 @@ function advagg_group_js(array $javascript) { $current_group_keys = NULL; $index = -1; foreach ($javascript as $key => $item) { + if (empty($item)) { + continue; + } // The browsers for which the JavaScript item needs to be loaded is part of // the information that determines when a new group is needed, but the order // of keys in the array doesn't matter, and we don't want a new group if all @@ -3249,8 +4547,32 @@ function advagg_group_js(array $javascript) { $item['browsers'] = array(); } - if (empty($item['type']) && $key === 'settings') { - $item['type'] = 'setting'; + // Fix missing types. + if (empty($item['type'])) { + // Setting is easy. + if ($key === 'settings') { + $item['type'] = 'setting'; + } + // Check for the schema or // for protocol-relative. + elseif ((stripos($item['data'], 'http://') === 0 + || stripos($item['data'], 'https://') === 0 + || (strpos($item['data'], '//') === 0 && strpos($item['data'], '///') === FALSE)) + ) { + $item['type'] = 'external'; + } + // See if the data contains a semicolon, new line, $, or quotes. + elseif (strpos($item['data'], ';') !== FALSE + || strpos($item['data'], "\n") + || strpos($item['data'], "$") + || strpos($item['data'], "'") + || strpos($item['data'], '"') + ) { + $item['type'] = 'inline'; + } + // Ends in .js. + elseif (stripos(strrev($item['data']), strrev('.js')) === 0) { + $item['type'] = 'file'; + } } switch ($item['type']) { @@ -3259,7 +4581,7 @@ function advagg_group_js(array $javascript) { // Help ensure maximum reuse of aggregate files by only grouping // together items that share the same 'group' value and 'every_page' // flag. See drupal_add_js() for details about that. - $group_keys = $item['preprocess'] ? array( + $group_keys = !empty($item['preprocess']) ? array( $item['type'], $item['group'], $item['every_page'], @@ -3277,6 +4599,11 @@ function advagg_group_js(array $javascript) { default: // Define this here so we don't get undefined alerts down below. $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); break; } @@ -3327,7 +4654,24 @@ function advagg_drupal_sort_css_js_stable(array &$items) { // the aggregate file generated for all of the common files to be reused // across a site visit without being cut by a page using a less common file. $nested = array(); - foreach ($items as $key => $item) { + foreach ($items as $key => &$item) { + // If weight is not set, make it 0. + if (!isset($item['weight'])) { + $item['weight'] = 0; + } + // If every_page is not set, make it FALSE. + if (!isset($item['every_page'])) { + $item['every_page'] = FALSE; + } + // If group is not set, make it CSS_DEFAULT/JS_DEFAULT (0). + if (!isset($item['group'])) { + $item['group'] = 0; + } + // If scope is not set, make it header. + if (!isset($item['scope'])) { + $item['scope'] = 'header'; + } + // Weight cast to string to preserve float. $weight = (string) $item['weight']; $nested[$item['group']][$item['every_page'] ? 1 : 0][$weight][$key] = $item; @@ -3384,7 +4728,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); } @@ -3392,11 +4736,11 @@ function advagg_json_encode($data) { // Default json encode options. $options = JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP | JSON_HEX_QUOT; if ($php550 && variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL) >= 0) { - // Output partial json if not in development mode & PHP >= 5.5.0. + // Output partial json if not in development mode and PHP >= 5.5.0. $options |= JSON_PARTIAL_OUTPUT_ON_ERROR; } if ($php540 && variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL) < 0) { - // Pretty print JSON if in development mode & PHP >= 5.4.0. + // Pretty print JSON if in development mode and PHP >= 5.4.0. $options |= JSON_PRETTY_PRINT; } // Encode to JSON. @@ -3435,12 +4779,12 @@ function advagg_json_encode($data) { break; } if (!empty($error_message)) { - if (module_exists('httprl') && function_exists('httprl_pr')) { + if (is_callable('httprl_pr')) { $pretty_data = httprl_pr($data); } - elseif (module_exists('devel') && function_exists('kprint_r')) { - // @ignore production_php:1 - $pretty_data = kprint_r($data); + elseif (is_callable('kprint_r')) { + // @codingStandardsIgnoreLine + $pretty_data = kprint_r($data, TRUE); } else { $pretty_data = '<pre>' . filter_xss(print_r($data, TRUE)) . '</pre>'; @@ -3455,7 +4799,7 @@ function advagg_json_encode($data) { } /** - * Will scan, flush, use & report any changes to css/js files in aggregates. + * Will scan, flush, use, and report any changes to css/js files in aggregates. */ function advagg_scan_filesystem_for_changes_live() { static $function_has_ran; @@ -3466,7 +4810,7 @@ function advagg_scan_filesystem_for_changes_live() { $bypass_cookie = FALSE; $cookie_name = 'AdvAggDisabled'; - $key = drupal_hash_base64(drupal_get_private_key()); + $key = drupal_hmac_base64('advagg_cookie', drupal_get_private_key() . drupal_get_hash_salt() . variable_get('cron_key', 'drupal')); if (!empty($_COOKIE[$cookie_name]) && $_COOKIE[$cookie_name] == $key) { $bypass_cookie = TRUE; } @@ -3491,13 +4835,16 @@ 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. Trigger: <code>@changes</code>', array( + '%filename' => $filename, + '%db_usage' => count($data[0]), + '%db_count' => count($data[1]), + '@changes' => print_r($data[2], TRUE), + '%type' => $ext, + ))); + } } } @@ -3519,14 +4866,38 @@ function advagg_match_file_pattern($filename) { * * @param string $path * Path to check. + * @param bool $strip_base_path + * Do no add the base path to the given path if TRUE. * * @return string * The path. */ -function advagg_convert_abs_to_rel($path) { - if (strpos($path, $GLOBALS['base_url']) === 0) { - $path = str_replace($GLOBALS['base_url'] . '/', $GLOBALS['base_path'], $path); +function advagg_convert_abs_to_rel($path, $strip_base_path = FALSE) { + $base_url = $GLOBALS['base_url']; + // Add a slash to end if none is found. + if (strpos(strrev($base_url), '/') !== 0) { + $base_url .= '/'; + } + // Set base path. + $base_path = $GLOBALS['base_path']; + if ($strip_base_path) { + $base_path = ''; + } + + // Do conversion of https and http to self references. + $base_url_https = advagg_force_https_path($base_url); + $path = str_replace($base_url_https, $base_path, $path); + $base_url_http = advagg_force_http_path($base_url); + $path = str_replace($base_url_http, $base_path, $path); + + $base_url = advagg_convert_abs_to_protocol($GLOBALS['base_url']); + // Add a slash to end if none is found. + if (strpos(strrev($base_url), '/') !== 0) { + $base_url .= '/'; } + // Do conversion of protocol relative to self references. + $path = str_replace($base_url, $base_path, $path); + return $path; } @@ -3540,17 +4911,14 @@ 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; } /** - * Convert http:// to https://. + * Convert http:// and // to https://. * * @param string $path * Path to check. @@ -3562,6 +4930,9 @@ function advagg_force_https_path($path) { if (strpos($path, 'http://') === 0) { $path = 'https://' . substr($path, 7); } + elseif (strpos($path, '//') === 0) { + $path = 'https:' . $path; + } return $path; } @@ -3588,12 +4959,23 @@ function advagg_force_http_path($path) { * Path to check. * @param array $aggregate_settings * Array of settings used. + * @param bool $run_file_create_url + * If TRUE then run the given path through file_create_url(). + * @param string $source_type + * CSS or JS; if empty url in not embedded in another file. * * @return string * The file uri. */ -function advagg_file_create_url($path, array $aggregate_settings = array()) { - $file_uri = file_create_url($path); +function advagg_file_create_url($path, array $aggregate_settings = array(), $run_file_create_url = TRUE, $source_type = '') { + $file_uri = $path; + if ($run_file_create_url) { + // This calls hook_file_url_alter(). + $file_uri = file_create_url($path); + } + elseif (strpos($path, '/') !== 0 && !advagg_is_external($path)) { + $file_uri = '/' . $path; + } // Ideally convert to relative path. if ((isset($aggregate_settings['variables']['advagg_convert_absolute_to_relative_path']) && $aggregate_settings['variables']['advagg_convert_absolute_to_relative_path']) @@ -3610,6 +4992,43 @@ function advagg_file_create_url($path, array $aggregate_settings = array()) { ) { $file_uri = advagg_convert_abs_to_protocol($file_uri); } + if ($source_type === 'css' + && !advagg_is_external($file_uri) + && ((isset($aggregate_settings['variables']['advagg_css_absolute_path']) + && $aggregate_settings['variables']['advagg_css_absolute_path']) + || (!isset($aggregate_settings['variables']['advagg_css_absolute_path']) + && variable_get('advagg_css_absolute_path', ADVAGG_CSS_ABSOLUTE_PATH))) + ) { + // Get public dir. + list($css_path) = advagg_get_root_files_dir(); + $parsed = parse_url($css_path[0]); + $new_parsed = array(); + if (!empty($parsed['host'])) { + $new_parsed['host'] = $parsed['host']; + } + if (!empty($parsed['path'])) { + $new_parsed['path'] = $parsed['path']; + } + $css_path_0 = advagg_glue_url($new_parsed); + $parsed = parse_url($css_path[1]); + $new_parsed = array(); + if (!empty($parsed['host'])) { + $new_parsed['host'] = $parsed['host']; + } + if (!empty($parsed['path'])) { + $new_parsed['path'] = $parsed['path']; + } + $css_path_1 = advagg_glue_url($new_parsed); + $pos = strpos($css_path_1, $css_path_0); + if (!empty($pos)) { + $public_dir = substr($css_path_1, 0, $pos); + + // If public dir is not in the file uri, use absolute URL. + if (strpos($file_uri, $public_dir) === FALSE) { + $file_uri = url($path, array('absolute' => TRUE)); + } + } + } // Finally force https. if ((isset($aggregate_settings['variables']['advagg_force_https_path']) && $aggregate_settings['variables']['advagg_force_https_path']) @@ -3621,7 +5040,6 @@ function advagg_file_create_url($path, array $aggregate_settings = array()) { return $file_uri; } - /** * Loads the stylesheet and resolves all @import commands. * @@ -3645,14 +5063,14 @@ function advagg_file_create_url($path, array $aggregate_settings = array()) { * * @see drupal_load_stylesheet() */ -function advagg_load_stylesheet($file, $optimize = FALSE, $reset_basepath = TRUE) { - // These statics are not cache variables, so we don't use drupal_static(). +function advagg_load_stylesheet($file, $optimize = FALSE, $reset_basepath = TRUE, $contents = '') { + // These static's are not cache variables, so we don't use drupal_static(). static $_optimize, $basepath; if ($reset_basepath) { $basepath = ''; } - // Store the value of $optimize for preg_replace_callback with nested - // @import loops. + // Store the value of $optimize for preg_replace_callback with nested @import + // loops. if (isset($optimize)) { $_optimize = $optimize; } @@ -3671,7 +5089,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 = (string) @advagg_file_get_contents($file); + } if ($contents) { // Return the processed stylesheet. $content = advagg_load_stylesheet_content($contents, $_optimize); @@ -3685,6 +5105,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. * @@ -3700,17 +5181,19 @@ function advagg_load_stylesheet($file, $optimize = FALSE, $reset_basepath = TRUE * @see drupal_load_stylesheet_content() */ function advagg_load_stylesheet_content($contents, $optimize = FALSE) { - // Remove multiple charset declarations for standards compliance (and fixing - // Safari problems). - $contents = preg_replace('/^@charset\s+[\'"](\S*?)\b[\'"];/i', '', $contents); + // If a BOM is found, convert the file to UTF-8. Used for inline CSS here. + $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. - $comment = '/\*[^*]*\*+(?:[^/*][^*]*\*+)*/'; // Regexp to match double quoted strings. - $double_quot = '"[^"\\\\]*(?:\\\\.[^"\\\\]*)*"'; // Regexp to match single quoted strings. + $comment = '/\*[^*]*\*+(?:[^/*][^*]*\*+)*/'; + $double_quot = '"[^"\\\\]*(?:\\\\.[^"\\\\]*)*"'; $single_quot = "'[^'\\\\]*(?:\\\\.[^'\\\\]*)*'"; // Strip all comment blocks, but keep double/single quoted strings. $contents = preg_replace( @@ -3718,11 +5201,11 @@ function advagg_load_stylesheet_content($contents, $optimize = FALSE) { "$1", $contents ); + // Remove certain whitespace. // There are different conditions for removing leading and trailing // whitespace. // @see http://php.net/manual/regexp.reference.subpatterns.php - // @ignore comment_comment_shell:13 $contents = preg_replace('< # Do not strip any space from within single or double quotes (' . $double_quot . '|' . $single_quot . ') @@ -3735,7 +5218,7 @@ function advagg_load_stylesheet_content($contents, $optimize = FALSE) { # - Opening parenthesis: Retain "@media (bar) and foo". # - Colon: Retain :pseudo-selectors. | ([\(:])\s+ - >xS', + >xSs', // Only one of the three capturing groups will match, so its reference // will contain the wanted value and the references for the // two non-matching groups will be replaced with empty strings. @@ -3747,6 +5230,10 @@ function advagg_load_stylesheet_content($contents, $optimize = FALSE) { $contents .= "\n"; } + // Remove multiple charset declarations for standards compliance (and fixing + // Safari problems). + $contents = preg_replace('/^@charset\s+[\'"](\S*?)\b[\'"];/i', '', $contents); + // Replaces @import commands with the actual stylesheet content. // This happens recursively but omits external files. $contents = preg_replace_callback('%@import\s*+(?:url\(\s*+)?+[\'"]?+(?![a-z]++:|/)([^\'"()\s]++)[\'"]?+\s*+\)?+\s*+;%i', '_advagg_load_stylesheet', $contents); @@ -3772,6 +5259,24 @@ function _advagg_load_stylesheet(array $matches) { // Load the imported stylesheet and replace @import commands in there as well. $file = advagg_load_stylesheet($filename, NULL, FALSE); + if (empty($file)) { + if (strpos($matches[0], 'http://') === 0 + || strpos($matches[0], 'https://') === 0 + || strpos($matches[0], '//') === 0 + ) { + return $matches[0]; + } + if (variable_get('advagg_debug', ADVAGG_DEBUG) >= 2) { + watchdog('advagg-debug', 'Trying to load @file via @import statement but it was not found.', array('@file' => $filename), WATCHDOG_DEBUG); + } + if (variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL) <= 1) { + return $matches[0]; + } + else { + return ''; + } + } + // Determine the file's directory. $directory = dirname($filename); // If the file is in the current directory, make sure '.' doesn't appear in @@ -3796,12 +5301,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 = (string) @advagg_file_get_contents($file); + if (stripos($contents, $function)) { + $hooks[$hook][] = $theme_key; + } } } } @@ -3822,40 +5342,46 @@ 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', - + // 'ais', + // // No control; same every time. 'bluecheese', // drupal_static('clientside_validation_settings') array. -// 'clientside_validation', + // 'clientside_validation', + // + // version_compare(VERSION, '7.14', '<'). + 'conditional_fields', // _css_injector_load_rule() function. // Changes the weight of all files added in init so no special handling. -// 'css_injector', - + // 'css_injector', + // // disable_css_ . $theme . _all variable; default: FALSE. // disable_css_ . $theme . _modules; default: array(). // disable_css_ . $theme . _files; default: array(). -// 'disable_css', - + // 'disable_css', + // // Empty call; commented code is same every time. 'elfinder', // excluded_css_custom variable; Default: ''. // excluded_javascript_custom variable; Default: ''. -// 'excluded', - + // 'excluded', + // // No control; same every time. 'fences', // jqmulti_jquery_path() function. // jqmulti_get_files() function. // jqmulti_load_always variable; Default: FALSE. -// 'jqmulti', - + // 'jqmulti', + // // No control; same every time. 'jquery_dollar', @@ -3902,7 +5428,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 & 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. @@ -3910,7 +5436,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'] : ''; @@ -3925,3 +5468,1510 @@ 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); +} + +/** + * Get the resource hint settings for the preload attribute. + * + * @param bool $return_defaults + * Default FALSE, TRUE returns the default values. + * + * @return array + * Ordered 2 dimensional array. + */ +function advagg_get_resource_hints_preload_settings($return_defaults = FALSE) { + $sub_defaults = array( + 'enabled' => 1, + 'push' => 0, + 'local' => 1, + 'external' => 1, + ); + // Collect your data. + $advagg_resource_hints_preload_settings_defaults = array( + 'style' => $sub_defaults + array( + '#weight' => -10, + 'title' => t('CSS Files'), + ), + 'font' => $sub_defaults + array( + '#weight' => -9, + 'title' => t('Font Files'), + ), + 'script' => $sub_defaults + array( + '#weight' => -8, + 'title' => t('JS Files'), + ), + 'svg' => $sub_defaults + array( + '#weight' => -7, + 'title' => t('SVG Files'), + ), + 'image' => $sub_defaults + array( + '#weight' => -6, + 'title' => t('Image Files'), + ), + 'all_others' => $sub_defaults + array( + '#weight' => -5, + 'title' => t('All Other Files'), + ), + ); + if ($return_defaults) { + return $advagg_resource_hints_preload_settings_defaults; + } + $advagg_resource_hints_preload_settings = variable_get('advagg_resource_hints_preload_settings', $advagg_resource_hints_preload_settings_defaults); + // Merge in defaults. + foreach ($advagg_resource_hints_preload_settings as $id => &$entry) { + if (isset($advagg_resource_hints_preload_settings_defaults[$id])) { + $entry += $advagg_resource_hints_preload_settings_defaults[$id]; + } + ksort($entry); + } + unset($entry); + // Sort the rows. + uasort($advagg_resource_hints_preload_settings, 'element_sort'); + return $advagg_resource_hints_preload_settings; +} + +/** + * See if the .htaccess file uses the RewriteBase directive. + * + * @param string $location + * The location of the .htaccess file. + * + * @return string + * The last active RewriteBase entry in htaccess. + */ +function advagg_htaccess_rewritebase($location = DRUPAL_ROOT) { + if (is_readable($location . '/.htaccess')) { + $htaccess = advagg_file_get_contents($location . '/.htaccess'); + $matches = array(); + $found = preg_match_all('/\\n\s*RewriteBase\s.*/i', $htaccess, $matches); + if ($found && !empty($matches[0])) { + $matches[0] = array_map('trim', $matches[0]); + return array_pop($matches[0]); + } + } + return ''; +} + +/** + * Get the latest version number for the remote version. + * + * @param array $library + * An associative array containing all information about the library. + * @param array $options + * An associative array containing options for the version parser. + * @param string $url + * URL for the remote version to lookup. + * + * @return string + * The latest version number as a string or 0 on failure. + */ +function advagg_get_github_version_json(array $library, array $options, $url) { + $http_options = array('timeout' => 2); + $package = drupal_http_request($url, $http_options); + if (empty($package->data)) { + // Try again. + $package = drupal_http_request($url, array('timeout' => 5)); + } + if (empty($package->data)) { + // Try again but force http. + $url = advagg_force_http_path($url); + $package = drupal_http_request($url, $http_options); + } + if (!empty($package->data)) { + $package = json_decode($package->data); + if (isset($package->version)) { + return (string) $package->version; + } + } + return 0; +} + +/** + * Get the latest version number for the remote version. + * + * @param array $library + * An associative array containing all information about the library. + * @param array $options + * An associative array containing options for the version parser. + * @param string $url + * URL for the remote version to lookup. + * + * @return string + * The latest version number as a string or 0 on failure. + */ +function advagg_get_github_version_txt(array $library, array $options, $url) { + $http_options = array('timeout' => 2); + $request = drupal_http_request($url, $http_options); + if (empty($request->data)) { + // Try again. + $request = drupal_http_request($url, array('timeout' => 5)); + } + if (empty($request->data)) { + // Try again but force http. + $url = advagg_force_http_path($url); + $request = drupal_http_request($url, $http_options); + } + if (!empty($request->data)) { + $matches = array(); + if (preg_match($options['pattern'], $request->data, $matches)) { + return $matches[1]; + } + } + return '0'; +} + +/** + * Update github version numbers to the latest. + * + * @param bool $refresh + * Set to TRUE to skip the cache and force a refresh of the data. + * + * @return mixed + * Key Value pair of the project name and remote version number. If $target is + * set then that version number is returned. + */ +function advagg_get_remote_libraries_versions($refresh = FALSE) { + $cid = __FUNCTION__; + $versions = array(); + if (!$refresh) { + $versions = advagg_get_remote_libraries_versions_cache($cid); + if (!empty($versions)) { + return $versions; + } + } + + if (is_callable('libraries_info')) { + $libraries = libraries_info(); + foreach ($libraries as $key => $library) { + // Get current version. + $libraries_detect = libraries_detect($key); + if (isset($libraries_detect['version'])) { + $versions[$key]['local'] = $libraries_detect['version']; + } + elseif (!empty($libraries_detect['local version'])) { + $versions[$key]['local'] = $libraries_detect['local version']; + } + + // Check if callback is live. + $remote = advagg_get_remote_libraries_version($key, $library, FALSE); + if (!empty($remote)) { + $versions[$key]['remote'] = $remote; + } + } + } + + if (!empty($versions)) { + cache_set($cid, $versions, 'cache_advagg_info'); + } + return $versions; +} + +/** + * Get the remote and local versions cache of the available libraries. + * + * @param string $cid + * Cache ID. + * + * @return array + * Library name => (local, remote). + */ +function advagg_get_remote_libraries_versions_cache($cid = '') { + if (empty($cid)) { + $cid = 'advagg_get_remote_libraries_versions'; + } + $versions = &drupal_static($cid, array()); + if (empty($versions)) { + $cache = cache_get($cid, 'cache_advagg_info'); + if (!empty($cache) && !empty($cache->data)) { + $versions = $cache->data; + } + } + return $versions; +} + +/** + * Get the latest version number for the remote version. + * + * @param string $name + * Name of the library. + * @param array $library + * An associative array containing all information about the library. + * @param bool $use_cache + * TRUE try the cache first. + * + * @return string + * The latest version number as a string or 0 on failure. + */ +function advagg_get_remote_libraries_version($name, array $library, $use_cache = TRUE) { + if ($use_cache) { + $cid = 'advagg_get_remote_libraries_versions'; + $versions = advagg_get_remote_libraries_versions_cache($cid); + if (isset($versions[$name]['remote'])) { + return $versions[$name]['remote']; + } + } + + // Remote url is not set, see if we can generate it given the current data. + if (empty($library['remote']['url']) + && !empty($library['version arguments']) + ) { + if (!isset($library['version arguments']['file']) + && isset($library['version arguments']['variants']) + ) { + // Use the first variant. + $file = reset($library['version arguments']['variants']); + $library['version arguments']['file'] = $file['file']; + $library['version arguments']['pattern'] = $file['pattern']; + } + + if (!empty($library['version arguments']['file'])) { + if (!empty($library['vendor url'])) { + // Use vendor url if it's a github one. + if (strpos($library['vendor url'], 'https://github.com/') === 0) { + $parsed_vendor = @parse_url($library['vendor url']); + // Previously: https://rawgit.com{$parsed_vendor['path']}/master/{$library['version arguments']['file']} . + $library['remote']['url'] = "https://cdn.jsdelivr.net/gh{$parsed_vendor['path']}@master/{$library['version arguments']['file']}"; + } + } + if (empty($library['remote']['url']) && !empty($library['download url'])) { + // Use download url if it's a github one. + if (strpos($library['download url'], 'https://github.com/') === 0) { + $parsed_download = @parse_url($library['download url']); + $paths = explode('/', $parsed_download['path']); + $parsed_download['path'] = "/{$paths[1]}/{$paths[2]}"; + // Previously: https://rawgit.com{$parsed_download['path']}/master/{$library['version arguments']['file']} . + $library['remote']['url'] = "https://cdn.jsdelivr.net/gh{$parsed_download['path']}@master/{$library['version arguments']['file']}"; + } + } + } + } + + // Remote callback is not set, try to generate it given the current data. + if (empty($library['remote']['callback']) + && isset($library['version arguments']['file']) + ) { + if (!empty($library['version callback'])) { + // Use defined parser. + $library['remote']['callback'] = $library['version callback']; + } + else { + if ($library['version arguments']['file'] === 'package.json') { + // JSON parser. + $library['remote']['callback'] = 'advagg_get_github_version_json'; + } + else { + // Text parser. + $library['remote']['callback'] = 'advagg_get_github_version_txt'; + } + } + } + + // Get remote version. + $return = 0; + if (!empty($library['remote']) + && !empty($library['remote']['callback']) + && !empty($library['remote']['url']) + && isset($library['version arguments']) + && is_callable($library['remote']['callback']) + ) { + $return = $library['remote']['callback']($library, $library['version arguments'], $library['remote']['url']); + + // Try package.json on failure. + if (empty($return) && $library['version arguments']['file'] !== 'package.json') { + $pos = strrpos($library['remote']['url'], $library['version arguments']['file']); + $library['remote']['url'] = substr($library['remote']['url'], 0, $pos) . 'package.json'; + $library['remote']['callback'] = 'advagg_get_github_version_json'; + $return = $library['remote']['callback']($library, $library['version arguments'], $library['remote']['url']); + } + } + return $return; +} + +/** + * Get the latest version number for the remote version. + * + * @param string $name + * Name of the library. + * @param string $module_name + * Name of the module where the library is registered. + * + * @return array + * The library array. + */ +function advagg_get_library($name, $module_name) { + $library = cache_get($name, 'cache_libraries'); + if ($library) { + $library = $library->data; + } + else { + if (is_callable('libraries_detect')) { + $library = libraries_detect($name); + } + elseif (is_callable("{$module_name}_libraries_info")) { + $library = call_user_func("{$module_name}_libraries_info"); + $library = $library[$name]; + } + } + return $library; +} + +/** + * Alter the js array fixing the type key if set incorrectly. + * + * @param array $array + * CSS or JS array. + * @param string $type + * CSS or JS. + */ +function advagg_fix_type(array &$array, $type) { + // Skip if advagg is disabled. + if (!advagg_enabled()) { + return; + } + + // Skip if setting is turned off. + if ($type === 'css' && !variable_get('advagg_css_fix_type', ADVAGG_CSS_FIX_TYPE)) { + return; + } + if ($type === 'js' && !variable_get('advagg_js_fix_type', ADVAGG_JS_FIX_TYPE)) { + return; + } + + // Fix type if it was incorrectly set. + // Get hostname and base path. + $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); + + foreach ($array as &$value) { + // Skip if the data is empty or not a string. + if (empty($value['data']) || !is_string($value['data'])) { + continue; + } + + // Default to file if type is not set. + if (!isset($value['type'])) { + $value['type'] = 'file'; + } + + // If inline, be done with processing. + if ($value['type'] === 'inline') { + continue; + } + + // Default to file if not file, inline, external, setting. + if ($value['type'] !== 'file' + && $value['type'] !== 'inline' + && $value['type'] !== 'external' + && $value['type'] !== 'setting' + ) { + if ($value['type'] === 'settings') { + $value['type'] = 'setting'; + } + else { + $value['type'] = 'file'; + } + } + + $lower = strtolower($value['data']); + $http_pos = strpos($lower, 'http://'); + $https_pos = strpos($lower, 'https://'); + $double_slash_pos = strpos($lower, '//'); + $tripple_slash_pos = strpos($lower, '///'); + $mod_base_url_pos = stripos($value['data'], $mod_base_url); + + // If type is external but doesn't start with http, https, or // change it + // to file. + if ($value['type'] === 'external' + && $http_pos !== 0 + && $https_pos !== 0 + && $double_slash_pos !== 0 + ) { + if (is_readable($value['data'])) { + $value['type'] = 'file'; + } + else { + // Fix for subdir issues. + $parsed = parse_url($value['data']); + if (strpos($parsed['path'], $GLOBALS['base_path']) === 0) { + $path = substr($parsed['path'], strlen($GLOBALS['base_path'])); + if (is_readable($path)) { + $value['data'] = $path; + $value['type'] = 'file'; + } + } + } + } + + // If type is file but it starts with http, https, or // change it to + // external. Skip tripple slash for local files. + if ($value['type'] === 'file' + && ($http_pos === 0 + || $https_pos === 0 + || ($double_slash_pos === 0 && $tripple_slash_pos === FALSE)) + ) { + $value['type'] = 'external'; + } + + // If type is external and starts with http, https, or // but points to + // this host change to file, but move it to the top of the aggregation + // stack. + if ($value['type'] === 'external' + && $mod_base_url_pos - 2 === $double_slash_pos + && ($http_pos === 0 + || $https_pos === 0 + || $double_slash_pos === 0 + ) + ) { + $path = substr($value['data'], $mod_base_url_pos + $mod_base_url_len); + if (is_readable($path)) { + $value['data'] = $path; + $value['type'] = 'file'; + $value['group'] = JS_LIBRARY; + $value['every_page'] = TRUE; + $value['weight'] = -40000; + } + else { + // Fix for subdir issues. + $parsed = parse_url($path); + if (strpos($parsed['path'], $GLOBALS['base_path']) === 0) { + $path = substr($parsed['path'], strlen($GLOBALS['base_path'])); + if (is_readable($path)) { + $value['data'] = $path; + $value['type'] = 'file'; + $value['group'] = JS_LIBRARY; + $value['every_page'] = TRUE; + $value['weight'] = -40000; + } + } + } + } + } + unset($value); +} + +/** + * Alter the CSS or JS array removing empty files from the aggregates. + * + * @param array $array + * CSS or JS array. + */ +function advagg_remove_empty_files(array &$array) { + if (!variable_get('advagg_js_remove_empty_files', ADVAGG_JS_REMOVE_EMPTY_FILES)) { + return; + } + + if (variable_get('advagg_fast_filesystem', ADVAGG_FAST_FILESYSTEM)) { + foreach ($array as $key => $value) { + if ($value['type'] !== 'file') { + continue; + } + // Check locally. + if (!is_readable($value['data']) || filesize($value['data']) == 0) { + unset($array[$key]); + } + } + } + else { + module_load_include('inc', 'advagg', 'advagg'); + $files = array(); + foreach ($array as $key => $value) { + if ($value['type'] !== 'file') { + continue; + } + $files[$key] = $value['data']; + } + // Check cache/db. + $info = advagg_get_info_on_files($files); + foreach ($info as $key => $values) { + if (empty($values['filesize'])) { + $key = array_search($values['data'], $files); + if (isset($array[$key])) { + unset($array[$key]); + } + } + } + } +} + +/** + * Alter the CSS or JS array adding in DNS domain info. + * + * @param array $array + * CSS or JS array. + * @param string $type + * CSS or JS. + */ +function advagg_add_default_dns_lookups(array &$array, $type) { + // Skip if advagg is disabled. + if (!advagg_enabled()) { + return; + } + // Remove this return once CSS lookups are needed. + if ($type !== 'js') { + return; + } + + // Add DNS information for some of the more popular modules. + foreach ($array as &$value) { + if (!is_string($value['data'])) { + continue; + } + // Google Ad Manager. + if (strpos($value['data'], '/google_service.') !== FALSE) { + if (!empty($value['dns_prefetch']) && is_string($value['dns_prefetch'])) { + $temp = $value['dns_prefetch']; + unset($value['dns_prefetch']); + $value['dns_prefetch'] = array($temp); + } + // Domains in the google_service.js file. + $value['dns_prefetch'][] = 'https://csi.gstatic.com'; + $value['dns_prefetch'][] = 'https://pubads.g.doubleclick.net'; + $value['dns_prefetch'][] = 'https://partner.googleadservices.com'; + $value['dns_prefetch'][] = 'https://securepubads.g.doubleclick.net'; + + // Domains in the google_ads.js file. + $value['dns_prefetch'][] = 'https://pagead2.googlesyndication.com'; + + // Other domains that usually get hit. + $value['dns_prefetch'][] = 'https://cm.g.doubleclick.net'; + $value['dns_prefetch'][] = 'https://tpc.googlesyndication.com'; + } + + // Google Analytics. + if (strpos($value['data'], 'GoogleAnalyticsObject') !== FALSE + || strpos($value['data'], '.google-analytics.com/ga.js') !== FALSE + ) { + if (!empty($value['dns_prefetch']) && is_string($value['dns_prefetch'])) { + $temp = $value['dns_prefetch']; + unset($value['dns_prefetch']); + $value['dns_prefetch'] = array($temp); + } + if ($GLOBALS['is_https'] && strpos($value['data'], '.google-analytics.com/ga.js') !== FALSE) { + $value['dns_prefetch'][] = 'https://ssl.google-analytics.com'; + } + else { + $value['dns_prefetch'][] = 'https://www.google-analytics.com'; + } + $value['dns_prefetch'][] = 'https://stats.g.doubleclick.net'; + } + } +} + +/** + * Insert element into an array at a specific position. + * + * @param array $input_array + * The original array. + * @param array $new_value + * The element that is getting inserted. + * @param int $location_key + * The key location. + * + * @return array + * The new array with the element merged in. + */ +function advagg_insert_into_array_at_location(array $input_array, array $new_value, $location_key) { + return array_merge(array_slice($input_array, 0, $location_key), $new_value, array_slice($input_array, $location_key)); +} + +/** + * Insert element into an array at a specific key location. + * + * @param array $input_array + * The original array. + * @param array $insert + * The element that is getting inserted; array(key => value). + * @param string $target_key + * The key name. + * @param int $location + * After is 1 , 0 is replace, -1 is before. + * + * @return array + * The new array with the element merged in. + */ +function advagg_insert_into_array_at_key(array $input_array, array $insert, $target_key, $location = 1) { + $output = array(); + $new_value = reset($insert); + $new_key = key($insert); + foreach ($input_array as $key => $value) { + if ($key === $target_key) { + // Insert before. + if ($location == -1) { + $output[$new_key] = $new_value; + $output[$key] = $value; + } + // Replace. + if ($location == 0) { + $output[$new_key] = $new_value; + } + // After. + if ($location == 1) { + $output[$key] = $value; + $output[$new_key] = $new_value; + } + } + else { + // Pick next key if there is an number collision. + if (is_numeric($key)) { + while (isset($output[$key])) { + $key++; + } + } + $output[$key] = $value; + } + } + // Add to array if not found. + if (!isset($output[$new_key])) { + // Before everything. + if ($location == -1) { + $output = $insert + $output; + } + // After everything. + if ($location == 1) { + $output[$new_key] = $new_value; + } + + } + return $output; +} + +/** + * Given a URL output a filename. + * + * @param string $url + * The url. + * @param string $strict + * If FALSE then slashes will be kept. + * + * @return string + * The filename. + */ +function advagg_url_to_filename($url, $strict = TRUE) { + // Keep filtering till there are no more changes. + $decoded1 = _advagg_url_to_filename_filter($url, $strict); + $decoded2 = _advagg_url_to_filename_filter($decoded1, $strict); + while ($decoded1 != $decoded2) { + $decoded1 = _advagg_url_to_filename_filter($decoded2, $strict); + $decoded2 = _advagg_url_to_filename_filter($decoded1, $strict); + } + $filename = $decoded1; + + // Shorten filename to a max of 250 characters. + $filename = mb_strcut($filename, 0, 250, mb_detect_encoding($filename)); + return $filename; +} + +/** + * Given a URL output a filtered filename. + * + * @param string $url + * The url. + * @param string $strict + * If FALSE then slashes will be kept. + * + * @return string + * The filename. + */ +function _advagg_url_to_filename_filter($url, $strict = TRUE) { + // URL Decode if needed. + $decoded1 = $url; + $decoded2 = rawurldecode($decoded1); + while ($decoded1 != $decoded2) { + $decoded1 = rawurldecode($decoded2); + $decoded2 = rawurldecode($decoded1); + } + $url = $decoded1; + + // Replace url spaces with a dash. + $filename = str_replace(array('%20', '+'), '-', $url); + + // Remove file system reserved characters + // https://en.wikipedia.org/wiki/Filename#Reserved_characters_and_words + // Remove control charters + // http://msdn.microsoft.com/en-us/library/windows/desktop/aa365247%28v=vs.85%29.aspx + // Remove non-printing characters DEL, NO-BREAK SPACE, SOFT HYPHEN + // Remove URI reserved characters + // https://tools.ietf.org/html/rfc3986#section-2.2 + // Remove URL unsafe characters + // https://www.ietf.org/rfc/rfc1738.txt + if ($strict) { + $filename = preg_replace('~[<>:"/\\|?*]|[\x00-\x1F]|[\x7F\xA0\xAD]|[#\[\]@!$&\'()+,;=%]|[{}^\~`]~x', '-', $filename); + } + else { + $filename = preg_replace('~[<>:"\\|?*]|[\x00-\x1F]|[\x7F\xA0\xAD]|[#\[\]@!$&\'()+,;=%]|[{}^\~`]~x', '-', $filename); + } + + // Replce all white spaces with a dash. + $filename = preg_replace('/[\r\n\t -]+/', '-', $filename); + + // Avoid ".", ".." or ".hiddenFiles". + $filename = ltrim($filename, '.-'); + + // Compress spaces in a file name and replace with a dash. + // Compress underscores in a file name and replace with a dash. + // Compress dashes in a file name and replace with a dash. + $filename = preg_replace(array('/ +/', '/_+/', '/-+/'), '-', $filename); + + // Compress dashes and dots in a file name and replace with a dot. + $filename = preg_replace(array('/-*\.-*/', '/\.{2,}/'), '.', $filename); + + // Lowercase for windows/unix interoperability + // http://support.microsoft.com/kb/100625. + $filename = mb_strtolower($filename, 'UTF-8'); + // Remove ? \ .. + $filename = str_replace(array('?', '\\', '..'), '', $filename); + + // ".file-name.-" becomes "file-name". + $filename = trim($filename, '.-'); + + return $filename; +} + +/** + * Given a URI return TRUE if it is external. + * + * @param string $uri + * The uri. + * + * @return bool + * TRUE if external. + */ +function advagg_is_external($uri) { + $http_pos = strpos($uri, 'http://'); + $https_pos = strpos($uri, 'https://'); + $double_slash_pos = strpos($uri, '//'); + if ($http_pos !== 0 + && $https_pos !== 0 + && $double_slash_pos !== 0 + ) { + return FALSE; + } + return TRUE; +} + +/** + * Same as file_get_contents() but will convert string to UTF-8 if needed. + * + * @return mixed + * The files contents or FALSE on failure. + */ +function advagg_file_get_contents() { + // Get the file contents. + $file_contents = call_user_func_array('file_get_contents', func_get_args()); + if ($file_contents === FALSE) { + return $file_contents; + } + + // If a BOM is found, convert the file to UTF-8. + $encoding = advagg_get_encoding_from_bom($file_contents); + if (!empty($encoding)) { + $file_contents = advagg_convert_to_utf8($file_contents, $encoding); + } + + return $file_contents; +} + +/** + * Get the description text based off the library version. + * + * @param string $library_name + * Name of the library. + * @param string $module_name + * Name of the module that contains hook_libraries_info for this library. + * + * @return array + * Description text and info array. + */ +function advagg_get_version_description($library_name, $module_name, $only_remote_ok = FALSE) { + $t = get_t(); + + // Get local and external library version numbers. + $versions = &drupal_static(__FUNCTION__); + if (!isset($versions)) { + $versions = advagg_get_remote_libraries_versions(TRUE); + } + + $description = ''; + if (!empty($versions[$library_name]['remote']) + && (empty($versions[$library_name]['local']) + || $versions[$library_name]['local'] !== $versions[$library_name]['remote'] + )) { + $library = advagg_get_library($library_name, $module_name); + if (empty($versions[$library_name]['local'])) { + $versions[$library_name]['local'] = 'NULL'; + } + if (!empty($library['installed'])) { + $description = $t('Go to the <a href="@url-page">@name</a> page and <a href="@url-zip">download</a> the latest version (@remote) into the @lib_path folder. An example valid filename is %version_file. Current Version: %version.', array( + '@name' => $library['name'], + '@lib_path' => $library['library path'], + '@url-page' => $library['vendor url'], + '@url-zip' => $library['download url'], + '@remote' => $versions[$library_name]['remote'], + '%version' => $versions[$library_name]['local'], + '%version_file' => $library['library path'] . '/' . $library['version arguments']['file'], + )); + } + elseif (!$only_remote_ok && is_callable('libraries_load')) { + $description = $t('Go to the <a href="@url-page">@name</a> page and <a href="@url-zip">download</a> the latest version (@remote) into the libraries folder (usually sites/all/libraries). An example valid filename is %version_file.', array( + '@name' => $library['name'], + '@url-page' => $library['vendor url'], + '@url-zip' => $library['download url'], + '@remote' => $versions[$library_name]['remote'], + '%version_file' => "sites/all/libraries/$library_name/{$library['version arguments']['file']}", + )); + } + elseif (!$only_remote_ok) { + $description = $t('Install the <a href="@url-lib-api">Libraries API</a> module and then go to the <a href="@url-page">@name</a> page and <a href="@url-zip">download</a> the latest version (@remote) into the libraries folder (usually sites/all/libraries). An example valid filename is %version_file.', array( + '@name' => $library['name'], + '@url-page' => $library['vendor url'], + '@url-zip' => $library['download url'], + '@remote' => $versions[$library_name]['remote'], + '%version_file' => "sites/all/libraries/$library_name/{$library['version arguments']['file']}", + '@url-lib-api' => 'https://www.drupal.org/project/libraries', + )); + } + } + $path = drupal_get_path('module', $module_name); + $info = drupal_parse_info_file("{$path}/{$module_name}.info"); + + // Check if library was unzipped with -master. + if (!empty($description) && is_callable('libraries_get_libraries')) { + $libraries_paths = libraries_get_libraries(); + if (!empty($libraries_paths["{$library_name}-master"])) { + $description = $t('Rename @lib_dir_master to @lib_dir at this location: @lib_path_master.', array( + '@lib_dir_master' => "{$library_name}-master", + '@lib_path_master' => $libraries_paths["{$library_name}-master"], + '@lib_dir' => $library_name, + )); + } + } + + return array($description, $info); +} + +/** + * 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_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; + } +} + +/** + * Display a message if there are requirement issues with AdvAgg. + * + * @param array $requirements + * Other requirements to list besides the standard ones. + */ +function advagg_display_message_if_requirements_not_met(array $requirements = array()) { + include_once DRUPAL_ROOT . '/includes/install.inc'; + module_load_include('install', 'advagg'); + $requirements += advagg_install_fast_checks(); + if (!empty($requirements)) { + module_load_include('admin.inc', 'system'); + usort($requirements, '_system_sort_requirements'); + $report = theme('status_report', array('requirements' => $requirements)); + drupal_set_message(t('Go to the <a href="@url">status report page</a> and fix the issues that AdvAgg lists there. Sneak peak: !report', array( + '@url' => url('admin/reports/status'), + '!report' => $report, + ))); + } +} + +/** + * Add in the preload header for CSS and JS external files. + * + * @param string $url + * The url of the external host. + * + * @return bool + * TRUE if it was added to the head. + */ +function advagg_add_preload_header($url = '', $as = '') { + // Get defaults and setup static's. + $list = &drupal_static(__FUNCTION__ . ':list', array()); + $output = &drupal_static(__FUNCTION__ . ':output'); + $header_strlen = &drupal_static(__FUNCTION__ . ':strlen', 0); + static $resource_hints_preload_order; + if (!isset($resource_hints_preload_order)) { + $resource_hints_preload_order = advagg_get_resource_hints_preload_settings(); + } + if (!isset($output)) { + $keys = array_keys($resource_hints_preload_order); + $output = array_fill_keys($keys, array()); + } + + // Output headers. + if (empty($url)) { + + // Call hook_advagg_preload_header_alter() + drupal_alter('advagg_preload_header', $output); + + // Build header string. + $header_value = ''; + foreach ($output as $value) { + if (!empty($value)) { + // Remove empty values. + $value = array_filter($value); + foreach ($value as $string) { + $header_strlen += strlen($string) + 2; + // Don't add if over the limit. + if ($header_strlen >= variable_get('advagg_resource_hints_preload_max_size', ADVAGG_RESOURCE_HINTS_PRELOAD_MAX_SIZE)) { + continue; + } + // Add to $header_value string. + if (empty($header_value)) { + $header_value = $string; + } + else { + $header_value .= ',' . $string; + } + } + } + } + if (!empty($header_value)) { + drupal_add_http_header('Link', $header_value, TRUE); + } + return FALSE; + } + + // Check for duplicates. + if (isset($list[$url])) { + return FALSE; + } + + // Fill in missing info. + $payload = "<{$url}>; rel=preload"; + list($as, $type, $crossorigin, $parse) = advagg_get_preload_info_from_url($url, $as); + if (!empty($as) && $as === 'php') { + $list[$url] = FALSE; + return FALSE; + } + + $additional_info = array(); + if (!empty($crossorigin)) { + $additional_info[] = "crossorigin"; + } + if (!empty($type)) { + $additional_info[] = $type; + } + $additional_info = implode('; ', $additional_info); + + // Get settings. + if (!empty($as) && !empty($resource_hints_preload_order[$as])) { + $settings = $resource_hints_preload_order[$as]; + } + elseif (!empty($resource_hints_preload_order['all_others'])) { + $settings = $resource_hints_preload_order['all_others']; + } + + // Apply settings. + if (!$settings['enabled']) { + $list[$url] = FALSE; + return FALSE; + } + if (!$settings['local'] && empty($parse['host'])) { + $list[$url] = FALSE; + return FALSE; + } + if (!$settings['external'] && !empty($parse['host'])) { + $list[$url] = FALSE; + return FALSE; + } + + // Add additional info and queue. + if (!empty($as)) { + $payload .= "; as={$as}"; + } + if (!empty($additional_info)) { + $payload .= "; {$additional_info}"; + } + if (!$settings['push']) { + $payload .= "; nopush"; + } + $list[$url] = TRUE; + $output[$as][] = $payload; +} + +/** + * Given a link get the as, type, and crossorigin attributes. + * + * @param string $url + * Link to the url that will be preloaded. + * @param string $as + * What type of content is this; font, image, video, etc. + * @param string $type + * The mime type of the file. + * @param string $crossorigin + * Preload cross-origin resources; fonts always need to be CORS. + * + * @return array + * An array containing + */ +function advagg_get_preload_info_from_url($url, $as = '', $type = '', $crossorigin = NULL) { + // Get extension. + $parse = @parse_url($url); + if (empty($parse['path'])) { + return FALSE; + } + $file_ext = strtolower(pathinfo($parse['path'], PATHINFO_EXTENSION)); + if (empty($file_ext)) { + $file_ext = basename($parse['path']); + } + + // Detect missing parts. + $list = advagg_preload_list(); + if (empty($as) && !empty($file_ext)) { + foreach ($list as $as_key => $list_type) { + $key = array_search($file_ext, $list_type); + if ($key !== FALSE) { + $as = $as_key; + // Type of font, ext is svg but file doesn't contain string font. + // This will be treated as an image. + if ($as === 'font' + && $file_ext === 'svg' + && stripos($url, 'font') === FALSE + ) { + $as = ''; + } + } + if (!empty($as)) { + break; + } + } + } + if (empty($type) && !empty($as)) { + $type = "$as/$file_ext"; + if ($file_ext === 'svg') { + $type .= '+xml'; + } + if ($file_ext === 'js') { + $type = 'text/javascript'; + } + } + if ($as === 'font' && is_null($crossorigin)) { + $crossorigin = 'anonymous'; + } + return array($as, $type, $crossorigin, $parse); +} + +/** + * Add preload link to the top of the html head. + * + * @param string $url + * Link to the url that will be preloaded. + * @param string $media + * Media types or media queries, allowing for responsive preloading. + * @param string $as + * What type of content is this; font, image, video, etc. + * @param string $type + * The mime type of the file. + * @param string $crossorigin + * Preload cross-origin resources; fonts always need to be CORS. + */ +function advagg_add_preload_link($url, $media = '', $as = '', $type = '', $crossorigin = NULL) { + static $weight = -2000; + $weight += 0.0001; + + $href = advagg_file_create_url($url); + $key = "advagg_preload:{$href}"; + + // Return here if url has already been added. + $stored_head = drupal_static('drupal_add_html_head'); + if (isset($stored_head[$key])) { + return TRUE; + } + + // Add basic attributes. + $attributes = array( + 'rel' => 'preload', + 'href' => $href, + ); + + // Fill in missing info. + list($as, $type, $crossorigin) = advagg_get_preload_info_from_url($url, $as, $type, $crossorigin); + + // Exit if no as. + if (empty($as)) { + return FALSE; + } + // Build up attributes array. + $attributes['as'] = $as; + if (!empty($type)) { + $attributes['type'] = $type; + } + if (!empty($crossorigin)) { + $attributes['crossorigin'] = $crossorigin; + } + if (!empty($media)) { + $attributes['media'] = $media; + } + // Call hook_advagg_preload_link_attributes_alter() + drupal_alter('advagg_preload_link_attributes', $attributes); + + // Add to HTML head. + $element = array( + '#type' => 'html_tag', + '#tag' => 'link', + '#attributes' => $attributes, + '#weight' => $weight, + ); + drupal_add_html_head($element, $key); + return TRUE; +} + +/** + * Generate a list of file types for the as field given the extension. + * + * @return array + * Returns an array of arrays. + */ +function advagg_preload_list() { + $list = array( + 'font' => array( + 'woff2', + 'woff', + 'ttf', + 'otf', + 'eot', + // Need to check if the svg file is in a font folder. + 'svg', + ), + 'image' => array( + 'gif', + 'jpg', + 'jpeg', + 'jpe', + 'jif', + 'jfif', + 'jfi', + 'png', + 'webp', + 'jp2', + 'jpx', + 'jxr', + 'heif', + 'heic', + 'bpg', + 'svg', + ), + 'style' => array( + 'css', + ), + 'script' => array( + 'js', + ), + 'video' => array( + 'mp4', + 'webm', + 'ogg', + ), + ); + + // Call hook_advagg_preload_list_alter() + drupal_alter('advagg_preload_list', $list); + return $list; +} + +/** + * Save form defaults or recommended values. + * + * @param array $element + * Form element or child element. + * + * @return array + * An array of form names and the recommended value for that setting. + */ +function advagg_find_all_recommended_admin_values(array &$element, $key_name = '#recommended_value') { + $results = array(); + $children = element_children($element); + foreach ($children as $key) { + $child = $element[$key]; + if (is_array($child)) { + if (!empty($child['#type']) + && !empty($child['#name']) + && isset($child[$key_name]) + ) { + $results[$child['#name']] = $child[$key_name]; + } + $results = array_merge($results, advagg_find_all_recommended_admin_values($child, $key_name)); + } + unset($child); + } + return $results; +} + +/** + * Get form values that have changed. + * + * @param array $element + * Form element or child element. + * + * @return array + * An array of form names and the recommended value for that setting. + */ +function advagg_find_all_changed_admin_values(array &$element) { + $results = array(); + $children = element_children($element); + foreach ($children as $key) { + $child = $element[$key]; + if (is_array($child)) { + if (!empty($child['#type']) + && !empty($child['#name']) + && isset($child['#default_value']) + && isset($child['#value']) + ) { + if ($child['#type'] === 'checkboxes') { + // Add in not selected by default values. + $child['#value'] += array_diff_assoc($child['#default_value'], $child['#value']); + } + if ($child['#default_value'] != $child['#value']) { + $results[$child['#name']] = array($child['#value'], $child['#default_value']); + } + } + $results = array_merge($results, advagg_find_all_changed_admin_values($child)); + } + unset($child); + } + return $results; +} + +/** + * Get form title and description. + * + * @param array $element + * Form element or child element. + * + * @return array + * An array of form names and the recommended value for that setting. + */ +function advagg_find_title(array &$element) { + $results = array(); + $children = element_children($element); + foreach ($children as $key) { + $child = $element[$key]; + if (is_array($child)) { + if (!empty($child['#type']) + && !empty($child['#name']) + && isset($child['#title']) + && isset($child['#default_value']) + && !isset($results[$child['#name']]) + && $child['#type'] !== 'radio' + ) { + $results[$child['#name']] = $child['#title']; + } + $results = array_merge($results, advagg_find_title($child)); + } + unset($child); + } + return $results; +} + +/** + * Save form defaults or recommended values. + * + * @param array $form_state + * Form state array from drupal form submit. + * @param string $trigger_key + * The key of the setting from the form that controls this. + */ +function advagg_set_admin_form_defaults_recommended(array &$form_state, $trigger_key) { + $changed = array(); + $recommended_values = array(); + + // Set to recommended values. + if ($form_state['values'][$trigger_key] == 2) { + $recommended_values = advagg_find_all_recommended_admin_values($form_state['complete form']); + foreach ($recommended_values as $key => $value) { + if (!isset($form_state['values'][$key])) { + $changed[$key] = array($value); + } + elseif ($value != $form_state['values'][$key]) { + $changed[$key] = array($value, $form_state['values'][$key]); + } + $form_state['values'][$key] = $value; + } + } + + // Set to defaults. + if ($form_state['values'][$trigger_key] == 0 || $form_state['values'][$trigger_key] == 2) { + // Reset to defaults. + foreach ($form_state['values'] as $key => &$value) { + // Skip non advagg settings, trigger key, or if a recommended value. + if (strpos($key, 'advagg_') !== 0 + || $key === $trigger_key + || isset($changed[$key]) + || isset($recommended_values[$key]) + ) { + continue; + } + + // Default to FALSE. + $default = FALSE; + // Get easy defaults. + if (defined(strtoupper($key))) { + $default = constant(strtoupper($key)); + } + + // Get more complex default values. + if ($key === 'advagg_resource_hints_preload_settings') { + $default = advagg_get_resource_hints_preload_settings(TRUE); + foreach ($default as $key => &$values) { + $default[$key]['weight'] = $values['#weight']; + unset($default[$key]['#weight'], $values['#weight'], $default[$key]['title'], $values['title']); + ksort($values); + } + ksort($default); + foreach ($value as $key => &$values) { + ksort($values); + } + ksort($value); + } + if ($key === 'advagg_relocate_css_inline_import_browsers') { + $default = array( + 'woff2' => 'woff2', + 'woff' => 'woff', + 'ttf' => 'ttf', + 'eot' => 0, + 'svg' => 0, + ); + } + + // See if it changed. + if ($default != $value) { + // After, Before. + $changed[$key] = array($default, $value); + $value = $default; + } + } + } + + if ($form_state['values'][$trigger_key] == 4) { + $changed = advagg_find_all_changed_admin_values($form_state['complete form']); + if (isset($changed[$trigger_key])) { + unset($changed[$trigger_key]); + } + } + + $all_titles_descriptions = advagg_find_title($form_state['complete form']); + foreach ($changed as $key => $values) { + + // Remove things that didn't really change. + if (isset($values[1])) { + if ($values[0] == $values[1]) { + unset($changed[$key]); + continue; + } + if (is_string($values[0]) + && is_string($values[1]) + && trim($values[0]) == trim($values[1]) + ) { + unset($changed[$key]); + continue; + } + } + + // Make output nicer. + if (!isset($values[1])) { + $values[1] = NULL; + } + if (is_bool($values[0]) && !is_bool($values[1]) + || !is_bool($values[0]) && is_bool($values[1]) + ) { + $values[0] = (bool) $values[0]; + $values[1] = (bool) $values[1]; + } + if (is_int($values[0]) && !is_int($values[1]) + || !is_int($values[0]) && is_int($values[1]) + ) { + $values[0] = (int) $values[0]; + $values[1] = (int) $values[1]; + } + + // Let user know what changed. + if (empty($all_titles_descriptions[$key])) { + drupal_set_message(t('%before -> %after for %title', array( + '%title' => $key, + '%before' => var_export($values[1], TRUE), + '%after' => var_export($values[0], TRUE), + ))); + } + else { + drupal_set_message(t('%before -> %after for %title', array( + '%title' => $all_titles_descriptions[$key], + '%before' => var_export($values[1], TRUE), + '%after' => var_export($values[0], TRUE), + ))); + } + } + + return $changed; +} + +/** + * Given a list of items see what ones need to be inserted/updated or deleted. + * + * @param array $defaults + * Array of default values, representing a row in the db. + * @param mixed $new_values + * Array of edited values, representing a row in the db. + * + * @return array + * Nested array strucutre; only the diff. + */ +function advagg_diff_multi(array $defaults, $new_values) { + $result = array(); + + foreach ($defaults as $key => $val) { + if (is_array($val) && isset($new_values[$key])) { + $tmp = advagg_diff_multi($val, $new_values[$key]); + if ($tmp) { + $result[$key] = $tmp; + } + } + elseif (!isset($new_values[$key])) { + $result[$key] = NULL; + } + elseif ($val != $new_values[$key]) { + $result[$key] = $new_values[$key]; + } + if (isset($new_values[$key])) { + unset($new_values[$key]); + } + } + + $result = $result + $new_values; + return $result; +} 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 b46c04833d..d7bfd0316a 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 @@ -8,17 +8,108 @@ /** * Form builder; Configure advagg settings. * - * @ingroup forms + * @ingroup advagg_forms * * @see system_settings_form() */ function advagg_bundler_admin_settings_form() { drupal_set_title(t('AdvAgg: Bundler')); + advagg_display_message_if_requirements_not_met(); $form = array(); - $form['advagg_bundler_active'] = array( + $options = array( + 0 => t('Use HTTP 1.1 settings'), + 2 => t('Use HTTP 2.0 settings'), + 4 => t('Use customized settings'), + ); + $form['advagg_bundler_admin_mode'] = array( + '#type' => 'radios', + '#title' => t('AdvAgg Settings'), + '#default_value' => variable_get('advagg_bundler_admin_mode', ADVAGG_BUNDLER_ADMIN_MODE), + '#options' => $options, + '#description' => t("Default settings will mirror core as closely as possible. <br>Recommended settings are optimized for speed."), + ); + + // Test http2. + $http2_support = 0; + $url = 'https://tools.keycdn.com/http2-query.php?url=' . url('', array('absolute' => TRUE)); + if (filter_var($_SERVER['HTTP_HOST'], FILTER_VALIDATE_IP) === FALSE && $_SERVER['HTTP_HOST'] !== 'localhost') { + $response = drupal_http_request($url, array( + 'timeout' => 3, + )); + } + else { + $response = new stdClass(); + $response->code = 0; + } + if ($response->code == 200 && !empty($response->data)) { + $http2_support = 1; + if (stripos($response->data, 'success') !== FALSE) { + $http2_support = 2; + } + } + if (stripos($_SERVER['SERVER_SOFTWARE'], 'apache') !== FALSE && is_callable('apache_get_modules')) { + $modules = apache_get_modules(); + $key_a = array_search('mod_http2', $modules); + $key_b = array_search('mod_h2', $modules); + if ($key_a !== FALSE || $key_b !== FALSE) { + $http2_support += 4; + } + } + // Display http2 info. + if ($http2_support & 2) { + $description = t('This server supports HTTP 2.0; the test from @url came back ok.', array('@url' => $url)); + } + else { + if ($http2_support == 0) { + $description = t('AdvAgg can not detect if this server supports HTTP 2.0. You can <a href="@url">go here to learn how to enable it</a>.', array('@url' => 'https://httpd.apache.org/docs/2.4/mod/mod_http2.html')); + } + elseif ($http2_support & 4) { + $description = t('It appears that this server could support HTTP 2.0 (apache <a href="@url">mod_http2</a> found)', array( + '@url' => 'https://httpd.apache.org/docs/2.4/mod/mod_http2.html', + )); + if ($http2_support & 1) { + $description .= t(', but it may not be configured to do so. The test from <a href="@test">here</a> was inconclusive.', array( + '@test' => $url, + )); + } + else { + if (count($response) == 1) { + $description .= t(', but the test from <a href="@test">here</a> was not actually done due to the URL being an IP address or localhost.', array( + '@test' => $url, + )); + } + else { + $description .= t(', but the test from <a href="@test">here</a> was inconclusive.', array( + '@test' => $url, + )); + } + } + } + else { + $description = t('This server does not support HTTP 2.0.'); + } + $description .= ' ' . t('<a href="@url">This guide</a> can help you get http2 enabled on your site.', array('@url' => 'https://geekflare.com/http2-implementation-apache-nginx/')); + } + if (!empty($description)) { + $form['advagg_http2'] = array( + '#markup' => "<p>{$description}</p>", + ); + } + + $form['global_container'] = array( + '#type' => 'container', + '#hidden' => TRUE, + '#states' => array( + 'visible' => array( + ':input[name="advagg_bundler_admin_mode"]' => array('value' => '4'), + ), + ), + ); + + $form['global_container']['advagg_bundler_active'] = array( '#type' => 'checkbox', - '#title' => t('Bundler is Active'), + '#title' => t('Bundler is Active (recommended)'), '#default_value' => variable_get('advagg_bundler_active', ADVAGG_BUNDLER_ACTIVE), '#description' => t('If not checked, the bundler will passively monitor your site, but it will not split up aggregates.'), ); @@ -40,33 +131,62 @@ function advagg_bundler_admin_settings_form() { 13 => 13, 14 => 14, 15 => 15, + 16 => 16, + 17 => 17, + 18 => 18, + 19 => 19, + 20 => 20, + 21 => 21, + 22 => 22, + 23 => 23, + 24 => 24, + 25 => 25, ); - $form['advagg_bundler_max_css'] = array( + $form['global_container']['advagg_bundler_max_css'] = array( '#type' => 'select', '#title' => t('Target Number Of CSS Bundles Per Page'), '#default_value' => variable_get('advagg_bundler_max_css', ADVAGG_BUNDLER_MAX_CSS), '#options' => $options, - '#description' => t('If 0 is selected then the bundler is disabled'), + '#description' => t('If 0 is selected then the bundler is disabled. 2 is recommended for HTTP 1.1 and 25 for HTTP 2.0.'), '#states' => array( 'disabled' => array( '#edit-advagg-bundler-active' => array('checked' => FALSE), ), ), + '#recommended_value' => 25, ); - $form['advagg_bundler_max_js'] = array( + $form['global_container']['advagg_bundler_max_js'] = array( '#type' => 'select', '#title' => t('Target Number Of JS Bundles Per Page'), '#default_value' => variable_get('advagg_bundler_max_js', ADVAGG_BUNDLER_MAX_JS), '#options' => $options, - '#description' => t('If 0 is selected then the bundler is disabled'), + '#description' => t('If 0 is selected then the bundler is disabled. 5 is recommended for HTTP 1.1 and 25 for HTTP 2.0.'), + '#states' => array( + 'disabled' => array( + '#edit-advagg-bundler-active' => array('checked' => FALSE), + ), + ), + '#recommended_value' => 25, + ); + + $form['global_container']['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), ), ), + '#recommended_value' => 0, ); - $form['info'] = array( + $form['global_container']['info'] = array( '#type' => 'fieldset', '#collapsible' => TRUE, '#collapsed' => TRUE, @@ -76,26 +196,34 @@ function advagg_bundler_admin_settings_form() { $analysis = advagg_bundler_analysis('', TRUE); $rawtext = print_r($analysis, TRUE); - $form['info']['advagg_bundler_info'] = array( + $form['global_container']['info']['advagg_bundler_info'] = array( '#type' => 'textarea', '#title' => t('%count different groupings', array('%count' => count($analysis))), '#default_value' => $rawtext, '#rows' => 30, ); - // Clear the cache bins on submit. + // Clear the cache bins on submit. Also remove advagg_bundler_info so it + // doesn't get added to the variable table. $form['#submit'][] = 'advagg_bundler_admin_settings_form_submit'; return system_settings_form($form); } -// Submit callback. /** - * Clear out the advagg cache bin when the save configuration button is pressed. + * Submit callback, clear out the advagg cache bin. + * + * @ingroup advagg_forms_callback */ function advagg_bundler_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(); + + // Unset advagg_bundler_info. + if (isset($form_state['values']['advagg_bundler_info'])) { + unset($form_state['values']['advagg_bundler_info']); } + + // Reset this form to defaults or recommended values; also show what changed. + advagg_set_admin_form_defaults_recommended($form_state, 'advagg_bundler_admin_mode'); } 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 e712cc17f7..1fed98a6e2 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 @@ -5,6 +5,11 @@ * Advanced aggregation bundler module. */ +/** + * @addtogroup advagg_hooks + * @{ + */ + /** * Implements hook_advagg_build_aggregate_plans_alter(). */ @@ -37,17 +42,20 @@ function advagg_bundler_advagg_build_aggregate_plans_alter(&$files, &$modified, $group = advagg_bundler_analysis($fileinfo['data']); $fileinfo['bundler'] = $group; - // Set $last_group if this is the first run of this foreach loop. - if (empty($last_group)) { - $last_group = $group['group_hash']; - } + if (!empty($group['group_hash'])) { + // Set $last_group if this is the first run of this foreach loop. + if (empty($last_group)) { + $last_group = $group['group_hash']; + } - if ($last_group != $group['group_hash']) { - // Bump Counter if group has changed from the last one. - ++$counter; - $last_group = $group['group_hash']; - $modified = TRUE; + if ($last_group != $group['group_hash']) { + // Bump Counter if group has changed from the last one. + ++$counter; + $last_group = $group['group_hash']; + $modified = TRUE; + } } + $groupings[$counter][] = $fileinfo; } // Make sure we didn't go over the max; if we did merge the smallest bundles @@ -91,6 +99,10 @@ function advagg_bundler_advagg_build_aggregate_plans_alter(&$files, &$modified, $files = advagg_generate_filenames($final_groupings, $type); } +/** + * @} End of "addtogroup advagg_hooks". + */ + /** * Merge bundles together if too many where created. * @@ -112,10 +124,22 @@ 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)); + // Skip if bundler data is missing. + if (empty($data['bundler'])) { + continue; + } + $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 +191,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 +258,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 58f449da76..31477cffe1 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,8 @@ dependencies[] = advagg configure = admin/config/development/performance/advagg/bundler -; Information added by Drupal.org packaging script on 2015-04-14 -version = "7.x-2.8" +; Information added by Drupal.org packaging script on 2020-11-19 +version = "7.x-2.35" core = "7.x" project = "advagg" -datestamp = "1429049283" - +datestamp = "1605792717" diff --git a/sites/all/modules/contrib/advagg/advagg_bundler/advagg_bundler.install b/sites/all/modules/contrib/advagg/advagg_bundler/advagg_bundler.install new file mode 100644 index 0000000000..0cfaf6e4e6 --- /dev/null +++ b/sites/all/modules/contrib/advagg/advagg_bundler/advagg_bundler.install @@ -0,0 +1,37 @@ +<?php + +/** + * @file + * Handles Advanced Aggregation installation and upgrade tasks. + */ + +/** + * @addtogroup hooks + * @{ + */ + +/** + * Implements hook_install(). + */ +function advagg_bundler_install() { + // New install gets a locked admin section. + variable_set('advagg_bundler_admin_mode', 0); +} + +/** + * Remove the advagg_bundler_info variable if set. + */ +function advagg_bundler_update_7201() { + $advagg_bundler_info = variable_get('advagg_bundler_info'); + if (!empty($advagg_bundler_info)) { + variable_del('advagg_bundler_info'); + return t('Removed the advagg_bundler_info variable.'); + } + else { + return t('Nothing needed to happen.'); + } +} + +/** + * @} End of "addtogroup hooks". + */ 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 b027cead19..8a0d0b738b 100644 --- a/sites/all/modules/contrib/advagg/advagg_bundler/advagg_bundler.module +++ b/sites/all/modules/contrib/advagg/advagg_bundler/advagg_bundler.module @@ -5,15 +5,20 @@ * Advanced aggregation bundler module. */ +/** + * @addtogroup default_variables + * @{ + */ + /** * Default for maximum number of CSS bundles used in a themes region. */ -define('ADVAGG_BUNDLER_MAX_CSS', 4); +define('ADVAGG_BUNDLER_MAX_CSS', 2); /** * Default for maximum number of JS bundles used in a themes region. */ -define('ADVAGG_BUNDLER_MAX_JS', 4); +define('ADVAGG_BUNDLER_MAX_JS', 5); /** * Default of the last used time before the bundle is considered outdated. @@ -30,6 +35,25 @@ define('ADVAGG_BUNDLER_OUTDATED', 1209600); */ define('ADVAGG_BUNDLER_ACTIVE', TRUE); +/** + * Default value to for bundler, set to file size. + */ +define('ADVAGG_BUNDLER_GROUPING_LOGIC', 1); + +/** + * If 4 the admin section gets unlocked. + */ +define('ADVAGG_BUNDLER_ADMIN_MODE', 4); + +/** + * @} End of "addtogroup default_variables". + */ + +/** + * @addtogroup hooks + * @{ + */ + /** * Implements hook_menu(). */ @@ -81,6 +105,10 @@ function advagg_bundler_form_advagg_admin_settings_form_alter(&$form, $form_stat } } +/** + * @} End of "addtogroup hooks". + */ + /** * Returns TRUE if the bundler will run. */ @@ -97,11 +125,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,100 +184,18 @@ function advagg_bundler_analysis($filename = '', $force = FALSE) { } if ($force || empty($cache->data)) { - // "Magic Query"; only needs to run once. - // 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(); - - // Create join query for the advagg_aggregates table. - $subquery_aggregates = db_select('advagg_aggregates', 'aa'); - if ($db_type === 'sqlsrv') { - // MS SQL does not support LPAD. - $subquery_aggregates->addExpression("RIGHT(REPLICATE('0',8) + CAST(COUNT(aav.aggregate_filenames_hash) AS char(8)),8)", 'counter'); - } - else { - $subquery_aggregates->addExpression("LPAD(CAST(COUNT(aav.aggregate_filenames_hash) AS char(8)), 8, '0')", 'counter'); - } - - $fields = array('counter'); - if ($db_type === 'mysql') { - db_query('SET SESSION group_concat_max_len = 65535'); - $fields = array('counter', 'hashlist'); - $subquery_aggregates->addExpression('GROUP_CONCAT(DISTINCT aa.aggregate_filenames_hash ORDER BY aa.aggregate_filenames_hash ASC)', 'hashlist'); + 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; } - - $analysis[$row->filename] = array( - 'group_hash' => $row->counter . ' ' . drupal_hash_base64($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; @@ -266,3 +216,194 @@ function advagg_bundler_analysis($filename = '', $force = FALSE) { // didn't give us anything. return 0; } + +/** + * Run the analysis query and return the analysis array. + * + * "Magic Query"; only needs to run once. Results are cached. + * This is what the raw SQL looks like: + * + * @code + * 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 + * @endcode + * + * @param bool $safesql + * Turn off SQL language that might cause errors. + * + * @return array + * The analysis array. + */ +function advagg_bundler_analyisis_query($safesql) { + // 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_critical_css/advagg_critical_css.admin.inc b/sites/all/modules/contrib/advagg/advagg_critical_css/advagg_critical_css.admin.inc new file mode 100644 index 0000000000..c9cae0350e --- /dev/null +++ b/sites/all/modules/contrib/advagg/advagg_critical_css/advagg_critical_css.admin.inc @@ -0,0 +1,381 @@ +<?php + +/** + * @file + * Admin page callbacks for the advagg critical css module. + */ + +/** + * Form builder; Configure advagg settings. + * + * @ingroup advagg_forms + * + * @see system_settings_form() + */ +function advagg_critical_css_admin_settings_form() { + drupal_set_title(t('AdvAgg: Critical CSS')); + advagg_display_message_if_requirements_not_met(); + + $form = array(); + + $default_theme = variable_get('theme_default', 'bartik'); + $global_theme = $GLOBALS['theme']; + $themes = array_keys(list_themes()); + + $form['#attached']['css'][] = array( + 'data' => ".form-item-lookup{padding-bottom:0;margin-bottom:0;}", + 'type' => 'inline', + ); + $form['add_item']['theme'] = array( + '#type' => 'select', + '#title' => t('Theme'), + '#options' => array_combine($themes, $themes), + '#default_value' => $default_theme, + '#description' => t('Theme Default: %default, Current Theme: %current', array( + '%default' => $default_theme, + '%current' => $global_theme, + )), + ); + $form['add_item']['user'] = array( + '#type' => 'select', + '#title' => t('User type'), + '#default_value' => 0, + '#options' => array( + 'anonymous' => t('anonymous'), + 'authenticated' => t('authenticated'), + 'all' => t('all'), + ), + ); + $type_options = array( + 0 => t('Disabled'), + 2 => t('URL'), + 8 => t('Node Type'), + ); + $form['add_item']['type'] = array( + '#type' => 'select', + '#title' => t('Type of lookup'), + '#default_value' => 2, + '#options' => $type_options, + ); + + $form['add_item']['lookup'] = array( + '#type' => 'textfield', + '#title' => t('Value to lookup'), + '#maxlength' => 255, + '#states' => array( + 'disabled' => array( + ':input[name="type"]' => array('value' => 0), + ), + ), + ); + $form['add_item']['lookup_container_disabled'] = array( + '#type' => 'container', + '#states' => array( + 'visible' => array( + ':input[name="type"]' => array('value' => 0), + ), + ), + ); + $form['add_item']['lookup_container_disabled']['disabled'] = array( + '#markup' => '<br>', + ); + $form['add_item']['lookup_container_current_path'] = array( + '#type' => 'container', + '#states' => array( + 'visible' => array( + ':input[name="type"]' => array('value' => 2), + ), + ), + ); + $form['add_item']['lookup_container_current_path']['current_path'] = array( + '#markup' => t('%front is the front page; can use internal URLs like %internal or an alias like %here', array( + '%front' => '<front>', + '%internal' => 'node/2', + '%here' => current_path(), + )), + ); + $form['add_item']['lookup_container_node_type'] = array( + '#type' => 'container', + '#states' => array( + 'visible' => array( + ':input[name="type"]' => array('value' => 8), + ), + ), + ); + $form['add_item']['lookup_container_node_type']['node_type'] = array( + '#markup' => t('Node type is the machine name of the node; list of node types: @node_types', array( + '@current_path' => 'https://api.drupal.org/api/drupal/includes%21path.inc/function/current_path/7.x', + '@request_path' => 'https://api.drupal.org/api/drupal/includes%21bootstrap.inc/function/request_path/7.x', + '@request_uri' => 'https://api.drupal.org/api/drupal/includes%21bootstrap.inc/function/request_uri/7.x', + '@node_types' => implode(', ', array_keys(node_type_get_names())), + )), + ); + + $form['add_item']['css'] = array( + '#type' => 'textarea', + '#title' => t('Critical CSS'), + '#description' => t('Can be generated via <a href="@url">https://www.sitelocity.com/critical-path-css-generator</a>. If this field is empty this entry will be deleted.', array( + '@url' => 'https://www.sitelocity.com/critical-path-css-generator', + )), + '#default_value' => '', + ); + $form['add_item']['dns'] = array( + '#type' => 'textarea', + '#title' => t('Hostnames to lookup'), + '#description' => t('Hosts that will be connected to.'), + '#default_value' => '', + ); + $form['add_item']['pre'] = array( + '#type' => 'textarea', + '#title' => t('Urls to Preload'), + '#description' => t('Assets for the browser that should be downloaded at a high priority.'), + '#default_value' => '', + ); + + // Lookup saved data. + $query = db_select('advagg_critical_css', 'acc') + ->fields('acc') + ->comment('Query called from ' . __FUNCTION__ . '()'); + $results = $query->execute(); + // Put results into array. + $counter = 0; + foreach ($results as $row) { + $counter++; + $row = (array) $row; + + foreach ($form['add_item'] as $key => $values) { + // Fix the states array for type. + if (!empty($values['#states'])) { + foreach ($values['#states'] as $states_key => $states_values) { + $states_value = reset($values['#states'][$states_key]); + $values['#states'][$states_key] = array(":input[name=\"{$counter}_type\"]" => $states_value); + } + } + $form['existing_items'][$counter]["{$counter}_{$key}"] = $values; + if (isset($row[$key])) { + $form['existing_items'][$counter]["{$counter}_{$key}"]['#default_value'] = $row[$key]; + } + } + + // Add in css to move the text hint up. + $form['#attached']['css'][] = array( + 'data' => ".form-item-{$counter}-lookup{padding-bottom:0;margin-bottom:0;}", + 'type' => 'inline', + ); + + // Add fieldset. + $filename = advagg_url_to_filename($row['lookup'], FALSE); + $base = drupal_get_path('theme', $row['theme']) . "/critical-css/{$row['user']}/"; + if ($row['type'] == 2) { + $base .= "urls/$filename"; + } + elseif ($row['type'] == 8) { + $base .= "type/$filename"; + } + else { + $base = ''; + } + $form['existing_items'][$counter] += array( + '#type' => 'fieldset', + '#collapsible' => TRUE, + '#collapsed' => TRUE, + '#title' => t('@type @theme @user @lookup', array( + '@theme' => $row['theme'], + '@type' => $type_options[$row['type']], + '@user' => $row['user'], + '@lookup' => $row['lookup'], + )), + ); + + if (!empty($base)) { + $form['existing_items'][$counter]['#description'] = t('If you wish to store this configuration in a file<br>Critical CSS: <code>@css</code>', array( + '@css' => "$base.css", + )); + if (!empty($row['dns'])) { + $form['existing_items'][$counter]['#description'] .= t('<br>Hostnames: <code>@dns</code>', array( + '@dns' => "$base.dns", + )); + } + if (!empty($row['pre'])) { + $form['existing_items'][$counter]['#description'] .= t('<br>Preload: <code>@pre</code>', array( + '@pre' => "$base.pre", + )); + } + } + } + + // Add top level fieldsets. + $form['add_item'] += array( + '#type' => 'fieldset', + '#title' => t('Add Critical CSS'), + '#collapsible' => TRUE, + '#collapsed' => $results->rowCount(), + ); + if (!empty($form['existing_items'])) { + $form['existing_items'] += array( + '#type' => 'fieldset', + '#title' => t('Edit Critical CSS'), + ); + } + + $form['advagg_critical_css_selector_blacklist'] = array( + '#type' => 'textarea', + '#title' => t('Selector Blacklist'), + '#description' => t('Selectors to exclude. Enter one per line. Useful for things like google ads.'), + '#default_value' => variable_get('advagg_critical_css_selector_blacklist', ''), + ); + + // Clear the cache bins on submit. + $form['#submit'][] = 'advagg_critical_css_admin_settings_form_submit'; + + // Most code below taken from system_settings_form(). + $form['actions']['#type'] = 'actions'; + $form['actions']['submit'] = array( + '#type' => 'submit', + '#value' => t('Save configuration'), + ); + $form['actions']['disable'] = array( + '#type' => 'submit', + '#value' => t('Disable All From Database'), + '#submit' => array('advagg_critical_css_admin_settings_form_submit_disable'), + ); + if (!empty($_POST) && form_get_errors()) { + drupal_set_message(t('The settings have not been saved because of the errors.'), 'error'); + } + // By default, render the form using theme_system_settings_form(). + if (!isset($form['#theme'])) { + $form['#theme'] = 'system_settings_form'; + } + return $form; +} + +/** + * Submit callback, process the advagg_critical_css form. + * + * Also clear out the advagg cache bin. + * + * @ingroup advagg_forms_callback + */ +function advagg_critical_css_admin_settings_form_submit_disable($form, &$form_state) { + $query = db_select('advagg_critical_css', 'acc') + ->fields('acc') + ->comment('Query called from ' . __FUNCTION__ . '()'); + $results = $query->execute(); + // Put results into array. + $insert_update = array(); + foreach ($results as $row) { + $row = (array) $row; + $new_row = $row; + $new_row['type'] = 0; + $insert_update[] = array( + $row, + $new_row, + ); + } + advagg_critical_css_table_insert_update($insert_update); +} + +/** + * Submit callback, process the advagg_critical_css form. + * + * Also clear out the advagg cache bin. + * + * @ingroup advagg_forms_callback + */ +function advagg_critical_css_admin_settings_form_submit($form, &$form_state) { + // Exclude unnecessary elements. + form_state_values_clean($form_state); + + // Save advagg_critical_css_selector_blacklist. + if (!isset($form_state['values']['advagg_critical_css_selector_blacklist'])) { + $form_state['values']['advagg_critical_css_selector_blacklist'] = ''; + } + $advagg_critical_css_selector_blacklist = variable_get('advagg_critical_css_selector_blacklist', ''); + if ($form_state['values']['advagg_critical_css_selector_blacklist'] !== $advagg_critical_css_selector_blacklist) { + variable_set('advagg_critical_css_selector_blacklist', $form_state['values']['advagg_critical_css_selector_blacklist']); + } + unset($form_state['values']['advagg_critical_css_selector_blacklist']); + + // Rearrange form values into key value pairs. + $items = advagg_critical_css_get_rows_from_form($form_state['values']); + // Get default values. + $default_values = advagg_find_all_recommended_admin_values($form_state['complete form'], '#default_value'); + unset($default_values['form_token']); + $default_items = advagg_critical_css_get_rows_from_form($default_values); + + // Get diff, see what items need to be saved. + $diff = advagg_diff_multi($default_items, $items); + $changed_items = array(); + foreach ($diff as $key => $values) { + $changed_items[$key] = $items[$key]; + } + + // Get items to insert/update and delete. + list($insert_update, $delete) = advagg_critical_css_get_db_operations_arrays($changed_items, $default_items); + advagg_critical_css_table_insert_update($insert_update); + advagg_critical_css_table_delete($delete); + + // Clear caches. + advagg_cache_clear_admin_submit(); + drupal_set_message(t('The configuration options have been saved.')); +} + +/** + * Translate from state values into a nested array strucutre. + * + * @param array $form_state_values + * From state values; from $form_state['values']. + * + * @return array + * Nested array strucutre, each index is a row in the db. + */ +function advagg_critical_css_get_rows_from_form(array $form_state_values) { + $items = array(); + $counter = 0; + foreach ($form_state_values as $key => $values) { + // Get the index from the start of the form name. + $matches = array(); + // 1_type turns into $counter = 1 and $key = type. + preg_match('/^(\d)_(.*)/', $key, $matches); + if (!empty($matches)) { + $counter = $matches[1]; + $key = $matches[2]; + } + $items[$counter][$key] = $values; + } + return $items; +} + +/** + * Given a list of items see what ones need to be inserted/updated or deleted. + * + * @param array $items + * Array of values, representing a row in the db. + * + * @return array + * Nested array strucutre, index 0 is the insert update, 1 is the deleted. + */ +function advagg_critical_css_get_db_operations_arrays(array $items, array $old_items) { + $insert_update = array(); + $delete = array(); + foreach ($items as $key => $values) { + // If the css is empty then this needs to be deleted. + if (empty($values['css'])) { + // Do not delete the new items entry (0); it's not in the db currently. + if (!empty($key)) { + $delete[$key] = $values; + } + } + else { + // Pass along the old key value pairs for db_merge. + if (!empty($old_items[$key])) { + $keys = $old_items[$key] + $values; + } + else { + $keys = $values; + } + $insert_update[$key] = array($keys, $values); + } + } + return array($insert_update, $delete); +} diff --git a/sites/all/modules/contrib/advagg/advagg_critical_css/advagg_critical_css.info b/sites/all/modules/contrib/advagg/advagg_critical_css/advagg_critical_css.info new file mode 100644 index 0000000000..a6a78db0aa --- /dev/null +++ b/sites/all/modules/contrib/advagg/advagg_critical_css/advagg_critical_css.info @@ -0,0 +1,14 @@ +name = AdvAgg Critical CSS +description = Control Critical CSS via UI and/or a 3rd party service. +package = Advanced CSS/JS Aggregation +core = 7.x +dependencies[] = advagg +dependencies[] = advagg_mod + +configure = admin/config/development/performance/advagg/critical-css + +; Information added by Drupal.org packaging script on 2020-11-19 +version = "7.x-2.35" +core = "7.x" +project = "advagg" +datestamp = "1605792717" diff --git a/sites/all/modules/contrib/advagg/advagg_critical_css/advagg_critical_css.install b/sites/all/modules/contrib/advagg/advagg_critical_css/advagg_critical_css.install new file mode 100644 index 0000000000..98c954d6f7 --- /dev/null +++ b/sites/all/modules/contrib/advagg/advagg_critical_css/advagg_critical_css.install @@ -0,0 +1,84 @@ +<?php + +/** + * @file + * Handles AdvAgg Critical CSS installation and upgrade tasks. + */ + +/** + * @addtogroup hooks + * @{ + */ + +/** + * Implements hook_schema(). + */ +function advagg_critical_css_schema() { + // Create database table. + $schema['advagg_critical_css'] = array( + 'description' => 'The critical css to inline.', + 'fields' => array( + 'theme' => array( + 'description' => 'The theme name.', + 'type' => 'varchar', + 'length' => 255, + 'default' => '', + 'binary' => TRUE, + ), + 'type' => array( + 'description' => 'Type like url or node.', + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + ), + 'user' => array( + 'description' => 'User Type or UID.', + 'type' => 'varchar', + 'length' => 255, + 'default' => '', + 'binary' => TRUE, + ), + 'lookup' => array( + 'description' => 'Value from current_path if url or node type if node.', + 'type' => 'varchar', + 'length' => 255, + 'default' => '', + 'binary' => TRUE, + ), + 'css' => array( + 'description' => 'Critical CSS.', + 'type' => 'blob', + 'size' => 'big', + ), + 'dns' => array( + 'description' => 'Hosts for dns lookedup.', + 'type' => 'blob', + 'size' => 'big', + ), + 'pre' => array( + 'description' => 'URLs for preloading.', + 'type' => 'blob', + 'size' => 'big', + ), + 'settings' => array( + 'description' => 'Extra settings if desired.', + 'type' => 'blob', + 'size' => 'big', + 'translatable' => TRUE, + 'serialize' => TRUE, + ), + ), + 'primary key' => array( + 'lookup', + 'user', + 'type', + 'theme', + ), + ); + + return $schema; +} + +/** + * @} End of "addtogroup hooks". + */ diff --git a/sites/all/modules/contrib/advagg/advagg_critical_css/advagg_critical_css.module b/sites/all/modules/contrib/advagg/advagg_critical_css/advagg_critical_css.module new file mode 100644 index 0000000000..6bd3c57271 --- /dev/null +++ b/sites/all/modules/contrib/advagg/advagg_critical_css/advagg_critical_css.module @@ -0,0 +1,239 @@ +<?php + +/** + * @file + * Advanced aggregation critical css module. + */ + +/** + * @addtogroup hooks + * @{ + */ + +/** + * Implements hook_menu(). + */ +function advagg_critical_css_menu() { + $file_path = drupal_get_path('module', 'advagg_critical_css'); + $config_path = advagg_admin_config_root_path(); + + $items[$config_path . '/advagg/critical-css'] = array( + 'title' => 'Critical CSS', + 'description' => 'Control critical css.', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('advagg_critical_css_admin_settings_form'), + 'type' => MENU_LOCAL_TASK, + 'access arguments' => array('administer site configuration'), + 'file path' => $file_path, + 'file' => 'advagg_critical_css.admin.inc', + 'weight' => 10, + ); + + return $items; +} + +/** + * Implements hook_module_implements_alter(). + */ +function advagg_critical_css_module_implements_alter(&$implementations, $hook) { + // Move critical_css_advagg_mod_critical_css_file_pre_alter to the bottom. + if ($hook === 'critical_css_advagg_mod_critical_css_file_pre_alter' && array_key_exists('advagg_critical_css', $implementations)) { + $item = $implementations['advagg_critical_css']; + unset($implementations['advagg_critical_css']); + $implementations['advagg_critical_css'] = $item; + } +} + +/** + * @} End of "addtogroup hooks". + */ + +/** + * @addtogroup advagg_hooks + * @{ + */ + +/** + * Implements hook_advagg_mod_critical_css_file_pre_alter(). + */ +function advagg_critical_css_advagg_mod_critical_css_file_pre_alter(&$filename, &$params, &$inline_strings) { + list($dirs, $front_page, $object) = $params; + + // Build query parameters. + $lookup = array($dirs[6]); + if ($front_page) { + $lookup = array('<front>'); + } + $lookup[] = $dirs[9]; + $lookup[] = $dirs[10]; + if (!empty($object->type)) { + $lookup[] = $object->type; + } + $type = array(2, 8); + $users = array(rtrim($dirs[2], '/\\'), rtrim($dirs[3], '/\\')); + + // Get Results. + $result = advagg_critical_css_table_get($GLOBALS['theme'], $type, $lookup, $users); + + // Put into the inline strings array. + if (!empty($result)) { + // Set string values. + $inline_strings[0] = $result['css']; + $inline_strings[1] = $result['dns']; + $inline_strings[2] = $result['pre']; + // Disable file lookup. + $dirs[0] = ''; + $dirs[1] = ''; + } + + // Repack the $params array. + $params = array($dirs, $front_page, $object); +} + +/** + * Implements hook_advagg_mod_critical_css_file_post_alter(). + */ +function advagg_critical_css_advagg_mod_critical_css_file_post_alter(&$filename, &$params, &$inline_strings) { + if (!empty($inline_strings[0])) { + // Remove given css selectors. + $selectors = variable_get('advagg_critical_css_selector_blacklist', ''); + $selectors_array = array_filter(array_map('trim', explode("\n", $selectors))); + foreach ($selectors_array as $pattern) { + $pattern = preg_quote($pattern, '/'); + $pattern = "/([^}]*{$pattern}[^{]*[^}]*\})/s"; + $inline_strings[0] = preg_replace($pattern, '', $inline_strings[0]); + } + } +} + +/** + * @} End of "addtogroup advagg_hooks". + */ + +/** + * Get the db select return object. + * + * @param string $theme + * Name of the current theme. + * @param array $type + * Array of int types to lookup. + * @param array $lookup + * The lookup value. + * @param array $user + * Array of user string values. + * + * @return SelectQuery + * Return the SelectQuery object after it has been executed. + */ +function advagg_critical_css_table_get($theme, array $type, array $lookup, array $user) { + $output = array(); + try { + $results = db_select('advagg_critical_css', 'acc') + ->fields('acc') + ->condition('theme', $theme) + ->condition('type', $type, 'IN') + ->condition('user', $user, 'IN') + ->condition('lookup', $lookup, 'IN') + ->orderBy('type', 'DESC') + ->execute(); + + // Get first result. + $output = $results->fetchAssoc(); + + // Check for a better match in other results if they exist. + foreach ($results as $values) { + $values = (array) $values; + if ($values['type'] < $output['type']) { + $output = $values; + break; + } + if ($values['type'] = $output['type']) { + if (($values['user'] === 'anonymous' || $values['user'] === 'authenticated') + && $output['user'] === 'all' + ) { + $output = $values; + break; + } + if (is_int($values['user'])) { + $output = $values; + break; + } + } + } + } + catch (PDOException $e) { + // Log the error if in development mode. + if (variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL) < 0) { + watchdog('advagg_critical_css', 'Development Mode - Caught PDO Exception: <code>@info</code>', array('@info' => $e)); + } + } + + return $output; +} + +/** + * Insert/Update data in the advagg_critical_css table. + * + * @param array $records + * List of rows needed that need to be changed in the db. + * + * @return array + * Return array of booleans if anything was written to the database. + */ +function advagg_critical_css_table_insert_update(array $records) { + $return = array(); + foreach ($records as $values) { + list($keys, $record) = $values; + if (!isset($record['settings'])) { + $record['settings'] = ''; + } + try { + $return[] = db_merge('advagg_critical_css') + ->key(array( + 'theme' => $keys['theme'], + 'user' => $keys['user'], + 'type' => $keys['type'], + 'lookup' => $keys['lookup'], + )) + ->fields($record) + ->execute(); + } + catch (PDOException $e) { + // Log the error if in development mode. + if (variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL) < 0) { + watchdog('advagg_critical_css', 'Development Mode - Caught PDO Exception: <code>@info</code>', array('@info' => $e)); + } + } + } + return $return; +} + +/** + * Delete data in the advagg_critical_css table. + * + * @param array $records + * List of rows needed that need to be removed from the db. + * + * @return array + * Return array of booleans if anything was removed from the database. + */ +function advagg_critical_css_table_delete(array $records) { + $return = array(); + foreach ($records as $record) { + try { + $return[] = db_delete('advagg_critical_css') + ->condition('theme', $record['theme']) + ->condition('user', $record['user']) + ->condition('type', $record['type']) + ->condition('lookup', $record['lookup']) + ->execute(); + } + catch (PDOException $e) { + // Log the error if in development mode. + if (variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL) < 0) { + watchdog('advagg_critical_css', 'Development Mode - Caught PDO Exception: <code>@info</code>', array('@info' => $e)); + } + } + } + return $return; +} 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 0813467169..e6e0319a97 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,8 @@ package = Advanced CSS/JS Aggregation core = 7.x dependencies[] = advagg -; Information added by Drupal.org packaging script on 2015-04-14 -version = "7.x-2.8" +; Information added by Drupal.org packaging script on 2020-11-19 +version = "7.x-2.35" core = "7.x" project = "advagg" -datestamp = "1429049283" - +datestamp = "1605792717" diff --git a/sites/all/modules/contrib/advagg/advagg_css_cdn/advagg_css_cdn.install b/sites/all/modules/contrib/advagg/advagg_css_cdn/advagg_css_cdn.install index 64c31a8c29..213c2599cf 100644 --- a/sites/all/modules/contrib/advagg/advagg_css_cdn/advagg_css_cdn.install +++ b/sites/all/modules/contrib/advagg/advagg_css_cdn/advagg_css_cdn.install @@ -5,6 +5,11 @@ * Handles Advanced Aggregation installation and upgrade tasks. */ +/** + * @addtogroup hooks + * @{ + */ + /** * Implements hook_requirements(). */ @@ -54,3 +59,7 @@ function advagg_css_cdn_requirements($phase) { return $requirements; } + +/** + * @} End of "addtogroup hooks". + */ 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 3984d00745..bd6cd42837 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 @@ -5,6 +5,11 @@ * Advanced aggregation js cdn module. */ +/** + * @addtogroup default_variables + * @{ + */ + /** * Default value to see if jquery-ui should be grabbed from the Google CDN. */ @@ -15,11 +20,20 @@ define('ADVAGG_CSS_CDN_JQUERY_UI', TRUE); */ define('ADVAGG_CSS_CDN_JQUERY_UI_VERSION', '1.8.7'); +/** + * @} End of "addtogroup default_variables". + */ + +/** + * @addtogroup hooks + * @{ + */ + /** * 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; } @@ -30,45 +44,31 @@ function advagg_css_cdn_css_alter(&$css) { $ui_mapping = advagg_css_cdn_get_ui_mapping(); foreach ($css as $name => $values) { - // @ignore sniffer_commenting_inlinecomment_spacingbefore:4 - // @ignore sniffer_whitespace_openbracketspacing_openingwhitespace:8 // Only modify if // advagg_css_cdn_jquery_ui is enabled, // name is in the $ui_mapping array. // and type is file. - if ( variable_get('advagg_css_cdn_jquery_ui', ADVAGG_CSS_CDN_JQUERY_UI) + if (variable_get('advagg_css_cdn_jquery_ui', ADVAGG_CSS_CDN_JQUERY_UI) && array_key_exists($name, $ui_mapping) && $css[$name]['type'] === 'file' ) { $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.". } } } /** - * Return an array of jquery ui files. + * @} End of "addtogroup hooks". + */ + +/** + * @addtogroup advagg_hooks + * @{ */ -function advagg_css_cdn_get_ui_mapping() { - // Replace jQuery UI's CSS, beginning by defining the mapping. - $ui_mapping = array( - 'misc/ui/jquery.ui.accordion.css' => 'ui.accordion', - 'misc/ui/jquery.ui.autocomplete.css' => 'ui.autocomplete', - 'misc/ui/jquery.ui.button.css' => 'ui.button', - 'misc/ui/jquery.ui.core.css' => 'ui.core', - 'misc/ui/jquery.ui.datepicker.css' => 'ui.datepicker', - 'misc/ui/jquery.ui.dialog.css' => 'ui.dialog', - 'misc/ui/jquery.ui.progressbar.css' => 'ui.progressbar', - 'misc/ui/jquery.ui.resizable.css' => 'ui.resizable', - 'misc/ui/jquery.ui.selectable.css' => 'ui.selectable', - 'misc/ui/jquery.ui.slider.css' => 'ui.slider', - 'misc/ui/jquery.ui.tabs.css' => 'ui.tabs', - 'misc/ui/jquery.ui.theme.css' => 'ui.theme', - ); - return $ui_mapping; -} /** * Implements hook_advagg_css_groups_alter(). @@ -116,8 +116,7 @@ function advagg_css_cdn_advagg_css_groups_alter(&$css_groups, $preprocess_css) { } else { $diff = array_merge(array_diff_assoc($group['browsers'], $target['browsers']), array_diff_assoc($target['browsers'], $group['browsers'])); - // @ignore sniffer_whitespace_openbracketspacing_openingwhitespace:8 - if ( $group['type'] != $target['type'] + if ($group['type'] != $target['type'] || $group['group'] != $target['group'] || $group['every_page'] != $target['every_page'] || $group['media'] != $target['media'] @@ -127,8 +126,7 @@ function advagg_css_cdn_advagg_css_groups_alter(&$css_groups, $preprocess_css) { ) { if (!empty($last_group)) { $diff = array_merge(array_diff_assoc($last_group['browsers'], $target['browsers']), array_diff_assoc($target['browsers'], $last_group['browsers'])); - // @ignore sniffer_whitespace_openbracketspacing_openingwhitespace:8 - if ( $last_group['type'] != $target['type'] + if ($last_group['type'] != $target['type'] || $last_group['group'] != $target['group'] || $last_group['every_page'] != $target['every_page'] || $last_group['media'] != $target['media'] @@ -169,3 +167,29 @@ function advagg_css_cdn_advagg_css_groups_alter(&$css_groups, $preprocess_css) { } ksort($css_groups); } + +/** + * @} End of "addtogroup advagg_hooks". + */ + +/** + * Return an array of jquery ui files. + */ +function advagg_css_cdn_get_ui_mapping() { + // Replace jQuery UI's CSS, beginning by defining the mapping. + $ui_mapping = array( + 'misc/ui/jquery.ui.accordion.css' => 'ui.accordion', + 'misc/ui/jquery.ui.autocomplete.css' => 'ui.autocomplete', + 'misc/ui/jquery.ui.button.css' => 'ui.button', + 'misc/ui/jquery.ui.core.css' => 'ui.core', + 'misc/ui/jquery.ui.datepicker.css' => 'ui.datepicker', + 'misc/ui/jquery.ui.dialog.css' => 'ui.dialog', + 'misc/ui/jquery.ui.progressbar.css' => 'ui.progressbar', + 'misc/ui/jquery.ui.resizable.css' => 'ui.resizable', + 'misc/ui/jquery.ui.selectable.css' => 'ui.selectable', + 'misc/ui/jquery.ui.slider.css' => 'ui.slider', + 'misc/ui/jquery.ui.tabs.css' => 'ui.tabs', + 'misc/ui/jquery.ui.theme.css' => 'ui.theme', + ); + return $ui_mapping; +} 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 53e4af3d56..66e1a10769 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 @@ -8,12 +8,13 @@ /** * Form builder; Configure advagg settings. * - * @ingroup forms + * @ingroup advagg_forms * * @see system_settings_form() */ function advagg_css_compress_admin_settings_form($form, $form_state) { drupal_set_title(t('AdvAgg: CSS Compression Settings')); + advagg_display_message_if_requirements_not_met(); $config_path = advagg_admin_config_root_path(); $form = array(); @@ -23,11 +24,18 @@ function advagg_css_compress_admin_settings_form($form, $form_state) { ); } - $description = ''; - $options = array( - 0 => t('Disabled'), - 2 => t('YUI'), - ); + // Tell user to update library if a new version is available. + $library_name = 'YUI-CSS-compressor-PHP-port'; + $module_name = 'advagg_css_compress'; + list($description) = advagg_get_version_description($library_name, $module_name); + if (!empty($description)) { + $form['advagg_version_msg'] = array( + '#markup' => "<p>{$description}</p>", + ); + } + + list($options, $description) = advagg_css_compress_configuration(); + $form['advagg_css_compressor'] = array( '#type' => 'radios', '#title' => t('File Compression: Select a Compressor'), @@ -35,11 +43,14 @@ function advagg_css_compress_admin_settings_form($form, $form_state) { '#options' => $options, '#description' => filter_xss($description), ); + $inline_options = $options; + unset($inline_options[-1]); + $inline_options[0] = t('Disabled'); $form['advagg_css_compress_inline'] = array( '#type' => 'radios', '#title' => t('Inline Compression: Select a Compressor'), '#default_value' => variable_get('advagg_css_compress_inline', ADVAGG_CSS_COMPRESS_INLINE), - '#options' => $options, + '#options' => $inline_options, '#description' => filter_xss($description), ); $form['advagg_css_compress_inline_if_not_cacheable'] = array( @@ -54,7 +65,7 @@ function advagg_css_compress_admin_settings_form($form, $form_state) { ), ); - $options[-1] = t('Default'); + $options[ADVAGG_CSS_COMPRESSOR_FILE_SETTINGS] = t('Default'); ksort($options); $form['per_file_settings'] = array( @@ -63,11 +74,11 @@ function advagg_css_compress_admin_settings_form($form, $form_state) { '#collapsible' => TRUE, '#collapsed' => TRUE, ); - // Get filename & filename_hash. + // Get filename and filename_hash. $results = db_select('advagg_files', 'af') ->fields('af', array('filename')) ->condition('filetype', 'css') - ->orderBy('af.filename', 'ASC') + ->orderBy('filename', 'ASC') ->execute(); $file_settings = variable_get('advagg_css_compressor_file_settings', array()); foreach ($results as $row) { @@ -93,23 +104,27 @@ 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'; return system_settings_form($form); } -// Submit callback. /** - * Clear out the advagg cache bin when the save configuration button is pressed. + * Submit callback that clears out the advagg cache bin. * * Also remove default settings inside of the per_file_settings fieldgroup. + * + * @ingroup advagg_forms_callback */ 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 +132,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; } @@ -127,6 +142,9 @@ function advagg_css_compress_admin_settings_form_submit($form, &$form_state) { continue; } $new_settings[substr($key, 36)] = $value; + + // Do not save this field into its own variable. + unset($form_state['values'][$key]); } if (!empty($new_settings) || !empty($file_settings)) { if (empty($new_settings)) { diff --git a/sites/all/modules/contrib/advagg/advagg_css_compress/advagg_css_compress.advagg.inc b/sites/all/modules/contrib/advagg/advagg_css_compress/advagg_css_compress.advagg.inc index cfd7454716..d152670a4e 100644 --- a/sites/all/modules/contrib/advagg/advagg_css_compress/advagg_css_compress.advagg.inc +++ b/sites/all/modules/contrib/advagg/advagg_css_compress/advagg_css_compress.advagg.inc @@ -5,6 +5,28 @@ * Advanced aggregation css compression module. */ +/** + * @addtogroup advagg_hooks + * @{ + */ + +/** + * Implements hook_advagg_get_css_file_contents_pre_alter(). + */ +function advagg_css_compress_advagg_get_css_file_contents_pre_alter(&$file, &$optimize, &$aggregate_settings) { + // Get per file settings. + if (!empty($aggregate_settings['variables']['advagg_css_compressor_file_settings'])) { + $form_api_filename = str_replace(array('/', '.'), array('__', '--'), $file); + if (isset($aggregate_settings['variables']['advagg_css_compressor_file_settings'][$form_api_filename])) { + $aggregate_settings['variables']['advagg_css_compressor'] = $aggregate_settings['variables']['advagg_css_compressor_file_settings'][$form_api_filename]; + } + } + + if (isset($aggregate_settings['variables']['advagg_css_compressor']) && $aggregate_settings['variables']['advagg_css_compressor'] == -1) { + $optimize = FALSE; + } +} + /** * Implements hook_advagg_get_css_aggregate_contents_alter(). */ @@ -33,21 +55,56 @@ function advagg_css_compress_advagg_get_css_aggregate_contents_alter(&$data, $fi return; } - if ($aggregate_settings['variables']['advagg_css_compressor'] == 2) { - advagg_css_compress_yui_cssmin($data); + list(, , , $functions) = advagg_css_compress_configuration(); + + if (isset($functions[$aggregate_settings['variables']['advagg_css_compressor']])) { + $run = $functions[$aggregate_settings['variables']['advagg_css_compressor']]; + if (function_exists($run)) { + $functions[$aggregate_settings['variables']['advagg_css_compressor']]($data); + } } } +/** + * @} End of "addtogroup advagg_hooks". + */ + /** * Use the CSSmin library from YUI to compress the CSS. */ function advagg_css_compress_yui_cssmin(&$data) { - // Only include CSSMin.inc if the CSSmin class doesn't exist. - if (!class_exists('CSSmin')) { - include drupal_get_path('module', 'advagg_css_compress') . '/yui/CSSMin.inc'; + // Try libraries for YUI. + if (is_callable('libraries_load')) { + libraries_load('YUI-CSS-compressor-PHP-port'); + if (class_exists('tubalmartin\CssMin\Minifier')) { + // The "use" alias requires php 5.3. + // @codingStandardsIgnoreLine + $cssmin = new tubalmartin\CssMin\Minifier(); + } + elseif (class_exists('CSSmin')) { + $cssmin = new CSSmin(); + } + } + if (!isset($cssmin)) { + // Load CSSMin.inc if the CSSmin class variable is not set. + if (!class_exists('CSSmin')) { + include drupal_get_path('module', 'advagg_css_compress') . '/yui/CSSMin.inc'; + } + $cssmin = new CSSmin(); + } + if (!isset($cssmin)) { + return; } - $cssmin = new CSSmin(FALSE); + // Set line break to 4k of text. + if (method_exists($cssmin, 'setLineBreakPosition')) { + $cssmin->setLineBreakPosition(4096); + } // Compress the CSS splitting lines after 4k of text. - $data = $cssmin->run($data, 4096); + if (method_exists($cssmin, 'run')) { + $compressed = $cssmin->run($data, 4096); + } + if (!empty($compressed)) { + $data = $compressed; + } } 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 bf818c3619..aba0cc76ee 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 @@ -3,12 +3,12 @@ description = Compress CSS with a 3rd party compressor, YUI currently. package = Advanced CSS/JS Aggregation core = 7.x dependencies[] = advagg +recommends[] = libraries configure = admin/config/development/performance/advagg/css-compress -; Information added by Drupal.org packaging script on 2015-04-14 -version = "7.x-2.8" +; Information added by Drupal.org packaging script on 2020-11-19 +version = "7.x-2.35" core = "7.x" project = "advagg" -datestamp = "1429049283" - +datestamp = "1605792717" diff --git a/sites/all/modules/contrib/advagg/advagg_css_compress/advagg_css_compress.install b/sites/all/modules/contrib/advagg/advagg_css_compress/advagg_css_compress.install index 7521f21975..b4daf3f6bb 100644 --- a/sites/all/modules/contrib/advagg/advagg_css_compress/advagg_css_compress.install +++ b/sites/all/modules/contrib/advagg/advagg_css_compress/advagg_css_compress.install @@ -5,6 +5,11 @@ * Handles AdvAgg CSS compress installation and upgrade tasks. */ +/** + * @addtogroup hooks + * @{ + */ + /** * Implements hook_requirements(). */ @@ -19,8 +24,7 @@ function advagg_css_compress_requirements($phase) { } // Make sure a compressor is being used. - // @ignore sniffer_whitespace_openbracketspacing_openingwhitespace - if ( variable_get('advagg_css_compressor', ADVAGG_CSS_COMPRESSOR) == 0 + if (variable_get('advagg_css_compressor', ADVAGG_CSS_COMPRESSOR) == 0 && variable_get('advagg_css_compress_inline', ADVAGG_CSS_COMPRESS_INLINE) == 0 ) { // Check all files. @@ -48,13 +52,25 @@ function advagg_css_compress_requirements($phase) { ); } } + + // Check version. + $lib_name = 'YUI-CSS-compressor-PHP-port'; + $module_name = 'advagg_css_compress'; + list($description, $info) = advagg_get_version_description($lib_name, $module_name); + if (!empty($description)) { + $requirements["{$module_name}_{$lib_name}_updates"] = array( + 'title' => $t('@module_name', array('@module_name' => $info['name'])), + 'severity' => REQUIREMENT_WARNING, + 'value' => $t('The @name library needs to be updated.', array('@name' => $lib_name)), + 'description' => $description, + ); + } + return $requirements; } /** - * Upgrade AdvAgg CSS Compress previous versions (6.x-1.x & 7.x-1.x) to 7.x-2.x. - * - * Implements hook_update_N(). + * Upgrade AdvAgg CSS Compress versions (6.x-1.x and 7.x-1.x) to 7.x-2.x. */ function advagg_css_compress_update_7200(&$sandbox) { // Bail if old DB Table does not exist. @@ -75,8 +91,6 @@ function advagg_css_compress_update_7200(&$sandbox) { /** * Change variable names so they are prefixed with the modules name. - * - * Implements hook_update_N(). */ function advagg_css_compress_update_7201(&$sandbox) { // Rename advagg_css_inline_compressor to advagg_css_compress_inline. @@ -98,3 +112,17 @@ function advagg_css_compress_update_7201(&$sandbox) { variable_set('advagg_css_compress_inline_if_not_cacheable', $old); } } + +/** + * Remove unused variables from the variable table. + */ +function advagg_css_compress_update_7202(&$sandbox) { + // Remove all old advagg css compress variables. + db_delete('variable') + ->condition('name', 'advagg_css_compressor_file_settings_%', 'LIKE') + ->execute(); +} + +/** + * @} End of "addtogroup hooks". + */ diff --git a/sites/all/modules/contrib/advagg/advagg_css_compress/advagg_css_compress.module b/sites/all/modules/contrib/advagg/advagg_css_compress/advagg_css_compress.module index 2eeb714f15..5f6fbb2315 100644 --- a/sites/all/modules/contrib/advagg/advagg_css_compress/advagg_css_compress.module +++ b/sites/all/modules/contrib/advagg/advagg_css_compress/advagg_css_compress.module @@ -5,6 +5,11 @@ * Advanced aggregation css compression module. */ +/** + * @addtogroup default_variables + * @{ + */ + /** * Default value for which css compression library to use. 0 is Disabled. */ @@ -23,7 +28,16 @@ define('ADVAGG_CSS_COMPRESS_INLINE_IF_NOT_CACHEABLE', FALSE); /** * Default value for per file compression settings. */ -define('ADVAGG_CSS_COMPRESSOR_FILE_SETTINGS', -1); +define('ADVAGG_CSS_COMPRESSOR_FILE_SETTINGS', -10); + +/** + * @} End of "addtogroup default_variables". + */ + +/** + * @addtogroup hooks + * @{ + */ /** * Implements hook_menu(). @@ -47,6 +61,15 @@ function advagg_css_compress_menu() { return $items; } +/** + * @} End of "addtogroup hooks". + */ + +/** + * @addtogroup advagg_hooks + * @{ + */ + /** * Implements hook_advagg_current_hooks_hash_array_alter(). */ @@ -86,3 +109,123 @@ function advagg_css_compress_advagg_modify_css_pre_render_alter(&$children, &$el unset($values); } } + +/** + * @} End of "addtogroup advagg_hooks". + */ + +/** + * @addtogroup 3rd_party_hooks + * @{ + */ + +/** + * Implements hook_libraries_info(). + */ +function advagg_css_compress_libraries_info() { + $libraries['YUI-CSS-compressor-PHP-port'] = array( + // Only used in administrative UI of Libraries API. + 'name' => 'YUI CSS compressor PHP port', + 'vendor url' => 'https://github.com/tubalmartin/YUI-CSS-compressor-PHP-port', + 'download url' => 'https://github.com/tubalmartin/YUI-CSS-compressor-PHP-port/archive/master.zip', + 'version callback' => 'advagg_css_compress_libraries_get_version', + 'version arguments' => array( + 'file' => 'README.md', + 'pattern' => '/###\s+v([0-9a-zA-Z\.-]+)/', + 'lines' => 1000, + 'cols' => 20, + 'default_version' => '2.4.8', + ), + 'local version' => '2.4.8-p10', + 'remote' => array( + 'callback' => 'advagg_get_github_version_txt', + 'url' => 'https://cdn.jsdelivr.net/gh/tubalmartin/YUI-CSS-compressor-PHP-port@master/README.md', + ), + 'versions' => array( + '2' => array( + 'files' => array( + 'php' => array( + 'cssmin.php', + 'data/hex-to-named-color-map.php', + 'data/named-to-hex-color-map.php', + ), + ), + ), + '3' => array( + 'files' => array( + 'php' => array( + 'src/Minifier.php', + 'src/Utils.php', + 'src/Colors.php', + 'src/data/hex-to-named-color-map.php', + 'src/data/named-to-hex-color-map.php', + ), + ), + ), + '4' => array( + 'files' => array( + 'php' => array( + 'src/Minifier.php', + 'src/Utils.php', + 'src/Colors.php', + ), + ), + ), + ), + ); + + return $libraries; +} + +/** + * @} End of "addtogroup 3rd_party_hooks". + */ + +/** + * Try libraries_get_version(), on failure use the passed in default_version. + * + * @param array $library + * An associative array containing all information about the library. + * @param array $options + * An associative array containing options for the version parser. + * + * @return string + * Version number. + */ +function advagg_css_compress_libraries_get_version(array $library, array $options) { + $return = libraries_get_version($library, $options); + if (empty($return) && !empty($options['default_version'])) { + $file = DRUPAL_ROOT . '/' . $library['library path'] . '/' . $options['file']; + if (is_readable($file)) { + return $options['default_version']; + } + } + return $return; +} + +/** + * Generate the js compress configuration. + * + * @return array + * Array($options, $description, $compressors, $functions). + */ +function advagg_css_compress_configuration() { + $description = ''; + $options = array( + -1 => t('Disable Core'), + 0 => t('Core'), + 2 => t('YUI'), + ); + + $compressors = array(); + $functions = array( + 2 => 'advagg_css_compress_yui_cssmin', + ); + + // Allow for other modules to alter this list. + $options_desc = array($options, $description); + drupal_alter('advagg_css_compress_configuration', $options_desc, $compressors, $functions); + list($options, $description) = $options_desc; + + return array($options, $description, $compressors, $functions); +} 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 c0c9bc378d..88df451301 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 @@ -15,7 +15,7 @@ // @ignore :file /*! - * cssmin.php v2.4.8-4 + * cssmin.php * Author: Tubal Martin - http://tubalmartin.me/ * Repo: https://github.com/tubalmartin/YUI-CSS-compressor-PHP-port * @@ -37,130 +37,137 @@ class CSSmin { const NL = '___YUICSSMIN_PRESERVED_NL___'; - const TOKEN = '___YUICSSMIN_PRESERVED_TOKEN_'; - const COMMENT = '___YUICSSMIN_PRESERVE_CANDIDATE_COMMENT_'; const CLASSCOLON = '___YUICSSMIN_PSEUDOCLASSCOLON___'; const QUERY_FRACTION = '___YUICSSMIN_QUERY_FRACTION___'; + const TOKEN = '___YUICSSMIN_PRESERVED_TOKEN_'; + const COMMENT = '___YUICSSMIN_PRESERVE_CANDIDATE_COMMENT_'; + const AT_RULE_BLOCK = '___YUICSSMIN_PRESERVE_AT_RULE_BLOCK_'; + private $comments; - private $preserved_tokens; - private $memory_limit; - private $max_execution_time; - private $pcre_backtrack_limit; - private $pcre_recursion_limit; - private $raise_php_limits; + private $atRuleBlocks; + private $preservedTokens; + private $chunkLength = 5000; + private $minChunkLength = 100; + private $memoryLimit; + private $maxExecutionTime = 60; // 1 min + private $pcreBacktrackLimit; + private $pcreRecursionLimit; + private $raisePhpLimits; + + private $unitsGroupRegex = '(?:ch|cm|em|ex|gd|in|mm|px|pt|pc|q|rem|vh|vmax|vmin|vw|%)'; + private $numRegex; /** - * @param bool|int $raise_php_limits - * If true, PHP settings will be raised if needed + * @param bool|int $raisePhpLimits If true, PHP settings will be raised if needed */ - public function __construct($raise_php_limits = TRUE) + public function __construct($raisePhpLimits = true) { - // Set suggested PHP limits - $this->memory_limit = 128 * 1048576; // 128MB in bytes - $this->max_execution_time = 60; // 1 min - $this->pcre_backtrack_limit = 1000 * 1000; - $this->pcre_recursion_limit = 500 * 1000; + $this->memoryLimit = 128 * 1048576; // 128MB in bytes + $this->pcreBacktrackLimit = 1000 * 1000; + $this->pcreRecursionLimit = 500 * 1000; + + $this->raisePhpLimits = (bool) $raisePhpLimits; - $this->raise_php_limits = (bool) $raise_php_limits; + $this->numRegex = '(?:\+|-)?\d*\.?\d+' . $this->unitsGroupRegex .'?'; } /** - * Minify a string of CSS + * Minifies a string of CSS * @param string $css - * @param int|bool $linebreak_pos + * @param int|bool $linebreakPos * @return string */ - public function run($css = '', $linebreak_pos = FALSE) + public function run($css = '', $linebreakPos = false) { if (empty($css)) { return ''; } - if ($this->raise_php_limits) { - $this->do_raise_php_limits(); + if ($this->raisePhpLimits) { + $this->doRaisePhpLimits(); } $this->comments = array(); - $this->preserved_tokens = array(); + $this->atRuleBlocks = array(); + $this->preservedTokens = array(); - $start_index = 0; - $length = strlen($css); + // process data urls + $css = $this->processDataUrls($css); - $css = $this->extract_data_urls($css); + // process comments + $css = preg_replace_callback('/(?<!\\\\)\/\*(.*?)\*(?<!\\\\)\//Ss', array($this, 'processComments'), $css); - // collect all comment blocks... - while (($start_index = $this->index_of($css, '/*', $start_index)) >= 0) { - $end_index = $this->index_of($css, '*/', $start_index + 2); - if ($end_index < 0) { - $end_index = $length; - } - $comment_found = $this->str_slice($css, $start_index + 2, $end_index); - $this->comments[] = $comment_found; - $comment_preserve_string = self::COMMENT . (count($this->comments) - 1) . '___'; - $css = $this->str_slice($css, 0, $start_index + 2) . $comment_preserve_string . $this->str_slice($css, $end_index); - // Set correct start_index: Fixes issue #2528130 - $start_index = $end_index + 2 + strlen($comment_preserve_string) - strlen($comment_found); - } + // process strings so their content doesn't get accidentally minified + $css = preg_replace_callback( + '/(?:"(?:[^\\\\"]|\\\\.|\\\\)*")|'."(?:'(?:[^\\\\']|\\\\.|\\\\)*')/S", + array($this, 'processStrings'), + $css + ); - // preserve strings so their content doesn't get accidentally minified - $css = preg_replace_callback('/(?:"(?:[^\\\\"]|\\\\.|\\\\)*")|'."(?:'(?:[^\\\\']|\\\\.|\\\\)*')/S", array($this, 'replace_string'), $css); + // Safe chunking: process at rule blocks so after chunking nothing gets stripped out + $css = preg_replace_callback( + '/@(?:document|(?:-(?:atsc|khtml|moz|ms|o|wap|webkit)-)?keyframes|media|supports).+?\}\s*\}/si', + array($this, 'processAtRuleBlocks'), + $css + ); - // Let's divide css code in chunks of 5.000 chars aprox. + // Let's divide css code in chunks of {$this->chunkLength} chars aprox. // Reason: PHP's PCRE functions like preg_replace have a "backtrack limit" // of 100.000 chars by default (php < 5.3.7) so if we're dealing with really // long strings and a (sub)pattern matches a number of chars greater than // the backtrack limit number (i.e. /(.*)/s) PCRE functions may fail silently // returning NULL and $css would be empty. $charset = ''; - $charset_regexp = '/(@charset)( [^;]+;)/i'; - $css_chunks = array(); - $css_chunk_length = 5000; // aprox size, not exact - $start_index = 0; - $i = $css_chunk_length; // save initial iterations + $charsetRegexp = '/(@charset)( [^;]+;)/i'; + $cssChunks = array(); $l = strlen($css); - - // if the number of characters is 5000 or less, do not chunk - if ($l <= $css_chunk_length) { - $css_chunks[] = $css; + // if the number of characters is <= {$this->chunkLength}, do not chunk + if ($l <= $this->chunkLength) { + $cssChunks[] = $css; } else { // chunk css code securely - while ($i < $l) { - $i += 50; // save iterations - if ($l - $start_index <= $css_chunk_length || $i >= $l) { - $css_chunks[] = $this->str_slice($css, $start_index); - break; - } - if ($css[$i - 1] === '}' && $i - $start_index > $css_chunk_length) { - // If there are two ending curly braces }} separated or not by spaces, - // join them in the same chunk (i.e. @media blocks) - $next_chunk = substr($css, $i); - if (preg_match('/^\s*\}/', $next_chunk)) { - $i = $i + $this->index_of($next_chunk, '}') + 1; + for ($startIndex = 0, $i = $this->chunkLength; $i < $l; $i++) { + if ($css[$i - 1] === '}' && $i - $startIndex >= $this->chunkLength) { + $cssChunks[] = $this->strSlice($css, $startIndex, $i); + $startIndex = $i; + // Move forward saving iterations when possible! + if ($startIndex + $this->chunkLength < $l) { + $i += $this->chunkLength; } - - $css_chunks[] = $this->str_slice($css, $start_index, $i); - $start_index = $i; } } + + // Final chunk + $cssChunks[] = $this->strSlice($css, $startIndex); } // Minify each chunk - for ($i = 0, $n = count($css_chunks); $i < $n; $i++) { - $css_chunks[$i] = $this->minify($css_chunks[$i], $linebreak_pos); + for ($i = 0, $n = count($cssChunks); $i < $n; $i++) { + $cssChunks[$i] = $this->minify($cssChunks[$i], $linebreakPos); // Keep the first @charset at-rule found - if (empty($charset) && preg_match($charset_regexp, $css_chunks[$i], $matches)) { + if (empty($charset) && preg_match($charsetRegexp, $cssChunks[$i], $matches)) { $charset = strtolower($matches[1]) . $matches[2]; } // Delete all @charset at-rules - $css_chunks[$i] = preg_replace($charset_regexp, '', $css_chunks[$i]); + $cssChunks[$i] = preg_replace($charsetRegexp, '', $cssChunks[$i]); } // Update the first chunk and push the charset to the top of the file. - $css_chunks[0] = $charset . $css_chunks[0]; + $cssChunks[0] = $charset . $cssChunks[0]; - return implode('', $css_chunks); + return trim(implode('', $cssChunks)); + } + + /** + * Sets the approximate number of characters to use when splitting a string in chunks. + * @param int $length + */ + public function set_chunk_length($length) + { + $length = (int) $length; + $this->chunkLength = $length < $this->minChunkLength ? $this->minChunkLength : $length; } /** @@ -169,7 +176,7 @@ class CSSmin */ public function set_memory_limit($limit) { - $this->memory_limit = $this->normalize_int($limit); + $this->memoryLimit = $this->normalizeInt($limit); } /** @@ -178,7 +185,7 @@ class CSSmin */ public function set_max_execution_time($seconds) { - $this->max_execution_time = (int) $seconds; + $this->maxExecutionTime = (int) $seconds; } /** @@ -187,7 +194,7 @@ class CSSmin */ public function set_pcre_backtrack_limit($limit) { - $this->pcre_backtrack_limit = (int) $limit; + $this->pcreBacktrackLimit = (int) $limit; } /** @@ -196,151 +203,272 @@ class CSSmin */ public function set_pcre_recursion_limit($limit) { - $this->pcre_recursion_limit = (int) $limit; + $this->pcreRecursionLimit = (int) $limit; } /** - * Try to configure PHP to use at least the suggested minimum settings + * Tries to configure PHP to use at least the suggested minimum settings + * @return void */ - private function do_raise_php_limits() + private function doRaisePhpLimits() { - $php_limits = array( - 'memory_limit' => $this->memory_limit, - 'max_execution_time' => $this->max_execution_time, - 'pcre.backtrack_limit' => $this->pcre_backtrack_limit, - 'pcre.recursion_limit' => $this->pcre_recursion_limit + $phpLimits = array( + 'memory_limit' => $this->memoryLimit, + 'max_execution_time' => $this->maxExecutionTime, + 'pcre.backtrack_limit' => $this->pcreBacktrackLimit, + 'pcre.recursion_limit' => $this->pcreRecursionLimit ); // If current settings are higher respect them. - foreach ($php_limits as $name => $suggested) { - $current = $this->normalize_int(ini_get($name)); - // memory_limit exception: allow -1 for "no memory limit". - if ($current > -1 && ($suggested == -1 || $current < $suggested)) { - ini_set($name, $suggested); + foreach ($phpLimits as $name => $suggested) { + $current = $this->normalizeInt(ini_get($name)); + + if ($current > $suggested) { + continue; + } + + // memoryLimit exception: allow -1 for "no memory limit". + if ($name === 'memory_limit' && $current === -1) { + continue; } + + // maxExecutionTime exception: allow 0 for "no memory limit". + if ($name === 'max_execution_time' && $current === 0) { + continue; + } + + ini_set($name, $suggested); } } /** - * Does bulk of the minification + * Registers a preserved token + * @param $token + * @return string The token ID string + */ + private function registerPreservedToken($token) + { + $this->preservedTokens[] = $token; + return self::TOKEN . (count($this->preservedTokens) - 1) .'___'; + } + + /** + * Gets the regular expression to match the specified token ID string + * @param $id + * @return string + */ + private function getPreservedTokenPlaceholderRegexById($id) + { + return '/'. self::TOKEN . $id .'___/'; + } + + /** + * Registers a candidate comment token + * @param $comment + * @return string The comment token ID string + */ + private function registerComment($comment) + { + $this->comments[] = $comment; + return '/*'. self::COMMENT . (count($this->comments) - 1) .'___*/'; + } + + /** + * Gets the candidate comment token ID string for the specified comment token ID + * @param $id + * @return string + */ + private function getCommentPlaceholderById($id) + { + return self::COMMENT . $id .'___'; + } + + /** + * Gets the regular expression to match the specified comment token ID string + * @param $id + * @return string + */ + private function getCommentPlaceholderRegexById($id) + { + return '/'. $this->getCommentPlaceholderById($id) .'/'; + } + + /** + * Registers an at rule block token + * @param $block + * @return string The comment token ID string + */ + private function registerAtRuleBlock($block) + { + $this->atRuleBlocks[] = $block; + return self::AT_RULE_BLOCK . (count($this->atRuleBlocks) - 1) .'___'; + } + + /** + * Gets the regular expression to match the specified at rule block token ID string + * @param $id + * @return string + */ + private function getAtRuleBlockPlaceholderRegexById($id) + { + return '/'. self::AT_RULE_BLOCK . $id .'___/'; + } + + /** + * Minifies the given input CSS string * @param string $css - * @param int|bool $linebreak_pos + * @param int|bool $linebreakPos * @return string */ - private function minify($css, $linebreak_pos) + private function minify($css, $linebreakPos) { + // Restore preserved at rule blocks + for ($i = 0, $max = count($this->atRuleBlocks); $i < $max; $i++) { + $css = preg_replace( + $this->getAtRuleBlockPlaceholderRegexById($i), + $this->escapeReplacementString($this->atRuleBlocks[$i]), + $css, + 1 + ); + } + // strings are safe, now wrestle the comments for ($i = 0, $max = count($this->comments); $i < $max; $i++) { - - $token = $this->comments[$i]; - $placeholder = '/' . self::COMMENT . $i . '___/'; + $comment = $this->comments[$i]; + $commentPlaceholder = $this->getCommentPlaceholderById($i); + $commentPlaceholderRegex = $this->getCommentPlaceholderRegexById($i); // ! in the first position of the comment means preserve // so push to the preserved tokens keeping the ! - if (substr($token, 0, 1) === '!') { - $this->preserved_tokens[] = $token; - $token_tring = self::TOKEN . (count($this->preserved_tokens) - 1) . '___'; - $css = preg_replace($placeholder, $token_tring, $css, 1); + if (preg_match('/^!/', $comment)) { + $preservedTokenPlaceholder = $this->registerPreservedToken($comment); + $css = preg_replace($commentPlaceholderRegex, $preservedTokenPlaceholder, $css, 1); // Preserve new lines for /*! important comments - $css = preg_replace('/\s*[\n\r\f]+\s*(\/\*'. $token_tring .')/S', self::NL.'$1', $css); - $css = preg_replace('/('. $token_tring .'\*\/)\s*[\n\r\f]+\s*/', '$1'.self::NL, $css); + $css = preg_replace('/\R+\s*(\/\*'. $preservedTokenPlaceholder .')/', self::NL.'$1', $css); + $css = preg_replace('/('. $preservedTokenPlaceholder .'\*\/)\s*\R+/', '$1'.self::NL, $css); continue; } // \ in the last position looks like hack for Mac/IE5 // shorten that to /*\*/ and the next one to /**/ - if (substr($token, (strlen($token) - 1), 1) === '\\') { - $this->preserved_tokens[] = '\\'; - $css = preg_replace($placeholder, self::TOKEN . (count($this->preserved_tokens) - 1) . '___', $css, 1); + if (preg_match('/\\\\$/', $comment)) { + $preservedTokenPlaceholder = $this->registerPreservedToken('\\'); + $css = preg_replace($commentPlaceholderRegex, $preservedTokenPlaceholder, $css, 1); $i = $i + 1; // attn: advancing the loop - $this->preserved_tokens[] = ''; - $css = preg_replace('/' . self::COMMENT . $i . '___/', self::TOKEN . (count($this->preserved_tokens) - 1) . '___', $css, 1); + $preservedTokenPlaceholder = $this->registerPreservedToken(''); + $css = preg_replace($this->getCommentPlaceholderRegexById($i), $preservedTokenPlaceholder, $css, 1); continue; } // keep empty comments after child selectors (IE7 hack) // e.g. html >/**/ body - if (strlen($token) === 0) { - $start_index = $this->index_of($css, $this->str_slice($placeholder, 1, -1)); - if ($start_index > 2) { - if (substr($css, $start_index - 3, 1) === '>') { - $this->preserved_tokens[] = ''; - $css = preg_replace($placeholder, self::TOKEN . (count($this->preserved_tokens) - 1) . '___', $css, 1); + if (strlen($comment) === 0) { + $startIndex = $this->indexOf($css, $commentPlaceholder); + if ($startIndex > 2) { + if (substr($css, $startIndex - 3, 1) === '>') { + $preservedTokenPlaceholder = $this->registerPreservedToken(''); + $css = preg_replace($commentPlaceholderRegex, $preservedTokenPlaceholder, $css, 1); + continue; } } } // in all other cases kill the comment - $css = preg_replace('/\/\*' . $this->str_slice($placeholder, 1, -1) . '\*\//', '', $css, 1); + $css = preg_replace('/\/\*' . $commentPlaceholder . '\*\//', '', $css, 1); } - // 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); + // Remove spaces before & after newlines + $css = preg_replace('/\s*'. self::NL .'\s*/', self::NL, $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, 'processOldIeSpecificMatrixDefinition'), + $css + ); // Shorten & preserve calculations calc(...) since spaces are important - $css = preg_replace_callback('/calc(\(((?:[^\(\)]+|(?1))*)\))/i', array($this, 'replace_calc'), $css); + $css = preg_replace_callback('/calc(\(((?:[^()]+|(?1))*)\))/i', array($this, 'processCalc'), $css); // Replace positive sign from numbers preceded by : or a white-space before the leading space is removed // +1.2em to 1.2em, +.8px to .8px, +2% to 2% - $css = preg_replace('/((?<!\\\\)\:|\s)\+(\.?\d+)/S', '$1$2', $css); + $css = preg_replace('/((?<!\\\\):|\s)\+(\.?\d+)/S', '$1$2', $css); // Remove leading zeros from integer and float numbers preceded by : or a white-space // 000.6 to .6, -0.8 to -.8, 0050 to 50, -01.05 to -1.05 - $css = preg_replace('/((?<!\\\\)\:|\s)(\-?)0+(\.?\d+)/S', '$1$2$3', $css); + $css = preg_replace('/((?<!\\\\):|\s)(-?)0+(\.?\d+)/S', '$1$2$3', $css); // Remove trailing zeros from float numbers preceded by : or a white-space // -6.0100em to -6.01em, .0100 to .01, 1.200px to 1.2px - $css = preg_replace('/((?<!\\\\)\:|\s)(\-?)(\d?\.\d+?)0+([^\d])/S', '$1$2$3$4', $css); + $css = preg_replace('/((?<!\\\\):|\s)(-?)(\d?\.\d+?)0+([^\d])/S', '$1$2$3$4', $css); // Remove trailing .0 -> -9.0 to -9 - $css = preg_replace('/((?<!\\\\)\:|\s)(\-?\d+)\.0([^\d])/S', '$1$2$3', $css); + $css = preg_replace('/((?<!\\\\):|\s)(-?\d+)\.0([^\d])/S', '$1$2$3', $css); // Replace 0 length numbers with 0 - $css = preg_replace('/((?<!\\\\)\:|\s)\-?\.?0+([^\d])/S', '${1}0$2', $css); + $css = preg_replace('/((?<!\\\\):|\s)-?\.?0+([^\d])/S', '${1}0$2', $css); // Remove the spaces before the things that should not have spaces before them. // But, be careful not to turn "p :link {...}" into "p:link{...}" // Swap out any pseudo-class colons with the token, and then swap back. - $css = preg_replace_callback('/(?:^|\})[^\{]*\s+\:/', array($this, 'replace_colon'), $css); + $css = preg_replace_callback('/(?:^|\})[^{]*\s+:/', array($this, 'processColon'), $css); // Remove spaces before the things that should not have spaces before them. - $css = preg_replace('/\s+([\!\{\}\;\:\>\+\(\)\]\~\=,])/', '$1', $css); + $css = preg_replace('/\s+([!{};:>+()\]~=,])/', '$1', $css); // Restore spaces for !important - $css = preg_replace('/\!important/i', ' !important', $css); + $css = preg_replace('/!important/i', ' !important', $css); // bring back the colon - $css = preg_replace('/' . self::CLASSCOLON . '/', ':', $css); + $css = preg_replace('/'. self::CLASSCOLON .'/', ':', $css); // retain space for special IE6 cases - $css = preg_replace_callback('/\:first\-(line|letter)(\{|,)/i', array($this, 'lowercase_pseudo_first'), $css); + $css = preg_replace_callback('/:first-(line|letter)(\{|,)/i', array($this, 'lowercasePseudoFirst'), $css); // no space after the end of a preserved comment $css = preg_replace('/\*\/ /', '*/', $css); // lowercase some popular @directives - $css = preg_replace_callback('/@(font-face|import|(?:-(?:atsc|khtml|moz|ms|o|wap|webkit)-)?keyframe|media|page|namespace)/i', array($this, 'lowercase_directives'), $css); + $css = preg_replace_callback( + '/@(document|font-face|import|(?:-(?:atsc|khtml|moz|ms|o|wap|webkit)-)?keyframes|media|namespace|page|' . + 'supports|viewport)/i', + array($this, 'lowercaseDirectives'), + $css + ); // lowercase some more common pseudo-elements - $css = preg_replace_callback('/:(active|after|before|checked|disabled|empty|enabled|first-(?:child|of-type)|focus|hover|last-(?:child|of-type)|link|only-(?:child|of-type)|root|:selection|target|visited)/i', array($this, 'lowercase_pseudo_elements'), $css); + $css = preg_replace_callback( + '/:(active|after|before|checked|disabled|empty|enabled|first-(?:child|of-type)|focus|hover|' . + 'last-(?:child|of-type)|link|only-(?:child|of-type)|root|:selection|target|visited)/i', + array($this, 'lowercasePseudoElements'), + $css + ); // lowercase some more common functions - $css = preg_replace_callback('/:(lang|not|nth-child|nth-last-child|nth-last-of-type|nth-of-type|(?:-(?:moz|webkit)-)?any)\(/i', array($this, 'lowercase_common_functions'), $css); + $css = preg_replace_callback( + '/:(lang|not|nth-child|nth-last-child|nth-last-of-type|nth-of-type|(?:-(?:moz|webkit)-)?any)\(/i', + array($this, 'lowercaseCommonFunctions'), + $css + ); // lower case some common function that can be values // NOTE: rgb() isn't useful as we replace with #hex later, as well as and() is already done for us - $css = preg_replace_callback('/([:,\( ]\s*)(attr|color-stop|from|rgba|to|url|(?:-(?:atsc|khtml|moz|ms|o|wap|webkit)-)?(?:calc|max|min|(?:repeating-)?(?:linear|radial)-gradient)|-webkit-gradient)/iS', array($this, 'lowercase_common_functions_values'), $css); + $css = preg_replace_callback( + '/([:,( ]\s*)(attr|color-stop|from|rgba|to|url|-webkit-gradient|' . + '(?:-(?:atsc|khtml|moz|ms|o|wap|webkit)-)?(?:calc|max|min|(?:repeating-)?(?:linear|radial)-gradient))/iS', + array($this, 'lowercaseCommonFunctionsValues'), + $css + ); // Put the space back in some cases, to support stuff like // @media screen and (-webkit-min-device-pixel-ratio:0){ - $css = preg_replace('/\band\(/i', 'and (', $css); + $css = preg_replace_callback('/(\s|\)\s)(and|not|or)\(/i', array($this, 'processAtRulesOperators'), $css); // Remove the spaces after the things that should not have spaces after them. - $css = preg_replace('/([\!\{\}\:;\>\+\(\[\~\=,])\s+/S', '$1', $css); + $css = preg_replace('/([!{}:;>+(\[~=,])\s+/S', '$1', $css); // remove unnecessary semicolons $css = preg_replace('/;+\}/', '}', $css); @@ -348,160 +476,378 @@ class CSSmin // Fix for issue: #2528146 // Restore semicolon if the last property is prefixed with a `*` (lte IE7 hack) // to avoid issues on Symbian S60 3.x browsers. - $css = preg_replace('/(\*[a-z0-9\-]+\s*\:[^;\}]+)(\})/', '$1;$2', $css); - - // Replace 0 <length> and 0 <percentage> values with 0. - // <length> data type: https://developer.mozilla.org/en-US/docs/Web/CSS/length - // <percentage> data type: https://developer.mozilla.org/en-US/docs/Web/CSS/percentage - $css = preg_replace('/([^\\\\]\:|\s)0(?:em|ex|ch|rem|vw|vh|vm|vmin|cm|mm|in|px|pt|pc|%)/iS', '${1}0', $css); - - // 0% step in a keyframe? restore the % unit - $css = preg_replace_callback('/(@[a-z\-]*?keyframes[^\{]+\{)(.*?)(\}\})/iS', array($this, 'replace_keyframe_zero'), $css); + $css = preg_replace('/(\*[a-z0-9\-]+\s*:[^;}]+)(\})/', '$1;$2', $css); - // Replace 0 0; or 0 0 0; or 0 0 0 0; with 0. - $css = preg_replace('/\:0(?: 0){1,3}(;|\}| \!)/', ':0$1', $css); + // Shorten zero values for safe properties only + $css = $this->shortenZeroValues($css); - // Fix for issue: #2528142 - // Replace text-shadow:0; with text-shadow:0 0 0; - $css = preg_replace('/(text-shadow\:0)(;|\}| \!)/i', '$1 0 0$2', $css); + // Shorten font-weight values + $css = preg_replace('/(font-weight:)bold\b/i', '${1}700', $css); + $css = preg_replace('/(font-weight:)normal\b/i', '${1}400', $css); - // Replace background-position:0; with background-position:0 0; - // same for transform-origin - // 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); + // Shorten suitable shorthand properties with repeated non-zero values + $css = preg_replace( + '/(margin|padding):('.$this->numRegex.') ('.$this->numRegex.') (?:\2) (?:\3)(;|\}| !)/i', + '$1:$2 $3$4', + $css + ); + $css = preg_replace( + '/(margin|padding):('.$this->numRegex.') ('.$this->numRegex.') ('.$this->numRegex.') (?:\3)(;|\}| !)/i', + '$1:$2 $3 $4$5', + $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) // This makes it more likely that it'll get further compressed in the next step. - $css = preg_replace_callback('/rgb\s*\(\s*([0-9,\s\-\.\%]+)\s*\)(.{1})/i', array($this, 'rgb_to_hex'), $css); - $css = preg_replace_callback('/hsl\s*\(\s*([0-9,\s\-\.\%]+)\s*\)(.{1})/i', array($this, 'hsl_to_hex'), $css); + $css = preg_replace_callback('/rgb\s*\(\s*([0-9,\s\-.%]+)\s*\)(.{1})/i', array($this, 'rgbToHex'), $css); + $css = preg_replace_callback('/hsl\s*\(\s*([0-9,\s\-.%]+)\s*\)(.{1})/i', array($this, 'hslToHex'), $css); - // Shorten colors from #AABBCC to #ABC or short color name. - $css = $this->compress_hex_colors($css); + // Shorten colors from #AABBCC to #ABC or shorter color name. + $css = $this->shortenHexColors($css); - // border: none to border:0, outline: none to outline:0 - $css = preg_replace('/(border\-?(?:top|right|bottom|left|)|outline)\:none(;|\}| \!)/iS', '$1:0$2', $css); + // Shorten long named colors: white -> #fff. + $css = $this->shortenNamedColors($css); // shorter opacity IE filter - $css = preg_replace('/progid\:DXImageTransform\.Microsoft\.Alpha\(Opacity\=/i', 'alpha(opacity=', $css); + $css = preg_replace('/progid:DXImageTransform\.Microsoft\.Alpha\(Opacity=/i', 'alpha(opacity=', $css); // Find a fraction that is used for Opera's -o-device-pixel-ratio query // Add token to add the "\" back in later $css = preg_replace('/\(([a-z\-]+):([0-9]+)\/([0-9]+)\)/i', '($1:$2'. self::QUERY_FRACTION .'$3)', $css); + // Patch new lines to avoid being removed when followed by empty rules cases + $css = preg_replace('/'. self::NL .'/', self::NL .'}', $css); + // Remove empty rules. - $css = preg_replace('/[^\};\{\/]+\{\}/S', '', $css); + $css = preg_replace('/[^{};\/]+\{\}/S', '', $css); + + // Restore new lines for /*! important comments + $css = preg_replace('/'. self::NL .'}/', "\n", $css); // Add "/" back to fix Opera -o-device-pixel-ratio query $css = preg_replace('/'. self::QUERY_FRACTION .'/', '/', $css); - // Replace multiple semi-colons in a row by a single one + // Replace multiple semi-colons in a row by a single one // See SF bug #1980989 $css = preg_replace('/;;+/', ';', $css); - // Restore new lines for /*! important comments - $css = preg_replace('/'. self::NL .'/', "\n", $css); - // Lowercase all uppercase properties - $css = preg_replace_callback('/(\{|\;)([A-Z\-]+)(\:)/', array($this, 'lowercase_properties'), $css); + $css = preg_replace_callback('/(\{|;)([A-Z\-]+)(:)/', array($this, 'lowercaseProperties'), $css); // Some source control tools don't like it when files containing lines longer // than, say 8000 characters, are checked in. The linebreak option is used in // that case to split long lines after a specific column. - if ($linebreak_pos !== FALSE && (int) $linebreak_pos >= 0) { - $linebreak_pos = (int) $linebreak_pos; - $start_index = $i = 0; - while ($i < strlen($css)) { - $i++; - if ($css[$i - 1] === '}' && $i - $start_index > $linebreak_pos) { - $css = $this->str_slice($css, 0, $i) . "\n" . $this->str_slice($css, $i); - $start_index = $i; + if ($linebreakPos !== false && (int) $linebreakPos >= 0) { + $linebreakPos = (int) $linebreakPos; + for ($startIndex = $i = 1, $l = strlen($css); $i < $l; $i++) { + if ($css[$i - 1] === '}' && $i - $startIndex > $linebreakPos) { + $css = $this->strSlice($css, 0, $i) . "\n" . $this->strSlice($css, $i); + $l = strlen($css); + $startIndex = $i; } } } // restore preserved comments and strings in reverse order - for ($i = count($this->preserved_tokens) - 1; $i >= 0; $i--) { - $css = preg_replace('/' . self::TOKEN . $i . '___/', $this->preserved_tokens[$i], $css, 1); + for ($i = count($this->preservedTokens) - 1; $i >= 0; $i--) { + $css = preg_replace( + $this->getPreservedTokenPlaceholderRegexById($i), + $this->escapeReplacementString($this->preservedTokens[$i]), + $css, + 1 + ); } - // Trim the final string (for any leading or trailing white spaces) - return trim($css); + // Trim the final string for any leading or trailing white space but respect newlines! + $css = preg_replace('/(^ | $)/', '', $css); + + return $css; } /** - * Utility method to replace all data urls with tokens before we start - * compressing, to avoid performance issues running some of the subsequent - * regexes against large strings chunks. - * + * Searches & replaces all data urls with tokens before we start compressing, + * to avoid performance issues running some of the subsequent regexes against large string chunks. * @param string $css * @return string */ - private function extract_data_urls($css) + private function processDataUrls($css) { // Leave data urls alone to increase parse performance. - $max_index = strlen($css) - 1; - $append_index = $index = $last_index = $offset = 0; + $maxIndex = strlen($css) - 1; + $appenIndex = $index = $lastIndex = $offset = 0; $sb = array(); - $pattern = '/url\(\s*(["\']?)data\:/i'; + $pattern = '/url\(\s*(["\']?)data:/i'; // Since we need to account for non-base64 data urls, we need to handle // ' and ) being part of the data string. Hence switching to indexOf, // to determine whether or not we have matching string terminators and // handling sb appends directly, instead of using matcher.append* methods. - while (preg_match($pattern, $css, $m, 0, $offset)) { - $index = $this->index_of($css, $m[0], $offset); - $last_index = $index + strlen($m[0]); - $start_index = $index + 4; // "url(".length() - $end_index = $last_index - 1; + $index = $this->indexOf($css, $m[0], $offset); + $lastIndex = $index + strlen($m[0]); + $startIndex = $index + 4; // "url(".length() + $endIndex = $lastIndex - 1; $terminator = $m[1]; // ', " or empty (not quoted) - $found_terminator = FALSE; + $terminatorFound = false; if (strlen($terminator) === 0) { $terminator = ')'; } - while ($found_terminator === FALSE && $end_index+1 <= $max_index) { - $end_index = $this->index_of($css, $terminator, $end_index + 1); - + while ($terminatorFound === false && $endIndex+1 <= $maxIndex) { + $endIndex = $this->indexOf($css, $terminator, $endIndex + 1); // endIndex == 0 doesn't really apply here - if ($end_index > 0 && substr($css, $end_index - 1, 1) !== '\\') { - $found_terminator = TRUE; - if (')' != $terminator) { - $end_index = $this->index_of($css, ')', $end_index); + if ($endIndex > 0 && substr($css, $endIndex - 1, 1) !== '\\') { + $terminatorFound = true; + if (')' !== $terminator) { + $endIndex = $this->indexOf($css, ')', $endIndex); } } } // Enough searching, start moving stuff over to the buffer - $sb[] = $this->str_slice($css, $append_index, $index); - - if ($found_terminator) { - $token = $this->str_slice($css, $start_index, $end_index); - $token = preg_replace('/\s+/', '', $token); - $this->preserved_tokens[] = $token; - - $preserver = 'url(' . self::TOKEN . (count($this->preserved_tokens) - 1) . '___)'; - $sb[] = $preserver; - - $append_index = $end_index + 1; + $sb[] = $this->strSlice($css, $appenIndex, $index); + + if ($terminatorFound) { + $token = $this->strSlice($css, $startIndex, $endIndex); + // Remove all spaces only for base64 encoded URLs. + $token = preg_replace_callback( + '/.+base64,.+/s', + array($this, 'removeSpacesFromDataUrls'), + trim($token) + ); + $preservedTokenPlaceholder = $this->registerPreservedToken($token); + $sb[] = 'url('. $preservedTokenPlaceholder .')'; + $appenIndex = $endIndex + 1; } else { // No end terminator found, re-add the whole match. Should we throw/warn here? - $sb[] = $this->str_slice($css, $index, $last_index); - $append_index = $last_index; + $sb[] = $this->strSlice($css, $index, $lastIndex); + $appenIndex = $lastIndex; } - $offset = $last_index; + $offset = $lastIndex; } - $sb[] = $this->str_slice($css, $append_index); + $sb[] = $this->strSlice($css, $appenIndex); return implode('', $sb); } /** - * Utility method to compress hex color values of the form #AABBCC to #ABC or short color name. + * Shortens all zero values for a set of safe properties + * e.g. padding: 0px 1px; -> padding:0 1px + * e.g. padding: 0px 0rem 0em 0.0pc; -> padding:0 + * @param string $css + * @return string + */ + private function shortenZeroValues($css) + { + $unitsGroupReg = $this->unitsGroupRegex; + $numOrPosReg = '('. $this->numRegex .'|top|left|bottom|right|center)'; + $oneZeroSafeProperties = array( + '(?:line-)?height', + '(?:(?:min|max)-)?width', + 'top', + 'left', + 'background-position', + 'bottom', + 'right', + 'border(?:-(?:top|left|bottom|right))?(?:-width)?', + 'border-(?:(?:top|bottom)-(?:left|right)-)?radius', + 'column-(?:gap|width)', + 'margin(?:-(?:top|left|bottom|right))?', + 'outline-width', + 'padding(?:-(?:top|left|bottom|right))?' + ); + $nZeroSafeProperties = array( + 'margin', + 'padding', + 'background-position' + ); + + $regStart = '/(;|\{)'; + $regEnd = '/i'; + + // First zero regex start + $oneZeroRegStart = $regStart .'('. implode('|', $oneZeroSafeProperties) .'):'; + + // Multiple zeros regex start + $nZerosRegStart = $regStart .'('. implode('|', $nZeroSafeProperties) .'):'; + + $css = preg_replace( + array( + $oneZeroRegStart .'0'. $unitsGroupReg . $regEnd, + $nZerosRegStart . $numOrPosReg .' 0'. $unitsGroupReg . $regEnd, + $nZerosRegStart . $numOrPosReg .' '. $numOrPosReg .' 0'. $unitsGroupReg . $regEnd, + $nZerosRegStart . $numOrPosReg .' '. $numOrPosReg .' '. $numOrPosReg .' 0'. $unitsGroupReg . $regEnd + ), + array( + '$1$2:0', + '$1$2:$3 0', + '$1$2:$3 $4 0', + '$1$2:$3 $4 $5 0' + ), + $css + ); + + // Remove background-position + array_pop($nZeroSafeProperties); + + // Replace 0 0; or 0 0 0; or 0 0 0 0; with 0 for safe properties only. + $css = preg_replace( + '/('. implode('|', $nZeroSafeProperties) .'):0(?: 0){1,3}(;|\}| !)'. $regEnd, + '$1:0$2', + $css + ); + + // Replace 0 0 0; or 0 0 0 0; with 0 0 for background-position property. + $css = preg_replace('/(background-position):0(?: 0){2,3}(;|\}| !)'. $regEnd, '$1:0 0$2', $css); + + return $css; + } + + /** + * Shortens all named colors with a shorter HEX counterpart for a set of safe properties + * e.g. white -> #fff + * @param string $css + * @return string + */ + private function shortenNamedColors($css) + { + $patterns = array(); + $replacements = array(); + $longNamedColors = array( + 'aliceblue' => '#f0f8ff', + 'antiquewhite' => '#faebd7', + 'aquamarine' => '#7fffd4', + 'black' => '#000', + 'blanchedalmond' => '#ffebcd', + 'blueviolet' => '#8a2be2', + 'burlywood' => '#deb887', + 'cadetblue' => '#5f9ea0', + 'chartreuse' => '#7fff00', + 'chocolate' => '#d2691e', + 'cornflowerblue' => '#6495ed', + 'cornsilk' => '#fff8dc', + 'darkblue' => '#00008b', + 'darkcyan' => '#008b8b', + 'darkgoldenrod' => '#b8860b', + 'darkgray' => '#a9a9a9', + 'darkgreen' => '#006400', + 'darkgrey' => '#a9a9a9', + 'darkkhaki' => '#bdb76b', + 'darkmagenta' => '#8b008b', + 'darkolivegreen' => '#556b2f', + 'darkorange' => '#ff8c00', + 'darkorchid' => '#9932cc', + 'darksalmon' => '#e9967a', + 'darkseagreen' => '#8fbc8f', + 'darkslateblue' => '#483d8b', + 'darkslategray' => '#2f4f4f', + 'darkslategrey' => '#2f4f4f', + 'darkturquoise' => '#00ced1', + 'darkviolet' => '#9400d3', + 'deeppink' => '#ff1493', + 'deepskyblue' => '#00bfff', + 'dodgerblue' => '#1e90ff', + 'firebrick' => '#b22222', + 'floralwhite' => '#fffaf0', + 'forestgreen' => '#228b22', + 'fuchsia' => '#f0f', + 'gainsboro' => '#dcdcdc', + 'ghostwhite' => '#f8f8ff', + 'goldenrod' => '#daa520', + 'greenyellow' => '#adff2f', + 'honeydew' => '#f0fff0', + 'indianred' => '#cd5c5c', + 'lavender' => '#e6e6fa', + 'lavenderblush' => '#fff0f5', + 'lawngreen' => '#7cfc00', + 'lemonchiffon' => '#fffacd', + 'lightblue' => '#add8e6', + 'lightcoral' => '#f08080', + 'lightcyan' => '#e0ffff', + 'lightgoldenrodyellow' => '#fafad2', + 'lightgray' => '#d3d3d3', + 'lightgreen' => '#90ee90', + 'lightgrey' => '#d3d3d3', + 'lightpink' => '#ffb6c1', + 'lightsalmon' => '#ffa07a', + 'lightseagreen' => '#20b2aa', + 'lightskyblue' => '#87cefa', + 'lightslategray' => '#778899', + 'lightslategrey' => '#778899', + 'lightsteelblue' => '#b0c4de', + 'lightyellow' => '#ffffe0', + 'limegreen' => '#32cd32', + 'mediumaquamarine' => '#66cdaa', + 'mediumblue' => '#0000cd', + 'mediumorchid' => '#ba55d3', + 'mediumpurple' => '#9370db', + 'mediumseagreen' => '#3cb371', + 'mediumslateblue' => '#7b68ee', + 'mediumspringgreen' => '#00fa9a', + 'mediumturquoise' => '#48d1cc', + 'mediumvioletred' => '#c71585', + 'midnightblue' => '#191970', + 'mintcream' => '#f5fffa', + 'mistyrose' => '#ffe4e1', + 'moccasin' => '#ffe4b5', + 'navajowhite' => '#ffdead', + 'olivedrab' => '#6b8e23', + 'orangered' => '#ff4500', + 'palegoldenrod' => '#eee8aa', + 'palegreen' => '#98fb98', + 'paleturquoise' => '#afeeee', + 'palevioletred' => '#db7093', + 'papayawhip' => '#ffefd5', + 'peachpuff' => '#ffdab9', + 'powderblue' => '#b0e0e6', + 'rebeccapurple' => '#663399', + 'rosybrown' => '#bc8f8f', + 'royalblue' => '#4169e1', + 'saddlebrown' => '#8b4513', + 'sandybrown' => '#f4a460', + 'seagreen' => '#2e8b57', + 'seashell' => '#fff5ee', + 'slateblue' => '#6a5acd', + 'slategray' => '#708090', + 'slategrey' => '#708090', + 'springgreen' => '#00ff7f', + 'steelblue' => '#4682b4', + 'turquoise' => '#40e0d0', + 'white' => '#fff', + 'whitesmoke' => '#f5f5f5', + 'yellow' => '#ff0', + 'yellowgreen' => '#9acd32' + ); + $propertiesWithColors = array( + 'color', + 'background(?:-color)?', + 'border(?:-(?:top|right|bottom|left|color)(?:-color)?)?', + 'outline(?:-color)?', + '(?:text|box)-shadow' + ); + + $regStart = '/(;|\{)('. implode('|', $propertiesWithColors) .'):([^;}]*)\b'; + $regEnd = '\b/iS'; + + foreach ($longNamedColors as $colorName => $colorCode) { + $patterns[] = $regStart . $colorName . $regEnd; + $replacements[] = '$1$2:$3'. $colorCode; + } + + // Run at least 4 times to cover most cases (same color used several times for the same property) + for ($i = 0; $i < 4; $i++) { + $css = preg_replace($patterns, $replacements, $css); + } + + return $css; + } + + /** + * Compresses HEX color values of the form #AABBCC to #ABC or short color name. * * DOES NOT compress CSS ID selectors which match the above pattern (which would break things). * e.g. #AddressForm { ... } @@ -515,239 +861,383 @@ class CSSmin * @param string $css * @return string */ - private function compress_hex_colors($css) + private function shortenHexColors($css) { - // Look for hex colors inside { ... } (to avoid IDs) and which don't have a =, or a " in front of them (to avoid filters) - $pattern = '/(\=\s*?["\']?)?#([0-9a-f])([0-9a-f])([0-9a-f])([0-9a-f])([0-9a-f])([0-9a-f])(\}|[^0-9a-f{][^{]*?\})/iS'; - $_index = $index = $last_index = $offset = 0; - $sb = array(); - // See: http://ajaxmin.codeplex.com/wikipage?title=CSS%20Colors - $short_safe = array( + // Look for hex colors inside { ... } (to avoid IDs) and + // which don't have a =, or a " in front of them (to avoid filters) + $pattern = + '/(=\s*?["\']?)?#([0-9a-f])([0-9a-f])([0-9a-f])([0-9a-f])([0-9a-f])([0-9a-f])(\}|[^0-9a-f{][^{]*?\})/iS'; + $_index = $index = $lastIndex = $offset = 0; + $longHexColors = array( + '#f0ffff' => 'azure', + '#f5f5dc' => 'beige', + '#ffe4c4' => 'bisque', + '#a52a2a' => 'brown', + '#ff7f50' => 'coral', + '#ffd700' => 'gold', '#808080' => 'gray', '#008000' => 'green', + '#4b0082' => 'indigo', + '#fffff0' => 'ivory', + '#f0e68c' => 'khaki', + '#faf0e6' => 'linen', '#800000' => 'maroon', '#000080' => 'navy', + '#fdf5e6' => 'oldlace', '#808000' => 'olive', '#ffa500' => 'orange', + '#da70d6' => 'orchid', + '#cd853f' => 'peru', + '#ffc0cb' => 'pink', + '#dda0dd' => 'plum', '#800080' => 'purple', + '#f00' => 'red', + '#fa8072' => 'salmon', + '#a0522d' => 'sienna', '#c0c0c0' => 'silver', + '#fffafa' => 'snow', + '#d2b48c' => 'tan', '#008080' => 'teal', - '#f00' => 'red' + '#ff6347' => 'tomato', + '#ee82ee' => 'violet', + '#f5deb3' => 'wheat' ); + $sb = array(); while (preg_match($pattern, $css, $m, 0, $offset)) { - $index = $this->index_of($css, $m[0], $offset); - $last_index = $index + strlen($m[0]); - $is_filter = $m[1] !== null && $m[1] !== ''; + $index = $this->indexOf($css, $m[0], $offset); + $lastIndex = $index + strlen($m[0]); + $isFilter = $m[1] !== null && $m[1] !== ''; - $sb[] = $this->str_slice($css, $_index, $index); + $sb[] = $this->strSlice($css, $_index, $index); - if ($is_filter) { + if ($isFilter) { // Restore, maintain case, otherwise filter will break - $sb[] = $m[1] . '#' . $m[2] . $m[3] . $m[4] . $m[5] . $m[6] . $m[7]; + $sb[] = $m[1] .'#'. $m[2] . $m[3] . $m[4] . $m[5] . $m[6] . $m[7]; } else { if (strtolower($m[2]) == strtolower($m[3]) && strtolower($m[4]) == strtolower($m[5]) && strtolower($m[6]) == strtolower($m[7])) { // Compress. - $hex = '#' . strtolower($m[3] . $m[5] . $m[7]); + $hex = '#'. strtolower($m[3] . $m[5] . $m[7]); } else { // Non compressible color, restore but lower case. - $hex = '#' . strtolower($m[2] . $m[3] . $m[4] . $m[5] . $m[6] . $m[7]); + $hex = '#'. strtolower($m[2] . $m[3] . $m[4] . $m[5] . $m[6] . $m[7]); } - // replace Hex colors to short safe color names - $sb[] = array_key_exists($hex, $short_safe) ? $short_safe[$hex] : $hex; + // replace Hex colors with shorter color names + $sb[] = array_key_exists($hex, $longHexColors) ? $longHexColors[$hex] : $hex; } - $_index = $offset = $last_index - strlen($m[8]); + $_index = $offset = $lastIndex - strlen($m[8]); } - $sb[] = $this->str_slice($css, $_index); + $sb[] = $this->strSlice($css, $_index); return implode('', $sb); } - /* CALLBACKS - * --------------------------------------------------------------------------------------------- - */ + // --------------------------------------------------------------------------------------------- + // CALLBACKS + // --------------------------------------------------------------------------------------------- - private function replace_string($matches) + private function processComments($matches) + { + $match = !empty($matches[1]) ? $matches[1] : ''; + return $this->registerComment($match); + } + + private function processStrings($matches) { $match = $matches[0]; $quote = substr($match, 0, 1); - // Must use addcslashes in PHP to avoid parsing of backslashes - $match = addcslashes($this->str_slice($match, 1, -1), '\\'); + $match = $this->strSlice($match, 1, -1); // maybe the string contains a comment-like substring? // one, maybe more? put'em back then - if (($pos = $this->index_of($match, self::COMMENT)) >= 0) { + if (($pos = strpos($match, self::COMMENT)) !== false) { for ($i = 0, $max = count($this->comments); $i < $max; $i++) { - $match = preg_replace('/' . self::COMMENT . $i . '___/', $this->comments[$i], $match, 1); + $match = preg_replace( + $this->getCommentPlaceholderRegexById($i), + $this->escapeReplacementString($this->comments[$i]), + $match, + 1 + ); } } // minify alpha opacity in filter strings - $match = preg_replace('/progid\:DXImageTransform\.Microsoft\.Alpha\(Opacity\=/i', 'alpha(opacity=', $match); + $match = preg_replace('/progid:DXImageTransform\.Microsoft\.Alpha\(Opacity=/i', 'alpha(opacity=', $match); - $this->preserved_tokens[] = $match; - return $quote . self::TOKEN . (count($this->preserved_tokens) - 1) . '___' . $quote; + $preservedTokenPlaceholder = $this->registerPreservedToken($match); + return $quote . $preservedTokenPlaceholder . $quote; } - private function replace_colon($matches) + private function processAtRuleBlocks($matches) { - return preg_replace('/\:/', self::CLASSCOLON, $matches[0]); + return $this->registerAtRuleBlock($matches[0]); } - private function replace_calc($matches) + private function processCalc($matches) { - $this->preserved_tokens[] = trim(preg_replace('/\s*([\*\/\(\),])\s*/', '$1', $matches[2])); - return 'calc('. self::TOKEN . (count($this->preserved_tokens) - 1) . '___' . ')'; + $token = preg_replace( + '/\)([+\-]{1})/', + ') $1', + preg_replace( + '/([+\-]{1})\(/', + '$1 (', + trim(preg_replace('/\s*([*\/(),])\s*/', '$1', $matches[2])) + ) + ); + $preservedTokenPlaceholder = $this->registerPreservedToken($token); + return 'calc('. $preservedTokenPlaceholder .')'; } - 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 processOldIeSpecificMatrixDefinition($matches) + { + $preservedTokenPlaceholder = $this->registerPreservedToken($matches[1]); + return 'filter:progid:DXImageTransform.Microsoft.Matrix('. $preservedTokenPlaceholder .')'; } - private function replace_keyframe_zero($matches) + private function processColon($matches) { - return $matches[1] . preg_replace('/0(\{|,[^\)\{]+\{)/', '0%$1', $matches[2]) . $matches[3]; + return preg_replace('/\:/', self::CLASSCOLON, $matches[0]); } - private function rgb_to_hex($matches) + private function removeSpacesFromDataUrls($matches) { - // Support for percentage values rgb(100%, 0%, 45%); - if ($this->index_of($matches[1], '%') >= 0){ - $rgbcolors = explode(',', str_replace('%', '', $matches[1])); - for ($i = 0; $i < count($rgbcolors); $i++) { - $rgbcolors[$i] = $this->round_number(floatval($rgbcolors[$i]) * 2.55); - } - } else { - $rgbcolors = explode(',', $matches[1]); - } + return preg_replace('/\s+/', '', $matches[0]); + } + + private function rgbToHex($matches) + { + $hexColors = array(); + $rgbColors = explode(',', $matches[1]); // Values outside the sRGB color space should be clipped (0-255) - for ($i = 0; $i < count($rgbcolors); $i++) { - $rgbcolors[$i] = $this->clamp_number(intval($rgbcolors[$i], 10), 0, 255); - $rgbcolors[$i] = sprintf("%02x", $rgbcolors[$i]); + for ($i = 0, $l = count($rgbColors); $i < $l; $i++) { + $hexColors[$i] = sprintf("%02x", $this->clampNumberSrgb($this->rgbPercentageToRgbInteger($rgbColors[$i]))); } // Fix for issue #2528093 - if (!preg_match('/[\s\,\);\}]/', $matches[2])){ - $matches[2] = ' ' . $matches[2]; + if (!preg_match('/[\s,);}]/', $matches[2])) { + $matches[2] = ' '. $matches[2]; } - return '#' . implode('', $rgbcolors) . $matches[2]; + return '#'. implode('', $hexColors) . $matches[2]; } - private function hsl_to_hex($matches) + private function hslToHex($matches) { - $values = explode(',', str_replace('%', '', $matches[1])); - $h = floatval($values[0]); - $s = floatval($values[1]); - $l = floatval($values[2]); + $hslValues = explode(',', $matches[1]); - // Wrap and clamp, then fraction! - $h = ((($h % 360) + 360) % 360) / 360; - $s = $this->clamp_number($s, 0, 100) / 100; - $l = $this->clamp_number($l, 0, 100) / 100; + $rgbColors = $this->hslToRgb($hslValues); - if ($s == 0) { - $r = $g = $b = $this->round_number(255 * $l); - } else { - $v2 = $l < 0.5 ? $l * (1 + $s) : ($l + $s) - ($s * $l); - $v1 = (2 * $l) - $v2; - $r = $this->round_number(255 * $this->hue_to_rgb($v1, $v2, $h + (1/3))); - $g = $this->round_number(255 * $this->hue_to_rgb($v1, $v2, $h)); - $b = $this->round_number(255 * $this->hue_to_rgb($v1, $v2, $h - (1/3))); - } + return $this->rgbToHex(array('', implode(',', $rgbColors), $matches[2])); + } - return $this->rgb_to_hex(array('', $r.','.$g.','.$b, $matches[2])); + private function processAtRulesOperators($matches) + { + return $matches[1] . strtolower($matches[2]) .' ('; } - private function lowercase_pseudo_first($matches) + private function lowercasePseudoFirst($matches) { return ':first-'. strtolower($matches[1]) .' '. $matches[2]; } - private function lowercase_directives($matches) + private function lowercaseDirectives($matches) { return '@'. strtolower($matches[1]); } - private function lowercase_pseudo_elements($matches) + private function lowercasePseudoElements($matches) { return ':'. strtolower($matches[1]); } - private function lowercase_common_functions($matches) + private function lowercaseCommonFunctions($matches) { return ':'. strtolower($matches[1]) .'('; } - private function lowercase_common_functions_values($matches) + private function lowercaseCommonFunctionsValues($matches) { return $matches[1] . strtolower($matches[2]); } - private function lowercase_properties($matches) + private function lowercaseProperties($matches) { - return $matches[1].strtolower($matches[2]).$matches[3]; + return $matches[1] . strtolower($matches[2]) . $matches[3]; } - /* HELPERS - * --------------------------------------------------------------------------------------------- + // --------------------------------------------------------------------------------------------- + // HELPERS + // --------------------------------------------------------------------------------------------- + + /** + * Clamps a number between a minimum and a maximum value. + * @param int|float $n the number to clamp + * @param int|float $min the lower end number allowed + * @param int|float $max the higher end number allowed + * @return int|float */ + private function clampNumber($n, $min, $max) + { + return min(max($n, $min), $max); + } - private function hue_to_rgb($v1, $v2, $vh) + /** + * Clamps a RGB color number outside the sRGB color space + * @param int|float $n the number to clamp + * @return int|float + */ + private function clampNumberSrgb($n) { - $vh = $vh < 0 ? $vh + 1 : ($vh > 1 ? $vh - 1 : $vh); - if ($vh * 6 < 1) return $v1 + ($v2 - $v1) * 6 * $vh; - if ($vh * 2 < 1) return $v2; - if ($vh * 3 < 2) return $v1 + ($v2 - $v1) * ((2/3) - $vh) * 6; - return $v1; + return $this->clampNumber($n, 0, 255); } - private function round_number($n) + /** + * Escapes backreferences such as \1 and $1 in a regular expression replacement string + * @param $string + * @return string + */ + private function escapeReplacementString($string) { - return intval(floor(floatval($n) + 0.5), 10); + return addcslashes($string, '\\$'); } - private function clamp_number($n, $min, $max) + /** + * Converts a HSL color into a RGB color + * @param array $hslValues + * @return array + */ + private function hslToRgb($hslValues) { - return min(max($n, $min), $max); + $h = floatval($hslValues[0]); + $s = floatval(str_replace('%', '', $hslValues[1])); + $l = floatval(str_replace('%', '', $hslValues[2])); + + // Wrap and clamp, then fraction! + $h = ((($h % 360) + 360) % 360) / 360; + $s = $this->clampNumber($s, 0, 100) / 100; + $l = $this->clampNumber($l, 0, 100) / 100; + + if ($s == 0) { + $r = $g = $b = $this->roundNumber(255 * $l); + } else { + $v2 = $l < 0.5 ? $l * (1 + $s) : ($l + $s) - ($s * $l); + $v1 = (2 * $l) - $v2; + $r = $this->roundNumber(255 * $this->hueToRgb($v1, $v2, $h + (1/3))); + $g = $this->roundNumber(255 * $this->hueToRgb($v1, $v2, $h)); + $b = $this->roundNumber(255 * $this->hueToRgb($v1, $v2, $h - (1/3))); + } + + return array($r, $g, $b); + } + + /** + * Tests and selects the correct formula for each RGB color channel + * @param $v1 + * @param $v2 + * @param $vh + * @return mixed + */ + private function hueToRgb($v1, $v2, $vh) + { + $vh = $vh < 0 ? $vh + 1 : ($vh > 1 ? $vh - 1 : $vh); + + if ($vh * 6 < 1) { + return $v1 + ($v2 - $v1) * 6 * $vh; + } + + if ($vh * 2 < 1) { + return $v2; + } + + if ($vh * 3 < 2) { + return $v1 + ($v2 - $v1) * ((2 / 3) - $vh) * 6; + } + + return $v1; } /** * PHP port of Javascript's "indexOf" function for strings only - * Author: Tubal Martin http://blog.margenn.com + * Author: Tubal Martin * * @param string $haystack * @param string $needle * @param int $offset index (optional) * @return int */ - private function index_of($haystack, $needle, $offset = 0) + private function indexOf($haystack, $needle, $offset = 0) { $index = strpos($haystack, $needle, $offset); - return ($index !== FALSE) ? $index : -1; + return ($index !== false) ? $index : -1; + } + + /** + * Convert strings like "64M" or "30" to int values + * @param mixed $size + * @return int + */ + private function normalizeInt($size) + { + if (is_string($size)) { + $letter = substr($size, -1); + $size = intval($size); + switch ($letter) { + case 'M': + case 'm': + return (int) $size * 1048576; + case 'K': + case 'k': + return (int) $size * 1024; + case 'G': + case 'g': + return (int) $size * 1073741824; + } + } + return (int) $size; + } + + /** + * Converts a string containing and RGB percentage value into a RGB integer value i.e. '90%' -> 229.5 + * @param $rgbPercentage + * @return int + */ + private function rgbPercentageToRgbInteger($rgbPercentage) + { + if (strpos($rgbPercentage, '%') !== false) { + $rgbPercentage = $this->roundNumber(floatval(str_replace('%', '', $rgbPercentage)) * 2.55); + } + + return intval($rgbPercentage, 10); + } + + /** + * Rounds a number to its closest integer + * @param $n + * @return int + */ + private function roundNumber($n) + { + return intval(round(floatval($n)), 10); } /** * PHP port of Javascript's "slice" function for strings only - * Author: Tubal Martin http://blog.margenn.com - * Tests: http://margenn.com/tubal/str_slice/ + * Author: Tubal Martin * * @param string $str * @param int $start index * @param int|bool $end index (optional) * @return string */ - private function str_slice($str, $start = 0, $end = FALSE) + private function strSlice($str, $start = 0, $end = false) { - if ($end !== FALSE && ($start < 0 || $end <= 0)) { + if ($end !== false && ($start < 0 || $end <= 0)) { $max = strlen($str); if ($start < 0) { @@ -767,25 +1257,7 @@ class CSSmin } } - $slice = ($end === FALSE) ? substr($str, $start) : substr($str, $start, $end - $start); - return ($slice === FALSE) ? '' : $slice; - } - - /** - * Convert strings like "64M" or "30" to int values - * @param mixed $size - * @return int - */ - private function normalize_int($size) - { - if (is_string($size)) { - switch (substr($size, -1)) { - case 'M': case 'm': return $size * 1048576; - case 'K': case 'k': return $size * 1024; - case 'G': case 'g': return $size * 1073741824; - } - } - - return (int) $size; + $slice = ($end === false) ? substr($str, $start) : substr($str, $start, $end - $start); + return ($slice === false) ? '' : $slice; } } diff --git a/sites/all/modules/contrib/advagg/advagg_ext_compress/advagg_ext_compress.admin.inc b/sites/all/modules/contrib/advagg/advagg_ext_compress/advagg_ext_compress.admin.inc new file mode 100644 index 0000000000..ec9457a1c7 --- /dev/null +++ b/sites/all/modules/contrib/advagg/advagg_ext_compress/advagg_ext_compress.admin.inc @@ -0,0 +1,83 @@ +<?php + +/** + * @file + * Admin page callbacks for the advagg external compression module. + */ + +/** + * Form builder; Configure advagg settings. + * + * @ingroup advagg_forms + * + * @see system_settings_form() + */ +function advagg_ext_compress_admin_settings_form($form, $form_state) { + drupal_set_title(t('AdvAgg: External Compressor')); + advagg_display_message_if_requirements_not_met(); + + $form = array(); + // CSS command line. + advagg_ext_compress_admin_cmd_generate($form, array('css', t('CSS'))); + // JS command line. + advagg_ext_compress_admin_cmd_generate($form, array('js', t('JavaScript'))); + + return system_settings_form($form); +} + +/** + * Generate a form for css or js depending on the input. + * + * @param array $form + * The form array to add to. + * @param array $params + * An array where key 0 is the machine name and key 1 is the title. + */ +function advagg_ext_compress_admin_cmd_generate(array &$form, array $params) { + $form[$params[0]] = array( + '#type' => 'fieldset', + '#title' => t('@title', array('@title' => $params[1])), + ); + $form[$params[0]]['cmd'] = array( + '#type' => 'fieldset', + '#title' => t('Command Line'), + ); + + $description = t('{%CWD%} = DRUPAL_ROOT. <br /> {%IN%} = input file. <br /> {%IN_URL_ENC%} = url pointing to the input file that has been url encoded. <br /> {%OUT%} = output file. <br /><br />'); + if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') { + $description .= ' ' . t('Example using the <a href="@link1">Microsoft Ajax Minifier</a>. <p><code>@code1</code></p>', array( + '@link1' => 'http://ajaxmin.codeplex.com/', + '@code1' => 'AjaxMinifier {%IN%} -o {%OUT%}', + )); + } + + if ($params[0] === 'js') { + $description .= ' ' . t('Example using the <a href="@link1">Google Closure Compiler</a>. <p><code>@code1</code></p>', array( + '@link1' => 'https://developers.google.com/closure/compiler/docs/gettingstarted_app', + '@code1' => 'java -jar compiler.jar --js {%CWD%}/{%IN%} --js_output_file {%OUT%}', + )); + + $description .= ' ' . t('Example using curl to compress via the <a href="@link1">Online Google Closure Compiler</a>. <p><code>@code1</code></p>', array( + '@link1' => 'https://developers.google.com/closure/compiler/docs/api-ref', + '@code1' => 'curl -o {%OUT%} -d output_info=compiled_code -d code_url={%IN_URL_ENC%} http://closure-compiler.appspot.com/compile', + )); + } + if ($params[0] === 'css') { + $description .= ' ' . t('Example using the <a href="@link1">YUI Compressor</a>. <p><code>@code1</code></p>', array( + '@link1' => 'http://yui.github.io/yuicompressor/', + '@code1' => 'java -jar yuicompressor-x.y.z.jar --type css --line-break 4096 {%CWD%}/{%IN%} -o {%OUT%}', + )); + + $description .= ' ' . t('Example using curl to compress via an online <a href="@link1">CSS Compressor</a>. <p><code>@code1</code></p>', array( + '@link1' => 'http://cnvyr.io/', + '@code1' => 'curl -o {%OUT%} -F \'files0=@{%IN%}\' http://srv.cnvyr.io/v1?min=css', + )); + } + + $form[$params[0]]['cmd']['advagg_ext_compress_' . $params[0] . '_cmd'] = array( + '#type' => 'textfield', + '#title' => t('Command to run'), + '#default_value' => variable_get('advagg_ext_compress_' . $params[0] . '_cmd', ''), + '#description' => $description, + ); +} 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 new file mode 100644 index 0000000000..955a0c5708 --- /dev/null +++ b/sites/all/modules/contrib/advagg/advagg_ext_compress/advagg_ext_compress.info @@ -0,0 +1,13 @@ +name = AdvAgg External Compression +description = Compress Javascript and/or CSS with a command line compressor. +package = Advanced CSS/JS Aggregation +core = 7.x +dependencies[] = advagg + +configure = admin/config/development/performance/advagg/ext-compress + +; Information added by Drupal.org packaging script on 2020-11-19 +version = "7.x-2.35" +core = "7.x" +project = "advagg" +datestamp = "1605792717" 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 new file mode 100644 index 0000000000..5bfe00f4b1 --- /dev/null +++ b/sites/all/modules/contrib/advagg/advagg_ext_compress/advagg_ext_compress.module @@ -0,0 +1,241 @@ +<?php + +/** + * @file + * Advanced CSS/JS aggregation external compression module. + */ + +/** + * @addtogroup hooks + * @{ + */ + +/** + * Implements hook_menu(). + */ +function advagg_ext_compress_menu() { + $file_path = drupal_get_path('module', 'advagg_ext_compress'); + $config_path = advagg_admin_config_root_path(); + + $items[$config_path . '/advagg/ext-compress'] = array( + 'title' => 'External Compression', + 'description' => 'Adjust External Compression settings.', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('advagg_ext_compress_admin_settings_form'), + 'type' => MENU_LOCAL_TASK, + 'access arguments' => array('administer site configuration'), + 'file path' => $file_path, + 'file' => 'advagg_ext_compress.admin.inc', + 'weight' => 10, + ); + + return $items; +} + +/** + * @} End of "addtogroup hooks". + */ + +/** + * @addtogroup advagg_hooks + * @{ + */ + +/** + * Implements hook_advagg_js_compress_configuration_alter(). + */ +function advagg_ext_compress_advagg_js_compress_configuration_alter(&$options_desc, &$compressors, &$functions) { + list($options, $description) = $options_desc; + + $key = 10; + while (isset($options[$key])) { + $key++; + } + $options[$key] = t('AdvAgg Command Line Compressor'); + $compressors[$key] = 'advagg_cmdline'; + $functions[$key] = 'advagg_ext_compress_js_compress'; + + $options_desc = array($options, $description); +} + +/** + * Implements hook_advagg_css_compress_configuration_alter(). + */ +function advagg_ext_compress_advagg_css_compress_configuration_alter(&$options_desc, &$compressors, &$functions) { + list($options, $description) = $options_desc; + + $key = 10; + while (isset($options[$key])) { + $key++; + } + $options[$key] = t('AdvAgg Command Line Compressor'); + $functions[$key] = 'advagg_ext_compress_css_compress'; + + $options_desc = array($options, $description); +} + +/** + * @} End of "addtogroup advagg_hooks". + */ + +/** + * Compress Javascript using via command line. + * + * @param string $input_file + * The file containing the uncompressed css/js data. + * @param string $ext + * The string css or js. + * @param array $debug + * Optional debug array. + * + * @return string + * The filename containing the compressed css/js data. + */ +function advagg_ext_compress_execute_cmd($input_file, $ext = '', array &$debug = array()) { + $run = variable_get("advagg_ext_compress_{$ext}_cmd", ''); + if (empty($run)) { + return FALSE; + } + + // Get file extension. + if (empty($ext)) { + $ext = strtolower(pathinfo($input_file, PATHINFO_EXTENSION)); + if ($ext !== 'css' && $ext !== 'js') { + // Get the $ext from the database. + $row = db_select('advagg_files', 'af') + ->fields('af') + ->condition('filename', $input_file) + ->execute()->fetchAssoc(); + if (!empty($row['filetype'])) { + $ext = $row['filetype']; + } + if ($ext === 'less') { + $ext = 'css'; + } + } + } + + // Generate temp file. + $temp_file = drupal_tempnam('temporary://', 'advagg_file_'); + $new_temp_file = $temp_file . '.' . basename($input_file); + @rename($temp_file, $new_temp_file); + // Set the permissions on the temp file. + drupal_chmod($new_temp_file); + $output = advagg_get_relative_path($new_temp_file); + + // Create command to run. + $cmd = str_replace(array( + '{%CWD%}', + '{%IN%}', + '{%IN_URL_ENC%}', + '{%OUT%}', + ), array( + DRUPAL_ROOT, + $input_file, + urlencode(file_create_url($input_file)), + escapeshellarg(realpath($output)), + ), $run); + + // Run command and return the output file. + $shell_output = array(); + $return_var = 0; + $shell = exec($cmd, $shell_output, $return_var); + $debug = array($cmd, $shell_output, $return_var, $shell); + + // Cleanup leftover files. + if (file_exists($temp_file)) { + @unlink($temp_file); + } + return $output; +} + +/** + * Compress Javascript using via command line. + * + * @param string $contents + * The JavaScript to compress. + * @param bool $log_errors + * TRUE to log errors. + * + * @return bool + * FALSE if this failed. + */ +function advagg_ext_compress_js_compress(&$contents, $log_errors) { + return advagg_ext_compress_string($contents, 'js', $log_errors); +} + +/** + * Compress CSS using via command line. + * + * @param string $contents + * The CSS to compress. + * @param bool $log_errors + * TRUE to log errors. + * + * @return bool + * FALSE if this failed. + */ +function advagg_ext_compress_css_compress(&$contents, $log_errors) { + return advagg_ext_compress_string($contents, 'css', $log_errors); +} + +/** + * Compress CSS using via command line. + * + * @param string $contents + * The data to compress. + * @param string $type + * Should be css or js. + * @param bool $log_errors + * TRUE to log errors. + * + * @return bool + * TRUE on success. + */ +function advagg_ext_compress_string(&$contents, $type, $log_errors) { + list($css_path, $js_path) = advagg_get_root_files_dir(); + if ($type === 'css') { + $dir = $css_path[0]; + } + else { + $dir = $js_path[0]; + } + $new_temp_file = $dir . '/advagg_file_' . drupal_hash_base64(microtime(TRUE) . mt_rand()) . '.' . $type; + $temp_file_full = advagg_get_relative_path($new_temp_file); + + file_put_contents($new_temp_file, $contents); + // Set the permissions on the temp file. + drupal_chmod($new_temp_file); + $debug = array(); + $output = advagg_ext_compress_execute_cmd($temp_file_full, $type, $debug); + if (empty($output)) { + return FALSE; + } + $new_contents = advagg_file_get_contents($output); + if (strpos($new_contents, 'Error') === 0) { + if ($log_errors) { + watchdog('advagg_ext_compress', "@a \n<br>\n<br> @b", array( + // Only log 4k of data. + '@a' => substr($new_contents, 0, 4096), + '@b' => print_r($debug, TRUE), + )); + } + $return = FALSE; + } + else { + $contents = $new_contents; + $return = TRUE; + } + + // Cleanup. + if (file_exists($new_temp_file)) { + unlink($new_temp_file); + } + if (file_exists($temp_file_full)) { + unlink($temp_file_full); + } + if (file_exists($output)) { + unlink($output); + } + return $return; +} 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 new file mode 100644 index 0000000000..32406e5850 --- /dev/null +++ b/sites/all/modules/contrib/advagg/advagg_font/advagg_font.admin.inc @@ -0,0 +1,209 @@ +<?php + +/** + * @file + * Admin page callbacks for the advagg font module. + */ + +/** + * Form builder; Configure advagg settings. + * + * @ingroup advagg_forms + * + * @see system_settings_form() + */ +function advagg_font_admin_settings_form() { + drupal_set_title(t('AdvAgg: Async Font Loader')); + advagg_display_message_if_requirements_not_met(); + $form = array(); + + $library = advagg_get_library('fontfaceobserver', 'advagg_font'); + $version = advagg_get_remote_libraries_version('fontfaceobserver', $library); + $options = array( + 0 => t('Disabled'), + 6 => t('Externally load the latest from github (version: @version)', array('@version' => $version)), + ); + $description = t('This will use the fallback font until the font has been downloaded. See <a href="@link">fontfaceobserver</a> for more info.', array( + '@link' => $library['vendor url'], + )); + if (function_exists('libraries_info')) { + if ($library['installed']) { + $options += array( + 2 => t('Inline javascript (version: @version)', array('@version' => $library['version'])), + 4 => t('Local file included in aggregate (version: @version)', array('@version' => $library['version'])), + ); + } + elseif (!is_readable('sites/all/libraries/fontfaceobserver/fontfaceobserver.js')) { + $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('Go to the <a href="@url">library report page</a> and make sure fontfaceobserver is installed correctly.', array( + '@url' => url('admin/reports/libraries'), + )); + } + } + elseif (is_readable('sites/all/libraries/fontfaceobserver/fontfaceobserver.js')) { + $description .= ' ' . t('To use fontfaceobserver locally the <a href="@url">libraries api module</a> needs to be installed.', array( + '@url' => 'https://www.drupal.org/project/libraries', + )); + } + else { + $description .= ' ' . t('To use fontfaceobserver locally the <a href="@url">libraries api module</a> 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', + )); + } + + ksort($options); + $form['advagg_font_fontfaceobserver'] = array( + '#type' => 'radios', + '#title' => t('Use font face observer to load fonts asynchronously.'), + '#default_value' => variable_get('advagg_font_fontfaceobserver', ADVAGG_FONT_FONTFACEOBSERVER), + '#options' => $options, + '#description' => $description, + ); + + $form['container'] = array( + '#type' => 'container', + '#states' => array( + 'invisible' => array( + ':input[name="advagg_font_fontfaceobserver"]' => array('value' => '0'), + ), + ), + ); + $form['container']['advagg_font_storage'] = array( + '#type' => 'checkbox', + '#title' => t('Use localStorage so the flash of unstyled text (FOUT) only happens once.'), + '#default_value' => variable_get('advagg_font_storage', ADVAGG_FONT_STORAGE), + '#description' => t('Data is stored in localStorage under advagg_fonts. If this is a problem you can disable localStorage from being used; if doing so the FOUT (Flash of Unstyled Text) will happen on every page load if cookies is also not being used.', array('@cookie' => 'advaggfont_pt-sans=PT Sans')), + ); + $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 <code>@cookie</code>. If this is a problem you can disable cookies from being set; if doing so the FOUT (Flash of Unstyled Text) will happen on every page load if localStorage is also not being used.', 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), + '#edit-advagg-font-storage' => array('checked' => FALSE), + ), + ), + ); + + // Get all css files and scan for quoted fonts. + $form['fonts'] = array( + '#type' => 'fieldset', + '#collapsible' => TRUE, + '#collapsed' => TRUE, + '#title' => t('Async Loaded fonts in CSS'), + '#description' => t('Assumes quoted fonts will be downloaded and unquoted fonts are fallbacks.'), + ); + $form['fonts_not_async'] = array( + '#type' => 'fieldset', + '#collapsible' => TRUE, + '#collapsed' => TRUE, + '#title' => t('NOT Async Loaded in CSS'), + '#description' => t('Assumes quoted fonts will be downloaded and unquoted fonts are fallbacks. If there is no fallback it will apear below.'), + ); + // Get filename, filename_hash, and changes. + $results = db_select('advagg_files', 'af') + ->fields('af', array('filename', 'filename_hash', 'changes')) + ->condition('filetype', 'css') + ->orderBy('filename', 'ASC') + ->execute(); + while ($row = $results->fetchAssoc()) { + if (!file_exists($row['filename'])) { + continue; + } + // Get the file contents. + $file_contents = (string) @advagg_file_get_contents($row['filename']); + // Get font names. + list($replacements, $fonts_with_no_replacements) = advagg_font_get_replacements_array($file_contents); + if (!empty($replacements)) { + $fonts = array(); + foreach ($replacements as $key => $replacement) { + // Do not display !important after the fallback font name. + $replacement[5] = str_replace(' !important', '', $replacement[5]); + $fonts[$key . ' ' . $replacement[3]] = $replacement[5]; + } + + $form['fonts'][$row['filename_hash']] = array( + '#markup' => '<div>' . t('%file - <code>@replacements</code><br />', array( + '@replacements' => str_ireplace('array', '', print_r($fonts, TRUE)), + '%file' => $row['filename'], + )) . '</div>', + ); + } + if (!empty($fonts_with_no_replacements)) { + $fonts = array(); + foreach ($fonts_with_no_replacements as $key => $replacement) { + // Do not display !important after the fallback font name. + $replacement = str_replace(' !important', '', $replacement); + $fonts[$key . ' ' . $replacement] = $replacement; + } + + $form['fonts_not_async'][$row['filename_hash']] = array( + '#markup' => '<div>' . t('%file - <code>@replacements</code><br />', array( + '@replacements' => str_ireplace('array', '', print_r($fonts, TRUE)), + '%file' => $row['filename'], + )) . '</div>', + ); + } + } + $children = element_children($form['fonts']); + + // If no fonts are found; disable this module. + if (count($children) == 0) { + $form['advagg_font_fontfaceobserver']['#default_value'] = 0; + $form['advagg_font_fontfaceobserver']['#disabled'] = TRUE; + + 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. + $form['#submit'][] = 'advagg_font_admin_settings_form_submit'; + + return system_settings_form($form); +} + +/** + * Submit callback, clear out the advagg cache bin. + * + * @ingroup advagg_forms_callback + */ +function advagg_font_admin_settings_form_submit($form, &$form_state) { + // Clear caches. + advagg_cache_clear_admin_submit(); + + // Disable cookie and local storage if ffo is disabled. + if (empty($form_state['values']['advagg_font_fontfaceobserver'])) { + $form_state['values']['advagg_font_cookie'] = 0; + $form_state['values']['advagg_font_storage'] = 0; + } + // Disable no fout if cookies and local storage are disabled. + if (empty($form_state['values']['advagg_font_cookie']) + && empty($form_state['values']['advagg_font_storage']) + ) { + $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 new file mode 100644 index 0000000000..ef1ef3a040 --- /dev/null +++ b/sites/all/modules/contrib/advagg/advagg_font/advagg_font.advagg.inc @@ -0,0 +1,63 @@ +<?php + +/** + * @file + * Advanced aggregation css compression module. + */ + +/** + * @addtogroup advagg_hooks + * @{ + */ + +/** + * Implements hook_advagg_get_css_file_contents_alter(). + */ +function advagg_font_advagg_get_css_file_contents_alter(&$contents, $file, $aggregate_settings) { + // Do nothing if this is disabled. + if (empty($aggregate_settings['variables']['advagg_font_fontfaceobserver'])) { + return; + } + + list($replacements) = advagg_font_get_replacements_array($contents); + foreach ($replacements as $replace) { + $contents = str_replace($replace[0], $replace[1] . $replace[2], $contents); + } +} + +/** + * Implements hook_advagg_get_info_on_files_alter(). + * + * Used to add external font info for css file. + */ +function advagg_font_advagg_get_info_on_files_alter(&$return, $cached_data, $bypass_cache) { + $advagg_font_ffo = variable_get('advagg_font_fontfaceobserver', ADVAGG_FONT_FONTFACEOBSERVER); + if (!empty($advagg_font_ffo)) { + foreach ($return as &$info) { + // Skip if not a css file. + if (empty($info['fileext']) || $info['fileext'] !== 'css') { + continue; + } + // Get the file contents. + $file_contents = (string) @advagg_file_get_contents($info['data']); + if (!empty($file_contents)) { + // Get font names. + list($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]] = str_replace(array('"', "'"), '', $replace[3]); + } + } + } + unset($info); + } +} + +/** + * @} End of "addtogroup advagg_hooks". + */ diff --git a/sites/all/modules/contrib/advagg/advagg_font/advagg_font.info b/sites/all/modules/contrib/advagg/advagg_font/advagg_font.info new file mode 100644 index 0000000000..da55e629b5 --- /dev/null +++ b/sites/all/modules/contrib/advagg/advagg_font/advagg_font.info @@ -0,0 +1,14 @@ +name = AdvAgg Async Font Loader +description = Allows one to load fonts in an async manner +package = Advanced CSS/JS Aggregation +core = 7.x +dependencies[] = advagg +recommends[] = libraries + +configure = admin/config/development/performance/advagg/font + +; Information added by Drupal.org packaging script on 2020-11-19 +version = "7.x-2.35" +core = "7.x" +project = "advagg" +datestamp = "1605792717" diff --git a/sites/all/modules/contrib/advagg/advagg_font/advagg_font.inline.js b/sites/all/modules/contrib/advagg/advagg_font/advagg_font.inline.js new file mode 100644 index 0000000000..ded1a40c16 --- /dev/null +++ b/sites/all/modules/contrib/advagg/advagg_font/advagg_font.inline.js @@ -0,0 +1,39 @@ +/** + * @file + * Used to add a class to the top level element based off of cookies. + */ + +/** + * Get advagg cookies for fonts or localStorage. + * + * Changes the top level class to include the font name found in the cookie. + */ +function advagg_font_inline() { + 'use strict'; + // Cookie handler. + var fonts = document.cookie.split('advaggf'); + for (var i = 0; i < fonts.length; i++) { + var font = fonts[i].split('='); + var pos = font[0].indexOf('ont_'); + if (pos !== -1) { + // Only allow alpha numeric class names. + window.document.documentElement.className += ' ' + font[0].substr(4).replace(/[^a-zA-Z0-9-]/g, ''); + } + } + + // Local Storage handler. + if (Storage !== void 0) { + fonts = JSON.parse(localStorage.getItem('advagg_fonts')); + var current_time = new Date().getTime(); + for (var key in fonts) { + if (fonts[key] >= current_time) { + // Only allow alpha numeric class names. + window.document.documentElement.className += ' ' + key.replace(/[^a-zA-Z0-9-]/g, ''); + } + } + } +} + + +// Check cookies ASAP and set class. +advagg_font_inline(); 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 0000000000..24081aba97 --- /dev/null +++ b/sites/all/modules/contrib/advagg/advagg_font/advagg_font.install @@ -0,0 +1,61 @@ +<?php + +/** + * @file + * Handles Advanced Aggregation installation and upgrade tasks. + */ + +/** + * @addtogroup hooks + * @{ + */ + +/** + * Implements hook_requirements(). + */ +function advagg_font_requirements($phase) { + $requirements = array(); + // If not at runtime, return here. + if ($phase !== 'runtime') { + return $requirements; + } + + // Ensure translations don't break at install time. + $t = get_t(); + // Get config path. + $config_path = advagg_admin_config_root_path(); + + // See if module is on. + if (variable_get('advagg_font_fontfaceobserver', ADVAGG_FONT_FONTFACEOBSERVER) == 0) { + $requirements['advagg_font_not_on'] = array( + 'title' => $t('AdvAgg Font'), + 'severity' => REQUIREMENT_WARNING, + 'value' => $t('AdvAgg async font loading is disabled.'), + 'description' => $t('Go to the <a href="@settings">AdvAgg Async Font Loader settings page</a> and select an option other than disabled, or go to the <a href="@modules">modules page</a> and disable the "AdvAgg Async Font Loader" module.', array( + '@settings' => url($config_path . '/advagg/font'), + '@modules' => url('admin/modules', array( + 'fragment' => 'edit-modules-advanced-cssjs-aggregation', + )), + )), + ); + } + + // Check version. + $lib_name = 'fontfaceobserver'; + $module_name = 'advagg_css_compress'; + list($description, $info) = advagg_get_version_description($lib_name, $module_name); + if (!empty($description)) { + $requirements["{$module_name}_{$lib_name}_updates"] = array( + 'title' => $t('@module_name', array('@module_name' => $info['name'])), + 'severity' => REQUIREMENT_WARNING, + 'value' => $t('The @name library needs to be updated.', array('@name' => $lib_name)), + 'description' => $description, + ); + } + + return $requirements; +} + +/** + * @} End of "addtogroup hooks". + */ diff --git a/sites/all/modules/contrib/advagg/advagg_font/advagg_font.js b/sites/all/modules/contrib/advagg/advagg_font/advagg_font.js new file mode 100644 index 0000000000..8b031464f1 --- /dev/null +++ b/sites/all/modules/contrib/advagg/advagg_font/advagg_font.js @@ -0,0 +1,110 @@ +/** + * @file + * Used to add a class to the top level element when an external font is ready. + */ + +/* global Drupal:false */ + +/** + * Run the check. + * + * @param {string} key + * The class name to add to the html tag. + * @param {string} value + * The font name. + */ +function advagg_run_check(key, value) { + 'use strict'; + // Only run if window.FontFaceObserver is defined. + if (window.FontFaceObserver) { + // Only alpha numeric value. + key = key.replace(/[^a-zA-Z0-9-]/g, ''); + if (typeof window.FontFaceObserver.prototype.load === 'function') { + new window.FontFaceObserver(value).load().then(function () { + advagg_run_check_inner(key, value); + }, function () {}); + } + else { + new window.FontFaceObserver(value).check().then(function () { + advagg_run_check_inner(key, value); + }, function () {}); + } + } + else { + // Try again in 100 ms. + window.setTimeout(function () { + advagg_run_check(key, value); + }, 100); + } +} + +/** + * Run the check. + * + * @param {string} key + * The class name to add to the html tag. + * @param {string} value + * The font name. + */ +function advagg_run_check_inner(key, value) { + 'use strict'; + // Set Class. + if (parseInt(Drupal.settings.advagg_font_no_fout, 10) !== 1) { + window.document.documentElement.className += ' ' + key; + } + + // Set for a day. + var expire_date = new Date().getTime() + 86400 * 1000; + + if (Storage !== void 0 && parseInt(Drupal.settings.advagg_font_storage, 10) === 1) { + // Use local storage. + var fonts = JSON.parse(localStorage.getItem('advagg_fonts')); + if (!fonts) { + fonts = {}; + } + fonts[key] = expire_date; + localStorage.setItem('advagg_fonts', JSON.stringify(fonts)); + } + else if (parseInt(Drupal.settings.advagg_font_cookie, 10) === 1) { + // Use cookies if enabled and local storage not available. + expire_date = new Date(expire_date).toUTCString(); + document.cookie = 'advaggfont_' + key + '=' + value + + '; expires=' + expire_date + + '; domain=.' + document.location.hostname + + '; path=/'; + } +} + +/** + * 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) { + if (Drupal.settings.advagg_font.hasOwnProperty(key)) { + var html_class = (' ' + window.document.documentElement.className + ' ').indexOf(' ' + key + ' '); + // If the class already exists in the html element do nothing. + if (html_class === -1) { + // Wait till the font is downloaded, then set cookie & class. + advagg_run_check(key, Drupal.settings.advagg_font[key]); + } + } + } +} + +/** + * Make sure window.Drupal.settings.advagg_font is defined before running. + */ +function advagg_font_check() { + 'use strict'; + if (window.Drupal && window.Drupal.settings && window.Drupal.settings.advagg_font) { + advagg_font_add_font_classes_on_load(); + } + else { + // Try again in 20 ms. + window.setTimeout(advagg_font_check, 20); + } +} + +// Start the process. +advagg_font_check(); diff --git a/sites/all/modules/contrib/advagg/advagg_font/advagg_font.module b/sites/all/modules/contrib/advagg/advagg_font/advagg_font.module new file mode 100644 index 0000000000..9f3a9c8686 --- /dev/null +++ b/sites/all/modules/contrib/advagg/advagg_font/advagg_font.module @@ -0,0 +1,542 @@ +<?php + +/** + * @file + * Advanced aggregation font module. + */ + +/** + * @addtogroup default_variables + * @{ + */ + +/** + * Default value to use font face observer for asynchronous font loading. + */ +define('ADVAGG_FONT_FONTFACEOBSERVER', 0); + +/** + * Default value to include font info in critical css. + */ +define('ADVAGG_FONT_ADD_TO_CRITICAL_CSS', 1); + +/** + * Default value to use localStorage in order to prevent the FOUT. + */ +define('ADVAGG_FONT_STORAGE', 1); + +/** + * 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); + +/** + * @} End of "addtogroup default_variables". + */ + +/** + * @addtogroup hooks + * @{ + */ + +/** + * 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_alter() { + // Skip if advagg is disabled. + if (!advagg_enabled()) { + return; + } + $advagg_font_ffo = variable_get('advagg_font_fontfaceobserver', ADVAGG_FONT_FONTFACEOBSERVER); + // Fontface Observer is disabled. + if (empty($advagg_font_ffo)) { + return; + } + + // Add settings. + drupal_add_js(array( + 'advagg_font' => array(), + 'advagg_font_storage' => variable_get('advagg_font_storage', ADVAGG_FONT_STORAGE), + '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), + ), array('type' => 'setting')); + + // Add inline script for reading the cookies and adding the fonts already + // loaded to the html class. + if (variable_get('advagg_font_cookie', ADVAGG_FONT_COOKIE) || variable_get('advagg_font_storage', ADVAGG_FONT_STORAGE)) { + $inline_script_min = 'for(var fonts=document.cookie.split("advaggf"),i=0;i<fonts.length;i++){var font=fonts[i].split("="),pos=font[0].indexOf("ont_");-1!==pos&&(window.document.documentElement.className+=" "+font[0].substr(4).replace(/[^a-zA-Z0-9\-]/g,""))}if(void 0!==Storage){fonts=JSON.parse(localStorage.getItem("advagg_fonts"));var current_time=(new Date).getTime();for(var key in fonts)fonts[key]>=current_time&&(window.document.documentElement.className+=" "+key.replace(/[^a-zA-Z0-9\-]/g,""))}'; + drupal_add_js($inline_script_min, array( + 'type' => 'inline', + 'group' => JS_LIBRARY - 1, + 'weight' => -50000, + 'scope' => 'above_css', + 'scope_lock' => TRUE, + 'movable' => FALSE, + 'no_defer' => TRUE, + )); + } + + // Get library data for fontfaceobserver. + $library = advagg_get_library('fontfaceobserver', 'advagg_font'); + // If libraries_load() does not exist load library externally. + if (!is_callable('libraries_load')) { + $advagg_font_ffo = 6; + } + // Add fontfaceobserver.js. + if ($advagg_font_ffo != 6 && empty($library['installed'])) { + // The fontfaceobserver library is not installed; use external variant. + $advagg_font_ffo = 6; + } + if ($advagg_font_ffo == 6) { + // Use the external variant. + foreach ($library['variants']['external']['files']['js'] as $data => $options) { + drupal_add_js($data, $options); + } + } + else { + // Load the fontfaceobserver library. + if ($advagg_font_ffo == 2) { + // Use the inline variant. + libraries_load('fontfaceobserver', 'inline'); + } + else { + libraries_load('fontfaceobserver'); + } + } + + // 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'; + drupal_add_js($file_path, array( + 'async' => TRUE, + 'defer' => TRUE, + )); +} + +/** + * Implements hook_css_alter(). + */ +function advagg_css_alter(&$css) { + // Skip if advagg is disabled. + if (!advagg_enabled()) { + return; + } + + // Skip if fontface is disabled. + if (empty(variable_get('advagg_font_fontfaceobserver', ADVAGG_FONT_FONTFACEOBSERVER))) { + return; + } + + // Skip if fonts added to critical css is disabled. + if (empty(variable_get('advagg_font_add_to_critical_css', ADVAGG_FONT_ADD_TO_CRITICAL_CSS))) { + return; + } + + $critical_css_key = NULL; + foreach ($css as $key => $values) { + if (!empty($values['critical-css']) && $values['type'] === 'inline') { + $critical_css_key = $key; + } + } + + // Skip if no critical css. + if (is_null($critical_css_key)) { + return; + } + + module_load_include('inc', 'advagg', 'advagg'); + $css_to_add = ''; + foreach ($css as $key => $values) { + if ($values['type'] === 'file') { + $info = advagg_get_info_on_file($key); + if (!empty($info['advagg_font'])) { + // Get the file contents. + $file_contents = (string) @advagg_file_get_contents($info['data']); + if (empty($file_contents)) { + continue; + } + list($replacements) = advagg_font_get_replacements_array($file_contents); + foreach ($replacements as $replace) { + $css_to_add .= $replace[2]; + } + } + } + } + if (!empty($css_to_add)) { + $css[$critical_css_key]['data'] .= "\n{$css_to_add}"; + } +} + +/** + * Implements hook_menu(). + */ +function advagg_font_menu() { + $file_path = drupal_get_path('module', 'advagg_font'); + $config_path = advagg_admin_config_root_path(); + + $items[$config_path . '/advagg/font'] = array( + 'title' => 'Async Font Loader', + 'description' => 'Load external fonts in a non blocking manner.', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('advagg_font_admin_settings_form'), + 'type' => MENU_LOCAL_TASK, + 'access arguments' => array('administer site configuration'), + 'file path' => $file_path, + 'file' => 'advagg_font.admin.inc', + 'weight' => 10, + ); + + return $items; +} + +/** + * @} End of "addtogroup hooks". + */ + +/** + * @addtogroup 3rd_party_hooks + * @{ + */ + +/** + * Implements hook_libraries_info(). + */ +function advagg_font_libraries_info() { + $libraries['fontfaceobserver'] = array( + // Only used in administrative UI of Libraries API. + 'name' => 'fontfaceobserver', + 'vendor url' => 'https://github.com/bramstein/fontfaceobserver', + 'download url' => 'https://github.com/bramstein/fontfaceobserver/archive/master.zip', + 'version arguments' => array( + 'file' => 'package.json', + // 1.50. : "version": "1.5.0". + 'pattern' => '/"version":\\s+"([0-9\.]+)"/', + 'lines' => 10, + ), + 'remote' => array( + 'callback' => 'advagg_get_github_version_json', + 'url' => 'https://cdn.jsdelivr.net/gh/bramstein/fontfaceobserver@master/package.json', + ), + 'files' => array( + 'js' => array( + 'fontfaceobserver.js' => array( + 'type' => 'file', + 'group' => JS_LIBRARY, + 'async' => TRUE, + 'defer' => TRUE, + ), + ), + ), + 'variants' => array(), + ); + // Get the latest tagged version for external file loading. + $version = advagg_get_remote_libraries_version('fontfaceobserver', $libraries['fontfaceobserver']); + $libraries['fontfaceobserver']['variants'] += array( + 'external' => array( + 'files' => array( + 'js' => array( + "https://cdn.jsdelivr.net/gh/bramstein/fontfaceobserver@v{$version}/fontfaceobserver.js" => array( + 'type' => 'external', + 'data' => "https://cdn.jsdelivr.net/gh/bramstein/fontfaceobserver@v{$version}/fontfaceobserver.js", + 'async' => TRUE, + 'defer' => TRUE, + ), + ), + ), + ), + ); + // Inline if local js is there. + $libraries_paths = array(); + if (is_callable('libraries_get_libraries')) { + $libraries_paths = libraries_get_libraries(); + } + if (!empty($libraries_paths['fontfaceobserver']) && is_readable($libraries_paths['fontfaceobserver'] . '/fontfaceobserver.js')) { + $libraries['fontfaceobserver']['variants'] += array( + 'inline' => array( + 'files' => array( + 'js' => array( + 'loadCSS_inline' => array( + 'type' => 'inline', + 'data' => (string) @advagg_file_get_contents($libraries_paths['fontfaceobserver'] . '/fontfaceobserver.js'), + 'no_defer' => TRUE, + ), + ), + ), + ), + ); + } + + return $libraries; +} + +/** + * @} End of "addtogroup 3rd_party_hooks". + */ + +/** + * @addtogroup advagg_hooks + * @{ + */ + +/** + * Implements hook_advagg_current_hooks_hash_array_alter(). + */ +function advagg_font_advagg_current_hooks_hash_array_alter(&$aggregate_settings) { + $aggregate_settings['variables']['advagg_font_fontfaceobserver'] = variable_get('advagg_font_fontfaceobserver', ADVAGG_FONT_FONTFACEOBSERVER); +} + +/** + * @} End of "addtogroup advagg_hooks". + */ + +/** + * Get the replacements array for the css. + * + * @param string $css_string + * String of CSS. + * + * @return array + * An array containing the replacemnts and the font class name. + */ +function advagg_font_get_replacements_array($css_string) { + // Get the CSS that contains a font-family rule. + $length = strlen($css_string); + $property_position = 0; + $property = 'font'; + $property_alt = 'font-family'; + $replacements = array(); + $fonts_with_no_replacements = array(); + $lower = strtolower($css_string); + $safe_fonts_list = array( + 'georgia' => TRUE, + 'palatino' => TRUE, + 'times new roman' => TRUE, + 'times' => TRUE, + + 'arial' => TRUE, + 'helvetica' => TRUE, + 'gadget' => TRUE, + 'verdana' => TRUE, + 'geneva' => TRUE, + 'tahoma' => TRUE, + 'garamond' => TRUE, + 'bookman' => TRUE, + 'comic sans ms' => TRUE, + 'cursive' => TRUE, + 'trebuchet ms' => TRUE, + 'arial black' => TRUE, + 'impact' => TRUE, + 'charcoal' => TRUE, + + 'courier new' => TRUE, + 'courier' => TRUE, + 'monaco' => TRUE, + + 'system' => TRUE, + ); + + while (($property_position = strpos($lower, $property, $property_position)) !== FALSE) { + // Find the start of the values for the property. + $start_of_values = strpos($css_string, ':', $property_position); + // Get the property at this location of the css. + $property_in_loop = trim(substr($css_string, $property_position, ($start_of_values - $property_position))); + + // Make sure this property is one of the ones we're looking for. + if ($property_in_loop !== $property && $property_in_loop !== $property_alt) { + $property_position += strlen($property); + continue; + } + + // 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 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_a = strpos($css_string, ';', $property_position); + $declaration_end_b = strpos($css_string, '}', $property_position); + if ($declaration_end_a === FALSE) { + $declaration_end = $declaration_end_b; + } + else { + $declaration_end = min($declaration_end_a, $declaration_end_b); + } + if ($declaration_end > $end) { + $declaration_end = $end; + } + // Add one in order to capture the } when we ge the full rule set. + $end++; + // Advance position for the next run of the while loop. + $property_position = $end; + + // Get values assigned to this property. + $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); + if (empty($values_array)) { + continue; + } + + // Values array, first element is a quoted string. + $dq = strpos($values_array[0], '"'); + $sq = strpos($values_array[0], "'"); + $quote_pos = ($sq !== FALSE) ? $sq : $dq; + // Skip if the first font is not quoted. + if ($quote_pos === FALSE) { + continue; + } + + $values_array[0] = trim($values_array[0]); + // Skip if only one font is listed. + if (count($values_array) === 1) { + $fonts_with_no_replacements[$values_array[0]] = ''; + continue; + } + + // Save the first value to a variable; starting at the quote. + $removed_value_original = substr($values_array[0], max($quote_pos - 1, 0)); + + // Resave first value. + if ($quote_pos > 1) { + $values_array[0] = trim(substr($values_array[0], 0, $quote_pos - 1)); + } + + // 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. + $values_array_copy = $values_array; + foreach ($values_array as $key => $value) { + if (strpos($value, '"') !== FALSE || strpos($value, "'") !== FALSE) { + unset($values_array[$key]); + } + elseif ($key !== 0) { + break; + } + } + + if (empty($values_array)) { + // See if there's a "safe" fallback that is quoted. + $values_array = $values_array_copy; + foreach ($values_array as $key => $value) { + if (strpos($value, '"') !== FALSE || strpos($value, "'") !== FALSE) { + if ($key !== 0) { + $lower_key = trim(trim(strtolower(trim($value)), '"'), "'"); + if (!empty($safe_fonts_list[$lower_key])) { + break; + } + } + unset($values_array[$key]); + } + elseif ($key !== 0) { + break; + } + } + if (empty($values_array)) { + // No unquoted values left; do not modify the css. + $key = array_shift($values_array_copy); + $fonts_with_no_replacements[$key] = implode(',', $values_array_copy); + continue; + } + } + + $extra = ''; + if (isset($values_array[0])) { + $extra = $values_array[0] . ' '; + unset($values_array[0]); + } + // 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 = $extra . 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 (stripos($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. + // Regexp to match comment blocks. + // Regexp to match double quoted strings. + // Regexp to match single quoted strings. + $comment = '/\*[^*]*\*+(?:[^/*][^*]*\*+)*/'; + $double_quot = '"[^"\\\\]*(?:\\\\.[^"\\\\]*)*"'; + $single_quot = "'[^'\\\\]*(?:\\\\.[^'\\\\]*)*'"; + // Strip all comment blocks, but keep double/single quoted strings. + $selectors_stripped = preg_replace( + "<($double_quot|$single_quot)|$comment>Ss", + "$1", + $selectors + ); + + // Add css class to all the selectors. + $selectors_array = explode(',', $selectors_stripped); + foreach ($selectors_array as &$selector) { + // Remove extra whitespace. + $selector = trim($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_string, $full_rule_set); + // Add in old rule set with new selectors. + $new_selectors_full_rule_set = $new_selectors . '{' . $property_in_loop . ': ' . $values_string . ';}'; + + // Record info. + $replacements[] = array( + $full_rule_set, + $new_values_full_rule_set, + $new_selectors_full_rule_set, + $removed_value_original, + $removed_value_classname, + $next_value_original, + ); + } + return array($replacements, $fonts_with_no_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 4abc1d89c3..d7a6735cf7 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,8 @@ package = Advanced CSS/JS Aggregation core = 7.x dependencies[] = advagg -; Information added by Drupal.org packaging script on 2015-04-14 -version = "7.x-2.8" +; Information added by Drupal.org packaging script on 2020-11-19 +version = "7.x-2.35" core = "7.x" project = "advagg" -datestamp = "1429049283" - +datestamp = "1605792717" diff --git a/sites/all/modules/contrib/advagg/advagg_js_cdn/advagg_js_cdn.install b/sites/all/modules/contrib/advagg/advagg_js_cdn/advagg_js_cdn.install index 61f9ae193a..39502ea5c3 100644 --- a/sites/all/modules/contrib/advagg/advagg_js_cdn/advagg_js_cdn.install +++ b/sites/all/modules/contrib/advagg/advagg_js_cdn/advagg_js_cdn.install @@ -5,6 +5,11 @@ * Handles Advanced Aggregation installation and upgrade tasks. */ +/** + * @addtogroup hooks + * @{ + */ + /** * Implements hook_requirements(). */ @@ -52,9 +57,13 @@ function advagg_js_cdn_requirements($phase) { 'title' => $t('Adv JS CDN'), 'severity' => REQUIREMENT_OK, 'value' => $t('OK'), - 'description' => $t('jQuery & jQuery UI JS should be coming from a CDN.') . ' ' . $description, + 'description' => $t('jQuery and jQuery UI JS should be coming from a CDN.') . ' ' . $description, ); } return $requirements; } + +/** + * @} End of "addtogroup hooks". + */ 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 74fefb1753..bcb164d83a 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 @@ -5,6 +5,11 @@ * Advanced aggregation js cdn module. */ +/** + * @addtogroup default_variables + * @{ + */ + /** * Default value to see if jquery should be grabbed from the Google CDN. */ @@ -30,11 +35,20 @@ define('ADVAGG_JS_CDN_JQUERY_UI_VERSION', '1.8.7'); */ define('ADVAGG_JS_CDN_COMPRESSION', TRUE); +/** + * @} End of "addtogroup default_variables". + */ + +/** + * @addtogroup hooks + * @{ + */ + /** * 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; } @@ -52,13 +66,11 @@ function advagg_js_cdn_js_alter(&$javascript) { $add_in_ui = FALSE; foreach ($javascript as $name => $values) { - // @ignore sniffer_commenting_inlinecomment_spacingbefore:4 - // @ignore sniffer_whitespace_openbracketspacing_openingwhitespace:8 // Only modify if // advagg_js_cdn_jquery is enabled, // name is misc/jquery.js, // and type is file. - if ( variable_get('advagg_js_cdn_jquery', ADVAGG_JS_CDN_JQUERY) + if (variable_get('advagg_js_cdn_jquery', ADVAGG_JS_CDN_JQUERY) && $name === 'misc/jquery.js' && $javascript[$name]['type'] === 'file' ) { @@ -73,13 +85,11 @@ function advagg_js_cdn_js_alter(&$javascript) { $javascript[$name]['type'] = 'external'; } - // @ignore sniffer_commenting_inlinecomment_spacingbefore:4 - // @ignore sniffer_whitespace_openbracketspacing_openingwhitespace:8 // Only modify if // advagg_js_cdn_jquery_ui is enabled, // name is in the $ui_mapping array. // and type is file. - if ( variable_get('advagg_js_cdn_jquery_ui', ADVAGG_JS_CDN_JQUERY_UI) + if (variable_get('advagg_js_cdn_jquery_ui', ADVAGG_JS_CDN_JQUERY_UI) && array_key_exists($name, $ui_mapping) && $javascript[$name]['type'] === 'file' ) { @@ -103,6 +113,10 @@ function advagg_js_cdn_js_alter(&$javascript) { } } +/** + * @} End of "addtogroup hooks". + */ + /** * Return an array of jquery ui files. * 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 bb7721ff40..3d9a309456 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 * @@ -712,7 +715,7 @@ $.widget("ui.mouse", { return this.mouseDelayMet; }, - // These are placeholder methods, to be overriden by extending plugin + // These are placeholder methods, to be overridden by extending plugin _mouseStart: function(event) {}, _mouseDrag: function(event) {}, _mouseStop: function(event) {}, @@ -2801,7 +2804,7 @@ $.widget("ui.selectable", $.ui.mouse, { }); } } else { - // UNSELECT + // DESELECT if (selectee.selecting) { if (event.metaKey && selectee.startselected) { selectee.$element.removeClass('ui-selecting'); @@ -3839,7 +3842,7 @@ $.widget("ui.sortable", $.ui.mouse, { //Various things done here to improve the performance: // 1. we create a setTimeout, that calls refreshPositions - // 2. on the instance, we have a counter variable, that get's higher after every append + // 2. on the instance, we have a counter variable, that gets higher after every append // 3. on the local scope, we copy the counter variable, and check in the timeout, if it's still the same // 4. this lets only the last addition to the timeout stack through this.counter = this.counter ? ++this.counter : 1; @@ -4956,7 +4959,7 @@ $.effects.explode = function(o) { var el = $(this).show().css('visibility', 'hidden'); var offset = el.offset(); - //Substract the margins - not fixing the problem yet. + //Subtract the margins - not fixing the problem yet. offset.top -= parseInt(el.css("marginTop"),10) || 0; offset.left -= parseInt(el.css("marginLeft"),10) || 0; @@ -7195,7 +7198,7 @@ function Datepicker() { onChangeMonthYear: null, // Define a callback function when the month or year is changed onClose: null, // Define a callback function when the datepicker is closed numberOfMonths: 1, // Number of months to show at a time - showCurrentAtPos: 0, // The position in multipe months at which to show the current month (starting at 0) + showCurrentAtPos: 0, // The position in multiple months at which to show the current month (starting at 0) stepMonths: 1, // Number of months to step back/forward stepBigMonths: 12, // Number of months to step back/forward for the big links altField: '', // Selector for an alternate field to store selected dates into @@ -7800,7 +7803,7 @@ $.extend(Datepicker.prototype, { if (inst == $.datepicker._curInst && $.datepicker._datepickerShowing && inst.input && inst.input.is(':visible') && !inst.input.is(':disabled')) inst.input.focus(); - // deffered render of the years select (to avoid flashes on Firefox) + // deferred render of the years select (to avoid flashes on Firefox) if( inst.yearshtml ){ var origyearshtml = inst.yearshtml; setTimeout(function(){ @@ -8547,20 +8550,20 @@ $.extend(Datepicker.prototype, { for (var col = 0; col < numMonths[1]; col++) { var selectedDate = this._daylightSavingAdjust(new Date(drawYear, drawMonth, inst.selectedDay)); var cornerClass = ' ui-corner-all'; - var calender = ''; + var calendar = ''; if (isMultiMonth) { - calender += '<div class="ui-datepicker-group'; + calendar += '<div class="ui-datepicker-group'; if (numMonths[1] > 1) switch (col) { - case 0: calender += ' ui-datepicker-group-first'; + case 0: calendar += ' ui-datepicker-group-first'; cornerClass = ' ui-corner-' + (isRTL ? 'right' : 'left'); break; - case numMonths[1]-1: calender += ' ui-datepicker-group-last'; + case numMonths[1]-1: calendar += ' ui-datepicker-group-last'; cornerClass = ' ui-corner-' + (isRTL ? 'left' : 'right'); break; - default: calender += ' ui-datepicker-group-middle'; cornerClass = ''; break; + default: calendar += ' ui-datepicker-group-middle'; cornerClass = ''; break; } - calender += '">'; + calendar += '">'; } - calender += '<div class="ui-datepicker-header ui-widget-header ui-helper-clearfix' + cornerClass + '">' + + calendar += '<div class="ui-datepicker-header ui-widget-header ui-helper-clearfix' + cornerClass + '">' + (/all|left/.test(cornerClass) && row == 0 ? (isRTL ? next : prev) : '') + (/all|right/.test(cornerClass) && row == 0 ? (isRTL ? prev : next) : '') + this._generateMonthYearHeader(inst, drawMonth, drawYear, minDate, maxDate, @@ -8573,7 +8576,7 @@ $.extend(Datepicker.prototype, { thead += '<th' + ((dow + firstDay + 6) % 7 >= 5 ? ' class="ui-datepicker-week-end"' : '') + '>' + '<span title="' + dayNames[day] + '">' + dayNamesMin[day] + '</span></th>'; } - calender += thead + '</tr></thead><tbody>'; + calendar += thead + '</tr></thead><tbody>'; var daysInMonth = this._getDaysInMonth(drawYear, drawMonth); if (drawYear == inst.selectedYear && drawMonth == inst.selectedMonth) inst.selectedDay = Math.min(inst.selectedDay, daysInMonth); @@ -8581,7 +8584,7 @@ $.extend(Datepicker.prototype, { var numRows = (isMultiMonth ? 6 : Math.ceil((leadDays + daysInMonth) / 7)); // calculate the number of rows to generate var printDate = this._daylightSavingAdjust(new Date(drawYear, drawMonth, 1 - leadDays)); for (var dRow = 0; dRow < numRows; dRow++) { // create date picker rows - calender += '<tr>'; + calendar += '<tr>'; var tbody = (!showWeek ? '' : '<td class="ui-datepicker-week-col">' + this._get(inst, 'calculateWeek')(printDate) + '</td>'); for (var dow = 0; dow < 7; dow++) { // create date picker days @@ -8613,16 +8616,16 @@ $.extend(Datepicker.prototype, { printDate.setDate(printDate.getDate() + 1); printDate = this._daylightSavingAdjust(printDate); } - calender += tbody + '</tr>'; + calendar += tbody + '</tr>'; } drawMonth++; if (drawMonth > 11) { drawMonth = 0; drawYear++; } - calender += '</tbody></table>' + (isMultiMonth ? '</div>' + + calendar += '</tbody></table>' + (isMultiMonth ? '</div>' + ((numMonths[0] > 0 && col == numMonths[1]-1) ? '<div class="ui-datepicker-row-break"></div>' : '') : ''); - group += calender; + group += calendar; } html += group; } @@ -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 74cfb727e2..461abcb255 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 @@ -8,12 +8,14 @@ /** * Form builder; Configure advagg settings. * - * @ingroup forms + * @ingroup advagg_forms * * @see system_settings_form() */ function advagg_js_compress_admin_settings_form($form, $form_state) { drupal_set_title(t('AdvAgg: JS Compression')); + $requirements = advagg_js_compress_check_cache_bin(); + advagg_display_message_if_requirements_not_met($requirements); $config_path = advagg_admin_config_root_path(); $form = array(); @@ -22,37 +24,27 @@ function advagg_js_compress_admin_settings_form($form, $form_state) { '#markup' => '<p>' . t('The settings below will not have any effect because AdvAgg is currently in <a href="@devel">development mode</a>. Once the cache settings have been set to normal or aggressive, JS minification will take place.', array('@devel' => url($config_path . '/advagg', array('fragment' => 'edit-advagg-cache-level')))) . '</p>', ); } + list($list, $redo_list) = advagg_js_compress_all_js_files_list(); - $description = ''; - $options = array( - 0 => t('Disabled'), - 1 => t('JSMin+ ~1300ms'), - // 2 => t('Packer ~500ms'), - // 3 is JSMin c extension. - // 4 is JShrink. - // 5 is JSqueeze. - ); - if (function_exists('jsmin')) { - $options[3] = t('JSMin ~2ms'); - $description .= t('JSMin is the very fast C complied version. Recommend using it.'); - } - else { - if (!defined('PHP_VERSION_ID') || PHP_VERSION_ID < 50310) { - $link = 'http://www.ypass.net/software/php_jsmin/'; - } - else { - $link = 'https://github.com/sqmk/pecl-jsmin/'; - } - $description .= t('You can use the much faster C version of JSMin (~2ms) by installing the <a href="@php_jsmin">JSMin PHP Extension</a> on this server.', array('@php_jsmin' => $link)); - } - // Add in JShrink & JSqueeze if using php 5.3 or higher. - if (defined('PHP_VERSION_ID') & PHP_VERSION_ID >= 50300) { - $options += array( - 4 => t('JShrink ~1000ms'), - 5 => t('JSqueeze ~600ms'), + if (!empty($redo_list)) { + $form['advagg_js_compressor_batch'] = array( + '#markup' => '<p>' . t('There are %count js files that need to be minified. <a href="@batch">Click this batch compress link to process these files.</a>', array( + '@batch' => url($config_path . '/advagg/js-compress/batch'), + '%count' => count($redo_list), + )) . '</p>', + ); + $form['advagg_js_compressor_redo_list'] = array( + '#type' => 'fieldset', + '#title' => t('List of files that need to be recompressed'), + '#collapsible' => TRUE, + '#collapsed' => TRUE, + ); + $form['advagg_js_compressor_redo_list']['list'] = array( + '#markup' => '<p><pre><tt>' . (print_r($redo_list, TRUE)) . '</tt></pre></p>', ); } + list($options, $description) = advagg_js_compress_configuration(); $form['advagg_js_compressor'] = array( '#type' => 'radios', '#title' => t('File Compression: Select a Compressor'), @@ -80,44 +72,44 @@ function advagg_js_compress_admin_settings_form($form, $form_state) { ), ); - $form['advagg_js_compress_packer'] = array( - '#type' => 'checkbox', - '#title' => t('Use Packer on non GZip JS Aggregates'), - '#default_value' => variable_get('advagg_js_compress_packer', ADVAGG_JS_COMPRESS_PACKER), - '#description' => t('If enabled the non gzip version of JS files will be compressed using the JS Packer. Packer works similar to gzip, thus using packer on a gzipped file does not give a big improvement in terms of bytes transferred over the wire. WARNING: This has a high chance of breaking your JS. Only Enable on production after testing the non gzipped version locally.'), - '#states' => array( - 'disabled' => array( - ':input[name="advagg_js_compressor"]' => array('value' => "0"), + if (variable_get('advagg_gzip', ADVAGG_GZIP) || variable_get('advagg_brotli', ADVAGG_BROTLI)) { + $form['advagg_js_compress_packer'] = array( + '#type' => 'checkbox', + '#title' => t('Here there be dragons! Use Packer on non gz/br JS Aggregates.'), + '#default_value' => variable_get('advagg_js_compress_packer', ADVAGG_JS_COMPRESS_PACKER), + '#description' => t('If enabled the non gzip/brotli version of JS files will be compressed using the JS Packer. Packer works similar to gz/br, thus using packer on a gzip/brotli file does not give a big improvement in terms of bytes transferred over the wire. WARNING: This has a high chance of breaking your JS. Only Enable on production after testing the non gzip/brotli version locally.'), + '#states' => array( + 'disabled' => array( + ':input[name="advagg_js_compressor"]' => array('value' => "0"), + ), ), - ), - ); + ); + } $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 (smallest files)'), + 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'); ksort($options); - $form['per_file_settings'] = array( '#type' => 'fieldset', '#title' => t('Per File Settings'), '#collapsible' => TRUE, '#collapsed' => TRUE, ); - // Get filename & filename_hash. - $results = db_select('advagg_files', 'af') - ->fields('af', array('filename')) - ->condition('filetype', 'js') - ->orderBy('af.filename', 'ASC') - ->execute(); $file_settings = variable_get('advagg_js_compressor_file_settings', array()); - foreach ($results as $row) { - $dir = dirname($row->filename); + foreach ($list as $row) { + $dir = dirname($row['data']); if (!isset($form['per_file_settings'][$dir])) { $form['per_file_settings'][$dir] = array( '#type' => 'fieldset', @@ -126,12 +118,25 @@ function advagg_js_compress_admin_settings_form($form, $form_state) { '#collapsed' => TRUE, ); } - $form_api_filename = str_replace(array('/', '.'), array('__', '--'), $row->filename); + $options_copy = $options; + if (!empty($row['advagg_js_compress'])) { + foreach ($row['advagg_js_compress'] as $key => $values) { + if (!isset($options_copy[$key])) { + continue; + } + if ($values['code'] != 1) { + unset($options_copy[$key]); + continue; + } + $options_copy[$key] .= " Ratio: {$values['ratio']}"; + } + } + $form_api_filename = str_replace(array('/', '.'), array('__', '--'), $row['data']); $form['per_file_settings'][$dir]['advagg_js_compressor_file_settings_' . $form_api_filename] = array( '#type' => 'radios', - '#title' => t('%filename: Select a Compressor', array('%filename' => $row->filename)), + '#title' => t('%filename: Select a Compressor', array('%filename' => $row['data'])), '#default_value' => isset($file_settings[$form_api_filename]) ? $file_settings[$form_api_filename] : ADVAGG_JS_COMPRESSOR_FILE_SETTINGS, - '#options' => $options, + '#options' => $options_copy, ); if ($form['per_file_settings'][$dir]['advagg_js_compressor_file_settings_' . $form_api_filename]['#default_value'] != ADVAGG_JS_COMPRESSOR_FILE_SETTINGS) { $form['per_file_settings'][$dir]['#collapsed'] = FALSE; @@ -139,23 +144,26 @@ function advagg_js_compress_admin_settings_form($form, $form_state) { } } + // No js files are found. + if (empty($list)) { + $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'; return system_settings_form($form); } -// Submit callback. /** - * Clear out the advagg cache bin when the save configuration button is pressed. + * Submit callback, clear out the advagg cache bin. * * Also remove default settings inside of the per_file_settings fieldgroup. + * + * @ingroup advagg_forms_callback */ 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()); @@ -163,7 +171,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; } @@ -173,6 +181,9 @@ function advagg_js_compress_admin_settings_form_submit($form, &$form_state) { continue; } $new_settings[substr($key, 35)] = $value; + + // Do not save this field into its own variable. + unset($form_state['values'][$key]); } if (!empty($new_settings) || !empty($file_settings)) { if (empty($new_settings)) { 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 86cb5bb114..d05a1f7bd0 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 @@ -5,89 +5,112 @@ * Advanced CSS/JS aggregation js compression module. */ +if (defined('PHP_VERSION_ID') && constant('PHP_VERSION_ID') >= 50300) { + // Include functions that use namespaces. + module_load_include('inc', 'advagg_js_compress', 'advagg_js_compress.php53'); +} + +/** + * @addtogroup advagg_hooks + * @{ + */ + /** * Implements hook_advagg_get_info_on_files_alter(). * * Used to make sure the info is up to date in the cache. */ function advagg_js_compress_advagg_get_info_on_files_alter(&$return, $cached_data, $bypass_cache) { - // Do nothing if compressors are disabled. - $compressors = advagg_js_compress_get_enabled_compressors(); - if (empty($compressors)) { - return; - } + $compressor_list = advagg_js_compress_get_enabled_compressors(array(), -1); + // Get cache ids. $cache_ids = array(); foreach ($return as $filename => &$info) { if (empty($info['fileext']) || $info['fileext'] !== 'js') { continue; } - // New or updated data or no advagg_js_compress data. - // @ignore sniffer_whitespace_openbracketspacing_openingwhitespace - if ( empty($cached_data[$info['cache_id']]) - || empty($info['advagg_js_compress']) - || $info['content_hash'] != $cached_data[$info['cache_id']]['content_hash'] - ) { - // Check the cache. - $cache_id = 'advagg:js_compress:info:' . $info['filename_hash']; - $cache_id .= !empty($info['content_hash']) ? ':' . $info['content_hash'] : ''; - $cache_ids[$filename] = $cache_id; - } - else { - foreach ($compressors as $id => $name) { - if (empty($info['advagg_js_compress'][$id])) { - $cache_id = 'advagg:js_compress:info:' . $info['filename_hash']; - $cache_id .= !empty($info['content_hash']) ? ':' . $info['content_hash'] : ''; - $cache_ids[$filename] = $cache_id; - break; + // Check the cache. + $cache_id = 'advagg:js_compress:info:' . $info['filename_hash']; + $cache_id .= !empty($info['content_hash']) ? ':' . $info['content_hash'] : ''; + $cache_ids[$filename] = $cache_id; + + // Verify current data. + $advagg_js_compress = array(); + if (!empty($info['advagg_js_compress'])) { + foreach ($info['advagg_js_compress'] as $values) { + $array_key = array_search($values['name'], $compressor_list); + if ($array_key !== FALSE) { + $cache_hits_data[$array_key] = $values; } } } + ksort($advagg_js_compress); + $info['advagg_js_compress'] = $advagg_js_compress; } - unset($info); + unset($info); // If no cache ids are found bail out. if (empty($cache_ids)) { return; } // Get cached values. - $cache_hits = array(); - if (!$bypass_cache) { - $values = array_values($cache_ids); - $cache_hits = cache_get_multiple($values, 'cache_advagg_info'); - } + $values = array_values($cache_ids); + $cache_hits = cache_get_multiple($values, 'cache_advagg_info'); + $compressors = advagg_js_compress_get_enabled_compressors(); + $advagg_get_info_on_file_cached_data = drupal_static('advagg_get_info_on_file'); + // Add cached values into $return. $filenames_info = array(); foreach ($cache_ids as $filename => $cache_id) { $info = &$return[$filename]; + // Add in cached values. if (!empty($cache_hits[$cache_id])) { - $info['advagg_js_compress'] = $cache_hits[$cache_id]->data; - foreach ($compressors as $id => $name) { - if (empty($info['advagg_js_compress'][$id])) { - // Generate values. - $filenames_info[$filename] = $info; - break; + // Verify cache data. + $cache_hits_data = array(); + foreach ($cache_hits[$cache_id]->data as $values) { + $array_key = array_search($values['name'], $compressor_list); + if ($array_key !== FALSE) { + $cache_hits_data[$array_key] = $values; } } + ksort($cache_hits_data); + + $info['advagg_js_compress'] = array_replace($info['advagg_js_compress'], $cache_hits_data); } - // Generate values. - else { - $filenames_info[$filename] = $info; + + // Generate missing values if needed. + foreach ($compressors as $id => $name) { + if (empty($info['advagg_js_compress'][$id])) { + $filenames_info[$filename] = $info; + break; + } + // Generate values if bypass cache is set and hashes do not match. + if ($bypass_cache + && (empty($advagg_get_info_on_file_cached_data[$info['cache_id']]['content_hash']) + || $info['content_hash'] !== $advagg_get_info_on_file_cached_data[$info['cache_id']]['content_hash'] + ) + ) { + $filenames_info[$filename] = $info; + break; + } } } + // Do nothing if compressors are disabled or cache level does not equal 0. + if (empty($compressors) || variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL) != 0) { + return; + } + if (!empty($filenames_info)) { $results = advagg_js_compress_run_mutiple_tests($filenames_info, $compressors); foreach ($results as $filename => $data) { $info = &$return[$filename]; if (!empty($info['advagg_js_compress'])) { - $info['advagg_js_compress'] += $data; - } - else { - $info['advagg_js_compress'] = $data; + $data += $info['advagg_js_compress']; } + $info['advagg_js_compress'] = $data; } } } @@ -117,12 +140,18 @@ function advagg_js_compress_advagg_get_js_file_contents_alter(&$contents, $filen $info = advagg_get_info_on_file($filename); if (!isset($info['advagg_js_compress'][$compressor]['code'])) { // Test file here on the spot. - $compressors_to_test = advagg_js_compress_get_enabled_compressors($aggregate_settings); - $info['advagg_js_compress'] = advagg_js_compress_run_test($filename, $info, $compressors_to_test); + if (variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL) == 0 + || variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL) == 1 + ) { + $compressors_to_test = advagg_js_compress_get_enabled_compressors($aggregate_settings); + $info['advagg_js_compress'] = advagg_js_compress_run_test($filename, $info, $compressors_to_test); + } } // Compress it if it passes the test. - if (!empty($info['advagg_js_compress'][$compressor]['code']) && $info['advagg_js_compress'][$compressor]['code'] == 1) { + if (!empty($info['advagg_js_compress'][$compressor]['code']) + && $info['advagg_js_compress'][$compressor]['code'] == 1 + ) { advagg_js_compress_prep($contents, $filename, $aggregate_settings); } } @@ -130,23 +159,25 @@ function advagg_js_compress_advagg_get_js_file_contents_alter(&$contents, $filen /** * Implements hook_advagg_save_aggregate_alter(). * - * Used to add in a .gz file if none exits & use packer on non gzip file. + * Used to add in a .gz file if none exits and use packer on non gzip file. */ function advagg_js_compress_advagg_save_aggregate_alter(&$files_to_save, $aggregate_settings, $other_parameters) { list($files, $type) = $other_parameters; - // Return if gzip is disabled. + // Return if gzip and brotli are disabled. // Return if packer is disabled. // Return if type is not js. - // @ignore sniffer_whitespace_openbracketspacing_openingwhitespace - if ( empty($aggregate_settings['variables']['advagg_gzip']) + if ((empty($aggregate_settings['variables']['advagg_gzip']) && empty($aggregate_settings['variables']['advagg_brotli'])) || empty($aggregate_settings['variables']['advagg_js_compress_packer']) || $type !== 'js' ) { return; } - // Use packer on non gzip js files. + // Use the first file in the array. + $data = reset($files_to_save); + $uri = key($files_to_save); + // Use packer on non gzip/brotli js files. $compressor = 2; module_load_include('inc', 'advagg', 'advagg'); @@ -161,8 +192,7 @@ function advagg_js_compress_advagg_save_aggregate_alter(&$files_to_save, $aggreg } // If this file causes php to bomb or the ratio is way too good then do not // use packer on this aggregate. - // @ignore sniffer_whitespace_openbracketspacing_openingwhitespace - if ( !isset($info['advagg_js_compress'][$compressor]['code']) + if (!isset($info['advagg_js_compress'][$compressor]['code']) || $info['advagg_js_compress'][$compressor]['code'] == -1 || $info['advagg_js_compress'][$compressor]['code'] == -3 ) { @@ -170,38 +200,16 @@ function advagg_js_compress_advagg_save_aggregate_alter(&$files_to_save, $aggreg } } - // See if a .gz file of this aggregate already exists. - $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 (!empty($pos)) { - $len = strlen($uri); - // .gz file exists, exit loop. - if ($pos == $len - 3) { - $gzip_exists = TRUE; - break; - } - } - } - - // Use the first file in the array. - $data = reset($files_to_save); - $uri = key($files_to_save); - - // If a .gz file does not exist, create one. - if (!$gzip_exists) { - // Compress it and add it to the $files_to_save array. - $compressed = gzencode($data, 9, FORCE_GZIP); - $files_to_save[$uri . '.gz'] = $compressed; - } - - // Use packer on non gzip JS data. + // Use packer on non gzip/brotli JS data. $aggregate_settings['variables']['advagg_js_compressor'] = $compressor; advagg_js_compress_prep($data, $uri, $aggregate_settings, FALSE); $files_to_save[$uri] = $data; } +/** + * @} End of "addtogroup advagg_hooks". + */ + /** * Get a list of enabled compressors. * @@ -212,22 +220,10 @@ function advagg_js_compress_advagg_save_aggregate_alter(&$files_to_save, $aggreg */ function advagg_js_compress_get_enabled_compressors(array $aggregate_settings = array(), $compressor = 0) { // Create array. - $compressors = array( - 1 => 'jsminplus', - 2 => 'packer', - ); - if (function_exists('jsmin')) { - $compressors[3] = 'jsmin'; - } + list(, , $compressors) = advagg_js_compress_configuration(); if ($compressor == -1) { return $compressors; } - if (defined('PHP_VERSION_ID') & PHP_VERSION_ID >= 50300) { - $compressors += array( - 4 => 'jshrink', - 5 => 'jsqueeze', - ); - } $return_compressors = array(); if (!empty($compressor)) { @@ -248,8 +244,8 @@ function advagg_js_compress_get_enabled_compressors(array $aggregate_settings = $packer = variable_get('advagg_js_compress_packer', ADVAGG_JS_COMPRESS_PACKER); } - if ($file == 1) { - $return_compressors[1] = $compressors[1]; + if (isset($compressors[$file])) { + $return_compressors[$file] = $compressors[$file]; } if ($packer) { $return_compressors[2] = $compressors[2]; @@ -268,17 +264,117 @@ function advagg_js_compress_get_enabled_compressors(array $aggregate_settings = $return_compressors[1] = $compressors[1]; } } - if ($file == 4) { - $return_compressors[4] = $compressors[4]; - } - if ($file == 5) { - $return_compressors[5] = $compressors[5]; - } } return $return_compressors; } +/** + * Compress a JS string. + * + * @param string $contents + * Javascript string. + * @param array $info + * Info about the js file. + * @param int $compressor + * Use a particular compressor. + * @param bool $force_run + * TRUE to skip cache and force the compression test. + * @param array $aggregate_settings + * The aggregate_settings array. + * @param bool $add_licensing + * FALSE to remove Source and licensing information comment. + * @param bool $log_errors + * FALSE to disable logging to watchdog on failure. + * + * @return bool + * FALSE if there was an error. + */ +function advagg_js_compress_do_it(&$contents, array $info, $compressor, $force_run, array $aggregate_settings, $log_errors) { + $no_errors = TRUE; + + // Try cache. + $content_hash = !empty($info['content_hash']) ? ":{$info['content_hash']}" : ''; + $cache_id = "advagg:js_compress:{$compressor}:{$info['filename_hash']}:{$content_hash}"; + + $cache = cache_get($cache_id, 'cache_advagg_aggregates'); + $force_run = variable_get('advagg_js_compress_force_run', $force_run); + if (!$force_run && !empty($cache->data)) { + $contents = $cache->data; + } + else { + // Strip Byte Order Marks (BOM's), preg_* cannot parse these well. + $contents = str_replace(pack("CCC", 0xef, 0xbb, 0xbf), "", $contents); + // Jsmin may have errors (incorrectly determining EOLs) with mixed tabs + // and spaces. An example: jQuery.Cycle 3.0.3 - http://jquery.malsup.com/ + if ($compressor == 3) { + $contents = str_replace("\t", " ", $contents); + } + + // Add end string to get true end of file. + // Generate random variable contents and add to end of js string. + $random = dechex(mt_rand()); + $end_string = "var advagg_end=\"$random\";"; + $contents .= "\n$end_string"; + + // Use the compressor. + list(, , , $functions) = advagg_js_compress_configuration(); + if (isset($functions[$compressor])) { + $run = $functions[$compressor]; + if (function_exists($run)) { + $no_errors = $run($contents, $log_errors, $aggregate_settings); + } + } + else { + return; + } + + // Get location of random variable. + $end_string = substr($end_string, 0, -1); + $pos = strrpos($contents, $end_string); + if ($pos === FALSE) { + $end_string = str_replace('"', "'", $end_string); + $pos = strrpos($contents, $end_string); + } + if ($pos !== FALSE) { + // Cut everything after random variable out of string. + $contents = substr($contents, 0, $pos); + } + + // 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. + if ($compressor == 3 || $compressor == 5) { + $a = strrpos($contents, ';'); + $b = strrpos($contents, '}'); + $c = strrpos($contents, ')'); + + // Simple scripts can just end, check to make sure there's a + // match before cutting. + if ($a !== FALSE || $b !== FALSE || $c !== FALSE) { + $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. + $contents = trim($contents) . ';'; + } + + if (empty($info['#no_cache'])) { + // Cache minified data for 1 week. + cache_set($cache_id, $contents, 'cache_advagg_aggregates', REQUEST_TIME + (86400 * 7)); + } + else { + // Cache minified inline data for 1 hour. + cache_set($cache_id, $contents, 'cache_advagg_aggregates', REQUEST_TIME + 3600); + } + } + return $no_errors; +} + /** * Compress a JS string. * @@ -294,8 +390,14 @@ function advagg_js_compress_get_enabled_compressors(array $aggregate_settings = * FALSE to disable logging to watchdog on failure. * @param bool $test_ratios * FALSE to disable compression ratio testing. + * @param bool $force_run + * TRUE to skip cache and force the compression test. + * + * @return bool + * FALSE if there was an error. */ -function advagg_js_compress_prep(&$contents, $filename, array $aggregate_settings, $add_licensing = TRUE, $log_errors = TRUE, $test_ratios = TRUE) { +function advagg_js_compress_prep(&$contents, $filename, array $aggregate_settings, $add_licensing = TRUE, $log_errors = TRUE, $test_ratios = TRUE, $force_run = FALSE) { + $no_errors = TRUE; // Get the info on this file. module_load_include('inc', 'advagg', 'advagg'); $compressor = $aggregate_settings['variables']['advagg_js_compressor']; @@ -303,9 +405,22 @@ function advagg_js_compress_prep(&$contents, $filename, array $aggregate_setting if ($compressor == 0) { return; } - // Do nothing if the cache settings are set to Development. - if (variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL) < 0) { - return; + // Do nothing if the file is already minified. + $url = file_create_url($filename); + $semicolon_count = substr_count($contents, ';'); + if ($compressor != 2 + && $semicolon_count > 10 + && $semicolon_count > (substr_count($contents, "\n", strpos($contents, ';')) * 5) + && !$force_run + ) { + $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)) { + $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"; + return; + } } // Get the JS string length before the compression operation. @@ -314,7 +429,7 @@ function advagg_js_compress_prep(&$contents, $filename, array $aggregate_setting // Do not use jsmin() if the function can not be called. if ($compressor == 3 && !function_exists('jsmin')) { - if (defined('PHP_VERSION_ID') & PHP_VERSION_ID >= 50300) { + if (defined('PHP_VERSION_ID') && constant('PHP_VERSION_ID') >= 50300) { $compressor = 5; watchdog('advagg_js_compress', 'The jsmin function does not exist. Using JSqueeze.'); } @@ -324,44 +439,24 @@ function advagg_js_compress_prep(&$contents, $filename, array $aggregate_setting } } - // Try cache. - $info = advagg_get_info_on_files(array($filename), FALSE, FALSE); - $info = $info[$filename]; - $cache_id = 'advagg:js_compress:' . $compressor . ':' . $info['filename_hash']; - $cache_id .= !empty($info['content_hash']) ? ':' . $info['content_hash'] : ''; - $cache = cache_get($cache_id, 'cache_advagg_aggregates'); - if (!empty($cache->data)) { - $contents = $cache->data; - } - else { - // Strip Byte Order Marks (BOM's), preg_* cannot parse these well. - $contents = str_replace(pack("CCC", 0xef, 0xbb, 0xbf), "", $contents); - // Use the compressor. - if ($compressor == 1) { - advagg_js_compress_jsminplus($contents, $log_errors); - } - elseif ($compressor == 2) { - advagg_js_compress_jspacker($contents); - } - elseif ($compressor == 3) { - $contents = jsmin($contents); - } - elseif ($compressor == 4) { - advagg_js_compress_jshrink($contents); + // 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.'); } - elseif ($compressor == 5) { - advagg_js_compress_jsqueeze($contents); + 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.'); } + } - // Ensure that $contents ends with ; or }. - if (strpbrk(substr(trim($contents), -1), ';}') === FALSE) { - // ; or } not found, add in ; to the end of $contents. - $contents = trim($contents) . ';'; - } + $info = advagg_get_info_on_files(array($filename), FALSE, FALSE); + $info = $info[$filename]; - // Cache minified data for at least 1 week. - cache_set($cache_id, $contents, 'cache_advagg_aggregates', REQUEST_TIME + (86400 * 7)); - } + $no_errors = advagg_js_compress_do_it($contents, $info, $compressor, $force_run, $aggregate_settings, $log_errors, $url); // Make sure compression ratios are good. $after = strlen($contents); @@ -370,22 +465,79 @@ function advagg_js_compress_prep(&$contents, $filename, array $aggregate_setting $ratio = ($before - $after) / $before; } - 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); - // Make sure the returned string is not empty or has a VERY high - // compression ratio. - if ( empty($contents) - || empty($ratio) - || $ratio < 0 - || $ratio > $aggregate_settings['variables']['advagg_js_compress_max_ratio'] - ) { - $contents = $contents_before; - } - elseif ($add_licensing && variable_get('advagg_js_compress_add_license', ADVAGG_JS_COMPRESS_ADD_LICENSE)) { - $url = url($filename, array('absolute' => TRUE)); - $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"; + // Get ratios settings. + $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); + $aggregate_settings['variables']['advagg_js_compress_ratio'] + = isset($aggregate_settings['variables']['advagg_js_compress_ratio']) ? + $aggregate_settings['variables']['advagg_js_compress_ratio'] : + variable_get('advagg_js_compress_ratio', ADVAGG_JS_COMPRESS_RATIO); + + // Get license settings. + $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) + || empty($ratio) + || $ratio < $aggregate_settings['variables']['advagg_js_compress_ratio'] + || $ratio > $aggregate_settings['variables']['advagg_js_compress_max_ratio'] + ) { + $contents = $contents_before; + if ($compressor !== 1) { + // Try again using jsmin+. + $no_errors = advagg_js_compress_do_it($contents, $info, 1, $force_run, $aggregate_settings, $log_errors, $url); + // Make sure compression ratios are good. + $after = strlen($contents); + $ratio = 0; + if ($before != 0) { + $ratio = ($before - $after) / $before; + } + if (empty($contents) + || empty($ratio) + || $ratio < $aggregate_settings['variables']['advagg_js_compress_ratio'] + || $ratio > $aggregate_settings['variables']['advagg_js_compress_max_ratio'] + ) { + $contents = $contents_before; + $add_licensing = FALSE; + } } } + + if ($add_licensing && ($add_license_setting == 1 || $add_license_setting == 3)) { + $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"; + } + + // Reset if cache settings are set to Development. + if (variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL) < 0 && !$force_run) { + $contents = $contents_before; + } + return $no_errors; +} + +/** + * 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; } /** @@ -397,141 +549,130 @@ function advagg_js_compress_prep(&$contents, $filename, array $aggregate_setting * FALSE to disable logging to watchdog on failure. */ function advagg_js_compress_jsminplus(&$contents, $log_errors = TRUE) { + $no_errors = TRUE; $contents_before = $contents; + $old_error = error_get_last(); - // Only include jsminplus.inc if the JSMinPlus class doesn't exist. + // Set max nesting level. if (!class_exists('JSMinPlus')) { - include drupal_get_path('module', '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); } } + + // Try libraries for jsminplus. + if (is_callable('libraries_load')) { + libraries_load('jsminplus'); + } + // Only include jsminplus.inc if the JSMinPlus class doesn't exist. + if (!class_exists('JSMinPlus')) { + include drupal_get_path('module', 'advagg_js_compress') . '/jsminplus.inc'; + } + ob_start(); try { // JSMin+ the contents of the aggregated file. - $contents = JSMinPlus::minify($contents); + $contents = @JSMinPlus::minify($contents); // Capture any output from JSMinPlus. $error = trim(ob_get_contents()); if (!empty($error)) { + $no_errors = FALSE; + throw new Exception($error); + } + $recent_error = error_get_last(); + if (!empty($recent_error) && serialize($recent_error) !== serialize($old_error)) { + $no_errors = FALSE; + $error = print_r($recent_error, TRUE); throw new Exception($error); } } catch (Exception $e) { + $no_errors = FALSE; // Log the exception thrown by JSMin+ and roll back to uncompressed content. if ($log_errors) { - watchdog('advagg_js_compress', $e->getMessage() . '<pre>' . $contents_before . '</pre>', NULL, WATCHDOG_WARNING); + watchdog('advagg_js_compress', '@message <pre> @contents </pre>', array( + '@message' => $e->getMessage(), + '@contents' => $contents_before, + ), WATCHDOG_WARNING); } $contents = $contents_before; } ob_end_clean(); + return $no_errors; } /** - * Compress a JS string using jshrink. + * Compress a JS string using packer. * * @param string $contents * Javascript string. * @param bool $log_errors * FALSE to disable logging to watchdog on failure. */ -function advagg_js_compress_jshrink(&$contents, $log_errors = TRUE) { +function advagg_js_compress_jspacker(&$contents, $log_errors = TRUE) { + $no_errors = TRUE; $contents_before = $contents; - // Only include jshrink.inc if the JShrink\Minifier class doesn't exist. - if (!class_exists('JShrink\Minifier')) { - include drupal_get_path('module', 'advagg_js_compress') . '/jshrink.inc'; - $nesting_level = ini_get('xdebug.max_nesting_level'); - if (!empty($nesting_level) && $nesting_level < 200) { - ini_set('xdebug.max_nesting_level', 200); - } + // Try libraries for jspacker. + if (is_callable('libraries_load')) { + libraries_load('jspacker'); + } + if (!class_exists('JavaScriptPacker')) { + include drupal_get_path('module', 'advagg_js_compress') . '/jspacker.inc'; } - ob_start(); - try { - // JShrink the contents of the aggregated file. - $contents = JShrink\Minifier::minify($contents, array('flaggedComments' => FALSE)); - // Capture any output from JShrink. - $error = trim(ob_get_contents()); - if (!empty($error)) { - throw new Exception($error); - } + // Add semicolons to the end of lines if missing. + $contents = str_replace("}\n", "};\n", $contents); + $contents = str_replace("\nfunction", ";\nfunction", $contents); + + // Use Packer on the contents of the aggregated file. + try { + $packer = new JavaScriptPacker($contents, 62, TRUE, FALSE); + $contents = $packer->pack(); } catch (Exception $e) { - // Log the exception thrown by JShrink & roll back to uncompressed content. + $no_errors = FALSE; + // Log the exception thrown by JSMin+ and roll back to uncompressed content. if ($log_errors) { - watchdog('advagg_js_compress', $e->getMessage() . '<pre>' . $contents_before . '</pre>', NULL, WATCHDOG_WARNING); + watchdog('advagg_js_compress', '@message <pre> @contents </pre>', array( + '@message' => $e->getMessage(), + '@contents' => $contents_before, + ), WATCHDOG_WARNING); } $contents = $contents_before; } - ob_end_clean(); + return $no_errors; } /** - * Compress a JS string using jsqueeze. + * Compress a JS string using jsmin. * * @param string $contents * Javascript string. * @param bool $log_errors * FALSE to disable logging to watchdog on failure. */ -function advagg_js_compress_jsqueeze(&$contents, $log_errors = TRUE) { +function advagg_js_compress_jsmin(&$contents, $log_errors = TRUE) { + $no_errors = TRUE; $contents_before = $contents; - // Only include jshrink.inc if the Patchwork\JSqueeze class doesn't exist. - if (!class_exists('Patchwork\JSqueeze')) { - include drupal_get_path('module', 'advagg_js_compress') . '/jsqueeze.inc'; - $nesting_level = ini_get('xdebug.max_nesting_level'); - if (!empty($nesting_level) && $nesting_level < 200) { - ini_set('xdebug.max_nesting_level', 200); - } - } - ob_start(); try { - // 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), - FALSE - ); - - // Capture any output from JSqueeze. - $error = trim(ob_get_contents()); - if (!empty($error)) { - throw new Exception($error); - } + $contents = jsmin($contents); } catch (Exception $e) { - // Log the exception thrown by JSqueeze & roll back to uncompressed content. + $no_errors = FALSE; + // Log the exception thrown by JSMin+ and roll back to uncompressed content. if ($log_errors) { - watchdog('advagg_js_compress', $e->getMessage() . '<pre>' . $contents_before . '</pre>', NULL, WATCHDOG_WARNING); + watchdog('advagg_js_compress', '@message <pre> @contents </pre>', array( + '@message' => $e->getMessage(), + '@contents' => $contents_before, + ), WATCHDOG_WARNING); } $contents = $contents_before; } - ob_end_clean(); -} - -/** - * Compress a JS string using packer. - * - * @param string $contents - * Javascript string. - */ -function advagg_js_compress_jspacker(&$contents) { - // Use Packer on the contents of the aggregated file. - if (!class_exists('JavaScriptPacker')) { - include drupal_get_path('module', 'advagg_js_compress') . '/jspacker.inc'; - } - - // Add semicolons to the end of lines if missing. - $contents = str_replace("}\n", "};\n", $contents); - $contents = str_replace("\nfunction", ";\nfunction", $contents); - - $packer = new JavaScriptPacker($contents, 62, TRUE, FALSE); - $contents = $packer->pack(); + return $no_errors; } /** @@ -573,8 +714,10 @@ function advagg_js_compress_run_test($filename, array $info = array(), array $co foreach ($compressor_list as $key => $name) { $results[$key] = array('code' => -1, 'ratio' => 0, 'name' => $name); } + $run_locally = TRUE; // Run this via httprl if possible. if (module_exists('httprl') && httprl_is_background_callback_capable()) { + $run_locally = FALSE; // Setup callback options array. $callback_options = array( array( @@ -609,8 +752,16 @@ function advagg_js_compress_run_test($filename, array $info = array(), array $co $results[$key] = $sub_result[$key]; } } + + // Try locally if all return back -1 (something wrong with httprl). + foreach ($results as $key => $value) { + if ($value['code'] != -1) { + $run_locally = TRUE; + break; + } + } } - else { + if ($run_locally) { // Save results, so if PHP bombs, this file is marked as bad. // CACHE_PERMANENT isn't good here. Use 2 weeks from now + 0-45 days. // The random 0 to 45 day addition is to prevent a cache stampeed. @@ -621,7 +772,7 @@ function advagg_js_compress_run_test($filename, array $info = array(), array $co } } - // Save & return results. + // Save and return results. // Save results, so if PHP bombs, this file is marked as bad. // CACHE_PERMANENT isn't good here. Use 2 weeks from now + 0-45 days. // The random 0 to 45 day addition is to prevent a cache stampeed. @@ -657,15 +808,18 @@ function advagg_js_compress_run_mutiple_tests(array $filenames_info, array $comp drupal_set_time_limit(0); } else { - $current_time = 5; - if (is_callable('getrusage')) { - $dat = getrusage(); - $current_time = $dat["ru_utime.tv_sec"]; + $max_execution_time = ini_get('max_execution_time'); + if ($max_execution_time != 0) { + $current_time = 5; + if (is_callable('getrusage')) { + $dat = getrusage(); + $current_time = $dat["ru_utime.tv_sec"]; + } + $max_time = max(30, ini_get('max_execution_time')); + $time_left = $max_time - $current_time; + // Give every file 3 seconds. + drupal_set_time_limit(count($filenames_info) * 3 + $time_left); } - $max_time = max(30, ini_get('max_execution_time')); - $time_left = $max_time - $current_time; - // Give every file 3 seconds. - drupal_set_time_limit(count($filenames_info) * 3 + $time_left); } } diff --git a/sites/all/modules/contrib/advagg/advagg_js_compress/advagg_js_compress.drush.inc b/sites/all/modules/contrib/advagg/advagg_js_compress/advagg_js_compress.drush.inc new file mode 100644 index 0000000000..9f3092ccf0 --- /dev/null +++ b/sites/all/modules/contrib/advagg/advagg_js_compress/advagg_js_compress.drush.inc @@ -0,0 +1,111 @@ +<?php + +/** + * @file + * Drush commands for AdvAgg JS minification. + */ + +/** + * @addtogroup 3rd_party_hooks + * @{ + */ + +/** + * Implements hook_drush_help(). + */ +function advagg_js_compress_drush_help($command) { + switch ($command) { + case 'drush:advagg-js-compress': + return dt('Run js minification for all js files.'); + + } +} + +/** + * Implements hook_drush_command(). + */ +function advagg_js_compress_drush_command() { + $items = array(); + $items['advagg-js-compress'] = array( + 'callback' => 'drush_advagg_js_compress', + 'description' => dt('Run js minification.'), + 'core' => array('7+'), + 'arguments' => array( + 'filename' => 'all will do all files, or specify the filename to target that file.', + ), + 'examples' => array( + 'drush advagg-js-compress' => dt('Minify only the files that need to be done.'), + 'drush advagg-js-compress all' => dt('Minify all js files again.'), + 'drush advagg-js-compress misc/jquery.once.js' => dt('Minify the misc/jquery.once.js file.'), + ), + 'aliases' => array( + 'advagg-jsc', + 'advagg-jsmin', + '', + ), + ); + + return $items; +} + +/** + * @} End of "addtogroup 3rd_party_hooks". + */ + +/** + * Callback function for drush advagg-js-compress. + * + * Callback is called by using drush_hook_command() where + * hook is the name of the module (advagg) and command is the name of + * the Drush command with all "-" characters converted to "_" characters. + * + * @param string $filename + * The filename to compress or all to redo all files. + */ +function drush_advagg_js_compress($filename = '') { + // Get the redo list. + list($list, $redo_list) = advagg_js_compress_all_js_files_list(); + + // Handle special use cases. + if (!empty($filename)) { + // Do all. + if (strtolower($filename) === 'all') { + $redo_list = $list; + } + else { + // Do a single file, search for it in the $list. + $redo_list = array(); + foreach ($list as $values) { + if ($values['data'] === $filename) { + $redo_list = array($values); + break; + } + } + + // Let user know if that file was not found. + if (empty($redo_list)) { + drush_log(dt('The file @filename was not found.', array( + '@filename' => $filename, + )), 'notice'); + return; + } + } + } + + // Return if nothing to do. + if (empty($redo_list)) { + drush_log(dt('All of @total js files are already minified.', array( + '@total' => count($list), + )), 'ok'); + return; + } + + // Let user know what will happen. + drush_log(dt('A total of @redo out of @total js files will be minified.', array( + '@redo' => count($redo_list), + '@total' => count($list), + )), 'ok'); + + // Compress js files and cache. + advagg_js_compress_redo_files($redo_list, 0, TRUE); +} 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 1e80416c77..24b9757fdb 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 @@ -1,14 +1,14 @@ name = AdvAgg Compress Javascript -description = Compress Javascript with a 3rd party compressor; JSMin+, JSMin c ext, JShrink, & JSqueeze currently. +description = Compress Javascript with a 3rd party compressor; JSMin+, JSMin c ext, JShrink, and JSqueeze currently. package = Advanced CSS/JS Aggregation core = 7.x dependencies[] = advagg +recommends[] = libraries configure = admin/config/development/performance/advagg/js-compress -; Information added by Drupal.org packaging script on 2015-04-14 -version = "7.x-2.8" +; Information added by Drupal.org packaging script on 2020-11-19 +version = "7.x-2.35" core = "7.x" project = "advagg" -datestamp = "1429049283" - +datestamp = "1605792717" diff --git a/sites/all/modules/contrib/advagg/advagg_js_compress/advagg_js_compress.install b/sites/all/modules/contrib/advagg/advagg_js_compress/advagg_js_compress.install index 3dcb38de06..674253bcfc 100644 --- a/sites/all/modules/contrib/advagg/advagg_js_compress/advagg_js_compress.install +++ b/sites/all/modules/contrib/advagg/advagg_js_compress/advagg_js_compress.install @@ -5,6 +5,11 @@ * Handles AdvAgg JS compress installation and upgrade tasks. */ +/** + * @addtogroup hooks + * @{ + */ + /** * Implements hook_requirements(). */ @@ -19,10 +24,9 @@ function advagg_js_compress_requirements($phase) { } // Make sure a compressor is being used. - // @ignore sniffer_whitespace_openbracketspacing_openingwhitespace - if ( variable_get('advagg_js_compressor', ADVAGG_JS_COMPRESSOR) == 0 - && variable_get('advagg_js_compress_inline', ADVAGG_JS_COMPRESS_INLINE) == 0 - ) { + if (variable_get('advagg_js_compressor', ADVAGG_JS_COMPRESSOR) == 0 + && variable_get('advagg_js_compress_inline', ADVAGG_JS_COMPRESS_INLINE) == 0 + ) { // Check all files. $file_settings = variable_get('advagg_js_compressor_file_settings', array()); $compression_used = FALSE; @@ -49,13 +53,13 @@ function advagg_js_compress_requirements($phase) { } } + $requirements += advagg_js_compress_check_cache_bin(); + return $requirements; } /** - * Upgrade AdvAgg JS Compress previous versions (6.x-1.x & 7.x-1.x) to 7.x-2.x. - * - * Implements hook_update_N(). + * Upgrade AdvAgg JS Compress versions (6.x-1.x and 7.x-1.x) to 7.x-2.x. */ function advagg_js_compress_update_7200(&$sandbox) { // Bail if old DB Table does not exist. @@ -81,8 +85,6 @@ function advagg_js_compress_update_7200(&$sandbox) { /** * Clear the cache_advagg_info cache. - * - * Implements hook_update_N(). */ function advagg_js_compress_update_7201(&$sandbox) { cache_clear_all('advagg:js_compress:', 'cache_advagg_info', TRUE); @@ -91,8 +93,6 @@ function advagg_js_compress_update_7201(&$sandbox) { /** * Change variable names so they are prefixed with the modules name. - * - * Implements hook_update_N(). */ function advagg_js_compress_update_7202(&$sandbox) { // Rename advagg_js_inline_compressor to advagg_js_compress_inline. @@ -133,3 +133,19 @@ function advagg_js_compress_update_7202(&$sandbox) { variable_set('advagg_js_compress_max_ratio', $old); } } + +/** + * Remove unused variables from the variable table. + * + * Implements hook_update_N(). + */ +function advagg_js_compress_update_7203(&$sandbox) { + // Remove all old advagg css compress variables. + db_delete('variable') + ->condition('name', 'advagg_js_compressor_file_settings_%', 'LIKE') + ->execute(); +} + +/** + * @} End of "addtogroup hooks". + */ 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 4e4c4db760..699ec266f1 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 @@ -5,6 +5,11 @@ * Advanced CSS/JS aggregation js compression module. */ +/** + * @addtogroup default_variables + * @{ + */ + /** * Default value to see packer is enabled. */ @@ -43,7 +48,38 @@ 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); + +/** + * Default value to refresh the data in the cache 1 week before the TTL expires. + */ +define('ADVAGG_JS_COMPRESS_REFRESH_BEFORE_CACHE_TTL', 604800); + + +/** + * @} End of "addtogroup default_variables". + */ + +/** + * @addtogroup hooks + * @{ + */ + +/** + * Implements hook_cron(). + */ +function advagg_js_compress_cron() { + // Get the redo list. + list(, $redo_list) = advagg_js_compress_all_js_files_list(); + + // Return if nothing to do. + if (empty($redo_list)) { + return; + } + + // Compress js files and cache. + advagg_js_compress_redo_files($redo_list); +} /** * Implements hook_menu(). @@ -63,18 +99,67 @@ function advagg_js_compress_menu() { 'file' => 'advagg_js_compress.admin.inc', 'weight' => 10, ); + $items[$config_path . '/advagg/js-compress/batch'] = array( + 'title' => 'Batch Generate', + 'page callback' => 'advagg_js_compress_batch_callback', + 'access arguments' => array('administer site configuration'), + ); return $items; } +/** + * Implements hook_module_implements_alter(). + */ +function advagg_js_compress_module_implements_alter(&$implementations, $hook) { + // Move advagg_js_compress below advagg. + if ($hook === 'advagg_save_aggregate_alter' && array_key_exists('advagg_js_compress', $implementations)) { + $advagg_key = ''; + $advagg_js_compress_key = ''; + $counter = 0; + foreach ($implementations as $key => $value) { + if ($key == 'advagg') { + $advagg_key = $counter; + } + if ($key == 'advagg_js_compress') { + $advagg_js_compress_key = $counter; + } + $counter++; + } + + if ($advagg_js_compress_key > $advagg_key) { + // Move advagg_js_compress to the top. + $item = array('advagg_js_compress' => $implementations['advagg_js_compress']); + unset($implementations['advagg_js_compress']); + $implementations = array_merge($item, $implementations); + + // Move advagg to the very top. + $item = array('advagg' => $implementations['advagg']); + unset($implementations['advagg']); + $implementations = array_merge($item, $implementations); + } + } +} + +/** + * @} End of "addtogroup hooks". + */ + +/** + * @addtogroup advagg_hooks + * @{ + */ + /** * Implements hook_advagg_current_hooks_hash_array_alter(). */ function advagg_js_compress_advagg_current_hooks_hash_array_alter(&$aggregate_settings) { $aggregate_settings['variables']['advagg_js_compressor'] = variable_get('advagg_js_compressor', ADVAGG_JS_COMPRESSOR); $aggregate_settings['variables']['advagg_js_compress_packer'] = variable_get('advagg_js_compress_packer', ADVAGG_JS_COMPRESS_PACKER); + $aggregate_settings['variables']['advagg_js_compress_ratio'] = variable_get('advagg_js_compress_ratio', ADVAGG_JS_COMPRESS_RATIO); $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); } /** @@ -85,7 +170,6 @@ function advagg_js_compress_advagg_current_hooks_hash_array_alter(&$aggregate_se function advagg_js_compress_advagg_modify_js_pre_render_alter(&$children, &$elements) { // Get variables. $aggregate_settings['variables']['advagg_js_compressor'] = variable_get('advagg_js_compress_inline', ADVAGG_JS_COMPRESS_INLINE); - $aggregate_settings['variables']['advagg_js_compress_max_ratio'] = variable_get('advagg_js_compress_max_ratio', ADVAGG_JS_COMPRESS_MAX_RATIO); // Do nothing if the compressor is disabled. if (empty($aggregate_settings['variables']['advagg_js_compressor'])) { @@ -128,6 +212,85 @@ function advagg_js_compress_advagg_modify_js_pre_render_alter(&$children, &$elem unset($values); } +/** + * @} End of "addtogroup advagg_hooks". + */ + +/** + * @addtogroup 3rd_party_hooks + * @{ + */ + +/** + * Implements hook_libraries_info(). + */ +function advagg_js_compress_libraries_info() { + $libraries['JShrink'] = array( + 'name' => 'JShrink', + 'vendor url' => 'https://github.com/tedious/JShrink', + 'download url' => 'https://github.com/tedious/JShrink/archive/master.zip', + 'local version' => '1.2.0', + 'version' => '1.2.0', + 'files' => array( + 'php' => array( + 'src/JShrink/Minifier.php', + ), + ), + ); + $libraries['jsqueeze'] = array( + 'name' => 'JSqueeze', + 'vendor url' => 'https://github.com/tchwork/jsqueeze', + 'download url' => 'https://github.com/tchwork/jsqueeze/archive/master.zip', + 'local version' => '2.0.5', + 'version' => '2.0.5', + 'files' => array( + 'php' => array( + 'src/JSqueeze.php', + ), + ), + ); + $libraries['jsminplus'] = array( + 'vendor url' => 'https://github.com/JSMinPlus/JSMinPlus', + 'download url' => 'https://github.com/JSMinPlus/JSMinPlus/archive/master.zip', + 'name' => 'JSMinPlus', + 'local version' => '1.4', + 'version arguments' => array( + 'file' => 'jsminplus.php', + 'pattern' => '/JSMinPlus\s+version\s+([0-9a-zA-Z\.-]+)/', + 'lines' => 10, + 'cols' => 40, + ), + 'files' => array( + 'php' => array( + 'jsminplus.php', + ), + ), + ); + $libraries['jspacker'] = array( + 'vendor url' => 'https://github.com/tholu/php-packer', + 'download url' => 'https://github.com/tholu/php-packer/archive/master.zip', + 'name' => 'JSPacker', + 'local version' => '1.1', + 'version arguments' => array( + 'file' => 'src/Packer.php', + 'pattern' => '/\.\s+version\s+([0-9a-zA-Z\.-]+)/', + 'lines' => 4, + 'cols' => 40, + ), + 'files' => array( + 'php' => array( + 'src/Packer.php', + ), + ), + ); + + return $libraries; +} + +/** + * @} End of "addtogroup 3rd_party_hooks". + */ + /** * Test a file, making sure it is compressible. * @@ -142,7 +305,7 @@ function advagg_js_compress_advagg_modify_js_pre_render_alter(&$children, &$elem * Array showing the results of the compression tests. */ function advagg_js_compress_test_file($filename, array $compressors, $cache_id) { - $contents = file_get_contents($filename); + $contents = (string) @advagg_file_get_contents($filename); // Get the JS string length before the compression operation. $contents_before = $contents; $before = strlen($contents); @@ -153,18 +316,25 @@ function advagg_js_compress_test_file($filename, array $compressors, $cache_id) foreach ($compressors as $key => $name) { $contents = $contents_before; $aggregate_settings['variables']['advagg_js_compressor'] = $key; - $aggregate_settings['variables']['advagg_js_compress_max_ratio'] = variable_get('advagg_js_compress_max_ratio', ADVAGG_JS_COMPRESS_MAX_RATIO); // Compress it. - advagg_js_compress_prep($contents, $filename, $aggregate_settings, FALSE, FALSE, FALSE); + $no_errors = advagg_js_compress_prep($contents, $filename, $aggregate_settings, FALSE, FALSE, FALSE, TRUE); $after = strlen($contents); $ratio = 0; if ($before != 0) { $ratio = ($before - $after) / $before; } + // Set to "-1" if the compressor threw an error. + if ($no_errors === FALSE) { + $results[$key] = array( + 'code' => -1, + 'ratio' => round($ratio, 5), + 'name' => $name, + ); + } // Set to "-2" if compression ratio sucks (it's already compressed). - if ($ratio < variable_get('advagg_js_compress_ratio', ADVAGG_JS_COMPRESS_RATIO)) { + elseif ($ratio < 0.001) { $results[$key] = array( 'code' => -2, 'ratio' => round($ratio, 5), @@ -172,7 +342,7 @@ function advagg_js_compress_test_file($filename, array $compressors, $cache_id) ); } // Set to "-3" if the compression ratio is way too good (bad js output). - elseif ($ratio > variable_get('advagg_js_compress_max_ratio', ADVAGG_JS_COMPRESS_MAX_RATIO)) { + elseif ($ratio > 0.999) { $results[$key] = array( 'code' => -3, 'ratio' => round($ratio, 5), @@ -187,6 +357,7 @@ function advagg_js_compress_test_file($filename, array $compressors, $cache_id) 'name' => $name, ); } + } $cache = cache_get($cache_id, 'cache_advagg_info'); @@ -207,3 +378,399 @@ function advagg_js_compress_test_file($filename, array $compressors, $cache_id) return $results; } + +/** + * Generate the js compress configuration. + * + * @return array + * Array($options, $description, $compressors, $functions). + */ +function advagg_js_compress_configuration() { + // Set the defaults. + $description = ''; + $options = array( + 0 => t('Disabled'), + 1 => t('JSMin+ ~1300ms'), + // 2 => t('Packer ~500ms'), + // 3 is JSMin c extension. + // 4 is JShrink. + // 5 is JSqueeze. + ); + if (function_exists('jsmin')) { + $options[3] = t('JSMin ~2ms'); + $description .= t('JSMin is the very fast C complied version. Recommend using it.'); + } + else { + if (!defined('PHP_VERSION_ID') || constant('PHP_VERSION_ID') < 50310) { + $link = 'http://www.ypass.net/software/php_jsmin/'; + } + else { + $link = 'https://github.com/sqmk/pecl-jsmin/'; + } + $description .= t('You can use the much faster C version of JSMin (~2ms) by installing the <a href="@php_jsmin">JSMin PHP Extension</a> on this server.', array('@php_jsmin' => $link)); + } + // Add in JShrink and JSqueeze if using php 5.3 or higher. + if (defined('PHP_VERSION_ID') && constant('PHP_VERSION_ID') >= 50300) { + $options += array( + 4 => t('JShrink ~1000ms'), + 5 => t('JSqueeze ~600ms'), + ); + } + + $compressors = array( + 1 => 'jsminplus', + 2 => 'packer', + ); + if (function_exists('jsmin')) { + $compressors[3] = 'jsmin'; + } + if (defined('PHP_VERSION_ID') && constant('PHP_VERSION_ID') >= 50300) { + $compressors += array( + 4 => 'jshrink', + 5 => 'jsqueeze', + ); + } + + $functions = array( + 1 => 'advagg_js_compress_jsminplus', + 2 => 'advagg_js_compress_jspacker', + 3 => 'advagg_js_compress_jsmin', + 4 => 'advagg_js_compress_jshrink', + 5 => 'advagg_js_compress_jsqueeze', + ); + + // Allow for other modules to alter this list. + $options_desc = array($options, $description); + // Call hook_advagg_js_compress_configuration_alter(). + drupal_alter('advagg_js_compress_configuration', $options_desc, $compressors, $functions); + list($options, $description) = $options_desc; + + return array($options, $description, $compressors, $functions); +} + +/** + * Get all js files and js files that are not compressed. + * + * @return array + * Array($list, $redo_list). + */ +function advagg_js_compress_all_js_files_list() { + // Get all files stored in the database. + $result = db_select('advagg_files', 'af') + ->fields('af') + ->condition('filetype', 'js') + ->orderBy('filename', 'ASC') + ->execute(); + if (empty($result)) { + return array(); + } + + module_load_include('inc', 'advagg_js_compress', 'advagg_js_compress.advagg'); + $compressor_list = advagg_js_compress_get_enabled_compressors(array(), -1); + $compressor_list_count = count($compressor_list); + + // Check if files have been compressed. + module_load_include('inc', 'advagg', 'advagg'); + $redo_list = array(); + $failed_redo_list = array(); + $list = array(); + $cache_ids = array(); + foreach ($result as $row) { + $row = (array) $row; + // Check cache for jsmin info. + $info = advagg_get_info_on_file($row['filename']); + if ($info['filesize'] == 0) { + continue; + } + $list[$row['filename']] = $info; + + // Get the cache id as well. + $cache_id = 'advagg:js_compress:info:' . $info['filename_hash']; + $cache_id .= !empty($info['content_hash']) ? ':' . $info['content_hash'] : ''; + $cache_ids[$cache_id] = $row['filename']; + } + + // Check for soon to expire cache ids. + $values = array_keys($cache_ids); + $cache_hits = cache_get_multiple($values, 'cache_advagg_info'); + $ttl = variable_get('advagg_js_compress_refresh_before_cache_ttl', ADVAGG_JS_COMPRESS_REFRESH_BEFORE_CACHE_TTL); + foreach ($cache_hits as $cache) { + if (!empty($cache->expire) && $cache->expire - $ttl < REQUEST_TIME) { + $info = $list[$cache_ids[$cache->cid]]; + $redo_list[$info['data']] = $info; + } + } + + foreach ($list as $info) { + // No jsmin info or incomplete data => rerun compression tests. + if (empty($info['advagg_js_compress']) || count($info['advagg_js_compress']) !== $compressor_list_count) { + $redo_list[$info['data']] = $info; + continue; + } + $empty_ratio_count = 0; + $bad_compression_count = 0; + foreach ($info['advagg_js_compress'] as $values) { + if (empty($values['ratio'])) { + if ($values['code'] != -1) { + $empty_ratio_count++; + } + else { + $bad_compression_count++; + } + } + } + // More than one compressor has an empty ratio. + if ($empty_ratio_count > 1) { + $failed_redo_list[$info['data']] = $info; + } + // All failed; try again. + if ($bad_compression_count == count($info['advagg_js_compress'])) { + $failed_redo_list[$info['data']] = $info; + } + } + + $redo_list = array_merge($redo_list, $failed_redo_list); + $reversed_needle = strrev('.min.js'); + $advagg_js_compressor_file_settings = variable_get('advagg_js_compressor_file_settings', []); + foreach ($redo_list as $key => $info) { + // Filter out file if the compressor is disabled. + $filename = str_replace(['/', '.'], ['__', '--'], $key); + if (isset($advagg_js_compressor_file_settings[$filename]) && $advagg_js_compressor_file_settings[$filename] == 0) { + unset($redo_list[$key]); + continue; + } + + // Filter out .min.js if they have already been ran. + if (stripos(strrev($info['data']), $reversed_needle) === 0 + && !empty($info['advagg_js_compress'][2]['ratio']) + ) { + unset($redo_list[$key]); + continue; + } + + // Filter out file if it is empty. + $data = file_get_contents($info['data']); + if (empty($data)) { + unset($redo_list[$key]); + continue; + } + + // Filter out file if it only contains a small amount of whitespace. + $count_ws = strlen($data); + $count = strlen(preg_replace('/\s/', '', $data)); + if ($count / $count_ws > 0.97) { + unset($redo_list[$key]); + continue; + } + } + + shuffle($redo_list); + return array($list, $redo_list); +} + +/** + * Get all js files and js files that are not compressed. + * + * @param array $redo_list + * JS files that need to be compressed. + * @param int $max_time + * Max amount of time to spend on compressing. + * @param bool $drush + * Set to TRUE to output drush info when running. + * + * @return array + * Array($list, $redo_list). + */ +function advagg_js_compress_redo_files(array $redo_list, $max_time = 30, $drush = FALSE) { + // Get the compressor list and start the clock. + module_load_include('inc', 'advagg_js_compress', 'advagg_js_compress.advagg'); + $compressor_list = advagg_js_compress_get_enabled_compressors(array(), -1); + shuffle($redo_list); + $time_start = microtime(TRUE); + if ($drush && (!is_callable('drush_log') || !is_callable('dt'))) { + $drush = FALSE; + } + + // Change settings for testing. + if (isset($GLOBALS['conf']['advagg_js_compress_force_run'])) { + $advagg_js_compress_force_run = $GLOBALS['conf']['advagg_js_compress_force_run']; + } + if (isset($GLOBALS['conf']['advagg_js_compress_add_license'])) { + $advagg_js_compress_add_license = $GLOBALS['conf']['advagg_js_compress_add_license']; + } + if (isset($GLOBALS['conf']['httprl_background_callback'])) { + $httprl_background_callback = $GLOBALS['conf']['httprl_background_callback']; + } + $GLOBALS['conf']['advagg_js_compress_force_run'] = TRUE; + $GLOBALS['conf']['advagg_js_compress_add_license'] = 0; + $GLOBALS['conf']['httprl_background_callback'] = FALSE; + + $counter = array(); + foreach ($redo_list as $key => $values) { + // Test the files for up to 30 seconds. + $filenames_info = array(); + $filenames_info[$values['data']] = $values; + $compressors = $compressor_list; + if (isset($values['compressors'])) { + $compressors = $values['compressors']; + } + + if ($drush) { + drush_log(dt('Compressing @data.', array( + '@data' => $values['data'], + )), 'ok'); + } + + // Remove jsqueeze if compression failed. + if (!empty($values['advagg_js_compress'])) { + $neg_one_counter = 0; + foreach ($values['advagg_js_compress'] as $compressor_data) { + if ($compressor_data['code'] == -1) { + $neg_one_counter++; + } + } + if (count($values['advagg_js_compress']) === $neg_one_counter) { + $compressor_key = array_search('jsqueeze', $compressors); + if ($compressor_key !== FALSE) { + unset($compressors[$compressor_key]); + } + } + } + + // Prime cache. + advagg_js_compress_run_mutiple_tests($filenames_info, $compressors); + // Add to cache. + advagg_get_info_on_file($values['data'], TRUE, TRUE); + + $counter[$key] = $values; + + // Stop after 30 seconds of processing. + $time_end = microtime(TRUE); + $time = $time_end - $time_start; + if (!empty($max_time) && $time > $max_time) { + break; + } + } + + // Put them back to normal. + if (isset($advagg_js_compress_force_run)) { + $GLOBALS['conf']['advagg_js_compress_force_run'] = $advagg_js_compress_force_run; + } + if (isset($advagg_js_compress_add_license)) { + $GLOBALS['conf']['advagg_js_compress_add_license'] = $advagg_js_compress_add_license; + } + if (isset($httprl_background_callback)) { + $GLOBALS['conf']['httprl_background_callback'] = $httprl_background_callback; + } + + return $counter; +} + +/** + * The batch callback. + */ +function advagg_js_compress_batch_callback() { + $batch = array( + 'operations' => array(), + 'finished' => 'advagg_js_compress_batch_done', + 'title' => t('Batch JS Minification'), + 'init_message' => t('Starting'), + 'progress_message' => t('Processed @current out of @total.'), + 'error_message' => t('JS minification has encountered an error.'), + ); + + list($list, $redo_list) = advagg_js_compress_all_js_files_list(); + $config_path = advagg_admin_config_root_path(); + if (empty($redo_list)) { + $redo_list = $list; + } + + foreach ($redo_list as $redo) { + $batch['operations'][] = array('advagg_js_compress_batch_process', array($redo)); + } + batch_set($batch); + // The path to redirect to when done. + batch_process($config_path . '/advagg/js-compress'); +} + +/** + * The batch processor. + */ +function advagg_js_compress_batch_process($redo, &$context) { + // Give it up to 3 minutes. + $max_time = ini_get('max_execution_time'); + if ($max_time != 0 && $max_time < 180) { + set_time_limit(180); + } + ignore_user_abort(TRUE); + + // Display a progress message... + $context['message'] = t("Now processing @filename...", array('@filename' => $redo['data'])); + // Do heavy lifting here... + advagg_js_compress_redo_files(array($redo)); +} + +/** + * The batch finish handler. + */ +function advagg_js_compress_batch_done($success, $results, $operations) { + if ($success) { + drupal_set_message(t('Done!')); + } + else { + $error_operation = reset($operations); + $message = t('An error occurred while processing %error_operation with arguments: @arguments', array( + '%error_operation' => $error_operation[0], + '@arguments' => print_r($error_operation[1], TRUE), + )); + drupal_set_message($message, 'error'); + } +} + +/** + * Test the cache_advagg_info cache bin; make sure it works. + * + * @return array + * Single section of the requirements array. + */ +function advagg_js_compress_check_cache_bin() { + $t = get_t(); + + $requirements = array(); + // Make sure cache table is working. + $cid = 'advagg_js_compres:install:cache_test'; + $bin = 'cache_advagg_info'; + cache_set($cid, TRUE, $bin); + $cache = cache_get($cid, $bin); + $working = FALSE; + if (!empty($cache->data)) { + cache_clear_all($cid, $bin); + $cache = cache_get($cid, $bin); + if (empty($cache->data)) { + $working = TRUE; + } + } + + if (empty($working)) { + $broken_name = get_class(_cache_get_object($bin)); + + if ($broken_name === 'DrupalFakeCache') { + $working_name = get_class(_cache_get_object('cache_form')); + if ($working_name === 'DrupalFakeCache') { + $working_name = 'DrupalDatabaseCache'; + } + $extra_description = t('Please add this to the bottom of your settings.php file. <p><code>@string</code></p>', array('@string' => "\$conf['cache_class_cache_advagg_info'] = '{$working_name}';")); + } + $requirements['advagg_js_compres_cache_bin'] = array( + 'title' => $t('AdvAgg JS Compressor - The %bin cache table does not work', array('%bin' => $bin)), + 'severity' => REQUIREMENT_WARNING, + 'value' => $t('The %class_name cache bin appears to be broken.', array('%class_name' => $broken_name)), + 'description' => $t('You need to adjust your settings.php file so that the %bin bin is working correctly.', array( + '%bin' => $bin, + )), + ); + $requirements['advagg_js_compres_cache_bin']['description'] .= " {$extra_description}"; + } + + return $requirements; +} 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 new file mode 100644 index 0000000000..a4fac7ca11 --- /dev/null +++ b/sites/all/modules/contrib/advagg/advagg_js_compress/advagg_js_compress.php53.inc @@ -0,0 +1,138 @@ +<?php + +/** + * @file + * Advanced CSS/JS aggregation js compression php 5.3+ functions. + */ + +use JShrink\Minifier; +use Patchwork\JSqueeze; + +/** + * Compress a JS string using jshrink. + * + * @param string $contents + * Javascript string. + * @param bool $log_errors + * FALSE to disable logging to watchdog on failure. + */ +function advagg_js_compress_jshrink(&$contents, $log_errors = TRUE) { + $no_errors = TRUE; + $contents_before = $contents; + + // Set max nesting level. + if (!class_exists('JShrink\Minifier')) { + $nesting_level = ini_get('xdebug.max_nesting_level'); + if (!empty($nesting_level) && $nesting_level < 200) { + ini_set('xdebug.max_nesting_level', 200); + } + } + + // Try libraries for JShrink. + if (is_callable('libraries_load')) { + libraries_load('JShrink'); + } + // Only include jshrink.inc if the JShrink\Minifier class doesn't exist. + if (!class_exists('JShrink\Minifier')) { + include drupal_get_path('module', 'advagg_js_compress') . '/jshrink.inc'; + } + + ob_start(); + try { + // JShrink the contents of the aggregated file. + $contents = Minifier::minify($contents, array('flaggedComments' => FALSE)); + + // Capture any output from JShrink. + $error = trim(ob_get_contents()); + if (!empty($error)) { + $no_errors = FALSE; + throw new Exception($error); + } + } + catch (Exception $e) { + $no_errors = FALSE; + // Log the JShrink exception and rollback to uncompressed content. + if ($log_errors) { + watchdog('advagg_js_compress', '@message <pre> @contents </pre>', array( + '@message' => $e->getMessage(), + '@contents' => $contents_before, + ), WATCHDOG_WARNING); + } + $contents = $contents_before; + } + ob_end_clean(); + return $no_errors; +} + +/** + * Compress a JS string using jsqueeze. + * + * @param string $contents + * 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, array $aggregate_settings = array()) { + $no_errors = TRUE; + $contents_before = $contents; + + // Set max nesting level. + if (!class_exists('Patchwork\JSqueeze')) { + $nesting_level = ini_get('xdebug.max_nesting_level'); + if (!empty($nesting_level) && $nesting_level < 200) { + ini_set('xdebug.max_nesting_level', 200); + } + } + + // Try libraries for jsqueeze. + if (is_callable('libraries_load')) { + libraries_load('jsqueeze'); + } + if (!class_exists('Patchwork\JSqueeze')) { + // Only include jshrink.inc if the Patchwork\JSqueeze class doesn't exist. + include drupal_get_path('module', 'advagg_js_compress') . '/jsqueeze.inc'; + } + + 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 JSqueeze(); + $contents = $jz->squeeze( + $contents, + TRUE, + $keep_important_comments, + FALSE + ); + + // Capture any output from JSqueeze. + $error = trim(ob_get_contents()); + if (!empty($error)) { + $no_errors = FALSE; + throw new Exception($error); + } + } + catch (Exception $e) { + $no_errors = FALSE; + // Log the JSqueeze exception and rollback to uncompressed content. + if ($log_errors) { + watchdog('advagg_js_compress', '@message <pre> @contents </pre>', array( + '@message' => $e->getMessage(), + '@contents' => $contents_before, + ), WATCHDOG_WARNING); + } + $contents = $contents_before; + } + ob_end_clean(); + return $no_errors; +} diff --git a/sites/all/modules/contrib/advagg/advagg_js_compress/jshrink.inc b/sites/all/modules/contrib/advagg/advagg_js_compress/jshrink.inc index 4886a801c6..5c0699fc02 100644 --- a/sites/all/modules/contrib/advagg/advagg_js_compress/jshrink.inc +++ b/sites/all/modules/contrib/advagg/advagg_js_compress/jshrink.inc @@ -116,6 +116,11 @@ class Minifier */ protected $options; + /** + * These characters are used to define strings. + */ + protected $stringDelimiters = array("'", '"', '`'); + /** * Contains the default options for minification. This array is merged with * the one passed in by the user to create the request specific set of @@ -157,9 +162,7 @@ class Minifier unset($jshrink); return $js; - } catch (\Exception $e) { - if (isset($jshrink)) { // Since the breakdownScript function probably wasn't finished // we clean it out before discarding it. @@ -218,12 +221,11 @@ class Minifier protected function loop() { while ($this->a !== false && !is_null($this->a) && $this->a !== '') { - switch ($this->a) { // new lines case "\n": // if the next line is something that can't stand alone preserve the newline - if (strpos('(-+{[@', $this->b) !== false) { + if ($this->b && strpos('(-+[@', $this->b) !== false) { echo $this->a; $this->saveString(); break; @@ -231,14 +233,17 @@ class Minifier // if B is a space we skip the rest of the switch block and go down to the // string/regex check below, resetting $this->b with getReal - if($this->b === ' ') + if ($this->b === ' ') { break; + } // otherwise we treat the newline like a space + // no break case ' ': - if(static::isAlphaNumeric($this->b)) + if (static::isAlphaNumeric($this->b)) { echo $this->a; + } $this->saveString(); break; @@ -259,14 +264,16 @@ class Minifier break; case ' ': - if(!static::isAlphaNumeric($this->a)) + if (!static::isAlphaNumeric($this->a)) { break; + } + // no break default: // check for some regex that breaks stuff if ($this->a === '/' && ($this->b === '\'' || $this->b === '"')) { $this->saveRegex(); - continue; + break; } echo $this->a; @@ -278,8 +285,9 @@ class Minifier // do reg check of doom $this->b = $this->getReal(); - if(($this->b == '/' && strpos('(,=:[!&|?', $this->a) !== false)) + if (($this->b == '/' && strpos('(,=:[!&|?', $this->a) !== false)) { $this->saveRegex(); + } } } @@ -309,7 +317,7 @@ class Minifier $char = $this->c; unset($this->c); - // Otherwise we start pulling from the input. + // Otherwise we start pulling from the input. } else { $char = substr($this->input, $this->index, 1); @@ -324,9 +332,9 @@ class Minifier // Normalize all whitespace except for the newline character into a // standard space. - if($char !== "\n" && ord($char) < 32) - + if ($char !== "\n" && ord($char) < 32) { return ' '; + } return $char; } @@ -354,10 +362,13 @@ class Minifier $this->c = $this->getChar(); if ($this->c === '/') { - return $this->processOneLineComments($startIndex); + $this->processOneLineComments($startIndex); + return $this->getReal(); } elseif ($this->c === '*') { - return $this->processMultiLineComments($startIndex); + $this->processMultiLineComments($startIndex); + + return $this->getReal(); } return $char; @@ -367,8 +378,8 @@ class Minifier * Removed one line comments, with the exception of some very specific types of * conditional comments. * - * @param int $startIndex The index point where "getReal" function started - * @return string + * @param int $startIndex The index point where "getReal" function started + * @return void */ protected function processOneLineComments($startIndex) { @@ -377,17 +388,12 @@ class Minifier // kill rest of line $this->getNext("\n"); + unset($this->c); + if ($thirdCommentString == '@') { $endPoint = $this->index - $startIndex; - unset($this->c); - $char = "\n" . substr($this->input, $startIndex, $endPoint); - } else { - // first one is contents of $this->c - $this->getChar(); - $char = $this->getChar(); + $this->c = "\n" . substr($this->input, $startIndex, $endPoint); } - - return $char; } /** @@ -395,7 +401,7 @@ class Minifier * Conditional comments and "license" style blocks are preserved. * * @param int $startIndex The index point where "getReal" function started - * @return bool|string False if there's no character + * @return void * @throws \RuntimeException Unclosed comments will throw an error */ protected function processMultiLineComments($startIndex) @@ -405,14 +411,13 @@ class Minifier // kill everything up to the next */ if it's there if ($this->getNext('*/')) { - $this->getChar(); // get * $this->getChar(); // get / $char = $this->getChar(); // get next real character // Now we reinsert conditional comments and YUI-style licensing comments if (($this->options['flaggedComments'] && $thirdCommentString === '!') - || ($thirdCommentString === '@') ) { + || ($thirdCommentString === '@')) { // If conditional comments or flagged comments are not the first thing in the script // we need to echo a and fill it with a space before moving on. @@ -429,21 +434,20 @@ class Minifier $endPoint = ($this->index - 1) - $startIndex; echo substr($this->input, $startIndex, $endPoint); - return $char; - } + $this->c = $char; + return; + } } else { $char = false; } - if($char === false) + if ($char === false) { throw new \RuntimeException('Unclosed multiline comment at position: ' . ($this->index - 2)); + } // if we're here c is part of the comment and therefore tossed - if(isset($this->c)) - unset($this->c); - - return $char; + $this->c = $char; } /** @@ -460,9 +464,9 @@ class Minifier $pos = strpos($this->input, $string, $this->index); // If it's not there return false. - if($pos === false) - + if ($pos === false) { return false; + } // Adjust position of index to jump ahead to the asked for string $this->index = $pos; @@ -486,7 +490,7 @@ class Minifier $this->a = $this->b; // If this isn't a string we don't need to do anything. - if ($this->a !== "'" && $this->a !== '"') { + if (!in_array($this->a, $this->stringDelimiters)) { return; } @@ -515,7 +519,11 @@ class Minifier // character, so those will be treated just fine using the switch // block below. case "\n": - throw new \RuntimeException('Unclosed string at position: ' . $startpos ); + if ($stringType === '`') { + echo $this->a; + } else { + throw new \RuntimeException('Unclosed string at position: ' . $startpos); + } break; // Escaped characters get picked up here. If it's an escaped new line it's not really needed @@ -555,16 +563,18 @@ class Minifier echo $this->a . $this->b; while (($this->a = $this->getChar()) !== false) { - if($this->a === '/') + if ($this->a === '/') { break; + } if ($this->a === '\\') { echo $this->a; $this->a = $this->getChar(); } - if($this->a === "\n") + if ($this->a === "\n") { throw new \RuntimeException('Unclosed regex pattern at position: ' . $this->index); + } echo $this->a; } @@ -579,7 +589,7 @@ class Minifier */ protected static function isAlphaNumeric($char) { - return preg_match('/^[\w\$]$/', $char) === 1 || $char == '/'; + return preg_match('/^[\w\$\pL]$/', $char) === 1 || $char == '/'; } /** @@ -625,5 +635,4 @@ class Minifier return $js; } - } diff --git a/sites/all/modules/contrib/advagg/advagg_js_compress/jsminplus.inc b/sites/all/modules/contrib/advagg/advagg_js_compress/jsminplus.inc index f3346d2fe6..af909ff09d 100644 --- a/sites/all/modules/contrib/advagg/advagg_js_compress/jsminplus.inc +++ b/sites/all/modules/contrib/advagg/advagg_js_compress/jsminplus.inc @@ -300,16 +300,16 @@ class JSMinPlus { // FALL THROUGH case JS_BLOCK: - $childs = $n->treeNodes; + $children = $n->treeNodes; $lastType = 0; - for ($c = 0, $i = 0, $j = count($childs); $i < $j; $i++) { - $type = $childs[$i]->type; - $t = $this->parseTree($childs[$i]); + for ($c = 0, $i = 0, $j = count($children); $i < $j; $i++) { + $type = $children[$i]->type; + $t = $this->parseTree($children[$i]); if (strlen($t)) { if ($c) { $s = rtrim($s, ';'); - if ($type == KEYWORD_FUNCTION && $childs[$i]->functionForm == DECLARED_FORM) { + if ($type == KEYWORD_FUNCTION && $children[$i]->functionForm == DECLARED_FORM) { // put declared functions on a new line $s .= "\n"; } @@ -476,9 +476,9 @@ class JSMinPlus { case KEYWORD_VAR: case KEYWORD_CONST: $s = $n->value . ' '; - $childs = $n->treeNodes; - for ($i = 0, $j = count($childs); $i < $j; $i++) { - $t = $childs[$i]; + $children = $n->treeNodes; + for ($i = 0, $j = count($children); $i < $j; $i++) { + $t = $children[$i]; $s .= ($i ? ',' : '') . $t->name; $u = $t->initializer; if ($u) { @@ -531,9 +531,9 @@ class JSMinPlus { case TOKEN_CONDCOMMENT_START: case TOKEN_CONDCOMMENT_END: $s = $n->value . ($n->type == TOKEN_CONDCOMMENT_START ? ' ' : ''); - $childs = $n->treeNodes; - for ($i = 0, $j = count($childs); $i < $j; $i++) { - $s .= $this->parseTree($childs[$i]); + $children = $n->treeNodes; + for ($i = 0, $j = count($children); $i < $j; $i++) { + $s .= $this->parseTree($children[$i]); } break; @@ -548,9 +548,9 @@ class JSMinPlus { break; case OP_COMMA: - $childs = $n->treeNodes; - for ($i = 0, $j = count($childs); $i < $j; $i++) { - $s .= ($i ? ',' : '') . $this->parseTree($childs[$i]); + $children = $n->treeNodes; + for ($i = 0, $j = count($children); $i < $j; $i++) { + $s .= ($i ? ',' : '') . $this->parseTree($children[$i]); } break; @@ -648,9 +648,9 @@ class JSMinPlus { break; case JS_LIST: - $childs = $n->treeNodes; - for ($i = 0, $j = count($childs); $i < $j; $i++) { - $s .= ($i ? ',' : '') . $this->parseTree($childs[$i]); + $children = $n->treeNodes; + for ($i = 0, $j = count($children); $i < $j; $i++) { + $s .= ($i ? ',' : '') . $this->parseTree($children[$i]); } break; @@ -665,18 +665,18 @@ class JSMinPlus { case JS_ARRAY_INIT: $s = '['; - $childs = $n->treeNodes; - for ($i = 0, $j = count($childs); $i < $j; $i++) { - $s .= ($i ? ',' : '') . $this->parseTree($childs[$i]); + $children = $n->treeNodes; + for ($i = 0, $j = count($children); $i < $j; $i++) { + $s .= ($i ? ',' : '') . $this->parseTree($children[$i]); } $s .= ']'; break; case JS_OBJECT_INIT: $s = '{'; - $childs = $n->treeNodes; - for ($i = 0, $j = count($childs); $i < $j; $i++) { - $t = $childs[$i]; + $children = $n->treeNodes; + for ($i = 0, $j = count($children); $i < $j; $i++) { + $t = $children[$i]; if ($i) { $s .= ','; } @@ -1435,7 +1435,7 @@ class JSParser { } if ($tt == OP_DOT) { - $this->t->mustMatch(TOKEN_IDENTIFIER); + $this->t->mustMatch(TOKEN_IDENTIFIER, true); array_push($operands, new JSNode($this->t, OP_DOT, array_pop($operands), new JSNode($this->t))); } else { @@ -1765,9 +1765,9 @@ class JSParser { } // Include closing bracket or postfix operator in [start,end] - $te = $this->t->currentToken()->end; - if ($n->end < $te) { - $n->end = $te; + $token_end = $this->t->currentToken()->end; + if ($n->end < $token_end) { + $n->end = $token_end; } array_push($operands, $n); @@ -1806,6 +1806,8 @@ class JSNode { public $varDecls = array(); public function __construct($t, $type = 0) { + $args = func_get_args(); + if ($token = $t->currentToken()) { $this->type = $type ? $type : $token->type; $this->value = $token->value; @@ -1819,7 +1821,6 @@ class JSNode { } if (($numargs = func_num_args()) > 2) { - $args = func_get_args(); for ($i = 2; $i < $numargs; $i++) { $this->addNode($args[$i]); } @@ -1973,12 +1974,12 @@ class JSTokenizer { return $this->peek() == TOKEN_END; } - public function match($tt) { - return $this->get() == $tt || $this->unget(); + public function match($tt, $op_dot = false) { + return $this->get(1000, $op_dot) == $tt || $this->unget(); } - public function mustMatch($tt) { - if (!$this->match($tt)) { + public function mustMatch($tt, $op_dot = false) { + if (!$this->match($tt, $op_dot)) { throw $this->newSyntaxError('Unexpected token; token ' . $tt . ' expected'); } @@ -2017,7 +2018,7 @@ class JSTokenizer { } } - public function get($chunksize = 1000) { + public function get($chunksize = 1000, $op_dot = false) { while ($this->lookahead) { $this->lookahead--; $this->tokenIndex = ($this->tokenIndex + 1) & 3; @@ -2264,7 +2265,14 @@ class JSTokenizer { throw $this->newSyntaxError('Illegal token'); } } - $tt = in_array($match[0], $this->keywords) ? $match[0] : TOKEN_IDENTIFIER; + + // Identifiers after an OP_DOT can include otherwise reserve keywords. + if ($op_dot) { + $tt = TOKEN_IDENTIFIER; + } + else { + $tt = in_array($match[0], $this->keywords) ? $match[0] : TOKEN_IDENTIFIER; + } } } diff --git a/sites/all/modules/contrib/advagg/advagg_js_compress/jspacker.inc b/sites/all/modules/contrib/advagg/advagg_js_compress/jspacker.inc index 4bcc8d4aae..aa8a8768b7 100644 --- a/sites/all/modules/contrib/advagg/advagg_js_compress/jspacker.inc +++ b/sites/all/modules/contrib/advagg/advagg_js_compress/jspacker.inc @@ -224,7 +224,7 @@ class JavaScriptPacker { private function _analyze($script, $regexp, $encode) { // analyse - // retreive all words in the script + // retrieve all words in the script $all = array(); preg_match_all($regexp, $script, $all); $_sorted = array(); // list of words sorted by frequency @@ -345,7 +345,7 @@ class JavaScriptPacker { $decode = preg_replace($ENCODE, $inline, $decode); } // special case: when $count==0 there are no keywords. I want to keep -// the basic shape of the unpacking funcion so i'll frig the code... + // the basic shape of the unpacking function so i'll frig the code... if ($count == 0) { $decode = preg_replace($this->_safeRegExp('($count)\\s*=\\s*1'), '$1=0', $decode, 1); } 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 322a451399..b5d42d2cec 100644 --- a/sites/all/modules/contrib/advagg/advagg_js_compress/jsqueeze.inc +++ b/sites/all/modules/contrib/advagg/advagg_js_compress/jsqueeze.inc @@ -1,26 +1,9 @@ <?php // @codingStandardsIgnoreFile -// @ignore comment_docblock_file:file -// @ignore style_curly_braces:file -// @ignore style_string_spacing:file -// @ignore style_else_spacing:file -// @ignore comment_comment_docblock_missing:file -// @ignore comment_comment_eg:file -// @ignore production_code:file -// @ignore druplart_unary:file -// @ignore style_uppercase_constants:file -// @ignore comment_comment_space:file -// @ignore druplart_conditional_assignment:file -// @ignore style_paren_spacing:file -// @ignore style_no_tabs:file -// @ignore comment_docblock_comment:file -// @ignore comment_comment_indent:file -// @ignore style_comma_spacing:file -// @ignore style_elseif:file // @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): @@ -33,7 +16,7 @@ namespace Patchwork; /* * * This class shrinks Javascript code -* (a process called minification nowdays) +* (a process called minification nowadays) * * Should work with most valid Javascript code, * even when semi-colons are missing. @@ -52,7 +35,7 @@ namespace Patchwork; * Notes: * - Source code must be parse error free before processing. * - In order to maximise later HTTP compression (deflate, gzip), -* new variables names are choosen by considering closures, +* new variables names are chosen by considering closures, * variables' frequency and characters' frequency. * - If you use with/eval then be careful. * @@ -99,18 +82,21 @@ 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', ); - - function __construct() + public function __construct() { $this->reserved = array_flip($this->reserved); $this->charFreq = array_fill(0, 256, 0); @@ -140,20 +126,19 @@ class JSqueeze * $parser = new JSqueeze; * $squeezed_js = $parser->squeeze($fat_js); */ - - function squeeze($code, $singleLine = true, $keepImportantComments = true, $specialVarRx = false) + public function squeeze($code, $singleLine = true, $keepImportantComments = true, $specialVarRx = false) { $code = trim($code); - if ('' === $code) return ''; + if ('' === $code) { + return ''; + } $this->argFreq = array(-1 => 0); $this->specialVarRx = $specialVarRx; $this->keepImportantComments = !!$keepImportantComments; - if (preg_match("#//[ \t]*jsqueeze\.specialVarRx[ \t]*=[ \t]*([\"']?)(.*)\1#i", $code, $key)) - { - if (!$key[1]) - { + if (preg_match("#//[ \t]*jsqueeze\.specialVarRx[ \t]*=[ \t]*([\"']?)(.*)\\1#i", $code, $key)) { + if (!$key[1]) { $key[2] = trim($key[2]); $key[1] = strtolower($key[2]); $key[1] = $key[1] && $key[1] != 'false' && $key[1] != 'none' && $key[1] != 'off'; @@ -165,16 +150,16 @@ class JSqueeze // Remove capturing parentheses $this->specialVarRx && $this->specialVarRx = preg_replace('/(?<!\\\\)((?:\\\\\\\\)*)\((?!\?)/', '(?:', $this->specialVarRx); - false !== strpos($code, "\r" ) && $code = strtr(str_replace("\r\n", "\n", $code), "\r", "\n"); - false !== strpos($code, "\xC2\x85" ) && $code = str_replace("\xC2\x85" , "\n", $code); // Next Line + false !== strpos($code, "\r") && $code = strtr(str_replace("\r\n", "\n", $code), "\r", "\n"); + false !== strpos($code, "\xC2\x85") && $code = str_replace("\xC2\x85", "\n", $code); // Next Line false !== strpos($code, "\xE2\x80\xA8") && $code = str_replace("\xE2\x80\xA8", "\n", $code); // Line Separator false !== strpos($code, "\xE2\x80\xA9") && $code = str_replace("\xE2\x80\xA9", "\n", $code); // Paragraph Separator - list($code, $this->strings ) = $this->extractStrings( $code); + list($code, $this->strings) = $this->extractStrings($code); list($code, $this->closures) = $this->extractClosures($code); - $key = "//''\"\"#0'"; // This crap has a wonderful property: it can not happend in any valid javascript, even in strings - $this->closures[$key] =& $code; + $key = "//''\"\"#0'"; // This crap has a wonderful property: it can not happen in any valid javascript, even in strings + $this->closures[$key] = &$code; $tree = array($key => array('parent' => false)); $this->makeVars($code, $tree[$key], $key); @@ -183,10 +168,15 @@ class JSqueeze $code = substr($tree[$key]['code'], 1); $code = preg_replace("'\breturn !'", 'return!', $code); $code = preg_replace("'\}(?=(else|while)[^\$.a-zA-Z0-9_])'", "}\r", $code); - $code = str_replace(array_keys($this->strings), array_values($this->strings), $code); - - if ($singleLine) $code = strtr($code, "\n", ';'); - else $code = str_replace("\n", ";\n", $code); + // preg_replace is much more efficient than str_replace here + // because we don't need to scan the entire code each time for every replacement + $code = preg_replace_callback('#//\'\'""\\d++(?:/?+\'|\\])#', array($this, 'restoreString'), $code); + + if ($singleLine) { + $code = strtr($code, "\n", ';'); + } else { + $code = str_replace("\n", ";\n", $code); + } false !== strpos($code, "\r") && $code = strtr(trim($code), "\r", "\n"); // Cleanup memory @@ -197,11 +187,9 @@ class JSqueeze return $code; } - protected function extractStrings($f) { - if ($cc_on = false !== strpos($f, '@cc_on')) - { + if ($cc_on = false !== strpos($f, '@cc_on')) { // Protect conditional comments from being removed $f = str_replace('#', '##', $f); $f = str_replace('/*@', '1#@', $f); @@ -225,210 +213,266 @@ class JSqueeze ); // Extract strings, removes comments - for ($i = 0; $i < $len; ++$i) - { - if ($instr) - { - if ('//' == $instr) - { - if ("\n" == $f[$i]) - { + for ($i = 0; $i < $len; ++$i) { + if ($instr) { + if ('//' == $instr) { + if ("\n" == $f[$i]) { $f[$i--] = ' '; $instr = false; } - } - else if ($f[$i] == $instr || ('/' == $f[$i] && "/'" == $instr)) - { - if ('!' == $instr) ; - else if ('*' == $instr) - { - if ('/' == $f[$i+1]) - { + } elseif ($f[$i] == $instr || ('/' == $f[$i] && "/'" == $instr)) { + if ('!' == $instr) { + } elseif ('*' == $instr) { + if ('/' == $f[$i + 1]) { ++$i; $instr = false; } - } - else - { - if ("/'" == $instr) - { - while (false !== strpos('gmi', $f[$i+1])) $s[] = $f[$i++]; + } else { + if ("/'" == $instr) { + while (isset($f[$i + 1]) && false !== strpos('gmi', $f[$i + 1])) { + $s[] = $f[$i++]; + } $s[] = $f[$i]; } $instr = false; } - } - else if ('*' == $instr) ; - else if ('!' == $instr) - { - if ('*' == $f[$i] && '/' == $f[$i+1]) - { + } elseif ('*' == $instr) { + } elseif ('!' == $instr) { + if ('*' == $f[$i] && '/' == $f[$i + 1]) { $s[] = "*/\r"; ++$i; $instr = false; + } elseif ("\n" == $f[$i]) { + $s[] = "\r"; + } else { + $s[] = $f[$i]; } - else if ("\n" == $f[$i]) $s[] = "\r"; - else $s[] = $f[$i]; - } - else if ('\\' == $f[$i]) - { + } elseif ('\\' == $f[$i]) { ++$i; - if ("\n" != $f[$i]) - { + if ("\n" != $f[$i]) { isset($q[$f[$i]]) && ++$q[$f[$i]]; - $s[] = '\\' . $f[$i]; + $s[] = '\\'.$f[$i]; } - } - else if ("'" == $f[$i] || '"' == $f[$i]) - { + } elseif ('[' == $f[$i] && "/'" == $instr) { + $instr = '/['; + $s[] = '['; + } elseif (']' == $f[$i] && '/[' == $instr) { + $instr = "/'"; + $s[] = ']'; + } elseif ("'" == $f[$i] || '"' == $f[$i]) { ++$q[$f[$i]]; - $s[] = '\\' . $f[$i]; - } - else $s[] = $f[$i]; - } - else switch ($f[$i]) - { - case ';': - // Remove triple semi-colon - if ($i>0 && ';' == $f[$i-1] && $i+1 < $len && ';' == $f[$i+1]) $f[$i] = $f[$i+1] = '/'; - else - { - $code[++$j] = ';'; - break; + $s[] = '\\'.$f[$i]; + } else { + $s[] = $f[$i]; } + } else { + switch ($f[$i]) { + case ';': + // Remove triple semi-colon + if ($i > 0 && ';' == $f[$i - 1] && $i + 1 < $len && ';' == $f[$i + 1]) { + $f[$i] = $f[$i + 1] = '/'; + } else { + $code[++$j] = ';'; + break; + } - case '/': - if ('*' == $f[$i+1]) - { - ++$i; - $instr = '*'; + case '/': + if ('*' == $f[$i + 1]) { + ++$i; + $instr = '*'; - if ($this->keepImportantComments && '!' == $f[$i+1]) - { + if ($this->keepImportantComments && '!' == $f[$i + 1]) { + ++$i; + // no break here + } else { + break; + } + } elseif ('/' == $f[$i + 1]) { ++$i; - // no break here + $instr = '//'; + break; + } else { + $a = $j && (' ' == $code[$j] || "\x7F" == $code[$j]) ? $code[$j - 1] : $code[$j]; + if (false !== strpos('-!%&;<=>~:^+|,()*?[{} ', $a) + || (false !== strpos('oenfd', $a) + && preg_match( + "'(?<![\$.a-zA-Z0-9_])(do|else|return|typeof|yield[ \x7F]?\*?)[ \x7F]?$'", + substr($code, $j - 7, 8) + )) + ) { + if (')' === $a && $j > 1) { + $a = 1; + $k = $j - (' ' == $code[$j] || "\x7F" == $code[$j]) - 1; + while ($k >= 0 && $a) { + if ('(' === $code[$k]) { + --$a; + } elseif (')' === $code[$k]) { + ++$a; + } + --$k; + } + if (!preg_match("'(?<![\$.a-zA-Z0-9_])(if|for|while)[ \x7F]?$'", substr($code, 0, $k + 1))) { + $code[++$j] = '/'; + break; + } + } + + $key = "//''\"\"".$K++.$instr = "/'"; + $a = $j; + $code .= $key; + while (isset($key[++$j - $a - 1])) { + $code[$j] = $key[$j - $a - 1]; + } + --$j; + isset($s) && ($s = implode('', $s)) && $cc_on && $this->restoreCc($s); + $strings[$key] = array('/'); + $s = &$strings[$key]; + } else { + $code[++$j] = '/'; + } + + break; } - else break; - } - else if ('/' == $f[$i+1]) - { - ++$i; - $instr = '//'; - break; - } - else - { - $a = $j && ' ' == $code[$j] ? $code[$j-1] : $code[$j]; - if (false !== strpos('-!%&;<=>~:^+|,(*?[{ ', $a) - || (false !== strpos('oenfd', $a) - && preg_match( - "'(?<![\$.a-zA-Z0-9_])(do|else|return|typeof|yield) ?$'", - substr($code, $j-7, 8) - ))) - { - $key = "//''\"\"" . $K++ . $instr = "/'"; - $a = $j; - $code .= $key; - while (isset($key[++$j-$a-1])) $code[$j] = $key[$j-$a-1]; --$j; - isset($s) && ($s = implode('', $s)) && $cc_on && $this->restoreCc($s); - $strings[$key] = array('/'); - $s =& $strings[$key]; + + case "'": + case '"': + $instr = $f[$i]; + $key = "//''\"\"".$K++.('!' == $instr ? ']' : "'"); + $a = $j; + $code .= $key; + while (isset($key[++$j - $a - 1])) { + $code[$j] = $key[$j - $a - 1]; } - else $code[++$j] = '/'; + --$j; + isset($s) && ($s = implode('', $s)) && $cc_on && $this->restoreCc($s); + $strings[$key] = array(); + $s = &$strings[$key]; + '!' == $instr && $s[] = "\r/*!"; break; - } - - case "'": - case '"': - $instr = $f[$i]; - $key = "//''\"\"" . $K++ . ('!' == $instr ? '!' : "'"); - $a = $j; - $code .= $key; - while (isset($key[++$j-$a-1])) $code[$j] = $key[$j-$a-1]; --$j; - isset($s) && ($s = implode('', $s)) && $cc_on && $this->restoreCc($s); - $strings[$key] = array(); - $s =& $strings[$key]; - '!' == $instr && $s[] = "\r/*!"; - - break; - - case "\n": - if ($j > 5) - { - ' ' == $code[$j] && --$j; - $code[++$j] = - false !== strpos('kend', $code[$j-1]) - && preg_match( - "'(?<![\$.a-zA-Z0-9_])(break|continue|return|yield) ?$'", - substr($code, $j-8, 9) + case "\n": + if ($j > 3) { + if (' ' == $code[$j] || "\x7F" == $code[$j]) { + --$j; + } + if (false === strpos('oefd', $code[$j]) + || !preg_match( + "'(?<![\$.a-zA-Z0-9_])(?:do|else|typeof|void)[ \x7F]?$'", + substr($code, $j - 6, 8) ) - ? ';' : ' '; - - break; - } + ) { + $code[++$j] = + false !== strpos('kend', $code[$j - 1]) + && preg_match( + "'(?<![\$.a-zA-Z0-9_])(?:break|continue|return|yield[ \x7F]?\*?)[ \x7F]?$'", + substr($code, $j - 9, 10) + ) + ? ';' : "\x7F"; + + break; + } + } - case "\t": $f[$i] = ' '; - case ' ': - if (!$j || ' ' == $code[$j]) break; + case "\t": $f[$i] = ' '; + case ' ': + if (!$j || ' ' == $code[$j] || "\x7F" == $code[$j]) { + break; + } - default: - $code[++$j] = $f[$i]; + default: + $code[++$j] = $f[$i]; + } } } isset($s) && ($s = implode('', $s)) && $cc_on && $this->restoreCc($s); unset($s); - $code = substr($code, 0, $j+1); + $code = substr($code, 0, $j + 1); $cc_on && $this->restoreCc($code, false); + // Deal with newlines before/after postfix/prefix operators + // (a string literal starts with `//` and ends with `'` at this stage) + // http://inimino.org/~inimino/blog/javascript_semicolons + // Newlines before prefix are a new statement when a completed expression precedes because postfix is a "restrictd production" + // A closing bracket `)` from if/for/while does not complete an expression, so mark possible `;` as `#` to deal with later + $code = preg_replace("#(?<=[a-zA-Z\$_\\d'\\]}])\x7F(--|\\+\\+)#", ';$1', $code); + $code = preg_replace("#(?<=\\))\x7F(--|\\+\\+)#", '#$1', $code); + // Newlines after postfix are a new statement if the following token can't be parsed otherwise + // i.e. it's a keyword, identifier, string or number literal, prefix operator, opening brace + // But a prefix operator can have a newline before its operand, so check a completed expression precedes to be sure it's a postfix + // Again mark case after closing bracket with `#` to deal with later + // Also ensure keywords that may be followed by an expression aren't mistaken for the end of a completed expression + // (note that postfix cannot apply to an expression completed with `}`) + $code = preg_replace("#(?<![\$.a-zA-Z0-9_])(do|else|return|throw|typeof|void|yield) ?+(--|\\+\\+)\x7F#", '$1$2 ', $code); + $code = preg_replace("#(?<=[a-zA-Z\$_\\d'\\]]) ?+(--|\\+\\+)\x7F(?=//|--|\\+\\+|[a-zA-Z\$_\\d[({])#", '$1;', $code); + $code = preg_replace("#(?<=\\)) ?+(--|\\+\\+)\x7F(?=//|--|\\+\\+|[a-zA-Z\$_\\d[({])#", '$1#', $code); + // Protect wanted spaces and remove unwanted ones + $code = strtr($code, "\x7F", ' '); $code = str_replace('- -', "-\x7F-", $code); $code = str_replace('+ +', "+\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); + $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); + false !== strpos($code, 'new Array') && $code = preg_replace("'new Array(?:\(\)|([;\])},:]))'", '[]$1', $code); false !== strpos($code, 'new Object') && $code = preg_replace("'new Object(?:\(\)|([;\])},:]))'", '{}$1', $code); // Add missing semi-colons after curly braces // This adds more semi-colons than strictly needed, // but it seems that later gzipping is favorable to the repetition of "};" - $code = preg_replace("'\}(?![:,;.()\[\]}\|&]|(else|catch|finally|while)[^\$.a-zA-Z0-9_])'", '};', $code); + $code = preg_replace("'\}(?![:,;.()\[\]}\|&?]|(else|catch|finally|while)[^\$.a-zA-Z0-9_])'", '};', $code); // Tag possible empty instruction for easy detection - $code = preg_replace("'(?<![\$.a-zA-Z0-9_])if\('" , '1#(', $code); - $code = preg_replace("'(?<![\$.a-zA-Z0-9_])for\('" , '2#(', $code); + $code = preg_replace("'(?<![\$.a-zA-Z0-9_])if\('", '1#(', $code); + $code = preg_replace("'(?<![\$.a-zA-Z0-9_])for\('", '2#(', $code); + $code = preg_replace("'(?<![\$.a-zA-Z0-9_])do while\('", '4#(', $code); $code = preg_replace("'(?<![\$.a-zA-Z0-9_])while\('", '3#(', $code); + $code = preg_replace("'(?<![\$.a-zA-Z0-9_])do(?![\$a-zA-Z0-9_])'", '5#', $code); $forPool = array(); $instrPool = array(); + $doPool = array(); $s = 0; + $d = 0; $f = array(); $j = -1; // Remove as much semi-colon as possible $len = strlen($code); - for ($i = 0; $i < $len; ++$i) - { - switch ($code[$i]) - { + for ($i = 0; $i < $len; ++$i) { + switch ($code[$i]) { case '(': - if ($j>=0 && "\n" == $f[$j]) $f[$j] = ';'; + if ($j >= 0 && "\n" == $f[$j]) { + $f[$j] = ';'; + } ++$s; - if ($i && '#' == $code[$i-1]) - { - $instrPool[$s - 1] = 1; - if ('2' == $code[$i-2]) $forPool[$s] = 1; + if ($i > 1 && '#' == $code[$i - 1]) { + switch ($code[$i - 2]) { + case '3': + if (isset($doPool[$d])) { + $instrPool[$s - 1] = 5; // `while` corresponds to `do` + unset($doPool[$d]); + } else { + $instrPool[$s - 1] = 1; + } + break; + case '2': + $forPool[$s] = 1; + // also set $instrPool + case '1': + case '4': + $instrPool[$s - 1] = 1; + } } $f[++$j] = '('; @@ -436,42 +480,88 @@ class JSqueeze case ']': case ')': - if ($i+1 < $len && !isset($forPool[$s]) && !isset($instrPool[$s-1]) && preg_match("'[a-zA-Z0-9_\$]'", $code[$i+1])) - { + if ($i + 1 < $len && !isset($forPool[$s]) && !isset($instrPool[$s - 1]) && preg_match("'[a-zA-Z0-9_\$]'", $code[$i + 1])) { $f[$j] .= $code[$i]; $f[++$j] = "\n"; + } else { + $f[++$j] = $code[$i]; } - else $f[++$j] = $code[$i]; - if (')' == $code[$i]) - { + if (')' == $code[$i]) { unset($forPool[$s]); --$s; + if (isset($instrPool[$s]) && 5 === $instrPool[$s]) { + $f[$j - 1] .= ')'; + $f[$j] = ';'; + } } continue 2; - case '}': - if ("\n" == $f[$j]) $f[$j] = '}'; - else $f[++$j] = '}'; + case '{': + ++$d; + $f[++$j] = '{'; break; - case ';': - if (isset($forPool[$s]) || isset($instrPool[$s])) $f[++$j] = ';'; - else if ($j>=0 && "\n" != $f[$j] && ';' != $f[$j]) $f[++$j] = "\n"; + case '}': + --$d; + if ("\n" == $f[$j]) { + $f[$j] = '}'; + } else { + $f[++$j] = '}'; + } + break; + case '+': + case '-': + $f[++$j] = $code[$i]; + if ($i + 1 < $len + && ($code[$i] === $code[$i + 1] || '#' === $code[$i + 1]) + ) { + // delay unsetting $instrPool[$s] + continue 2; + } break; case '#': - switch ($f[$j]) - { + switch ($f[$j]) { case '1': $f[$j] = 'if'; break 2; case '2': $f[$j] = 'for'; break 2; case '3': $f[$j] = 'while'; break 2; + case '4': + // special case `while` that doesn't correspond to the `do` + $f[$j] = 'do while'; + $doPool[$d] = 1; + break 2; + case '5': + $f[$j] = 'do'; + $doPool[$d] = 1; + case ';': // added after `do..while` - no extra `;` needed + break 2; + case ')': + case '+': + case '-': + if (isset($instrPool[$s])) { + // prefix operator in conditional/loop statement - no `;` + break 2; + } + //else treat as `;` + //default should never happen } + case ';': + if (isset($forPool[$s]) || isset($instrPool[$s]) && 5 !== $instrPool[$s]) { + $f[++$j] = ';'; + } elseif ($j >= 0 && "\n" != $f[$j] && ';' != $f[$j]) { + $f[++$j] = "\n"; + } + + break; + case '['; - if ($j>=0 && "\n" == $f[$j]) $f[$j] = ';'; + if ($j >= 0 && "\n" == $f[$j]) { + $f[$j] = ';'; + } default: $f[++$j] = $code[$i]; } @@ -480,37 +570,39 @@ 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("'(?<![\$.a-zA-Z0-9_])else\n'", "\n", $f); + $f = preg_replace("'(?<![\$.a-zA-Z0-9_])else([\n}])'", '$1', $f); $r1 = array( // keywords with a direct object - 'case','delete','do','else','function','in','instanceof','break', + 'case','delete','do','else','function','in','instanceof','of','break', 'new','return','throw','typeof','var','void','yield','let','if', + 'const','get','set','continue', ); $r2 = array( // keywords with a subject - 'in','instanceof', + 'in','instanceof','of', ); // Fix missing semi-colons - $f = preg_replace("'(?<!(?<![a-zA-Z0-9_\$])" . implode(')(?<!(?<![a-zA-Z0-9_\$])', $r1) . ") (?!(" . implode('|', $r2) . ")(?![a-zA-Z0-9_\$]))'", "\n", $f); + $f = preg_replace("'(?<!(?<![a-zA-Z0-9_\$])".implode(')(?<!(?<![a-zA-Z0-9_\$])', $r1).') (?!('.implode('|', $r2).")(?![a-zA-Z0-9_\$]))'", "\n", $f); $f = preg_replace("'(?<!(?<![a-zA-Z0-9_\$])do)(?<!(?<![a-zA-Z0-9_\$])else) if\('", "\nif(", $f); - $f = preg_replace("'(?<=--|\+\+)(?<![a-zA-Z0-9_\$])(" . implode('|', $r1) . ")(?![a-zA-Z0-9_\$])'", "\n$1", $f); + $f = preg_replace("'(?<=--|\+\+)(?<![a-zA-Z0-9_\$])(".implode('|', $r1).")(?![a-zA-Z0-9_\$])'", "\n$1", $f); $f = preg_replace("'(?<![a-zA-Z0-9_\$])for\neach\('", 'for each(', $f); - $f = preg_replace("'(?<![a-zA-Z0-9_\$])\n(" . implode('|', $r2) . ")(?![a-zA-Z0-9_\$])'", '$1', $f); + $f = preg_replace("'(?<![a-zA-Z0-9_\$])\n(".implode('|', $r2).")(?![a-zA-Z0-9_\$])'", '$1', $f); // Merge strings - if ($q["'"] > $q['"']) $q = array($q[1], $q[0]); - $f = preg_replace("#//''\"\"[0-9]+'#", $q[0] . '$0' . $q[0], $f); - strpos($f, $q[0] . '+' . $q[0]) && $f = str_replace($q[0] . '+' . $q[0], '', $f); + if ($q["'"] > $q['"']) { + $q = array($q[1], $q[0]); + } + $f = preg_replace("#//''\"\"[0-9]+'#", $q[0].'$0'.$q[0], $f); + strpos($f, $q[0].'+'.$q[0]) && $f = str_replace($q[0].'+'.$q[0], '', $f); $len = count($strings); - foreach ($strings as $r1 => &$r2) - { + foreach ($strings as $r1 => &$r2) { $r2 = "/'" == substr($r1, -2) ? str_replace(array("\\'", '\\"'), array("'", '"'), $r2) - : str_replace('\\' . $q[1], $q[1], $r2); + : str_replace('\\'.$q[1], $q[1], $r2); } // Restore wanted spaces @@ -521,12 +613,11 @@ class JSqueeze protected function extractClosures($code) { - $code = ';' . $code; + $code = ';'.$code; $this->argFreq[-1] += substr_count($code, '}catch('); - if ($this->argFreq[-1]) - { + if ($this->argFreq[-1]) { // Special catch scope handling // FIXME: this implementation doesn't work with nested catch scopes who need @@ -534,31 +625,29 @@ class JSqueeze $f = preg_split("@}catch\(({$this->varRx})@", $code, -1, PREG_SPLIT_DELIM_CAPTURE); - $code = 'catch$scope$var' . mt_rand(); - $this->specialVarRx = $this->specialVarRx ? '(?:' . $this->specialVarRx . '|' . preg_quote($code) . ')' : preg_quote($code); + $code = 'catch$scope$var'.mt_rand(); + $this->specialVarRx = $this->specialVarRx ? '(?:'.$this->specialVarRx.'|'.preg_quote($code).')' : preg_quote($code); $i = count($f) - 1; - while ($i) - { + while ($i) { $c = 1; $j = 0; $l = strlen($f[$i]); - while ($c && $j < $l) - { + while ($c && $j < $l) { $s = $f[$i][$j++]; $c += '(' == $s ? 1 : (')' == $s ? -1 : 0); } - if (!$c) do - { - $s = $f[$i][$j++]; - $c += '{' == $s ? 1 : ('}' == $s ? -1 : 0); + if (!$c) { + do { + $s = $f[$i][$j++]; + $c += '{' == $s ? 1 : ('}' == $s ? -1 : 0); + } while ($c && $j < $l); } - while ($c && $j < $l); - $c = preg_quote($f[$i-1], '#'); - $f[$i-2] .= '}catch(' . preg_replace("#([.,{]?)(?<![a-zA-Z0-9_\$@]){$c}\\b#", '$1' . $code, $f[$i-1] . substr($f[$i], 0, $j)) . substr($f[$i], $j); + $c = preg_quote($f[$i - 1], '#'); + $f[$i - 2] .= '}catch('.preg_replace("#([.,{]?)(?<![a-zA-Z0-9_\$@]){$c}\\b#", '$1'.$code, $f[$i - 1].substr($f[$i], 0, $j)).substr($f[$i], $j); unset($f[$i--], $f[$i--]); } @@ -566,39 +655,39 @@ class JSqueeze $code = $f[0]; } - $f = preg_split("'(?<![a-zA-Z0-9_\$])(function[ (].*?\{)'", $code, -1, PREG_SPLIT_DELIM_CAPTURE); + $f = preg_split("'(?<![a-zA-Z0-9_\$])((?:function[ (]|get |set ).*?\{)'", $code, -1, PREG_SPLIT_DELIM_CAPTURE); $i = count($f) - 1; $closures = array(); - while ($i) - { + while ($i) { $c = 1; $j = 0; $l = strlen($f[$i]); - while ($c && $j < $l) - { + while ($c && $j < $l) { $s = $f[$i][$j++]; $c += '{' == $s ? 1 : ('}' == $s ? -1 : 0); } - switch (substr($f[$i-2], -1)) - { - default: if (false !== $c = strpos($f[$i-1], ' ', 8)) break; + switch (substr($f[$i - 2], -1)) { + default: + if (false !== $c = strpos($f[$i - 1], ' ', 8)) { + break; + } case false: case "\n": case ';': case '{': case '}': case ')': case ']': - $c = strpos($f[$i-1], '(', 8); + $c = strpos($f[$i - 1], '(', 4); } $l = "//''\"\"#$i'"; - $code = substr($f[$i-1], $c); - $closures[$l] = $code . substr($f[$i], 0, $j); - $f[$i-2] .= substr($f[$i-1], 0, $c) . $l . substr($f[$i], $j); + $code = substr($f[$i - 1], $c); + $closures[$l] = $code.substr($f[$i], 0, $j); + $f[$i - 2] .= substr($f[$i - 1], 0, $c).$l.substr($f[$i], $j); - if ('(){' !== $code) - { + if ('(){' !== $code) { $j = substr_count($code, ','); - do isset($this->argFreq[$j]) ? ++$this->argFreq[$j] : $this->argFreq[$j] = 1; - while ($j--); + do { + isset($this->argFreq[$j]) ? ++$this->argFreq[$j] : $this->argFreq[$j] = 1; + } while ($j--); } $i -= 2; @@ -609,58 +698,57 @@ class JSqueeze protected function makeVars($closure, &$tree, $key) { - $tree['code'] =& $closure; + $tree['code'] = &$closure; $tree['nfe'] = false; $tree['used'] = array(); $tree['local'] = array(); // Replace multiple "var" declarations by a single one - $closure = preg_replace_callback("'(?<=[\n\{\}])var [^\n\{\}]+(?:\nvar [^\n\{\}]+)+'", array(&$this, 'mergeVarDeclarations'), $closure); + $closure = preg_replace_callback("'(?<=[\n\{\}])var [^\n\{\};]+(?:\nvar [^\n\{\};]+)+'", array($this, 'mergeVarDeclarations'), $closure); // Get all local vars (functions, arguments and "var" prefixed) - $vars =& $tree['local']; + $vars = &$tree['local']; - if (preg_match("'^( [^(]*)?\((.*?)\)\{'", $closure, $v)) - { - if ($v[1]) - { + if (preg_match("'^( [^(]*)?\((.*?)\)\{'", $closure, $v)) { + if ($v[1]) { $vars[$tree['nfe'] = substr($v[1], 1)] = -1; - $tree['parent']['local'][';' . $key] =& $vars[$tree['nfe']]; + $tree['parent']['local'][';'.$key] = &$vars[$tree['nfe']]; } - if ($v[2]) - { + if ($v[2]) { $i = 0; $v = explode(',', $v[2]); - foreach ($v as $w) $vars[$w] = $this->argFreq[$i++] - 1; // Give a bonus to argument variables + foreach ($v as $w) { + $vars[$w] = $this->argFreq[$i++] - 1; // Give a bonus to argument variables + } } } $v = preg_split("'(?<![\$.a-zA-Z0-9_])var '", $closure); - if ($i = count($v) - 1) - { + if ($i = count($v) - 1) { $w = array(); - while ($i) - { + while ($i) { $j = $c = 0; $l = strlen($v[$i]); - while ($j < $l) - { - switch ($v[$i][$j]) - { + while ($j < $l) { + switch ($v[$i][$j]) { case '(': case '[': case '{': ++$c; break; case ')': case ']': case '}': - if ($c-- <= 0) break 2; + if ($c-- <= 0) { + break 2; + } break; case ';': case "\n": - if (!$c) break 2; + if (!$c) { + break 2; + } default: $c || $w[] = $v[$i][$j]; @@ -674,115 +762,142 @@ class JSqueeze } $v = explode(',', implode('', $w)); - foreach ($v as $w) if (preg_match("'^{$this->varRx}'", $w, $v)) isset($vars[$v[0]]) || $vars[$v[0]] = 0; + foreach ($v as $w) { + if (preg_match("'^{$this->varRx}'", $w, $v)) { + isset($vars[$v[0]]) || $vars[$v[0]] = 0; + } + } } - if (preg_match_all("@function ({$this->varRx})//''\"\"#@", $closure, $v)) - { - foreach ($v[1] as $w) isset($vars[$w]) || $vars[$w] = 0; + if (preg_match_all("@function ({$this->varRx})//''\"\"#@", $closure, $v)) { + foreach ($v[1] as $w) { + isset($vars[$w]) || $vars[$w] = 0; + } } - if ($this->argFreq[-1] && preg_match_all("@}catch\(({$this->varRx})@", $closure, $v)) - { + if ($this->argFreq[-1] && preg_match_all("@}catch\(({$this->varRx})@", $closure, $v)) { $v[0] = array(); - foreach ($v[1] as $w) isset($v[0][$w]) ? ++$v[0][$w] : $v[0][$w] = 1; - foreach ($v[0] as $w => $v) $vars[$w] = $this->argFreq[-1] - $v; + foreach ($v[1] as $w) { + isset($v[0][$w]) ? ++$v[0][$w] : $v[0][$w] = 1; + } + foreach ($v[0] as $w => $v) { + $vars[$w] = $this->argFreq[-1] - $v; + } } // Get all used vars, local and non-local - $vars =& $tree['used']; - - if (preg_match_all("#([.,{]?)(?<![a-zA-Z0-9_\$])({$this->varRx})(:?)#", $closure, $w, PREG_SET_ORDER)) - { - foreach ($w as $k) - { - if (',' === $k[1] || '{' === $k[1]) - { - if (':' === substr($k[3], -1)) $k = '.' . $k[2]; - else $k = $k[2]; + $vars = &$tree['used']; + + if (preg_match_all("#([.,{]?(?:[gs]et )?)(?<![a-zA-Z0-9_\$])({$this->varRx})(:?)#", $closure, $w, PREG_SET_ORDER)) { + foreach ($w as $k) { + if (isset($k[1][0]) && (',' === $k[1][0] || '{' === $k[1][0])) { + if (':' === $k[3]) { + $k = '.'.$k[2]; + } elseif ('get ' === substr($k[1], 1, 4) || 'set ' === substr($k[1], 1, 4)) { + ++$this->charFreq[ord($k[1][1])]; // "g" or "s" + ++$this->charFreq[101]; // "e" + ++$this->charFreq[116]; // "t" + $k = '.'.$k[2]; + } else { + $k = $k[2]; + } + } else { + $k = $k[1].$k[2]; } - else $k = $k[1] . $k[2]; isset($vars[$k]) ? ++$vars[$k] : $vars[$k] = 1; } } - if (preg_match_all("#//''\"\"[0-9]+(?:['!]|/')#", $closure, $w)) foreach ($w[0] as $a) - { - $v = "'" === substr($a, -1) && "/'" !== substr($a, -2) && $this->specialVarRx - ? preg_split("#([.,{]?(?<![a-zA-Z0-9_\$@]){$this->specialVarRx}:?)#", $this->strings[$a], -1, PREG_SPLIT_DELIM_CAPTURE) - : array($this->strings[$a]); - $a = count($v); - - for ($i = 0; $i < $a; ++$i) - { - $k = $v[$i]; - - if (1 === $i%2) - { - if (',' === $k[0] || '{' === $k[0]) - { - if (':' === substr($k, -1)) $k = '.' . substr($k, 1, -1); - else $k = substr($k, 1); - } - else if (':' === substr($k, -1)) $k = substr($k, 0, -1); + if (preg_match_all("#//''\"\"[0-9]+(?:['!]|/')#", $closure, $w)) { + foreach ($w[0] as $a) { + $v = "'" === substr($a, -1) && "/'" !== substr($a, -2) && $this->specialVarRx + ? preg_split("#([.,{]?(?:[gs]et )?(?<![a-zA-Z0-9_\$@]){$this->specialVarRx}:?)#", $this->strings[$a], -1, PREG_SPLIT_DELIM_CAPTURE) + : array($this->strings[$a]); + $a = count($v); + + for ($i = 0; $i < $a; ++$i) { + $k = $v[$i]; + + if (1 === $i % 2) { + if (',' === $k[0] || '{' === $k[0]) { + if (':' === substr($k, -1)) { + $k = '.'.substr($k, 1, -1); + } elseif ('get ' === substr($k, 1, 4) || 'set ' === substr($k, 1, 4)) { + ++$this->charFreq[ord($k[1])]; // "g" or "s" + ++$this->charFreq[101]; // "e" + ++$this->charFreq[116]; // "t" + $k = '.'.substr($k, 5); + } else { + $k = substr($k, 1); + } + } elseif (':' === substr($k, -1)) { + $k = substr($k, 0, -1); + } - $w =& $tree; + $w = &$tree; - while (isset($w['parent']) && !(isset($w['used'][$k]) || isset($w['local'][$k]))) $w =& $w['parent']; + while (isset($w['parent']) && !(isset($w['used'][$k]) || isset($w['local'][$k]))) { + $w = &$w['parent']; + } - (isset($w['used'][$k]) || isset($w['local'][$k])) && (isset($vars[$k]) ? ++$vars[$k] : $vars[$k] = 1); + (isset($w['used'][$k]) || isset($w['local'][$k])) && (isset($vars[$k]) ? ++$vars[$k] : $vars[$k] = 1); - unset($w); - } + unset($w); + } - if (0 === $i%2 || !isset($vars[$k])) foreach (count_chars($v[$i], 1) as $k => $w) $this->charFreq[$k] += $w; + if (0 === $i % 2 || !isset($vars[$k])) { + foreach (count_chars($v[$i], 1) as $k => $w) { + $this->charFreq[$k] += $w; + } + } + } } } // Propagate the usage number to parents - foreach ($vars as $w => $a) - { - $k =& $tree; + foreach ($vars as $w => $a) { + $k = &$tree; $chain = array(); - do - { - $vars =& $k['local']; - $chain[] =& $k; - if (isset($vars[$w])) - { + do { + $vars = &$k['local']; + $chain[] = &$k; + if (isset($vars[$w])) { unset($k['used'][$w]); - if (isset($vars[$w])) $vars[$w] += $a; - else $vars[$w] = $a; + if (isset($vars[$w])) { + $vars[$w] += $a; + } else { + $vars[$w] = $a; + } $a = false; break; } - } - while ($k['parent'] && $k =& $k['parent']); + } while ($k['parent'] && $k = &$k['parent']); - if ($a && !$k['parent']) - { - if (isset($vars[$w])) $vars[$w] += $a; - else $vars[$w] = $a; + if ($a && !$k['parent']) { + if (isset($vars[$w])) { + $vars[$w] += $a; + } else { + $vars[$w] = $a; + } } - if (isset($tree['used'][$w]) && isset($vars[$w])) foreach ($chain as &$b) - { - isset($b['local'][$w]) || $b['used'][$w] =& $vars[$w]; + if (isset($tree['used'][$w]) && isset($vars[$w])) { + foreach ($chain as &$b) { + isset($b['local'][$w]) || $b['used'][$w] = &$vars[$w]; + } } } - // Analyse childs + // Analyse children - $tree['childs'] = array(); - $vars =& $tree['childs']; + $tree['children'] = array(); + $vars = &$tree['children']; - if (preg_match_all("@//''\"\"#[0-9]+'@", $closure, $w)) - { - foreach ($w[0] as $a) - { + if (preg_match_all("@//''\"\"#[0-9]+'@", $closure, $w)) { + foreach ($w[0] as $a) { $vars[$a] = array('parent' => &$tree); $this->makeVars($this->closures[$a], $vars[$a], $a); } @@ -796,120 +911,125 @@ class JSqueeze protected function renameVars(&$tree, $root) { - if ($root) - { + if ($root) { $tree['local'] += $tree['used']; $tree['used'] = array(); - foreach ($tree['local'] as $k => $v) - { - if ('.' == $k[0]) $k = substr($k, 1); + foreach ($tree['local'] as $k => $v) { + if ('.' == $k[0]) { + $k = substr($k, 1); + } - if ('true' === $k) $this->charFreq[48] += $v; - else if ('false' === $k) $this->charFreq[49] += $v; - else if (!$this->specialVarRx || !preg_match("#^{$this->specialVarRx}$#", $k)) - { - foreach (count_chars($k, 1) as $k => $w) $this->charFreq[$k] += $w * $v; + if ('true' === $k) { + $this->charFreq[48] += $v; + } elseif ('false' === $k) { + $this->charFreq[49] += $v; + } elseif (!$this->specialVarRx || !preg_match("#^{$this->specialVarRx}$#", $k)) { + foreach (count_chars($k, 1) as $k => $w) { + $this->charFreq[$k] += $w * $v; + } + } elseif (2 == strlen($k)) { + $tree['used'][] = $k[1]; } - else if (2 == strlen($k)) $tree['used'][] = $k[1]; } - arsort($this->charFreq); + $this->charFreq = $this->rsort($this->charFreq); $this->str0 = ''; $this->str1 = ''; - foreach ($this->charFreq as $k => $v) - { - if (!$v) break; + foreach ($this->charFreq as $k => $v) { + if (!$v) { + break; + } $v = chr($k); - if ((64 < $k && $k < 91) || (96 < $k && $k < 123)) // A-Z a-z - { + if ((64 < $k && $k < 91) || (96 < $k && $k < 123)) { // A-Z a-z $this->str0 .= $v; $this->str1 .= $v; - } - else if (47 < $k && $k < 58) // 0-9 - { + } elseif (47 < $k && $k < 58) { // 0-9 $this->str1 .= $v; } } - if ('' === $this->str0) - { + if ('' === $this->str0) { $this->str0 = 'claspemitdbfrugnjvhowkxqyzCLASPEMITDBFRUGNJVHOWKXQYZ'; - $this->str1 = $this->str0 . '0123456789'; + $this->str1 = $this->str0.'0123456789'; } - foreach ($tree['local'] as $var => $root) - { - if ('.' != substr($var, 0, 1) && isset($tree['local'][".{$var}"])) $tree['local'][$var] += $tree['local'][".{$var}"]; + foreach ($tree['local'] as $var => $root) { + if ('.' != substr($var, 0, 1) && isset($tree['local'][".{$var}"])) { + $tree['local'][$var] += $tree['local'][".{$var}"]; + } } - foreach ($tree['local'] as $var => $root) - { - if ('.' == substr($var, 0, 1) && isset($tree['local'][substr($var, 1)])) $tree['local'][$var] = $tree['local'][substr($var, 1)]; + foreach ($tree['local'] as $var => $root) { + if ('.' == substr($var, 0, 1) && isset($tree['local'][substr($var, 1)])) { + $tree['local'][$var] = $tree['local'][substr($var, 1)]; + } } - arsort($tree['local']); + $tree['local'] = $this->rsort($tree['local']); - foreach ($tree['local'] as $var => $root) switch (substr($var, 0, 1)) - { - case '.': - if (!isset($tree['local'][substr($var, 1)])) - { - $tree['local'][$var] = '#' . ($this->specialVarRx && 3 < strlen($var) && preg_match("'^\.{$this->specialVarRx}$'", $var) ? $this->getNextName($tree) . '$' : substr($var, 1)); - } - break; + foreach ($tree['local'] as $var => $root) { + switch (substr($var, 0, 1)) { + case '.': + if (!isset($tree['local'][substr($var, 1)])) { + $tree['local'][$var] = '#'.($this->specialVarRx && 3 < strlen($var) && preg_match("'^\.{$this->specialVarRx}$'", $var) ? $this->getNextName($tree).'$' : substr($var, 1)); + } + break; - case ';': $tree['local'][$var] = 0 === $root ? '' : $this->getNextName($tree); - case '#': break; + case ';': $tree['local'][$var] = 0 === $root ? '' : $this->getNextName($tree); + case '#': break; - default: - $root = $this->specialVarRx && 2 < strlen($var) && preg_match("'^{$this->specialVarRx}$'", $var) ? $this->getNextName($tree) . '$' : $var; - $tree['local'][$var] = $root; - if (isset($tree['local'][".{$var}"])) $tree['local'][".{$var}"] = '#' . $root; + default: + $root = $this->specialVarRx && 2 < strlen($var) && preg_match("'^{$this->specialVarRx}$'", $var) ? $this->getNextName($tree).'$' : $var; + $tree['local'][$var] = $root; + if (isset($tree['local'][".{$var}"])) { + $tree['local'][".{$var}"] = '#'.$root; + } + } } - foreach ($tree['local'] as $var => $root) $tree['local'][$var] = preg_replace("'^#'", '.', $tree['local'][$var]); - } - else - { - arsort($tree['local']); - if (false !== $tree['nfe']) $tree['used'][] = $tree['local'][$tree['nfe']]; + foreach ($tree['local'] as $var => $root) { + $tree['local'][$var] = preg_replace("'^#'", '.', $tree['local'][$var]); + } + } else { + $tree['local'] = $this->rsort($tree['local']); + if (false !== $tree['nfe']) { + $tree['used'][] = $tree['local'][$tree['nfe']]; + } - foreach ($tree['local'] as $var => $root) - if ($tree['nfe'] !== $var) + foreach ($tree['local'] as $var => $root) { + if ($tree['nfe'] !== $var) { $tree['local'][$var] = 0 === $root ? '' : $this->getNextName($tree); + } + } } - $this->local_tree =& $tree['local']; - $this->used_tree =& $tree['used']; + $this->local_tree = &$tree['local']; + $this->used_tree = &$tree['used']; - $tree['code'] = preg_replace_callback("#[.,{ ]?(?<![a-zA-Z0-9_\$@]){$this->varRx}:?#", array(&$this, 'getNewName'), $tree['code']); - $this->specialVarRx && $tree['code'] = preg_replace_callback("#//''\"\"[0-9]+'#", array(&$this, 'renameInString'), $tree['code']); + $tree['code'] = preg_replace_callback("#[.,{ ]?(?:[gs]et )?(?<![a-zA-Z0-9_\$@]){$this->varRx}:?#", array($this, 'getNewName'), $tree['code']); + + if ($this->specialVarRx && preg_match_all("#//''\"\"[0-9]+'#", $tree['code'], $b)) { + foreach ($b[0] as $a) { + $this->strings[$a] = preg_replace_callback( + "#[.,{]?(?:[gs]et )?(?<![a-zA-Z0-9_\$@]){$this->specialVarRx}:?#", + array($this, 'getNewName'), + $this->strings[$a] + ); + } + } - foreach ($tree['childs'] as $a => &$b) - { + foreach ($tree['children'] as $a => &$b) { $this->renameVars($b, false); $tree['code'] = str_replace($a, $b['code'], $tree['code']); - unset($tree['childs'][$a]); + unset($tree['children'][$a]); } } - protected function renameInString($a) - { - $b =& $this->strings[$a[0]]; - unset($this->strings[$a[0]]); - - return preg_replace_callback( - "#[.,{]?(?<![a-zA-Z0-9_\$@]){$this->specialVarRx}:?#", - array(&$this, 'getNewName'), - $b - ); - } - protected function getNewName($m) { $m = $m[0]; @@ -917,25 +1037,25 @@ class JSqueeze $pre = '.' === $m[0] ? '.' : ''; $post = ''; - if (',' === $m[0] || '{' === $m[0] || ' ' === $m[0]) - { + if (',' === $m[0] || '{' === $m[0] || ' ' === $m[0]) { $pre = $m[0]; - if (':' === substr($m, -1)) - { + if (':' === substr($m, -1)) { $post = ':'; - $m = (' ' !== $m[0] ? '.' : '') . substr($m, 1, -1); + $m = (' ' !== $m[0] ? '.' : '').substr($m, 1, -1); + } elseif ('get ' === substr($m, 1, 4) || 'set ' === substr($m, 1, 4)) { + $pre .= substr($m, 1, 4); + $m = '.'.substr($m, 5); + } else { + $m = substr($m, 1); } - else $m = substr($m, 1); - } - else if (':' === substr($m, -1)) - { + } elseif (':' === substr($m, -1)) { $post = ':'; $m = substr($m, 0, -1); } $post = (isset($this->reserved[$m]) - ? ('true' === $m ? '!0' : ('false' === $m ? '!1': $m)) + ? ('true' === $m ? '!0' : ('false' === $m ? '!1' : $m)) : ( isset($this->local_tree[$m]) ? $this->local_tree[$m] @@ -945,20 +1065,20 @@ class JSqueeze : $m ) ) - ) . $post; + ).$post; - return '' === $post ? '' : ($pre . ('.' === $post[0] ? substr($post, 1) : $post)); + return '' === $post ? '' : ($pre.('.' === $post[0] ? substr($post, 1) : $post)); } protected function getNextName(&$tree = array(), &$counter = false) { - if (false === $counter) - { - $counter =& $tree['counter']; + if (false === $counter) { + $counter = &$tree['counter']; isset($counter) || $counter = -1; $exclude = array_flip($tree['used']); + } else { + $exclude = $tree; } - else $exclude = $tree; ++$counter; @@ -968,8 +1088,7 @@ class JSqueeze $name = $this->str0[$counter % $len0]; $i = intval($counter / $len0) - 1; - while ($i>=0) - { + while ($i >= 0) { $name .= $this->str1[ $i % $len1 ]; $i = intval($i / $len1) - 1; } @@ -986,4 +1105,47 @@ class JSqueeze $s = str_replace('1#@', '/*@', $s); $s = str_replace('##', '#', $s); } + + protected function restoreString($m) + { + return $this->strings[$m[0]]; + } + + private function rsort($array) + { + if (!$array) { + return $array; + } + + $i = 0; + $tuples = array(); + foreach ($array as $k => &$v) { + $tuples[] = array(++$i, $k, &$v); + } + + usort($tuples, function ($a, $b) { + if ($b[2] > $a[2]) { + return 1; + } + if ($b[2] < $a[2]) { + return -1; + } + if ($b[0] > $a[0]) { + return -1; + } + if ($b[0] < $a[0]) { + return 1; + } + + return 0; + }); + + $array = array(); + + foreach ($tuples as $t) { + $array[$t[1]] = &$t[2]; + } + + return $array; + } } 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 478888dc7c..b3a4ba6588 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 @@ -8,116 +8,200 @@ /** * Form builder; Configure advagg settings. * - * @ingroup forms + * @ingroup advagg_forms * * @see system_settings_form() */ function advagg_mod_admin_settings_form() { drupal_set_title(t('AdvAgg: Modifications')); - + advagg_display_message_if_requirements_not_met(); + $config_path = advagg_admin_config_root_path(); $form = array(); - $form['js'] = array( + + $options = array( + 0 => t('Use default (safe) settings'), + 2 => t('Use recommended (optimized) settings'), + 4 => t('Use customized settings'), + ); + $form['advagg_mod_admin_mode'] = array( + '#type' => 'radios', + '#title' => t('AdvAgg Settings'), + '#default_value' => variable_get('advagg_mod_admin_mode', ADVAGG_MOD_ADMIN_MODE), + '#options' => $options, + '#description' => t("Default settings will mirror core as closely as possible. <br>Recommended settings are optimized for speed."), + ); + $form['global_container'] = array( + '#type' => 'container', + '#hidden' => TRUE, + '#states' => array( + 'visible' => array( + ':input[name="advagg_mod_admin_mode"]' => array('value' => '4'), + ), + ), + ); + + // Tell user to update library if a new version is available. + $library_name = 'loadCSS'; + $module_name = 'advagg_mod'; + list($description) = advagg_get_version_description($library_name, $module_name); + if (!empty($description)) { + $form['global_container']['advagg_version_msg'] = array( + '#markup' => "<p>{$description}</p>", + ); + } + $form['global_container']['js'] = array( '#type' => 'fieldset', '#title' => t('JS'), ); - $form['js']['advagg_mod_js_preprocess'] = array( + $form['global_container']['js']['advagg_mod_js_preprocess'] = array( '#type' => 'checkbox', - '#title' => t('Enable preprocess on all JS'), + '#title' => t('Enable preprocess on all JS (recommended)'), '#default_value' => variable_get('advagg_mod_js_preprocess', ADVAGG_MOD_JS_PREPROCESS), '#description' => t('Force all JavaScript to have the preprocess attribute be set to TRUE. All JavaScript files will be aggregated if enabled.'), + '#recommended_value' => TRUE, ); - $form['js']['advagg_mod_js_remove_unused'] = array( + $form['global_container']['js']['advagg_mod_js_remove_unused'] = array( '#type' => 'checkbox', '#title' => t('Remove unused JavaScript if possible'), '#default_value' => variable_get('advagg_mod_js_remove_unused', ADVAGG_MOD_JS_REMOVE_UNUSED), - '#description' => t('This will scan all included JS files for references to jquery and drupal. If none are found then the core JavaScript (jquery.js, drupal.js, & Drupal.settings) is removed and not loaded on that page. If you have a site that does not use a lot of Javascript this might be helpful as it could prevent unused JavaScript from being executed, thus speeding up your sites frontend performance. Enabling this usually has negative backend performance impact.'), + '#description' => t('This will scan all included JS files for references to jquery and drupal. If none are found then the core JavaScript (jquery.js, drupal.js, Drupal.settings) is removed and not loaded on that page. If you have a site that does not use a lot of Javascript this might be helpful as it could prevent unused JavaScript from being executed, thus speeding up your sites frontend performance. Enabling this usually has negative backend performance impact.'), ); + $form['global_container']['js']['advagg_mod_js_no_ajaxpagestate'] = array( + '#type' => 'checkbox', + '#title' => t('Remove ajaxPageState CSS and JS data if ajax.js is not used on this page (recommended)'), + '#default_value' => variable_get('advagg_mod_js_no_ajaxpagestate', ADVAGG_MOD_JS_NO_AJAXPAGESTATE), + '#description' => t('This assumes that the only thing that uses Drupal.settings.ajaxPageState.css and Drupal.settings.ajaxPageState.js is ajax.js.'), + '#recommended_value' => TRUE, + ); + $form['global_container']['js']['advagg_mod_js_inline_resource_hints'] = array( + '#type' => 'checkbox', + '#title' => t('Resource hint src attributes found in the HTML content (recommended)'), + '#default_value' => variable_get('advagg_mod_js_inline_resource_hints', ADVAGG_MOD_JS_INLINE_RESOURCE_HINTS), + '#description' => t('Scan any JavaScript that was added to the content part of the page incorrectly for a src attribute; if found use the advagg <a href="@url">resource hints settings</a> for faster loading.', array('@url' => url($config_path . '/advagg', array('fragment' => 'edit-resource-hints')))), + '#disabled' => !variable_get('advagg_resource_hints_dns_prefetch', ADVAGG_RESOURCE_HINTS_DNS_PREFETCH) && !variable_get('advagg_resource_hints_preconnect', ADVAGG_RESOURCE_HINTS_PRECONNECT) && !variable_get('advagg_resource_hints_preload', ADVAGG_RESOURCE_HINTS_PRELOAD), + '#recommended_value' => TRUE, + ); + if ($form['global_container']['js']['advagg_mod_js_inline_resource_hints']['#disabled']) { + $form['global_container']['js']['advagg_mod_js_inline_resource_hints']['#description'] .= ' ' . t('Currently disabled; to enable check the desired checkboxes under Resource Hints on the <a href="@url">configuration page</a>', array('@url' => url($config_path . '/advagg', array('fragment' => 'edit-resource-hints')))); + } // Optimize JavaScript Ordering. - $form['js']['adjust_sort'] = array( + $form['global_container']['js']['adjust_sort'] = array( '#type' => 'fieldset', '#title' => t('Optimize JavaScript Ordering'), '#description' => t('The settings in here might change the order in which the JavaScript is loaded. It will move the scripts around so that more optimal aggregates are built. In most cases enabling these checkboxes will cause no negative side effects.'), ); - $form['js']['adjust_sort']['advagg_mod_js_head_extract'] = array( + $form['global_container']['js']['adjust_sort']['advagg_mod_js_head_extract'] = array( '#type' => 'checkbox', - '#title' => t('Move JavaScript added by drupal_add_html_head() into drupal_add_js()'), + '#title' => t('Move JavaScript added by drupal_add_html_head() into drupal_add_js() (recommended)'), '#default_value' => variable_get('advagg_mod_js_head_extract', ADVAGG_MOD_JS_HEAD_EXTRACT), '#description' => t('This will move JavaScript added incorrectly to Drupal into the top of the drupal_add_js() queue.'), + '#recommended_value' => TRUE, ); - $form['js']['adjust_sort']['advagg_mod_js_adjust_sort_external'] = array( + $form['global_container']['js']['adjust_sort']['advagg_mod_js_adjust_sort_external'] = array( '#type' => 'checkbox', - '#title' => t('Move all external scripts to the top of the execution order'), + '#title' => t('Move all external scripts to the top of the execution order (recommended)'), '#default_value' => variable_get('advagg_mod_js_adjust_sort_external', ADVAGG_MOD_JS_ADJUST_SORT_EXTERNAL), '#description' => t('This will group all external JavaScript files to be above all other JavaScript.'), + '#recommended_value' => TRUE, ); - $form['js']['adjust_sort']['advagg_mod_js_adjust_sort_inline'] = array( + $form['global_container']['js']['adjust_sort']['advagg_mod_js_adjust_sort_inline'] = array( '#type' => 'checkbox', - '#title' => t('Move all inline scripts to the bottom of the execution order'), + '#title' => t('Move all inline scripts to the bottom of the execution order (recommended)'), '#default_value' => variable_get('advagg_mod_js_adjust_sort_inline', ADVAGG_MOD_JS_ADJUST_SORT_INLINE), '#description' => t('This will group all inline JavaScript to be below all other JavaScript.'), + '#recommended_value' => TRUE, ); - $form['js']['adjust_sort']['advagg_mod_js_adjust_sort_browsers'] = array( + $form['global_container']['js']['adjust_sort']['advagg_mod_js_adjust_sort_browsers'] = array( '#type' => 'checkbox', - '#title' => t('Move all browser conditional JavaScript to the bottom of the group'), + '#title' => t('Move all browser conditional JavaScript to the bottom of the group (recommended)'), '#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.'), + '#recommended_value' => TRUE, ); - if (module_exists('googleanalytics') && variable_get('googleanalytics_cache', 0)) { - $form['js']['adjust_sort']['expert'] = array( + if (module_exists('googleanalytics') + && is_callable('googleanalytics_api') + && is_callable('_googleanalytics_cache') + ) { + $form['global_container']['js']['adjust_sort']['expert'] = array( '#type' => 'fieldset', '#title' => t('Experimental Settings'), '#collapsible' => TRUE, '#collapsed' => !variable_get('advagg_mod_ga_inline_to_file', ADVAGG_MOD_GA_INLINE_TO_FILE), ); - $form['js']['adjust_sort']['expert']['advagg_mod_ga_inline_to_file'] = array( + $form['global_container']['js']['adjust_sort']['expert']['advagg_mod_ga_inline_to_file'] = array( '#type' => 'checkbox', '#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), + '#description' => t('The AdvAgg Relocate module is more capable. Recommend using it if you are using this functionality. This setting will soon be deprecated.'), + ); + $form['global_container']['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. If the browser supports preconnect and under Resource Hints on the <a href="@url">configuration page</a>, preconnect is enabled (currently %state), then it will use preconnect instead of this robots.txt hack.', array( + '@url' => url($config_path . '/advagg', array('fragment' => 'edit-resource-hints')), + '%state' => variable_get('advagg_resource_hints_preconnect', ADVAGG_RESOURCE_HINTS_PRECONNECT) ? t('enabled') : t('disabled'), + )), ); } // Adjust javascript location and execution. - $form['js']['placement'] = array( + $form['global_container']['js']['placement'] = array( '#type' => 'fieldset', '#title' => t('Adjust javascript location and execution'), '#description' => t('Most of the time adjusting the settings are safe but in some rare cases adjusting these can cause serious JavaScript issues with your site.'), ); - $form['js']['placement']['advagg_mod_js_footer'] = array( + $form['global_container']['js']['placement']['advagg_mod_js_footer'] = array( '#type' => 'radios', '#title' => t('Move JS to the footer'), '#default_value' => variable_get('advagg_mod_js_footer', ADVAGG_MOD_JS_FOOTER), '#options' => array( 0 => t('Disabled'), 1 => t('All but JavaScript Libraries'), - 2 => t('All'), + 3 => t('All but what is in the $all_in_footer_list (recommended)'), + 2 => t('All (might break things)'), ), '#description' => t("If you have JavaScript inline in the body of your document, such as if you are displaying ads, you may need to keep Drupal JS Libraries in the head instead of moving them to the footer. This will keep all JS added with the JS_LIBRARY group in the head while still moving all other JavaScript to the footer."), + '#recommended_value' => 3, ); - $form['js']['placement']['advagg_mod_js_defer'] = array( - '#type' => 'checkbox', + $form['global_container']['js']['placement']['advagg_mod_js_defer'] = array( + '#type' => 'radios', '#title' => t('Deferred JavaScript Execution: Add The defer Tag To All Script Tags'), '#default_value' => variable_get('advagg_mod_js_defer', ADVAGG_MOD_JS_DEFER), + '#options' => array( + 0 => t('Disabled'), + 2 => t('All but external scripts (recommended)'), + 1 => t('All (might break things)'), + ), '#description' => t('This will delay the script execution until the HTML parser has finished. This will have a similar effect to moving all JavaScript to the footer. This might break javascript (especially inline); only use after extensive testing! <a href="@link">More Info</a>', array( '@link' => 'http://peter.sh/experiments/asynchronous-and-deferred-javascript-execution-explained/', )), + '#recommended_value' => 2, + ); + $form['global_container']['js']['placement']['advagg_mod_js_async_in_header'] = array( + '#type' => 'checkbox', + '#title' => t('Asynchronous JavaScript Execution: Group together in the header (recommended)'), + '#default_value' => variable_get('advagg_mod_js_async_in_header', ADVAGG_MOD_JS_ASYNC_IN_HEADER), + '#description' => t('This will move all async JavaScript code to the header in the same group.'), + '#recommended_value' => TRUE, ); - $form['js']['placement']['advagg_mod_js_footer_inline_alter'] = array( + $form['global_container']['js']['placement']['advagg_mod_js_footer_inline_alter'] = array( '#type' => 'checkbox', - '#title' => t('Put a wrapper around inline JS if it was added in the content section incorrectly'), + '#title' => t('Put a wrapper around inline JS if it was added in the content section incorrectly (recommended)'), '#default_value' => variable_get('advagg_mod_js_footer_inline_alter', ADVAGG_MOD_JS_FOOTER_INLINE_ALTER), '#description' => t('This will put a wrapper around any inline JavaScript that was added to the content part of the page incorrectly. The wrapper will check every 250ms until window.Drupal.settings and window.jQuery are defined; at that point the inlined code will then run.'), - + '#recommended_value' => TRUE, '#states' => array( 'disabled' => array( ':input[name="advagg_mod_js_footer"]' => array('value' => '0'), - ':input[name="advagg_mod_js_defer"]' => array('checked' => FALSE), + ':input[name="advagg_mod_js_defer"]' => array('value' => '0'), ':input[name="advagg_mod_js_async"]' => array('checked' => FALSE), ), ), ); $advagg_mod_wrap_inline_js_skip_list = trim(variable_get('advagg_mod_wrap_inline_js_skip_list', ADVAGG_MOD_WRAP_INLINE_JS_SKIP_LIST)); $advagg_mod_wrap_inline_js_xpath = variable_get('advagg_mod_wrap_inline_js_xpath', ADVAGG_MOD_WRAP_INLINE_JS_XPATH); - $form['js']['placement']['advagg_mod_wrap_inline'] = array( + $form['global_container']['js']['placement']['advagg_mod_wrap_inline'] = array( '#type' => 'fieldset', '#title' => t('Inline Wrapper Settings'), '#collapsible' => TRUE, @@ -128,7 +212,7 @@ function advagg_mod_admin_settings_form() { ), ), ); - $form['js']['placement']['advagg_mod_wrap_inline']['advagg_mod_wrap_inline_js_skip_list'] = array( + $form['global_container']['js']['placement']['advagg_mod_wrap_inline']['advagg_mod_wrap_inline_js_skip_list'] = array( '#type' => 'textarea', '#title' => t('Inline skip list for wrapper code'), '#default_value' => variable_get('advagg_mod_wrap_inline_js_skip_list', ADVAGG_MOD_WRAP_INLINE_JS_SKIP_LIST), @@ -138,54 +222,85 @@ function advagg_mod_admin_settings_form() { '@url' => 'https://developers.google.com/adwords-remarketing-tag/asynchronous/', )), ); - $form['js']['placement']['advagg_mod_wrap_inline']['advagg_mod_wrap_inline_js_xpath'] = array( + $form['global_container']['js']['placement']['advagg_mod_wrap_inline']['advagg_mod_wrap_inline_js_xpath'] = array( '#type' => 'checkbox', '#title' => t('Use XPath instead of regex when searching for inline scripts'), '#default_value' => variable_get('advagg_mod_wrap_inline_js_xpath', ADVAGG_MOD_WRAP_INLINE_JS_XPATH), '#description' => t('In general this should be disabled due to the unpredictable nature of parsing html snippets using DOMDocument loadHTML(). Only enable if you have script tags inside a textarea that have not been ran through htmlentities().'), + '#recommended_value' => TRUE, + ); + $form['global_container']['js']['placement']['advagg_mod_js_defer_inline_alter'] = array( + '#type' => 'checkbox', + '#title' => t('Deferred inline JavaScript Execution: Put a wrapper around inline JS so it runs from a setTimeout call (recommended).'), + '#default_value' => variable_get('advagg_mod_js_defer_inline_alter', ADVAGG_MOD_JS_DEFER_INLINE_ALTER), + '#description' => t('This will put a wrapper around any inline JavaScript.'), + '#recommended_value' => TRUE, ); - $form['js']['placement']['expert'] = array( + $advagg_mod_defer_inline_js_skip_list = trim(variable_get('advagg_mod_defer_inline_js_skip_list', ADVAGG_MOD_DEFER_INLINE_JS_SKIP_LIST)); + $form['global_container']['js']['placement']['advagg_mod_defer_inline'] = array( + '#type' => 'fieldset', + '#title' => t('Deferred Inline Settings'), + '#collapsible' => TRUE, + '#collapsed' => empty($advagg_mod_defer_inline_js_skip_list), + '#states' => array( + 'visible' => array( + ':input[name="advagg_mod_js_defer_inline_alter"]' => array('checked' => TRUE), + ), + ), + ); + $form['global_container']['js']['placement']['advagg_mod_defer_inline']['advagg_mod_defer_inline_js_skip_list'] = array( + '#type' => 'textarea', + '#title' => t('Inline skip list for wrapper code'), + '#default_value' => variable_get('advagg_mod_defer_inline_js_skip_list', ADVAGG_MOD_DEFER_INLINE_JS_SKIP_LIST), + '#description' => t("If the inline JavaScript matches a given string then the whole inline script will not be wrapped. Enter one per line."), + ); + $form['global_container']['js']['placement']['expert'] = array( '#type' => 'fieldset', '#title' => t('Experimental Settings'), - '#description' => t('Here there be dragons.'), '#collapsible' => TRUE, '#collapsed' => !variable_get('advagg_mod_js_async', ADVAGG_MOD_JS_ASYNC), ); - $form['js']['placement']['expert']['advagg_mod_js_async'] = array( + $form['global_container']['js']['placement']['expert']['advagg_mod_js_async'] = array( '#type' => 'checkbox', - '#title' => t('Asynchronous JavaScript Execution: Add The async Tag To All Script Tags'), + '#title' => t('Here there be dragons! Asynchronous JavaScript Execution: Add The async Tag To All Script Tags'), '#default_value' => variable_get('advagg_mod_js_async', ADVAGG_MOD_JS_ASYNC), '#description' => t('This will cause the script to be downloaded in the background and executed out of order. This will break most javascript as most code is not async safe; only use after extensive testing! <a href="@link">More Info</a>', array( '@link' => 'http://peter.sh/experiments/asynchronous-and-deferred-javascript-execution-explained/', )), ); // Outdated settings. - $form['js']['old'] = array( + $form['global_container']['js']['old'] = array( '#type' => 'fieldset', '#title' => t('Outdated settings that should not be used'), '#collapsible' => TRUE, '#collapsed' => !variable_get('advagg_mod_js_async_shim', ADVAGG_MOD_JS_ASYNC_SHIM), ); - $form['js']['old']['advagg_mod_js_async_shim'] = array( + $form['global_container']['js']['old']['advagg_mod_js_async_shim'] = array( '#type' => 'checkbox', '#title' => t('Rewrite asynchronous script tags to inline, old-browser-compatible scripts.'), '#default_value' => variable_get('advagg_mod_js_async_shim', ADVAGG_MOD_JS_ASYNC_SHIM), '#description' => t('Rewrites all scripts in the page with an "async" attribute to an inline JavaScript loading the script asynchronously in an old browser compatible way. List of <a href="@link">supported browsers</a>. Once all commonly used browsers support the "async" attribute you can happily disable this checkbox.', array('@link' => 'http://caniuse.com/script-async')), ); - $config_path = advagg_admin_config_root_path(); - $form['css'] = array( + $form['global_container']['css'] = array( '#type' => 'fieldset', '#title' => t('CSS'), ); - $form['css']['advagg_mod_css_preprocess'] = array( + $form['global_container']['css']['advagg_mod_css_preprocess'] = array( '#type' => 'checkbox', - '#title' => t('Enable preprocess on all CSS'), + '#title' => t('Enable preprocess on all CSS (recommended)'), '#default_value' => variable_get('advagg_mod_css_preprocess', ADVAGG_MOD_CSS_PREPROCESS), '#description' => t('Force all CSS to have the preprocess attribute be set to TRUE. All CSS files will be aggregated if enabled.'), + '#recommended_value' => TRUE, ); - // @ignore security_fapi_title - $form['css']['advagg_mod_css_preprocess']['#description'] .= module_exists('advagg_bundler') ? ' ' . t('You might want to increase the <a href="@link">CSS Bundles Per Page</a> if this is checked.', array('@link' => url($config_path . '/advagg/bundler'))) : ''; + $form['global_container']['css']['advagg_mod_css_preprocess']['#description'] .= module_exists('advagg_bundler') ? ' ' . t('You might want to increase the <a href="@link">CSS Bundles Per Page</a> if this is checked.', array('@link' => url($config_path . '/advagg/bundler'))) : ''; + if (is_callable('omega_extension_enabled') + && is_callable('omega_theme_get_setting') + && omega_extension_enabled('development') + && omega_theme_get_setting('omega_livereload', TRUE) + ) { + $form['global_container']['css']['advagg_mod_css_preprocess']['#description'] .= ' ' . t('The omega theme is in development mode and livereload is also enabled. This setting combination disables CSS aggregation. If you wish to have CSS aggregation turned on, go to the <a href="@url">theme settings page</a>, select the Omega theme/subtheme and turn off LiveReload and/or turn off the Development extension.', array('@url' => url('admin/appearance/settings'))); + } // Only test the translate option if // the locale function is defined OR // the locale_custom_strings variable is not empty. @@ -194,7 +309,7 @@ function advagg_mod_admin_settings_form() { // Only show option if something comes back translated. $files = advagg_mod_admin_test_css_files('css'); if (!empty($files)) { - $form['css']['advagg_mod_css_translate'] = array( + $form['global_container']['css']['advagg_mod_css_translate'] = array( '#type' => 'checkbox', '#title' => t('Translate CSS content strings'), '#default_value' => variable_get('advagg_mod_css_translate', ADVAGG_MOD_CSS_TRANSLATE), @@ -206,47 +321,47 @@ function advagg_mod_admin_settings_form() { } } // Optimize CSS Ordering. - $form['css']['adjust_sort'] = array( + $form['global_container']['css']['adjust_sort'] = array( '#type' => 'fieldset', '#title' => t('Optimize CSS Ordering'), '#description' => t('The settings in here might change the order in which the CSS is loaded. It will move the CSS around so that more optimal aggregates are built. In most cases enabling these checkboxes will cause no negative side effects.'), ); - $form['css']['adjust_sort']['advagg_mod_css_head_extract'] = array( + $form['global_container']['css']['adjust_sort']['advagg_mod_css_head_extract'] = array( '#type' => 'checkbox', - '#title' => t('Move CSS added by drupal_add_html_head() into drupal_add_css()'), + '#title' => t('Move CSS added by drupal_add_html_head() into drupal_add_css() (recommended)'), '#default_value' => variable_get('advagg_mod_css_head_extract', ADVAGG_MOD_CSS_HEAD_EXTRACT), '#description' => t('This will move CSS added incorrectly to Drupal into the top of the drupal_add_css() queue.'), + '#recommended_value' => TRUE, ); - $form['css']['adjust_sort']['advagg_mod_css_adjust_sort_external'] = array( + $form['global_container']['css']['adjust_sort']['advagg_mod_css_adjust_sort_external'] = array( '#type' => 'checkbox', - '#title' => t('Move all external CSS to the top of the execution order'), + '#title' => t('Move all external CSS to the top of the execution order (recommended)'), '#default_value' => variable_get('advagg_mod_css_adjust_sort_external', ADVAGG_MOD_CSS_ADJUST_SORT_EXTERNAL), '#description' => t('This will group all external CSS files to be above all other CSS.'), + '#recommended_value' => TRUE, ); - $form['css']['adjust_sort']['advagg_mod_css_adjust_sort_inline'] = array( + $form['global_container']['css']['adjust_sort']['advagg_mod_css_adjust_sort_inline'] = array( '#type' => 'checkbox', - '#title' => t('Move all inline CSS to the bottom of the execution order'), + '#title' => t('Move all inline CSS to the bottom of the execution order (recommended)'), '#default_value' => variable_get('advagg_mod_css_adjust_sort_inline', ADVAGG_MOD_CSS_ADJUST_SORT_INLINE), '#description' => t('This will group all inline CSS to be below all other CSS.'), + '#recommended_value' => TRUE, ); - $form['css']['adjust_sort']['advagg_mod_css_adjust_sort_browsers'] = array( + $form['global_container']['css']['adjust_sort']['advagg_mod_css_adjust_sort_browsers'] = array( '#type' => 'checkbox', - '#title' => t('Move all browser conditional CSS to the bottom of the group'), + '#title' => t('Move all browser conditional CSS to the bottom of the group (recommended)'), '#default_value' => variable_get('advagg_mod_css_adjust_sort_browsers', ADVAGG_MOD_CSS_ADJUST_SORT_BROWSERS), '#description' => t('This will group all browser conditional CSS to be in the lowest group of that conditional rule.'), + '#recommended_value' => TRUE, ); // Adjust CSS location and execution. - $form['css']['placement'] = array( + $form['global_container']['css']['placement'] = array( '#type' => 'fieldset', '#title' => t('Adjust CSS location and execution'), - ); - $form['css']['placement']['expert'] = array( - '#type' => 'fieldset', - '#title' => t('Experimental Settings'), '#collapsible' => TRUE, '#collapsed' => !variable_get('advagg_mod_css_defer', ADVAGG_MOD_CSS_DEFER), ); - $form['css']['placement']['expert']['advagg_mod_css_defer'] = array( + $form['global_container']['css']['placement']['advagg_mod_css_defer'] = array( '#type' => 'radios', '#title' => t('Deferred CSS Execution: Use JS to load CSS'), '#default_value' => variable_get('advagg_mod_css_defer', ADVAGG_MOD_CSS_DEFER), @@ -254,94 +369,254 @@ function advagg_mod_admin_settings_form() { 0 => t('Disabled'), 1 => t('All in head, above js'), 3 => t('All in head'), - 5 => t('All in footer except for JS loading code (If enabled this is recommended)'), + 4 => t('All in head, use link rel="preload" (recommended)'), + 5 => t('All in footer except for JS loading code'), 7 => t('All in footer'), ), '#description' => t('This will try to optimize CSS delivery by using JavaScript to load the CSS. This might break CSS on different browsers and will cause a flash of unstyled content (FOUC). Only enable after extensive testing! <a href="@link">More Info</a>', array( '@link' => 'http://stackoverflow.com/questions/19374843/css-delivery-optimization-how-to-defer-css-loading', )), + '#recommended_value' => 4, ); - $form['css']['placement']['expert']['advagg_mod_css_defer_js_code'] = array( + // 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 (blacklist)'), + ADVAGG_MOD_VISIBILITY_LISTED => t('Only the listed pages (whitelist)'), + ); + $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' => '<front>', + )); + + if (module_exists('php') && $access) { + $options += array(ADVAGG_MOD_VISIBILITY_PHP => t('Pages on which this PHP code returns <code>TRUE</code> (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' => '<?php ?>')); + } + else { + $title = t('Pages'); + } + $options += array(ADVAGG_MOD_VISIBILITY_FILE_CONTROLLED => t('File Controlled (Recommended).')); + + if ($visibility_defer == ADVAGG_MOD_VISIBILITY_PHP && !$access) { + $form['global_container']['css']['placement']['visibility_all'] = array( + '#type' => 'value', + '#value' => $visibility_defer, + ); + $form['global_container']['css']['placement']['pages_all'] = array( + '#type' => 'value', + '#value' => $css_defer_pages, + ); + } + else { + $form['global_container']['css']['placement']['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. File Controlled will only use loadCSS() if a critical css file has been found and can be inlined.'), + '#states' => array( + 'disabled' => array( + ':input[name="advagg_mod_css_defer"]' => array('value' => '0'), + ), + ), + '#recommended_value' => ADVAGG_MOD_VISIBILITY_FILE_CONTROLLED, + ); + $form['global_container']['css']['placement']['advagg_mod_css_defer_pages'] = array( + '#type' => 'textarea', + '#title' => '<span class="element-invisible">' . $title . '</span>', + '#default_value' => $css_defer_pages, + '#description' => $description, + '#states' => array( + 'disabled' => array( + ':input[name="advagg_mod_css_defer"]' => array('value' => '0'), + ), + 'invisible' => array( + ':input[name="advagg_mod_css_defer_visibility"]' => array('value' => '3'), + ), + ), + ); + list(, $params) = advagg_mod_find_critical_css_file(); + list($dirs) = $params; + $theme_path = drupal_get_path('theme', variable_get('theme_default', NULL)) . '/'; + $form['global_container']['css']['placement']['advagg_mod_css_defer_visibility_file_explained'] = array( + '#type' => 'item', + '#markup' => t('Valid example file locations for this page: <p><code>@line1</code><br><code>@line2</code><br><code>@line3</code></p> valid example file locations for the "front" page: <p><code>@line4</code><br><code>@line5</code><br><code>@line6</code></p> valid example file locations for "node/1" (<a href="@url">current_path()</a>) page: <p><code>@line7</code><br><code>@line8</code><br><code>@line9</code></p> valid example file locations for the node type of "page": <p><code>@line10</code><br><code>@line11</code><br><code>@line12</code></p>', array( + '@url' => 'https://api.drupal.org/api/drupal/includes%21path.inc/function/current_path/7.x', + '@line1' => "{$dirs[0]}{$dirs[1]}{$dirs[2]}{$dirs[4]}{$dirs[6]}.css", + '@line2' => "{$dirs[0]}{$dirs[1]}anonymous/{$dirs[4]}{$dirs[6]}.css", + '@line3' => "{$dirs[0]}{$dirs[1]}{$dirs[3]}{$dirs[4]}{$dirs[6]}.css", + '@line4' => "{$theme_path}{$dirs[1]}{$dirs[2]}{$dirs[4]}front.css", + '@line5' => "{$theme_path}{$dirs[1]}anonymous/{$dirs[4]}front.css", + '@line6' => "{$theme_path}{$dirs[1]}{$dirs[3]}{$dirs[4]}front.css", + '@line7' => "{$theme_path}{$dirs[1]}{$dirs[2]}{$dirs[4]}node/1.css", + '@line8' => "{$theme_path}{$dirs[1]}anonymous/{$dirs[4]}node/1.css", + '@line9' => "{$theme_path}{$dirs[1]}{$dirs[3]}{$dirs[4]}node/1.css", + '@line10' => "{$theme_path}{$dirs[1]}{$dirs[2]}{$dirs[5]}page.css", + '@line11' => "{$theme_path}{$dirs[1]}anonymous/{$dirs[5]}page.css", + '@line12' => "{$theme_path}{$dirs[1]}{$dirs[3]}{$dirs[5]}page.css", + )), + '#states' => array( + 'visible' => array( + ':input[name="advagg_mod_css_defer_visibility"]' => array('value' => '3'), + ), + ), + ); + } + $form['global_container']['css']['placement']['advagg_mod_css_defer_skip_first_file'] = array( + '#type' => 'radios', + '#title' => t('Do not defer the first css file'), + '#default_value' => variable_get('advagg_mod_css_defer_skip_first_file', ADVAGG_MOD_CSS_DEFER_SKIP_FIRST_FILE), + '#description' => t('Link Stylesheet will make the first css file be blocking. Inline CSS will inline up to @size of the first CSS files.', array('@size' => advagg_mod_admin_byte2human(variable_get('advagg_mod_css_defer_inline_size_limit', ADVAGG_MOD_CSS_DEFER_INLINE_SIZE_LIMIT)))), + '#options' => array( + 0 => t('Disabled'), + 2 => t('Link Stylesheet'), + 4 => t('Inline CSS (no more than @size)', array('@size' => advagg_mod_admin_byte2human(variable_get('advagg_mod_css_defer_inline_size_limit', ADVAGG_MOD_CSS_DEFER_INLINE_SIZE_LIMIT)))), + ), + '#states' => array( + 'disabled' => array( + ':input[name="advagg_mod_css_defer"]' => array('value' => '0'), + ), + 'invisible' => array( + ':input[name="advagg_mod_css_defer_visibility"]' => array('value' => '3'), + ), + ), + ); + $form['global_container']['css']['placement']['advagg_mod_css_defer_js_code'] = array( '#type' => 'radios', '#title' => t('How to include the JS loading code'), '#default_value' => variable_get('advagg_mod_css_defer_js_code', ADVAGG_MOD_CSS_DEFER_JS_CODE), '#options' => array( - 0 => t('Inline javascript loader library (If enabled this is recommended)'), + 0 => t('Inline javascript loader library (recommended)'), 2 => t('Local file included in aggregate'), - // 4 => t('Externally load the latest from github (SLOW! Not a CDN!!!)'), + 4 => t('Externally load the latest from github'), ), '#description' => t('The <a href="@link">loadCSS</a> library can be included in various ways.', array( '@link' => 'https://github.com/filamentgroup/loadCSS', )), + '#recommended_value' => 0, '#states' => array( 'disabled' => array( ':input[name="advagg_mod_css_defer"]' => array('value' => '0'), ), ), + + ); + $form['global_container']['css']['placement']['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'), + '#states' => array( + 'disabled' => array( + ':input[name="advagg_mod_css_defer"]' => array('value' => '0'), + ), + 'invisible' => array( + ':input[name="advagg_mod_css_defer_visibility"]' => array('value' => '3'), + ), + ), ); - $form['landing_page'] = array( + $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], $options[ADVAGG_MOD_VISIBILITY_FILE_CONTROLLED]); + $form['global_container']['landing_page'] = array( '#type' => 'fieldset', '#title' => t('Inline CSS/JS on specific pages'), - '#description' => t('For most people this is a setting that should not be used. This will prevent all local CSS & JavaScript files from being downloaded; instead the contents of them will all be inlined. This will cause the raw HTML downloaded to be a lot bigger but it will cause less connections to your webserver from being created. This can sometimes be useful for certain landing pages.'), + '#description' => t('For most people these are settings that should not be used. This will prevent all local CSS and/or JavaScript files from being downloaded; instead the contents of them will all be inlined. This will cause the raw HTML downloaded to be a lot bigger but it will cause less connections to your webserver from being created. This can sometimes be useful for certain landing pages.'), '#collapsible' => TRUE, - '#collapsed' => TRUE, + '#collapsed' => ($pages_all || $pages_css || $pages_js) ? FALSE : TRUE, ); - // Taken from block_admin_configure(). - $access = user_access('use PHP for settings'); - $visibility = variable_get('advagg_mod_inline_visibility', ADVAGG_MOD_VISIBILITY_LISTED); - $pages = variable_get('advagg_mod_inline_pages', ''); - if ($visibility == ADVAGG_MOD_VISIBILITY_PHP && !$access) { - $form['landing_page']['path']['visibility'] = array( + $visibility_all = variable_get('advagg_mod_inline_visibility', ADVAGG_MOD_VISIBILITY_LISTED); + if ($visibility_all == ADVAGG_MOD_VISIBILITY_PHP && !$access) { + $form['global_container']['landing_page']['path']['visibility_all'] = array( '#type' => 'value', - '#value' => $visibility, + '#value' => $visibility_all, ); - $form['landing_page']['path']['pages'] = array( + $form['global_container']['landing_page']['path']['pages_all'] = array( '#type' => 'value', - '#value' => $pages, + '#value' => $pages_all, ); } else { - $options = array( - // ADVAGG_MOD_VISIBILITY_NOTLISTED => t('All pages except those listed'), - ADVAGG_MOD_VISIBILITY_LISTED => t('Only the listed pages'), + $form['global_container']['landing_page']['path']['advagg_mod_inline_visibility'] = array( + '#type' => 'radios', + '#title' => t('Inline CSS and JS on specific pages'), + '#options' => $options, + '#default_value' => $visibility_all, ); - $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' => '<front>', - )); + $form['global_container']['landing_page']['path']['advagg_mod_inline_pages'] = array( + '#type' => 'textarea', + '#title' => '<span class="element-invisible">' . $title . '</span>', + '#default_value' => $pages_all, + '#description' => $description, + ); + } - if (module_exists('php') && $access) { - $options += array(ADVAGG_MOD_VISIBILITY_PHP => t('Pages on which this PHP code returns <code>TRUE</code> (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' => '<?php ?>')); - } - else { - $title = t('Pages'); - } - $form['landing_page']['path']['advagg_mod_inline_settings'] = array( + $visibility_css = variable_get('advagg_mod_inline_css_visibility', ADVAGG_MOD_VISIBILITY_LISTED); + if ($visibility_css == ADVAGG_MOD_VISIBILITY_PHP && !$access) { + $form['global_container']['landing_page']['path']['visibility_all'] = array( + '#type' => 'value', + '#value' => $visibility_css, + ); + $form['global_container']['landing_page']['path']['pages_all'] = array( + '#type' => 'value', + '#value' => $pages_css, + ); + } + else { + $form['global_container']['landing_page']['path']['advagg_mod_inline_css_visibility'] = array( + '#type' => 'radios', + '#title' => t('Inline CSS on specific pages'), + '#options' => $options, + '#default_value' => $visibility_css, + ); + $form['global_container']['landing_page']['path']['advagg_mod_inline_css_pages'] = array( + '#type' => 'textarea', + '#title' => '<span class="element-invisible">' . $title . '</span>', + '#default_value' => $pages_css, + '#description' => $description, + ); + } + $visibility_js = variable_get('advagg_mod_inline_js_visibility', ADVAGG_MOD_VISIBILITY_LISTED); + if ($visibility_js == ADVAGG_MOD_VISIBILITY_PHP && !$access) { + $form['global_container']['landing_page']['path']['visibility_all'] = array( + '#type' => 'value', + '#value' => $visibility_js, + ); + $form['global_container']['landing_page']['path']['pages_all'] = array( + '#type' => 'value', + '#value' => $pages_js, + ); + } + else { + $form['global_container']['landing_page']['path']['advagg_mod_inline_js_visibility'] = array( '#type' => 'radios', - '#title' => t('Inline CSS/JS on specific pages'), + '#title' => t('Inline JS on specific pages'), '#options' => $options, - '#default_value' => $visibility, + '#default_value' => $visibility_js, ); - $form['landing_page']['path']['advagg_mod_inline_pages'] = array( + $form['global_container']['landing_page']['path']['advagg_mod_inline_js_pages'] = array( '#type' => 'textarea', '#title' => '<span class="element-invisible">' . $title . '</span>', - '#default_value' => $pages, + '#default_value' => $pages_js, '#description' => $description, ); } - $form['unified_multisite'] = array( + $form['global_container']['unified_multisite'] = array( '#type' => 'fieldset', '#title' => t('Unified Multisite'), '#description' => t('For most people this is a setting that should not be used.'), '#collapsible' => TRUE, '#collapsed' => TRUE, ); - $form['unified_multisite']['advagg_mod_unified_multisite_dir'] = array( + $form['global_container']['unified_multisite']['advagg_mod_unified_multisite_dir'] = array( '#type' => 'textfield', '#title' => t('Shared Directory'), '#default_value' => variable_get('advagg_mod_unified_multisite_dir', ''), @@ -363,42 +638,62 @@ function advagg_mod_admin_settings_form() { return system_settings_form($form); } -// Validate callback. /** - * Make sure the unified multisite directory was created correctly. + * Validate callback, check that the unified multisite directory was created. + * + * @ingroup advagg_forms_callback */ function advagg_mod_admin_settings_form_validate($form, &$form_state) { + // Unified Mmultisite. $multisite_dir = rtrim($form_state['values']['advagg_mod_unified_multisite_dir'], '/'); - // Return if unified_multisite_dir is not set. - if (empty($multisite_dir)) { - return; + if (!empty($multisite_dir)) { + // Prepare directory. + $css_dir = $multisite_dir . '/advagg_css'; + $js_dir = $multisite_dir . '/advagg_js'; + if (!file_prepare_directory($css_dir, FILE_CREATE_DIRECTORY) + || !file_prepare_directory($js_dir, FILE_CREATE_DIRECTORY) + ) { + if (!is_dir($multisite_dir) || !is_writable($multisite_dir)) { + form_set_error('advagg_mod_unified_multisite_dir', t('%dir is not a directory or can not be written to. The shared directory needs to have the same permissions as the "Public file system path" found on the <a href="@file_system_link">File System configuration page</a>.', array( + '%dir' => $multisite_dir, + '@file_system_link' => url('admin/config/media/file-system'), + ))); + return; + } + } } - // Prepare directory. - $css_dir = $multisite_dir . '/advagg_css'; - $js_dir = $multisite_dir . '/advagg_js'; - // @ignore sniffer_whitespace_openbracketspacing_openingwhitespace - if ( !file_prepare_directory($css_dir, FILE_CREATE_DIRECTORY) - || !file_prepare_directory($js_dir, FILE_CREATE_DIRECTORY) - ) { - if (!is_dir($multisite_dir) || !is_writable($multisite_dir)) { - form_set_error('advagg_mod_unified_multisite_dir', t('%dir is not a directory or can not be written to. The shared directory needs to have the same permissions as the "Public file system path" found on the <a href="@file_system_link">File System configuration page</a>.', array( - '%dir' => $multisite_dir, - '@file_system_link' => url('admin/config/media/file-system'), - ))); - return; + // Use JS to load CSS. + if ($form_state['values']['advagg_mod_css_defer']) { + if ($form_state['values']['advagg_mod_css_defer_visibility'] == ADVAGG_MOD_VISIBILITY_LISTED + && empty($form_state['values']['advagg_mod_css_defer_pages']) + ) { + form_set_error('advagg_mod_css_defer_pages', t('The Deferred CSS Execution setting will run only on specific pages. On other pages it will act as being Disabled. Please input what pages you wish to have the CSS be deferred. Currently none are selected.')); + } + if ($form_state['values']['advagg_mod_css_defer_visibility'] == ADVAGG_MOD_VISIBILITY_NOTLISTED + && $form_state['values']['advagg_mod_css_defer_pages'] === '*' + ) { + form_set_error('advagg_mod_css_defer_pages', t('The Deferred CSS Execution setting will run only on specific pages. On other pages it will act as being Disabled. Please input what pages you wish to have the CSS not be deferred. Currently all pages are disabled (*).')); } } } -// Submit callback. /** - * Clear out the advagg cache bin when the save configuration button is pressed. + * Submit callback, clear out the advagg cache bin. + * + * @ingroup advagg_forms_callback */ 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(); + + // Reset this form to defaults or recommended values; also show what changed. + advagg_set_admin_form_defaults_recommended($form_state, 'advagg_mod_admin_mode'); + + // If file controlled, turn off skip first file turn on admin defer. + if ($form_state['values']['advagg_mod_css_defer_visibility'] == 3) { + $form_state['values']['advagg_mod_css_defer_skip_first_file'] = 0; + $form_state['values']['advagg_mod_css_defer_admin'] = TRUE; } // If unified_multisite_dir has changed, flush menu router at the end of the @@ -409,6 +704,10 @@ function advagg_mod_admin_settings_form_submit($form, &$form_state) { register_shutdown_function('advagg_get_root_files_dir', TRUE); register_shutdown_function('menu_rebuild'); } + + if (empty($form_state['values']['advagg_mod_js_defer_inline_alter']) && !empty($form_state['values']['advagg_mod_js_defer_jquery'])) { + $form_state['values']['advagg_mod_js_defer_jquery'] = FALSE; + } } /** @@ -422,8 +721,8 @@ function advagg_mod_admin_test_css_files() { // Get list of files. $query_files = db_select('advagg_files', 'af') ->fields('af', array('filename_hash', 'filename')) - ->condition('af.filetype', 'css') - ->orderBy('filename', 'DESC') + ->condition('filetype', 'css') + ->orderBy('filename', 'ASC') ->execute() ->fetchAllKeyed(); $files = array_values($query_files); @@ -438,7 +737,7 @@ function advagg_mod_admin_test_css_files() { continue; } // Load CSS file. - $file_contents = advagg_load_stylesheet_content(file_get_contents($filename), TRUE); + $file_contents = advagg_load_stylesheet_content((string) @advagg_file_get_contents($filename), TRUE); // Code taken from drupal_load_stylesheet_content(). // Regexp to match double quoted strings. @@ -473,7 +772,7 @@ function advagg_mod_admin_test_css_files() { continue; } // Run t function. - // @ignore sniffer_semantics_functioncall_notliteralstring + // @codingStandardsIgnoreLine $after = t($before); // Only include it if strings are different. @@ -487,3 +786,26 @@ function advagg_mod_admin_test_css_files() { } return $output; } + +/** + * Converts a number of bytes into human readable format. + * + * @param string $bytes + * Number to convert into a more human readable format. + * @param int $precision + * Number of decimals to output. + * + * @return string + * Human readable format of the bytes. + */ +function advagg_mod_admin_byte2human($bytes, $precision = 0) { + $units = array('', 'K', 'M', 'G', 'T'); + + $bytes = max($bytes, 0); + $pow = floor(($bytes ? log($bytes) : 0) / log(1024)); + $pow = min($pow, count($units) - 1); + $bytes /= (1 << (10 * $pow)); + + $output = ceil(round($bytes, $precision + 2) * 10) / 10; + return $output . '' . $units[$pow]; +} diff --git a/sites/all/modules/contrib/advagg/advagg_mod/advagg_mod.advagg.inc b/sites/all/modules/contrib/advagg/advagg_mod/advagg_mod.advagg.inc index 743751192b..b3fac149c1 100644 --- a/sites/all/modules/contrib/advagg/advagg_mod/advagg_mod.advagg.inc +++ b/sites/all/modules/contrib/advagg/advagg_mod/advagg_mod.advagg.inc @@ -5,6 +5,11 @@ * Advanced CSS/JS aggregation modifier module. */ +/** + * @addtogroup advagg_hooks + * @{ + */ + /** * Implements hook_advagg_get_info_on_files_alter(). * @@ -16,8 +21,7 @@ function advagg_mod_advagg_get_info_on_files_alter(&$return, $cached_data, $bypa continue; } // New or updated data or no advagg_js_compress data. - // @ignore sniffer_whitespace_openbracketspacing_openingwhitespace - if ( empty($cached_data[$info['cache_id']]) + if (empty($cached_data[$info['cache_id']]) || empty($info['advagg_mod']) || $info['content_hash'] != $cached_data[$info['cache_id']]['content_hash'] ) { @@ -28,6 +32,16 @@ function advagg_mod_advagg_get_info_on_files_alter(&$return, $cached_data, $bypa unset($info); } +/** + * Implements hook_advagg_get_js_file_contents_alter(). + */ +function advagg_mod_advagg_get_js_file_contents_alter(&$contents, $filename, $aggregate_settings) { + // Fix adminimal_admin_menu js issues with admin menu. + if (module_exists('adminimal_admin_menu')) { + advagg_mod_fix_js_adminimal_admin_menu($contents, $filename); + } +} + /** * Implements hook_advagg_get_css_file_contents_alter(). * @@ -54,6 +68,10 @@ function advagg_mod_advagg_get_css_file_contents_alter(&$contents, $filename, $a $contents = preg_replace_callback($css_content_pattern, 'advagg_mod_advagg_css_content_t_replace_callback', $contents); } +/** + * @} End of "addtogroup advagg_hooks". + */ + /** * Run preg matches through the t() function. * @@ -82,8 +100,67 @@ function advagg_mod_advagg_css_content_t_replace_callback(array $matches) { } // Run t function. - // @ignore sniffer_semantics_functioncall_notliteralstring + // @codingStandardsIgnoreLine $after = t($before); // Put back. return str_replace($matches[1], str_replace($before, $after, $matches[1]), $matches[0]); } + +/** + * Fix admin menu. + * + * @param string $contents + * The contents of the js file. + * @param string $filename + * The filename. + */ +function advagg_mod_fix_js_adminimal_admin_menu(&$contents, $filename) { + // Get adminimal_admin_menu path. + $adminimal_admin_menu_path = drupal_get_path('module', 'adminimal_admin_menu'); + // Only match slicknav js. + if ($filename !== "$adminimal_admin_menu_path/js/slicknav/jquery.slicknav.js" + && $filename !== "$adminimal_admin_menu_path/js/slicknav/jquery-no-conflict.slicknav.js" + ) { + return; + } + + // Lines to look for. + $inserted_string_1 = "Drupal.admin = Drupal.admin || {};\n"; + $inserted_string_2 = "Drupal.admin.behaviors = Drupal.admin.behaviors || {};\n"; + if (strpos($contents, $inserted_string_1) !== FALSE + && strpos($contents, $inserted_string_2) !== FALSE + ) { + // Do nothing if the lines already exist. + return; + } + + // Get length of slicknav javascript. + $strlen = strlen($contents); + // Get the first occurrence of the problem string. + $end = strpos($contents, 'Drupal.admin.behaviors.responsivemenu'); + if ($end === FALSE) { + return; + } + // Get the start of this code block. + $start = strrpos($contents, '(function($)', $end - $strlen); + if ($start === FALSE) { + return; + } + // Get the lcoation of {. + $middle = strpos($contents, "{\n", $start); + if ($middle === FALSE) { + $middle = strpos($contents, "{", $start); + if ($middle === FALSE) { + return; + } + else { + $middle += 1; + } + } + else { + $middle += 2; + } + + // Insert new js code. + $contents = substr_replace($contents, $inserted_string_1 . $inserted_string_2, $middle, 0); +} 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 3e8945399e..5ecf7d1e8d 100644 --- a/sites/all/modules/contrib/advagg/advagg_mod/advagg_mod.info +++ b/sites/all/modules/contrib/advagg/advagg_mod/advagg_mod.info @@ -3,12 +3,12 @@ description = Allows one to alter the CSS and JS array. package = Advanced CSS/JS Aggregation core = 7.x dependencies[] = advagg +recommends[] = libraries configure = admin/config/development/performance/advagg/mod -; Information added by Drupal.org packaging script on 2015-04-14 -version = "7.x-2.8" +; Information added by Drupal.org packaging script on 2020-11-19 +version = "7.x-2.35" core = "7.x" project = "advagg" -datestamp = "1429049283" - +datestamp = "1605792717" diff --git a/sites/all/modules/contrib/advagg/advagg_mod/advagg_mod.install b/sites/all/modules/contrib/advagg/advagg_mod/advagg_mod.install new file mode 100644 index 0000000000..9cb2892e2a --- /dev/null +++ b/sites/all/modules/contrib/advagg/advagg_mod/advagg_mod.install @@ -0,0 +1,87 @@ +<?php + +/** + * @file + * Handles AdvAgg mod installation and upgrade tasks. + */ + +/** + * @addtogroup hooks + * @{ + */ + +/** + * Implements hook_install(). + */ +function advagg_mod_install() { + // New install gets a locked admin section. + variable_set('advagg_mod_admin_mode', 0); +} + +/** + * Merge advagg_mod_css_defer_rel_preload into advagg_mod_css_defer. + */ +function advagg_mod_update_7201(&$sandbox) { + // Merge advagg_mod_css_defer_rel_preload into advagg_mod_css_defer. + $advagg_mod_css_defer = variable_get('advagg_mod_css_defer', ADVAGG_MOD_CSS_DEFER); + $advagg_mod_css_defer_rel_preload = variable_get('advagg_mod_css_defer_rel_preload', ADVAGG_MOD_CSS_DEFER_REL_PRELOAD); + variable_del('advagg_mod_css_defer_rel_preload'); + if (!empty($advagg_mod_css_defer) && !empty($advagg_mod_css_defer_rel_preload)) { + variable_set('advagg_mod_css_defer', 4); + return t('The advagg_mod_css_defer_rel_preload variable has been deleted. The advagg_mod_css_defer variable has been set to a value of 4 (All in head, use link rel="preload"). You can adjust this value on the <a href="@url">AdvAgg Modifications page</a>', array('@url' => url('admin/config/development/performance/advagg/mod', array('fragment' => 'edit-advagg-mod-css-defer')))); + } + else { + return t('The advagg_mod_css_defer_rel_preload variable has been deleted.'); + } +} + +/** + * Move advagg_mod_js_get_external_dns to advagg_mod_js_inline_resource_hints. + */ +function advagg_mod_update_7202(&$sandbox) { + $advagg_mod_js_inline_resource_hints = variable_get('advagg_mod_js_inline_resource_hints', NULL); + $advagg_mod_js_get_external_dns = variable_get('advagg_mod_js_get_external_dns', NULL); + + if (is_null($advagg_mod_js_get_external_dns)) { + return t('Nothing needed to happen.'); + } + variable_del('advagg_mod_js_get_external_dns'); + if (!is_null($advagg_mod_js_inline_resource_hints) || empty($advagg_mod_js_inline_resource_hints)) { + return t('Nothing needed to happen.'); + } + variable_set('advagg_mod_js_inline_resource_hints', $advagg_mod_js_inline_resource_hints); + return t('Variable has been moved.'); +} + +/** + * @} End of "addtogroup hooks". + */ + +/** + * Implements hook_requirements(). + */ +function advagg_mod_requirements($phase) { + $requirements = array(); + // Ensure translations don't break at install time. + $t = get_t(); + + // If not at runtime, return here. + if ($phase !== 'runtime') { + return $requirements; + } + + // Check version. + $lib_name = 'loadCSS'; + $module_name = 'advagg_mod'; + list($description, $info) = advagg_get_version_description($lib_name, $module_name); + if (!empty($description)) { + $requirements["{$module_name}_{$lib_name}_updates"] = array( + 'title' => $t('@module_name', array('@module_name' => $info['name'])), + 'severity' => REQUIREMENT_WARNING, + 'value' => $t('The @name library needs to be updated.', array('@name' => $lib_name)), + 'description' => $description, + ); + } + + return $requirements; +} 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 1580baeb24..1a56b3e97a 100644 --- a/sites/all/modules/contrib/advagg/advagg_mod/advagg_mod.module +++ b/sites/all/modules/contrib/advagg/advagg_mod/advagg_mod.module @@ -5,7 +5,11 @@ * Advanced aggregation modifier module. */ -// Define default variables. +/** + * @addtogroup default_variables + * @{ + */ + /** * Default value to move all JS to the footer. */ @@ -19,7 +23,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 +78,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(). @@ -94,15 +103,15 @@ define('ADVAGG_MOD_JS_ASYNC', FALSE); /** * Default value to wrap inline content javascript so it runs when it is ready. */ -define('ADVAGG_MOD_JS_FOOTER_INLINE_ALTER', TRUE); +define('ADVAGG_MOD_JS_FOOTER_INLINE_ALTER', FALSE); /** - * Turns on functionality on every page except the listed pages. + * Turns on functionality on every page except the listed pages (blacklist). */ define('ADVAGG_MOD_VISIBILITY_NOTLISTED', 0); /** - * Turns on functionality only on the listed pages. + * Turns on functionality only on the listed pages (whitelist). */ define('ADVAGG_MOD_VISIBILITY_LISTED', 1); @@ -111,6 +120,11 @@ define('ADVAGG_MOD_VISIBILITY_LISTED', 1); */ define('ADVAGG_MOD_VISIBILITY_PHP', 2); +/** + * Turns on functionality if there is a file matching the page pattern. + */ +define('ADVAGG_MOD_VISIBILITY_FILE_CONTROLLED', 3); + /** * Default value of the inclusion method for the loadCSS code. */ @@ -131,40 +145,206 @@ define('ADVAGG_MOD_WRAP_INLINE_JS_SKIP_LIST', ''); */ define('ADVAGG_MOD_WRAP_INLINE_JS_XPATH', FALSE); -// Core hook implementations. +/** + * Default value to wrap inline content javascript so it runs deferred. + */ +define('ADVAGG_MOD_JS_DEFER_INLINE_ALTER', FALSE); + +/** + * Default value for inline scripts that should not be deferred. + */ +define('ADVAGG_MOD_DEFER_INLINE_JS_SKIP_LIST', ''); + +/** + * Default value to move async js to the header. + */ +define('ADVAGG_MOD_JS_ASYNC_IN_HEADER', FALSE); + +/** + * Default value to remove ajaxPageState if ajax.js is not used. + */ +define('ADVAGG_MOD_JS_NO_AJAXPAGESTATE', FALSE); + +/** + * Default value to scan html for src tags and do resource hints on it. + */ +define('ADVAGG_MOD_JS_INLINE_RESOURCE_HINTS', FALSE); + +/** + * Default value to defer jquery. + */ +define('ADVAGG_MOD_JS_DEFER_JQUERY', TRUE); + +/** + * 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); + +/** + * Default value to not defer the first CSS file. + */ +define('ADVAGG_MOD_CSS_DEFER_SKIP_FIRST_FILE', 0); + +/** + * Default value of the inlined css size. + */ +define('ADVAGG_MOD_CSS_DEFER_INLINE_SIZE_LIMIT', 12288); + +/** + * Default to strip !important from inline critical css. + */ +define('ADVAGG_MOD_INLINE_CRITICAL_CSS_STRIP_IMPORTANT', TRUE); + +/** + * If 4 the admin section gets unlocked. + */ +define('ADVAGG_MOD_ADMIN_MODE', 4); + +/** + * Default value. + */ +define('ADVAGG_MOD_INLINE_JS_VISIBILITY', ADVAGG_MOD_VISIBILITY_LISTED); + +/** + * Default value. + */ +define('ADVAGG_MOD_INLINE_CSS_VISIBILITY', ADVAGG_MOD_VISIBILITY_LISTED); + +/** + * Default value. + */ +define('ADVAGG_MOD_INLINE_VISIBILITY', ADVAGG_MOD_VISIBILITY_LISTED); + +/** + * Default value. + */ +define('ADVAGG_MOD_CSS_DEFER_VISIBILITY', ADVAGG_MOD_VISIBILITY_LISTED); + +/** + * @} End of "addtogroup default_variables". + */ + +/** + * @addtogroup hooks + * @{ + */ + +/** + * Implements hook_module_implements_alter(). + */ +function advagg_mod_module_implements_alter(&$implementations, $hook) { + // Move advagg_mod to the top. + if ($hook === 'library_alter' && array_key_exists('advagg_mod', $implementations)) { + $item = array('advagg_mod' => $implementations['advagg_mod']); + 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']); + } + + // Move advagg_mod_css_post_alter to the bottom. + if ($hook === 'css_post_alter' && array_key_exists('advagg_mod', $implementations)) { + $item = $implementations['advagg_mod']; + unset($implementations['advagg_mod']); + $implementations['advagg_mod'] = $item; + } + + // Move advagg_mod_js_post_alter to the bottom. + if ($hook === 'js_post_alter' && array_key_exists('advagg_mod', $implementations)) { + $item = $implementations['advagg_mod']; + unset($implementations['advagg_mod']); + $implementations['advagg_mod'] = $item; + } +} + +/** + * Implements hook_library_alter(). + */ +function advagg_mod_library_alter(&$javascript, $module) { + if (!advagg_enabled()) { + return; + } + if (!module_exists('jquery_update')) { + return; + } + if (!advagg_mod_inline_page_js()) { + return; + } + // Set the CDN to none for this page as everything is going to inlined. + $GLOBALS['conf']['jquery_update_jquery_cdn'] = 'none'; + $GLOBALS['conf']['jquery_update_jquery_migrate_cdn'] = 'none'; +} + /** * Implements hook_init(). */ function advagg_mod_init() { - // 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)) { + if (!module_exists('advagg') || !advagg_enabled()) { return; } - $counter_filename = $dir . '/' . ADVAGG_SPACE . 'advagg_global_counter'; - $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); + // 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); } - else { - $shared_counter = (int) file_get_contents($counter_filename); - if ($shared_counter == $local_counter) { - // Counters are the same, return. - return; - } - elseif ($shared_counter < $local_counter) { - // Local counter is higher, update saved file and return. + // 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)) { + $counter_filename = $dir . '/' . ADVAGG_SPACE . 'advagg_global_counter'; + $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, TRUE); - return; + advagg_save_data($counter_filename, $local_counter); } - elseif ($shared_counter > $local_counter) { - // Shared counter is higher, update local copy and return. - variable_set('advagg_global_counter', $shared_counter); - return; + else { + $shared_counter = (int) advagg_file_get_contents($counter_filename); + + if ($shared_counter == $local_counter) { + // Counters are the same, return. + } + 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); + } + elseif ($shared_counter > $local_counter) { + // Shared counter is higher, update local copy and return. + variable_set('advagg_global_counter', $shared_counter); + } + } + } + + // Disable js in footer on imce page. + // Disable js defer on imce page. + // https://www.drupal.org/node/2817523 + if (module_exists('imce')) { + $args = arg(); + if ($args[0] === 'imce' && empty($args[1])) { + if (variable_get('advagg_mod_js_footer', ADVAGG_MOD_JS_FOOTER)) { + $GLOBALS['conf']['advagg_mod_js_footer'] = 0; + } + if (variable_get('advagg_mod_js_defer', ADVAGG_MOD_JS_DEFER)) { + $GLOBALS['conf']['advagg_mod_js_defer'] = 0; + } } } } @@ -192,302 +372,96 @@ function advagg_mod_menu() { } /** - * Implements hook_js_alter(). + * Implements hook_element_info_alter(). */ -function advagg_mod_js_alter(&$js) { - if (module_exists('advagg') && !advagg_enabled()) { - return; +function advagg_mod_element_info_alter(&$type) { + if (!isset($type['styles']['#pre_render'])) { + $type['styles']['#pre_render'] = array(); } - - // Change google analytics inline loader to be inside of an aggregrated file. - if (variable_get('advagg_mod_ga_inline_to_file', ADVAGG_MOD_GA_INLINE_TO_FILE)) { - advagg_mod_ga_inline_to_file($js); + $key_drupal = array_search('drupal_pre_render_styles', $type['styles']['#pre_render']); + $key_advagg = array_search('advagg_pre_render_styles', $type['styles']['#pre_render']); + if ($key_drupal !== FALSE) { + $type['styles']['#pre_render'] = advagg_insert_into_array_at_location($type['styles']['#pre_render'], array('_advagg_mod_pre_render_styles'), $key_drupal); } - - // Only add JS if it's actually needed. - if (variable_get('advagg_mod_js_remove_unused', ADVAGG_MOD_JS_REMOVE_UNUSED)) { - advagg_remove_js_if_not_used($js); + elseif ($key_advagg !== FALSE) { + $type['styles']['#pre_render'] = advagg_insert_into_array_at_location($type['styles']['#pre_render'], array('_advagg_mod_pre_render_styles'), $key_advagg); } - - // Change sort order so aggregates do not get split up. - // @ignore sniffer_whitespace_openbracketspacing_openingwhitespace - if ( variable_get('advagg_mod_js_adjust_sort_external', ADVAGG_MOD_JS_ADJUST_SORT_EXTERNAL) - || variable_get('advagg_mod_js_adjust_sort_inline', ADVAGG_MOD_JS_ADJUST_SORT_INLINE) - || variable_get('advagg_mod_js_adjust_sort_browsers', ADVAGG_MOD_JS_ADJUST_SORT_BROWSERS) - ) { - advagg_mod_sort_css_js($js, 'js'); + else { + $type['styles']['#pre_render'][] = '_advagg_mod_pre_render_styles'; } - // Move all JS to the footer. - $move_js_to_footer = variable_get('advagg_mod_js_footer', ADVAGG_MOD_JS_FOOTER); - if (!empty($move_js_to_footer)) { - foreach ($js as $name => &$values) { - if ($move_js_to_footer == 1 && $values['group'] <= JS_LIBRARY) { - continue; - } - - if (!empty($values['scope_lock'])) { - continue; - } - - // Do not move modernizr js to the footer. - if ( $values['type'] !== 'inline' - && $values['type'] !== 'setting' - && stripos($values['data'], '/modernizr.') !== FALSE - ) { - continue; - } - - // Do not move html5shiv or html5shiv-printshiv js to the footer. - if ( $values['type'] !== 'inline' - && $values['type'] !== 'setting' - && ( stripos($values['data'], '/html5shiv.') !== FALSE - || stripos($values['data'], '/html5shiv-printshiv.') !== FALSE - ) - ) { - continue; - } - - // If JS is not in the header increase group by 10000. - if ($values['scope'] !== 'header') { - $values['group'] += 10000; - } - // If JS is already in the footer increase group by 10000. - if ($values['scope'] === 'footer') { - $values['group'] += 10000; - } - $values['scope'] = 'footer'; - } - unset($values); + if (!isset($type['scripts']['#pre_render'])) { + $type['scripts']['#pre_render'] = array(); } - - // Do not use preprocessing if JS is inlined. - // Do not use defer if JS is inlined. - if (advagg_mod_inline_page()) { - advagg_mod_inline_js($js); - return; + $key_drupal = array_search('drupal_pre_render_scripts', $type['scripts']['#pre_render']); + $key_advagg = array_search('advagg_pre_render_scripts', $type['scripts']['#pre_render']); + $key_omega = array_search('omega_pre_render_scripts', $type['scripts']['#pre_render']); + $key_aurora = array_search('aurora_pre_render_scripts', $type['scripts']['#pre_render']); + if ($key_drupal !== FALSE) { + $type['scripts']['#pre_render'] = advagg_insert_into_array_at_location($type['scripts']['#pre_render'], array('_advagg_mod_pre_render_scripts'), $key_drupal); } - - // Force all JS to be preprocessed. - if (variable_get('advagg_mod_js_preprocess', ADVAGG_MOD_JS_PREPROCESS)) { - foreach ($js as $name => &$values) { - $values['preprocess'] = TRUE; - $values['cache'] = TRUE; - } - unset($values); + elseif ($key_advagg !== FALSE) { + $type['scripts']['#pre_render'] = advagg_insert_into_array_at_location($type['scripts']['#pre_render'], array('_advagg_mod_pre_render_scripts'), $key_advagg); } + elseif ($key_omega !== FALSE) { + $type['scripts']['#pre_render'] = advagg_insert_into_array_at_location($type['scripts']['#pre_render'], array('_advagg_mod_pre_render_scripts'), $key_omega); + } + elseif ($key_aurora !== FALSE) { + $type['scripts']['#pre_render'] = advagg_insert_into_array_at_location($type['scripts']['#pre_render'], array('_advagg_mod_pre_render_scripts'), $key_aurora); + } + else { + $type['scripts']['#pre_render'][] = '_advagg_mod_pre_render_scripts'; + } +} - // Add the defer or the async tag to all JS. - // @ignore sniffer_whitespace_openbracketspacing_openingwhitespace - if ( variable_get('advagg_mod_js_defer', ADVAGG_MOD_JS_DEFER) - || variable_get('advagg_mod_js_async', ADVAGG_MOD_JS_ASYNC) - ) { - - if (module_exists('openlayers')) { - $has_openlayers = FALSE; - foreach ($js as &$values) { - if ($values['type'] === 'inline' || !is_string($values['data'])) { - continue; - } - if (stripos($values['data'], 'openlayers')) { - $has_openlayers = TRUE; - break; - } - } - unset($values); - if ($has_openlayers) { - // Openlayers fix; external scripts can not be loaded out of order. - $openlayers = array(); - // Cloudmade. - $path = variable_get('openlayers_layers_cloudmade_js', ''); - if (valid_url($path, TRUE)) { - $openlayers['openlayers_layers_cloudmade'] = $path; - } - // Google. - $mapdomain = variable_get('openlayers_layers_google_mapdomain', 'maps.google.com'); - $openlayers['openlayers_layers_google'] = $mapdomain . '/maps'; - // VirtualEarth. - $openlayers['openlayers_layers_virtualearth'] = 'dev.virtualearth.net/mapcontrol'; - // Yahoo. - $openlayers['openlayers_layers_yahoo'] = 'api.maps.yahoo.com/ajaxymap'; - } - } - - $use_on_error = FALSE; - // If everything is async safe then we can use on error. - // Only needed if the jquery_update javascript is loaded via async/defer. - if ($use_on_error) { - $jquery_update_fallback = ''; - $jquery_update_ui_fallback = ''; - $inline_array = array(); - if (module_exists('jquery_update') && variable_get('jquery_update_jquery_cdn', 'none') !== 'none') { - foreach ($js as $name => &$values) { - if ($values['type'] !== 'inline') { - continue; - } - if (strpos($values['data'], 'window.jQuery') !== FALSE) { - $path = drupal_get_path('module', 'jquery_update'); - $version = variable_get('jquery_update_jquery_version', '1.10'); - $min = variable_get('jquery_update_compression_type', 'min') == 'none' ? '' : '.min'; - $jquery_update_fallback = base_path() . $path . '/replace/jquery/' . $version . '/jquery' . $min . '.js'; - $inline_array = $values; - unset($js[$name]); - continue; - } - if (strpos($values['data'], 'window.jQuery.ui') !== FALSE) { - $path = drupal_get_path('module', 'jquery_update'); - $min = variable_get('jquery_update_compression_type', 'min') == 'none' ? '' : '.min'; - $js_path = ($min == '.min') ? '/replace/ui/ui/minified/jquery-ui.min.js' : '/replace/ui/ui/jquery-ui.js'; - $jquery_update_ui_fallback = base_path() . $path . $js_path; - if (empty($inline_array)) { - $inline_array = $values; - } - unset($js[$name]); - continue; - } - } - unset($values); - } - if (!empty($jquery_update_fallback) || !empty($jquery_update_ui_fallback)) { - $inline_array['group'] = '-150'; - $inline_array['weight'] += -10; - $inline_array['data'] = 'function advagg_fallback(file){var head = document.getElementsByTagName("head")[0];var script = document.createElement("script");script.src = file;script.type = "text/javascript";head.appendChild(script);};'; - $js[] = $inline_array; - } - } +/** + * Implements hook_css_alter(). + */ +function advagg_mod_css_alter(&$css) { + if (!module_exists('advagg') || !advagg_enabled()) { + return; + } - foreach ($js as $name => &$values) { - if ($values['type'] !== 'file' && $values['type'] !== 'external') { + // Force all CSS to be preprocessed. + if (variable_get('advagg_mod_css_preprocess', ADVAGG_MOD_CSS_PREPROCESS)) { + foreach ($css as &$values) { + if (!empty($values['preprocess_lock'])) { continue; } - if (variable_get('advagg_mod_js_defer', ADVAGG_MOD_JS_DEFER)) { - // Everything is defer. - $values['defer'] = TRUE; - } - if (variable_get('advagg_mod_js_async', ADVAGG_MOD_JS_ASYNC)) { - // Everything is async. - $values['async'] = TRUE; - } - - if (strpos($name, 'jquery.js') !== FALSE || strpos($name, 'jquery.min.js') !== FALSE) { - // jquery_update fallback. - if (module_exists('jquery_update') && variable_get('jquery_update_jquery_cdn', 'none') !== 'none') { - if ($use_on_error) { - if (!isset($values['onerror'])) { - $values['onerror'] = ''; - } - $values['onerror'] .= 'advagg_fallback(\'' . $jquery_update_fallback . '\');'; - } - // Do not defer/async the loading of jquery.js - if (variable_get('advagg_mod_js_defer', ADVAGG_MOD_JS_DEFER)) { - $values['defer'] = FALSE; - } - if (variable_get('advagg_mod_js_async', ADVAGG_MOD_JS_ASYNC)) { - $values['async'] = FALSE; - } - } - } - if (strpos($name, 'jquery-ui.js') !== FALSE || strpos($name, 'jquery-ui.min.js') !== FALSE) { - // jquery_update ui fallback. - if (module_exists('jquery_update') && variable_get('jquery_update_jquery_cdn', 'none') !== 'none') { - if ($use_on_error) { - if (!isset($values['onerror'])) { - $values['onerror'] = ''; - } - $values['onerror'] .= 'advagg_fallback(' . $jquery_update_ui_fallback . ');'; - } - // Do not defer/async the loading of jquery-ui.js - if (variable_get('advagg_mod_js_defer', ADVAGG_MOD_JS_DEFER)) { - $values['defer'] = FALSE; - } - if (variable_get('advagg_mod_js_async', ADVAGG_MOD_JS_ASYNC)) { - $values['async'] = FALSE; - } - } - } - - // Drupal settings. - if ($name === 'misc/drupal.js') { - // Initialize the Drupal.settings JavaScript object after this has - // loaded. - if (!isset($values['onload'])) { - $values['onload'] = ''; - } - $matches[0] = $matches[2] = 'init_drupal_core_settings();'; - $values['onload'] .= advagg_mod_wrap_inline_js($matches, "window.init_drupal_core_settings && window.init_drupal_core_settings && window.jQuery && window.Drupal", 20); - } - - // Openlayers. - if (!empty($openlayers)) { - foreach ($openlayers as $search_string) { - if (strpos($name, $search_string) !== FALSE) { - // Do not defer/async the loading of OpenLayers.js - if (variable_get('advagg_mod_js_defer', ADVAGG_MOD_JS_DEFER)) { - $values['defer'] = FALSE; - } - if (variable_get('advagg_mod_js_async', ADVAGG_MOD_JS_ASYNC)) { - $values['async'] = FALSE; - } - } - } - } - - // Wistia. - if (strpos($name, '//fast.wistia.') !== FALSE) { - // Do not defer/async the loading of any wistia js. - if (variable_get('advagg_mod_js_defer', ADVAGG_MOD_JS_DEFER)) { - $values['defer'] = FALSE; - } - if (variable_get('advagg_mod_js_async', ADVAGG_MOD_JS_ASYNC)) { - $values['async'] = FALSE; - } - } - + $values['preprocess'] = TRUE; } unset($values); } } /** - * Implements hook_css_alter(). + * Implements hook_css_post_alter(). */ -function advagg_mod_css_alter(&$css) { - if (module_exists('advagg') && !advagg_enabled()) { +function advagg_mod_css_post_alter(&$css) { + if (!module_exists('advagg') || !advagg_enabled()) { return; } - // @ignore sniffer_whitespace_openbracketspacing_openingwhitespace:5 // Change sort order so aggregates do not get split up. - if ( variable_get('advagg_mod_css_adjust_sort_external', ADVAGG_MOD_CSS_ADJUST_SORT_EXTERNAL) + if (variable_get('advagg_mod_css_adjust_sort_external', ADVAGG_MOD_CSS_ADJUST_SORT_EXTERNAL) || variable_get('advagg_mod_css_adjust_sort_inline', ADVAGG_MOD_CSS_ADJUST_SORT_INLINE) || variable_get('advagg_mod_css_adjust_sort_browsers', ADVAGG_MOD_CSS_ADJUST_SORT_BROWSERS) ) { advagg_mod_sort_css_js($css, 'css'); } - - // Do not use preprocessing if CSS is inlined. - if (advagg_mod_inline_page()) { - advagg_mod_inline_css($css); - return; - } - - // Force all CSS to be preprocessed. - if (variable_get('advagg_mod_css_preprocess', ADVAGG_MOD_CSS_PREPROCESS)) { - foreach ($css as &$values) { - $values['preprocess'] = TRUE; - } - unset($values); - } } /** * Implements hook_html_head_alter(). */ function advagg_mod_html_head_alter(&$head_elements) { + if (!module_exists('advagg') || !advagg_enabled()) { + return; + } + foreach ($head_elements as $key => $element) { - // CSS - // @ignore sniffer_whitespace_openbracketspacing_openingwhitespace - if ( variable_get('advagg_mod_css_head_extract', ADVAGG_MOD_CSS_HEAD_EXTRACT) + // CSS. + if (variable_get('advagg_mod_css_head_extract', ADVAGG_MOD_CSS_HEAD_EXTRACT) && !empty($element['#tag']) && $element['#tag'] === 'link' && !empty($element['#attributes']['type']) @@ -495,8 +469,7 @@ function advagg_mod_html_head_alter(&$head_elements) { && !empty($element['#attributes']['href']) ) { $type = 'file'; - // @ignore sniffer_whitespace_openbracketspacing_openingwhitespace - if ( strpos($element['#attributes']['href'], 'http://') === 0 + if (strpos($element['#attributes']['href'], 'http://') === 0 || strpos($element['#attributes']['href'], 'https://') === 0 || strpos($element['#attributes']['href'], '//') === 0 ) { @@ -510,9 +483,8 @@ function advagg_mod_html_head_alter(&$head_elements) { )); unset($head_elements[$key]); } - // JS - // @ignore sniffer_whitespace_openbracketspacing_openingwhitespace - if ( variable_get('advagg_mod_js_head_extract', ADVAGG_MOD_JS_HEAD_EXTRACT) + // JS. + if (variable_get('advagg_mod_js_head_extract', ADVAGG_MOD_JS_HEAD_EXTRACT) && !empty($element['#tag']) && $element['#tag'] === 'script' && !empty($element['#attributes']['type']) @@ -520,8 +492,7 @@ function advagg_mod_html_head_alter(&$head_elements) { && !empty($element['#attributes']['src']) ) { $type = 'file'; - // @ignore sniffer_whitespace_openbracketspacing_openingwhitespace - if ( strpos($element['#attributes']['src'], 'http://') === 0 + if (strpos($element['#attributes']['src'], 'http://') === 0 || strpos($element['#attributes']['src'], 'https://') === 0 || strpos($element['#attributes']['src'], '//') === 0 ) { @@ -545,6 +516,10 @@ function advagg_mod_html_head_alter(&$head_elements) { * Insert advagg_mod_process_move_js before _advagg_process_html. */ function advagg_mod_theme_registry_alter(&$theme_registry) { + if (!module_exists('advagg') || !advagg_enabled()) { + return; + } + if (!isset($theme_registry['html'])) { return; } @@ -568,114 +543,220 @@ function advagg_mod_theme_registry_alter(&$theme_registry) { * Used to wrap inline JS in a function in order to prevent js errors when JS is * moved to the footer. */ -function advagg_mod_process_move_js(&$variables) { - // Only run if. - // $variables['page'] is not empty. - // Setting is enabled. - if (empty($variables['page']) || !variable_get('advagg_mod_js_footer_inline_alter', ADVAGG_MOD_JS_FOOTER_INLINE_ALTER)) { +function advagg_mod_process_move_js(array &$variables) { + if (!module_exists('advagg') || !advagg_enabled()) { return; } - // @ignore sniffer_whitespace_openbracketspacing_openingwhitespace - if ( variable_get('advagg_mod_js_footer', ADVAGG_MOD_JS_FOOTER) != 2 - && !variable_get('advagg_mod_js_defer', ADVAGG_MOD_JS_DEFER) - && !variable_get('advagg_mod_js_async', ADVAGG_MOD_JS_ASYNC) + // Return if settings are disabled. + if (!variable_get('advagg_mod_js_footer_inline_alter', ADVAGG_MOD_JS_FOOTER_INLINE_ALTER) + && !variable_get('advagg_mod_js_inline_resource_hints', ADVAGG_MOD_JS_INLINE_RESOURCE_HINTS) ) { return; } - $pattern = '/<script((?:(?!src=).)*?)>(.*?)<\/script>/smix'; - $callback = 'advagg_mod_wrap_inline_js'; - // Wrap inline JS with a check so that it only runs once Drupal.settings & - // jQuery are not undefined. - // @ignore sniffer_whitespace_openbracketspacing_openingwhitespace - if ( !empty($variables['page']['#children']) - && is_string($variables['page']['#children']) - && stripos($variables['page']['#children'], '<script') !== FALSE - ) { - if (variable_get('advagg_mod_wrap_inline_js_xpath', ADVAGG_MOD_WRAP_INLINE_JS_XPATH)) { - $variables['page']['#children'] = advagg_mod_xpath_script_wrapper($variables['page']['#children']); - } - else { - $variables['page']['#children'] = preg_replace_callback($pattern, $callback, $variables['page']['#children']); + // Search all the children for script tags. + foreach (element_children($variables) as $child) { + // Skip if empty. + if (empty($variables[$child])) { + continue; } - return; - } - // @ignore sniffer_whitespace_openbracketspacing_openingwhitespace - if ( is_string($variables['page']) - && stripos($variables['page'], '<script') !== FALSE - ) { - if (variable_get('advagg_mod_wrap_inline_js_xpath', ADVAGG_MOD_WRAP_INLINE_JS_XPATH)) { - $variables['page'] = advagg_mod_xpath_script_wrapper($variables['page']); + + // Handle strings. + if (is_string($variables[$child]) + && stripos($variables[$child], '<script') !== FALSE + ) { + advagg_mod_js_inline_processor($variables[$child]); } - else { - $variables['page'] = preg_replace_callback($pattern, $callback, $variables['page']); + if (is_array($variables[$child])) { + if (isset($variables[$child]['#children']) + && is_string($variables[$child]['#children']) + && stripos($variables[$child]['#children'], '<script') !== FALSE + ) { + advagg_mod_js_inline_processor($variables[$child]['#children']); + } + if (isset($variables[$child]['#markup']) + && is_string($variables[$child]['#markup']) + && stripos($variables[$child]['#markup'], '<script') !== FALSE + ) { + // advagg_mod_js_inline_processor($variables[$child]['#markup']); + // Uncomment to also process #markup. + } + // advagg_mod_process_move_js($variables[$child]); + // Uncomment to make this recursive. } - return; } } /** - * Use DOMDocument's loadHTML along with DOMXPath's query to find script tags. - * - * Once found, it will also wrap them in a javascript loader function. - * - * @param string $html - * HTML fragments. - * - * @return string - * The HTML fragment with less markup errors and script tags wrapped. + * Implements hook_page_alter(). */ -function advagg_mod_xpath_script_wrapper($html) { - // Do not throw errors when parsing the html. - libxml_use_internal_errors(TRUE); +function advagg_mod_page_alter() { + // Skip if advagg is disabled. + if (!advagg_enabled()) { + return; + } + // Return early if this setting is disabled. + list(, , , , , , , , , , $css_defer) = advagg_mod_get_lists(); + if (empty($css_defer)) { + return; + } + if (variable_get('advagg_mod_css_defer_visibility', ADVAGG_MOD_VISIBILITY_LISTED) != ADVAGG_MOD_VISIBILITY_FILE_CONTROLLED) { + return; + } - $dom = new DOMDocument(); - // Load html with full tags all around. - $dom->loadHTML('<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd"> - <html><head><meta http-equiv="content-type" content="text/html; charset=utf-8"></head><body>' . $html . '</body></html>'); - $xpath = new DOMXPath($dom); - // Get all script tags that are not inside of a textarea and do not contain a - // src attribute. - $nodes = $xpath->query('//script[not(@src)][not(ancestor::textarea)]'); + // Get critical css file. + list(, , $inline_strings) = advagg_mod_find_critical_css_file(); + + $preload_from_inline_css = array(); + $domains_from_inline_css = array(); + // Add inline critical css for front page. + if (!empty($inline_strings[0])) { + // Extract url() references to add to the preloaded links. + $matches = array(); + // Match url ( "' ... '" ). + $pattern = '/url\s*\(\s*[\'"]?(.+?)[\'"]?\s*\)/i'; + preg_match_all($pattern, $inline_strings[0], $matches); + if (!empty($matches[1])) { + foreach ($matches[1] as $key => $url) { + $parsed = parse_url($url); + // Remove data URIs. + if (!empty($parsed['scheme']) && $parsed['scheme'] === 'data') { + unset($matches[1][$key]); + continue; + } + // Remote paths without a period. + if (empty($parsed['path']) || strpos($parsed['path'], '.') === FALSE) { + unset($matches[1][$key]); + continue; + } - foreach ($nodes as $node) { - $matches[2] = $node->nodeValue; - // $matches[0] = $dom->saveHTML($node); - $matches[0] = $node->nodeValue; - $new_html = advagg_mod_wrap_inline_js($matches); - $advagg = $dom->createElement('script'); - $advagg->appendchild($dom->createTextNode($new_html)); - $node->parentNode->replaceChild($advagg, $node); + if (isset($parsed['host'])) { + $domains_from_inline_css[] = $url; + } + } + $preload_from_inline_css = $matches[1]; + } + + // Add critical css. + drupal_add_css('advagg_mod_critical_css', array( + 'data' => $inline_strings[0], + 'type' => 'inline', + 'group' => CSS_SYSTEM - 1, + 'weight' => -50000, + 'movable' => FALSE, + 'critical-css' => TRUE, + )); + // Add critical css js loader. + advagg_mod_add_loadcss_js_lib(); + } + + // Add in domain prefetch. + $domains = array(); + if (!empty($inline_strings[1])) { + $domains = preg_split("/\\r\\n|\\r|\\n/", $inline_strings[1]); + } + $domains = array_merge($domains, $domains_from_inline_css); + // Remove duplicates and empty sets. + $domains = array_filter(array_unique($domains)); + if (!empty($domains)) { + foreach ($domains as $domain) { + advagg_add_dns_prefetch(trim($domain)); + } } - // Render to HTML. - $output = $dom->saveHTML(); - // Remove the tags we added. - $output = str_replace(array('<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd"> - <html><head><meta http-equiv="content-type" content="text/html; charset=utf-8"></head><body>', '</body></html>'), array('', ''), $output); + // Add in files to preload. + $preload = array(); + if (!empty($inline_strings[2])) { + $preload = preg_split("/\\r\\n|\\r|\\n/", $inline_strings[2]); + } + $preload = array_merge($preload, $preload_from_inline_css); + // Remove duplicates and empty sets. + $preload = array_filter(array_unique($preload)); + if (!empty($preload)) { + $preload_array = array(); + $counter = 0; + foreach ($preload as $value) { + if (empty($value)) { + $counter++; + continue; + } + if (stripos($value, 'as: ') === 0) { + $preload_array[$counter]['as'] = trim(substr($value, 4)); + } + elseif (stripos($value, 'type: ') === 0) { + $preload_array[$counter]['type'] = trim(substr($value, 6)); + } + elseif (stripos($value, 'media: ') === 0) { + $preload_array[$counter]['media'] = trim(substr($value, 7)); + } + elseif (stripos($value, 'crossorigin: ') === 0) { + $preload_array[$counter]['crossorigin'] = trim(substr($value, 13)); + } + elseif (stripos($value, 'url: ') === 0) { + if (!empty($preload_array[$counter]['url'])) { + $counter++; + } + $preload_array[$counter]['url'] = trim(substr($value, 4)); + } + else { + if (!empty($preload_array[$counter]['url'])) { + $counter++; + } + $preload_array[$counter]['url'] = trim($value); + } + } + foreach ($preload_array as $values) { + // Skip if url is not set. + if (empty($values['url'])) { + continue; + } - // Clear any errors. - libxml_clear_errors(); - return $output; + $url = $values['url']; + $media = ''; + if (!empty($values['media'])) { + $media = $values['media']; + } + $as = ''; + if (!empty($values['as'])) { + $as = $values['as']; + } + $type = ''; + if (!empty($values['type'])) { + $type = $values['type']; + } + $crossorigin = NULL; + if (!empty($values['crossorigin'])) { + $crossorigin = $values['crossorigin']; + } + advagg_add_preload_link($url, $media, $as, $type, $crossorigin); + } + } } -// AdvAgg hook implementations. +/** + * @} End of "addtogroup hooks". + */ + +/** + * @addtogroup advagg_hooks + * @{ + */ + /** * Implements hook_advagg_modify_js_pre_render_alter(). */ function advagg_mod_advagg_modify_js_pre_render_alter(&$children, &$elements) { - if (module_exists('advagg') && !advagg_enabled()) { + if (!module_exists('advagg') || !advagg_enabled()) { return; } // Do not use defer/async shim if JS is inlined. - if (advagg_mod_inline_page()) { + if (advagg_mod_inline_page() || advagg_mod_inline_page_js()) { return; } - // @ignore sniffer_whitespace_openbracketspacing_openingwhitespace - if ( variable_get('advagg_mod_js_defer', ADVAGG_MOD_JS_DEFER) + if (variable_get('advagg_mod_js_defer', ADVAGG_MOD_JS_DEFER) || variable_get('advagg_mod_js_async', ADVAGG_MOD_JS_ASYNC) ) { // Capture all onload code. @@ -685,8 +766,89 @@ 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'); + + $ie_fixes = array(); + foreach ($elements['#groups'] as $group) { + if ($group['type'] !== 'file' + || empty($group['defer']) + || empty($group['items']['files']) + ) { + continue; + } - foreach ($children as &$values) { + $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)); + } + } + } + + // Count the number of holdReady's there are. + $holdready_count = array(); + foreach ($children as $key => &$values) { + if (!empty($values['#attributes']['onload']) + && (stripos($values['#attributes']['onload'], 'jQuery.holdReady(true)') !== FALSE + || stripos($values['#attributes']['onload'], 'jQuery.holdReady(!0)') !== FALSE + ) + ) { + // Normalize the src attribute. + $src = $values['#attributes']['src']; + $pos = strpos($values['#attributes']['src'], '#'); + if ($pos !== FALSE) { + $src = substr($values['#attributes']['src'], 0, $pos); + } + $holdready_count[$src] = TRUE; + break; + } + } + + 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. @@ -700,7 +862,11 @@ function advagg_mod_advagg_modify_js_pre_render_alter(&$children, &$elements) { } } if ($found) { - $values['#value'] = 'function init_drupal_core_settings() {' . $values['#value'] . '}'; + $holdready_string = ''; + if (!empty($holdready_count)) { + $holdready_string = "\nif(jQuery.isFunction(jQuery.holdReady)){jQuery.holdReady(false);}"; + } + $values['#value'] = "function init_drupal_core_settings() {{$values['#value']} {$holdready_string}} if(window.jQuery && window.Drupal){init_drupal_core_settings();}"; } } } @@ -711,8 +877,7 @@ function advagg_mod_advagg_modify_js_pre_render_alter(&$children, &$elements) { foreach ($children as &$values) { if (isset($values['#attributes']) && isset($values['#attributes']['async']) && $values['#attributes']['async'] === 'async' && !empty($values['#attributes']['src'])) { $source = $values['#attributes']['src']; - // @ignore sniffer_whitespace_openbracketspacing_openingwhitespace - if ( strpos($source, 'http://') !== 0 + if (strpos($source, 'http://') !== 0 && strpos($source, 'https://') !== 0 && strpos($source, '//') !== 0 ) { @@ -738,48 +903,57 @@ function advagg_mod_advagg_modify_js_pre_render_alter(&$children, &$elements) { * Implements hook_advagg_modify_css_pre_render_alter(). */ function advagg_mod_advagg_modify_css_pre_render_alter(&$children, &$elements) { - if (module_exists('advagg') && !advagg_enabled()) { + // Skip if there is no css. + if (empty($children)) { + return; + } + + if (!module_exists('advagg') || !advagg_enabled()) { return; } // Return early if this setting is disabled. - $css_defer = variable_get('advagg_mod_css_defer', ADVAGG_MOD_CSS_DEFER); + list(, , , , , , , , , , $css_defer) = advagg_mod_get_lists(array(), $elements['#items']); if (empty($css_defer)) { return; } - $css_defer_js_code = variable_get('advagg_mod_css_defer_js_code', ADVAGG_MOD_CSS_DEFER_JS_CODE); - // Make advagg_mod_loadStyleSheet() available. - $type = 'external'; - $data = '//rawgit.com/filamentgroup/loadCSS/master/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"; - } - 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<g.length;d++)g[d].href&&g[d].href.indexOf(a)>-1&&(c=!0);c?b():setTimeout(function(){e.onloadcssdefined(b)})},e.onloadcssdefined(function(){e.media=c||"all"}),e}'; + $critical_css = FALSE; + // Only check for the critical-css key if configured to do so. + if (variable_get('advagg_mod_css_defer_visibility', ADVAGG_MOD_VISIBILITY_LISTED) == ADVAGG_MOD_VISIBILITY_FILE_CONTROLLED) { + foreach ($elements['#items'] as $item) { + if ($item['type'] === 'inline' && !empty($item['critical-css'])) { + $critical_css = TRUE; + break; + } + } + // Return early if there's no critical css for the path and deferring is + // file controlled. + if (!$critical_css) { + return; + } } - $options = array( - 'type' => $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; + + if (!$critical_css) { + // 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 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; + } } - drupal_add_js($data, $options); + + // Modify css. + static $added; + advagg_mod_add_loadcss_js_lib(array(), $elements['#items']); // Wrap CSS in noscript tags. + $defer_skip_first_file = variable_get('advagg_mod_css_defer_skip_first_file', ADVAGG_MOD_CSS_DEFER_SKIP_FIRST_FILE); $options = array( 'type' => 'inline', 'scope' => $css_defer >= 5 ? 'footer' : 'header', @@ -788,100 +962,1944 @@ function loadCSS(a,b,c,d){"use strict";var e=window.document.createElement("link 'weight' => $css_defer == 1 ? -50000 : 0, 'movable' => $css_defer == 1 ? FALSE : TRUE, ); - foreach ($children as &$values) { - // Do not defer inline scripts. - if ($values['#tag'] === 'style') { + + // Get the key of the last css file that will use loadcss. + $last_key = NULL; + foreach ($children as $children_key => $values) { + // Do not count inline styles. + if ($values['#tag'] === 'style' || empty($values['#attributes']['href']) + ) { + continue; + } + // Only use if no browser conditionals. + if (isset($values['#browsers']['!IE']) + && $values['#browsers']['!IE'] === TRUE + && isset($values['#browsers']['IE']) + && $values['#browsers']['IE'] === TRUE + ) { + $last_key = $children_key; + } + } + // If all css uses a browser conditional, then use the last one to release. + if ($last_key === NULL) { + $last_key = $children_key; + } + + $preload_array = array(); + foreach ($children as $children_key => &$values) { + // Do not defer inline styles. + if ($values['#tag'] === 'style' || empty($values['#attributes']['href'])) { continue; } - if (!empty($values['#attributes']['href'])) { - // Wrap current css in noscript tags. - $values['#prefix'] = '<noscript>' . "\n"; - $values['#suffix'] = '</noscript>'; + if (empty($preload_array) && $defer_skip_first_file == 2) { + $preload_array[] = array(); + continue; + } + + // If this is the last CSS file release the hold on jquery.ready. + $onload_extra = ''; + if ($last_key === $children_key) { + // Run holdready once it is defined. + $holdready_script = 'window.advagg_mod_loadcss = function() {if (window.jQuery) {if (jQuery.isFunction(jQuery.holdReady)){jQuery.holdReady(false);}} else {setTimeout(advagg_mod_loadcss, 100);}};'; + $onload_extra = "{$holdready_script}setTimeout(advagg_mod_loadcss, 200);"; + $GLOBALS['advagg_mod_loadcss_jquery_holdready'] = TRUE; + } + $id = "advagg_loadcss_$children_key"; + if ($css_defer == 4) { + $copy = $values; + $copy['#attributes']['rel'] = 'preload'; + $copy['#attributes']['as'] = 'style'; + $copy['#attributes']['onload'] = "{$onload_extra}this.onload=null;this.rel='stylesheet'"; + $preload_array[$children_key] = $copy; + } + else { // Add browsers to the js options. if (isset($values['#browsers'])) { $options['browsers'] = $values['#browsers']; } - $inline = 'loadCSS("' . $values['#attributes']['href'] . '")'; - // Make async work if it's being used. - if ($type !== 'inline') { - $matches[2] = $matches[0] = $inline; - $inline = advagg_mod_wrap_inline_js($matches, "window.loadCSS", 40); + + // Create loadCSS wrapper code. + $inline = "loadCSS(\"{$values['#attributes']['href']}\", document.getElementById(\"$id\")"; + if ($values['#attributes']['media'] !== 'all') { + $inline .= ", \"{$values['#attributes']['media']}\""; + } + if (!empty($values['#attributes']['crossorigin'])) { + $inline .= ", \"{$values['#attributes']['crossorigin']}\""; } + $inline .= ')'; + + // Create onloadCSS wrapper code. + if (!empty($values['#attributes']['onloadCSS'])) { + $inline = "onloadCSS({$inline}, function() {{$onload_extra}{$values['#attributes']['onloadCSS']}});"; + } + elseif (!empty($onload_extra)) { + $inline = "onloadCSS({$inline}, function() {{$onload_extra}});"; + } + + // Make code work if loader code is below loadcss calls. + $matches[2] = $matches[0] = $inline; + $inline = advagg_mod_wrap_inline_js($matches, "window.loadCSS", 40); + // Add in script tags to load css via js. - drupal_add_js($inline, $options); - // Reset for next item in loop. - if (isset($options['browsers'])) { - unset($options['browsers']); + if (!isset($added[$values['#attributes']['href']])) { + drupal_add_js($inline, $options); + $added[$values['#attributes']['href']] = TRUE; + } + } + // Wrap current css in noscript tags. + $values['#prefix'] = "<noscript id=\"$id\">\n"; + $values['#suffix'] = '</noscript>'; + // Reset for next item in loop. + if (isset($options['browsers'])) { + unset($options['browsers']); + } + } + unset($values); + + // Add in the element after the noscript element keeping the css order. + $new_elements = array(); + foreach ($elements as $elements_key => $elements_value) { + // Build old array. + if (is_numeric($elements_key)) { + $new_elements[] = $elements_value; + } + else { + $new_elements[$elements_key] = $elements_value; + } + // Splice in new data. + if (isset($preload_array[$elements_key])) { + $new_elements[] = $preload_array[$elements_key]; + } + } + $elements = $new_elements; + unset($new_elements); +} + +/** + * Implements hook_advagg_hooks_implemented_alter(). + */ +function advagg_mod_advagg_hooks_implemented_alter(&$hooks, $all) { + if ($all) { + $hooks += array( + 'advagg_mod_get_lists_alter' => array(), + ); + } +} + +/** + * Implements hook_advagg_get_root_files_dir_alter(). + */ +function advagg_mod_advagg_get_root_files_dir_alter(&$css_paths, &$js_paths) { + $dir = rtrim(variable_get('advagg_mod_unified_multisite_dir', ''), '/'); + if (empty($dir) || !file_exists($dir) || !is_dir($dir)) { + return; + } + // Change directory. + $css_paths[0] = $dir . '/advagg_css'; + $js_paths[0] = $dir . '/advagg_js'; + + file_prepare_directory($css_paths[0], FILE_CREATE_DIRECTORY); + file_prepare_directory($js_paths[0], FILE_CREATE_DIRECTORY); + + // Set the URI of the directory. + $css_paths[1] = advagg_get_relative_path($css_paths[0]); + $js_paths[1] = advagg_get_relative_path($js_paths[0]); +} + +/** + * Implements hook_advagg_current_hooks_hash_array_alter(). + */ +function advagg_mod_advagg_current_hooks_hash_array_alter(&$aggregate_settings) { + // JS Settings. + $aggregate_settings['variables']['advagg_mod_js_async_shim'] = variable_get('advagg_mod_js_async_shim', ADVAGG_MOD_JS_ASYNC_SHIM); + + // Make safe if using the aggressive cache. + if (variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL) >= 5) { + $aggregate_settings['variables']['advagg_mod_js_preprocess'] = variable_get('advagg_mod_js_preprocess', ADVAGG_MOD_JS_PREPROCESS); + $aggregate_settings['variables']['advagg_mod_js_remove_unused'] = variable_get('advagg_mod_js_remove_unused', ADVAGG_MOD_JS_REMOVE_UNUSED); + $aggregate_settings['variables']['advagg_mod_js_head_extract'] = variable_get('advagg_mod_js_head_extract', ADVAGG_MOD_JS_HEAD_EXTRACT); + $aggregate_settings['variables']['advagg_mod_js_adjust_sort_external'] = variable_get('advagg_mod_js_adjust_sort_external', ADVAGG_MOD_JS_ADJUST_SORT_EXTERNAL); + $aggregate_settings['variables']['advagg_mod_js_adjust_sort_inline'] = variable_get('advagg_mod_js_adjust_sort_inline', ADVAGG_MOD_JS_ADJUST_SORT_INLINE); + $aggregate_settings['variables']['advagg_mod_js_adjust_sort_browsers'] = variable_get('advagg_mod_js_adjust_sort_browsers', ADVAGG_MOD_JS_ADJUST_SORT_BROWSERS); + $aggregate_settings['variables']['advagg_mod_ga_inline_to_file'] = variable_get('advagg_mod_ga_inline_to_file', ADVAGG_MOD_GA_INLINE_TO_FILE); + $aggregate_settings['variables']['advagg_mod_js_footer'] = variable_get('advagg_mod_js_footer', ADVAGG_MOD_JS_FOOTER); + $aggregate_settings['variables']['advagg_mod_js_defer'] = variable_get('advagg_mod_js_defer', ADVAGG_MOD_JS_DEFER); + $aggregate_settings['variables']['advagg_mod_js_footer_inline_alter'] = variable_get('advagg_mod_js_footer_inline_alter', ADVAGG_MOD_JS_FOOTER_INLINE_ALTER); + $aggregate_settings['variables']['advagg_mod_js_async'] = variable_get('advagg_mod_js_async', ADVAGG_MOD_JS_ASYNC); + } + + // CSS Settings. + $aggregate_settings['variables']['advagg_mod_css_translate'] = variable_get('advagg_mod_css_translate', ADVAGG_MOD_CSS_TRANSLATE); + if (variable_get('advagg_mod_css_translate', ADVAGG_MOD_CSS_TRANSLATE)) { + $aggregate_settings['variables']['advagg_mod_css_translate_lang'] = isset($GLOBALS['language']->language) ? $GLOBALS['language']->language : 'en'; + } + + $aggregate_settings['variables']['advagg_mod_css_adjust_sort_external'] = variable_get('advagg_mod_css_adjust_sort_external', ADVAGG_MOD_CSS_ADJUST_SORT_EXTERNAL); + $aggregate_settings['variables']['advagg_mod_css_adjust_sort_inline'] = variable_get('advagg_mod_css_adjust_sort_inline', ADVAGG_MOD_CSS_ADJUST_SORT_INLINE); + + // Make safe if using the aggressive cache. + if (variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL) >= 5) { + $aggregate_settings['variables']['advagg_mod_css_preprocess'] = variable_get('advagg_mod_css_preprocess', ADVAGG_MOD_CSS_PREPROCESS); + $aggregate_settings['variables']['advagg_mod_css_head_extract'] = variable_get('advagg_mod_css_head_extract', ADVAGG_MOD_CSS_HEAD_EXTRACT); + $aggregate_settings['variables']['advagg_mod_css_adjust_sort_browsers'] = variable_get('advagg_mod_css_adjust_sort_browsers', ADVAGG_MOD_CSS_ADJUST_SORT_BROWSERS); + $aggregate_settings['variables']['advagg_mod_css_defer'] = variable_get('advagg_mod_css_defer', ADVAGG_MOD_CSS_DEFER); + $aggregate_settings['variables']['advagg_mod_css_defer_js_code'] = variable_get('advagg_mod_css_defer_js_code', ADVAGG_MOD_CSS_DEFER_JS_CODE); + $aggregate_settings['variables']['advagg_mod_inline_visibility'] = variable_get('advagg_mod_inline_visibility', ADVAGG_MOD_VISIBILITY_LISTED); + $aggregate_settings['variables']['advagg_mod_inline_pages'] = variable_get('advagg_mod_inline_pages', ''); + $aggregate_settings['variables']['advagg_mod_css_defer_visibility'] = variable_get('advagg_mod_css_defer_visibility', ADVAGG_MOD_VISIBILITY_LISTED); + $aggregate_settings['variables']['advagg_mod_css_defer_pages'] = variable_get('advagg_mod_css_defer_pages', ''); + + // Run functions for page visibility. + $aggregate_settings['variables']['advagg_mod_inline_page'] = advagg_mod_inline_page(); + $aggregate_settings['variables']['advagg_mod_inline_page_js'] = advagg_mod_inline_page_js(); + $aggregate_settings['variables']['advagg_mod_inline_page_css'] = advagg_mod_inline_page_css(); + $aggregate_settings['variables']['advagg_mod_css_defer_page'] = advagg_mod_css_defer_page(); + } +} + +/** + * @} End of "addtogroup advagg_hooks". + */ + +/** + * @addtogroup 3rd_party_hooks + * @{ + */ + +/** + * Implements hook_libraries_info(). + */ +function advagg_mod_libraries_info() { + $libraries['loadCSS'] = array( + 'name' => 'loadCSS', + 'vendor url' => 'https://github.com/filamentgroup/loadCSS', + 'download url' => 'https://github.com/filamentgroup/loadCSS/archive/master.zip', + 'version arguments' => array( + 'file' => 'package.json', + 'pattern' => '/"version":\\s+"([0-9\.]+)"/', + 'lines' => 100, + ), + // Called before the library is loaded. + 'callbacks' => array( + 'pre-load' => array( + 'advagg_mod_libraries_preload_callback', + ), + ), + 'local version' => '2.0.1', + 'remote' => array( + 'callback' => 'advagg_get_github_version_json', + 'url' => 'https://cdn.jsdelivr.net/gh/filamentgroup/loadCSS@master/package.json', + ), + 'files' => array( + 'js' => array( + 'src/loadCSS.js' => array( + 'type' => 'file', + 'async' => TRUE, + ), + ), + ), + ); + // Get the latest tagged version for external file loading. + $version = advagg_get_remote_libraries_version('loadCSS', $libraries['loadCSS']); + // Get the advagg_mod path for local loading. + $advagg_mod_path = drupal_get_path('module', 'advagg_mod'); + $libraries['loadCSS'] += array( + 'variants' => array( + 'normal-preload' => array( + 'files' => array( + 'js' => array( + 'src/cssrelpreload.js' => array( + 'type' => 'file', + 'async' => TRUE, + ), + ), + ), + ), + 'normal-onload' => array( + 'files' => array( + 'js' => array( + 'src/loadCSS.js' => array( + 'type' => 'file', + 'async' => TRUE, + ), + 'src/onloadCSS.js' => array( + 'type' => 'file', + 'async' => TRUE, + ), + ), + ), + ), + 'minified' => array( + 'files' => array( + 'js' => array( + 'src/loadCSS.min.js' => array( + 'type' => 'file', + 'async' => TRUE, + ), + ), + ), + ), + 'minified-preload' => array( + 'files' => array( + 'js' => array( + 'src/cssrelpreload.min.js' => array( + 'type' => 'file', + 'async' => TRUE, + ), + ), + ), + ), + 'minified-onload' => array( + 'files' => array( + 'js' => array( + 'src/loadCSS.min.js' => array( + 'type' => 'file', + 'async' => TRUE, + ), + 'src/onloadCSS.min.js' => array( + 'type' => 'file', + 'async' => TRUE, + ), + ), + ), + ), + 'external' => array( + 'files' => array( + 'js' => array( + "//cdn.jsdelivr.net/gh/filamentgroup/loadCSS@v{$version}/src/loadCSS.js" => array( + 'type' => 'external', + 'data' => "//cdn.jsdelivr.net/gh/filamentgroup/loadCSS@v{$version}/src/loadCSS.js", + 'async' => TRUE, + ), + ), + ), + ), + 'external-preload' => array( + 'files' => array( + 'js' => array( + "//cdn.jsdelivr.net/gh/filamentgroup/loadCSS@v{$version}/src/cssrelpreload.js" => array( + 'type' => 'external', + 'data' => "//cdn.jsdelivr.net/gh/filamentgroup/loadCSS@v{$version}/src/cssrelpreload.js", + 'async' => TRUE, + ), + ), + ), + ), + 'external-onload' => array( + 'files' => array( + 'js' => array( + "//cdn.jsdelivr.net/gh/filamentgroup/loadCSS@v{$version}/src/loadCSS.js" => array( + 'type' => 'external', + 'data' => "//cdn.jsdelivr.net/gh/filamentgroup/loadCSS@v{$version}/src/loadCSS.js", + 'async' => TRUE, + ), + "//cdn.jsdelivr.net/gh/filamentgroup/loadCSS@v{$version}/src/onloadCSS.js" => array( + 'type' => 'external', + 'data' => "//cdn.jsdelivr.net/gh/filamentgroup/loadCSS@v{$version}/src/onloadCSS.js", + 'async' => TRUE, + ), + ), + ), + ), + 'local' => array( + 'version' => '1.3.1', + 'files' => array( + 'js' => array( + "{$advagg_mod_path}/loadCSS.js" => array( + 'type' => 'file', + 'data' => "{$advagg_mod_path}/loadCSS.js", + 'async' => TRUE, + ), + ), + ), + ), + 'local-preload' => array( + 'version' => '1.3.1', + 'files' => array( + 'js' => array( + "{$advagg_mod_path}/cssrelpreload.js" => array( + 'type' => 'file', + 'data' => "{$advagg_mod_path}/cssrelpreload.js", + 'async' => TRUE, + ), + ), + ), + ), + 'local-onload' => array( + 'version' => '1.3.1', + 'files' => array( + 'js' => array( + "{$advagg_mod_path}/loadCSS.js" => array( + 'type' => 'file', + 'data' => "{$advagg_mod_path}/loadCSS.js", + 'async' => TRUE, + ), + "{$advagg_mod_path}/onloadCSS.js" => array( + 'type' => 'file', + 'data' => "{$advagg_mod_path}/onloadCSS.js", + 'async' => TRUE, + ), + ), + ), + ), + 'local-minified' => array( + 'version' => '1.3.1', + 'files' => array( + 'js' => array( + "{$advagg_mod_path}/loadCSS.min.js" => array( + 'type' => 'file', + 'data' => "{$advagg_mod_path}/loadCSS.min.js", + 'async' => TRUE, + ), + ), + ), + ), + 'local-minified-preload' => array( + 'version' => '1.3.1', + 'files' => array( + 'js' => array( + "{$advagg_mod_path}/cssrelpreload.min.js" => array( + 'type' => 'file', + 'data' => "{$advagg_mod_path}/cssrelpreload.min.js", + 'async' => TRUE, + ), + ), + ), + ), + 'local-minified-onload' => array( + 'version' => '1.3.1', + 'files' => array( + 'js' => array( + "{$advagg_mod_path}/loadCSS.min.js" => array( + 'type' => 'file', + 'data' => "{$advagg_mod_path}/loadCSS.min.js", + 'async' => TRUE, + ), + "{$advagg_mod_path}/onloadCSS.min.js" => array( + 'type' => 'file', + 'data' => "{$advagg_mod_path}/onloadCSS.min.js", + 'async' => TRUE, + ), + ), + ), + ), + ), + ); + + // Add inline data. + $loadcss_loc = "{$advagg_mod_path}/loadCSS.min.js"; + $cssrelpreload_loc = "{$advagg_mod_path}/cssrelpreload.min.js"; + $onloadcss_loc = "{$advagg_mod_path}/onloadCSS.min.js"; + // Use given library if there. + $libraries_paths = array(); + if (is_callable('libraries_get_libraries')) { + $libraries_paths = libraries_get_libraries(); + } + if (isset($libraries_paths['loadCSS'])) { + // Get location of loadCSS. + if (is_readable($libraries_paths['loadCSS'] . '/src/loadCSS.min.js')) { + $loadcss_loc = $libraries_paths['loadCSS'] . '/src/loadCSS.min.js'; + $libraries['loadCSS']['variants']['minified']['#files_exists'] = TRUE; + } + elseif (is_readable($libraries_paths['loadCSS'] . '/src/loadCSS.js')) { + $loadcss_loc = $libraries_paths['loadCSS'] . '/src/loadCSS.js'; + } + + // Get location of cssrelpreload. + if (is_readable($libraries_paths['loadCSS'] . '/src/cssrelpreload.min.js')) { + $cssrelpreload_loc = $libraries_paths['loadCSS'] . '/src/cssrelpreload.min.js'; + if ($libraries['loadCSS']['variants']['minified']['#files_exists']) { + $libraries['loadCSS']['variants']['minified-preload']['#files_exists'] = TRUE; + } + } + elseif (is_readable($libraries_paths['loadCSS'] . '/src/cssrelpreload.js')) { + $cssrelpreload_loc = $libraries_paths['loadCSS'] . '/src/cssrelpreload.js'; + } + + // Get location of onloadCSS. + if (is_readable($libraries_paths['loadCSS'] . '/src/onloadCSS.min.js')) { + $onloadcss_loc = $libraries_paths['loadCSS'] . '/src/onloadCSS.min.js'; + if ($libraries['loadCSS']['variants']['minified']['#files_exists']) { + $libraries['loadCSS']['variants']['minified-preload']['#files_exists'] = TRUE; } } + elseif (is_readable($libraries_paths['loadCSS'] . '/src/onloadCSS.js')) { + $onloadcss_loc = $libraries_paths['loadCSS'] . '/src/onloadCSS.js'; + } + } + + // Add inline scripts. + $libraries['loadCSS']['variants'] += array( + 'inline' => array( + 'files' => array( + 'js' => array( + 'loadCSS_inline' => array( + 'type' => 'inline', + 'data' => (string) @advagg_file_get_contents($loadcss_loc), + 'no_defer' => TRUE, + ), + ), + ), + ), + 'inline-preload' => array( + 'files' => array( + 'js' => array( + 'cssrelpreload_inline' => array( + 'type' => 'inline', + 'data' => (string) @advagg_file_get_contents($cssrelpreload_loc), + 'no_defer' => TRUE, + ), + ), + ), + ), + 'inline-onload' => array( + 'files' => array( + 'js' => array( + 'loadCSS_inline' => array( + 'type' => 'inline', + 'data' => (string) @advagg_file_get_contents($loadcss_loc), + 'no_defer' => TRUE, + ), + 'onloadCSS_inline' => array( + 'type' => 'inline', + 'data' => (string) @advagg_file_get_contents($onloadcss_loc), + 'no_defer' => TRUE, + ), + ), + ), + ), + ); + + if (!is_callable('libraries_detect')) { + // Set defaults. + $default_options = advagg_mod_loadcss_js_defaults(); + foreach ($libraries['loadCSS']['files']['js'] as &$value) { + $value += $default_options; + } + foreach ($libraries['loadCSS']['variants'] as &$values) { + foreach ($values['files']['js'] as &$value) { + $value += $default_options; + } + } + } + + return $libraries; +} + +/** + * Implements hook_magic(). + */ +function advagg_mod_magic(array $magic_settings, $theme) { + // $magic_settings is READ ONLY. + $settings = array(); + + // If possible disable access and set default to false. + if (!isset($magic_settings['css']['magic_embedded_mqs']['#access'])) { + $settings['css']['magic_embedded_mqs']['#access'] = FALSE; + } + if (!isset($magic_settings['css']['magic_embedded_mqs']['#default_value'])) { + $settings['css']['magic_embedded_mqs']['#default_value'] = FALSE; + } + if (!isset($magic_settings['js']['magic_footer_js']['#access'])) { + $settings['js']['magic_footer_js']['#access'] = FALSE; + } + if (!isset($magic_settings['js']['magic_footer_js']['#default_value'])) { + $settings['js']['magic_footer_js']['#default_value'] = FALSE; + } + if (!isset($magic_settings['js']['magic_library_head']['#access'])) { + $settings['js']['magic_library_head']['#access'] = FALSE; + } + if (!isset($magic_settings['js']['magic_library_head']['#default_value'])) { + $settings['js']['magic_library_head']['#default_value'] = FALSE; + } + if (!isset($magic_settings['js']['magic_experimental_js']['#access'])) { + $settings['js']['magic_experimental_js']['#access'] = FALSE; + } + if (!isset($magic_settings['js']['magic_experimental_js']['#default_value'])) { + $settings['js']['magic_experimental_js']['#default_value'] = FALSE; + } + + // Add in our own validate function so we can preprocess variables before + // they are saved. + $settings['#validate'] = array('advagg_mod_magic_form_validate'); + // Must not contain anything from the $magic_settings array. + return $settings; +} + +/** + * @} End of "addtogroup 3rd_party_hooks". + */ + +/** + * Get the default loadcss options for the js used. + * + * @return array + * Key => value options array for drupal_add_js(). + */ +function advagg_mod_loadcss_js_defaults() { + list(, , , , , , , , , , $css_defer) = advagg_mod_get_lists(); + $default_options = array( + '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, + ); + return $default_options; +} + +/** + * Callback right before loadcss lib is loaded; set defaults. + * + * @param array $version_variant + * Array of the library that is about to be loaded. + */ +function advagg_mod_libraries_preload_callback(array &$version_variant) { + // Get default options. + $default_options = advagg_mod_loadcss_js_defaults(); + // Set defaults for the given configuration. + foreach ($version_variant['files']['js'] as &$value) { + $value += $default_options; + } +} + +/** + * Adds the loadcss js library if needed. + * + * @param array $js + * The JS array. + * @param array $css + * The CSS array. + */ +function advagg_mod_add_loadcss_js_lib(array $js = array(), array $css = array()) { + if (!module_exists('advagg') || !advagg_enabled()) { + return; + } + + // Return early if this setting is disabled. + list(, , , , , , , , , , $css_defer) = advagg_mod_get_lists($js, $css); + if (empty($css_defer)) { + return; + } + + static $added; + $library = advagg_get_library('loadCSS', 'advagg_mod'); + $options_defaults = advagg_mod_loadcss_js_defaults(); + + $preload = '-onload'; + if ($css_defer == 4) { + $preload = '-preload'; + } + + $css_defer_js_code = variable_get('advagg_mod_css_defer_js_code', ADVAGG_MOD_CSS_DEFER_JS_CODE); + // Inline load. + if ($css_defer_js_code == 0) { + if (!empty($library['installed'])) { + libraries_load('loadCSS', "inline{$preload}"); + } + else { + foreach ($library['variants']["inline{$preload}"]['files']['js'] as $data => $options) { + if (!isset($added[$data])) { + if (!empty($options['data'])) { + drupal_add_js($options['data'], $options + $options_defaults); + $added[$data] = TRUE; + } + else { + // Fallback to load as a file if no inline js. + $css_defer_js_code = 2; + } + } + } + } + } + // Load as a file. + if ($css_defer_js_code == 2) { + if ($library['installed']) { + if (variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL) >= 0 && $library['variants']['minified']['#files_exists']) { + libraries_load('loadCSS', "minified{$preload}"); + } + else { + if ($preload) { + libraries_load('loadCSS'); + } + else { + libraries_load('loadCSS', "normal{$preload}"); + } + } + } + else { + foreach ($library['variants']["local{$preload}"]['files']['js'] as $data => $options) { + if (!isset($added[$data])) { + if (!empty($options['data'])) { + drupal_add_js($options['data'], $options + $options_defaults); + $added[$data] = TRUE; + } + else { + // Fallback to external load. + $css_defer_js_code = 4; + } + } + } + } + } + // Load external library. + if ($css_defer_js_code == 4) { + foreach ($library['variants']["external{$preload}"]['files']['js'] as $data => $options) { + if (!isset($added[$data])) { + drupal_add_js($options['data'], $options + $options_defaults); + $added[$data] = TRUE; + } + } + } +} + +/** + * Try to find the critical css file. + * + * @return array + * The css and dns files to use. + */ +function advagg_mod_find_critical_css_file() { + $filename = FALSE; + + // Normalize request uri. + $base_path = base_path(); + $request_uri = request_uri(); + $pos = strpos($request_uri, $base_path); + if ($pos === 0) { + $request_uri = substr($request_uri, strlen($base_path)); + } + + $dirs = array( + 0 => drupal_get_path('theme', $GLOBALS['theme']) . '/', + 1 => 'critical-css/', + // Use authenticated|anonymous or all. + 2 => user_is_logged_in() ? 'authenticated/' : 'anonymous/', + 3 => 'all/', + // Use urls or object_type. + 4 => 'urls/', + 5 => 'type/', + // Different variations of the current URL. + 6 => current_path(), + 7 => advagg_url_to_filename($request_uri, FALSE), + 8 => advagg_url_to_filename(request_path(), FALSE), + 9 => $request_uri, + 10 => request_path(), + ); + $front_page = drupal_is_front_page(); + if (!$front_page) { + $front_page = drupal_get_path_alias() == variable_get('site_frontpage', 'node'); + } + $object = menu_get_object(); + $params = array($dirs, $front_page, $object); + $inline_strings = array('', '', ''); + + // Allow for altering the starting point. + // Call hook_advagg_mod_critical_css_file_pre_alter(). + drupal_alter('advagg_mod_critical_css_file_pre', $filename, $params, $inline_strings); + list($dirs, $front_page, $object) = $params; + + // Look in themename/critical-css/authenticated|anonymous/urls|object_type. + if ((!empty($dirs[0]) || !empty($dirs[1])) && is_readable("{$dirs[0]}{$dirs[1]}")) { + if (!$filename + && $front_page + && is_readable("{$dirs[0]}{$dirs[1]}{$dirs[2]}{$dirs[4]}front.css") + ) { + $filename = "{$dirs[0]}{$dirs[1]}{$dirs[2]}{$dirs[4]}front"; + } + if (!$filename + && is_readable("{$dirs[0]}{$dirs[1]}{$dirs[2]}{$dirs[4]}{$dirs[6]}.css") + ) { + $filename = "{$dirs[0]}{$dirs[1]}{$dirs[2]}{$dirs[4]}{$dirs[6]}"; + } + if (!$filename + && is_readable("{$dirs[0]}{$dirs[1]}{$dirs[2]}{$dirs[4]}{$dirs[7]}.css") + ) { + $filename = "{$dirs[0]}{$dirs[1]}{$dirs[2]}{$dirs[4]}{$dirs[7]}"; + } + if (!$filename + && is_readable("{$dirs[0]}{$dirs[1]}{$dirs[2]}{$dirs[4]}{$dirs[8]}.css") + ) { + $filename = "{$dirs[0]}{$dirs[1]}{$dirs[2]}{$dirs[4]}{$dirs[8]}"; + } + if (isset($object->type)) { + $filtered_object_type = advagg_url_to_filename($object->type); + if (!$filename + && is_readable("{$dirs[0]}{$dirs[1]}{$dirs[2]}{$dirs[5]}{$object->type}.css") + ) { + $filename = "{$dirs[0]}{$dirs[1]}{$dirs[2]}{$dirs[5]}{$object->type}"; + } + if (!$filename + && is_readable("{$dirs[0]}{$dirs[1]}{$dirs[2]}{$dirs[5]}$filtered_object_type.css") + ) { + $filename = "{$dirs[0]}{$dirs[1]}{$dirs[2]}{$dirs[5]}$filtered_object_type"; + } + } + + // Look in themename/critical-css/all/urls|object_type. + if (!$filename + && $front_page + && is_readable("{$dirs[0]}{$dirs[1]}{$dirs[3]}{$dirs[4]}front.css") + ) { + $filename = "{$dirs[0]}{$dirs[1]}{$dirs[3]}{$dirs[4]}front"; + } + if (!$filename + && is_readable("{$dirs[0]}{$dirs[1]}{$dirs[3]}{$dirs[4]}{$dirs[6]}.css") + ) { + $filename = "{$dirs[0]}{$dirs[1]}{$dirs[3]}{$dirs[4]}{$dirs[6]}"; + } + if (!$filename + && is_readable("{$dirs[0]}{$dirs[1]}{$dirs[3]}{$dirs[4]}{$dirs[7]}.css") + ) { + $filename = "{$dirs[0]}{$dirs[1]}{$dirs[3]}{$dirs[4]}{$dirs[7]}"; + } + if (!$filename + && is_readable("{$dirs[0]}{$dirs[1]}{$dirs[3]}{$dirs[4]}{$dirs[8]}.css") + ) { + $filename = "{$dirs[0]}{$dirs[1]}{$dirs[3]}{$dirs[4]}{$dirs[8]}"; + } + if (isset($object->type)) { + $filtered_object_type = advagg_url_to_filename($object->type); + if (!$filename + && is_readable("{$dirs[0]}{$dirs[1]}{$dirs[3]}{$dirs[5]}{$object->type}.css") + ) { + $filename = "{$dirs[0]}{$dirs[1]}{$dirs[3]}{$dirs[5]}{$object->type}"; + } + if (!$filename + && is_readable("{$dirs[0]}{$dirs[1]}{$dirs[3]}{$dirs[5]}$filtered_object_type.css") + ) { + $filename = "{$dirs[0]}{$dirs[1]}{$dirs[3]}{$dirs[5]}$filtered_object_type"; + } + } + } + + // Build filenames array. + $filenames = array('', '', ''); + if ($filename) { + $filenames = array( + "$filename.css", + "$filename.dns", + "$filename.pre", + ); + } + + // Get inline css string. + if (empty($inline_strings[0]) + && !empty($filenames[0]) + && is_readable($filenames[0]) + ) { + module_load_include('inc', 'advagg', 'advagg'); + $inline_css = advagg_load_stylesheet($filenames[0], TRUE); + + // Allow other modules to modify this files contents. + // Call hook_advagg_get_css_file_contents_alter(). + drupal_alter('advagg_get_css_file_contents', $inline_css, $filenames[0]); + $inline_strings[0] = $inline_css; + } + + // Remove starting and ending style tags. + if (stripos($inline_strings[0], '<style>') === 0) { + $inline_strings[0] = trim(substr($inline_strings[0], 7)); + } + $len = strlen($inline_strings[0]); + if (stripos($inline_strings[0], '</style>') === $len - 8) { + $inline_strings[0] = trim(substr($inline_strings[0], 0, $len - 8)); + } + + // Add in domain prefetch. + if (empty($inline_strings[1]) + && !empty($filenames[1]) + && is_readable($filenames[1]) + ) { + $inline_strings[1] = file_get_contents($filenames[1]); + } + + // Add in files to preload. + if (empty($inline_strings[2]) + && !empty($filenames[2]) + && is_readable($filenames[2]) + ) { + $inline_strings[2] = file_get_contents($filenames[2]); + } + + // Remove !important from all CSS rules. Strips it even without a space in + // front, unlike the earlier version of this code. + if (variable_get('advagg_mod_inline_critical_css_strip_important', ADVAGG_MOD_INLINE_CRITICAL_CSS_STRIP_IMPORTANT)) { + if (!empty($inline_strings)) { + $inline_strings[0] = str_replace('!important', '', $inline_strings[0]); + } + } + + // Allow for altering the ending point. + // Call hook_advagg_mod_critical_css_file_post_alter(). + drupal_alter('advagg_mod_critical_css_file_post', $filenames, $params, $inline_strings); + + return array($filenames, $params, $inline_strings); +} + +/** + * Form validation handler. Disable certain magic settings before being saved. + */ +function advagg_mod_magic_form_validate($form, &$form_state) { + // Disable magic functionality if it is a duplicate of AdvAgg. + $form_state['values']['magic_embedded_mqs'] = 0; + $form_state['values']['magic_footer_js'] = 0; + $form_state['values']['magic_library_head'] = 0; + $form_state['values']['magic_experimental_js'] = 0; +} + +/** + * Alter the js array. + * + * @param array $js + * JS array. + */ +function advagg_mod_js_pre_alter(array &$js) { + if (!module_exists('advagg') || !advagg_enabled()) { + return; + } + + // Change google analytics inline loader to be inside of an aggregrated file. + if (variable_get('advagg_mod_ga_inline_to_file', ADVAGG_MOD_GA_INLINE_TO_FILE)) { + advagg_mod_ga_inline_to_file($js); + } + + if (variable_get('advagg_mod_js_inline_resource_hints', ADVAGG_MOD_JS_INLINE_RESOURCE_HINTS)) { + advagg_mod_find_inline_domains($js); + } +} + +/** + * Add dns_prefetch for inline js domains. + * + * @param array $js + * JS array. + */ +function advagg_mod_find_inline_domains(array &$js) { + $parsed = @parse_url($GLOBALS['base_root']); + $host = $parsed['host']; + + foreach ($js as &$values) { + if ($values['type'] !== 'inline') { + continue; + } + // Find quoted strings in JS. + $matches = array(); + $pattern = "/[\"'](.*?)[\"']/"; + $matched = preg_match_all($pattern, $values['data'], $matches); + + if (!$matched) { + continue; + } + + // Find domains in the quoted strings and dns_prefetch it. + foreach ($matches[1] as $value) { + if (strpos($value, '//') !== FALSE) { + $parsed = @parse_url($value); + if (!empty($parsed['host']) && $host !== $parsed['host']) { + $values['dns_prefetch'][] = $value; + } + } + } + } +} + +/** + * Alter the js array. + * + * @param array $js + * JS array. + */ +function advagg_mod_js_post_alter(array &$js) { + if (!module_exists('advagg') || !advagg_enabled()) { + return; + } + + // Only add JS if it's actually needed. + if (variable_get('advagg_mod_js_remove_unused', ADVAGG_MOD_JS_REMOVE_UNUSED)) { + advagg_mod_remove_js_if_not_used($js); + } + + // Change sort order so aggregates do not get split up. + if (variable_get('advagg_mod_js_adjust_sort_external', ADVAGG_MOD_JS_ADJUST_SORT_EXTERNAL) + || variable_get('advagg_mod_js_adjust_sort_inline', ADVAGG_MOD_JS_ADJUST_SORT_INLINE) + || variable_get('advagg_mod_js_adjust_sort_browsers', ADVAGG_MOD_JS_ADJUST_SORT_BROWSERS) + ) { + advagg_mod_sort_css_js($js, 'js'); + } + + // Move JS to the footer. + advagg_mod_js_move_to_footer($js); + + // Force all JS to be preprocessed. + if (variable_get('advagg_mod_js_preprocess', ADVAGG_MOD_JS_PREPROCESS)) { + foreach ($js as &$values) { + if (!empty($values['preprocess_lock'])) { + continue; + } + $values['preprocess'] = TRUE; + $values['cache'] = TRUE; + } + unset($values); + } + + // Add the defer or the async tag to JS. + $jquery_deferred = advagg_mod_js_async_defer($js); + // Inline JS defer. + advagg_mod_inline_defer($js, $jquery_deferred); + + // Move all async JS to the header. + if (variable_get('advagg_mod_js_async_in_header', ADVAGG_MOD_JS_ASYNC_IN_HEADER)) { + foreach ($js as &$values) { + // Skip if not file or external. + if ($values['type'] !== 'file' && $values['type'] !== 'external') { + continue; + } + // Skip if not async. + if (empty($values['async']) && empty($values['attributes']['async'])) { + continue; + } + // Skip if scope locked. + if (!empty($values['scope_lock'])) { + continue; + } + + // Move to the header with a group of 1000. + $values['scope'] = 'header'; + $values['group'] = 1000; + } + 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)) { + return; + } + foreach ($js as &$values) { + if (!isset($values['dns_prefetch'])) { + continue; + } + foreach ($values['dns_prefetch'] as &$url) { + // Prefetch stats.g.doubleclick.net domain. + if (strpos($url, '//stats.g.doubleclick.net') === FALSE) { + continue; + } + if (variable_get('advagg_resource_hints_preconnect', ADVAGG_RESOURCE_HINTS_PRECONNECT)) { + $parse = @parse_url($url); + $inline_script = 'var preconnect_support = false; try {if (document.createElement("link").relList.supports("preconnect")) {preconnect_support = true;}} catch (e) {} if (!preconnect_support) { var prefetch = document.createElement("link"); prefetch.href = "https://' . $parse['host'] . '/robots.txt"; prefetch.rel="prefetch"; document.getElementsByTagName("head")[0].appendChild(prefetch);}'; + $js['advagg_preconnect_support'] = array( + 'type' => 'inline', + 'group' => JS_LIBRARY - 1, + 'weight' => -50000, + 'scope_lock' => TRUE, + 'movable' => FALSE, + 'no_defer' => TRUE, + 'data' => $inline_script, + ) + drupal_js_defaults($inline_script); + } + else { + $url .= '#prefetch'; + } + } + } +} + +/** + * Remove ajaxPageState CSS/JS if misc/ajax.js is not used. + * + * @param array $scripts + * Render array. + */ +function advagg_mod_js_no_ajaxpagestate(array &$scripts) { + if (!module_exists('advagg') || !advagg_enabled()) { + return; + } + + if (!variable_get('advagg_mod_js_no_ajaxpagestate', ADVAGG_MOD_JS_NO_AJAXPAGESTATE) || variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL) < 0) { + return; + } + + // 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. + if (isset($scripts['#items']['settings']['data'][$delta]['ajaxPageState']['js'])) { + unset($scripts['#items']['settings']['data'][$delta]['ajaxPageState']['js']); + } + // Remove css files. + if (isset($scripts['#items']['settings']['data'][$delta]['ajaxPageState']['css'])) { + unset($scripts['#items']['settings']['data'][$delta]['ajaxPageState']['css']); + } + // Cleanup. + if (empty($scripts['#items']['settings']['data'][$delta]['ajaxPageState'])) { + unset($scripts['#items']['settings']['data'][$delta]['ajaxPageState']); + if (empty($scripts['#items']['settings']['data'][$delta])) { + unset($scripts['#items']['settings']['data'][$delta]); + } + } + } + } + } +} + +/** + * Generate a list of rules and exceptions for js files and inline. + * + * Controls inline wrapping and defer. Controls no async/defer file list. + * Controls files that stay in the header. + * + * @param array $js + * The JS array. + * @param array $css + * The CSS array. + * + * @return array + * A multidimensional array. + */ +function advagg_mod_get_lists(array $js = array(), array $css = array()) { + $lists = &drupal_static(__FUNCTION__); + $js_count = count($js); + $css_count = count($css); + $key = $js_count . '.' . $css_count; + if (!isset($lists[$key])) { + // Do not move to footer file list. + $header_file_list = array( + // Modernizr js. + '/modernizr.', + // Html5shiv and html5shiv-printshiv. + '/html5shiv.', + '/html5shiv-printshiv.', + // Google Admanager Needs to be in the header. + '/google_service.', + ); + + // Do not move to footer inline list. + $header_inline_list = array( + // Google Analytics should be in the header to verify for Webmaster tools. + 'GoogleAnalyticsObject', + 'window.google_analytics_uacct', + // Google Admanager Needs to be in the header. + 'GS_googleAddAdSenseService(', + 'GS_googleEnableAllServices(', + 'GA_googleAddSlot(', + 'GA_googleFetchAds(', + ); + + // Do not defer/async list. + $no_async_defer_list = array( + // Wistia js. + '//fast.wistia.', + // Maps. + '//dev.virtualearth.net', + '//api.maps.yahoo.com', + // Google Admanager can't be forced defer/async. + '/google_service.', + ); + // Openlayers. + if (module_exists('openlayers')) { + // Openlayers fix; external scripts can not be loaded out of order. + // Cloudmade. + $path = variable_get('openlayers_layers_cloudmade_js', ''); + if (valid_url($path, TRUE)) { + $no_async_defer_list['openlayers_layers_cloudmade'] = $path; + } + // Google. + $mapdomain = variable_get('openlayers_layers_google_mapdomain', 'maps.google.com'); + $no_async_defer_list['openlayers_layers_google'] = $mapdomain . '/maps'; + } + + // Wrap inline js so it does not run until the condition is TRUE. + // Inline search string => js condition. + $inline_wrapper_list = array(); + + // 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)))); + $inline_js_wrap_skip_list[] = '.write('; + $inline_js_wrap_skip_list[] = '._fbq'; + $inline_js_wrap_skip_list[] = '.fbq'; + $inline_js_wrap_skip_list[] = 'gtm.start'; + $inline_js_wrap_skip_list[] = '_gaq.push(["_'; + $inline_js_wrap_skip_list[] = 'ga("'; + $inline_js_wrap_skip_list[] = "ga('"; + $inline_js_wrap_skip_list[] = 'GoogleAnalyticsObject'; + $inline_js_wrap_skip_list[] = 'window.google_analytics_uacct'; + $inline_js_wrap_skip_list[] = 'function krumo('; + $inline_js_wrap_skip_list[] = '// no advagg'; + $inline_js_wrap_skip_list[] = '// noadvagg'; + $inline_js_wrap_skip_list[] = '// no-advagg'; + $inline_js_wrap_skip_list[] = '//no advagg'; + $inline_js_wrap_skip_list[] = '//noadvagg'; + $inline_js_wrap_skip_list[] = '//no-advagg'; + // Google Admanager can not be wrapped in a callback function. + $inline_js_wrap_skip_list[] = 'GS_googleAddAdSenseService('; + $inline_js_wrap_skip_list[] = 'GS_googleEnableAllServices('; + $inline_js_wrap_skip_list[] = 'GA_googleAddSlot('; + $inline_js_wrap_skip_list[] = 'GA_googleFetchAds('; + $inline_js_wrap_skip_list[] = 'GA_googleFillSlot('; + $inline_js_wrap_skip_list[] = 'adsbygoogle'; + $inline_js_wrap_skip_list[] = '_paq.push(["'; + if (module_exists('h5p')) { + $inline_js_wrap_skip_list[] = 'H5PIntegration'; + } + + // 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('; + $inline_js_defer_skip_list[] = '._fbq'; + $inline_js_defer_skip_list[] = '.fbq'; + $inline_js_defer_skip_list[] = 'gtm.start'; + $inline_js_defer_skip_list[] = '_gaq.push(["_'; + $inline_js_defer_skip_list[] = 'GoogleAnalyticsObject'; + $inline_js_defer_skip_list[] = 'window.google_analytics_uacct'; + $inline_js_defer_skip_list[] = '// no advagg'; + $inline_js_defer_skip_list[] = '// noadvagg'; + $inline_js_defer_skip_list[] = '// no-advagg'; + $inline_js_defer_skip_list[] = '//no advagg'; + $inline_js_defer_skip_list[] = '//noadvagg'; + $inline_js_defer_skip_list[] = '//no-advagg'; + // Google Admanager can not be wrapped in a callback function. + $inline_js_defer_skip_list[] = 'GS_googleAddAdSenseService('; + $inline_js_defer_skip_list[] = 'GS_googleEnableAllServices('; + $inline_js_defer_skip_list[] = 'GA_googleAddSlot('; + $inline_js_defer_skip_list[] = 'GA_googleFetchAds('; + $inline_js_defer_skip_list[] = 'GA_googleFillSlot('; + $inline_js_defer_skip_list[] = 'adsbygoogle'; + $inline_js_defer_skip_list[] = '_paq.push(["'; + if (module_exists('h5p')) { + $inline_js_defer_skip_list[] = 'H5PIntegration'; + } + + // If there is a fast clicker, ajax links might not work if ajax.js is + // loaded in the footer. + $all_in_footer_list = array( + 'misc/ajax.js' => array( + '/jquery.js', + '/jquery.min.js', + '/jquery.once.js', + '/ajax.js', + '/drupal.js', + 'settings', + ), + ); + + $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); + $css_defer = variable_get('advagg_mod_css_defer', ADVAGG_MOD_CSS_DEFER); + + // Allow other modules to add/edit the above lists. + // Call hook_advagg_mod_get_lists_alter(). + $lists[$key] = array( + $header_file_list, + $header_inline_list, + $no_async_defer_list, + $inline_wrapper_list, + $inline_js_wrap_skip_list, + $inline_js_defer_skip_list, + $all_in_footer_list, + $move_js_to_footer, + $defer_setting, + $async_setting, + $css_defer, + ); + drupal_alter('advagg_mod_get_lists', $lists[$key], $js, $css); + } + return $lists[$key]; +} + +/** + * Move JS to the footer. + * + * @param array $js + * JS array. + */ +function advagg_mod_js_move_to_footer(array &$js) { + // Move all JS to the footer. + 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)) { + foreach ($all_in_footer_list as $key => $search_strings) { + if (isset($js[$key])) { + foreach ($js as $name => &$values) { + foreach ($search_strings as $string) { + if (!empty($string) && strpos($name, (string) $string) !== FALSE) { + $values['scope_lock'] = TRUE; + break; + } + if (is_string($values['data']) && !empty($string) && strpos($values['data'], (string) $string) !== FALSE) { + $values['scope_lock'] = TRUE; + break; + } + if (!empty($values['pre_relocate_data']) && is_string($values['pre_relocate_data']) && !empty($string) && strpos($values['pre_relocate_data'], (string) $string) !== FALSE) { + $values['scope_lock'] = TRUE; + break; + } + } + } + } + } + } + + foreach ($js as $key => &$values) { + // If scope is not set, this js is not getting used. remove it. + if (!isset($values['scope'])) { + unset($js[$key]); + continue; + } + + if (strpos($values['scope'], ':') !== FALSE) { + continue; + } + + // Skip if a library and configured to do so. + if ($move_js_to_footer == 1 && $values['group'] <= JS_LIBRARY) { + continue; + } + + // Skip if the scope has been locked. + if (!empty($values['scope_lock'])) { + continue; + } + + // Allow certain scripts to be kept in the header. + if ($values['type'] !== 'inline' && $values['type'] !== 'setting') { + foreach ($header_file_list as $search_string) { + if (stripos($values['data'], $search_string) !== FALSE) { + continue 2; + } + if (!empty($values['pre_relocate_data']) && stripos($values['pre_relocate_data'], $search_string) !== FALSE) { + continue 2; + } + } + } + + // Allow certain inline scripts to be kept in the header. + if ($values['type'] === 'inline') { + foreach ($header_inline_list as $search_string) { + if (strpos($values['data'], $search_string) !== FALSE) { + continue 2; + } + } + } + + // If group is not set, make it JS_DEFAULT (0). + if (!isset($values['group'])) { + $values['group'] = JS_DEFAULT; + } + // If weight is not set, make it 0. + if (!isset($values['weight'])) { + $values['weight'] = 0; + } + // If every_page is not set, make it FALSE. + if (!isset($values['every_page'])) { + $values['every_page'] = FALSE; + } + + // If JS is not in the header increase group by 10000. + if ($values['scope'] !== 'header' && is_numeric($values['group'])) { + $values['group'] += 10000; + } + // If JS is already in the footer increase group by 10000. + if ($values['scope'] === 'footer' && is_numeric($values['group'])) { + $values['group'] += 10000; + } + $values['scope'] = 'footer'; + } + unset($values); +} + +/** + * Add the defer and or the async tag to js. + * + * @param array $js + * JS array. + * + * @return bool + * TRUE if jQuery is deferred. + */ +function advagg_mod_js_async_defer(array &$js) { + $jquery_deferred = FALSE; + $jquery_rev = strrev('/jquery.js'); + $jquery_min_rev = strrev('/jquery.min.js'); + $jquery_ui_rev = strrev('/jquery-ui.js'); + $jquery_ui_min_rev = strrev('jquery-ui.min.js'); + + // Return early if this is disabled. + list(, , $no_async_defer_list, $inline_wrapper_list, , , , , $defer_setting, $async_setting) = advagg_mod_get_lists($js); + if (!$defer_setting && !$async_setting) { + // Special handling if using loadcss to defer css loading. + if (!empty($GLOBALS['advagg_mod_loadcss_jquery_holdready'])) { + foreach ($js as $name => &$values) { + // Special handling for jQuery. + if (stripos(strrev($name), $jquery_rev) === 0 + || stripos(strrev($name), $jquery_min_rev) === 0 + ) { + // Do not fire jQuery.ready until Drupal.settings has been defined. + $values['onload'] = "if(jQuery.isFunction(jQuery.holdReady)){jQuery.holdReady(true);}"; + break; + } + } + } + return $jquery_deferred; + } + + // Disable this section of code for now; the on error attribute only works + // with async safe JS. + $use_on_error = FALSE; + if (variable_get('advagg_mod_js_defer_jquery', ADVAGG_MOD_JS_DEFER_JQUERY) !== FALSE + && variable_get('advagg_mod_js_defer_inline_alter', ADVAGG_MOD_JS_DEFER_INLINE_ALTER) + && variable_get('jquery_update_jquery_cdn', 'none') !== 'none' + ) { + $use_on_error = TRUE; + } + // If everything is async safe then we can use on error. + // Only needed if the jquery_update javascript is loaded via async/defer. + if ($use_on_error) { + $jquery_update_fallback = ''; + $jquery_update_ui_fallback = ''; + $jquery_migrate_fallback = ''; + $inline_array = array(); + if (module_exists('jquery_update') + && (variable_get('jquery_update_jquery_cdn', 'none') !== 'none' + || variable_get('jquery_update_jquery_migrate_cdn', 'none') !== 'none' + )) { + $min = variable_get('jquery_update_compression_type', 'min') == 'none' ? '' : '.min'; + $path = drupal_get_path('module', 'jquery_update'); + foreach ($js as $name => &$values) { + // Skip if not inline. + if ($values['type'] !== 'inline') { + continue; + } + + // JQuery UI. + if (stripos($values['data'], 'window.jQuery.ui') !== FALSE + && stripos($values['data'], 'document.write("<script') !== FALSE + && variable_get('jquery_update_jquery_cdn', 'none') !== 'none' + ) { + $js_path = ($min == '.min') ? '/replace/ui/ui/minified/jquery-ui.min.js' : '/replace/ui/ui/jquery-ui.js'; + $jquery_update_ui_fallback = "{$GLOBALS['base_path']}{$path}{$js_path}"; + if (empty($inline_array)) { + $inline_array = $values; + } + unset($js[$name]); + continue; + } + // JQuery Migrate. + if (stripos($values['data'], 'window.jQuery.migrateWarnings') !== FALSE + && stripos($values['data'], 'document.write("<script') !== FALSE + && variable_get('jquery_update_jquery_migrate_cdn', 'none') !== 'none' + ) { + $version = '1.2.1'; + $jquery_migrate_fallback = "{$GLOBALS['base_path']}{$path}/replace/jquery-migrate/{$version}/jquery-migrate{$min}.js"; + $inline_array = $values; + unset($js[$name]); + continue; + } + // JQuery. + // This should always be last. + if (stripos($values['data'], 'window.jQuery') !== FALSE + && stripos($values['data'], 'document.write("<script') !== FALSE + && variable_get('jquery_update_jquery_cdn', 'none') !== 'none' + ) { + $version = variable_get('jquery_update_jquery_version', '1.10'); + $jquery_update_fallback = "{$GLOBALS['base_path']}{$path}/replace/jquery/{$version}/jquery{$min}.js"; + $inline_array = $values; + unset($js[$name]); + continue; + } + } + unset($values); + } + if (!empty($jquery_update_fallback) + || !empty($jquery_update_ui_fallback) + || !empty($jquery_migrate_fallback) + ) { + // Add in the advagg_fallback() function so it's available inline. + $inline_array['group'] = '-150'; + $inline_array['weight'] += -10; + $inline_array['data'] = 'function advagg_fallback(file){var head = document.getElementsByTagName("head")[0];var script = document.createElement("script");script.src = file;script.type = "text/javascript";head.appendChild(script);};'; + $inline_array['scope_lock'] = TRUE; + $inline_array['movable'] = FALSE; + $inline_array['no_defer'] = TRUE; + $inline_array['scope'] = 'header'; + $js['advagg_fallback'] = $inline_array; + } + } + + // Make all scripts defer and/or async. + $hold_ready = FALSE; + foreach ($js as $name => &$values) { + // Skip if not a file or external. + if ($values['type'] !== 'file' && $values['type'] !== 'external') { + continue; + } + + // Everything is defer. + if (variable_get('advagg_mod_js_defer', ADVAGG_MOD_JS_DEFER) && empty($values['nodefer'])) { + $values['defer'] = TRUE; + } + // Everything is async. + if (variable_get('advagg_mod_js_async', ADVAGG_MOD_JS_ASYNC) && empty($values['noasync'])) { + $values['async'] = TRUE; + } + + // Special handling for jQuery. + if (stripos(strrev($name), $jquery_rev) === 0 + || stripos(strrev($name), $jquery_min_rev) === 0 + ) { + $jquery_deferred = TRUE; + // Do not fire jQuery.ready until Drupal.settings has been defined. + if (empty($hold_ready)) { + if (!empty($GLOBALS['advagg_mod_loadcss_jquery_holdready'])) { + $values['onload'] = "if(jQuery.isFunction(jQuery.holdReady)){jQuery.holdReady(true);jQuery.holdReady(true);}"; + } + else { + $values['onload'] = "if(jQuery.isFunction(jQuery.holdReady)){jQuery.holdReady(true);}"; + } + + $hold_ready = TRUE; + } + // jquery_update fallback. + if (module_exists('jquery_update') && variable_get('jquery_update_jquery_cdn', 'none') !== 'none') { + if ($use_on_error) { + if (!isset($values['onerror'])) { + $values['onerror'] = ''; + } + $values['onerror'] .= "advagg_fallback('{$jquery_update_fallback}');"; + } + // Do not defer/async the loading of jquery.js. + if (!variable_get('advagg_mod_js_defer_jquery', ADVAGG_MOD_JS_DEFER_JQUERY) !== FALSE + && variable_get('advagg_mod_js_defer_inline_alter', ADVAGG_MOD_JS_DEFER_INLINE_ALTER) + && variable_get('jquery_update_jquery_cdn', 'none') !== 'none' + ) { + if (variable_get('advagg_mod_js_defer', ADVAGG_MOD_JS_DEFER)) { + $values['defer'] = FALSE; + $jquery_deferred = FALSE; + } + } + if (variable_get('advagg_mod_js_async', ADVAGG_MOD_JS_ASYNC)) { + $values['async'] = FALSE; + } + // Defer/async is off; done with loop. + continue; + } + } + // Special handling for jQuery migrate. + if (stripos($name, '/jquery-migrate') !== FALSE) { + // jquery_update ui fallback. + if (module_exists('jquery_update') && variable_get('jquery_update_jquery_migrate_cdn', 'none') !== 'none') { + if ($use_on_error) { + if (!isset($values['onerror'])) { + $values['onerror'] = ''; + } + $values['onerror'] .= "advagg_fallback('{$jquery_migrate_fallback}');"; + } + // Do not defer/async the loading of jquery-migrate. + if (!variable_get('advagg_mod_js_defer_jquery', ADVAGG_MOD_JS_DEFER_JQUERY) !== FALSE + && variable_get('advagg_mod_js_defer_inline_alter', ADVAGG_MOD_JS_DEFER_INLINE_ALTER) + && variable_get('jquery_update_jquery_cdn', 'none') !== 'none' + ) { + if (variable_get('advagg_mod_js_defer', ADVAGG_MOD_JS_DEFER)) { + $values['defer'] = FALSE; + } + } + if (variable_get('advagg_mod_js_async', ADVAGG_MOD_JS_ASYNC)) { + $values['async'] = FALSE; + } + // Defer/async is off; done with loop. + continue; + } + } + // Special handling for jQuery UI. + if (stripos(strrev($name), $jquery_ui_rev) === 0 + || stripos(strrev($name), $jquery_ui_min_rev) === 0 + ) { + // jquery_update ui fallback. + if (module_exists('jquery_update') && variable_get('jquery_update_jquery_cdn', 'none') !== 'none') { + if ($use_on_error) { + if (!isset($values['onerror'])) { + $values['onerror'] = ''; + } + $values['onerror'] .= "advagg_fallback('{$jquery_update_ui_fallback}');"; + } + // Do not defer/async the loading of jquery-ui.js. + if (!variable_get('advagg_mod_js_defer_jquery', ADVAGG_MOD_JS_DEFER_JQUERY) !== FALSE + && variable_get('advagg_mod_js_defer_inline_alter', ADVAGG_MOD_JS_DEFER_INLINE_ALTER) + && variable_get('jquery_update_jquery_cdn', 'none') !== 'none' + ) { + if (variable_get('advagg_mod_js_defer', ADVAGG_MOD_JS_DEFER)) { + $values['defer'] = FALSE; + } + } + if (variable_get('advagg_mod_js_async', ADVAGG_MOD_JS_ASYNC)) { + $values['async'] = FALSE; + } + // Defer/async is off; done with loop. + continue; + } + } + + // Drupal settings; don't run until misc/drupal.js has ran. + if ($name === 'misc/drupal.js') { + // Initialize the Drupal.settings JavaScript object after this has + // loaded. + if (!isset($values['onload'])) { + $values['onload'] = ''; + } + $matches[0] = $matches[2] = 'init_drupal_core_settings();'; + $values['onload'] .= advagg_mod_wrap_inline_js($matches, "window.init_drupal_core_settings && window.jQuery && window.Drupal", 1); + } + + // No async defer list. + foreach ($no_async_defer_list as $search_string) { + if (strpos($name, $search_string) !== FALSE) { + // Do not defer/async the loading this script. + if (variable_get('advagg_mod_js_defer', ADVAGG_MOD_JS_DEFER)) { + $values['defer'] = FALSE; + } + if (variable_get('advagg_mod_js_async', ADVAGG_MOD_JS_ASYNC)) { + $values['async'] = FALSE; + } + } + } + + // Do not defer external scripts setting. + if ($defer_setting == 2 && $values['type'] === 'external') { + $values['defer'] = FALSE; + } + + } + unset($values); + + // Inline script special handling. + foreach ($js as &$values) { + if ($values['type'] !== 'inline') { + continue; + } + foreach ($inline_wrapper_list as $search_string => $js_condition) { + if (strpos($values['data'], $search_string) !== FALSE) { + $matches[0] = $matches[2] = $values['data']; + $values['data'] = advagg_mod_wrap_inline_js($matches, $js_condition); + } + } + } + unset($values); + + return $jquery_deferred; +} + +/** + * Defer inline js by using setTimeout. + * + * @param array $js + * JS array. + * @param bool $jquery_deferred + * TRUE if jquery is deferred. + */ +function advagg_mod_inline_defer(array &$js, $jquery_deferred) { + if ($jquery_deferred) { + $bootstrap_rev = strrev('/bootstrap.js'); + $bootstrap_min_rev = strrev('/bootstrap.min.js'); + foreach ($js as &$values) { + // Defer bootstrap if jquery is deferred. + if (is_string($values['data']) + && (stripos(strrev($values['data']), $bootstrap_rev) === 0 + || stripos(strrev($values['data']), $bootstrap_min_rev) === 0 + )) { + $values['defer'] = TRUE; + continue; + } + + // Only do inline. + if ($values['type'] === 'inline') { + // 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 inline scripts by wrapping the code in setTimeout callback. + $matches[2] = $matches[0] = $values['data']; + $values['data'] = advagg_mod_wrap_inline_js($matches); + } + elseif (stripos($values['data'], 'jQuery.') !== FALSE || stripos($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)) { + $jquery_rev = strrev('/jquery.js'); + $jquery_min_rev = strrev('/jquery.min.js'); + 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 (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. + * + * @param array $elements + * A render array containing: + * - #items: The JavaScript items as returned by drupal_add_js() and + * altered by drupal_get_js(). + * - #group_callback: A function to call to group #items. Following + * this function, #aggregate_callback is called to aggregate items within + * the same group into a single file. + * - #aggregate_callback: A function to call to aggregate the items within + * the groups arranged by the #group_callback function. + * + * @return array + * A render array that will render to a string of JavaScript tags. + * + * @see drupal_get_js() + */ +function _advagg_mod_pre_render_scripts(array $elements) { + if (advagg_mod_inline_page() || advagg_mod_inline_page_js()) { + advagg_mod_inline_js($elements['#items']); + } + + return $elements; +} + +/** + * A #pre_render callback to inline all CSS on this page. + * + * @param array $elements + * A render array containing: + * - '#items': The CSS items as returned by drupal_add_css() and altered by + * drupal_get_css(). + * - '#group_callback': A function to call to group #items to enable the use + * of fewer tags by aggregating files and/or using multiple @import + * statements within a single tag. + * - '#aggregate_callback': A function to call to aggregate the items within + * the groups arranged by the #group_callback function. + * + * @return array + * A render array that will render to a string of XHTML CSS tags. + * + * @see drupal_get_css() + */ +function _advagg_mod_pre_render_styles(array $elements) { + if (!module_exists('advagg') || !advagg_enabled()) { + return $elements; + } + + if (advagg_mod_inline_page() || advagg_mod_inline_page_css()) { + advagg_mod_inline_css($elements['#items']); + } + elseif (variable_get('advagg_mod_css_defer_skip_first_file', ADVAGG_MOD_CSS_DEFER_SKIP_FIRST_FILE) == 4) { + list(, , , , , , , , , , $css_defer) = advagg_mod_get_lists(array(), $elements['#items']); + $css_defer_admin = variable_get('advagg_mod_css_defer_admin', ADVAGG_MOD_CSS_DEFER_ADMIN); + if (advagg_mod_css_defer_page() + && !empty($css_defer) + && (!empty($css_defer_admin) || !path_is_admin(current_path())) + ) { + advagg_mod_inline_css($elements['#items'], 0, variable_get('advagg_mod_css_defer_inline_size_limit', ADVAGG_MOD_CSS_DEFER_INLINE_SIZE_LIMIT)); + } + } + + return $elements; +} + +/** + * Use DOMDocument's loadHTML along with DOMXPath's query to find script tags. + * + * Once found, it will also wrap them in a javascript loader function. + * + * @param string $html + * HTML fragments. + * + * @return string + * The HTML fragment with less markup errors and script tags wrapped. + */ +function advagg_mod_xpath_script_wrapper($html) { + // Do not throw errors when parsing the html. + libxml_use_internal_errors(TRUE); + + $dom = new DOMDocument(); + // Load html with full tags all around. + $dom->loadHTML('<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd"> +<html><head><meta http-equiv="content-type" content="text/html; charset=utf-8"></head><body>' . $html . '</body></html>'); + $xpath = new DOMXPath($dom); + // Get all script tags that + // are not inside of a textarea + // do not contain a src attribute + // and the type is empty or has the type of javascript. + $nodes = $xpath->query("//script[not(@src)][not(ancestor::textarea)][contains(translate(@type, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'), 'javascript') or not(@type)]"); + + foreach ($nodes as $node) { + $matches[2] = $node->nodeValue; + // $matches[0] = $dom->saveHTML($node); + $matches[0] = $node->nodeValue; + $new_html = advagg_mod_wrap_inline_js($matches); + $advagg = $dom->createElement('script'); + $advagg->appendchild($dom->createTextNode($new_html)); + $node->parentNode->replaceChild($advagg, $node); } - unset($values); + // Render to HTML. + $output = $dom->saveHTML(); + + // Remove the tags we added. + $output = str_replace(array( + '<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd"> +<html><head><meta http-equiv="content-type" content="text/html; charset=utf-8"></head><body>', + '</body></html>', + ), array('', ''), $output); + + // Clear any errors. + libxml_clear_errors(); + return $output; } /** - * Implements hook_advagg_get_root_files_dir_alter(). + * Use DOMDocument's loadHTML along with DOMXPath's query to find script tags. + * + * Once found, it will add the src to the dns prefetch list. + * + * @param string $html + * HTML fragments. */ -function advagg_mod_advagg_get_root_files_dir_alter(&$css_paths, &$js_paths) { - $dir = rtrim(variable_get('advagg_mod_unified_multisite_dir', ''), '/'); - if (empty($dir) || !file_exists($dir) || !is_dir($dir)) { - return; - } - // Change directory. - $css_paths[0] = $dir . '/advagg_css'; - $js_paths[0] = $dir . '/advagg_js'; +function advagg_mod_xpath_script_external_dns($html) { + // Do not throw errors when parsing the html. + libxml_use_internal_errors(TRUE); - file_prepare_directory($css_paths[0], FILE_CREATE_DIRECTORY); - file_prepare_directory($js_paths[0], FILE_CREATE_DIRECTORY); + $dom = new DOMDocument(); + // Load html with full tags all around. + $dom->loadHTML('<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd"> +<html><head><meta http-equiv="content-type" content="text/html; charset=utf-8"></head><body>' . $html . '</body></html>'); + $xpath = new DOMXPath($dom); + // Get all script tags that + // are not inside of a textarea + // have a src attribute. + $nodes = $xpath->query("//script[@src][not(ancestor::textarea)]"); - // Set the URI of the directory. - $css_paths[1] = advagg_get_relative_path($css_paths[0]); - $js_paths[1] = advagg_get_relative_path($js_paths[0]); + // Add the src attribute to dns-prefetch. + foreach ($nodes as $node) { + advagg_add_dns_prefetch($node->attributes->getNamedItem('src')->nodeValue); + } + + // Clear any errors. + libxml_clear_errors(); } /** - * Implements hook_advagg_current_hooks_hash_array_alter(). + * Callback for preg_replace_callback. + * + * Used to wrap inline JS in a function in order to defer the inline js code. + * + * @param string $input + * JavaScript code to wrap in setTimeout. + * + * @return string + * Inline javascript code wrapped up in a loader. */ -function advagg_mod_advagg_current_hooks_hash_array_alter(&$aggregate_settings) { - // JS Settings. - $aggregate_settings['variables']['advagg_mod_js_async_shim'] = variable_get('advagg_mod_js_async_shim', ADVAGG_MOD_JS_ASYNC_SHIM); - - // Make safe if using the aggressive cache. - if (variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL) >= 5) { - $aggregate_settings['variables']['advagg_mod_js_preprocess'] = variable_get('advagg_mod_js_preprocess', ADVAGG_MOD_JS_PREPROCESS); - $aggregate_settings['variables']['advagg_mod_js_remove_unused'] = variable_get('advagg_mod_js_remove_unused', ADVAGG_MOD_JS_REMOVE_UNUSED); - $aggregate_settings['variables']['advagg_mod_js_head_extract'] = variable_get('advagg_mod_js_head_extract', ADVAGG_MOD_JS_HEAD_EXTRACT); - $aggregate_settings['variables']['advagg_mod_js_adjust_sort_external'] = variable_get('advagg_mod_js_adjust_sort_external', ADVAGG_MOD_JS_ADJUST_SORT_EXTERNAL); - $aggregate_settings['variables']['advagg_mod_js_adjust_sort_inline'] = variable_get('advagg_mod_js_adjust_sort_inline', ADVAGG_MOD_JS_ADJUST_SORT_INLINE); - $aggregate_settings['variables']['advagg_mod_js_adjust_sort_browsers'] = variable_get('advagg_mod_js_adjust_sort_browsers', ADVAGG_MOD_JS_ADJUST_SORT_BROWSERS); - $aggregate_settings['variables']['advagg_mod_ga_inline_to_file'] = variable_get('advagg_mod_ga_inline_to_file', ADVAGG_MOD_GA_INLINE_TO_FILE); - $aggregate_settings['variables']['advagg_mod_js_footer'] = variable_get('advagg_mod_js_footer', ADVAGG_MOD_JS_FOOTER); - $aggregate_settings['variables']['advagg_mod_js_defer'] = variable_get('advagg_mod_js_defer', ADVAGG_MOD_JS_DEFER); - $aggregate_settings['variables']['advagg_mod_js_footer_inline_alter'] = variable_get('advagg_mod_js_footer_inline_alter', ADVAGG_MOD_JS_FOOTER_INLINE_ALTER); - $aggregate_settings['variables']['advagg_mod_js_async'] = variable_get('advagg_mod_js_async', ADVAGG_MOD_JS_ASYNC); +function advagg_mod_defer_inline_js($input) { + if (variable_get('advagg_mod_js_defer_jquery', ADVAGG_MOD_JS_DEFER_JQUERY) !== FALSE + && variable_get('jquery_update_jquery_cdn', 'none') !== 'none' + && (stripos($input, 'jQuery') !== FALSE + || strpos($input, '$(') !== FALSE + || stripos($input, 'Drupal.') !== FALSE + ) + ) { + $matches[2] = $matches[0] = $input; + return advagg_mod_wrap_inline_js($matches); } - // CSS Settings. - $aggregate_settings['variables']['advagg_mod_css_translate'] = variable_get('advagg_mod_css_translate', ADVAGG_MOD_CSS_TRANSLATE); - if (variable_get('advagg_mod_css_translate', ADVAGG_MOD_CSS_TRANSLATE)) { - $aggregate_settings['variables']['advagg_mod_css_translate_lang'] = isset($GLOBALS['language']->language) ? $GLOBALS['language']->language : 'en'; + // Get inline defer js skip list. + list(, , , , , $inline_js_defer_skip_list) = advagg_mod_get_lists(); + if (!empty($inline_js_defer_skip_list)) { + // If the line is on the skip list then do not inline the script. + foreach ($inline_js_defer_skip_list as $string_to_check) { + if (stripos($input, $string_to_check) !== FALSE) { + return $input; + } + } } - // Make safe if using the aggressive cache. - if (variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL) >= 5) { - $aggregate_settings['variables']['advagg_mod_css_preprocess'] = variable_get('advagg_mod_css_preprocess', ADVAGG_MOD_CSS_PREPROCESS); - $aggregate_settings['variables']['advagg_mod_css_head_extract'] = variable_get('advagg_mod_css_head_extract', ADVAGG_MOD_CSS_HEAD_EXTRACT); - $aggregate_settings['variables']['advagg_mod_css_adjust_sort_external'] = variable_get('advagg_mod_css_adjust_sort_external', ADVAGG_MOD_CSS_ADJUST_SORT_EXTERNAL); - $aggregate_settings['variables']['advagg_mod_css_adjust_sort_inline'] = variable_get('advagg_mod_css_adjust_sort_inline', ADVAGG_MOD_CSS_ADJUST_SORT_INLINE); - $aggregate_settings['variables']['advagg_mod_css_adjust_sort_browsers'] = variable_get('advagg_mod_css_adjust_sort_browsers', ADVAGG_MOD_CSS_ADJUST_SORT_BROWSERS); - $aggregate_settings['variables']['advagg_mod_css_defer'] = variable_get('advagg_mod_css_defer', ADVAGG_MOD_CSS_DEFER); - $aggregate_settings['variables']['advagg_mod_css_defer_js_code'] = variable_get('advagg_mod_css_defer_js_code', ADVAGG_MOD_CSS_DEFER_JS_CODE); - $aggregate_settings['variables']['advagg_mod_inline_visibility'] = variable_get('advagg_mod_inline_visibility', ADVAGG_MOD_VISIBILITY_LISTED); - $aggregate_settings['variables']['advagg_mod_inline_pages'] = variable_get('advagg_mod_inline_pages', ''); - } + // Use a counter in order to create unique function names. + static $counter; + ++$counter; + + // JS wrapper code. + $new = " +function advagg_mod_defer_${counter}() { + ${input}; +} +window.setTimeout(advagg_mod_defer_${counter}, 0);"; + + return $new; } -// Helper Functions. /** * Callback for preg_replace_callback. * @@ -899,15 +2917,29 @@ function advagg_mod_advagg_current_hooks_hash_array_alter(&$aggregate_settings) * Inline javascript code wrapped up in a loader to prevent errors. */ function advagg_mod_wrap_inline_js(array $matches, $check_string = NULL, $ms_wait = 250) { + list(, , , $inline_wrapper_list, $inline_js_wrap_skip_list) = advagg_mod_get_lists(); + if (empty($check_string)) { - $check_string = 'window.jQuery && window.Drupal && window.Drupal.settings'; + foreach ($inline_wrapper_list as $search_string => $js_condition) { + if (strpos($matches[2], $search_string) !== FALSE) { + $check_string = $js_condition; + break; + } + } + if (empty($check_string)) { + $check_string = 'window.jQuery && window.Drupal && window.Drupal.settings'; + } } - // Get inline js skip list string and convert it to an array. - $inline_js_skip_list = array_filter(array_map('trim', explode("\n", variable_get('advagg_mod_wrap_inline_js_skip_list', '')))); - if (!empty($inline_js_skip_list)) { + // Always wrap inline if it contains jquery or drupal. + if (!empty($inline_js_wrap_skip_list) + && stripos($matches[2], '(jQuery') === FALSE + && stripos($matches[2], 'jQuery.') === FALSE + && stripos($matches[2], '(Drupal') === FALSE + && stripos($matches[2], 'Drupal.') === FALSE + ) { // If the line is on the skip list then do not inline the script. - foreach ($inline_js_skip_list as $string_to_check) { + foreach ($inline_js_wrap_skip_list as $string_to_check) { if (stripos($matches[2], $string_to_check) !== FALSE) { return $matches[0]; } @@ -969,8 +3001,7 @@ advagg_mod_${counter}_check();"; * String: css or js. */ function advagg_mod_sort_css_js(array &$array, $type) { - // @ignore sniffer_whitespace_openbracketspacing_openingwhitespace:3 - if ( ($type === 'js' && variable_get('advagg_mod_js_adjust_sort_external', ADVAGG_MOD_JS_ADJUST_SORT_EXTERNAL)) + if (($type === 'js' && variable_get('advagg_mod_js_adjust_sort_external', ADVAGG_MOD_JS_ADJUST_SORT_EXTERNAL)) || ($type === 'css' && variable_get('advagg_mod_css_adjust_sort_external', ADVAGG_MOD_CSS_ADJUST_SORT_EXTERNAL)) ) { // Find all external items. @@ -991,13 +3022,13 @@ function advagg_mod_sort_css_js(array &$array, $type) { } // Find "lightest" item. - if ($value['group'] < $group) { + if (isset($value['group']) && $value['group'] < $group) { $group = $value['group']; } - if ($value['every_page'] && !$every_page) { + if (!empty($value['every_page']) && !$every_page) { $every_page = $value['every_page']; } - if ($value['weight'] < $weight) { + if (isset($value['weight']) && $value['weight'] < $weight) { $weight = $value['weight']; } @@ -1033,7 +3064,7 @@ function advagg_mod_sort_css_js(array &$array, $type) { } // If bootstrap is used, it must be loaded after jquery. Don't move // bootstrap if jquery is not above it. - if ( strpos($value['data'], 'jquery.min.js') !== FALSE + if (strpos($value['data'], 'jquery.min.js') !== FALSE || strpos($value['data'], 'jquery.js') !== FALSE ) { $found_jquery = TRUE; @@ -1050,8 +3081,7 @@ function advagg_mod_sort_css_js(array &$array, $type) { } } - // @ignore sniffer_whitespace_openbracketspacing_openingwhitespace:3 - if ( ($type === 'js' && variable_get('advagg_mod_js_adjust_sort_inline', ADVAGG_MOD_JS_ADJUST_SORT_INLINE)) + if (($type === 'js' && variable_get('advagg_mod_js_adjust_sort_inline', ADVAGG_MOD_JS_ADJUST_SORT_INLINE)) || ($type === 'css' && variable_get('advagg_mod_css_adjust_sort_inline', ADVAGG_MOD_CSS_ADJUST_SORT_INLINE)) ) { // Find all inline items. @@ -1072,13 +3102,13 @@ function advagg_mod_sort_css_js(array &$array, $type) { } // Find "heaviest" item. - if ($value['group'] > $group) { + if (isset($value['group']) && $value['group'] > $group) { $group = $value['group']; } - if (!$value['every_page'] && $every_page) { - $every_page = $value['every_page']; + if (empty($value['every_page']) && $every_page) { + $every_page = FALSE; } - if ($value['weight'] > $weight) { + if (isset($value['weight']) && $value['weight'] > $weight) { $weight = $value['weight']; } @@ -1114,8 +3144,7 @@ function advagg_mod_sort_css_js(array &$array, $type) { } } - // @ignore sniffer_whitespace_openbracketspacing_openingwhitespace:3 - if ( ($type === 'js' && variable_get('advagg_mod_js_adjust_sort_browsers', ADVAGG_MOD_JS_ADJUST_SORT_BROWSERS)) + if (($type === 'js' && variable_get('advagg_mod_js_adjust_sort_browsers', ADVAGG_MOD_JS_ADJUST_SORT_BROWSERS)) || ($type === 'css' && variable_get('advagg_mod_css_adjust_sort_browsers', ADVAGG_MOD_CSS_ADJUST_SORT_BROWSERS)) ) { // Get a list of browsers. @@ -1185,10 +3214,10 @@ function advagg_mod_sort_css_js(array &$array, $type) { } /** - * Returns TRUE if this page should have inline CSS/JS. + * Returns TRUE if this page should have inline CSS and JS. * * @return bool - * TRUE or FALSE. + * TRUE or FALSE. Default is FALSE. */ function advagg_mod_inline_page() { $visibility = variable_get('advagg_mod_inline_visibility', ADVAGG_MOD_VISIBILITY_LISTED); @@ -1196,6 +3225,42 @@ function advagg_mod_inline_page() { return advagg_mod_match_path($pages, $visibility); } +/** + * Returns TRUE if this page should have inline CSS. + * + * @return bool + * TRUE or FALSE. Default is FALSE. + */ +function advagg_mod_inline_page_css() { + $visibility = variable_get('advagg_mod_inline_css_visibility', ADVAGG_MOD_VISIBILITY_LISTED); + $pages = variable_get('advagg_mod_inline_css_pages', ''); + return advagg_mod_match_path($pages, $visibility); +} + +/** + * Returns TRUE if this page should have inline JS. + * + * @return bool + * TRUE or FALSE. Default is FALSE. + */ +function advagg_mod_inline_page_js() { + $visibility = variable_get('advagg_mod_inline_js_visibility', ADVAGG_MOD_VISIBILITY_LISTED); + $pages = variable_get('advagg_mod_inline_js_pages', ''); + return advagg_mod_match_path($pages, $visibility); +} + +/** + * Returns TRUE if this page should have critical CSS inlined. + * + * @return bool + * TRUE or FALSE. Default is FALSE. + */ +function advagg_mod_css_defer_page() { + $visibility = variable_get('advagg_mod_css_defer_visibility', ADVAGG_MOD_VISIBILITY_LISTED); + $pages = variable_get('advagg_mod_css_defer_pages', ''); + return advagg_mod_match_path($pages, $visibility); +} + /** * Transforms all JS files into inline JS. * @@ -1212,7 +3277,7 @@ function advagg_mod_inline_js(array &$js) { } $filename = $values['data']; if (file_exists($filename)) { - $contents = file_get_contents($filename); + $contents = (string) @advagg_file_get_contents($filename); } // Allow other modules to modify this files contents. // Call hook_advagg_get_js_file_contents_alter(). @@ -1229,15 +3294,21 @@ function advagg_mod_inline_js(array &$js) { * * @param array $css * CSS array. + * @param int $file_limit + * The number of files to inline; 0 means no limit. + * @param int $size_limit + * The number of bytes to inline; 0 means no limit. * * @see advagg_get_css_aggregate_contents() * @see drupal_build_css_cache() */ -function advagg_mod_inline_css(array &$css) { +function advagg_mod_inline_css(array &$css, $file_limit = 0, $size_limit = 0) { $aggregate_settings = advagg_current_hooks_hash_array(); $optimize = TRUE; module_load_include('inc', 'advagg', 'advagg'); + $count = 0; + $size = 0; foreach ($css as &$values) { // Only process files. if ($values['type'] !== 'file') { @@ -1246,6 +3317,9 @@ function advagg_mod_inline_css(array &$css) { $file = $values['data']; if (file_exists($file)) { + if (!empty($file_limit) && $count > $file_limit) { + break; + } $contents = advagg_load_css_stylesheet($file, $optimize, $aggregate_settings); // Allow other modules to modify this files contents. @@ -1260,8 +3334,14 @@ function advagg_mod_inline_css(array &$css) { $contents = preg_replace($regexp, '', $contents); $contents = implode('', $matches[0]) . $contents; + $size += strlen($contents); + if (!empty($size_limit) && $size > $size_limit) { + break; + } + $values['data'] = $contents; $values['type'] = 'inline'; + $count++; } } unset($values); @@ -1281,22 +3361,33 @@ function advagg_mod_inline_css(array &$css) { * @see block_block_list_alter() */ function advagg_mod_match_path($pages, $visibility) { + // Default to not matching. + $page_match = FALSE; // Limited visibility blocks must list at least one page. - if ($visibility == ADVAGG_MOD_VISIBILITY_LISTED && empty($pages)) { - $page_match = FALSE; - } - elseif ($pages) { - // Match path if necessary. - // Convert path to lowercase. This allows comparison of the same path - // with different case. Ex: /Page, /page, /PAGE. - $pages = drupal_strtolower($pages); - if ($visibility < ADVAGG_MOD_VISIBILITY_PHP) { + if (empty($pages) && $visibility <= ADVAGG_MOD_VISIBILITY_PHP) { + if ($visibility == ADVAGG_MOD_VISIBILITY_NOTLISTED) { + $page_match = TRUE; + } + } + else { + // Match on php. + if ($visibility == ADVAGG_MOD_VISIBILITY_PHP) { + if (module_exists('php')) { + $page_match = php_eval($pages); + } + } + // Match the given $pages. + elseif ($visibility < ADVAGG_MOD_VISIBILITY_PHP) { + $current_path = current_path(); + // Convert path to lowercase. This allows comparison of the same path + // with different case. Ex: /Page, /page, /PAGE. + $pages = drupal_strtolower($pages); // Convert the Drupal path to lowercase. - $path = drupal_strtolower(drupal_get_path_alias($_GET['q'])); + $path = drupal_strtolower(drupal_get_path_alias($current_path)); // Compare the lowercase internal and lowercase path alias (if any). $page_match = drupal_match_path($path, $pages); - if ($path != $_GET['q']) { - $page_match = $page_match || drupal_match_path($_GET['q'], $pages); + if ($path != $current_path) { + $page_match = $page_match || drupal_match_path($current_path, $pages); } // When $visibility has a value of 0 (ADVAGG_MOD_VISIBILITY_NOTLISTED), // the block is displayed on all pages except those listed in $pages. @@ -1304,15 +3395,6 @@ function advagg_mod_match_path($pages, $visibility) { // those pages listed in $block->pages. $page_match = !($visibility xor $page_match); } - elseif (module_exists('php')) { - $page_match = php_eval($pages); - } - else { - $page_match = FALSE; - } - } - else { - $page_match = TRUE; } return $page_match; @@ -1334,8 +3416,7 @@ function advagg_mod_js_contains_jquery_drupal($filename, $type = '') { if ($type === 'inline') { $contents = $filename; } - // @ignore sniffer_whitespace_openbracketspacing_openingwhitespace:1 - elseif ( $type === 'external' + elseif ($type === 'external' || strpos($filename, 'http://') === 0 || strpos($filename, 'https://') === 0 || strpos($filename, '//') === 0 @@ -1346,7 +3427,7 @@ function advagg_mod_js_contains_jquery_drupal($filename, $type = '') { } } elseif (file_exists($filename)) { - $contents = file_get_contents($filename); + $contents = (string) @advagg_file_get_contents($filename); } } @@ -1388,27 +3469,49 @@ function advagg_mod_js_contains_jquery_drupal($filename, $type = '') { */ function advagg_mod_ga_inline_to_file(array &$js) { // Do nothing if the googleanalytics module is not enabled. - if (!module_exists('googleanalytics')) { + if (!module_exists('googleanalytics') + || !is_callable('googleanalytics_api') + || !is_callable('_googleanalytics_cache') + ) { return; } // 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)) { foreach ($js as $key => $value) { @@ -1416,22 +3519,41 @@ function advagg_mod_ga_inline_to_file(array &$js) { 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; } } } @@ -1442,12 +3564,7 @@ function advagg_mod_ga_inline_to_file(array &$js) { * @param array $js * JS array. */ -function advagg_remove_js_if_not_used(array &$js) { - // Do not run the following code if drupal_add_js_page_defaults exists. - if (function_exists('drupal_add_js_page_defaults')) { - return; - } - +function advagg_mod_remove_js_if_not_used(array &$js) { $files_skiplist = array( 'drupal.js', 'jquery.js', @@ -1466,23 +3583,23 @@ function advagg_remove_js_if_not_used(array &$js) { $include_drupal = FALSE; module_load_include('inc', 'advagg', 'advagg'); - // Look at each JavaScript entry & get the info on it. + // Look at each JavaScript entry and get the info on it. $files_info_filenames = array(); foreach ($js as &$values) { - if ($values['type'] === 'file' || $values['type'] === 'external') { - foreach ($files_skiplist as $skip_name) { - if (substr_compare($values['data'], $skip_name, -strlen($skip_name), strlen($skip_name)) === 0) { - continue 2; - } + if ($values['type'] !== 'file' || !is_string($values['data'])) { + continue; + } + foreach ($files_skiplist as $skip_name) { + if (strlen($skip_name) < strlen($values['data']) && substr_compare($values['data'], $skip_name, -strlen($skip_name), strlen($skip_name)) === 0) { + continue 2; } - $files_info_filenames[] = $values['data']; - } + $files_info_filenames[] = $values['data']; } unset($values); $files_info = advagg_get_info_on_files($files_info_filenames); - // Look at each JavaScript entry & see if it uses jquery or drupal. + // Look at each JavaScript entry and see if it uses jquery or drupal. foreach ($js as $name => &$values) { if ($values['type'] === 'file' || $values['type'] === 'external') { foreach ($files_skiplist as $skip_name) { @@ -1498,15 +3615,21 @@ function advagg_remove_js_if_not_used(array &$js) { } } } - // Get advagg_mod info if not set. - if (!isset($files_info[$name]['advagg_mod'])) { + // Get advagg_mod info if not set; skip external files. + if (!isset($files_info[$name]['advagg_mod']) && $values['type'] !== 'external') { $files_info[$name]['advagg_mod'] = advagg_mod_js_contains_jquery_drupal($values['data'], $values['type']); } - if ($files_info[$name]['advagg_mod']['contents']['drupal']) { + + // See what needs to be included. + if (!empty($files_info[$name]['advagg_mod']['contents']['drupal'])) { $include_jquery = TRUE; $include_drupal = TRUE; + break; + } + elseif (!empty($files_info[$name]['advagg_mod']['contents']['jquery'])) { + $include_jquery = TRUE; } - elseif ($files_info[$name]['advagg_mod']['contents']['jquery']) { + elseif (isset($values['requires_jquery']) && !empty($values['requires_jquery'])) { $include_jquery = TRUE; } } @@ -1546,62 +3669,189 @@ function advagg_remove_js_if_not_used(array &$js) { } } -// @ignore sniffer_commenting_functioncomment_hookreturndoc:12 -// @ignore sniffer_commenting_functioncomment_hookparamdoc:8 /** - * Implements hook_magic(). - * - * @param array $magic_settings - * The renderable form array of the magic module theme settings. READ ONLY. - * @param string $theme - * The theme that the settings will be editing. + * Given html, do some processing on the script tags included inside it. * - * @return array - * The array of settings within the magic module theme page. Must not contain - * anything from the $magic_settings array. + * @param string $html + * String containing html markup. */ -function advagg_mod_magic(array $magic_settings, $theme) { - $settings = array(); - - // If possible disable access and set default to false. - if (!isset($magic_settings['css']['magic_embedded_mqs']['#access'])) { - $settings['css']['magic_embedded_mqs']['#access'] = FALSE; +function advagg_mod_js_inline_processor(&$html) { + // Add src script to dns-prefetch. + if (variable_get('advagg_mod_js_inline_resource_hints', ADVAGG_MOD_JS_INLINE_RESOURCE_HINTS)) { + advagg_mod_xpath_script_external_dns($html); } - if (!isset($magic_settings['css']['magic_embedded_mqs']['#default_value'])) { - $settings['css']['magic_embedded_mqs']['#default_value'] = FALSE; + + // Setting is enabled. + // All JS is in the footer. + // OR All JS is defer. + // OR All JS is async. + if (variable_get('advagg_mod_js_footer_inline_alter', ADVAGG_MOD_JS_FOOTER_INLINE_ALTER) + && (variable_get('advagg_mod_js_footer', ADVAGG_MOD_JS_FOOTER) >= 1 + || variable_get('advagg_mod_js_defer', ADVAGG_MOD_JS_DEFER) + || variable_get('advagg_mod_js_async', ADVAGG_MOD_JS_ASYNC) + )) { + $pattern = '/<script(?![^<>]*src=)(?:(?![^<>]*type=)|(?=[^<>]*type="?[^<>"]*javascript))(.*?)>(.*?)<\/script>/smix'; + $callback = 'advagg_mod_wrap_inline_js'; + // Wrap inline JS with a check so that it only runs once Drupal.settings & + // jQuery are not undefined. + if (variable_get('advagg_mod_wrap_inline_js_xpath', ADVAGG_MOD_WRAP_INLINE_JS_XPATH)) { + $html = advagg_mod_xpath_script_wrapper($html); + } + else { + $html = preg_replace_callback($pattern, $callback, $html); + } } - if (!isset($magic_settings['js']['magic_footer_js']['#access'])) { - $settings['js']['magic_footer_js']['#access'] = FALSE; +} + +/** + * 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) && is_object($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; + } } - if (!isset($magic_settings['js']['magic_footer_js']['#default_value'])) { - $settings['js']['magic_footer_js']['#default_value'] = FALSE; +} + +/** + * 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 { + // @codingStandardsIgnoreLine + $output = '</div>' . dprint_r($queries, TRUE); + } + return $output; } - if (!isset($magic_settings['js']['magic_library_head']['#access'])) { - $settings['js']['magic_library_head']['#access'] = FALSE; +} + +/** + * 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); } - if (!isset($magic_settings['js']['magic_library_head']['#default_value'])) { - $settings['js']['magic_library_head']['#default_value'] = FALSE; + + $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 (!isset($magic_settings['js']['magic_experimental_js']['#access'])) { - $settings['js']['magic_experimental_js']['#access'] = FALSE; + if (version_compare(PHP_VERSION, '5.4.0', '>=')) { + $options |= JSON_PRETTY_PRINT; } - if (!isset($magic_settings['js']['magic_experimental_js']['#default_value'])) { - $settings['js']['magic_experimental_js']['#default_value'] = FALSE; + + // 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--; } - // Add in our own validate function so we can preprocess variables before - // they are saved. - $settings['#validate'] = array('advagg_mod_magic_form_validate'); - return $settings; -} + 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); -/** - * Form validation handler. Disable certain magic settings before being saved. - */ -function advagg_mod_magic_form_validate($form, &$form_state) { - // Disable magic functionality if it is a duplicate of AdvAgg. - $form_state['values']['magic_embedded_mqs'] = 0; - $form_state['values']['magic_footer_js'] = 0; - $form_state['values']['magic_library_head'] = 0; - $form_state['values']['magic_experimental_js'] = 0; + 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 56ed03451b..45cb22b7fd 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 @@ -3,13 +3,16 @@ * Used to load CSS via JS so css doesn't block the browser. */ +/* eslint-disable no-unused-vars */ + /** * 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) { + 'use strict'; if (document.createStyleSheet) { document.createStyleSheet(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 0000000000..ee140f73df --- /dev/null +++ b/sites/all/modules/contrib/advagg/advagg_mod/cssrelpreload.js @@ -0,0 +1,112 @@ +/** + * @file + * Used to load CSS via JS so css doesn't block the browser. + */ + +/* @codingStandardsIgnoreFile */ +/* eslint-disable */ + +/*! loadCSS. [c]2017 Filament Group, Inc. MIT License */ +/* This file is meant as a standalone workflow for +- testing support for link[rel=preload] +- enabling async CSS loading in browsers that do not support rel=preload +- applying rel preload css once loaded, whether supported or not. +*/ +(function( w ){ + "use strict"; + // rel=preload support test + if( !w.loadCSS ){ + w.loadCSS = function(){}; + } + // define on the loadCSS obj + var rp = loadCSS.relpreload = {}; + // rel=preload feature support test + // runs once and returns a function for compat purposes + rp.support = (function(){ + var ret; + try { + ret = w.document.createElement( "link" ).relList.supports( "preload" ); + } catch (e) { + ret = false; + } + return function(){ + return ret; + }; + })(); + + // if preload isn't supported, get an asynchronous load by using a non-matching media attribute + // then change that media back to its intended value on load + rp.bindMediaToggle = function( link ){ + // remember existing media attr for ultimate state, or default to 'all' + var finalMedia = link.media || "all"; + + function enableStylesheet(){ + link.media = finalMedia; + } + + // bind load handlers to enable media + if( link.addEventListener ){ + link.addEventListener( "load", enableStylesheet ); + } else if( link.attachEvent ){ + link.attachEvent( "onload", enableStylesheet ); + } + + // Set rel and non-applicable media type to start an async request + // note: timeout allows this to happen async to let rendering continue in IE + setTimeout(function(){ + link.rel = "stylesheet"; + link.media = "only x"; + }); + // also enable media after 3 seconds, + // which will catch very old browsers (android 2.x, old firefox) that don't support onload on link + setTimeout( enableStylesheet, 3000 ); + }; + + // loop through link elements in DOM + rp.poly = function(){ + // double check this to prevent external calls from running + if( rp.support() ){ + return; + } + var links = w.document.getElementsByTagName( "link" ); + for( var i = 0; i < links.length; i++ ){ + var link = links[ i ]; + // qualify links to those with rel=preload and as=style attrs + if( link.rel === "preload" && link.getAttribute( "as" ) === "style" && !link.getAttribute( "data-loadcss" ) ){ + // prevent rerunning on link + link.setAttribute( "data-loadcss", true ); + // bind listeners to toggle media back + rp.bindMediaToggle( link ); + } + } + }; + + // if unsupported, run the polyfill + if( !rp.support() ){ + // run once at least + rp.poly(); + + // rerun poly on an interval until onload + var run = w.setInterval( rp.poly, 500 ); + if( w.addEventListener ){ + w.addEventListener( "load", function(){ + rp.poly(); + w.clearInterval( run ); + } ); + } else if( w.attachEvent ){ + w.attachEvent( "onload", function(){ + rp.poly(); + w.clearInterval( run ); + } ); + } + } + + + // commonjs + if( typeof exports !== "undefined" ){ + exports.loadCSS = loadCSS; + } + else { + w.loadCSS = loadCSS; + } +}( typeof global !== "undefined" ? global : 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 0000000000..928ff479a7 --- /dev/null +++ b/sites/all/modules/contrib/advagg/advagg_mod/cssrelpreload.min.js @@ -0,0 +1,3 @@ +/*eslint-disable */ +/*! loadCSS rel=preload polyfill. [c]2017 Filament Group, Inc. MIT License */ +!function(t){"use strict";t.loadCSS||(t.loadCSS=function(){});var e=loadCSS.relpreload={};if(e.support=function(){var e;try{e=t.document.createElement("link").relList.supports("preload")}catch(t){e=!1}return function(){return e}}(),e.bindMediaToggle=function(t){function e(){t.media=a}var a=t.media||"all";t.addEventListener?t.addEventListener("load",e):t.attachEvent&&t.attachEvent("onload",e),setTimeout(function(){t.rel="stylesheet",t.media="only x"}),setTimeout(e,3e3)},e.poly=function(){if(!e.support())for(var a=t.document.getElementsByTagName("link"),n=0;n<a.length;n++){var o=a[n];"preload"!==o.rel||"style"!==o.getAttribute("as")||o.getAttribute("data-loadcss")||(o.setAttribute("data-loadcss",!0),e.bindMediaToggle(o))}},!e.support()){e.poly();var a=t.setInterval(e.poly,500);t.addEventListener?t.addEventListener("load",function(){e.poly(),t.clearInterval(a)}):t.attachEvent&&t.attachEvent("onload",function(){e.poly(),t.clearInterval(a)})}"undefined"!=typeof exports?exports.loadCSS=loadCSS:t.loadCSS=loadCSS}("undefined"!=typeof global?global:this); \ No newline at end of file diff --git a/sites/all/modules/contrib/advagg/advagg_mod/loadCSS.js b/sites/all/modules/contrib/advagg/advagg_mod/loadCSS.js index 9d547b765e..d00b0afdfe 100644 --- a/sites/all/modules/contrib/advagg/advagg_mod/loadCSS.js +++ b/sites/all/modules/contrib/advagg/advagg_mod/loadCSS.js @@ -3,54 +3,89 @@ * Used to load CSS via JS so css doesn't block the browser. */ -/*! -loadCSS: load a CSS file asynchronously. -[c]2014 @scottjehl, Filament Group, Inc. -Licensed MIT -*/ -function loadCSS( href, before, media, callback ){ +/* @codingStandardsIgnoreFile */ +/* eslint-disable */ + +/*! loadCSS. [c]2017 Filament Group, Inc. MIT License */ +(function(w){ "use strict"; - // Arguments explained: - // `href` is the URL for your CSS file. - // `before` optionally defines the element we'll use as a reference for injecting our <link> - // By default, `before` uses the first <script> element in the page. - // However, since the order in which stylesheets are referenced matters, you might need a more specific location in your document. - // If so, pass a different reference element to the `before` argument and it'll insert before that instead - // note: `insertBefore` is used instead of `appendChild`, for safety re: http://www.paulirish.com/2011/surefire-dom-element-insertion/ - var ss = window.document.createElement( "link" ); - var ref = before || window.document.getElementsByTagName( "script" )[ 0 ]; - var sheets = window.document.styleSheets; - ss.rel = "stylesheet"; - ss.href = href; - // temporarily, set media to something non-matching to ensure it'll fetch without blocking render - ss.media = "only x"; - // DEPRECATED - if( callback ) { - ss.onload = callback; - } + /* exported loadCSS */ + var loadCSS = function( href, before, media, crossorigin ){ + // Arguments explained: + // `href` [REQUIRED] is the URL for your CSS file. + // `before` [OPTIONAL] is the element the script should use as a reference for injecting our stylesheet <link> before + // By default, loadCSS attempts to inject the link after the last stylesheet or script in the DOM. However, you might desire a more specific location in your document. + // `media` [OPTIONAL] is the media type or query of the stylesheet. By default it will be 'all' + var doc = w.document; + var ss = doc.createElement( "link" ); + var ref; + if( before ){ + ref = before; + } + else { + var refs = ( doc.body || doc.getElementsByTagName( "head" )[ 0 ] ).childNodes; + ref = refs[ refs.length - 1]; + } - // inject link - ref.parentNode.insertBefore( ss, ref ); - // This function sets the link's media back to `all` so that the stylesheet applies once it loads - // It is designed to poll until document.styleSheets includes the new sheet. - ss.onloadcssdefined = function( cb ){ - var defined; - for( var i = 0; i < sheets.length; i++ ){ - if( sheets[ i ].href && sheets[ i ].href.indexOf( href ) > -1 ){ - defined = true; - } + var sheets = doc.styleSheets; + ss.rel = "stylesheet"; + ss.href = href; + if( crossorigin ){ + ss.setAttribute("crossorigin", crossorigin); } - if( defined ){ - cb(); + + // temporarily set media to something inapplicable to ensure it'll fetch without blocking render + ss.media = "only x"; + + // wait until body is defined before injecting link. This ensures a non-blocking load in IE11. + function ready( cb ){ + if( doc.body ){ + return cb(); + } + setTimeout(function(){ + ready( cb ); + }); } - else { + // Inject link + // Note: the ternary preserves the existing behavior of "before" argument, but we could choose to change the argument to "after" in a later release and standardize on ref.nextSibling for all refs + // Note: `insertBefore` is used instead of `appendChild`, for safety re: http://www.paulirish.com/2011/surefire-dom-element-insertion/ + ready( function(){ + ref.parentNode.insertBefore( ss, ( before ? ref : ref.nextSibling ) ); + }); + // A method (exposed on return object for external use) that mimics onload by polling document.styleSheets until it includes the new sheet. + var onloadcssdefined = function( cb ){ + var resolvedHref = ss.href; + var i = sheets.length; + while( i-- ){ + if( sheets[ i ].href === resolvedHref ){ + return cb(); + } + } setTimeout(function() { - ss.onloadcssdefined( cb ); + onloadcssdefined( cb ); }); + }; + + function loadCB(){ + if( ss.addEventListener ){ + ss.removeEventListener( "load", loadCB ); + } + ss.media = media || "all"; } + + // once loaded, set link's media back to `all` so that the stylesheet applies once it loads + if( ss.addEventListener ){ + ss.addEventListener( "load", loadCB); + } + ss.onloadcssdefined = onloadcssdefined; + onloadcssdefined( loadCB ); + return ss; }; - ss.onloadcssdefined(function() { - ss.media = media || "all"; - }); - return ss; -} + // commonjs + if( typeof exports !== "undefined" ){ + exports.loadCSS = loadCSS; + } + else { + w.loadCSS = loadCSS; + } +}( typeof global !== "undefined" ? global : this )); diff --git a/sites/all/modules/contrib/advagg/advagg_mod/loadCSS.min.js b/sites/all/modules/contrib/advagg/advagg_mod/loadCSS.min.js index 3ebe99eca3..f4cd4f07c1 100644 --- a/sites/all/modules/contrib/advagg/advagg_mod/loadCSS.min.js +++ b/sites/all/modules/contrib/advagg/advagg_mod/loadCSS.min.js @@ -1,2 +1,3 @@ -//[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<g.length;d++)g[d].href&&g[d].href.indexOf(a)>-1&&(c=!0);c?b():setTimeout(function(){e.onloadcssdefined(b)})},e.onloadcssdefined(function(){e.media=c||"all"}),e} \ No newline at end of file +/*eslint-disable */ +/*! loadCSS. [c]2017 Filament Group, Inc. MIT License */ +!function(a){"use strict";var b=function(b,c,d,e){function k(a){return f.body?a():void setTimeout(function(){k(a)})}function m(){g.addEventListener&&g.removeEventListener("load",m),g.media=d||"all"}var h,f=a.document,g=f.createElement("link");if(c)h=c;else{var i=(f.body||f.getElementsByTagName("head")[0]).childNodes;h=i[i.length-1]}var j=f.styleSheets;g.rel="stylesheet",g.href=b,e&&g.setAttribute("crossorigin",e),g.media="only x",k(function(){h.parentNode.insertBefore(g,c?h:h.nextSibling)});var l=function(a){for(var b=g.href,c=j.length;c--;)if(j[c].href===b)return a();setTimeout(function(){l(a)})};return g.addEventListener&&g.addEventListener("load",m),g.onloadcssdefined=l,l(m),g};"undefined"!=typeof exports?exports.loadCSS=b:a.loadCSS=b}("undefined"!=typeof global?global:this); \ No newline at end of file diff --git a/sites/all/modules/contrib/advagg/advagg_mod/onloadCSS.js b/sites/all/modules/contrib/advagg/advagg_mod/onloadCSS.js new file mode 100644 index 0000000000..c51dc0f2aa --- /dev/null +++ b/sites/all/modules/contrib/advagg/advagg_mod/onloadCSS.js @@ -0,0 +1,37 @@ +/** + * @file + * Used to run js after the CSS has been loaded. + */ + +/* @codingStandardsIgnoreFile */ +/* eslint-disable */ + +/*! onloadCSS. (onload callback for loadCSS) [c]2017 Filament Group, Inc. MIT License */ +/* global navigator */ +/* exported onloadCSS */ +function onloadCSS( ss, callback ) { + var called; + function newcb(){ + if( !called && callback ){ + called = true; + callback.call( ss ); + } + } + if( ss.addEventListener ){ + ss.addEventListener( "load", newcb ); + } + if( ss.attachEvent ){ + ss.attachEvent( "onload", newcb ); + } + + // This code is for browsers that don’t support onload + // No support for onload (it'll bind but never fire): + // * Android 4.3 (Samsung Galaxy S4, Browserstack) + // * Android 4.2 Browser (Samsung Galaxy SIII Mini GT-I8200L) + // * Android 2.3 (Pantech Burst P9070) + + // Weak inference targets Android < 4.4 + if( "isApplicationInstalled" in navigator && "onloadcssdefined" in ss ) { + ss.onloadcssdefined( newcb ); + } +} diff --git a/sites/all/modules/contrib/advagg/advagg_mod/onloadCSS.min.js b/sites/all/modules/contrib/advagg/advagg_mod/onloadCSS.min.js new file mode 100644 index 0000000000..ca2751b603 --- /dev/null +++ b/sites/all/modules/contrib/advagg/advagg_mod/onloadCSS.min.js @@ -0,0 +1,3 @@ +/*eslint-disable */ +/*! onloadCSS. (onload callback for loadCSS) [c]2017 Filament Group, Inc. MIT License */ +function onloadCSS(n,a){function t(){!d&&a&&(d=!0,a.call(n))}var d;n.addEventListener&&n.addEventListener("load",t),n.attachEvent&&n.attachEvent("onload",t),"isApplicationInstalled"in navigator&&"onloadcssdefined"in n&&n.onloadcssdefined(t)} \ No newline at end of file diff --git a/sites/all/modules/contrib/advagg/advagg_relocate/advagg_relocate.admin.inc b/sites/all/modules/contrib/advagg/advagg_relocate/advagg_relocate.admin.inc new file mode 100644 index 0000000000..b7ddf711d0 --- /dev/null +++ b/sites/all/modules/contrib/advagg/advagg_relocate/advagg_relocate.admin.inc @@ -0,0 +1,532 @@ +<?php + +/** + * @file + * Admin page callbacks for the advagg relocate module. + */ + +/** + * Form builder; Configure advagg settings. + * + * @ingroup advagg_forms + * + * @see system_settings_form() + */ +function advagg_relocate_admin_settings_form() { + drupal_set_title(t('AdvAgg: Relocate')); + advagg_display_message_if_requirements_not_met(); + + $form = array(); + $times[0] = 'No Limit'; + // @codingStandardsIgnoreStart + $times += drupal_map_assoc(array( + 60 * 60 * 12, // 12 hours. + 60 * 60 * 24, // 1 day. + 60 * 60 * 24 * 4, // 4 days. + 60 * 60 * 24 * 7, // 1 week. + 60 * 60 * 24 * 7 * 2, // 2 weeks. + 60 * 60 * 24 * 7 * 3, // 3 weeks. + 60 * 60 * 24 * 30, // 1 month. + ), 'format_interval'); + // @codingStandardsIgnoreEnd + + $options = array( + 0 => t('Use default (safe) settings'), + 2 => t('Use recommended (optimized) settings'), + 4 => t('Use customized settings'), + ); + $form['advagg_relocate_admin_mode'] = array( + '#type' => 'radios', + '#title' => t('AdvAgg Settings'), + '#default_value' => variable_get('advagg_relocate_admin_mode', ADVAGG_RELOCATE_ADMIN_MODE), + '#options' => $options, + '#description' => t("Default settings will mirror core as closely as possible. <br>Recommended settings are optimized for speed."), + ); + $form['global_container'] = array( + '#type' => 'container', + '#hidden' => TRUE, + '#states' => array( + 'visible' => array( + ':input[name="advagg_relocate_admin_mode"]' => array('value' => '4'), + ), + ), + ); + + $form['global_container']['js'] = array( + '#type' => 'fieldset', + '#title' => t('JS Settings'), + ); + $form['global_container']['js']['advagg_relocate_js'] = array( + '#type' => 'checkbox', + '#title' => t('Move external JS files to a local JS file'), + '#default_value' => variable_get('advagg_relocate_js', ADVAGG_RELOCATE_JS), + '#description' => t('External JS will be moved to a JS file that will be included in the aggregated JS output.'), + '#recommended_value' => TRUE, + ); + $form['global_container']['js']['advagg_relocate_js_ttl'] = array( + '#type' => 'select', + '#title' => t('Only cache external JavaScript files if the browser cache TTL is under this amount.'), + '#options' => $times, + '#default_value' => variable_get('advagg_relocate_js_ttl', ADVAGG_RELOCATE_JS_TTL), + '#description' => t('As an example the Minimum needed to not get penalized by Google PageSpeed Insights is <a href="@url">1 week</a>.', array('@url' => 'https://developers.google.com/speed/docs/insights/LeverageBrowserCaching')), + ); + $form['global_container']['js']['advagg_relocate_js_ga_local'] = array( + '#type' => 'checkbox', + '#title' => t('Move inline google analytics inline analytics.js loader code to drupal_add_js'), + '#default_value' => variable_get('advagg_relocate_js_ga_local', ADVAGG_RELOCATE_JS_GA_LOCAL), + '#description' => t('If the inline loader code is included in the drupal_add_js array it can then be loaded locally if the above checkbox is checked.'), + '#recommended_value' => TRUE, + '#states' => array( + 'invisible' => array( + '#edit-advagg-relocate-js' => array('checked' => FALSE), + ), + ), + ); + $form['global_container']['js']['advagg_relocate_js_piwik_local'] = array( + '#type' => 'checkbox', + '#title' => t('Move inline piwik.js loader code to drupal_add_js'), + '#default_value' => variable_get('advagg_relocate_js_piwik_local', ADVAGG_RELOCATE_JS_PIWIK_LOCAL), + '#description' => t('If the inline loader code is included in the drupal_add_js array it can then be loaded locally if the above checkbox is checked.'), + '#recommended_value' => TRUE, + '#states' => array( + 'invisible' => array( + '#edit-advagg-relocate-js' => array('checked' => FALSE), + ), + ), + ); + $form['global_container']['js']['advagg_relocate_js_gtm_local'] = array( + '#type' => 'checkbox', + '#title' => t('Move inline google tag manager inline gtm.js loader code to drupal_add_js'), + '#default_value' => variable_get('advagg_relocate_js_gtm_local', ADVAGG_RELOCATE_JS_GTM_LOCAL), + '#description' => t('If the inline loader code is included in the drupal_add_js array it can then be loaded locally if the above checkbox is checked.'), + '#recommended_value' => TRUE, + '#states' => array( + 'invisible' => array( + '#edit-advagg-relocate-js' => array('checked' => FALSE), + ), + ), + ); + $form['global_container']['js']['advagg_relocate_js_perfectaudience_local'] = array( + '#type' => 'checkbox', + '#title' => t('Move inline perfectaudience loader code to drupal_add_js'), + '#default_value' => variable_get('advagg_relocate_js_perfectaudience_local', ADVAGG_RELOCATE_JS_PERFECTAUDIENCE_LOCAL), + '#description' => t('If the inline loader code is included in the drupal_add_js array it can then be loaded locally if the above checkbox is checked.'), + '#recommended_value' => TRUE, + '#states' => array( + 'invisible' => array( + '#edit-advagg-relocate-js' => array('checked' => FALSE), + ), + ), + ); + $form['global_container']['js']['advagg_relocate_js_twitter_uwt_local'] = array( + '#type' => 'checkbox', + '#title' => t('Move inline twitter loader code to drupal_add_js'), + '#default_value' => variable_get('advagg_relocate_js_twitter_uwt_local', ADVAGG_RELOCATE_JS_TWITTER_UWT_LOCAL), + '#description' => t('If the inline loader code is included in the drupal_add_js array it can then be loaded locally if the above checkbox is checked.'), + '#recommended_value' => TRUE, + '#states' => array( + 'invisible' => array( + '#edit-advagg-relocate-js' => array('checked' => FALSE), + ), + ), + ); + $form['global_container']['js']['advagg_relocate_js_linkedin_insight_local'] = array( + '#type' => 'checkbox', + '#title' => t('Move inline linkedin insight loader code to drupal_add_js'), + '#default_value' => variable_get('advagg_relocate_js_linkedin_insight_local', ADVAGG_RELOCATE_JS_LINKEDIN_INSIGHT_LOCAL), + '#description' => t('If the inline loader code is included in the drupal_add_js array it can then be loaded locally if the above checkbox is checked.'), + '#recommended_value' => TRUE, + '#states' => array( + 'invisible' => array( + '#edit-advagg-relocate-js' => array('checked' => FALSE), + ), + ), + ); + $form['global_container']['js']['advagg_relocate_js_fbds_local'] = array( + '#type' => 'checkbox', + '#title' => t('Move inline fbds.js loader code to drupal_add_js'), + '#default_value' => variable_get('advagg_relocate_js_fbds_local', ADVAGG_RELOCATE_JS_FBDS_LOCAL), + '#description' => t('If the inline loader code is included in the drupal_add_js array it can then be loaded locally if the above checkbox is checked.'), + '#recommended_value' => TRUE, + '#states' => array( + 'invisible' => array( + '#edit-advagg-relocate-js' => array('checked' => FALSE), + ), + ), + ); + $form['global_container']['js']['advagg_relocate_js_fbevents_local'] = array( + '#type' => 'checkbox', + '#title' => t('Move inline fbevents.js loader code to drupal_add_js'), + '#default_value' => variable_get('advagg_relocate_js_fbevents_local', ADVAGG_RELOCATE_JS_FBEVENTS_LOCAL), + '#description' => t('If the inline loader code is included in the drupal_add_js array it can then be loaded locally if the above checkbox is checked.'), + '#recommended_value' => TRUE, + '#states' => array( + 'invisible' => array( + '#edit-advagg-relocate-js' => array('checked' => FALSE), + ), + ), + ); + $form['global_container']['js']['advagg_relocate_js_fbevents_local_ids'] = array( + '#type' => 'textarea', + '#title' => t('List of Facebook Pixel IDs that will be used.'), + '#default_value' => variable_get('advagg_relocate_js_fbevents_local_ids', ADVAGG_RELOCATE_JS_FBEVENTS_LOCAL_IDS), + '#description' => t('New line separated.'), + '#states' => array( + 'invisible' => array( + '#edit-advagg-relocate-js-fbevents-local' => array('checked' => FALSE), + ), + ), + ); + $advagg_relocate_js_files_blacklist = variable_get('advagg_relocate_js_files_blacklist', ADVAGG_RELOCATE_JS_FILES_BLACKLIST); + $form['global_container']['js']['advagg_relocate_js_files_blacklist'] = array( + '#type' => 'textarea', + '#title' => t('External JS files blacklist'), + '#default_value' => $advagg_relocate_js_files_blacklist, + '#description' => t('New line separated; hostname, path, and filename. Example: <br><code>ajax.googleapis.com/ajax/libs/jquery/1.2.6/jquery.min.js</code><br><code>ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js</code>'), + '#states' => array( + 'invisible' => array( + '#edit-advagg-relocate-js' => array('checked' => FALSE), + ), + ), + ); + $advagg_relocate_js_domains_blacklist = variable_get('advagg_relocate_js_domains_blacklist', ADVAGG_RELOCATE_JS_DOMAINS_BLACKLIST); + $form['global_container']['js']['advagg_relocate_js_domains_blacklist'] = array( + '#type' => 'textarea', + '#title' => t('External JS domains blacklist'), + '#default_value' => $advagg_relocate_js_domains_blacklist, + '#description' => t('New line separated; hostname only. Example:<br><code>code.jquery.com</code><br><code>maxcdn.bootstrapcdn.com</code>'), + '#states' => array( + 'invisible' => array( + '#edit-advagg-relocate-js' => array('checked' => FALSE), + ), + ), + ); + + $form['global_container']['css'] = array( + '#type' => 'fieldset', + '#title' => t('CSS Settings'), + ); + $form['global_container']['css']['advagg_relocate_css'] = array( + '#type' => 'checkbox', + '#title' => t('Move external CSS files to a local CSS file'), + '#default_value' => variable_get('advagg_relocate_css', ADVAGG_RELOCATE_CSS), + '#description' => t('External CSS will be moved to a CSS file that will be included in the aggregated CSS output.'), + '#recommended_value' => TRUE, + ); + $form['global_container']['css']['advagg_relocate_css_ttl'] = array( + '#type' => 'select', + '#title' => t('Only cache external CSS files if the browser cache TTL is under this amount.'), + '#options' => $times, + '#default_value' => variable_get('advagg_relocate_css_ttl', ADVAGG_RELOCATE_CSS_TTL), + '#description' => t('As an example the Minimum needed to not get penalized by Google PageSpeed Insights is <a href="@url">1 week</a>.', array('@url' => 'https://developers.google.com/speed/docs/insights/LeverageBrowserCaching')), + ); + $advagg_relocate_css_files_blacklist = variable_get('advagg_relocate_css_files_blacklist', ADVAGG_RELOCATE_CSS_FILES_BLACKLIST); + $form['global_container']['css']['advagg_relocate_css_files_blacklist'] = array( + '#type' => 'textarea', + '#title' => t('External CSS files blacklist'), + '#default_value' => $advagg_relocate_css_files_blacklist, + '#description' => t('New line separated; hostname, path, and filename. Example: <br><code>ajax.googleapis.com/ajax/libs/jqueryui/1.12.1/themes/smoothness/jquery-ui.css</code><br><code>ajax.googleapis.com/ajax/libs/jqueryui/1.9.2/themes/smoothness/jquery-ui.css</code>'), + '#states' => array( + 'invisible' => array( + '#edit-advagg-relocate-css' => array('checked' => FALSE), + ), + ), + ); + $advagg_relocate_css_domains_blacklist = variable_get('advagg_relocate_css_domains_blacklist', ADVAGG_RELOCATE_CSS_DOMAINS_BLACKLIST); + $form['global_container']['css']['advagg_relocate_css_domains_blacklist'] = array( + '#type' => 'textarea', + '#title' => t('External CSS domains blacklist'), + '#default_value' => $advagg_relocate_css_domains_blacklist, + '#description' => t('New line separated; hostname only. Example:<br><code>code.jquery.com</code><br><code>maxcdn.bootstrapcdn.com</code>'), + '#states' => array( + 'invisible' => array( + '#edit-advagg-relocate-css' => array('checked' => FALSE), + ), + ), + ); + + $form['global_container']['css']['fonts'] = array( + '#type' => 'fieldset', + '#title' => t('Font CSS Settings'), + ); + $form['global_container']['css']['fonts']['advagg_relocate_css_inline_import'] = array( + '#type' => 'checkbox', + '#title' => t('Move external CSS font files to inline CSS'), + '#default_value' => variable_get('advagg_relocate_css_inline_import', ADVAGG_RELOCATE_CSS_INLINE_IMPORT), + '#description' => t('External font CSS will be moved to inline style tags.'), + '#recommended_value' => TRUE, + ); + $form['global_container']['css']['fonts']['advagg_relocate_css_inline_external'] = array( + '#type' => 'checkbox', + '#title' => t('Inline @import CSS font files in local .css files'), + '#default_value' => variable_get('advagg_relocate_css_inline_external', ADVAGG_RELOCATE_CSS_INLINE_EXTERNAL), + '#description' => t('External font CSS will be put in the aggregated CSS output.'), + '#recommended_value' => TRUE, + ); + + $options = array( + 'woff2' => t('<a href="@url">woff2</a> - Edge 14+, FF 39+, Chrome 36+, Opera 23+, iOS 10.1+, Android 5+', array('@url' => 'http://caniuse.com/#feat=woff2')), + 'woff' => t('<a href="@url">woff</a> - IE 9+, FF 3.6+, Chrome 5+, Safari 5.1+, Opera 11.5+, iOS 5.1+, Android 4.4+', array('@url' => 'http://caniuse.com/#feat=woff')), + 'ttf' => t('<a href="@url">ttf</a> - Older Safari, Android, iOS.', array('@url' => 'http://caniuse.com/#feat=ttf')), + 'eot' => t('<a href="@url">eot</a> - IE6, IE7, IE8 and IE9 in compatibility mode.', array('@url' => 'http://caniuse.com/#feat=eot')), + 'svg' => t('<a href="@url">svg</a> - Legacy iOS (4.0 and lower).', array('@url' => 'http://caniuse.com/#feat=svg')), + ); + $defaults = array( + 'woff2' => 'woff2', + 'woff' => 'woff', + 'ttf' => 'ttf', + 'eot' => 0, + 'svg' => 0, + ); + $form['global_container']['css']['fonts']['advagg_relocate_css_inline_import_browsers'] = array( + '#type' => 'checkboxes', + '#title' => t('Browser support for fonts'), + '#options' => $options, + '#default_value' => variable_get('advagg_relocate_css_inline_import_browsers', $defaults), + '#description' => t('<a href="@url">Read more</a>.', array('@url' => 'https://css-tricks.com/snippets/css/using-font-face/')), + '#states' => array( + 'invisible' => array( + '#edit-advagg-relocate-css-inline-import' => array('checked' => FALSE), + '#edit-advagg-relocate-css-inline-external' => array('checked' => FALSE), + ), + ), + ); + $form['global_container']['css']['fonts']['per_file_settings'] = array( + '#type' => 'fieldset', + '#title' => t('What import statements should be inlined?'), + '#states' => array( + 'invisible' => array( + '#edit-advagg-relocate-css-inline-import' => array('checked' => FALSE), + '#edit-advagg-relocate-css-inline-external' => array('checked' => FALSE), + ), + ), + ); + $advagg_relocate_css_font_domains = variable_get('advagg_relocate_css_font_domains', ADVAGG_RELOCATE_CSS_FONT_DOMAINS); + $form['global_container']['css']['fonts']['per_file_settings']['advagg_relocate_css_font_domains'] = array( + '#type' => 'textarea', + '#title' => t('What external domains are supported'), + '#default_value' => $advagg_relocate_css_font_domains, + '#description' => t('Currently Only fonts.googleapis.com has been tested. Please <a href="@url">open an issue</a> if you want to inline import statements for other domains.', array('@url' => 'https://www.drupal.org/project/issues/advagg')), + '#disabled' => TRUE, + ); + + $advagg_relocate_css_file_settings = variable_get('advagg_relocate_css_file_settings', array()); + $files_with_import = advagg_relocate_admin_list_files_with_import(); + + if (!empty($files_with_import)) { + $domains = explode("\n", $advagg_relocate_css_font_domains); + foreach ($files_with_import as $filename => $references) { + // Convert filename to form api friendly name. + $form_api_filename = str_replace( + array('/', '.', '=', '&', ' '), + array('__', '--', '_', '-', '-'), + $filename + ); + + // Settings for the all in filename checkbox. + $default_value = TRUE; + if (isset($advagg_relocate_css_file_settings['all:' . $form_api_filename])) { + $default_value = $advagg_relocate_css_file_settings['all:' . $form_api_filename]; + } + $form['global_container']['css']['fonts']['per_file_settings']['advagg_relocate_css_file_settings_all_' . $form_api_filename] = array( + '#type' => 'checkbox', + '#default_value' => $default_value, + '#recommended_value' => TRUE, + '#title' => t('All in @filename', array('@filename' => $filename)), + ); + $form_api_html_filename = str_replace(array('__', '--', '_'), '-', $form_api_filename); + + // Fieldset for the checkboxes. + $form['global_container']['css']['fonts']['per_file_settings'][$form_api_filename] = array( + '#type' => 'fieldset', + '#title' => t('@filename', array('@filename' => $filename)), + '#states' => array( + 'disabled' => array( + '#edit-advagg-relocate-css-file-settings-all-' . $form_api_html_filename => array('checked' => TRUE), + ), + ), + ); + + // Build options and the default enabled settings. + $default_values = array(); + $options = array(); + foreach ($references as $key => $value) { + $include = FALSE; + foreach ($domains as $domain) { + if (strpos($value, $domain) !== FALSE) { + $include = TRUE; + break; + } + } + if ($include) { + $options[$key] = $value; + $default_values[$key] = $key; + } + } + + // Apply saved settings to the checkboxes. + if (isset($advagg_relocate_css_file_settings[$form_api_filename]) + && is_array($advagg_relocate_css_file_settings[$form_api_filename]) + ) { + $default_values = $advagg_relocate_css_file_settings[$form_api_filename]; + } + $form['global_container']['css']['fonts']['per_file_settings'][$form_api_filename]['advagg_relocate_css_file_settings_' . $form_api_filename] = array( + '#type' => 'checkboxes', + '#options' => $options, + '#default_value' => $default_values, + ); + + // No viable options for this file; remove fieldset and checkboxes. + if (empty($options)) { + unset($form['global_container']['css']['fonts']['per_file_settings'][$form_api_filename]); + unset($form['global_container']['css']['fonts']['per_file_settings']['advagg_relocate_css_file_settings_all_' . $form_api_filename]); + } + } + } + + // Clear the cache bins on submit. + $form['#submit'][] = 'advagg_relocate_admin_settings_form_submit'; + + return system_settings_form($form); +} + +/** + * Submit callback, clear out the advagg cache bin. + * + * Also process the advagg_relocate_css_file_settings variable. + * + * @ingroup advagg_forms_callback + */ +function advagg_relocate_admin_settings_form_submit($form, &$form_state) { + // Work around PHP bug with $_POST not containing all the data. + $alt_post = array(); + $input = rawurldecode(file_get_contents('php://input')); + parse_str($input, $alt_post); + $alt_post = drupal_json_decode(str_replace(' ', '+', drupal_json_encode($alt_post))); + $diff = advagg_diff_multi($_POST, $alt_post); + if (!empty($diff)) { + foreach ($diff as $k => $v) { + if (isset($form_state['values'][$k])) { + $form_state['values'][$k] = $v; + } + } + } + + // Reset this form to defaults or recommended values; also show what changed. + advagg_set_admin_form_defaults_recommended($form_state, 'advagg_relocate_admin_mode'); + + // Get settings that start with advagg_relocate_css_file_settings. + if (defined('PHP_VERSION_ID') && PHP_VERSION_ID >= 50600) { + $vars = array_filter($form_state['values'], '_advagg_relocate_admin_is_css_file_settings', ARRAY_FILTER_USE_KEY); + } + else { + $vars = array_intersect_key($form_state['values'], array_flip(array_filter(array_keys($form_state['values']), '_advagg_relocate_admin_is_css_file_settings'))); + } + + // Unset found values. + foreach ($vars as $key => $value) { + if (isset($form_state['values'][$key])) { + unset($form_state['values'][$key]); + } + // If all is set, remove sub options for that file. + if (strpos($key, 'advagg_relocate_css_file_settings_all_') === 0 && !empty($value)) { + $sub_key = str_replace('advagg_relocate_css_file_settings_all_', 'advagg_relocate_css_file_settings_', $key); + if (isset($vars[$sub_key])) { + unset($vars[$sub_key]); + } + } + } + + // Save under one variable. + $saved_values = array(); + foreach ($vars as $key => $value) { + $key = str_replace('advagg_relocate_css_file_settings_all_', 'all:', $key); + $key = str_replace('advagg_relocate_css_file_settings_', '', $key); + $saved_values[$key] = $value; + } + $form_state['values']['advagg_relocate_css_file_settings'] = $saved_values; + + // Clear caches. + advagg_cache_clear_admin_submit(); +} + +/** + * See if the key starts with 'advagg_relocate_css_file_settings'. + * + * @param string $key + * They array key as a string. + * + * @return bool + * TRUE if the input string starts with 'advagg_relocate_css_file_settings'. + */ +function _advagg_relocate_admin_is_css_file_settings($key) { + return strpos($key, 'advagg_relocate_css_file_settings') === 0; +} + +/** + * Check all CSS files, see if any contains an @import that is external. + * + * @param array $files + * An array of filenames to check. + * + * @return array + * An array of filenames that contain an @import that is external. + */ +function advagg_relocate_admin_list_files_with_import(array $files = array()) { + if (empty($files)) { + // Get filename. + $results = db_select('advagg_files', 'af') + ->fields('af', array('filename')) + ->condition('filetype', 'css') + ->orderBy('filename', 'ASC') + ->execute(); + while ($row = $results->fetchAssoc()) { + $files[] = $row['filename']; + } + } + $files_with_import_statements = array(); + if (empty($files)) { + return $files_with_import_statements; + } + + module_load_include('inc', 'advagg', 'advagg'); + foreach ($files as $file) { + if (!file_exists($file)) { + continue; + } + // Get the file contents. + $file_contents = advagg_load_css_stylesheet($file, FALSE); + if (strpos($file_contents, '@import') !== FALSE) { + $matches = array(); + preg_match_all('%@import\s*+(?:url\(\s*+)?+[\'"]?+((?:http:\/\/|https:\/\/|\/\/)(?:[^\'"()\s]++))[\'"]?+\s*+\)?+\s*+;%i', $file_contents, $matches); + if (!empty($matches[0])) { + $output = array(); + foreach ($matches[1] as $k => $v) { + $v = str_replace(array('=', '&', ' '), array('_', '-', '-'), $v); + $output[$v] = $matches[0][$k]; + } + + $files_with_import_statements[$file] = $output; + } + } + } + return $files_with_import_statements; +} + +/** + * Add a cache clear button to the advagg operations form. + */ +function advagg_relocate_admin_form_advagg_admin_operations_form(&$form, &$form_state) { + $form['drastic_measures']['flush_relocate_cache'] = array( + '#type' => 'fieldset', + '#collapsible' => TRUE, + '#collapsed' => TRUE, + '#title' => t('Clear the advagg relocate cache'), + '#description' => t('Rarely needed but useful if a download was corrupted.'), + ); + $form['drastic_measures']['flush_relocate_cache']['advagg_flush'] = array( + '#type' => 'submit', + '#value' => t('Flush AdvAgg Relocate Cache'), + '#submit' => array('advagg_relocate_flush_cache_button'), + ); +} diff --git a/sites/all/modules/contrib/advagg/advagg_relocate/advagg_relocate.advagg.inc b/sites/all/modules/contrib/advagg/advagg_relocate/advagg_relocate.advagg.inc new file mode 100644 index 0000000000..37ee2b76eb --- /dev/null +++ b/sites/all/modules/contrib/advagg/advagg_relocate/advagg_relocate.advagg.inc @@ -0,0 +1,1104 @@ +<?php + +/** + * @file + * Advanced aggregation relocate module. + */ + +/** + * @addtogroup advagg_hooks + * @{ + */ + +/** + * Implements hook_advagg_get_info_on_files_alter(). + */ +function advagg_relocate_advagg_get_info_on_files_alter(&$return, $cached_data, $bypass_cache) { + $aggregate_settings = advagg_current_hooks_hash_array(); + // Check external js setting. + if (empty($aggregate_settings['variables']['advagg_relocate_js'])) { + return; + } + foreach ($return as $key => &$info) { + // Skip if not a js file. + if (empty($info['fileext']) || $info['fileext'] !== 'js') { + continue; + } + // Get the file contents. + $file_contents = (string) @advagg_file_get_contents($info['data']); + if (empty($file_contents)) { + continue; + } + $value['data'] = $file_contents; + $scripts_found = advagg_relocate_js_script_rewrite_list($key, $value, $aggregate_settings); + + if (!empty($scripts_found)) { + $info['advagg_relocate'] = $scripts_found; + } + } +} + +/** + * Implements hook_advagg_get_js_file_contents_alter(). + */ +function advagg_relocate_advagg_get_js_file_contents_alter(&$contents, $filename, $aggregate_settings) { + // Do nothing if this is disabled. + if (empty($aggregate_settings['variables']['advagg_relocate_js'])) { + return; + } + + module_load_include('inc', 'advagg', 'advagg'); + // Get info on file. + $info = advagg_get_info_on_file($filename); + if (!empty($info['advagg_relocate'])) { + // Set values so it thinks its an inline script. + $value['data'] = &$contents; + $key = $filename; + $scripts_found = advagg_relocate_js_script_rewrite_list($key, $value, $aggregate_settings); + + // Do rewrite of the data. + if (!empty($scripts_found)) { + $js[$key] = &$value; + advagg_relocate_js_script_rewrite($js, $scripts_found); + } + } +} + +/** + * Implements hook_advagg_get_css_aggregate_contents_alter(). + */ +function advagg_relocate_advagg_get_css_aggregate_contents_alter(&$data, $files, $aggregate_settings) { + // Set variables if needed. + if (!isset($aggregate_settings['variables']['advagg_relocate_css_inline_import'])) { + $aggregate_settings['variables']['advagg_relocate_css_inline_import'] = variable_get('advagg_relocate_css_inline_import', ADVAGG_RELOCATE_CSS_INLINE_IMPORT); + } + + // Do nothing if this is disabled. + if (empty($aggregate_settings['variables']['advagg_relocate_css_inline_import'])) { + return; + } + + if (strpos($data, '@import') !== FALSE) { + // Set values that will be used when preg_replace_callback is ran. + _advagg_relocate_callback(array(), $files, $aggregate_settings); + + // Replace external import statements with the contents of them. + $data = preg_replace_callback('%@import\s*+(?:url\(\s*+)?+[\'"]?+((?:http:\/\/|https:\/\/|\/\/)(?:[^\'"()\s]++))[\'"]?+\s*+\)?+\s*+;%i', '_advagg_relocate_callback', $data); + + // Per the W3C specification at + // http://www.w3.org/TR/REC-CSS2/cascade.html#at-import, @import rules + // must proceed any other style, so we move those to the top. + $matches = array(); + $regexp = '/@import[^;]+;/i'; + preg_match_all($regexp, $data, $matches); + $data = preg_replace($regexp, '', $data); + // Add import statements to the top of the stylesheet. + $data = implode("\n", $matches[0]) . $data; + } +} + +/** + * Implements hook_advagg_relocate_process_http_request_alter(). + */ +function advagg_relocate_advagg_relocate_process_http_request_alter(&$response, $type) { + if ($type !== 'js' + || strpos($response->url, 'connect.facebook.net/en_US/fbevents.js') === FALSE + ) { + return 1; + } + + // Fix loader so it works if not loaded from connect.facebook.net. + $base_fb_url = 'https://connect.facebook.net'; + $matches = array(); + $pattern = '/function\s+([\w]{1,2})\(\)\s*\{\s*var\s+([\w]{1,2})\s*=\s*null,\s*([\w]{1,2})\s*=\s*null,\s*([\w]{1,2})\s*=\s*([\w]{1,2})\.getElementsByTagName\([\'"]script[\'"]\)/'; + preg_match($pattern, $response->data, $matches); + // Bail out if not matched. + if (empty($matches[0])) { + return 2; + } + // Transform + // function B(){var E=null,F=null,G=b.getElementsByTagName("script"); + // to + // function B(){var E="https://connect.facebook.net",G=b.getElementsByTagName("script"),F=G[0]. + $response->data = str_replace($matches[0], "function {$matches[1]}(){var {$matches[2]}=\"$base_fb_url\",{$matches[4]}={$matches[5]}.getElementsByTagName(\"script\"),{$matches[3]}={$matches[4]}[0]", $response->data); + + // Get Facebook IDs. + $fb_ids = array_filter(array_map('trim', explode("\n", variable_get('advagg_relocate_js_fbevents_local_ids', ADVAGG_RELOCATE_JS_FBEVENTS_LOCAL_IDS)))); + if (empty($fb_ids)) { + return 3; + } + + // Get Facebook Version. + $matches = array(); + $pattern = '/fbq.version\s*=\s*[\'"]([\.\d]+)[\'"]/'; + preg_match($pattern, $response->data, $matches); + if (empty($matches[1])) { + return 4; + } + $version = $matches[1]; + + // Get Release Segment. + $segment = 'stable'; + $matches = array(); + $pattern = '/fbq._releaseSegment\s*=\s*[\'"](.+)[\'"]/'; + preg_match($pattern, $response->data, $matches); + if (!empty($matches[1])) { + $segment = $matches[1]; + } + + // Update local copies of the /signals/config/ js. + $js = array(); + foreach ($fb_ids as $fb_id) { + $url = "$base_fb_url/signals/config/$fb_id?v=$version&r=$segment"; + $js[$url]['data'] = $url; + $js[$url]['type'] = 'external'; + $js[$url]['#fbid'] = "config$fb_id"; + $url = "$base_fb_url/signals/plugins/$fb_id?v=$version&r=$segment"; + $js[$url]['data'] = $url; + $js[$url]['type'] = 'external'; + $js[$url]['#fbid'] = "plugins$fb_id"; + } + if (!empty($js)) { + advagg_relocate_js_post_alter($js, TRUE); + } + // Get a list of the local copies for this version. + $local_copies = array(); + foreach ($js as $values) { + if ($values['type'] === 'file') { + // Create an aggregate just for this file. + $values += drupal_js_defaults($values); + $elements = array($values); + $groups = advagg_group_js($elements); + _advagg_aggregate_js($groups); + if (isset($groups[0]['data'])) { + $local_copies[$values['#fbid']] = advagg_file_create_url($groups[0]['data']); + } + } + } + if (empty($local_copies)) { + return 5; + } + + // Add the local copies to the js file. + $local_copies = json_encode($local_copies); + $matches = array(); + $pattern = '/return\s*\{\s*baseURL:\s*([\w]{1,2}),\s*scriptElement:\s*([\w]{1,2})\s*\}/'; + preg_match($pattern, $response->data, $matches); + // Bail out if not matched. + if (empty($matches[0])) { + return 6; + } + // Transform + // return{baseURL:E,scriptElement:F} + // to + // return{baseURL:E,scriptElement:F,localCopies:ARRAY_OF_LOCAL_JS_FILES}. + $response->data = str_replace($matches[0], "return{baseURL:{$matches[1]},scriptElement:{$matches[2]},localCopies:$local_copies}", $response->data); + + // Change logic so it'll use the local copy if it exists. + $matches = array(); + $pattern = '/([\w]{1,2})\s*=\s*([\w]{1,2})\.baseURL;\s*var\s+([\w]{1,2})\s*=\s*([\w]{1,2})\s*\+\s*[\'"]\/signals\/config\/[\'"]\s*\+\s*([\w]{1,2})\s*\+\s*[\'"]\?v=[\'"]\s*\+\s*([\w]{1,2})\s*\+\s*[\'"]\&r=[\'"]\s*\+\s*([\w]{1,2}),\s*([\w]{1,2})\s*=\s*([\w]{1,2})\.createElement\([\'"]script[\'"]\);\s*[\w]{1,2}.src\s*=\s*[\w]{1,2};/'; + preg_match($pattern, $response->data, $matches); + // Bail out if not matched. + if (empty($matches[0])) { + return 7; + } + + // Transform + // 1 2 3 4 5 6 7 8 9 8 3 + // I=H.baseURL;var J=I+"/signals/config/"+E+"?v="+F+"&r="+G,K=b.createElement("script");K.src=J; + // to + // 1 2 3 4 5 6 7 8 9 2 2 5 3 2 5 8 3 + // I=H.baseURL;var J=I+"/signals/config/"+E+"?v="+F+"&r="+G,K=b.createElement("script");if(H.localCopies&&H.localCopies["config"+E]){J=H.localCopies["config"+E];}K.src=J;. + $response->data = str_replace($matches[0], + "{$matches[1]}={$matches[2]}.baseURL;var {$matches[3]}={$matches[4]}+\"/signals/config/\"+{$matches[5]}+\"?v=\"+{$matches[6]}+\"&r=\"+{$matches[7]},{$matches[8]}={$matches[9]}.createElement(\"script\");if({$matches[2]}.localCopies&&{$matches[2]}.localCopies[\"config\"+{$matches[5]}]){{$matches[3]}={$matches[2]}.localCopies[\"config\"+{$matches[5]}];}{$matches[8]}.src={$matches[3]};", + $response->data + ); + + // Change logic so it'll use the local copy if it exists. + $matches = array(); + $pattern = '/if\s*\(([\w]{1,2})\.baseURL\s*\&\&\s*([\w]{1,2})\.scriptElement\)\s*\{\s*var\s+([\w]{1,2})\s*\=\s*([\w]{1,2})\.baseURL\s*\+\s*[\'"]\/signals\/plugins\/[\'"]\s*\+\s*([\w]{1,2})\s*\+\s*[\'"]\.js\?v\=[\'"]\s*\+\s*([\w]{1,2})\.version;/'; + preg_match($pattern, $response->data, $matches); + // Bail out if not matched. + if (empty($matches[0])) { + return 8; + } + // Transform + // if(C.baseURL&&C.scriptElement){var D=C.baseURL+"/signals/plugins/"+A+".js?v="+g.version; + // to + // if(C.baseURL&&C.scriptElement){var D=C.baseURL+"/signals/plugins/"+A+".js?v="+g.version;if(C.localCopies&&C.localCopies["plugins"+A]){D=C.localCopies["plugins"+A];}. + $response->data = str_replace($matches[0], "if({$matches[1]}.baseURL&&{$matches[2]}.scriptElement){var {$matches[3]}={$matches[4]}.baseURL+\"/signals/plugins/\"+{$matches[5]}+\".js?v=\"+{$matches[6]}.version;if({$matches[1]}.localCopies&&{$matches[1]}.localCopies[\"plugins\"+{$matches[5]}]){{$matches[3]}={$matches[1]}.localCopies[\"plugins\"+{$matches[5]}];}", $response->data); +} + +/** + * @} End of "addtogroup advagg_hooks". + */ + +/** + * Gets external CSS files and puts the contents of it in the aggregate. + * + * @param array $matches + * Array of matched items from preg_replace_callback(). + * @param array $files + * List of files with the media type. + * @param array $aggregate_settings + * Array of settings. + * + * @return string + * Contents of the import statement. + */ +function _advagg_relocate_callback(array $matches = array(), array $files = array(), array $aggregate_settings = array()) { + // Store values for preg_replace_callback callback. + $_args = &drupal_static(__FUNCTION__, array()); + if (!empty($files)) { + $_args['files'] = $files; + } + if (!empty($aggregate_settings)) { + $_args['aggregate_settings'] = $aggregate_settings; + } + // Short circuit if no matches were passed in. + if (empty($matches)) { + return ''; + } + + // Bail if not matched. + if (empty($matches[1])) { + return $matches[0]; + } + + // Check URL. + if (!advagg_relocate_check_domain_of_font_url($matches[1], $_args['aggregate_settings'])) { + return $matches[0]; + } + + // Check per file settings. + if (!isset($_args['aggregate_settings']['variables']['advagg_relocate_css_file_settings'])) { + $_args['aggregate_settings']['variables']['advagg_relocate_css_file_settings'] = variable_get('advagg_relocate_css_file_settings', array()); + } + + $key_to_check = str_replace(array('=', '&', ' '), array('_', '-', '-'), $matches[1]); + + foreach ($_args['files'] as $filename => $values) { + $form_api_filename = str_replace( + array('/', '.', '=', '&', ' '), + array('__', '--', '_', '-', '+'), + $filename + ); + // All has been checked; good to go. + if (!empty($_args['aggregate_settings']['variables']['advagg_relocate_css_file_settings']["all:{$form_api_filename}"])) { + continue; + } + + // This file is good to be inlined. + if (!empty($_args['aggregate_settings']['variables']['advagg_relocate_css_file_settings'][$form_api_filename][$key_to_check])) { + continue; + } + + // No go, return unaltered. + return $matches[0]; + } + + $font_faces = advagg_relocate_get_remote_font_data($matches[1], $_args['aggregate_settings']); + return advagg_relocate_font_face_parser($font_faces); +} + +/** + * Given an array of font data output a new CSS string. + * + * @param array $font_faces + * Array of font data. + * + * @return string + * String of CSS font data. + */ +function advagg_relocate_font_face_parser(array $font_faces) { + $new_css = ''; + foreach ($font_faces as $values => $src) { + $output = ''; + $output .= str_replace('; ', ";\n", $values); + if (isset($src['eot'])) { + $output .= "src: {$src['eot']};\n"; + } + $output .= 'src:'; + foreach ($src as $key => $location) { + if (is_numeric($key)) { + $output .= "$location,"; + } + } + if (isset($src['eot'])) { + $src['eot'] = str_replace('.eot', '.eot?#iefix', $src['eot']); + $output .= "{$src['eot']} format('embedded-opentype'),"; + } + if (isset($src['woff2'])) { + $output .= "{$src['woff2']},"; + } + if (isset($src['woff'])) { + $output .= "{$src['woff']},"; + } + if (isset($src['ttf'])) { + $output .= "{$src['ttf']},"; + } + if (isset($src['svg'])) { + $output .= "{$src['svg']},"; + } + $output = str_replace(array('),l', '),u'), array("),\nl", "),\nu"), trim($output, ',') . ';'); + $new_css .= "@font-face {\n$output\n}\n"; + } + + return $new_css; +} + +/** + * Gets external CSS and JS files; caches and returns response. + * + * @param string $urls + * URLs to get. + * @param string $type + * Will be css or js. + * @param array $options + * Array of settings for the http request. + * @param bool $force_check + * TRUE if you want to force check the external source. + * + * @return array + * Array of http responses. + */ +function advagg_relocate_get_remote_data($urls, $type, array $options = array(), $force_check = FALSE) { + // Set arguments for drupal_http_request(). + $options += array( + 'headers' => array( + 'Accept-Encoding' => 'gzip, deflate', + 'Connection' => 'close', + 'Referer' => $GLOBALS['base_root'] . request_uri(), + ), + 'timeout' => 8, + 'version' => '1.0', + ); + if (function_exists('brotli_uncompress')) { + $options['headers']['Accept-Encoding'] .= ', br'; + } + + // Build CID. + $cids = array(); + foreach ($urls as $k => $v) { + $cids["advagg_relocate_{$type}_external:{$k}"] = "advagg_relocate_{$type}_external:{$k}"; + } + + // Try local cache. + $return = array(); + $responses = array(); + $cached_data = cache_get_multiple($cids, 'cache_advagg_info'); + $cached_data = array_merge($cids, $cached_data); + $url_to_cid = array(); + $request_sent = FALSE; + foreach ($cached_data as $cid => $cache) { + // CID not set, skip. + if (empty($cid)) { + continue; + } + // Set cid, filename and get url. + $options['cid'] = $cid; + $options['filename'] = substr($cid, 26 + strlen($type)); + + // Filename lookup failure, skip. + if (empty($urls[$options['filename']])) { + continue; + } + + // Add url to the lookup array. + $url = advagg_force_https_path($urls[$options['filename']]); + $url_to_cid[$url] = $cid; + + // Reset headers if needed. + if (isset($options['headers']['If-None-Match'])) { + unset($options['headers']['If-None-Match']); + } + if (isset($options['headers']['If-Modified-Since'])) { + unset($options['headers']['If-Modified-Since']); + } + + // Use cached data or setup for 304. + if (!empty($cache->data)) { + if ($cache->expire >= REQUEST_TIME + && isset($cache->data->url) + && empty($force_check) + ) { + $return[$cache->data->url] = $cache->data; + continue; + } + else { + // Set header for 304 response. + if (isset($cached_data->data->headers['etag'])) { + $options['headers']['If-None-Match'] = $cached_data->data->headers['etag']; + } + if (isset($cached_data->created)) { + $options['headers']['If-Modified-Since'] = gmdate('D, d M Y H:i:s T', $cached_data->created); + } + } + } + + // Get data. + if (module_exists('httprl')) { + $request_sent = TRUE; + httprl_request($url, $options); + } + else { + $request_sent = TRUE; + $responses[$url] = drupal_http_request($url, $options); + if (!isset($responses[$url]->options)) { + $responses[$url]->options = $options; + } + if (!isset($responses[$url]->url)) { + $responses[$url]->url = $url; + } + } + } + if ($request_sent && module_exists('httprl')) { + $responses = httprl_send_request(); + } + if (empty($responses)) { + return $return; + } + + // Try failures again. + advagg_relocate_try_failures_again($responses); + + // Process remote data. + foreach ($responses as $url => $response) { + // Content length does not match the response data. + if (!empty($response->headers['content-length']) + && $response->headers['content-length'] > strlen($response->data) + ) { + continue; + } + + // No url is a no go. + if (empty($response->url)) { + $response->url = $url; + } + if (isset($response->options['cid'])) { + $cid = $response->options['cid']; + } + elseif (isset($url_to_cid[$response->url])) { + $cid = $url_to_cid[$response->url]; + } + else { + // Can't match up url to the cid. + continue; + } + // Update object. + if (!isset($response->options['filename'])) { + $response->options['filename'] = substr($cid, 26 + strlen($type)); + } + if (!isset($response->options['cid'])) { + $response->options['cid'] = $cid; + } + + advagg_relocate_process_http_request($response, $type); + + if ($response->code == 304 && !empty($cached_data->data)) { + // Update cache expire time. + cache_set($cid, $cached_data->data, 'cache_advagg_info', REQUEST_TIME + $response->ttl); + + // Return cached data. + $return[$cached_data->data->url] = $cached_data->data; + } + + // Skip if not a 200. + if ($response->code != 200 + && $response->code != 201 + && $response->code != 202 + && $response->code != 206 + ) { + continue; + } + if (empty($response->data)) { + continue; + } + + $response->local_cache = FALSE; + // Save data to the cache. + if (!empty($response->data)) { + $response->hash = drupal_hash_base64($response->data); + $response->local_cache = TRUE; + cache_set($cid, $response, 'cache_advagg_info', REQUEST_TIME + $response->ttl); + $response->local_cache = FALSE; + } + $return[$response->url] = $response; + } + return $return; +} + +/** + * Get the TTL and fix UTF-8 encoding. + * + * @param object $response + * Response from http request. + * @param string $type + * Can be css, js, or font. + */ +function advagg_relocate_process_http_request(&$response, $type) { + // Get ttl from response. + $ttl = 0; + if ($type === 'css') { + $ttl = variable_get('advagg_relocate_css_min_ttl', ADVAGG_RELOCATE_CSS_MIN_TTL); + } + if ($type === 'js') { + $ttl = variable_get('advagg_relocate_js_min_ttl', ADVAGG_RELOCATE_JS_MIN_TTL); + } + + $now = REQUEST_TIME; + if (isset($response->headers['expires'])) { + $expires = strtotime($response->headers['expires']); + if (isset($response->headers['date'])) { + $now = max($now, strtotime($response->headers['date'])); + } + $ttl = max($ttl, $expires - $now); + } + if (isset($response->headers['cache-control'])) { + $cache_control_array = advagg_relocate_parse_cache_control($response->headers['cache-control']); + if (isset($cache_control_array['max-age']) && is_numeric($cache_control_array['max-age'])) { + $ttl = max($ttl, $cache_control_array['max-age']); + } + if (isset($cache_control_array['s-maxage']) && is_numeric($cache_control_array['s-maxage'])) { + $ttl = max($ttl, $cache_control_array['s-maxage']); + } + } + $response->ttl = $ttl; + + // If a BOM is found, convert the string to UTF-8. + if (isset($response->data)) { + $encoding = advagg_get_encoding_from_bom($response->data); + if (!empty($encoding)) { + $response->data = advagg_convert_to_utf8($response->data, $encoding); + } + } + + // Call hook_advagg_relocate_process_http_request_alter(). + drupal_alter('advagg_relocate_process_http_request', $response, $type); +} + +/** + * Decompress the data. + * + * @param object $response + * Response from http request. + * + * @return bool + * FALSE if something went wrong. + */ +function advagg_relocate_uncompress_data(&$response) { + // Uncompress. + if (!empty($response->headers['content-encoding']) + && !empty($response->data) + && (!isset($response->chunk_size) || (!empty($response->headers['content-length']) && $response->headers['content-length'] == strlen($response->data))) + && ($response->headers['content-encoding'] === 'gzip' + || $response->headers['content-encoding'] === 'deflate' + || $response->headers['content-encoding'] === 'br' + )) { + // Do the first level of decoding if not already done. + if ($response->headers['content-encoding'] === 'gzip') { + $chunk = @gzinflate(substr($response->data, 10)); + } + elseif ($response->headers['content-encoding'] === 'deflate') { + $chunk = @gzinflate($response->data); + } + elseif ($response->headers['content-encoding'] === 'br' && is_callable('brotli_uncompress')) { + $chunk = @brotli_uncompress($response->data); + } + if (isset($chunk)) { + if ($chunk !== FALSE) { + $response->data = $chunk; + } + else { + return FALSE; + } + } + } + + return TRUE; +} + +/** + * Detect failures and try again. + * + * @param array $responses + * An array of $response objects from a http request. + */ +function advagg_relocate_try_failures_again(array &$responses) { + // Try failures again. + foreach ($responses as $key => &$response) { + // Strlen doesn't match. + if (!empty($response->headers['content-length']) + && $response->headers['content-length'] > strlen($response->data) + ) { + $response->code = 0; + if (isset($response->options['headers']['connection'])) { + unset($response->options['headers']['connection']); + } + } + + // Decode data. + $decode_ok = advagg_relocate_uncompress_data($response); + if (!$decode_ok) { + // If decoding failed try again. + if (isset($response->options['headers']['Accept-Encoding'])) { + unset($response->options['headers']['Accept-Encoding']); + } + $response->code = 0; + } + + // Try again if code is empty. + if (empty($response->code)) { + $url = $response->url; + $options = $response->options; + $responses[$key] = drupal_http_request($url, $options); + if (!isset($responses[$key]->options)) { + $responses[$key]->options = $options; + } + if (!isset($responses[$key]->url)) { + $responses[$key]->url = $url; + } + } + } + + // Try failures again, but force http. + foreach ($responses as $key => $response) { + if (empty($response->code)) { + $url = advagg_force_http_path($response->url); + $options = $response->options; + $responses[$key] = drupal_http_request($url, $options); + if (!isset($responses[$key]->options)) { + $responses[$key]->options = $options; + } + if (!isset($responses[$key]->url)) { + $responses[$key]->url = $url; + } + } + } +} + +/** + * Gets external CSS files; caches it and returns css font rules. + * + * @param string $url + * URL of the CSS file to import. + * @param array $aggregate_settings + * Array of settings. + * + * @return array + * Array of font data. + */ +function advagg_relocate_get_remote_font_data($url, array $aggregate_settings) { + // Set default settings if needed. + $font_type_defaults = array( + 'woff2' => 'woff2', + 'woff' => 'woff', + 'ttf' => 'ttf', + ); + if (!isset($aggregate_settings['variables']['advagg_relocate_css_inline_import_browsers'])) { + $aggregate_settings['variables']['advagg_relocate_css_inline_import_browsers'] = variable_get('advagg_relocate_css_inline_import_browsers', $font_type_defaults); + } + // Make sure advagg_relocate_css_inline_import_browsers is an array. + if (!is_array($aggregate_settings['variables']['advagg_relocate_css_inline_import_browsers'])) { + $aggregate_settings['variables']['advagg_relocate_css_inline_import_browsers'] = array($aggregate_settings['variables']['advagg_relocate_css_inline_import_browsers'] => $aggregate_settings['variables']['advagg_relocate_css_inline_import_browsers']); + } + // Use defaults if no matches for known font types. + if (!isset($aggregate_settings['variables']['advagg_relocate_css_inline_import_browsers']['woff2']) + && !isset($aggregate_settings['variables']['advagg_relocate_css_inline_import_browsers']['woff']) + && !isset($aggregate_settings['variables']['advagg_relocate_css_inline_import_browsers']['ttf']) + && !isset($aggregate_settings['variables']['advagg_relocate_css_inline_import_browsers']['eot']) + && !isset($aggregate_settings['variables']['advagg_relocate_css_inline_import_browsers']['svg']) + ) { + $aggregate_settings['variables']['advagg_relocate_css_inline_import_browsers'] += $font_type_defaults; + } + + // Set arguments for drupal_http_request(). + $options = array( + 'headers' => array( + 'Accept-Encoding' => 'gzip, deflate', + 'Connection' => 'close', + 'Referer' => $GLOBALS['base_root'] . request_uri(), + ), + 'timeout' => 8, + 'version' => '1.0', + ); + if (function_exists('brotli_uncompress')) { + $options['headers']['Accept-Encoding'] .= ', br'; + } + + // If protocol relative, force https. + if (strpos($url, '//') === 0) { + $url = advagg_force_https_path($url); + } + + // Build CID. + $aggregate_settings['variables']['advagg_relocate_css_inline_import_browsers'] = array_filter($aggregate_settings['variables']['advagg_relocate_css_inline_import_browsers']); + $fonts = implode(',', $aggregate_settings['variables']['advagg_relocate_css_inline_import_browsers']); + $cid = "advagg_relocate_css_inline_import:$fonts:$url"; + + // Try local cache. + $cached_data = cache_get($cid, 'cache_advagg_info'); + if (!empty($cached_data->data[0])) { + if ($cached_data->expire >= REQUEST_TIME) { + return $cached_data->data[0]; + } + else { + // Set header for 304 response. + // $options['headers']['If-None-Match'] = $response->headers['etag'];. + $options['headers']['If-Modified-Since'] = gmdate('D, d M Y H:i:s T', $cached_data->created); + } + } + + // Get external data. + $responses = array(); + if (module_exists('httprl')) { + // Get ttf. + if (!empty($aggregate_settings['variables']['advagg_relocate_css_inline_import_browsers']['ttf'])) { + $options['#font-type'] = 'ttf'; + httprl_request($url . '#ttf', $options); + } + + // Get eot. + if (!empty($aggregate_settings['variables']['advagg_relocate_css_inline_import_browsers']['eot'])) { + $options['#font-type'] = 'eot'; + $options['headers']['User-Agent'] = 'Mozilla/5.0 (Windows; U; MSIE 7.0; Windows NT 6.0; en-US)'; + httprl_request($url . '#eot', $options); + } + + // Get svg. + if (!empty($aggregate_settings['variables']['advagg_relocate_css_inline_import_browsers']['svg'])) { + $options['#font-type'] = 'svg'; + $options['headers']['User-Agent'] = 'Mozilla/5.0 (iPad; U; CPU OS 3_2_2 like Mac OS X; nl-nl) AppleWebKit/531.21.10 (KHTML, like Gecko) Version/4.0.4 Mobile/7B500 Safari/531.21.10'; + httprl_request($url . '#svg', $options); + } + + // Get woff. + if (!empty($aggregate_settings['variables']['advagg_relocate_css_inline_import_browsers']['woff'])) { + $options['#font-type'] = 'woff'; + $options['headers']['User-Agent'] = 'Mozilla/5.0 (Windows; U; MSIE 9.0; Windows NT 9.0; en-US)'; + httprl_request($url . '#woff', $options); + } + + // Get woff2. + if (!empty($aggregate_settings['variables']['advagg_relocate_css_inline_import_browsers']['woff2'])) { + $options['#font-type'] = 'woff2'; + $options['headers']['User-Agent'] = 'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:40.0) Gecko/20100101 Firefox/40.1'; + httprl_request($url . '#woff2', $options); + } + + $responses = httprl_send_request(); + } + if (empty($responses)) { + // Get ttf. + if (!empty($aggregate_settings['variables']['advagg_relocate_css_inline_import_browsers']['ttf'])) { + $options['#font-type'] = 'ttf'; + $responses['ttf'] = drupal_http_request($url . '#ttf', $options); + if (!isset($responses['ttf']->options)) { + $responses['ttf']->options = $options; + } + if (!isset($responses[$url]->url)) { + $responses['ttf']->url = $url . '#ttf'; + } + } + + // Get eot. + if (!empty($aggregate_settings['variables']['advagg_relocate_css_inline_import_browsers']['eot'])) { + $options['#font-type'] = 'eot'; + $options['headers']['User-Agent'] = 'Mozilla/5.0 (Windows; U; MSIE 7.0; Windows NT 6.0; en-US)'; + $responses['eot'] = drupal_http_request($url . '#eot', $options); + if (!isset($responses['eot']->options)) { + $responses['eot']->options = $options; + } + if (!isset($responses[$url]->url)) { + $responses['eot']->url = $url . '#eot'; + } + } + + // Get svg. + if (!empty($aggregate_settings['variables']['advagg_relocate_css_inline_import_browsers']['svg'])) { + $options['#font-type'] = 'svg'; + $options['headers']['User-Agent'] = 'Mozilla/5.0 (iPad; U; CPU OS 3_2_2 like Mac OS X; nl-nl) AppleWebKit/531.21.10 (KHTML, like Gecko) Version/4.0.4 Mobile/7B500 Safari/531.21.10'; + $responses['svg'] = drupal_http_request($url . '#svg', $options); + if (!isset($responses['svg']->options)) { + $responses['svg']->options = $options; + } + if (!isset($responses[$url]->url)) { + $responses['svg']->url = $url . '#svg'; + } + } + + // Get woff. + if (!empty($aggregate_settings['variables']['advagg_relocate_css_inline_import_browsers']['woff'])) { + $options['#font-type'] = 'woff'; + $options['headers']['User-Agent'] = 'Mozilla/5.0 (Windows; U; MSIE 9.0; Windows NT 9.0; en-US)'; + $responses['woff'] = drupal_http_request($url . '#woff', $options); + if (!isset($responses['woff']->options)) { + $responses['woff']->options = $options; + } + if (!isset($responses[$url]->url)) { + $responses['woff']->url = $url . '#woff'; + } + } + + // Get woff2. + if (!empty($aggregate_settings['variables']['advagg_relocate_css_inline_import_browsers']['woff2'])) { + $options['#font-type'] = 'woff2'; + $options['headers']['User-Agent'] = 'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:40.0) Gecko/20100101 Firefox/40.1'; + $responses['woff2'] = drupal_http_request($url . '#woff2', $options); + if (!isset($responses['woff2']->options)) { + $responses['woff2']->options = $options; + } + if (!isset($responses[$url]->url)) { + $responses['woff2']->url = $url . '#woff2'; + } + } + } + + // Try failures again. + advagg_relocate_try_failures_again($responses); + + // Parse data. + $font_faces = array(); + $ttl = 0; + foreach ($responses as $key => $response) { + if ($response->code == 304 && !empty($cached_data->data[0])) { + // This might need to be better handled in the future. + return $cached_data->data[0]; + } + + // Set the font type if not set. + if (empty($response->options['#font-type'])) { + if (!is_numeric($key)) { + $response->options['#font-type'] = $key; + } + else { + continue; + } + } + + if ($response->code != 200 + && $response->code != 201 + && $response->code != 202 + && $response->code != 206 + ) { + return FALSE; + } + if (empty($response->data)) { + return FALSE; + } + + advagg_relocate_process_http_request($response, 'font'); + $ttl = max($ttl, $response->ttl); + + // Parse the CSS. + $font_face = advagg_relocate_parse_css_font_face( + $response->data, + array('font-family', 'font-style', 'font-weight', 'src'), + $response->options['#font-type'] + ); + + // Format into a better data structure and combine. + foreach ($font_face as $k => $values) { + if (!isset($font_faces[$k])) { + $font_faces[$k] = $font_face[$k]; + continue; + } + + foreach ($values as $index => $value) { + if (!in_array($value, $font_faces[$k])) { + if ($index === $response->options['#font-type']) { + $font_faces[$k][$index] = $values[$index]; + } + else { + $font_faces[$k][] = $values[$index]; + } + } + } + } + } + + // Save data to the cache. + if (!empty($font_faces)) { + cache_set($cid, array($font_faces, $responses), 'cache_advagg_info', REQUEST_TIME + $ttl); + } + return $font_faces; +} + +/** + * Parse the cache-control string into a key value array. + * + * @param string $cache_control + * The cache-control string. + * + * @return array + * Returns a key value array. + */ +function advagg_relocate_parse_cache_control($cache_control) { + $cache_control_array = explode(',', $cache_control); + $cache_control_array = array_map('trim', $cache_control_array); + $cache_control_parsed = array(); + foreach ($cache_control_array as $value) { + if (strpos($value, '=') !== FALSE) { + $temp = array(); + parse_str($value, $temp); + $cache_control_parsed += $temp; + } + else { + $cache_control_parsed[$value] = TRUE; + } + } + return $cache_control_parsed; +} + +/** + * Parse the font family string into a structured array. + * + * @param string $css_string + * The raw css string. + * @param array $properties + * The css properties to get. + * @param string $type + * The type of font file. + * + * @return array + * Returns a key value array. + */ +function advagg_relocate_parse_css_font_face($css_string, array $properties, $type) { + // Get the CSS that contains a font-family rule. + $length = strlen($css_string); + $property_position = 0; + $lower = strtolower($css_string); + + $attributes = array(); + foreach ($properties as $property) { + while (($property_position = strpos($lower, $property, $property_position)) !== FALSE) { + // Find the start of the values for the property. + $start_of_values = strpos($css_string, ':', $property_position); + // Get the property at this location of the css. + $property_in_loop = trim(substr($css_string, $property_position, ($start_of_values - $property_position))); + + // Make sure this property is one of the ones we're looking for. + if ($property_in_loop !== $property) { + $property_position += strlen($property); + continue; + } + + // Get position of 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 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 end of the declaration of the property. + $declaration_end_a = strpos($css_string, ';', $property_position); + $declaration_end_b = strpos($css_string, '}', $property_position); + if ($declaration_end_a === FALSE) { + $declaration_end = $declaration_end_b; + } + else { + $declaration_end = min($declaration_end_a, $declaration_end_b); + } + if ($declaration_end > $end) { + $declaration_end = $end; + } + // Add one in order to capture the } when we ge the full rule set. + $end++; + // Advance position for the next run of the while loop. + $property_position = $end; + + // Get values assigned to this property. + $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); + $values_array = array_map('trim', $values_array); + + foreach ($values_array as $key => $value) { + if (stripos($value, "'$type'") !== FALSE + || stripos($value, ".$type") !== FALSE + ) { + unset($values_array[$key]); + $values_array[$type] = $value; + } + } + $attributes[$property][] = $values_array; + } + } + + // Make sure src is the last one. + $temp = $attributes['src']; + unset($attributes['src']); + $attributes['src'] = $temp; + + // Parse attributes into an output array. + $temp = array(); + $output = array(); + foreach ($attributes as $property => $values) { + foreach ($values as $key => $value) { + if ($property !== 'src') { + + $value = implode(',', $value); + if (!isset($temp[$key])) { + $temp[$key] = ''; + } + $temp[$key] .= "$property: $value; "; + } + else { + $output[$temp[$key]] = $value; + } + } + } + + return $output; +} + +/** + * Parse the font family string into a structured array. + * + * @param string $filename + * The filename to save. + * @param string $data + * The data to save to the file. + * @param bool $local_cache + * TRUE if the data came from the cache bin. + * @param string $hash + * Contents hash; if different resave data. + * + * @return array + * Returns an array of errors that might have happened. + */ +function _advagg_relocate_save_remote_asset($filename, $data, $local_cache, $hash) { + // Save remote data. + $errors = array(); + $saved = FALSE; + $dir = variable_get('advagg_relocate_directory', ADVAGG_RELOCATE_DIRECTORY); + $full_filename = $dir . $filename; + $local_hash = ''; + if (!is_readable($full_filename)) { + $errors = advagg_save_data($full_filename, $data); + $saved = TRUE; + } + elseif (empty($local_cache)) { + $file_contents = @advagg_file_get_contents($full_filename); + if (!empty($file_contents)) { + $local_hash = drupal_hash_base64($file_contents); + } + if ($hash !== $local_hash) { + $errors = advagg_save_data($full_filename, $data, TRUE); + $saved = TRUE; + } + } + return array($full_filename, $errors, $saved); +} diff --git a/sites/all/modules/contrib/advagg/advagg_relocate/advagg_relocate.info b/sites/all/modules/contrib/advagg/advagg_relocate/advagg_relocate.info new file mode 100644 index 0000000000..0fea8be883 --- /dev/null +++ b/sites/all/modules/contrib/advagg/advagg_relocate/advagg_relocate.info @@ -0,0 +1,13 @@ +name = AdvAgg Relocate +description = Inline css import rules; also make any external file local. +package = Advanced CSS/JS Aggregation +core = 7.x +dependencies[] = advagg + +configure = admin/config/development/performance/advagg/relocate + +; Information added by Drupal.org packaging script on 2020-11-19 +version = "7.x-2.35" +core = "7.x" +project = "advagg" +datestamp = "1605792717" diff --git a/sites/all/modules/contrib/advagg/advagg_relocate/advagg_relocate.install b/sites/all/modules/contrib/advagg/advagg_relocate/advagg_relocate.install new file mode 100644 index 0000000000..17e62ead8e --- /dev/null +++ b/sites/all/modules/contrib/advagg/advagg_relocate/advagg_relocate.install @@ -0,0 +1,23 @@ +<?php + +/** + * @file + * Handles AdvAgg Relocate installation and upgrade tasks. + */ + +/** + * @addtogroup hooks + * @{ + */ + +/** + * Implements hook_install(). + */ +function advagg_relocate_install() { + // New install gets a locked admin section. + variable_set('advagg_relocate_admin_mode', 0); +} + +/** + * @} End of "addtogroup hooks". + */ diff --git a/sites/all/modules/contrib/advagg/advagg_relocate/advagg_relocate.module b/sites/all/modules/contrib/advagg/advagg_relocate/advagg_relocate.module new file mode 100644 index 0000000000..f461f7c1e2 --- /dev/null +++ b/sites/all/modules/contrib/advagg/advagg_relocate/advagg_relocate.module @@ -0,0 +1,1350 @@ +<?php + +/** + * @file + * Advanced aggregation relocate module. + */ + +/** + * @addtogroup default_variables + * @{ + */ + +/** + * Default value to see if JS is loaded locally. + */ +define('ADVAGG_RELOCATE_JS', FALSE); + +/** + * If the external file has a longer TTL then this value do not cache locally. + */ +define('ADVAGG_RELOCATE_JS_TTL', 604800); + +/** + * Minimum cache lifetime, 600 seconds by default. + */ +define('ADVAGG_RELOCATE_JS_MIN_TTL', 600); + +/** + * Default value to see if GA JS is loaded locally. + */ +define('ADVAGG_RELOCATE_JS_GA_LOCAL', FALSE); + +/** + * Default value to see if GTM JS is loaded locally. + */ +define('ADVAGG_RELOCATE_JS_GTM_LOCAL', FALSE); + +/** + * Default value to see if fbds JS is loaded locally. + */ +define('ADVAGG_RELOCATE_JS_FBDS_LOCAL', FALSE); + +/** + * Default value to see if fbevents JS is loaded locally. + */ +define('ADVAGG_RELOCATE_JS_FBEVENTS_LOCAL', FALSE); + +/** + * Default value to see if piwik JS is loaded locally. + */ +define('ADVAGG_RELOCATE_JS_PIWIK_LOCAL', FALSE); + +/** + * Default value to see if perfectaudience JS is loaded locally. + */ +define('ADVAGG_RELOCATE_JS_PERFECTAUDIENCE_LOCAL', FALSE); + +/** + * Default value to see if twitter uwt JS is loaded locally. + */ +define('ADVAGG_RELOCATE_JS_TWITTER_UWT_LOCAL', FALSE); + +/** + * Default value to see if twitter uwt JS is loaded locally. + */ +define('ADVAGG_RELOCATE_JS_LINKEDIN_INSIGHT_LOCAL', FALSE); + +/** + * Default value to see if CSS is loaded locally. + */ +define('ADVAGG_RELOCATE_CSS', FALSE); + +/** + * If the external file has a longer TTL then this value do not cache locally. + */ +define('ADVAGG_RELOCATE_CSS_TTL', 604800); + +/** + * Minimum cache lifetime, 600 seconds by default. + */ +define('ADVAGG_RELOCATE_CSS_MIN_TTL', 600); + +/** + * Default value to see if css inlining import is enabled. + */ +define('ADVAGG_RELOCATE_CSS_INLINE_IMPORT', FALSE); + +/** + * Default value to see if css inlining external css is enabled. + */ +define('ADVAGG_RELOCATE_CSS_INLINE_EXTERNAL', FALSE); + +/** + * Default value for blacklisted JS domains. + */ +define('ADVAGG_RELOCATE_JS_DOMAINS_BLACKLIST', "js.stripe.com\n"); + +/** + * Default value for blacklisted JS files. + */ +define('ADVAGG_RELOCATE_JS_FILES_BLACKLIST', ''); + +/** + * Default value for list of fbevents.js pixel ids. + */ +define('ADVAGG_RELOCATE_JS_FBEVENTS_LOCAL_IDS', ''); + +/** + * Default value for supported CSS font domains. + */ +define('ADVAGG_RELOCATE_CSS_FONT_DOMAINS', "fonts.googleapis.com"); + +/** + * Default value for blacklisted CSS domains. + */ +define('ADVAGG_RELOCATE_CSS_DOMAINS_BLACKLIST', ''); + +/** + * Default value for blacklisted CSS files. + */ +define('ADVAGG_RELOCATE_CSS_FILES_BLACKLIST', ''); + +/** + * Default value for where relocated files are kept. + */ +define('ADVAGG_RELOCATE_DIRECTORY', 'public://advagg_relocate/'); + +/** + * If 4 the admin section gets unlocked. + */ +define('ADVAGG_RELOCATE_ADMIN_MODE', 4); + +/** + * @} End of "addtogroup default_variables". + */ + +/** + * @addtogroup hooks + * @{ + */ + +/** + * Implements hook_module_implements_alter(). + */ +function advagg_relocate_module_implements_alter(&$implementations, $hook) { + // Move advagg_relocate to the top. + if ($hook === 'advagg_get_js_file_contents_alter' && array_key_exists('advagg_relocate', $implementations)) { + $item = array('advagg_relocate' => $implementations['advagg_relocate']); + unset($implementations['advagg_relocate']); + $implementations = array_merge($item, $implementations); + } +} + +/** + * Implements hook_menu(). + */ +function advagg_relocate_menu() { + $file_path = drupal_get_path('module', 'advagg_relocate'); + $config_path = advagg_admin_config_root_path(); + + $items[$config_path . '/advagg/relocate'] = array( + 'title' => 'Relocate', + 'description' => 'Move external items to be local.', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('advagg_relocate_admin_settings_form'), + 'type' => MENU_LOCAL_TASK, + 'access arguments' => array('administer site configuration'), + 'file path' => $file_path, + 'file' => 'advagg_relocate.admin.inc', + 'weight' => 10, + ); + + return $items; +} + +/** + * Implements hook_css_alter(). + */ +function advagg_relocate_css_alter(&$css) { + if (!module_exists('advagg') || !advagg_enabled()) { + return; + } + + $aggregate_settings = advagg_current_hooks_hash_array(); + // Check external css setting. + if (empty($aggregate_settings['variables']['advagg_relocate_css_inline_external'])) { + return; + } + + // Handle fonts. + $replacements = array(); + foreach ($css as $key => &$values) { + if ($values['type'] !== 'external') { + continue; + } + if (!advagg_relocate_check_domain_of_font_url($key, $aggregate_settings)) { + continue; + } + + module_load_include('advagg.inc', 'advagg_relocate'); + $font_faces = advagg_relocate_get_remote_font_data($key, $aggregate_settings); + if (empty($font_faces)) { + continue; + } + $new_css = advagg_relocate_font_face_parser($font_faces); + $values['data'] = $new_css; + $values['type'] = 'inline'; + + // Add DNS information for font domains. + $parse = @parse_url($key); + if (strpos($parse['host'], 'fonts.googleapis.com') !== FALSE) { + // Add fonts.gstatic.com when fonts.googleapis.com is added. + $values['dns_prefetch'] = 'https://fonts.gstatic.com/#crossorigin'; + $values['preload'] = 'https://fonts.gstatic.com/#crossorigin'; + } + // Move this css to the top. + if (module_exists('advagg_mod') && $aggregate_settings['variables']['advagg_mod_css_adjust_sort_external']) { + $values['group'] = CSS_SYSTEM - 1; + $values['weight'] = -50000; + $values['movable'] = FALSE; + } + // Do not move this css to the bottom. + if (module_exists('advagg_mod') && $aggregate_settings['variables']['advagg_mod_css_adjust_sort_inline']) { + $values['movable'] = FALSE; + } + + $replacements[basename($key)] = $key; + } + if (!empty($replacements)) { + $css = advagg_relocate_key_rename($css, $replacements); + } +} + +/** + * Implements hook_cron(). + */ +function advagg_relocate_cron() { + // Get filenames in directory. + $dir = rtrim(variable_get('advagg_relocate_directory', ADVAGG_RELOCATE_DIRECTORY), '/'); + $files = file_scan_directory($dir, '/.*/'); + + // Get cached objects from filenames. + $cids = array(); + foreach ($files as $info) { + $ext = strtolower(pathinfo($info->filename, PATHINFO_EXTENSION)); + $cids["advagg_relocate_{$ext}_external:{$info->filename}"] = "advagg_relocate_{$ext}_external:{$info->filename}"; + } + $cached_data = cache_get_multiple($cids, 'cache_advagg_info'); + + // Build css and js arrays. + $css = array(); + $js = array(); + foreach ($cached_data as $values) { + $url = $values->data->url; + $ext = strtolower(pathinfo($url, PATHINFO_EXTENSION)); + if ($ext === "css") { + $css[$url]['data'] = $url; + $css[$url]['type'] = 'external'; + } + elseif ($ext === "js") { + $js[$url]['data'] = $url; + $js[$url]['type'] = 'external'; + } + elseif (!empty($values->headers['content-type']) && stripos($values->headers['content-type'], 'css')) { + $css[$url]['data'] = $url; + $css[$url]['type'] = 'external'; + } + elseif (!empty($values->headers['content-type']) && stripos($values->headers['content-type'], 'javascript')) { + $js[$url]['data'] = $url; + $js[$url]['type'] = 'external'; + } + } + + // Refresh cached data. + if (!empty($js)) { + advagg_relocate_js_post_alter($js, TRUE); + } + if (!empty($css)) { + advagg_relocate_css_post_alter($css, TRUE); + } +} + +/** + * Implements hook_form_FORM_ID_alter(). + * + * Give advice on how to temporarily disable css/js aggregation. + */ +function advagg_relocate_form_advagg_admin_operations_form_alter(&$form, &$form_state) { + module_load_include('admin.inc', 'advagg_relocate'); + advagg_relocate_admin_form_advagg_admin_operations_form($form, $form_state); +} + +/** + * @} End of "addtogroup hooks". + */ + +/** + * @addtogroup advagg_hooks + * @{ + */ + +/** + * Alter the css array. + * + * @param array $css + * CSS array. + * @param bool $force_check + * TRUE if you want to force check the external source. + */ +function advagg_relocate_css_post_alter(array &$css, $force_check = FALSE) { + if (!module_exists('advagg') || !advagg_enabled()) { + return; + } + + $aggregate_settings = advagg_current_hooks_hash_array(); + + // Check external css setting. + if (empty($aggregate_settings['variables']['advagg_relocate_css'])) { + return; + } + + // Get all external css files. + $urls = _advagg_relocate_get_urls($css, 'css', $aggregate_settings); + if (empty($urls)) { + return; + } + + // Make advagg_save_data() available. + module_load_include('inc', 'advagg', 'advagg.missing'); + module_load_include('advagg.inc', 'advagg_relocate'); + $responses = advagg_relocate_get_remote_data($urls, 'css', array(), $force_check); + + // Get s3fs no_rewrite_cssjs setting. + $s3fs_no_rewrite_cssjs = advagg_get_s3fs_config('no_rewrite_cssjs'); + + $filenames = array(); + $advagg_relocate_css_ttl = variable_get('advagg_relocate_css_ttl', ADVAGG_RELOCATE_CSS_TTL); + foreach ($responses as $value) { + if (!empty($advagg_relocate_css_ttl) && $value->ttl >= $advagg_relocate_css_ttl) { + continue; + } + + $rehash = FALSE; + // Handle @import statements. + if (strpos($value->data, '@import') !== FALSE) { + // Handle "local" import statements. + advagg_relocate_load_stylesheet_local(array(), dirname($value->url) . '/'); + $value->data = preg_replace_callback('%@import\s*+(?:url\(\s*+)?+[\'"]?+(?![a-z]++:|/)([^\'"()\s]++)[\'"]?+\s*+\)?+\s*+;%i', 'advagg_relocate_load_stylesheet_local', $value->data); + + // Replace external import statements with the contents of them. + $value->data = preg_replace_callback('%@import\s*+(?:url\(\s*+)?+[\'"]?+((?:http:\/\/|https:\/\/|\/\/)(?:[^\'"()\s]++))[\'"]?+\s*+\)?+\s*+;%i', 'advagg_relocate_load_stylesheet_external', $value->data); + $rehash = TRUE; + } + // Fix external url references. + if (strpos($value->data, 'url(') !== FALSE) { + module_load_include('inc', 'advagg', 'advagg'); + // Set anchor point for local url() statements in the css. + _advagg_build_css_path(array(), dirname($value->url) . '/'); + // Anchor all paths in the CSS with its base URL, ignoring external, + // absolute paths, and urls that start with # or %23 (SVG). + $value->data = preg_replace_callback('%url\(\s*+[\'"]?+(?![a-z]++:|/|\#|\%23+)([^\'"\)]++)[\'"]?+\s*+\)%i', '_advagg_build_css_path', $value->data); + $rehash = TRUE; + } + + if ($rehash) { + $value->hash = drupal_hash_base64($value->data); + } + + // Save remote data. + list($full_filename, $errors, $saved) = _advagg_relocate_save_remote_asset($value->options['filename'], $value->data, $value->local_cache, $value->hash); + if (!empty($errors)) { + continue; + } + + // Replace remote data with local data. + $relative_path = advagg_get_relative_path($full_filename); + $key = $urls[$value->options['filename']]; + $css = advagg_relocate_key_rename($css, array($relative_path => $key)); + $css[$relative_path]['pre_relocate_data'] = $css[$relative_path]['data']; + $css[$relative_path]['data'] = $relative_path; + $css[$relative_path]['type'] = 'file'; + if (defined('ADVAGG_MOD_CSS_PREPROCESS') && variable_get('advagg_mod_css_preprocess', ADVAGG_MOD_CSS_PREPROCESS) && empty($css[$relative_path]['preprocess_lock'])) { + $css[$relative_path]['preprocess'] = TRUE; + } + + // Handle domain prefectch. + $key_host = parse_url($key, PHP_URL_HOST); + if (!empty($css[$relative_path]['dns_prefetch'])) { + foreach ($css[$relative_path]['dns_prefetch'] as $key => $domain_name) { + if (strpos($domain_name, $key_host) !== FALSE && strpos($value->data, $key_host)) { + unset($css[$relative_path]['dns_prefetch'][$key]); + } + } + } + // List of files that need the cache cleared. + if ($saved) { + $filenames[] = (!is_null($s3fs_no_rewrite_cssjs) && empty($s3fs_no_rewrite_cssjs)) ? $full_filename : $relative_path; + } + } + if (!empty($filenames)) { + module_load_include('inc', 'advagg'); + module_load_include('cache.inc', 'advagg'); + $files = advagg_get_info_on_files($filenames, TRUE); + advagg_push_new_changes($files); + } +} + +/** + * Alter the js array. + * + * @param string $key + * Key that can be used to lookup the value from the js array. + * @param array $value + * Inner part of the js array. + * @param array $aggregate_settings + * Array of settings. + * + * @return array + * An array of inline scripts found and locations for them in the file. + */ +function advagg_relocate_js_script_rewrite_list($key, array $value, array $aggregate_settings) { + $scripts_found = array(); + // Handle analytics.js. + if (!empty($aggregate_settings['variables']['advagg_relocate_js_ga_local'])) { + $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()'); + $middle = strpos($value['data'], '})(window,document,"script",', $start); + $end = strpos($value['data'], ',"ga");ga("create",', $middle); + // Found the GA code. + if ($start !== FALSE && $middle !== FALSE && $end !== FALSE) { + $scripts_found['analytics.js'] = array($key, $start, $middle, $end); + } + } + + // Handle piwik.js. + if (!empty($aggregate_settings['variables']['advagg_relocate_js_piwik_local'])) { + $start = strpos($value['data'], 'var _paq'); + $middle = strpos($value['data'], '_paq.push(["setTrackerUrl"', $start); + // Skip if not the paq code. + if ($start !== FALSE && $middle !== FALSE) { + $scripts_found['piwik.js'] = array($key, $start, $middle); + } + } + + // Handle gtm.js. + if (!empty($aggregate_settings['variables']['advagg_relocate_js_gtm_local'])) { + $start = strpos($value['data'], '(function(w,d,s,l,i){'); + $middle = strpos($value['data'], 'var f=d.getElementsByTagName(s)[0]', $start); + $end = strpos($value['data'], '})(window,document,', $middle); + // Skip if not the GTM code. + if ($start !== FALSE && $middle !== FALSE && $end !== FALSE) { + $scripts_found['gtm.js'] = array($key, $start, $middle, $end); + } + } + + // Handle fbds.js. + if (!empty($aggregate_settings['variables']['advagg_relocate_js_fbds_local'])) { + $start = strpos($value['data'], 'var _fbq'); + $middle = strpos($value['data'], '(!_fbq.loaded)', $start); + $end = strpos($value['data'], 's.parentNode.insertBefore(fbds', $middle); + // Skip if not the fbds code. + if ($start !== FALSE && $middle !== FALSE && $end !== FALSE) { + $scripts_found['fbds.js'] = array($key, $start, $middle, $end); + } + } + + // Handle fbevents.js. + if (!empty($aggregate_settings['variables']['advagg_relocate_js_fbevents_local'])) { + $end = strpos($value['data'], 'connect.facebook.net/en_US/fbevents.js'); + // Skip if not the fbevents code. + if ($end !== FALSE) { + // Get middle of string. + $matches = array(); + preg_match('/fbq\s*=\s*function\(\)/', $value['data'], $matches, PREG_OFFSET_CAPTURE); + if (!empty($matches[0][1])) { + $middle = $matches[0][1]; + // Get start of string. + $matches = array(); + preg_match('/\!\s*function\(f,b,e,v,n,t,s\)/', $value['data'], $matches, PREG_OFFSET_CAPTURE); + if (isset($matches[0][1])) { + $start = $matches[0][1]; + if ($middle - $start <= 90) { + $scripts_found['fbevents.js'] = array($key, $start, $middle, $end); + } + } + } + } + } + + // Handle perfectaudience.js. + if (!empty($aggregate_settings['variables']['advagg_relocate_js_perfectaudience_local'])) { + $matches = array(); + preg_match('/window\._pa\s*=\s*window._pa\s*\|\|\s*\{\s*\}\s*;/', $value['data'], $matches, PREG_OFFSET_CAPTURE); + if (!empty($matches[0][1])) { + $start = $matches[0][1]; + $middle = strpos($value['data'], '//tag.perfectaudience.com/serve/', $start); + $end = strpos($value['data'], 's.parentNode.insertBefore(pa, s);', $middle); + // Add if perfectaudience code. + if ($start !== FALSE && $middle !== FALSE && $end !== FALSE) { + $scripts_found['perfectaudience.js'] = array( + $key, + $start, + $middle, + $end, + ); + } + } + + // Handle twitter uwt.js. + if (!empty($aggregate_settings['variables']['advagg_relocate_js_twitter_uwt_local'])) { + $start = strpos($value['data'], '!function(e,t,n,s,u,a){e.twq||'); + $middle = strpos($value['data'], '//static.ads-twitter.com/uwt.js', $start); + $end = strpos($value['data'], "a.parentNode.insertBefore(u,a))}(window,document,'script');", $middle); + // Add in twitter uwt.js code. + if ($start !== FALSE && $middle !== FALSE && $end !== FALSE) { + $scripts_found['uwt.js'] = array($key, $start, $middle, $end); + } + } + + // Handle linkedin insight.js. + if (!empty($aggregate_settings['variables']['advagg_relocate_js_linkedin_insight_local'])) { + $start = strpos($value['data'], '_linkedin_data_partner_id'); + $middle = strpos($value['data'], '//snap.licdn.com/li.lms-analytics/insight.min.js', $start); + $end = strpos($value['data'], "s.parentNode.insertBefore(b, s)", $middle); + // Add in linkedin insight.js code. + if ($start !== FALSE && $middle !== FALSE && $end !== FALSE) { + $scripts_found['linkedin_insight.js'] = array( + $key, + $start, + $middle, + $end, + ); + } + } + } + + return $scripts_found; +} + +/** + * Add external. + * + * @param array $js + * JS array. + * @param array $scripts_found + * An array of inline scripts found and locations for them in the file. + */ +function advagg_relocate_js_script_rewrite(array &$js, array $scripts_found) { + // Move inline code to external if possible. + foreach ($scripts_found as $key_name => $values) { + if ($key_name === 'analytics.js') { + list($key, $start, $middle, $end) = $values; + $value = $js[$key]; + + // Get analytics.js URL and add it to the js array. + $analytics_js_url = substr($value['data'], $middle + 29, $end - strlen($value['data']) - 1); + $insert = array( + $analytics_js_url => array( + 'data' => $analytics_js_url, + 'type' => 'external', + 'async' => TRUE, + 'defer' => TRUE, + 'noasync' => FALSE, + 'nodefer' => FALSE, + ), + ); + $js = advagg_insert_into_array_at_key($js, $insert, $key); + $js[$analytics_js_url] += $value; + + // Fix if analytics.js is already local. + $http_pos = strpos($analytics_js_url, 'http://'); + $https_pos = strpos($analytics_js_url, 'https://'); + $double_slash_pos = strpos($analytics_js_url, '//'); + if ($http_pos !== 0 + && $https_pos !== 0 + && $double_slash_pos !== 0 + ) { + $js[$analytics_js_url]['type'] = 'file'; + } + + // Shorten function arguments. + $value['data'] = substr($value['data'], 0, $middle + 27) . substr($value['data'], $end); + // Strip loader string. + $value['data'] = substr($value['data'], 0, $start + 132) . substr($value['data'], $middle); + // Shorten function parameters. + $value['data'] = str_replace('function(i,s,o,g,r,a,m){i["GoogleAnalyticsObject"]', 'function(i,s,o,r){i["GoogleAnalyticsObject"]', $value['data']); + $js[$key]['pre_relocate_data'] = $js[$key]['data']; + $js[$key]['data'] = $value['data']; + // Pin to header as the js expects ga to be there. + $js[$key]['scope'] = 'header'; + $js[$key]['scope_lock'] = TRUE; + + // Handle ga("require", ...) calls for external scripts to be loaded. + $matches = array(); + preg_match_all('/ga\([\'"]require[\'"],\s*[\'"][a-zA-Z0-9_ ]*[\'"],\s*[\'"](.*?)[\'"]\)/', $value['data'], $matches, PREG_SET_ORDER); + foreach ($matches as $match) { + // Remove inline js loader code. + $start = strpos($js[$key]['data'], $match[0]); + if ($start === FALSE) { + continue; + } + $strlen = strlen($match[0]); + $js[$key]['data'] = substr($js[$key]['data'], 0, $start) . substr($js[$key]['data'], $start + $strlen); + + // Add script to the drupal js array. + $url = '//www.google-analytics.com/plugins/ua/' . $match[1]; + $insert = array( + $url => array( + 'data' => $url, + 'type' => 'external', + 'async' => TRUE, + 'defer' => TRUE, + 'noasync' => FALSE, + 'nodefer' => FALSE, + ), + ); + $js = advagg_insert_into_array_at_key($js, $insert, $analytics_js_url); + $js[$url] += $value; + } + } + + if ($key_name === 'piwik.js') { + list($key, $start, $middle) = $values; + $value = $js[$key]; + + // Get URL. + $url = variable_get('matomo_url_http', ''); + if ($GLOBALS['is_https']) { + // Use HTTPS. + $url = variable_get('matomo_url_https', ''); + } + if (is_callable('_matomo_cache') + && variable_get('matomo_cache', 0) + && $matomo_cache = _matomo_cache($url . 'piwik.js') + ) { + // Try cache. + $url = $matomo_cache; + } + if (empty($url)) { + // Extract info from inline script. + // "https:" == document.location.protocol ? "https://.../piwik/" : "http://.../piwik/". + $pattern = '/[\'"]https\:[\'"]\s*==\s*document\.location\.protocol.*?[\'"](.*?)[\'"]\s*\:\s*[\'"](.*?)[\'"]/'; + preg_match($pattern, $value['data'], $matches); + if ($GLOBALS['is_https'] && !empty($matches[1])) { + $url = $matches[1]; + } + elseif (empty($GLOBALS['is_https']) && !empty($matches[2])) { + $url = $matches[2]; + } + } + if (!empty($url)) { + // Use HTTP. + // The $url may or may not have "piwik.js" at the end (it has if it came from _matomo_cache call above, otherwise not). Add if needed. + $url = check_url($url); + if (basename($url) != 'piwik.js') { + $url = $url . 'piwik.js'; + } + } + + // Final checks. + if (!empty($url)) { + $scope = variable_get('matomo_js_scope', $value['scope']); + $url = advagg_convert_abs_to_rel($url, TRUE); + $type = 'file'; + if (advagg_is_external($url)) { + $parsed = parse_url($url); + if (strpos($parsed['path'], $GLOBALS['base_path']) === 0) { + $path = substr($parsed['path'], strlen($GLOBALS['base_path'])); + if (file_exists($path)) { + $url = $path; + } + else { + $type = 'external'; + } + } + else { + $type = 'external'; + } + } + + // Add to js array. + $insert = array( + $url => array( + 'scope' => $scope, + 'data' => $url, + 'type' => $type, + 'async' => TRUE, + 'defer' => TRUE, + 'noasync' => FALSE, + 'nodefer' => FALSE, + ), + ); + $js = advagg_insert_into_array_at_key($js, $insert, $key); + + $matches = array(); + // s.parentNode.insertBefore(g,s);. + $pattern = '/\,?\s*[\w]{1,2}\.parentNode\.insertBefore\(\s*[\w]{1,2}\s*,\s*[\w]{1,2}\s*\)\s*\;*/'; + preg_match($pattern, $value['data'], $matches); + // Strip loader string. + $value['data'] = str_replace($matches[0], '', $value['data']); + + $js[$key]['pre_relocate_data'] = $js[$key]['data']; + $js[$key]['data'] = $value['data']; + // Pin to header as the js expects paq to be loaded before the file. + $js[$key]['scope'] = 'header'; + $js[$key]['scope_lock'] = TRUE; + $js[$key]['weight'] = -50000; + $js[$key]['movable'] = FALSE; + } + } + + if ($key_name === 'gtm.js') { + list($key, $start, $middle, $end) = $values; + $value = $js[$key]; + + // Get URL for script. + $matches_a = array(); + preg_match('/j.src\s*=\s*[\'"](.*?);/', $value['data'], $matches_a); + $matches_b = array(); + preg_match('/\}\)\(window,document,[\'"]script[\'"],[\'"](.*?)[\'"],[\'"](.*?)[\'"]\);/', $value['data'], $matches_b); + if (empty($matches_a[1]) || empty($matches_b[1])) { + continue; + } + if ($matches_b[1] !== 'dataLayer') { + $matches_b[1] = '&l=' . $matches_b[1]; + } + else { + $matches_b[1] = ''; + } + $gtm_url = trim(str_replace(array( + "'+i", + "+dl+'", + "+dl", + ), array( + $matches_b[2], + $matches_b[1], + $matches_b[1], + ), $matches_a[1]), "'"); + + // Add script to the drupal js array. + $insert = array( + $gtm_url => array( + 'data' => $gtm_url, + 'type' => 'external', + 'async' => TRUE, + 'defer' => TRUE, + 'noasync' => FALSE, + 'nodefer' => FALSE, + ), + ); + $js = advagg_insert_into_array_at_key($js, $insert, $key); + $js[$gtm_url] += $value; + + // Shorten function arguments. + $args = explode(',', substr($value['data'], $end + 3, -2)); + $value['data'] = substr($value['data'], 0, $end + 9) . ",{$args[3]});"; + // Strip loader string. + $value['data'] = substr($value['data'], 0, $middle) . substr($value['data'], $end); + // Shorten function parameters. + $value['data'] = str_replace('(function(w,d,s,l,i){', '(function(w,l){', $value['data']); + // Add back. + $js[$key]['pre_relocate_data'] = $js[$key]['data']; + $js[$key]['data'] = $value['data']; + // Pin to header as the js expects dataLayer to be there. + $js[$key]['scope'] = 'header'; + $js[$key]['scope_lock'] = TRUE; + } + + if ($key_name === 'fbds.js') { + list($key, $start, $middle, $end) = $values; + $value = $js[$key]; + + // Get URL for script. + $matches = array(); + preg_match('/fbds.src\s*=\s*[\'"](.*?)[\'"];/', $value['data'], $matches); + if (empty($matches[1])) { + continue; + } + $url = trim($matches[1]); + + // Add script to the drupal js array. + $insert = array( + $url => array( + 'data' => $url, + 'type' => 'external', + 'async' => TRUE, + 'defer' => TRUE, + 'noasync' => FALSE, + 'nodefer' => FALSE, + ), + ); + $js = advagg_insert_into_array_at_key($js, $insert, $key); + $js[$url] += $value; + + // Strip loader string. + $matches = array(); + preg_match('/if\s*\(!_fbq.loaded\)\s*\{/', $value['data'], $matches, PREG_OFFSET_CAPTURE); + $new_js = substr($value['data'], 0, $matches[0][1]); + // Set loaded TRUE. + $matches = array(); + preg_match('/_fbq.loaded\s*=\s*true/', $value['data'], $matches, PREG_OFFSET_CAPTURE); + $new_js .= $matches[0][0] . ';'; + // Get the rest of the JS string. + $end = strpos($value['data'], '}', $matches[0][1]); + $new_js .= trim(substr($value['data'], $end + 1)); + $js[$key]['pre_relocate_data'] = $js[$key]['data']; + $js[$key]['data'] = $new_js; + // Pin to header as the js expects _fbq to be there. + $js[$key]['scope'] = 'header'; + $js[$key]['scope_lock'] = TRUE; + } + + if ($key_name === 'fbevents.js') { + list($key, $start, $middle, $end) = $values; + $value = $js[$key]; + + // Add script to the drupal js array. + $url = 'https://connect.facebook.net/en_US/fbevents.js'; + $insert = array( + $url => array( + 'data' => $url, + 'type' => 'external', + ), + ); + $js = advagg_insert_into_array_at_key($js, $insert, $key); + $js[$url] += $value; + + $before = substr($value['data'], 0, $start); + $after = ltrim(substr($value['data'], $end + 40), ';'); + + // Get all facebook pixel ids. + $fb_ids = array_filter(array_map('trim', explode("\n", variable_get('advagg_relocate_js_fbevents_local_ids', ADVAGG_RELOCATE_JS_FBEVENTS_LOCAL_IDS)))); + $matches = array(); + preg_match('/fbq\(\s*[\'"]init[\'"]\s*,\s*[\'"](\d+)[\'"]\s*\)/', $value['data'], $matches); + if (!empty($matches[1])) { + $fb_ids[] = $matches[1]; + } + $fb_ids = array_filter($fb_ids); + + // Update in place the ids if any others were found inline. + $GLOBALS['conf']['advagg_relocate_js_fbevents_local_ids'] = implode("\n", $fb_ids); + + // Get scripts before and after; replace middle of script. + $new_js = $before . "!function(f,b,e,v,n,t,s){if(f.fbq)return;n=f.fbq=function(){n.callMethod?n.callMethod.apply(n,arguments):n.queue.push(arguments)};if(!f._fbq)f._fbq=n;n.push=n;n.loaded=!0;n.version='2.0';n.queue=[];t=b.createElement(e);t.async=!0;t.src=v;s=b.getElementsByTagName(e)[0]}(window,document,'script','//connect.facebook.net/en_US/fbevents.js');" . $after; + $js[$key]['pre_relocate_data'] = $js[$key]['data']; + $js[$key]['data'] = $new_js; + // Pin to header as the js expects fbq to be there. + $js[$key]['scope'] = 'header'; + $js[$key]['scope_lock'] = TRUE; + } + + if ($key_name === 'perfectaudience.js') { + list($key, $start, $middle, $end) = $values; + $value = $js[$key]; + + // Reindex. + $matches = array(); + preg_match('/window\._pa\s*=\s*window._pa\s*\|\|\s*\{\s*\}\s*;/', $value['data'], $matches, PREG_OFFSET_CAPTURE); + if (!empty($matches[0][1])) { + $start = $matches[0][1]; + $middle = strpos($value['data'], '//tag.perfectaudience.com/serve/', $start); + $end = strpos($value['data'], 's.parentNode.insertBefore(pa, s);', $middle); + + $substr = substr($value['data'], $start, $end - $start + 35); + $matches = array(); + preg_match('/\.src\s*=\s*[\'"](\/\/tag.perfectaudience.com\/serve\/.*\.js)[\'"]/', $substr, $matches); + if (!empty($matches[1])) { + $url = $matches[1]; + + // Add script to the drupal js array. + $insert = array( + $url => array( + 'data' => $url, + 'type' => 'external', + 'async' => TRUE, + 'defer' => TRUE, + 'noasync' => FALSE, + 'nodefer' => FALSE, + ), + ); + $js = advagg_insert_into_array_at_key($js, $insert, $key); + $js[$url] += $value; + + // s.parentNode.insertBefore(pa, s);. + $pattern = '/\,?\s*[\w]{1,2}\.parentNode\.insertBefore\(\s*[\w]{1,2}\s*,\s*[\w]{1,2}\s*\)\s*\;*/'; + // Strip loader string. + $new_substr = preg_replace($pattern, '', $substr); + $js[$key]['data'] = str_replace($substr, $new_substr, $value['data']); + } + } + } + + if ($key_name === 'uwt.js') { + list($key, $start, $middle, $end) = $values; + $value = $js[$key]; + + $url = '//static.ads-twitter.com/uwt.js'; + // Add script to the drupal js array. + $insert = array( + $url => array( + 'data' => $url, + 'type' => 'external', + 'scope_lock' => FALSE, + 'scope' => 'footer', + ), + ); + $js = advagg_insert_into_array_at_key($js, $insert, $key); + $js[$url] += $value; + + // Reindex. + $start = strpos($value['data'], '!function(e,t,n,s,u,a){e.twq||'); + $middle = strpos($value['data'], '//static.ads-twitter.com/uwt.js', $start); + $end = strpos($value['data'], "a.parentNode.insertBefore(u,a))}(window,document,'script');", $middle); + + $substr = substr($value['data'], $start, $end - $start + 61); + // a.parentNode.insertBefore(u,a). + $pattern = '/\,?\s*[\w]{1,2}\.parentNode\.insertBefore\(\s*[\w]{1,2}\s*,\s*[\w]{1,2}\s*\)\s*\;*/'; + // Strip loader string. + $new_substr = preg_replace($pattern, '', $substr); + $js[$key]['data'] = str_replace($substr, $new_substr, $value['data']); + } + + if ($key_name === 'linkedin_insight.js') { + list($key, $start, $middle, $end) = $values; + $value = $js[$key]; + + $url = 'https://snap.licdn.com/li.lms-analytics/insight.min.js'; + // Add script to the drupal js array. + $insert = array( + $url => array( + 'data' => $url, + 'type' => 'external', + 'async' => TRUE, + 'defer' => TRUE, + 'noasync' => FALSE, + 'nodefer' => FALSE, + ), + ); + $js = advagg_insert_into_array_at_key($js, $insert, $key); + $js[$url] += $value; + + $start = strpos($value['data'], '_linkedin_data_partner_id'); + $middle = strpos($value['data'], '//snap.licdn.com/li.lms-analytics/insight.min.js', $start); + $end = strpos($value['data'], "s.parentNode.insertBefore(b, s)", $middle); + + $substr = substr($value['data'], $start, $end - $start + 35); + // a.parentNode.insertBefore(u,a). + $pattern = '/\,?\s*[\w]{1,2}\.parentNode\.insertBefore\(\s*[\w]{1,2}\s*,\s*[\w]{1,2}\s*\)\s*\;*/'; + // Strip loader string. + $new_substr = preg_replace($pattern, '', $substr); + $js[$key]['data'] = str_replace($substr, $new_substr, $value['data']); + } + } +} + +/** + * Alter the js array. + * + * @param array $js + * JS array. + * @param bool $force_check + * TRUE if you want to force check the external source. + */ +function advagg_relocate_js_post_alter(array &$js, $force_check = FALSE) { + if (!module_exists('advagg') || !advagg_enabled()) { + return; + } + + // Check external js setting. + $aggregate_settings = advagg_current_hooks_hash_array(); + if (empty($aggregate_settings['variables']['advagg_relocate_js'])) { + return; + } + + // Look for inline scrtips that add external js via inline code. + $scripts_found = array(); + module_load_include('inc', 'advagg', 'advagg'); + + $temp_inserts = array(); + foreach ($js as $key => $value) { + if ($value['type'] === 'inline') { + $scripts_found += advagg_relocate_js_script_rewrite_list($key, $value, $aggregate_settings); + } + if ($value['type'] === 'file') { + $info = advagg_get_info_on_file($key); + if (!empty($info['advagg_relocate'])) { + // Get the file contents. + $file_contents = (string) @advagg_file_get_contents($info['data']); + if (empty($file_contents)) { + continue; + } + $value['data'] = $file_contents; + $temp_inserts[$key] = array("advagg_relocate:{$key}" => $value); + $scripts_found += advagg_relocate_js_script_rewrite_list("advagg_relocate:{$key}", $value, $aggregate_settings); + } + } + } + + // Add in file as inline so replacement works. + if (!empty($temp_inserts)) { + foreach ($temp_inserts as $key => $value) { + $js = advagg_insert_into_array_at_key($js, $value, $key); + } + } + + // Rewrite inline scrtips and add external references to js array. + advagg_relocate_js_script_rewrite($js, $scripts_found); + + // Remove temp inline code. + if (!empty($temp_inserts)) { + foreach ($temp_inserts as $value) { + reset($value); + $key = key($value); + unset($js[$key]); + } + } + + // Get all external js files. + $urls = _advagg_relocate_get_urls($js, 'js', $aggregate_settings); + if (empty($urls)) { + return; + } + + // Make advagg_save_data() available. + module_load_include('inc', 'advagg', 'advagg.missing'); + module_load_include('advagg.inc', 'advagg_relocate'); + $responses = advagg_relocate_get_remote_data($urls, 'js', array(), $force_check); + + // Get s3fs no_rewrite_cssjs setting. + $s3fs_no_rewrite_cssjs = advagg_get_s3fs_config('no_rewrite_cssjs'); + + $filenames = array(); + $advagg_relocate_js_ttl = variable_get('advagg_relocate_js_ttl', ADVAGG_RELOCATE_JS_TTL); + foreach ($responses as $value) { + // If the external file has a longer TTL than 1 week, do not cache. + if (!empty($advagg_relocate_js_ttl) && $value->ttl >= $advagg_relocate_js_ttl) { + continue; + } + + list($full_filename, $errors, $saved) = _advagg_relocate_save_remote_asset($value->options['filename'], $value->data, $value->local_cache, $value->hash); + if (!empty($errors)) { + watchdog('advagg-relocate', 'Write failed. Errors: <pre><tt>@errors</tt></pre>', array( + '@errors' => $errors, + )); + continue; + } + + // Replace remote data with local data. + $relative_path = advagg_get_relative_path($full_filename); + $key = $urls[$value->options['filename']]; + $js = advagg_relocate_key_rename($js, array($relative_path => $key)); + $js[$relative_path]['pre_relocate_data'] = $js[$relative_path]['data']; + $js[$relative_path]['data'] = $relative_path; + $js[$relative_path]['type'] = 'file'; + if (defined('ADVAGG_MOD_JS_PREPROCESS') && variable_get('advagg_mod_js_preprocess', ADVAGG_MOD_JS_PREPROCESS) && empty($js[$relative_path]['preprocess_lock'])) { + $js[$relative_path]['preprocess'] = TRUE; + } + + // Check for any .write( statements in the JS code. + if (strpos($value->data, '.write(') !== FALSE) { + if ((!isset($js[$relative_path]['noasync']) || $js[$relative_path]['noasync'] !== FALSE) + || (!isset($js[$relative_path]['nodefer']) || $js[$relative_path]['nodefer'] !== FALSE) + ) { + $js[$relative_path]['async'] = FALSE; + $js[$relative_path]['defer'] = FALSE; + $js[$relative_path]['noasync'] = TRUE; + $js[$relative_path]['nodefer'] = TRUE; + } + } + + // Handle domain prefectch. + $parse = @parse_url($key); + $key_host = ''; + if (!empty($parse['host'])) { + $key_host = $parse['host']; + } + if (!empty($key_host) && !empty($js[$relative_path]['dns_prefetch'])) { + foreach ($js[$relative_path]['dns_prefetch'] as $key => $domain_name) { + if (strpos($domain_name, $key_host) !== FALSE && strpos($value->data, $key_host)) { + unset($js[$relative_path]['dns_prefetch'][$key]); + } + } + } + + // Make sure the external reference has been removed. + $schemes = array('//', 'http', 'https'); + foreach ($schemes as $scheme) { + $parse['scheme'] = $scheme; + $key = advagg_glue_url($parse); + if (isset($js[$key])) { + unset($js[$key]); + } + } + + // List of files that need the cache cleared. + if ($saved) { + $filenames[] = (!is_null($s3fs_no_rewrite_cssjs) && empty($s3fs_no_rewrite_cssjs)) ? $full_filename : $relative_path; + } + } + + if (!empty($filenames)) { + module_load_include('inc', 'advagg'); + module_load_include('cache.inc', 'advagg'); + $files = advagg_get_info_on_files($filenames, TRUE); + advagg_push_new_changes($files); + } +} + +/** + * Implements hook_advagg_current_hooks_hash_array_alter(). + */ +function advagg_relocate_advagg_current_hooks_hash_array_alter(&$aggregate_settings) { + $aggregate_settings['variables']['advagg_relocate_css_inline_import'] = variable_get('advagg_relocate_css_inline_import', ADVAGG_RELOCATE_CSS_INLINE_IMPORT); + + $defaults = array( + 'woff2' => 'woff2', + 'woff' => 'woff', + 'ttf' => 'ttf', + ); + $aggregate_settings['variables']['advagg_relocate_css_inline_import_browsers'] = variable_get('advagg_relocate_css_inline_import_browsers', $defaults); + + $aggregate_settings['variables']['advagg_relocate_css_file_settings'] = variable_get('advagg_relocate_css_file_settings', array()); + + $aggregate_settings['variables']['advagg_relocate_css_font_domains'] = variable_get('advagg_relocate_css_font_domains', ADVAGG_RELOCATE_CSS_FONT_DOMAINS); + + $types = array('css' => 'CSS', 'js' => 'JS'); + foreach ($types as $type_lower => $type_upper) { + $domains_blacklist = array_filter(array_map('trim', explode("\n", variable_get("advagg_relocate_{$type_lower}_domains_blacklist", constant("ADVAGG_RELOCATE_{$type_upper}_DOMAINS_BLACKLIST"))))); + $aggregate_settings['variables']["advagg_relocate_{$type_lower}_domains_blacklist"] = $domains_blacklist; + + $files_blacklist = array_filter(array_map('trim', explode("\n", variable_get("advagg_relocate_{$type_lower}_files_blacklist", constant("ADVAGG_RELOCATE_{$type_upper}_FILES_BLACKLIST"))))); + $aggregate_settings['variables']["advagg_relocate_{$type_lower}_files_blacklist"] = $files_blacklist; + } + + $aggregate_settings['variables']['advagg_relocate_css_inline_external'] = variable_get('advagg_relocate_css_inline_external', ADVAGG_RELOCATE_CSS_INLINE_EXTERNAL); + + $aggregate_settings['variables']['advagg_relocate_css'] = variable_get('advagg_relocate_css', ADVAGG_RELOCATE_CSS); + + $aggregate_settings['variables']['advagg_relocate_js'] = variable_get('advagg_relocate_js', ADVAGG_RELOCATE_JS); + $aggregate_settings['variables']['advagg_relocate_js_ga_local'] = variable_get('advagg_relocate_js_ga_local', ADVAGG_RELOCATE_JS_GA_LOCAL); + $aggregate_settings['variables']['advagg_relocate_js_gtm_local'] = variable_get('advagg_relocate_js_gtm_local', ADVAGG_RELOCATE_JS_GTM_LOCAL); + $aggregate_settings['variables']['advagg_relocate_js_fbds_local'] = variable_get('advagg_relocate_js_fbds_local', ADVAGG_RELOCATE_JS_FBDS_LOCAL); + $aggregate_settings['variables']['advagg_relocate_js_fbevents_local'] = variable_get('advagg_relocate_js_fbevents_local', ADVAGG_RELOCATE_JS_FBEVENTS_LOCAL); + $aggregate_settings['variables']['advagg_relocate_js_piwik_local'] = variable_get('advagg_relocate_js_piwik_local', ADVAGG_RELOCATE_JS_PIWIK_LOCAL); + $aggregate_settings['variables']['advagg_relocate_js_perfectaudience_local'] = variable_get('advagg_relocate_js_perfectaudience_local', ADVAGG_RELOCATE_JS_PERFECTAUDIENCE_LOCAL); + $aggregate_settings['variables']['advagg_relocate_js_twitter_uwt_local'] = variable_get('advagg_relocate_js_twitter_uwt_local', ADVAGG_RELOCATE_JS_TWITTER_UWT_LOCAL); + $aggregate_settings['variables']['advagg_relocate_js_linkedin_insight_local'] = variable_get('advagg_relocate_js_linkedin_insight_local', ADVAGG_RELOCATE_JS_LINKEDIN_INSIGHT_LOCAL); + +} + +/** + * @} End of "addtogroup advagg_hooks". + */ + +/** + * Convert local @import statements to external. + * + * @param array $matches + * Array of matched items from preg_replace_callback(). + * @param string $url + * URL of where the original CSS is located. + * + * @return string + * New import statement. + */ +function advagg_relocate_load_stylesheet_local(array $matches, $url = '') { + $_url = &drupal_static(__FUNCTION__, ''); + // Store base path for preg_replace_callback. + if (!empty($url)) { + $_url = $url; + } + // Short circuit if no matches were passed in. + if (empty($matches)) { + return ''; + } + + $css_url = $_url . $matches[1]; + return "@import \"{$css_url}\";"; +} + +/** + * Convert external @import statements to be local. + * + * @param array $matches + * Array of matched items from preg_replace_callback(). + * + * @return string + * Contents of the import statement or new import statement. + */ +function advagg_relocate_load_stylesheet_external(array $matches) { + // Build css array. + $css = array( + $matches[1] => array( + 'type' => 'external', + 'data' => $matches[1], + ), + ); + // Recursively pull in imported fonts. + advagg_relocate_css_alter($css); + + // Remove '../' segments where possible. + $values = reset($css); + if ($values['type'] !== 'inline') { + $last = ''; + $url = $values['data']; + while ($url != $last) { + $last = $url; + $url = preg_replace('`(^|/)(?!\.\./)([^/]+)/\.\./`', '$1', $url); + } + // Build css array. + $css = array( + $url => array( + 'type' => $values['type'], + 'data' => $url, + ), + ); + } + // Recursively pull in external references. + advagg_relocate_css_post_alter($css); + + $values = reset($css); + $key = key($css); + if ($values['type'] === 'inline') { + return "/* Contents of $key */\n{$values['data']}"; + } + else { + if (!advagg_is_external($values['data'])) { + $dir = variable_get('advagg_relocate_directory', ADVAGG_RELOCATE_DIRECTORY); + $path = advagg_get_relative_path($dir) . '/'; + $values['data'] = str_replace($path, '', $values['data']); + } + return "@import \"{$values['data']}\";"; + } +} + +/** + * Return a filename => url array for external assets. + * + * @param array $data + * CSS or JS data array. + * @param string $type + * Either css or js. + * @param array $aggregate_settings + * Array of settings. + * + * @return array + * Array of external assets to be served locally. + */ +function _advagg_relocate_get_urls(array $data, $type, array $aggregate_settings) { + $domains_blacklist = $aggregate_settings['variables']["advagg_relocate_{$type}_domains_blacklist"]; + $files_blacklist = $aggregate_settings['variables']["advagg_relocate_{$type}_files_blacklist"]; + $urls = array(); + foreach ($data as $key => $value) { + // Get all external js files. + if ($value['type'] !== 'external') { + continue; + } + // If no_relocate=TRUE, do not move it to be local. + if (!empty($value['no_relocate'])) { + continue; + } + if (empty($value['data'])) { + $value['data'] = $key; + } + $host = parse_url($value['data'], PHP_URL_HOST); + if (!empty($domains_blacklist)) { + foreach ($domains_blacklist as $domain) { + if ($domain === $host) { + continue 2; + } + } + } + if (!empty($files_blacklist)) { + foreach ($files_blacklist as $file) { + if (strpos($file, $host) !== FALSE) { + continue 2; + } + } + } + + // Encode the URL into a filename. Force HTTPS. + $filename = advagg_url_to_filename(advagg_force_https_path($value['data'])); + // Make sure it ends with .css or .js. + if (stripos(strrev($filename), strrev($type)) !== 0) { + $filename .= ".$type"; + } + $urls[$filename] = $key; + } + return $urls; +} + +/** + * Verifies that the external CSS file is from a domain we allow for inlining. + * + * @param string $url + * The full URL of the css file. + * @param array $aggregate_settings + * Array of settings. + * + * @return bool + * TRUE if the URL can be inlined. + */ +function advagg_relocate_check_domain_of_font_url($url, array $aggregate_settings) { + // Bail if the host or path and query string are empty. + $parse = @parse_url($url); + if (empty($parse) + || empty($parse['host']) + || (empty($parse['path']) && empty($parse['query'])) + ) { + return FALSE; + } + + // Bail if the host doesn't match one of the listed domains. + if (!isset($aggregate_settings['variables']['advagg_relocate_css_font_domains'])) { + $aggregate_settings['variables']['advagg_relocate_css_font_domains'] = variable_get('advagg_relocate_css_font_domains', ADVAGG_RELOCATE_CSS_FONT_DOMAINS); + } + if (strpos($aggregate_settings['variables']['advagg_relocate_css_font_domains'], $parse['host']) === FALSE) { + return FALSE; + } + return TRUE; +} + +/** + * Replace JS key with another key. + * + * @param array $input + * Input array. + * @param array $replacements + * Key value pair; key is the new key, value is the old key. + */ +function advagg_relocate_key_rename(array $input, array $replacements) { + $output = array(); + foreach ($input as $k => $v) { + $replacement_key = array_search($k, $replacements, TRUE); + if ($replacement_key !== FALSE) { + $output[$replacement_key] = $v; + } + else { + $output[$k] = $v; + } + } + return $output; +} + +/** + * Perform a cache flush of the advagg relocate module. + */ +function advagg_relocate_flush_cache_button() { + cache_clear_all('advagg_relocate_', 'cache_advagg_info', TRUE); + drupal_set_message(t('AdvAgg Relocate Cache Cleared.')); +} diff --git a/sites/all/modules/contrib/advagg/advagg_sri/advagg_sri.admin.inc b/sites/all/modules/contrib/advagg/advagg_sri/advagg_sri.admin.inc new file mode 100644 index 0000000000..d971fbfbc8 --- /dev/null +++ b/sites/all/modules/contrib/advagg/advagg_sri/advagg_sri.admin.inc @@ -0,0 +1,55 @@ +<?php + +/** + * @file + * Admin page callbacks for the advagg sri module. + */ + +/** + * Form builder; Configure advagg settings. + * + * @ingroup advagg_forms + * + * @see system_settings_form() + */ +function advagg_sri_admin_settings_form() { + drupal_set_title(t('AdvAgg: Subresource Integrity')); + advagg_display_message_if_requirements_not_met(); + + $form = array(); + $form['advagg_sri'] = array( + '#type' => 'radios', + '#title' => t('Subresource Integrity Level'), + '#default_value' => variable_get('advagg_sri', ADVAGG_SRI), + '#options' => array( + 0 => t('Disabled'), + 1 => t('SHA-256'), + 2 => t('SHA-384'), + 3 => t('SHA-512'), + ), + ); + + if (module_exists('httprl')) { + $form['advagg_sri_file_generation'] = array( + '#type' => 'checkbox', + '#title' => t('Always output the page with the subresource integrity attribute.'), + '#default_value' => variable_get('advagg_sri_file_generation', ADVAGG_SRI_FILE_GENERATION), + '#description' => t('If checked - background processes will not usually be used when generating aggregated files; sometimes resulting in a slower page load. Noted though that the page cache is disabled if the all the aggregates do not have the integrity attribute.'), + ); + } + + // Clear the cache bins on submit. + $form['#submit'][] = 'advagg_sri_admin_settings_form_submit'; + + return system_settings_form($form); +} + +/** + * Submit callback, clears out the advagg cache bin. + * + * @ingroup advagg_forms_callback + */ +function advagg_sri_admin_settings_form_submit($form, &$form_state) { + // Clear caches. + advagg_cache_clear_admin_submit(); +} diff --git a/sites/all/modules/contrib/advagg/advagg_sri/advagg_sri.advagg.inc b/sites/all/modules/contrib/advagg/advagg_sri/advagg_sri.advagg.inc new file mode 100644 index 0000000000..16ca56ad6a --- /dev/null +++ b/sites/all/modules/contrib/advagg/advagg_sri/advagg_sri.advagg.inc @@ -0,0 +1,240 @@ +<?php + +/** + * @file + * Advanced aggregation sri module. + */ + +/** + * @addtogroup advagg_hooks + * @{ + */ + +/** + * Implements hook_advagg_save_aggregate_alter(). + * + * Save the hash of the file. + */ +function advagg_sri_advagg_save_aggregate_alter(array &$files_to_save, array $aggregate_settings, array $other_parameters) { + // * @param array $files_to_save + // * Array($uri => $contents). + // * @param array $aggregate_settings + // * Array of settings. + // * @param array $other_parameters + // * Array of containing $files and $type. + foreach ($files_to_save as $uri => $contents) { + // Skip gzip/brotli files. + $ext = strtolower(pathinfo($uri, PATHINFO_EXTENSION)); + if ($ext === 'gz' || $ext === 'br') { + continue; + } + + $hashes = advagg_sri_compute_hashes($contents); + + // Save to the database. + $filename = basename($uri); + advagg_sri_set_filename_hashes($filename, $hashes); + } +} + +/** + * Implements hook_advagg_build_aggregate_plans_post_alter(). + */ +function advagg_sri_advagg_build_aggregate_plans_post_alter(array &$plans) { + // * @param array $plans + // * Array of aggregate files. + $advagg_sri = variable_get('advagg_sri', ADVAGG_SRI); + if (empty($advagg_sri)) { + return; + } + if ($advagg_sri == 1) { + $sha_bits = 'sha256'; + } + if ($advagg_sri == 2) { + $sha_bits = 'sha384'; + } + if ($advagg_sri == 3) { + $sha_bits = 'sha512'; + } + + // Get all aggregates. + $files = array(); + $filenames = array(); + foreach ($plans as $key => $values) { + if ($values['type'] !== 'file' || empty($values['cache'])) { + continue; + } + $files[$values['filename']] = $key; + $filenames[$values['filepath']] = $values['filename']; + } + + // Lookup hashes. + $hashes = array(); + if (!empty($filenames)) { + $hashes = advagg_sri_get_filenames_hashes($filenames); + } + + // Set attributes. + foreach ($hashes as $filename => $hash) { + if (isset($files[$filename]) && isset($plans[$files[$filename]])) { + $plans[$files[$filename]]['attributes']['integrity'] = $sha_bits . '-' . $hash[$sha_bits]; + } + } +} + +/** + * Implements hook_advagg_build_aggregate_plans_alter(). + */ +function advagg_sri_advagg_build_aggregate_plans_alter() { + if (module_exists('httprl') && variable_get('advagg_use_httprl', ADVAGG_USE_HTTPRL) && variable_get('advagg_sri_file_generation', ADVAGG_SRI_FILE_GENERATION)) { + $GLOBALS['conf']['advagg_use_httprl'] = FALSE; + } +} + +/** + * Implements hook_advagg_missing_root_file(). + */ +function advagg_sri_advagg_missing_root_file($aggregate_filename, $filename, $cache) { + // Remove entries from the DB. + if (!empty($aggregate_filename) && !empty($cache)) { + advagg_sri_del_filename_hashes($aggregate_filename); + } + // Remove entries from the Cache. + $ext = strtolower(pathinfo($aggregate_filename, PATHINFO_EXTENSION)); + $cid = "advagg:$ext:"; + cache_clear_all($cid, 'cache_advagg_aggregates', TRUE); +} + +/** + * Implements hook_advagg_removed_aggregates(). + */ +function advagg_sri_advagg_removed_aggregates($kill_list) { + foreach ($kill_list as $uri) { + if (is_object($uri) && property_exists($uri, 'uri')) { + $temp = $uri->uri; + unset($uri); + $uri = $temp; + } + // Skip gzip/brotli files. + $ext = strtolower(pathinfo($uri, PATHINFO_EXTENSION)); + if ($ext === 'gz' || $ext === 'br') { + continue; + } + $filename = basename($uri); + advagg_sri_del_filename_hashes($filename); + } +} + +/** + * @} End of "addtogroup advagg_hooks". + */ + +/** + * Store settings associated with hash. + * + * @param string $filename + * The name of the aggregated filename. + * @param array $hashes + * An array of SHA hashes of the contents of this filename. + * + * @return MergeQuery + * value from db_merge + */ +function advagg_sri_set_filename_hashes($filename, array $hashes) { + return db_merge('advagg_sri') + ->key(array('filename' => $filename)) + ->fields(array( + 'filename' => $filename, + 'hashes' => serialize($hashes), + )) + ->execute(); +} + +/** + * Store settings associated with hash. + * + * @param string $filename + * The name of the aggregated filename. + * + * @return DeleteQuery + * value from db_delete + */ +function advagg_sri_del_filename_hashes($filename) { + return db_delete('advagg_sri') + ->condition('filename', $filename) + ->execute(); +} + +/** + * Returns the hashes settings. + * + * @param array $filenames + * An array of filenames. + * + * @return array + * The hashes for the given filenames. + */ +function advagg_sri_get_filenames_hashes(array $filenames) { + $hashes = array(); + // Do not use the DB if in development mode. + if (variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL) < 0) { + $rows = db_select('advagg_sri', 'advagg_sri') + ->fields('advagg_sri', array('filename', 'hashes')) + ->condition('filename', $filenames, 'IN') + ->execute(); + foreach ($rows as $row) { + $hashes[$row->filename] = unserialize($row->hashes); + } + } + + // If the hash is not in the database, generate it on demand. + $db_filenames = array_keys($hashes); + $not_in_db = array_diff($filenames, $db_filenames); + foreach ($not_in_db as $file) { + $filepath = array_search($file, $filenames); + if (is_readable($filepath)) { + // Do not use advagg_file_get_contents here. + $contents = (string) @file_get_contents($filepath); + if (!empty($contents)) { + $hashes[$file] = advagg_sri_compute_hashes($contents); + advagg_sri_set_filename_hashes($file, $hashes[$file]); + } + } + } + + // Check to make sure we have all hashes. + $all_hashes = array_keys($hashes); + $not_hashed = array_diff($filenames, $all_hashes); + if (!empty($not_hashed)) { + drupal_page_is_cacheable(FALSE); + // Disable saving to the cache if a sri is missing. + if (variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL) > 1) { + $GLOBALS['conf']['advagg_cache_level'] = 0; + } + + if (!module_exists('httprl') || !variable_get('advagg_use_httprl', ADVAGG_USE_HTTPRL)) { + watchdog('advagg_sri', 'The subresource integrity hashes could not be generated for these files: %files', array('%files' => print_r($not_hashed, TRUE))); + } + } + + return $hashes; +} + +/** + * Returns an array of hashes. + * + * @param string $contents + * The string to hash. + * + * @return array + * The hashes for the given string. + */ +function advagg_sri_compute_hashes($contents) { + // Generate hashes. + $hashes = array( + 'sha512' => base64_encode(hash('sha512', $contents, TRUE)), + 'sha384' => base64_encode(hash('sha384', $contents, TRUE)), + 'sha256' => base64_encode(hash('sha256', $contents, TRUE)), + ); + return $hashes; +} diff --git a/sites/all/modules/contrib/advagg/advagg_sri/advagg_sri.info b/sites/all/modules/contrib/advagg/advagg_sri/advagg_sri.info new file mode 100644 index 0000000000..2289cb8026 --- /dev/null +++ b/sites/all/modules/contrib/advagg/advagg_sri/advagg_sri.info @@ -0,0 +1,13 @@ +name = AdvAgg Subresource Integrity +description = Provide the integrity attribute for CSS and JS files. +package = Advanced CSS/JS Aggregation +core = 7.x +dependencies[] = advagg + +configure = admin/config/development/performance/advagg/sri + +; Information added by Drupal.org packaging script on 2020-11-19 +version = "7.x-2.35" +core = "7.x" +project = "advagg" +datestamp = "1605792717" diff --git a/sites/all/modules/contrib/advagg/advagg_sri/advagg_sri.install b/sites/all/modules/contrib/advagg/advagg_sri/advagg_sri.install new file mode 100644 index 0000000000..c95c1e9dc3 --- /dev/null +++ b/sites/all/modules/contrib/advagg/advagg_sri/advagg_sri.install @@ -0,0 +1,75 @@ +<?php + +/** + * @file + * Handles Advanced Aggregation installation and upgrade tasks. + */ + +/** + * @addtogroup hooks + * @{ + */ + +/** + * Implements hook_install(). + */ +function advagg_sri_install() { + module_load_include('install', 'advagg', 'advagg'); + $tables = array( + 'advagg_sri' => array( + 'filename', + ), + ); + + $schema = advagg_sri_schema(); + foreach ($tables as $table => $fields) { + // Change utf8_bin to ascii_bin. + advagg_install_change_table_collation($table, $fields, 'ascii_bin', $schema[$table]['fields']); + } +} + +/** + * Implements hook_uninstall(). + */ +function advagg_sri_uninstall() { + // Remove variables. + db_delete('variable') + ->condition('name', 'advagg_sri%', 'LIKE') + ->execute(); +} + +/** + * Implements hook_schema(). + */ +function advagg_sri_schema() { + $schema = array(); + + // Copy the variable table and change a couple of things. + $schema['advagg_sri'] = drupal_get_schema_unprocessed('system', 'variable'); + + // Create the filename field. + $schema['advagg_sri']['fields']['filename'] = $schema['advagg_sri']['fields']['name']; + $schema['advagg_sri']['fields']['filename']['length'] = 143; + $schema['advagg_sri']['fields']['filename']['description'] = 'The name of the aggregate.'; + $schema['advagg_sri']['fields']['filename']['binary'] = TRUE; + $schema['advagg_sri']['fields']['filename']['collation'] = 'ascii_bin'; + $schema['advagg_sri']['fields']['filename']['charset'] = 'ascii'; + $schema['advagg_sri']['fields']['filename']['mysql_character_set'] = 'ascii'; + + // Create the hashes field. + $schema['advagg_sri']['fields']['hashes'] = $schema['advagg_sri']['fields']['value']; + $schema['advagg_sri']['fields']['hashes']['description'] = 'The hashes associated with this filename.'; + + // Set primary key and table description. + $schema['advagg_sri']['description'] = 'Stores sha hashes of this file.'; + $schema['advagg_sri']['primary key'][0] = 'filename'; + + // Remove the name and value fileds. + unset($schema['advagg_sri']['fields']['name'], $schema['advagg_sri']['fields']['value']); + + return $schema; +} + +/** + * @} End of "addtogroup hooks". + */ diff --git a/sites/all/modules/contrib/advagg/advagg_sri/advagg_sri.module b/sites/all/modules/contrib/advagg/advagg_sri/advagg_sri.module new file mode 100644 index 0000000000..2bd92e18c2 --- /dev/null +++ b/sites/all/modules/contrib/advagg/advagg_sri/advagg_sri.module @@ -0,0 +1,80 @@ +<?php + +/** + * @file + * Advanced aggregation sri module. + */ + +/** + * @addtogroup default_variables + * @{ + */ + +/** + * Default value of the SHA hash level. + */ +define('ADVAGG_SRI', 0); + +/** + * Default value to force SRI to always be generated. + */ +define('ADVAGG_SRI_FILE_GENERATION', FALSE); + +/** + * @} End of "addtogroup default_variables". + */ + +/** + * @addtogroup hooks + * @{ + */ + +/** + * Implements hook_menu(). + */ +function advagg_sri_menu() { + $file_path = drupal_get_path('module', 'advagg_sri'); + $config_path = advagg_admin_config_root_path(); + + $items[$config_path . '/advagg/sri'] = array( + 'title' => 'Subresource Integrity', + 'description' => 'Hash aggregated files.', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('advagg_sri_admin_settings_form'), + 'type' => MENU_LOCAL_TASK, + 'access arguments' => array('administer site configuration'), + 'file path' => $file_path, + 'file' => 'advagg_sri.admin.inc', + 'weight' => 10, + ); + + return $items; +} + +/** + * Implements hook_module_implements_alter(). + */ +function advagg_sri_module_implements_alter(&$implementations, $hook) { + // Move advagg_sri to the bottom. + if ($hook === 'advagg_save_aggregate_alter' && array_key_exists('advagg_sri', $implementations)) { + $item = $implementations['advagg_sri']; + unset($implementations['advagg_sri']); + $implementations['advagg_sri'] = $item; + } +} + +/** + * Implements hook_form_FORM_ID_alter(). + */ +function advagg_sri_form_advagg_admin_settings_form_alter(&$form, $form_state) { + // Disable httprl if SRI is set to always on. + if (variable_get('advagg_sri_file_generation', ADVAGG_SRI_FILE_GENERATION)) { + $form['global']['advagg_use_httprl']['#default_value'] = FALSE; + $form['global']['advagg_use_httprl']['#disabled'] = TRUE; + $form['global']['advagg_use_httprl']['#description'] = t('The Subresource Integrity submodule has disabled httprl usage. This is the "Always output the page with the subresource integrity attribute" checkbox.'); + } +} + +/** + * @} End of "addtogroup hooks". + */ diff --git a/sites/all/modules/contrib/advagg/advagg_validator/advagg_validator.admin.inc b/sites/all/modules/contrib/advagg/advagg_validator/advagg_validator.admin.inc index eb4024164e..af591211e8 100644 --- a/sites/all/modules/contrib/advagg/advagg_validator/advagg_validator.admin.inc +++ b/sites/all/modules/contrib/advagg/advagg_validator/advagg_validator.admin.inc @@ -5,14 +5,26 @@ * Admin page callbacks for the advagg validator module. */ +/** + * @addtogroup default_variables + * @{ + */ + +/** + * @} End of "addtogroup default_variables". + */ + /** * Form builder; CSS validator via w3. * * @return array * form array. + * + * @ingroup advagg_forms */ function advagg_validator_admin_css_w3_form() { drupal_set_title(t('AdvAgg: CSS Validator using w3.org')); + advagg_display_message_if_requirements_not_met(); $form = advagg_validator_admin_form_generator('css', FALSE); @@ -30,21 +42,48 @@ function advagg_validator_admin_css_w3_form() { * * @return array * form array. + * + * @ingroup advagg_forms */ function advagg_validator_admin_css_lint_form() { drupal_set_title(t('AdvAgg: CSS Validator using CSSLINT')); + advagg_display_message_if_requirements_not_met(); - $form = advagg_validator_admin_form_generator('css', TRUE); + // Tell user to update library if a new version is available. + $module_name = 'advagg_validator'; + $lib_name = 'csslint'; + list($description) = advagg_get_version_description($lib_name, $module_name, TRUE); + $form = array(); + if (!empty($description)) { + $form['advagg_version_msg'] = array( + '#markup' => "<p>{$description}</p>", + ); + } - $form['#attached']['js'][] = array( - 'data' => '//rawgithub.com/stubbornella/csslint/master/release/csslint.js', - 'type' => 'external', - ); + $form += advagg_validator_admin_form_generator('css', TRUE); + $library = advagg_get_library('csslint', 'advagg_validator'); + if (!empty($library['installed'])) { + $form['#attached']['libraries_load'][] = array('csslint'); + } + else { + $form['#attached']['js'] = $library['variants']['external']['files']['js']; + } $form['#attached']['js'][] = array( 'data' => drupal_get_path('module', 'advagg_validator') . '/advagg_validator.js', 'type' => 'file', ); + // Comma separated code. + // https://github.com/CSSLint/csslint/wiki/Command-line-interface#--ignore + $ignore_list = variable_get('advagg_validator_csslint_ignore', ADVAGG_VALIDATOR_CSSLINT_IGNORE); + if (is_array($ignore_list)) { + $ignore_list = implode(',', $ignore_list); + } + $form['#attached']['js'][] = array( + 'data' => array('csslint' => array('ignore' => $ignore_list)), + 'type' => 'setting', + ); + return $form; } @@ -53,16 +92,34 @@ function advagg_validator_admin_css_lint_form() { * * @return array * form array. + * + * @ingroup advagg_forms */ function advagg_validator_admin_js_hint_form() { drupal_set_title(t('AdvAgg: JavaScript Validator using JSHINT')); + advagg_display_message_if_requirements_not_met(); + + // Tell user to update library if a new version is available. + $module_name = 'advagg_validator'; + $lib_name = 'jshint'; + $form = array(); + list($description) = advagg_get_version_description($lib_name, $module_name, TRUE); + if (!empty($description)) { + $form['advagg_version_msg'] = array( + '#markup' => "<p>{$description}</p>", + ); + } $form = advagg_validator_admin_form_generator('js', TRUE); + $library = advagg_get_library('jshint', 'advagg_validator'); - $form['#attached']['js'][] = array( - 'data' => '//rawgithub.com/jshint/jshint/master/dist/jshint.js', - 'type' => 'external', - ); + if (!empty($library['installed'])) { + libraries_load('jshint'); + $form['#attached']['libraries_load'][] = array('jshint'); + } + else { + $form['#attached']['js'] = $library['variants']['external']['files']['js']; + } $form['#attached']['js'][] = array( 'data' => drupal_get_path('module', 'advagg_validator') . '/advagg_validator.js', 'type' => 'file', @@ -93,6 +150,15 @@ function advagg_validator_admin_js_hint_form() { 'CKEDITOR' => FALSE, ), ); + + // Comma separated code. + // https://jslinterrors.com/ + $ignore_list = variable_get('advagg_validator_jshint_ignore', ADVAGG_VALIDATOR_JSHINT_IGNORE); + if (is_array($ignore_list)) { + $ignore_list = implode(',', $ignore_list); + } + $settings['ignore'] = $ignore_list; + $form['#attached']['js'][] = array( 'data' => array('jshint' => $settings), 'type' => 'setting', @@ -126,7 +192,7 @@ function advagg_validator_admin_form_generator($type, $run_client_side) { $point = &$form; $built = array(); foreach ($levels as $key => $value) { - // Build direcotry structure, + // Build direcotry structure. $built[] = $value; $point = &$point[$value]; if (!is_array($point)) { @@ -232,14 +298,18 @@ function advagg_validator_admin_form_generator($type, $run_client_side) { return $form; } +/** + * @addtogroup advagg_forms_callback + * @{ + */ -// Submit callbacks. /** - * Display file info in a drupal message. + * Submit callback, display file info in a drupal message. */ function advagg_validator_admin_test_advagg_css_submit($form, &$form_state) { module_load_include('inc', 'advagg_validator', 'advagg_validator'); + // @codingStandardsIgnoreLine if (!empty($form_state['input']['ajax_page_state'])) { return; } @@ -282,6 +352,7 @@ function advagg_validator_admin_test_advagg_css_callback($form, &$form_state) { * Display file info in a drupal message. */ function advagg_validator_admin_test_advagg_css_directory_submit($form, &$form_state) { + // @codingStandardsIgnoreLine if (!empty($form_state['input']['ajax_page_state']) || empty($form_state['values']['op']) || strpos($form_state['values']['op'], t('Check this Directory:')) !== 0) { return; } @@ -356,6 +427,7 @@ function advagg_validator_admin_test_advagg_css_directory_callback($form, &$form * Display file info in a drupal message. */ function advagg_validator_admin_test_advagg_css_subdirectory_submit($form, &$form_state) { + // @codingStandardsIgnoreLine if (!empty($form_state['input']['ajax_page_state']) || empty($form_state['values']['op']) || strpos($form_state['values']['op'], t('Check this Directory and all Subdirectories:')) !== 0) { return; } @@ -430,6 +502,7 @@ function advagg_validator_admin_test_advagg_css_subdirectory_callback($form, &$f function advagg_validator_admin_test_all_css_submit($form, &$form_state) { module_load_include('inc', 'advagg_validator', 'advagg_validator'); + // @codingStandardsIgnoreLine if (!empty($form_state['input']['ajax_page_state'])) { return; } @@ -468,6 +541,10 @@ function advagg_validator_admin_test_all_css_callback($form, &$form_state) { return '<div id="advagg-validator-css-validator-ajax">' . $output . '</div>'; } +/** + * @} End of "addtogroup advagg_forms_callback". + */ + /** * Do not display info on a file if it is valid. * diff --git a/sites/all/modules/contrib/advagg/advagg_validator/advagg_validator.advagg.inc b/sites/all/modules/contrib/advagg/advagg_validator/advagg_validator.advagg.inc new file mode 100644 index 0000000000..e5d58fc897 --- /dev/null +++ b/sites/all/modules/contrib/advagg/advagg_validator/advagg_validator.advagg.inc @@ -0,0 +1,31 @@ +<?php + +/** + * @file + * Advanced CSS/JS aggregation validator module. + */ + +/** + * @addtogroup advagg_hooks + * @{ + */ + +/** + * Implements hook_advagg_update_github_versions_alter(). + */ +function advagg_validator_advagg_update_github_versions_alter(&$projects) { + $projects['csslint'] = array( + 'callback' => 'advagg_get_github_version', + 'url' => 'https://cdn.jsdelivr.net/gh/CSSLint/csslint@master/package.json', + 'variable_name' => 'advagg_validator_github_version_csslint', + ); + $projects['jshint'] = array( + 'callback' => 'advagg_get_github_version', + 'url' => 'https://cdn.jsdelivr.net/gh/jshint/jshint@master/package.json', + 'variable_name' => 'advagg_validator_github_version_jshint', + ); +} + +/** + * @} End of "addtogroup advagg_hooks". + */ diff --git a/sites/all/modules/contrib/advagg/advagg_validator/advagg_validator.inc b/sites/all/modules/contrib/advagg/advagg_validator/advagg_validator.inc index 9686d53e84..e4177899ee 100644 --- a/sites/all/modules/contrib/advagg/advagg_validator/advagg_validator.inc +++ b/sites/all/modules/contrib/advagg/advagg_validator/advagg_validator.inc @@ -16,7 +16,7 @@ function advagg_validator_test_advagg_css($options = array()) { $query_files = db_select('advagg_files', 'af') ->fields('af', array('filename_hash', 'filename')) ->condition('af.filetype', 'css') - ->orderBy('filename', 'DESC') + ->orderBy('filename', 'ASC') ->execute() ->fetchAllKeyed(); $files = array_values($query_files); @@ -74,7 +74,7 @@ function advagg_validator_test_css_files($files, $options = array()) { continue; } - $file_contents = file_get_contents($filename); + $file_contents = (string) @advagg_file_get_contents($filename); $lines = file($filename); $filename_hash = drupal_hash_base64($filename); $content_hash = drupal_hash_base64($file_contents); @@ -134,7 +134,7 @@ function advagg_validator_test_css_files($files, $options = array()) { db_merge('advagg_validator') ->key(array( 'filename_hash' => $record['filename_hash'], - )) + )) ->fields($record) ->execute(); } @@ -153,9 +153,9 @@ function advagg_validator_test_css_files($files, $options = array()) { * @return array * Info from the w3c server. */ -function advagg_validator_test_css_file_w3c($filename, &$validator_options = array()) { +function advagg_validator_test_css_file_w3c($filename, array &$validator_options = array()) { // Get CSS files contents. - $validator_options['text'] = file_get_contents($filename); + $validator_options['text'] = (string) @advagg_file_get_contents($filename); // Add in defaults. $validator_options += array( @@ -175,7 +175,7 @@ function advagg_validator_test_css_file_w3c($filename, &$validator_options = arr // Send request. $result = drupal_http_request($url); if (!empty($result->data) && $result->code == 200) { - // Parse XML & return info. + // Parse XML and return info. $return = advagg_validator_parse_soap_response_w3c($result->data); $return['filename'] = $filename; if (isset($validator_options['text'])) { @@ -298,7 +298,7 @@ function advagg_validator_dom_extractor($dom) { * An associative array (keyed on the chosen key) of objects with 'uri', * 'filename', and 'name' members corresponding to the matching files. */ -function advagg_validator_file_scan_directory($dir, $mask, $options = array(), $depth = 0) { +function advagg_validator_file_scan_directory($dir, $mask, array $options = array(), $depth = 0) { // Merge in defaults. $options += array( 'nomask' => '/(\.\.?|CVS)$/', diff --git a/sites/all/modules/contrib/advagg/advagg_validator/advagg_validator.info b/sites/all/modules/contrib/advagg/advagg_validator/advagg_validator.info index 5845cb7c89..82a1c39b63 100644 --- a/sites/all/modules/contrib/advagg/advagg_validator/advagg_validator.info +++ b/sites/all/modules/contrib/advagg/advagg_validator/advagg_validator.info @@ -1,14 +1,14 @@ name = AdvAgg CSS/JS Validator -description = Validate the CSS & JS files used in Aggregation for syntax errors. +description = Validate the CSS and JS files used in Aggregation for syntax errors. package = Advanced CSS/JS Aggregation core = 7.x dependencies[] = advagg +recommends[] = libraries configure = admin/config/development/performance/advagg/validator -; Information added by Drupal.org packaging script on 2015-04-14 -version = "7.x-2.8" +; Information added by Drupal.org packaging script on 2020-11-19 +version = "7.x-2.35" core = "7.x" project = "advagg" -datestamp = "1429049283" - +datestamp = "1605792717" diff --git a/sites/all/modules/contrib/advagg/advagg_validator/advagg_validator.install b/sites/all/modules/contrib/advagg/advagg_validator/advagg_validator.install index 618b4e91e0..e513487d27 100644 --- a/sites/all/modules/contrib/advagg/advagg_validator/advagg_validator.install +++ b/sites/all/modules/contrib/advagg/advagg_validator/advagg_validator.install @@ -5,6 +5,69 @@ * Handles AdvAgg Validator installation and upgrade tasks. */ +/** + * @addtogroup hooks + * @{ + */ + +/** + * Implements hook_install(). + */ +function advagg_validator_install() { + module_load_include('install', 'advagg', 'advagg'); + $tables = array( + 'advagg_validator' => array( + 'filename_hash', + 'content_hash', + ), + ); + + $schema = advagg_validator_schema(); + foreach ($tables as $table => $fields) { + // Change utf8_bin to ascii_bin. + advagg_install_change_table_collation($table, $fields, 'ascii_bin', $schema[$table]['fields']); + } +} + +/** + * Implements hook_requirements(). + */ +function advagg_validator_requirements($phase) { + $requirements = array(); + // Ensure translations don't break at install time. + $t = get_t(); + + // If not at runtime, return here. + if ($phase !== 'runtime') { + return $requirements; + } + + // Check version. + $module_name = 'advagg_validator'; + $lib_name = 'csslint'; + list($description, $info) = advagg_get_version_description($lib_name, $module_name, TRUE); + if (!empty($description)) { + $requirements["{$module_name}_{$lib_name}_updates"] = array( + 'title' => $t('@module_name', array('@module_name' => $info['name'])), + 'severity' => REQUIREMENT_WARNING, + 'value' => $t('The @name library needs to be updated.', array('@name' => $lib_name)), + 'description' => $description, + ); + } + $lib_name = 'jshint'; + list($description, $info) = advagg_get_version_description($lib_name, $module_name, TRUE); + if (!empty($description)) { + $requirements["{$module_name}_{$lib_name}_updates"] = array( + 'title' => $t('@module_name', array('@module_name' => $info['name'])), + 'severity' => REQUIREMENT_WARNING, + 'value' => $t('The @name library needs to be updated.', array('@name' => $lib_name)), + 'description' => $description, + ); + } + + return $requirements; +} + /** * Implements hook_schema(). */ @@ -20,20 +83,24 @@ function advagg_validator_schema() { 'not null' => TRUE, ), 'filename_hash' => array( - 'description' => 'Hash of path and filename. Used to join tables & for lookup.', - 'type' => 'varchar', + 'description' => 'Hash of path and filename. Used to join tables and for lookup.', + 'type' => 'char', 'length' => 43, 'not null' => TRUE, 'default' => '', 'binary' => TRUE, + 'collation' => 'ascii_bin', + 'charset' => 'ascii', ), 'content_hash' => array( 'description' => 'Hash of the file content. Used to see if the file has changed.', - 'type' => 'varchar', + 'type' => 'char', 'length' => 43, 'not null' => TRUE, 'default' => '', 'binary' => TRUE, + 'collation' => 'ascii_bin', + 'charset' => 'ascii', ), 'filetype' => array( 'description' => 'Filetype.', @@ -63,8 +130,6 @@ function advagg_validator_schema() { } /** - * Implements hook_update_N(). - * * Update the schema making the varchar columns utf8_bin in MySQL. */ function advagg_validator_update_7201(&$sandbox) { @@ -86,3 +151,30 @@ function advagg_validator_update_7201(&$sandbox) { return t('The following columns inside of these database tables where converted to utf8_bin: <br />@data', array('@data' => print_r($tables_altered, TRUE))); } + +/** + * Update schema making the varchar columns char. Change utf8_bin to ascii_bin. + */ +function advagg_validator_update_7202(&$sandbox) { + module_load_include('install', 'advagg', 'advagg'); + $tables = array( + 'advagg_validator' => array( + 'filename_hash', + 'content_hash', + ), + ); + + $schema = advagg_validator_schema(); + foreach ($tables as $table => $fields) { + foreach ($fields as $field) { + // Change varchar to char. + db_change_field($table, $field, $field, $schema[$table]['fields'][$field]); + } + // Change utf8_bin to ascii_bin. + advagg_install_change_table_collation($table, $fields, 'ascii_bin', $schema[$table]['fields']); + } +} + +/** + * @} End of "addtogroup hooks". + */ diff --git a/sites/all/modules/contrib/advagg/advagg_validator/advagg_validator.js b/sites/all/modules/contrib/advagg/advagg_validator/advagg_validator.js index a6d2a3a24d..cf86cd6d71 100644 --- a/sites/all/modules/contrib/advagg/advagg_validator/advagg_validator.js +++ b/sites/all/modules/contrib/advagg/advagg_validator/advagg_validator.js @@ -1,18 +1,18 @@ -/* global jQuery:false */ -/* global Drupal:false */ -/* global JSHINT:false */ -/* global CSSLint:false */ - /** * @file * Run JSHINT in the browser against the servers JS. */ +/* global jQuery:false */ +/* global Drupal:false */ +/* global JSHINT:false */ +/* global CSSLint:false */ + /** * Have clicks to advagg_validator_js classes run JSHINT clientside. */ (function ($) { - "use strict"; + 'use strict'; Drupal.behaviors.advagg_validator_js_simple = { attach: function (context, settings) { $('.advagg_validator_js', context).click(function (context) { @@ -21,7 +21,7 @@ // Clear out the results. $(results).html(''); // Loop over each filename. - $.each($(this).siblings('.filenames'), function() { + $.each($(this).siblings('.filenames'), function () { var filename = $(this).val(); if (filename) { try { @@ -31,14 +31,14 @@ dataType: 'text', async: false }); - if (JSHINT(x.responseText, Drupal.settings.jshint, Drupal.settings.jshint.predef)) { - $(results).append('<h4>' + filename + ' Passed!</h4>'); - } - else { - $(results).append('<p><h4>' + filename + ' Failed!</h4>'); - $(results).append('<ul>'); + if (!JSHINT(x.responseText, Drupal.settings.jshint, Drupal.settings.jshint.predef)) { + $(results).append('<p><h4>' + filename + '</h4><ul>'); for (var i = 0; i < JSHINT.errors.length; i++) { - $(results).append('<li><b>' + JSHINT.errors[i].line + ':</b> ' + JSHINT.errors[i].reason + '</li>'); + var ignore = (Drupal.settings.jshint && Drupal.settings.jshint.ignore) ? Drupal.settings.jshint.ignore.split(',') : []; + if (ignore.indexOf(JSHINT.errors[i].code) === -1) { + var w = JSHINT.errors[i].reason + ' (line ' + JSHINT.errors[i].line + ', col ' + JSHINT.errors[i].character + ', rule ' + JSHINT.errors[i].code + ')'; + $(results).append('<li class="' + JSHINT.errors[i].id.replace(/[()]/g, '') + '">' + w.replace(/ /g, ' ') + '</li>'); + } } $(results).append('</ul></p>'); } @@ -59,7 +59,7 @@ * Have clicks to advagg_validator_recursive_js classes run JSHINT clientside. */ (function ($) { - "use strict"; + 'use strict'; Drupal.behaviors.advagg_validator_js_recursive = { attach: function (context, settings) { $('.advagg_validator_recursive_js', context).click(function (context) { @@ -68,7 +68,7 @@ // Clear out the results. $(results).html(''); // Loop over each filename. - $.each($(this).parent().find('.filenames'), function() { + $.each($(this).parent().find('.filenames'), function () { var filename = $(this).val(); if (filename) { try { @@ -78,13 +78,14 @@ dataType: 'text', async: false }); - if (JSHINT(x.responseText, Drupal.settings.jshint, Drupal.settings.jshint.predef)) { - $(results).append('<h4>' + filename + ' Passed!</h4>'); - } else { - $(results).append('<p><h4>' + filename + ' Failed!</h4>'); - $(results).append('<ul>'); + if (!JSHINT(x.responseText, Drupal.settings.jshint, Drupal.settings.jshint.predef)) { + $(results).append('<p><h4>' + filename + '</h4><ul>'); for (var i = 0; i < JSHINT.errors.length; i++) { - $(results).append('<li><b>' + JSHINT.errors[i].line + ':</b> ' + JSHINT.errors[i].reason + '</li>'); + var ignore = (Drupal.settings.jshint && Drupal.settings.jshint.ignore) ? Drupal.settings.jshint.ignore.split(',') : []; + if (ignore.indexOf(JSHINT.errors[i].code) === -1) { + var w = JSHINT.errors[i].reason + ' (line ' + JSHINT.errors[i].line + ', col ' + JSHINT.errors[i].character + ', rule ' + JSHINT.errors[i].code + ')'; + $(results).append('<li class="' + JSHINT.errors[i].id.replace(/[()]/g, '') + '">' + w.replace(/ /g, ' ') + '</li>'); + } } $(results).append('</ul></p>'); } @@ -105,7 +106,7 @@ * Have clicks to advagg_validator_css classes run CSSLint clientside. */ (function ($) { - "use strict"; + 'use strict'; Drupal.behaviors.advagg_validator_css_simple = { attach: function (context, settings) { $('.advagg_validator_css', context).click(function (context) { @@ -114,7 +115,7 @@ // Clear out the results. $(results).html(''); // Loop over each filename. - $.each($(this).siblings('.filenames'), function() { + $.each($(this).siblings('.filenames'), function () { var filename = $(this).val(); if (filename) { try { @@ -127,11 +128,13 @@ var y = CSSLint.verify(x.responseText); var z = y.messages; - $(results).append('<p><h4>' + filename + '</h4>'); - $(results).append('<ul>'); + $(results).append('<p><h4>' + filename + '</h4><ul>'); for (var i = 0, len = z.length; i < len; i++) { - var w = z[i].message + ' (line ' + z[i].line + ', col ' + z[i].col + ')'; - $(results).append('<li class="' + z[i].type + '">' + w.replace(/ /g, ' ') + '</li>'); + var ignore = (Drupal.settings.csslint && Drupal.settings.csslint.ignore) ? Drupal.settings.csslint.ignore.split(',') : []; + if (ignore.indexOf(z[i].rule.id) === -1) { + var w = z[i].message + ' (line ' + z[i].line + ', col ' + z[i].col + ', rule ' + z[i].rule.id + ')'; + $(results).append('<li class="' + z[i].type + '">' + w.replace(/ /g, ' ') + '</li>'); + } } $(results).append('</ul></p>'); } @@ -151,7 +154,7 @@ * Have clicks to advagg_validator_recursive_css classes run CSSLint clientside. */ (function ($) { - "use strict"; + 'use strict'; Drupal.behaviors.advagg_validator_css_recursive = { attach: function (context, settings) { $('.advagg_validator_recursive_css', context).click(function (context) { @@ -160,7 +163,7 @@ // Clear out the results. $(results).html(''); // Loop over each filename. - $.each($(this).parent().find('.filenames'), function() { + $.each($(this).parent().find('.filenames'), function () { var filename = $(this).val(); if (filename) { try { @@ -173,11 +176,13 @@ var y = CSSLint.verify(x.responseText); var z = y.messages; - $(results).append('<p><h4>' + filename + '</h4>'); - $(results).append('<ul>'); + $(results).append('<p><h4>' + filename + '</h4><ul>'); for (var i = 0, len = z.length; i < len; i++) { - var w = z[i].message + ' (line ' + z[i].line + ', col ' + z[i].col + ')'; - $(results).append('<li class="' + z[i].type + '">' + w.replace(/ /g, ' ') + '</li>'); + var ignore = (Drupal.settings.csslint && Drupal.settings.csslint.ignore) ? Drupal.settings.csslint.ignore.split(',') : []; + if (ignore.indexOf(z[i].rule.id) === -1) { + var w = z[i].message + ' (line ' + z[i].line + ', col ' + z[i].col + ', rule ' + z[i].rule.id + ')'; + $(results).append('<li class="' + z[i].type + '">' + w.replace(/ /g, ' ') + '</li>'); + } } $(results).append('</ul></p>'); } diff --git a/sites/all/modules/contrib/advagg/advagg_validator/advagg_validator.module b/sites/all/modules/contrib/advagg/advagg_validator/advagg_validator.module index b6c48db377..724f75d89a 100644 --- a/sites/all/modules/contrib/advagg/advagg_validator/advagg_validator.module +++ b/sites/all/modules/contrib/advagg/advagg_validator/advagg_validator.module @@ -5,12 +5,40 @@ * Advanced aggregation validation module. */ +/** + * @addtogroup default_variables + * @{ + */ + +/** + * Default css lint ignore settings. + */ +define('ADVAGG_VALIDATOR_CSSLINT_IGNORE', ''); + +/** + * Default js hint ignore settings. + */ +define('ADVAGG_VALIDATOR_JSHINT_IGNORE', ''); + +/** + * @} End of "addtogroup default_variables". + */ + +/** + * @addtogroup hooks + * @{ + */ + /** * Implements hook_menu(). */ function advagg_validator_menu() { + // Make sure the advagg_admin_config_root_path() function is available. + drupal_load('module', 'advagg'); + $file_path = drupal_get_path('module', 'advagg_validator'); $config_path = advagg_admin_config_root_path(); + $items = array(); $items[$config_path . '/advagg/validate_css_w3'] = array( 'title' => 'Validate CSS via W3', @@ -48,3 +76,129 @@ function advagg_validator_menu() { return $items; } + +/** + * Implements hook_menu_alter(). + */ +function advagg_validator_menu_alter(&$items) { + // Make sure the advagg_admin_config_root_path() function is available. + drupal_load('module', 'advagg'); + $config_path = advagg_admin_config_root_path(); + + if (!isset($items[$config_path . '/advagg'])) { + // If the advagg module is not enabled, redirect the /advagg path to + // /advagg/validate_css_w3. + $items[$config_path . '/advagg'] = array( + 'title' => 'Advanced CSS/JS Aggregation', + 'type' => MENU_LOCAL_TASK, + 'weight' => 1, + 'description' => $items[$config_path . '/advagg/validate_css_w3']['description'], + 'page callback' => 'drupal_goto', + 'page arguments' => array($config_path . '/advagg/validate_css_w3'), + 'access arguments' => array('administer site configuration'), + ); + } + if (!isset($items[$config_path . '/default'])) { + // Make sure the performance page has a default path. + $items[$config_path . '/default'] = array( + 'title' => 'Performance', + 'type' => MENU_DEFAULT_LOCAL_TASK, + 'file path' => drupal_get_path('module', 'system'), + 'weight' => -10, + ); + } +} + +/** + * @} End of "addtogroup hooks". + */ + +/** + * @addtogroup 3rd_party_hooks + * @{ + */ + +/** + * Implements hook_libraries_info(). + */ +function advagg_validator_libraries_info() { + $libraries['csslint'] = array( + 'name' => 'csslint', + 'vendor url' => 'https://github.com/CSSLint/csslint', + 'download url' => 'https://github.com/CSSLint/csslint/archive/master.zip', + 'version arguments' => array( + 'file' => 'package.json', + 'pattern' => '/"version":\\s+"([0-9\.]+)"/', + 'lines' => 10, + ), + 'remote' => array( + 'callback' => 'advagg_get_github_version_json', + 'url' => 'https://cdn.jsdelivr.net/gh/CSSLint/csslint@master/package.json', + ), + 'files' => array( + 'js' => array( + 'dist/csslint.js' => array( + 'type' => 'file', + ), + ), + ), + 'variants' => array(), + ); + // Get the latest tagged version for external file loading. + $version = advagg_get_remote_libraries_version('csslint', $libraries['csslint']); + $libraries['csslint']['variants'] += array( + 'external' => array( + 'files' => array( + 'js' => array( + "https://cdn.jsdelivr.net/gh/CSSLint/csslint@v{$version}/dist/csslint.js" => array( + 'type' => 'external', + 'data' => "https://cdn.jsdelivr.net/gh/CSSLint/csslint@v{$version}/dist/csslint.js", + ), + ), + ), + ), + ); + + $libraries['jshint'] = array( + 'name' => 'jshint', + 'vendor url' => 'https://github.com/jshint/jshint', + 'download url' => 'https://github.com/jshint/jshint/archive/master.zip', + 'version arguments' => array( + 'file' => 'package.json', + 'pattern' => '/"version":\\s+"([0-9\.]+)"/', + 'lines' => 10, + ), + 'remote' => array( + 'callback' => 'advagg_get_github_version_json', + 'url' => 'https://cdn.jsdelivr.net/gh/jshint/jshint@master/package.json', + ), + 'files' => array( + 'js' => array( + 'dist/jshint.js' => array( + 'type' => 'file', + ), + ), + ), + 'variants' => array(), + ); + // Get the latest tagged version for external file loading. + $version = advagg_get_remote_libraries_version('jshint', $libraries['jshint']); + $libraries['jshint']['variants'] += array( + 'external' => array( + 'files' => array( + 'js' => array( + "https://cdn.jsdelivr.net/gh/jshint/jshint@{$version}/dist/jshint.js" => array( + 'type' => 'external', + 'data' => "https://cdn.jsdelivr.net/gh/jshint/jshint@{$version}/dist/jshint.js", + ), + ), + ), + ), + ); + + return $libraries; +} + +/** + * @} End of "addtogroup 3rd_party_hooks". + */ diff --git a/sites/all/modules/contrib/advagg/composer.json b/sites/all/modules/contrib/advagg/composer.json new file mode 100644 index 0000000000..ee61c50dcb --- /dev/null +++ b/sites/all/modules/contrib/advagg/composer.json @@ -0,0 +1,19 @@ +{ + "name": "drupal/advagg", + "description": "Improved aggregation of CSS/JS files to speed up page load times and prevent 404s.", + "type": "drupal-module", + "license": "GPL-2.0+", + "homepage": "https://drupal.org/project/advagg", + "authors": [ + { + "name": "Mike Carper (mikeytown2)", + "homepage": "https://www.drupal.org/u/mikeytown2", + "role": "Creator, Maintainer" + } + ], + "support": { + "issues": "https://drupal.org/project/issues/advagg", + "irc": "irc://irc.freenode.org/drupal-contribute", + "source": "https://cgit.drupalcode.org/advagg" + } +} diff --git a/sites/all/modules/contrib/advagg/tests/advagg.test b/sites/all/modules/contrib/advagg/tests/advagg.test index da9227354e..4b67fd7e24 100644 --- a/sites/all/modules/contrib/advagg/tests/advagg.test +++ b/sites/all/modules/contrib/advagg/tests/advagg.test @@ -6,7 +6,22 @@ */ /** - * Resets static variables related to adding CSS to a page. + * @defgroup advagg_tests Advanced Aggregates Tests + * + * @{ + * Advanced Aggregates testing functionality. + */ + +// Include all files in the project for simple sanity checking. +$files = file_scan_directory(drupal_get_path('module', 'advagg'), "/.*\.(inc|module|install|php)$/", array( + 'nomask' => '/(\\.\\.?|CVS|tpl\.php)$/', +)); +foreach ($files as $file) { + include_once DRUPAL_ROOT . '/' . $file->uri; +} + +/** + * Resets static variables related to adding CSS or JS to a page. */ function advagg_test_reset_statics() { drupal_static_reset('drupal_add_css'); @@ -14,6 +29,68 @@ function advagg_test_reset_statics() { drupal_static_reset('drupal_get_library'); drupal_static_reset('drupal_add_js'); drupal_static_reset('drupal_add_js:jquery_added'); + drupal_static_reset('advagg_get_js'); +} + +/** + * Generates a large CSS string. + * + * @param int $selector_count + * The number of selectors to generate. + * @param int $denominator + * The max string length of the selector names. + * + * @return string + * Generated CSS string. + */ +function advagg_test_generate_selector_css($selector_count, $denominator = 5) { + static $count = 0; + $pool = array_merge(range('a', 'z'), range('A', 'Z')); + $selector_count = 10000; + $css = ''; + while ($selector_count > 0) { + $rand_string = advagg_test_randon_string(($selector_count % $denominator) + 3, $pool); + $css .= ".{$rand_string}, "; + --$selector_count; + } + $css .= "#last$count {z-index: 2; margin-left: -1px; content: \" \"; display: table;}"; + + ++$count; + return $css; +} + +/** + * Generates random string. + * + * @param int $length + * How many characters will this string contain. + * @param array $pool + * Array of characters to use. + * + * @return string + * Random string. + */ +function advagg_test_randon_string($length, array $pool) { + $string = ''; + $count = count($pool); + for ($i = 0; $i < $length; $i++) { + $string .= $pool[mt_rand(0, $count - 1)]; + } + return $string; +} + +/** + * Strip the codingStandardsIgnoreFile string from the input. + * + * @param string $input + * The input string. + * + * @return string + * The input string with codingStandardsIgnoreFile removed from it. + */ +function advagg_test_remove_sniffer_comments($input) { + $string = "/* @codingStandardsIgnoreFile */\n"; + return str_replace($string, '', $input); } /** @@ -22,9 +99,25 @@ function advagg_test_reset_statics() { class AdvAggCascadingStylesheetsTestCase extends DrupalWebTestCase { /** * Store configured value for CSS preprocessing. + * + * @var bool */ protected $preprocessCss = NULL; + /** + * Create user. + * + * @var object + */ + protected $bigUser; + + /** + * Theme settings. + * + * @var array + */ + protected $themes; + /** * Provide information to the UI for this test. */ @@ -42,21 +135,43 @@ class AdvAggCascadingStylesheetsTestCase extends DrupalWebTestCase { public function setUp() { // Enable any modules required for the test. This should be an array of // module names. - parent::setUp(array('advagg', 'php', 'locale', 'common_test', 'menu_test')); + parent::setUp(array( + 'advagg', + 'php', + 'locale', + 'common_test', + 'menu_test', + 'color', + )); // Include the advagg.module file. drupal_load('module', 'advagg'); module_load_include('inc', 'advagg', 'advagg'); - // Reset drupal_add_css() before each test. - advagg_test_reset_statics(); // Set settings for testing. $GLOBALS['conf']['advagg_convert_absolute_to_relative_path'] = FALSE; $GLOBALS['conf']['advagg_convert_absolute_to_protocol_relative_path'] = FALSE; $GLOBALS['conf']['advagg_force_https_path'] = FALSE; + $GLOBALS['conf']['advagg_skip_file_create_url_inside_css'] = FALSE; // Disable CSS preprocessing. $this->preprocessCss = variable_get('preprocess_css', 0); variable_set('preprocess_css', 0); + + // Create users. + $this->bigUser = $this->drupalCreateUser(array('administer themes')); + + // This tests the color module in both Bartik and Garland. + $this->themes = array( + 'bartik' => array( + 'palette_input' => 'palette[bg]', + 'scheme' => 'slate', + 'scheme_color' => '#3b3b3b', + ), + ); + theme_enable(array_keys($this->themes)); + + // Reset drupal_add_css() before each test. + advagg_test_reset_statics(); } /** @@ -72,6 +187,34 @@ class AdvAggCascadingStylesheetsTestCase extends DrupalWebTestCase { * Tests rendering the stylesheets. */ public function testRenderFile() { + foreach ($this->themes as $theme => $test_values) { + variable_set('theme_default', $theme); + $settings_path = 'admin/appearance/settings/' . $theme; + + $this->drupalLogin($this->bigUser); + $this->drupalGet($settings_path); + $this->assertResponse(200); + $edit['scheme'] = ''; + $edit[$test_values['palette_input']] = '#123456'; + $this->drupalPost($settings_path, $edit, t('Save configuration')); + + // Reset drupal_add_css() before each test. + $GLOBALS['conf']['advagg_convert_absolute_to_relative_path'] = FALSE; + $GLOBALS['conf']['advagg_convert_absolute_to_protocol_relative_path'] = FALSE; + advagg_test_reset_statics(); + // Add the css file. + $stylesheets = variable_get('color_' . $theme . '_stylesheets', array()); + drupal_add_css($stylesheets[0]); + $css = file_create_url($stylesheets[0]); + // Get the render array. + $full_css = advagg_get_css(); + $styles = drupal_render($full_css); + $this->assertTrue(strpos($styles, $css) !== FALSE, "Rendered CSS includes the added stylesheet ($css)."); + } + + // Reset drupal_add_css() before each test. + advagg_test_reset_statics(); + // Add the css file. $css = drupal_get_path('module', 'simpletest') . '/simpletest.css'; drupal_add_css($css); @@ -79,7 +222,7 @@ class AdvAggCascadingStylesheetsTestCase extends DrupalWebTestCase { $full_css = advagg_get_css(); // Render the CSS. $styles = drupal_render($full_css); - $this->assertTrue(strpos($styles, $css) > 0, 'Rendered CSS includes the added stylesheet.'); + $this->assertTrue(strpos($styles, $css) > 0, "Rendered CSS includes the added stylesheet ($css)."); // Verify that newlines are properly added inside style tags. $query_string = variable_get('css_js_query_string', '0'); $css_processed = "<style type=\"text/css\" media=\"all\">\n@import url(\"" . check_plain(file_create_url($css)) . "?" . $query_string . "\");\n</style>"; @@ -212,13 +355,28 @@ class AdvAggCascadingStylesheetsTestCase extends DrupalWebTestCase { global $language; $language->direction = LANGUAGE_RTL; $path = drupal_get_path('module', 'system'); - drupal_add_css($path . '/system.base.css'); + drupal_add_css($path . '/system.base.css', array('group' => CSS_SYSTEM)); + drupal_add_css($path . '/system.menus.css', array('group' => CSS_SYSTEM)); + drupal_add_css($path . '/system.theme.css', array('group' => CSS_SYSTEM)); // Get the render array. $full_css = advagg_get_css(); // Render the CSS. $styles = drupal_render($full_css); // Check to see if system.base-rtl.css was also added. - $this->assert(strpos($styles, $path . '/system.base-rtl.css') !== FALSE, 'CSS is alterable as right to left overrides are added.'); + $base_pos = strpos($styles, $path . '/system.base.css'); + $base_rtl_pos = strpos($styles, $path . '/system.base-rtl.css'); + $this->assert($base_rtl_pos !== FALSE, 'CSS is alterable as right to left overrides are added.'); + $this->assert($base_pos < $base_rtl_pos, 'system.base-rtl.css is added after system.base.css.'); + // Check to see if system.menus-rtl.css was also added. + $menus_pos = strpos($styles, $path . '/system.menus.css'); + $menus_rtl_pos = strpos($styles, $path . '/system.menus-rtl.css'); + $this->assert($menus_rtl_pos !== FALSE, 'CSS is alterable as right to left overrides are added.'); + $this->assert($menus_pos < $menus_rtl_pos, 'system.menus-rtl.css is added after system.menus.css.'); + // Check to see if system.theme-rtl.css was also added. + $theme_pos = strpos($styles, $path . '/system.theme.css'); + $theme_rtl_pos = strpos($styles, $path . '/system.theme-rtl.css'); + $this->assert($theme_rtl_pos !== FALSE, 'CSS is alterable as right to left overrides are added.'); + $this->assert($theme_pos < $theme_rtl_pos, 'system.theme-rtl.css is added after system.theme.css.'); // Change the language back to left to right. $language->direction = LANGUAGE_LTR; @@ -343,6 +501,56 @@ class AdvAggCascadingStylesheetsTestCase extends DrupalWebTestCase { $this->assertText('Custom theme: seven. Actual theme: seven.', 'The result of hook_custom_theme() does not override what was set in a theme callback.'); $this->assertRaw('seven/style.css', "The Seven theme's CSS appears on the page."); + // + // Test css split file processing. + // Generate a massive css file. + $css_string = advagg_test_generate_selector_css(1000); + $css_string .= '@media print {' . advagg_test_generate_selector_css(1000) . '}'; + $css_string .= advagg_test_generate_selector_css(1000); + $css_string .= '@media screen {' . advagg_test_generate_selector_css(1000) . '}'; + $css_string .= advagg_test_generate_selector_css(1000); + $css_string .= '@media print {' . advagg_test_generate_selector_css(1000) . '}'; + $css_string .= advagg_test_generate_selector_css(9000); + $css_string .= '@media print {' . advagg_test_generate_selector_css(9000) . '}'; + $css_string .= advagg_test_generate_selector_css(9000); + $css_string .= '@media screen {' . advagg_test_generate_selector_css(9000) . '}'; + $css_string .= '@media print {' . advagg_test_generate_selector_css(50) . '}'; + $css_string .= '@media screen {' . advagg_test_generate_selector_css(50) . '}'; + $css_string .= advagg_test_generate_selector_css(10); + $css_string .= '@media print {' . advagg_test_generate_selector_css(50) . '}'; + $css_string .= '@media screen {' . advagg_test_generate_selector_css(50) . '}'; + $css_string .= '@media print {' . advagg_test_generate_selector_css(50) . '}'; + $css_string .= '@media screen {' . advagg_test_generate_selector_css(50) . '}'; + $css_string .= advagg_test_generate_selector_css(10); + $css_string .= advagg_test_generate_selector_css(10); + $css_string .= advagg_test_generate_selector_css(10); + $css_string .= advagg_test_generate_selector_css(10); + $css_string .= advagg_test_generate_selector_css(15000); + $css_string .= '@media print {' . advagg_test_generate_selector_css(15000) . '}'; + $css_string .= advagg_test_generate_selector_css(10); + $css_string .= advagg_test_generate_selector_css(10); + $css_string .= advagg_test_generate_selector_css(10); + $css_string .= advagg_test_generate_selector_css(10); + $css_string .= advagg_test_generate_selector_css(10); + $file_info = array( + // Use a file that exists but isn't actually being used here. + 'data' => drupal_get_path('module', 'advagg') . '/tests/css_test_files/advagg.css', + ); + $before_selector_count = advagg_count_css_selectors($css_string); + // Split the huge css file. + $parts = advagg_split_css_file($file_info, $css_string); + $after = ''; + foreach ($parts as $part) { + // Get written file. + $after .= "\n" . (string) @advagg_file_get_contents($part['data']); + // Cleanup. + unlink($part['data']); + } + // Note that a diff of the text can not be used for this test. Counting + // selectors is close enough for now. + $after_selector_count = advagg_count_css_selectors($after); + $this->assertEqual($before_selector_count, $after_selector_count, t('Expected %before selectors, got %after.', array('%before' => $before_selector_count, '%after' => $after_selector_count))); + // // Test css file processing. advagg_test_reset_statics(); @@ -381,20 +589,22 @@ class AdvAggCascadingStylesheetsTestCase extends DrupalWebTestCase { $file_path = $path . '/' . $file; $file_url = $GLOBALS['base_url'] . '/' . $file_path; - $expected = file_get_contents($file_path . '.unoptimized.css'); + $expected = advagg_file_get_contents($file_path . '.unoptimized.css'); $unoptimized_output = advagg_load_stylesheet($file_path, FALSE); $this->assertEqual($unoptimized_output, $expected, format_string('Unoptimized CSS file has expected contents (@file)', array('@file' => $file))); - $expected = file_get_contents($file_path . '.optimized.css'); + $expected = advagg_file_get_contents($file_path . '.optimized.css'); + $expected = advagg_test_remove_sniffer_comments($expected); $optimized_output = advagg_load_stylesheet($file_path, TRUE); $this->assertEqual($optimized_output, $expected, format_string('Optimized CSS file has expected contents (@file)', array('@file' => $file))); // Repeat the tests by accessing the stylesheets by URL. - $expected = file_get_contents($file_path . '.unoptimized.css'); + $expected = advagg_file_get_contents($file_path . '.unoptimized.css'); $unoptimized_output_url = advagg_load_stylesheet($file_url, FALSE); $this->assertEqual($unoptimized_output_url, $expected, format_string('Unoptimized CSS file (loaded from an URL) has expected contents (@file)', array('@file' => $file))); - $expected = file_get_contents($file_path . '.optimized.css'); + $expected = advagg_file_get_contents($file_path . '.optimized.css'); + $expected = advagg_test_remove_sniffer_comments($expected); $optimized_output_url = advagg_load_stylesheet($file_url, TRUE); $this->assertEqual($optimized_output_url, $expected, format_string('Optimized CSS file (loaded from an URL) has expected contents (@file)', array('@file' => $file))); } @@ -407,54 +617,121 @@ class AdvAggCascadingStylesheetsTestCase extends DrupalWebTestCase { 'charset_newline.css', 'charset_sameline.css', ); + $path = drupal_get_path('module', 'advagg') . '/tests/css_test_files'; foreach ($testfiles as $file) { $file_path = $path . '/' . $file; $file_url = $GLOBALS['base_url'] . '/' . $file_path; - $expected = file_get_contents($file_path . '.optimized.css'); + $expected = advagg_file_get_contents($file_path . '.optimized.css'); + $expected = advagg_test_remove_sniffer_comments($expected); $optimized_output = advagg_load_stylesheet($file_path, TRUE); $this->assertEqual($optimized_output, $expected, format_string('Optimized CSS file has expected contents (@file)', array('@file' => $file))); - $expected = file_get_contents($file_path . '.optimized.css'); + $expected = advagg_file_get_contents($file_path . '.optimized.css'); + $expected = advagg_test_remove_sniffer_comments($expected); $optimized_output_url = advagg_load_stylesheet($file_url, TRUE); $this->assertEqual($optimized_output_url, $expected, format_string('Optimized CSS file (loaded from an URL) has expected contents (@file)', array('@file' => $file))); } - // File. Tests: advagg.css - // - Various url() tests. - // https://www.drupal.org/node/1514182 - // https://www.drupal.org/node/1961340 - // https://www.drupal.org/node/2362643 - // https://www.drupal.org/node/2112067 - $testfiles = array( - 'advagg.css', + // Set all to FALSE. + $GLOBALS['conf']['advagg_convert_absolute_to_relative_path'] = FALSE; + $GLOBALS['conf']['advagg_convert_absolute_to_protocol_relative_path'] = FALSE; + $GLOBALS['conf']['advagg_force_https_path'] = FALSE; + $GLOBALS['conf']['advagg_skip_file_create_url_inside_css'] = FALSE; + $settings_to_change = array( + '' => '', + 'advagg_skip_file_create_url_inside_css' => TRUE, + 'advagg_convert_absolute_to_relative_path' => TRUE, + 'advagg_convert_absolute_to_protocol_relative_path' => TRUE, + 'advagg_force_https_path' => TRUE, ); - foreach ($testfiles as $file) { - $file_path = $path . '/' . $file; - $file_url = $GLOBALS['base_url'] . '/' . $file_path; - $aggregate_settings = array( - 'variables' => array( - 'is_https' => FALSE, - 'base_path' => ($GLOBALS['base_path'] === '/checkout/') ? $GLOBALS['base_path'] : $GLOBALS['base_path'] . 'advagg_base_path_test/', - 'advagg_convert_absolute_to_relative_path' => TRUE, - 'advagg_convert_absolute_to_protocol_relative_path' => FALSE, - 'advagg_force_https_path' => FALSE, - ), - ); - if (module_exists('cdn')) { - $aggregate_settings['variables'][CDN_MODE_VARIABLE] = CDN_DISABLED; - $aggregate_settings['variables'][CDN_STATUS_VARIABLE] = CDN_DISABLED; + $advagg_path = drupal_get_path('module', 'advagg'); + $path = $advagg_path . '/tests/css_test_files'; + foreach ($settings_to_change as $name => $value) { + $before = ''; + if (!empty($name)) { + $before = variable_get($name, defined(strtoupper($name)) ? constant(strtoupper($name)) : NULL); + $GLOBALS['conf'][$name] = $value; } - $expected = file_get_contents($file_path . '.optimized.css'); - $optimized_output = advagg_load_css_stylesheet($file_path, TRUE, $aggregate_settings); - $optimized_output = str_replace($aggregate_settings['variables']['base_path'] . drupal_get_path('module', 'advagg') . '/', '', $optimized_output); - $this->assertEqual($optimized_output, $expected, format_string('Optimized CSS file has expected contents (@file)', array('@file' => $file)) . "<br>\n<br>\n" . $path . "<br>\n<br>\n" . $file_path . "<br>\n<br>\n" . $file_url . "<br>\n<br>\n" . drupal_get_path('module', 'advagg') . "<br>\n<br>\n" . file_create_url($file_path) . "<br>\n<br>\n" . $optimized_output . "<br>\n<br>\n" . $GLOBALS['base_url'] . "<br>\n<br>\n" . $GLOBALS['base_path'] . "<br>\n<br>\n" . $GLOBALS['base_root'] . "<br>\n<br>\n" . $aggregate_settings['variables']['base_path']); + // File. Tests: advagg.css + // - Various url() tests. + // https://www.drupal.org/node/1514182 + // https://www.drupal.org/node/1961340 + // https://www.drupal.org/node/2362643 + // https://www.drupal.org/node/2112067 + $testfiles = array( + 'advagg.css', + ); - $expected = file_get_contents($file_path . '.optimized.css'); - $optimized_output_url = advagg_load_css_stylesheet($file_url, TRUE, $aggregate_settings); - $optimized_output_url = str_replace($aggregate_settings['variables']['base_path'] . drupal_get_path('module', 'advagg') . '/', '', $optimized_output_url); - $this->assertEqual($optimized_output_url, $expected, format_string('Optimized CSS file (loaded from an URL) has expected contents (@file)', array('@file' => $file))); + foreach ($testfiles as $testfile) { + $base_url_before = $GLOBALS['base_url']; + $GLOBALS['base_url'] = advagg_force_http_path($GLOBALS['base_url']); + + $aggregate_settings = array( + 'variables' => array( + 'is_https' => FALSE, + 'base_path' => ($GLOBALS['base_path'] === '/checkout/') ? $GLOBALS['base_path'] : $GLOBALS['base_path'] . 'advagg_base_path_test/', + 'advagg_convert_absolute_to_relative_path' => $GLOBALS['conf']['advagg_convert_absolute_to_relative_path'], + 'advagg_convert_absolute_to_protocol_relative_path' => $GLOBALS['conf']['advagg_convert_absolute_to_protocol_relative_path'], + 'advagg_force_https_path' => $GLOBALS['conf']['advagg_force_https_path'], + 'advagg_skip_file_create_url_inside_css' => $GLOBALS['conf']['advagg_skip_file_create_url_inside_css'], + ), + ); + if (module_exists('cdn')) { + $aggregate_settings['variables'][CDN_MODE_VARIABLE] = CDN_DISABLED; + $aggregate_settings['variables'][CDN_STATUS_VARIABLE] = CDN_DISABLED; + } + + $file_path = $path . '/' . $testfile; + $files = array( + 'file' => $file_path, + 'external' => $GLOBALS['base_url'] . '/' . $file_path, + ); + $expected = advagg_test_remove_sniffer_comments(advagg_file_get_contents($file_path . '.optimized.css')); + foreach ($files as $type => $file) { + $optimized_output = advagg_load_css_stylesheet($file, TRUE, $aggregate_settings); + $mode = 0; + if ($aggregate_settings['variables']['advagg_skip_file_create_url_inside_css'] && $type !== 'external') { + $mode = "01"; + $optimized_output = str_replace($aggregate_settings['variables']['base_path'] . $advagg_path . '/', '', $optimized_output); + } + elseif (!$aggregate_settings['variables']['advagg_convert_absolute_to_relative_path'] + && !$aggregate_settings['variables']['advagg_convert_absolute_to_protocol_relative_path'] + && !$aggregate_settings['variables']['advagg_force_https_path'] + ) { + $mode = 4; + $optimized_output = str_replace('http://' . $_SERVER['HTTP_HOST'] . $aggregate_settings['variables']['base_path'] . $advagg_path . '/', '', $optimized_output); + } + elseif ($aggregate_settings['variables']['advagg_convert_absolute_to_protocol_relative_path']) { + $mode = 2; + $optimized_output = str_replace('//' . $_SERVER['HTTP_HOST'] . $aggregate_settings['variables']['base_path'] . $advagg_path . '/', '', $optimized_output); + } + elseif ($aggregate_settings['variables']['advagg_force_https_path']) { + $mode = 3; + $optimized_output = str_replace('https://' . $_SERVER['HTTP_HOST'] . $aggregate_settings['variables']['base_path'] . $advagg_path . '/', '', $optimized_output); + } + else { + $mode = 1; + $optimized_output = str_replace($aggregate_settings['variables']['base_path'] . $advagg_path . '/', '', $optimized_output); + } + $this->assertEqual($optimized_output, $expected, format_string("Optimized CSS file has expected contents (@file). Setting tested: @name; value before: @before, value after: @after.<br>mode: @mode. <p>!replacements</p> <p><code>!debug</code></p>", array( + '@file' => $file, + '@name' => $name, + '@before' => (is_bool($before) || strlen((string) $before) == 0) ? strtoupper(var_export($before, TRUE)) : $before, + '@after' => (is_bool($value) || strlen((string) $value) == 0) ? strtoupper(var_export($value, TRUE)) : $value, + '@mode' => $mode, + '!replacements' => "1: {$aggregate_settings['variables']['base_path']}{$advagg_path}/ <br> 2: //{$_SERVER['HTTP_HOST']}{$aggregate_settings['variables']['base_path']}{$advagg_path}/ <br> 3: https://{$_SERVER['HTTP_HOST']}{$aggregate_settings['variables']['base_path']}{$advagg_path}/ <br> 4: http://{$_SERVER['HTTP_HOST']}{$aggregate_settings['variables']['base_path']}{$advagg_path}/", + '!debug' => nl2br(str_replace(' ', ' ', $optimized_output)), + ))); + + $GLOBALS['base_url'] = $base_url_before; + } + } + + if (!empty($name)) { + $GLOBALS['conf'][$name] = $before; + } } } @@ -466,6 +743,8 @@ class AdvAggCascadingStylesheetsTestCase extends DrupalWebTestCase { class AdvAggJavaScriptTestCase extends DrupalWebTestCase { /** * Store configured value for JavaScript preprocessing. + * + * @var bool */ protected $preprocessJs = NULL; @@ -487,6 +766,7 @@ class AdvAggJavaScriptTestCase extends DrupalWebTestCase { // Enable Locale and SimpleTest in the test environment. parent::setUp(array( 'locale', + 'locale_test', 'simpletest', 'common_test', 'form_test', @@ -571,7 +851,7 @@ class AdvAggJavaScriptTestCase extends DrupalWebTestCase { // 'javascript_always_use_jquery' variable is set to FALSE, drupal_get_js() // should not return jQuery and other standard scripts and settings, nor // should it return the requested settings (since they cannot actually be - // addded to the page without jQuery). + // added to the page without jQuery). advagg_test_reset_statics(); variable_set('javascript_always_use_jquery', FALSE); drupal_add_js(array('testJavaScriptSetting' => 'test'), 'setting'); @@ -755,7 +1035,8 @@ class AdvAggJavaScriptTestCase extends DrupalWebTestCase { "-8 #2", "-8 #3", "-8 #4", - "-5 #1", // The external script. + // -5 #1: The external script. + "-5 #1", "0 #1", "0 #2", "0 #3", @@ -959,6 +1240,153 @@ class AdvAggJavaScriptTestCase extends DrupalWebTestCase { $this->assertTrue(strpos($scripts, $child_js), 'The child #attached JavaScript was included when loading from cache.'); $this->assertTrue(strpos($scripts, $subchild_js), 'The subchild #attached JavaScript was included when loading from cache.'); $_SERVER['REQUEST_METHOD'] = $request_method; + + // + // Tests the localisation of JavaScript libraries. Verifies that the + // datepicker can be localized. + advagg_test_reset_statics(); + drupal_add_library('system', 'ui.datepicker'); + $GLOBALS['conf']['advagg_mod_js_footer'] = 0; + $render_array = advagg_get_js(); + $javascript = drupal_render($render_array); + $this->assertTrue(strpos($javascript, 'locale.datepicker.js'), 'locale.datepicker.js added to scripts.'); + + // + // Functional tests for JavaScript parsing for translatable strings. + // Tests parsing js files for translatable strings. + advagg_test_reset_statics(); + $filename = drupal_get_path('module', 'locale_test') . '/locale_test.js'; + // Parse the file to look for source strings. + _locale_parse_js_file($filename); + + // Get all of the source strings that were found. + $source_strings = db_select('locales_source', 's') + ->fields('s', array('source', 'context')) + ->condition('s.location', $filename) + ->execute() + ->fetchAllKeyed(); + + // List of all strings that should be in the file. + $test_strings = array( + "Standard Call t" => '', + "Whitespace Call t" => '', + + "Single Quote t" => '', + "Single Quote \\'Escaped\\' t" => '', + "Single Quote Concat strings t" => '', + + "Double Quote t" => '', + "Double Quote \\\"Escaped\\\" t" => '', + "Double Quote Concat strings t" => '', + + "Context !key Args t" => "Context string", + + "Context Unquoted t" => "Context string unquoted", + "Context Single Quoted t" => "Context string single quoted", + "Context Double Quoted t" => "Context string double quoted", + + "Standard Call plural" => '', + "Standard Call @count plural" => '', + "Whitespace Call plural" => '', + "Whitespace Call @count plural" => '', + + "Single Quote plural" => '', + "Single Quote @count plural" => '', + "Single Quote \\'Escaped\\' plural" => '', + "Single Quote \\'Escaped\\' @count plural" => '', + + "Double Quote plural" => '', + "Double Quote @count plural" => '', + "Double Quote \\\"Escaped\\\" plural" => '', + "Double Quote \\\"Escaped\\\" @count plural" => '', + + "Context !key Args plural" => "Context string", + "Context !key Args @count plural" => "Context string", + + "Context Unquoted plural" => "Context string unquoted", + "Context Unquoted @count plural" => "Context string unquoted", + "Context Single Quoted plural" => "Context string single quoted", + "Context Single Quoted @count plural" => "Context string single quoted", + "Context Double Quoted plural" => "Context string double quoted", + "Context Double Quoted @count plural" => "Context string double quoted", + ); + + // Assert that all strings were found properly. + foreach ($test_strings as $str => $context) { + $args = array('%source' => $str, '%context' => $context); + + // Make sure that the string was found in the file. + $this->assertTrue(isset($source_strings[$str]), format_string('Found source string: %source', $args)); + + // Make sure that the proper context was matched. + $this->assertTrue(isset($source_strings[$str]) && $source_strings[$str] === $context, strlen($context) > 0 ? format_string('Context for %source is %context', $args) : format_string('Context for %source is blank', $args)); + } + $this->assertEqual(count($source_strings), count($test_strings), 'Found correct number of source strings.'); + + // + // Adds a language and checks that the JavaScript translation files are + // properly created and rebuilt on deletion. + $user = $this->drupalCreateUser(array( + 'translate interface', + 'administer languages', + 'access administration pages', + )); + $this->drupalLogin($user); + + $langcode = 'xx'; + // The English name for the language. This will be translated. + $name = $this->randomName(16); + // The native name for the language. + $native = $this->randomName(16); + // The domain prefix. + $prefix = $langcode; + + // Add custom language. + $edit = array( + 'langcode' => $langcode, + 'name' => $name, + 'native' => $native, + 'prefix' => $prefix, + 'direction' => '0', + ); + $this->drupalPost('admin/config/regional/language/add', $edit, t('Add custom language')); + drupal_static_reset('language_list'); + + // Build the JavaScript translation file. + $this->drupalGet('admin/config/regional/translate/translate'); + + // Retrieve the id of the first string available in the {locales_source} + // table and translate it. + $query = db_select('locales_source', 'l'); + $query->addExpression('min(l.lid)', 'lid'); + $result = $query->condition('l.location', '%.js%', 'LIKE') + ->condition('l.textgroup', 'default') + ->execute(); + $url = 'admin/config/regional/translate/edit/' . $result->fetchObject()->lid; + $edit = array('translations[' . $langcode . ']' => $this->randomName()); + $this->drupalPost($url, $edit, t('Save translations')); + + // Trigger JavaScript translation parsing and building. + require_once DRUPAL_ROOT . '/includes/locale.inc'; + _locale_rebuild_js($langcode); + + // Retrieve the JavaScript translation hash code for the custom language to + // check that the translation file has been properly built. + $file = db_select('languages', 'l') + ->fields('l', array('javascript')) + ->condition('language', $langcode) + ->execute() + ->fetchObject(); + $js_file = 'public://' . variable_get('locale_js_directory', 'languages') . '/' . $langcode . '_' . $file->javascript . '.js'; + $this->assertTrue($result = file_exists($js_file), format_string('JavaScript file created: %file', array('%file' => $result ? $js_file : 'not found'))); + + // Test JavaScript translation rebuilding. + file_unmanaged_delete($js_file); + $this->assertTrue($result = !file_exists($js_file), format_string('JavaScript file deleted: %file', array('%file' => $result ? $js_file : 'found'))); + cache_clear_all(); + _locale_rebuild_js($langcode); + $this->assertTrue($result = file_exists($js_file), format_string('JavaScript file rebuilt: %file', array('%file' => $result ? $js_file : 'not found'))); + } } @@ -973,8 +1401,8 @@ class AdvAggThemeTestCase extends AJAXTestCase { */ public static function getInfo() { return array( - 'name' => 'Theme API & AJAX', - 'description' => 'Test low-level theme functions and AJAX framework functions & commands.', + 'name' => 'Theme API and AJAX', + 'description' => 'Test low-level theme functions and AJAX framework functions and commands.', 'group' => 'AdvAgg', ); } @@ -1001,7 +1429,7 @@ class AdvAggThemeTestCase extends AJAXTestCase { } /** - * Check theme and ajax functions & commands. + * Check theme and ajax functions and commands. */ public function testCssOverride() { // Ensures a theme's .info file is able to override a module CSS file from @@ -1024,7 +1452,7 @@ class AdvAggThemeTestCase extends AJAXTestCase { variable_set('preprocess_css', 0); // - // Test ajax & js settings. + // Test ajax and js settings. advagg_test_reset_statics(); $commands = $this->drupalGetAJAX('ajax-test/render'); // Verify that there is a command to load settings added with @@ -1084,7 +1512,7 @@ class AdvAggThemeTestCase extends AJAXTestCase { 'preprocess' => TRUE, 'data' => $expected['css'], 'browsers' => array('IE' => TRUE, '!IE' => TRUE), - ) + ), ), TRUE); $expected_css_html = drupal_render($expected_css_render_array); $expected_js_render_array = advagg_get_js('header', array($expected['js'] => drupal_js_defaults($expected['js'])), TRUE); @@ -1287,3 +1715,7 @@ class AdvAggThemeTestCase extends AJAXTestCase { } } + +/** + * @} End of "defgroup advagg_tests". + */ diff --git a/sites/all/modules/contrib/advagg/tests/css_test_files/advagg.css b/sites/all/modules/contrib/advagg/tests/css_test_files/advagg.css index 9a570b5d66..b2aee57100 100644 --- a/sites/all/modules/contrib/advagg/tests/css_test_files/advagg.css +++ b/sites/all/modules/contrib/advagg/tests/css_test_files/advagg.css @@ -1,3 +1,4 @@ +/* @codingStandardsIgnoreFile */ /* https://www.drupal.org/node/1514182#comment-9532431 */ home { background: url('geo-md-webfont.eot?#iefix'); @@ -14,6 +15,22 @@ home { home { background: url( geo-md-webfont.eot?#iefix ); } +/* https://www.drupal.org/node/2685177 */ +home { + background: url('fostbook-webfont.svg#Foundry Sterling Book'); +} +home { + background: url( 'fostbook-webfont.svg#Foundry Sterling Book' ); +} +home { + background: url("fostbook-webfont.svg#Foundry Sterling Book"); +} +home { + background: url(fostbook-webfont.svg#Foundry Sterling Book); +} +home { + background: url( fostbook-webfont.svg#Foundry Sterling Book ); +} /* https://www.drupal.org/node/2362643 */ home { background: url(#SVGID_1_); @@ -38,4 +55,35 @@ html { 5%200h2000v21h-2000V0z%22%2F%3E%3C%2Fsvg%3E'); } +/* Example from https://www.w3.org/TR/CSS2/syndata.html#rule-sets */ +q[example="public class foo\ +{\ + private int x;\ +\ + foo(int x) {\ + this.x = x;\ + }\ +\ +}"] { color: red } + +/* A pseudo selector with essential whitespace wrapped in quotes. */ +q[style*="quotes: none"] { + quotes: none; +} + +q[style*='quotes: none'] { + quotes: none; +} + +q:after { + content: ": colon & escaped double \" quotes \"."; +} + +q:after { + content: ' (brackets & escaped single \' quotes \') '; +} + +q:after { + content: "I'm Quote"; +} diff --git a/sites/all/modules/contrib/advagg/tests/css_test_files/advagg.css.optimized.css b/sites/all/modules/contrib/advagg/tests/css_test_files/advagg.css.optimized.css index 9368ec596b..64b75975d4 100644 --- a/sites/all/modules/contrib/advagg/tests/css_test_files/advagg.css.optimized.css +++ b/sites/all/modules/contrib/advagg/tests/css_test_files/advagg.css.optimized.css @@ -1,2 +1,11 @@ -home{background:url(tests/css_test_files/geo-md-webfont.eot?#iefix);}home{background:url(tests/css_test_files/geo-md-webfont.eot?#iefix);}home{background:url(tests/css_test_files/geo-md-webfont.eot?#iefix);}home{background:url(tests/css_test_files/geo-md-webfont.eot?#iefix);}home{background:url(tests/css_test_files/geo-md-webfont.eot?#iefix);}home{background:url(#SVGID_1_);}home{background:url(#a);}home{background:url('#a');}home{background:url("#a");}home{background:url("#a");}home{background:url(#a);}html{background-image:url('data:image/svg+xml;charset=US-ASCII,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22utf-8%22%3F%3E%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%222000%22%20height%3D%2221%22%20viewBox%3D%22-423.5%200%202000%2021%22%20enable-background%3D%22new%20-423.5%200%202000%2021%22%3E%3ClinearGradient%20id%3D%22SVGID_1_%22%20gradientUnits%3D%22userSpaceOnUse%22%20x1%3D%221393.788%22%20y1%3D%22404.834%22%20x2%3D%221395.121%22%20y2%3D%22404.834%22%20gradientTransform%3D%22matrix(1500%200%200%20-538%20-2091105%20217811)%22%3E%3Cstop%20offset%3D%220%22%20stop-color%3D%22%23fff%22%20stop-opacity%3D%220%22%2F%3E%3Cstop%20offset%3D%220%22%20stop-color%3D%22%23fff%22%2F%3E%3Cstop%20offset%3D%22.55%22%20stop-color%3D%22%23fff%22%2F%3E%3Cstop%20offset%3D%22.8%22%20stop-color%3D%22%23fff%22%20stop-opacity%3D%220%22%2F%3E%3Cstop%20offset%3D%221%22%20stop-color%3D%22%23fff%22%20stop-opacity%3D%220%22%2F%3E%3C%2FlinearGradient%3E%3Cpath%20fill%3D%22url(%23SVGID_1_)%22%20d%3D%22M-423. -5%200h2000v21h-2000V0z%22%2F%3E%3C%2Fsvg%3E');} \ No newline at end of file +/* @codingStandardsIgnoreFile */ +home{background:url(tests/css_test_files/geo-md-webfont.eot?#iefix);}home{background:url(tests/css_test_files/geo-md-webfont.eot?#iefix);}home{background:url(tests/css_test_files/geo-md-webfont.eot?#iefix);}home{background:url(tests/css_test_files/geo-md-webfont.eot?#iefix);}home{background:url(tests/css_test_files/geo-md-webfont.eot?#iefix);}home{background:url(tests/css_test_files/fostbook-webfont.svg#Foundry Sterling Book);}home{background:url(tests/css_test_files/fostbook-webfont.svg#Foundry Sterling Book);}home{background:url(tests/css_test_files/fostbook-webfont.svg#Foundry Sterling Book);}home{background:url(tests/css_test_files/fostbook-webfont.svg#Foundry Sterling Book);}home{background:url(tests/css_test_files/fostbook-webfont.svg#Foundry Sterling Book);}home{background:url(#SVGID_1_);}home{background:url(#a);}home{background:url('#a');}home{background:url("#a");}home{background:url("#a");}home{background:url(#a);}html{background-image:url('data:image/svg+xml;charset=US-ASCII,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22utf-8%22%3F%3E%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%222000%22%20height%3D%2221%22%20viewBox%3D%22-423.5%200%202000%2021%22%20enable-background%3D%22new%20-423.5%200%202000%2021%22%3E%3ClinearGradient%20id%3D%22SVGID_1_%22%20gradientUnits%3D%22userSpaceOnUse%22%20x1%3D%221393.788%22%20y1%3D%22404.834%22%20x2%3D%221395.121%22%20y2%3D%22404.834%22%20gradientTransform%3D%22matrix(1500%200%200%20-538%20-2091105%20217811)%22%3E%3Cstop%20offset%3D%220%22%20stop-color%3D%22%23fff%22%20stop-opacity%3D%220%22%2F%3E%3Cstop%20offset%3D%220%22%20stop-color%3D%22%23fff%22%2F%3E%3Cstop%20offset%3D%22.55%22%20stop-color%3D%22%23fff%22%2F%3E%3Cstop%20offset%3D%22.8%22%20stop-color%3D%22%23fff%22%20stop-opacity%3D%220%22%2F%3E%3Cstop%20offset%3D%221%22%20stop-color%3D%22%23fff%22%20stop-opacity%3D%220%22%2F%3E%3C%2FlinearGradient%3E%3Cpath%20fill%3D%22url(%23SVGID_1_)%22%20d%3D%22M-423. +5%200h2000v21h-2000V0z%22%2F%3E%3C%2Fsvg%3E');}q[example="public class foo\ +{\ + private int x;\ +\ + foo(int x) {\ + this.x = x;\ + }\ +\ +}"]{color:red}q[style*="quotes: none"]{quotes:none;}q[style*='quotes: none']{quotes:none;}q:after{content:": colon & escaped double \" quotes \".";}q:after{content:' (brackets & escaped single \' quotes \') ';}q:after{content:"I'm Quote";} \ No newline at end of file diff --git a/sites/all/modules/contrib/advagg/tests/css_test_files/charset.css b/sites/all/modules/contrib/advagg/tests/css_test_files/charset.css index ba7696ddf4..78ae95151c 100644 --- a/sites/all/modules/contrib/advagg/tests/css_test_files/charset.css +++ b/sites/all/modules/contrib/advagg/tests/css_test_files/charset.css @@ -1 +1,7 @@ +/* @codingStandardsIgnoreFile */ @charset "UTF-8";html{font-family:"sans-serif";} + +/* Test data with multi-byte characters */ +.copyright-©, a-with-tilde-ã, plus-minus-± { + font-weight: bold; +} \ No newline at end of file diff --git a/sites/all/modules/contrib/advagg/tests/css_test_files/charset.css.optimized.css b/sites/all/modules/contrib/advagg/tests/css_test_files/charset.css.optimized.css index 4d35c9ab34..f21c548d35 100644 --- a/sites/all/modules/contrib/advagg/tests/css_test_files/charset.css.optimized.css +++ b/sites/all/modules/contrib/advagg/tests/css_test_files/charset.css.optimized.css @@ -1 +1,2 @@ -html{font-family:"sans-serif";} \ No newline at end of file +/* @codingStandardsIgnoreFile */ +html{font-family:"sans-serif";}.copyright-©,a-with-tilde-ã,plus-minus-±{font-weight:bold;} \ No newline at end of file diff --git a/sites/all/modules/contrib/advagg/tests/css_test_files/charset_newline.css b/sites/all/modules/contrib/advagg/tests/css_test_files/charset_newline.css index 68f3f42b2c..ad411b3f2b 100644 --- a/sites/all/modules/contrib/advagg/tests/css_test_files/charset_newline.css +++ b/sites/all/modules/contrib/advagg/tests/css_test_files/charset_newline.css @@ -1,2 +1,3 @@ +/* @codingStandardsIgnoreFile */ @charset 'UTF-8'; html{font-family:"sans-serif";} diff --git a/sites/all/modules/contrib/advagg/tests/css_test_files/charset_newline.css.optimized.css b/sites/all/modules/contrib/advagg/tests/css_test_files/charset_newline.css.optimized.css index 4d35c9ab34..374b7c65d0 100644 --- a/sites/all/modules/contrib/advagg/tests/css_test_files/charset_newline.css.optimized.css +++ b/sites/all/modules/contrib/advagg/tests/css_test_files/charset_newline.css.optimized.css @@ -1 +1,2 @@ +/* @codingStandardsIgnoreFile */ html{font-family:"sans-serif";} \ No newline at end of file diff --git a/sites/all/modules/contrib/advagg/tests/css_test_files/charset_sameline.css b/sites/all/modules/contrib/advagg/tests/css_test_files/charset_sameline.css index ba7696ddf4..9a19284b9a 100644 --- a/sites/all/modules/contrib/advagg/tests/css_test_files/charset_sameline.css +++ b/sites/all/modules/contrib/advagg/tests/css_test_files/charset_sameline.css @@ -1 +1,2 @@ +/* @codingStandardsIgnoreFile */ @charset "UTF-8";html{font-family:"sans-serif";} diff --git a/sites/all/modules/contrib/advagg/tests/css_test_files/charset_sameline.css.optimized.css b/sites/all/modules/contrib/advagg/tests/css_test_files/charset_sameline.css.optimized.css index 4d35c9ab34..374b7c65d0 100644 --- a/sites/all/modules/contrib/advagg/tests/css_test_files/charset_sameline.css.optimized.css +++ b/sites/all/modules/contrib/advagg/tests/css_test_files/charset_sameline.css.optimized.css @@ -1 +1,2 @@ +/* @codingStandardsIgnoreFile */ html{font-family:"sans-serif";} \ No newline at end of file diff --git a/sites/all/modules/contrib/advagg/tests/css_test_files/comment_hacks.css b/sites/all/modules/contrib/advagg/tests/css_test_files/comment_hacks.css index 7d0dd44118..f27a0b87f1 100644 --- a/sites/all/modules/contrib/advagg/tests/css_test_files/comment_hacks.css +++ b/sites/all/modules/contrib/advagg/tests/css_test_files/comment_hacks.css @@ -1,3 +1,5 @@ +/* @codingStandardsIgnoreFile */ + /* * A sample css file, designed to test the effectiveness and stability * of function <code>drupal_load_stylesheet_content()</code>. @@ -7,6 +9,11 @@ A large comment block to test for segfaults and speed. This is 60K a's. Extreme but useful to demonstrate flaws in comment striping regexp. aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa*/ .test1 { display:block;} +/* A pseudo selector with essential whitespace wrapped in quotes. */ +img[style*="float: right"] { + padding-left:5px; +} + /* A multiline IE-mac hack (v.2) taken from Zen theme*/ /* Hides from IE-mac \*/ html .clear-block { diff --git a/sites/all/modules/contrib/advagg/tests/css_test_files/comment_hacks.css.optimized.css b/sites/all/modules/contrib/advagg/tests/css_test_files/comment_hacks.css.optimized.css index 1207be33f1..bd49738994 100644 --- a/sites/all/modules/contrib/advagg/tests/css_test_files/comment_hacks.css.optimized.css +++ b/sites/all/modules/contrib/advagg/tests/css_test_files/comment_hacks.css.optimized.css @@ -1 +1,2 @@ -.test1{display:block;}html .clear-block{height:1%;}.clear-block{display:block;font:italic bold 12px/30px Georgia,serif;}.test2{display:block;}.bkslshv1{background-color:#c00;}.test3{display:block;}.test4{display:block;}.comment-in-double-quotes:before{content:"/* ";}.this_rule_must_stay{color:#f00;background-color:#fff;}.comment-in-double-quotes:after{content:" */";}.comment-in-single-quotes:before{content:'/*';}.this_rule_must_stay{color:#f00;background-color:#fff;}.comment-in-single-quotes:after{content:'*/';}.comment-in-mixed-quotes:before{content:'"/*"';}.this_rule_must_stay{color:#f00;background-color:#fff;}.comment-in-mixed-quotes:after{content:"'*/'";}.comment-in-quotes-with-escaped:before{content:'/* \" \' */';}.this_rule_must_stay{color:#f00;background-color:#fff;}.comment-in-quotes-with-escaped:after{content:"*/ \" \ '";} \ No newline at end of file +/* @codingStandardsIgnoreFile */ +.test1{display:block;}img[style*="float: right"]{padding-left:5px;}html .clear-block{height:1%;}.clear-block{display:block;font:italic bold 12px/30px Georgia,serif;}.test2{display:block;}.bkslshv1{background-color:#c00;}.test3{display:block;}.test4{display:block;}.comment-in-double-quotes:before{content:"/* ";}.this_rule_must_stay{color:#f00;background-color:#fff;}.comment-in-double-quotes:after{content:" */";}.comment-in-single-quotes:before{content:'/*';}.this_rule_must_stay{color:#f00;background-color:#fff;}.comment-in-single-quotes:after{content:'*/';}.comment-in-mixed-quotes:before{content:'"/*"';}.this_rule_must_stay{color:#f00;background-color:#fff;}.comment-in-mixed-quotes:after{content:"'*/'";}.comment-in-quotes-with-escaped:before{content:'/* \" \' */';}.this_rule_must_stay{color:#f00;background-color:#fff;}.comment-in-quotes-with-escaped:after{content:"*/ \" \ '";} \ No newline at end of file diff --git a/sites/all/modules/contrib/advagg/tests/css_test_files/comment_hacks.css.unoptimized.css b/sites/all/modules/contrib/advagg/tests/css_test_files/comment_hacks.css.unoptimized.css index 7d0dd44118..f27a0b87f1 100644 --- a/sites/all/modules/contrib/advagg/tests/css_test_files/comment_hacks.css.unoptimized.css +++ b/sites/all/modules/contrib/advagg/tests/css_test_files/comment_hacks.css.unoptimized.css @@ -1,3 +1,5 @@ +/* @codingStandardsIgnoreFile */ + /* * A sample css file, designed to test the effectiveness and stability * of function <code>drupal_load_stylesheet_content()</code>. @@ -7,6 +9,11 @@ A large comment block to test for segfaults and speed. This is 60K a's. Extreme but useful to demonstrate flaws in comment striping regexp. aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa*/ .test1 { display:block;} +/* A pseudo selector with essential whitespace wrapped in quotes. */ +img[style*="float: right"] { + padding-left:5px; +} + /* A multiline IE-mac hack (v.2) taken from Zen theme*/ /* Hides from IE-mac \*/ html .clear-block { diff --git a/sites/all/modules/contrib/advagg/tests/css_test_files/css_input_with_import.css b/sites/all/modules/contrib/advagg/tests/css_test_files/css_input_with_import.css index 34270ee696..cd23bd87d6 100644 --- a/sites/all/modules/contrib/advagg/tests/css_test_files/css_input_with_import.css +++ b/sites/all/modules/contrib/advagg/tests/css_test_files/css_input_with_import.css @@ -1,4 +1,4 @@ - +/* @codingStandardsIgnoreFile */ @import "import1.css"; @import "import2.css"; @@ -29,4 +29,3 @@ textarea, select { font: 1em/160% Verdana, sans-serif; color: #494949; } - diff --git a/sites/all/modules/contrib/advagg/tests/css_test_files/css_input_with_import.css.optimized.css b/sites/all/modules/contrib/advagg/tests/css_test_files/css_input_with_import.css.optimized.css index 83f56423d8..5f1b3081ed 100644 --- a/sites/all/modules/contrib/advagg/tests/css_test_files/css_input_with_import.css.optimized.css +++ b/sites/all/modules/contrib/advagg/tests/css_test_files/css_input_with_import.css.optimized.css @@ -1,4 +1,5 @@ -ul,select{font:1em/160% Verdana,sans-serif;color:#494949;}.ui-icon{background-image:url(images/icon.png);}.data .double-quote{background-image:url("data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7");}.data .single-quote{background-image:url('data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDACAWGBwYFCAcGhwkIiAmMFA0MCwsMGJGSjpQdGZ6eHJmcG6AkLicgIiuim5woNqirr7EztDOfJri8uDI8LjKzsb/2wBDASIkJDAqMF40NF7GhHCExsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsb/wAARCAABAAEDASIAAhEBAxEB/8QAFQABAQAAAAAAAAAAAAAAAAAAAAb/xAAUEAEAAAAAAAAAAAAAAAAAAAAA/8QAFAEBAAAAAAAAAAAAAAAAAAAAAP/EABQRAQAAAAAAAAAAAAAAAAAAAAD/2gAMAwEAAhEDEQA/AKAAH//Z');}.data .no-quote{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACEAAAAEAQAAAAAo/mtHAAAAIElEQVQIHWMRnWHwcRNLN8NZ7QYWwT8PlBlYsgqVBRsAankIMw5MtnoAAAAASUVORK5CYII=);}p,select{font:1em/160% Verdana,sans-serif;color:#494949;}@import url("http://example.com/style.css");@import url("//example.com/style.css");body{margin:0;padding:0;background:#edf5fa;font:76%/170% Verdana,sans-serif;color:#494949;}.this .is .a .test{font:1em/100% Verdana,sans-serif;color:#494949;}.this +/* @codingStandardsIgnoreFile */ +ul,select,import1{font:1em/160% Verdana,sans-serif;color:#494949;}.ui-icon{background-image:url(images/icon.png);}.data .double-quote{background-image:url("data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7");}.data .single-quote{background-image:url('data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDACAWGBwYFCAcGhwkIiAmMFA0MCwsMGJGSjpQdGZ6eHJmcG6AkLicgIiuim5woNqirr7EztDOfJri8uDI8LjKzsb/2wBDASIkJDAqMF40NF7GhHCExsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsb/wAARCAABAAEDASIAAhEBAxEB/8QAFQABAQAAAAAAAAAAAAAAAAAAAAb/xAAUEAEAAAAAAAAAAAAAAAAAAAAA/8QAFAEBAAAAAAAAAAAAAAAAAAAAAP/EABQRAQAAAAAAAAAAAAAAAAAAAAD/2gAMAwEAAhEDEQA/AKAAH//Z');}.data .no-quote{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACEAAAAEAQAAAAAo/mtHAAAAIElEQVQIHWMRnWHwcRNLN8NZ7QYWwT8PlBlYsgqVBRsAankIMw5MtnoAAAAASUVORK5CYII=);}@import url('https://fonts.googleapis.com/css?family=Ubuntu');p,select,import2{font:1em/160% Verdana,sans-serif;color:#494949;}@import url("http://example.com/style.css");@import url("//example.com/style.css");body{margin:0;padding:0;background:#edf5fa;font:76%/170% Verdana,sans-serif;color:#494949;}.this .is .a .test{font:1em/100% Verdana,sans-serif;color:#494949;}.this .is .a .test{font:1em/100% Verdana,sans-serif;color:#494949;}textarea,select{font:1em/160% Verdana,sans-serif;color:#494949;} \ No newline at end of file diff --git a/sites/all/modules/contrib/advagg/tests/css_test_files/css_input_with_import.css.unoptimized.css b/sites/all/modules/contrib/advagg/tests/css_test_files/css_input_with_import.css.unoptimized.css index c6d26e64d3..33a60562e3 100644 --- a/sites/all/modules/contrib/advagg/tests/css_test_files/css_input_with_import.css.unoptimized.css +++ b/sites/all/modules/contrib/advagg/tests/css_test_files/css_input_with_import.css.unoptimized.css @@ -1,7 +1,8 @@ +/* @codingStandardsIgnoreFile */ +/* @codingStandardsIgnoreFile */ - -ul, select { +ul, select, import1 { font: 1em/160% Verdana, sans-serif; color: #494949; } @@ -21,8 +22,10 @@ ul, select { background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACEAAAAEAQAAAAAo/mtHAAAAIElEQVQIHWMRnWHwcRNLN8NZ7QYWwT8PlBlYsgqVBRsAankIMw5MtnoAAAAASUVORK5CYII=); } +/* @codingStandardsIgnoreFile */ -p, select { +@import url('https://fonts.googleapis.com/css?family=Ubuntu'); +p, select, import2 { font: 1em/160% Verdana, sans-serif; color: #494949; } @@ -54,4 +57,3 @@ textarea, select { font: 1em/160% Verdana, sans-serif; color: #494949; } - diff --git a/sites/all/modules/contrib/advagg/tests/css_test_files/css_input_without_import.css b/sites/all/modules/contrib/advagg/tests/css_test_files/css_input_without_import.css index 620360abc5..a6519e6b7b 100644 --- a/sites/all/modules/contrib/advagg/tests/css_test_files/css_input_without_import.css +++ b/sites/all/modules/contrib/advagg/tests/css_test_files/css_input_without_import.css @@ -1,9 +1,9 @@ +/* @codingStandardsIgnoreFile */ /** * @file Basic css that does not use import */ - body { margin: 0; padding: 0; diff --git a/sites/all/modules/contrib/advagg/tests/css_test_files/css_input_without_import.css.optimized.css b/sites/all/modules/contrib/advagg/tests/css_test_files/css_input_without_import.css.optimized.css index e2c13fd912..d89954f47d 100644 --- a/sites/all/modules/contrib/advagg/tests/css_test_files/css_input_without_import.css.optimized.css +++ b/sites/all/modules/contrib/advagg/tests/css_test_files/css_input_without_import.css.optimized.css @@ -1,3 +1,4 @@ +/* @codingStandardsIgnoreFile */ body{margin:0;padding:0;background:#edf5fa;font:76%/170% Verdana,sans-serif;color:#494949;}.this .is .a .test{font:1em/100% Verdana,sans-serif;color:#494949;}.this .is .a diff --git a/sites/all/modules/contrib/advagg/tests/css_test_files/css_input_without_import.css.unoptimized.css b/sites/all/modules/contrib/advagg/tests/css_test_files/css_input_without_import.css.unoptimized.css index 620360abc5..a6519e6b7b 100644 --- a/sites/all/modules/contrib/advagg/tests/css_test_files/css_input_without_import.css.unoptimized.css +++ b/sites/all/modules/contrib/advagg/tests/css_test_files/css_input_without_import.css.unoptimized.css @@ -1,9 +1,9 @@ +/* @codingStandardsIgnoreFile */ /** * @file Basic css that does not use import */ - body { margin: 0; padding: 0; diff --git a/sites/all/modules/contrib/advagg/tests/css_test_files/css_subfolder/css_input_with_import.css b/sites/all/modules/contrib/advagg/tests/css_test_files/css_subfolder/css_input_with_import.css index d90ecbcb4f..fca7a79ef7 100644 --- a/sites/all/modules/contrib/advagg/tests/css_test_files/css_subfolder/css_input_with_import.css +++ b/sites/all/modules/contrib/advagg/tests/css_test_files/css_subfolder/css_input_with_import.css @@ -1,4 +1,4 @@ - +/* @codingStandardsIgnoreFile */ @import "../import1.css"; @import "../import2.css"; diff --git a/sites/all/modules/contrib/advagg/tests/css_test_files/css_subfolder/css_input_with_import.css.optimized.css b/sites/all/modules/contrib/advagg/tests/css_test_files/css_subfolder/css_input_with_import.css.optimized.css index 5d40017a8f..7cb26ed652 100644 --- a/sites/all/modules/contrib/advagg/tests/css_test_files/css_subfolder/css_input_with_import.css.optimized.css +++ b/sites/all/modules/contrib/advagg/tests/css_test_files/css_subfolder/css_input_with_import.css.optimized.css @@ -1,4 +1,5 @@ -ul,select{font:1em/160% Verdana,sans-serif;color:#494949;}.ui-icon{background-image:url(../images/icon.png);}.data .double-quote{background-image:url("data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7");}.data .single-quote{background-image:url('data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDACAWGBwYFCAcGhwkIiAmMFA0MCwsMGJGSjpQdGZ6eHJmcG6AkLicgIiuim5woNqirr7EztDOfJri8uDI8LjKzsb/2wBDASIkJDAqMF40NF7GhHCExsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsb/wAARCAABAAEDASIAAhEBAxEB/8QAFQABAQAAAAAAAAAAAAAAAAAAAAb/xAAUEAEAAAAAAAAAAAAAAAAAAAAA/8QAFAEBAAAAAAAAAAAAAAAAAAAAAP/EABQRAQAAAAAAAAAAAAAAAAAAAAD/2gAMAwEAAhEDEQA/AKAAH//Z');}.data .no-quote{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACEAAAAEAQAAAAAo/mtHAAAAIElEQVQIHWMRnWHwcRNLN8NZ7QYWwT8PlBlYsgqVBRsAankIMw5MtnoAAAAASUVORK5CYII=);}p,select{font:1em/160% Verdana,sans-serif;color:#494949;}body{margin:0;padding:0;background:#edf5fa;font:76%/170% Verdana,sans-serif;color:#494949;}.this .is .a .test{font:1em/100% Verdana,sans-serif;color:#494949;}.this +/* @codingStandardsIgnoreFile */ +ul,select,import1{font:1em/160% Verdana,sans-serif;color:#494949;}.ui-icon{background-image:url(../images/icon.png);}.data .double-quote{background-image:url("data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7");}.data .single-quote{background-image:url('data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDACAWGBwYFCAcGhwkIiAmMFA0MCwsMGJGSjpQdGZ6eHJmcG6AkLicgIiuim5woNqirr7EztDOfJri8uDI8LjKzsb/2wBDASIkJDAqMF40NF7GhHCExsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsb/wAARCAABAAEDASIAAhEBAxEB/8QAFQABAQAAAAAAAAAAAAAAAAAAAAb/xAAUEAEAAAAAAAAAAAAAAAAAAAAA/8QAFAEBAAAAAAAAAAAAAAAAAAAAAP/EABQRAQAAAAAAAAAAAAAAAAAAAAD/2gAMAwEAAhEDEQA/AKAAH//Z');}.data .no-quote{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACEAAAAEAQAAAAAo/mtHAAAAIElEQVQIHWMRnWHwcRNLN8NZ7QYWwT8PlBlYsgqVBRsAankIMw5MtnoAAAAASUVORK5CYII=);}@import url('https://fonts.googleapis.com/css?family=Ubuntu');p,select,import2{font:1em/160% Verdana,sans-serif;color:#494949;}body{margin:0;padding:0;background:#edf5fa;font:76%/170% Verdana,sans-serif;color:#494949;}.this .is .a .test{font:1em/100% Verdana,sans-serif;color:#494949;}.this .is .a .test{font:1em/100% Verdana,sans-serif;color:#494949;}textarea,select{font:1em/160% Verdana,sans-serif;color:#494949;} \ No newline at end of file diff --git a/sites/all/modules/contrib/advagg/tests/css_test_files/css_subfolder/css_input_with_import.css.unoptimized.css b/sites/all/modules/contrib/advagg/tests/css_test_files/css_subfolder/css_input_with_import.css.unoptimized.css index 6d7151bb38..67f4d81c13 100644 --- a/sites/all/modules/contrib/advagg/tests/css_test_files/css_subfolder/css_input_with_import.css.unoptimized.css +++ b/sites/all/modules/contrib/advagg/tests/css_test_files/css_subfolder/css_input_with_import.css.unoptimized.css @@ -1,7 +1,8 @@ +/* @codingStandardsIgnoreFile */ +/* @codingStandardsIgnoreFile */ - -ul, select { +ul, select, import1 { font: 1em/160% Verdana, sans-serif; color: #494949; } @@ -21,8 +22,10 @@ ul, select { background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACEAAAAEAQAAAAAo/mtHAAAAIElEQVQIHWMRnWHwcRNLN8NZ7QYWwT8PlBlYsgqVBRsAankIMw5MtnoAAAAASUVORK5CYII=); } +/* @codingStandardsIgnoreFile */ -p, select { +@import url('https://fonts.googleapis.com/css?family=Ubuntu'); +p, select, import2 { font: 1em/160% Verdana, sans-serif; color: #494949; } diff --git a/sites/all/modules/contrib/advagg/tests/css_test_files/import1.css b/sites/all/modules/contrib/advagg/tests/css_test_files/import1.css index e53d6d52cd..5f4cd00594 100644 --- a/sites/all/modules/contrib/advagg/tests/css_test_files/import1.css +++ b/sites/all/modules/contrib/advagg/tests/css_test_files/import1.css @@ -1,5 +1,6 @@ +/* @codingStandardsIgnoreFile */ -ul, select { +ul, select, import1 { font: 1em/160% Verdana, sans-serif; color: #494949; } diff --git a/sites/all/modules/contrib/advagg/tests/css_test_files/import2.css b/sites/all/modules/contrib/advagg/tests/css_test_files/import2.css index 367eb57118..9208dc0a3a 100644 --- a/sites/all/modules/contrib/advagg/tests/css_test_files/import2.css +++ b/sites/all/modules/contrib/advagg/tests/css_test_files/import2.css @@ -1,5 +1,7 @@ +/* @codingStandardsIgnoreFile */ -p, select { +@import url('https://fonts.googleapis.com/css?family=Ubuntu'); +p, select, import2 { font: 1em/160% Verdana, sans-serif; color: #494949; } diff --git a/sites/all/modules/contrib/advagg/tpl/imce-page.tpl.php b/sites/all/modules/contrib/advagg/tpl/imce-page.tpl.php new file mode 100644 index 0000000000..3695c76ac8 --- /dev/null +++ b/sites/all/modules/contrib/advagg/tpl/imce-page.tpl.php @@ -0,0 +1,41 @@ +<?php + +/** + * @file + * Override imce-page.tpl.php, fixing css/js aggregation issues. + */ + +if (isset($_GET['app'])) { + drupal_add_js(drupal_get_path('module', 'imce') . '/js/imce_set_app.js'); +} +$title = t('File Browser'); +$full_css = advagg_get_css(); +$rendered_css = drupal_render($full_css); +$scripts_header_array = advagg_get_js('header'); +$rendered_scripts_header = drupal_render($scripts_header_array); +$scripts_footer_array = advagg_get_js('footer'); +$rendered_scripts_footer = drupal_render($scripts_footer_array); +$html_head = drupal_get_html_head(); +$status_messages = theme('status_messages'); + +print <<<HTML +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" lang="{$GLOBALS['language']->language}" xml:lang="{$GLOBALS['language']->language}" class="imce"> + +<head> + <title>{$title} + + {$html_head} + {$rendered_css} + {$rendered_scripts_header} + + + + +
{$status_messages}
+{$content} +{$rendered_scripts_footer} + + +HTML; From c8a1834e65351d05aee076c47b7c3206b2d828cc Mon Sep 17 00:00:00 2001 From: Rob Davis Date: Mon, 21 Nov 2022 13:00:38 +0000 Subject: [PATCH 05/22] store vscode setup because this includes xdebug and other settings we would want to standardise for all devs, so we avoid the 'works for me' table flip moment. Saves everyone time, developing rather than developing the development env --- .gitignore | 6 +++--- .vscode/launch.json | 15 +++++++++++++++ .vscode/settings.json | 4 ++++ 3 files changed, 22 insertions(+), 3 deletions(-) create mode 100644 .vscode/launch.json create mode 100644 .vscode/settings.json diff --git a/.gitignore b/.gitignore index 3f8fa7a472..c519157c4a 100644 --- a/.gitignore +++ b/.gitignore @@ -19,9 +19,6 @@ sites/all/drush .settings .idea -# VS Code files -.vscode - # Development files index2.php @@ -39,3 +36,6 @@ docker-compose.dev.yml sites/default/files/advagg_css/ sites/default/files/advagg_js/ sites/default/files/css/ + +# files and assets +sites/default/files \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000000..37cdc61e38 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,15 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Listen for XDebug", + "type": "php", + "request": "launch", + "hostname": "0.0.0.0", + "port": 9003, + "pathMappings": { + "/var/www/html": "${workspaceFolder}" + } + } + ] +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000000..41226a3820 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,4 @@ +{ + "git.path": "/usr/bin/git", + "php.validate.executablePath": "/usr/bin/php" +} \ No newline at end of file From e6294bdc58b4c8510185b0aea7cc7616cc7becaf Mon Sep 17 00:00:00 2001 From: Rob Davis Date: Thu, 24 Nov 2022 08:18:45 +0000 Subject: [PATCH 06/22] dev end --- .gitignore | 3 --- .vscode/launch.json | 0 .vscode/settings.json | 4 ++++ 3 files changed, 4 insertions(+), 3 deletions(-) create mode 100644 .vscode/launch.json create mode 100644 .vscode/settings.json diff --git a/.gitignore b/.gitignore index 3f8fa7a472..55427d7594 100644 --- a/.gitignore +++ b/.gitignore @@ -19,9 +19,6 @@ sites/all/drush .settings .idea -# VS Code files -.vscode - # Development files index2.php diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000000..e69de29bb2 diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000000..41226a3820 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,4 @@ +{ + "git.path": "/usr/bin/git", + "php.validate.executablePath": "/usr/bin/php" +} \ No newline at end of file From 38793f93e5efde23978090656dc2c0908fa5f1a1 Mon Sep 17 00:00:00 2001 From: Rob Davis Date: Thu, 24 Nov 2022 08:20:12 +0000 Subject: [PATCH 07/22] dev end for debugging --- .vscode/launch.json | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/.vscode/launch.json b/.vscode/launch.json index e69de29bb2..0482d7b0cc 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -0,0 +1,15 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Listen for XDebug", + "type": "php", + "request": "launch", + "hostname": "0.0.0.0", + "port": 9003, + "pathMappings": { + "/var/www/html": "${workspaceFolder}" + } + } + ] +} From e3a9e1bd0acf65734d9fc9845749e77a4de7b166 Mon Sep 17 00:00:00 2001 From: Rob Davis Date: Thu, 24 Nov 2022 15:26:12 +0000 Subject: [PATCH 08/22] updating the branch with a standard .gitignore approach --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 55427d7594..46424bf616 100644 --- a/.gitignore +++ b/.gitignore @@ -36,3 +36,6 @@ docker-compose.dev.yml sites/default/files/advagg_css/ sites/default/files/advagg_js/ sites/default/files/css/ + +# files and assets +sites/default/files From 2030142ad4716e3a9e42d573d64aeeb9cb46f690 Mon Sep 17 00:00:00 2001 From: Rob Davis Date: Fri, 25 Nov 2022 12:31:12 +0000 Subject: [PATCH 09/22] #6564 adding https://github.com/jbroutier/iucn-api-client --- composer | 1 + composer.json | 3 +- composer.lock | 747 +++++- sites/all/libraries/vendor/autoload.php | 18 + .../libraries/vendor/composer/ClassLoader.php | 157 +- .../vendor/composer/InstalledVersions.php | 352 +++ .../vendor/composer/autoload_classmap.php | 9 +- .../vendor/composer/autoload_files.php | 5 +- .../vendor/composer/autoload_namespaces.php | 2 +- .../vendor/composer/autoload_psr4.php | 12 +- .../vendor/composer/autoload_real.php | 45 +- .../vendor/composer/autoload_static.php | 70 + .../libraries/vendor/composer/installed.json | 2160 +++++++++++------ .../libraries/vendor/composer/installed.php | 385 +++ .../vendor/composer/platform_check.php | 26 + .../all/libraries/vendor/doctrine/collections | 1 + .../libraries/vendor/doctrine/deprecations | 1 + .../vendor/jbroutier/iucn-api-client | 1 + sites/all/libraries/vendor/psr/container | 1 + sites/all/libraries/vendor/psr/log | 1 + .../vendor/symfony/deprecation-contracts | 1 + .../all/libraries/vendor/symfony/http-client | 1 + .../vendor/symfony/http-client-contracts | 1 + .../libraries/vendor/symfony/polyfill-php73 | 1 + .../libraries/vendor/symfony/polyfill-php80 | 1 + .../vendor/symfony/service-contracts | 1 + .../modules/iucn/iucn.module | 8 + tools/phpcomposer/composer.phar | Bin 0 -> 2810737 bytes 28 files changed, 3275 insertions(+), 736 deletions(-) create mode 120000 composer create mode 100644 sites/all/libraries/vendor/composer/InstalledVersions.php create mode 100644 sites/all/libraries/vendor/composer/installed.php create mode 100644 sites/all/libraries/vendor/composer/platform_check.php create mode 160000 sites/all/libraries/vendor/doctrine/collections create mode 160000 sites/all/libraries/vendor/doctrine/deprecations create mode 160000 sites/all/libraries/vendor/jbroutier/iucn-api-client create mode 160000 sites/all/libraries/vendor/psr/container create mode 160000 sites/all/libraries/vendor/psr/log create mode 160000 sites/all/libraries/vendor/symfony/deprecation-contracts create mode 160000 sites/all/libraries/vendor/symfony/http-client create mode 160000 sites/all/libraries/vendor/symfony/http-client-contracts create mode 160000 sites/all/libraries/vendor/symfony/polyfill-php73 create mode 160000 sites/all/libraries/vendor/symfony/polyfill-php80 create mode 160000 sites/all/libraries/vendor/symfony/service-contracts create mode 100755 tools/phpcomposer/composer.phar diff --git a/composer b/composer new file mode 120000 index 0000000000..73cdb9e23d --- /dev/null +++ b/composer @@ -0,0 +1 @@ +tools/phpcomposer/composer.phar \ No newline at end of file diff --git a/composer.json b/composer.json index 854db1a3b9..32c5aa15a1 100644 --- a/composer.json +++ b/composer.json @@ -7,7 +7,8 @@ "require": { "knplabs/github-api": "^1.7", "michelf/php-markdown": "^1.8", - "php-http/guzzle6-adapter": "^1.1" + "php-http/guzzle6-adapter": "^1.1", + "jbroutier/iucn-api-client": "^1.0" }, "license": "GPL-2.0" } diff --git a/composer.lock b/composer.lock index 242e1f096c..76ec728776 100644 --- a/composer.lock +++ b/composer.lock @@ -4,8 +4,121 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "7cd35b3f391970a0c9ec23872e9716d0", + "content-hash": "7620fe7504dc7dd97e205e0c9216c484", "packages": [ + { + "name": "doctrine/collections", + "version": "1.8.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/collections.git", + "reference": "2b44dd4cbca8b5744327de78bafef5945c7e7b5e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/collections/zipball/2b44dd4cbca8b5744327de78bafef5945c7e7b5e", + "reference": "2b44dd4cbca8b5744327de78bafef5945c7e7b5e", + "shasum": "" + }, + "require": { + "doctrine/deprecations": "^0.5.3 || ^1", + "php": "^7.1.3 || ^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "^9.0 || ^10.0", + "phpstan/phpstan": "^1.4.8", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.1.5", + "vimeo/psalm": "^4.22" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Common\\Collections\\": "lib/Doctrine/Common/Collections" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "PHP Doctrine Collections library that adds additional functionality on top of PHP arrays.", + "homepage": "https://www.doctrine-project.org/projects/collections.html", + "keywords": [ + "array", + "collections", + "iterators", + "php" + ], + "support": { + "issues": "https://github.com/doctrine/collections/issues", + "source": "https://github.com/doctrine/collections/tree/1.8.0" + }, + "time": "2022-09-01T20:12:10+00:00" + }, + { + "name": "doctrine/deprecations", + "version": "v1.0.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/deprecations.git", + "reference": "0e2a4f1f8cdfc7a92ec3b01c9334898c806b30de" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/deprecations/zipball/0e2a4f1f8cdfc7a92ec3b01c9334898c806b30de", + "reference": "0e2a4f1f8cdfc7a92ec3b01c9334898c806b30de", + "shasum": "" + }, + "require": { + "php": "^7.1|^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "^9", + "phpunit/phpunit": "^7.5|^8.5|^9.5", + "psr/log": "^1|^2|^3" + }, + "suggest": { + "psr/log": "Allows logging deprecations via PSR-3 logger implementation" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Deprecations\\": "lib/Doctrine/Deprecations" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "A small layer on top of trigger_error(E_USER_DEPRECATED) or PSR-3 logging with options to disable all deprecations or selectively for packages.", + "homepage": "https://www.doctrine-project.org/", + "support": { + "issues": "https://github.com/doctrine/deprecations/issues", + "source": "https://github.com/doctrine/deprecations/tree/v1.0.0" + }, + "time": "2022-05-02T15:47:09+00:00" + }, { "name": "guzzle/guzzle", "version": "v3.9.3", @@ -283,6 +396,60 @@ ], "time": "2017-03-20T17:10:46+00:00" }, + { + "name": "jbroutier/iucn-api-client", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/jbroutier/iucn-api-client.git", + "reference": "d2b95768b40d3c4606030a626e7c99393deef1de" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/jbroutier/iucn-api-client/zipball/d2b95768b40d3c4606030a626e7c99393deef1de", + "reference": "d2b95768b40d3c4606030a626e7c99393deef1de", + "shasum": "" + }, + "require": { + "doctrine/collections": "^1.7.2", + "php": "^7.4|^8.0", + "symfony/http-client": "^5.4|^6.0" + }, + "require-dev": { + "ekino/phpstan-banned-code": "^1.0.0", + "ext-json": "*", + "phpstan/extension-installer": "^1.1.0", + "phpstan/phpstan": "^1.8.4", + "phpstan/phpstan-deprecation-rules": "^1.0.0", + "phpstan/phpstan-phpunit": "^1.1.1", + "phpstan/phpstan-strict-rules": "^1.4.3", + "phpunit/phpunit": "^9.5.24" + }, + "type": "library", + "autoload": { + "psr-4": { + "IucnApi\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jérémie BROUTIER", + "email": "jeremie.broutier@posteo.net", + "homepage": "https://github.com/jbroutier", + "role": "developer" + } + ], + "description": "A PHP client to retrieve data from The IUCN Red List of Threatened Species™.", + "support": { + "issues": "https://github.com/jbroutier/iucn-api-client/issues", + "source": "https://github.com/jbroutier/iucn-api-client/tree/1.0.0" + }, + "time": "2022-09-09T16:48:06+00:00" + }, { "name": "knplabs/github-api", "version": "1.7.1", @@ -557,6 +724,54 @@ ], "time": "2016-01-26T13:27:02+00:00" }, + { + "name": "psr/container", + "version": "1.1.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "513e0666f7216c7459170d56df27dfcefe1689ea" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/513e0666f7216c7459170d56df27dfcefe1689ea", + "reference": "513e0666f7216c7459170d56df27dfcefe1689ea", + "shasum": "" + }, + "require": { + "php": ">=7.4.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "support": { + "issues": "https://github.com/php-fig/container/issues", + "source": "https://github.com/php-fig/container/tree/1.1.2" + }, + "time": "2021-11-05T16:50:12+00:00" + }, { "name": "psr/http-message", "version": "1.0.1", @@ -607,6 +822,123 @@ ], "time": "2016-08-06T14:39:51+00:00" }, + { + "name": "psr/log", + "version": "1.1.4", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "d49695b909c3b7628b6289db5479a1c204601f11" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/d49695b909c3b7628b6289db5479a1c204601f11", + "reference": "d49695b909c3b7628b6289db5479a1c204601f11", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "Psr/Log/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/1.1.4" + }, + "time": "2021-05-03T11:20:27+00:00" + }, + { + "name": "symfony/deprecation-contracts", + "version": "v2.5.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "e8b495ea28c1d97b5e0c121748d6f9b53d075c66" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/e8b495ea28c1d97b5e0c121748d6f9b53d075c66", + "reference": "e8b495ea28c1d97b5e0c121748d6f9b53d075c66", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.5-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "files": [ + "function.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "A generic function and convention to trigger deprecation notices", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/deprecation-contracts/tree/v2.5.2" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-01-02T09:53:40+00:00" + }, { "name": "symfony/event-dispatcher", "version": "v2.8.48", @@ -666,6 +998,416 @@ "description": "Symfony EventDispatcher Component", "homepage": "https://symfony.com", "time": "2018-11-21T14:20:20+00:00" + }, + { + "name": "symfony/http-client", + "version": "v5.4.15", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-client.git", + "reference": "8f29b0f06c9ff48c8431e78eb90c8bd6f82cb12b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-client/zipball/8f29b0f06c9ff48c8431e78eb90c8bd6f82cb12b", + "reference": "8f29b0f06c9ff48c8431e78eb90c8bd6f82cb12b", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "psr/log": "^1|^2|^3", + "symfony/deprecation-contracts": "^2.1|^3", + "symfony/http-client-contracts": "^2.4", + "symfony/polyfill-php73": "^1.11", + "symfony/polyfill-php80": "^1.16", + "symfony/service-contracts": "^1.0|^2|^3" + }, + "provide": { + "php-http/async-client-implementation": "*", + "php-http/client-implementation": "*", + "psr/http-client-implementation": "1.0", + "symfony/http-client-implementation": "2.4" + }, + "require-dev": { + "amphp/amp": "^2.5", + "amphp/http-client": "^4.2.1", + "amphp/http-tunnel": "^1.0", + "amphp/socket": "^1.1", + "guzzlehttp/promises": "^1.4", + "nyholm/psr7": "^1.0", + "php-http/httplug": "^1.0|^2.0", + "psr/http-client": "^1.0", + "symfony/dependency-injection": "^4.4|^5.0|^6.0", + "symfony/http-kernel": "^4.4.13|^5.1.5|^6.0", + "symfony/process": "^4.4|^5.0|^6.0", + "symfony/stopwatch": "^4.4|^5.0|^6.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\HttpClient\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides powerful methods to fetch HTTP resources synchronously or asynchronously", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/http-client/tree/v5.4.15" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-10-25T16:22:13+00:00" + }, + { + "name": "symfony/http-client-contracts", + "version": "v2.5.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-client-contracts.git", + "reference": "ba6a9f0e8f3edd190520ee3b9a958596b6ca2e70" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-client-contracts/zipball/ba6a9f0e8f3edd190520ee3b9a958596b6ca2e70", + "reference": "ba6a9f0e8f3edd190520ee3b9a958596b6ca2e70", + "shasum": "" + }, + "require": { + "php": ">=7.2.5" + }, + "suggest": { + "symfony/http-client-implementation": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.5-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\HttpClient\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to HTTP clients", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/http-client-contracts/tree/v2.5.2" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-04-12T15:48:08+00:00" + }, + { + "name": "symfony/polyfill-php73", + "version": "v1.27.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php73.git", + "reference": "9e8ecb5f92152187c4799efd3c96b78ccab18ff9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/9e8ecb5f92152187c4799efd3c96b78ccab18ff9", + "reference": "9e8ecb5f92152187c4799efd3c96b78ccab18ff9", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.27-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php73\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 7.3+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php73/tree/v1.27.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-11-03T14:55:06+00:00" + }, + { + "name": "symfony/polyfill-php80", + "version": "v1.27.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php80.git", + "reference": "7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936", + "reference": "7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.27-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php80\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ion Bazan", + "email": "ion.bazan@gmail.com" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php80/tree/v1.27.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-11-03T14:55:06+00:00" + }, + { + "name": "symfony/service-contracts", + "version": "v2.5.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/service-contracts.git", + "reference": "4b426aac47d6427cc1a1d0f7e2ac724627f5966c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/4b426aac47d6427cc1a1d0f7e2ac724627f5966c", + "reference": "4b426aac47d6427cc1a1d0f7e2ac724627f5966c", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "psr/container": "^1.1", + "symfony/deprecation-contracts": "^2.1|^3" + }, + "conflict": { + "ext-psr": "<1.1|>=2" + }, + "suggest": { + "symfony/service-implementation": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.5-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Service\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to writing services", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/service-contracts/tree/v2.5.2" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-05-30T19:17:29+00:00" } ], "packages-dev": [], @@ -675,5 +1417,6 @@ "prefer-stable": false, "prefer-lowest": false, "platform": [], - "platform-dev": [] + "platform-dev": [], + "plugin-api-version": "2.3.0" } diff --git a/sites/all/libraries/vendor/autoload.php b/sites/all/libraries/vendor/autoload.php index 6fbdaae191..b752f3deb7 100644 --- a/sites/all/libraries/vendor/autoload.php +++ b/sites/all/libraries/vendor/autoload.php @@ -2,6 +2,24 @@ // autoload.php @generated by Composer +if (PHP_VERSION_ID < 50600) { + if (!headers_sent()) { + header('HTTP/1.1 500 Internal Server Error'); + } + $err = 'Composer 2.3.0 dropped support for autoloading on PHP <5.6 and you are running '.PHP_VERSION.', please upgrade PHP or use Composer 2.2 LTS via "composer self-update --2.2". Aborting.'.PHP_EOL; + if (!ini_get('display_errors')) { + if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') { + fwrite(STDERR, $err); + } elseif (!headers_sent()) { + echo $err; + } + } + trigger_error( + $err, + E_USER_ERROR + ); +} + require_once __DIR__ . '/composer/autoload_real.php'; return ComposerAutoloaderInit737e2bc6f7218ac89562dae1a4248a16::getLoader(); diff --git a/sites/all/libraries/vendor/composer/ClassLoader.php b/sites/all/libraries/vendor/composer/ClassLoader.php index fce8549f07..afef3fa2ad 100644 --- a/sites/all/libraries/vendor/composer/ClassLoader.php +++ b/sites/all/libraries/vendor/composer/ClassLoader.php @@ -37,57 +37,130 @@ * * @author Fabien Potencier * @author Jordi Boggiano - * @see http://www.php-fig.org/psr/psr-0/ - * @see http://www.php-fig.org/psr/psr-4/ + * @see https://www.php-fig.org/psr/psr-0/ + * @see https://www.php-fig.org/psr/psr-4/ */ class ClassLoader { + /** @var ?string */ + private $vendorDir; + // PSR-4 + /** + * @var array[] + * @psalm-var array> + */ private $prefixLengthsPsr4 = array(); + /** + * @var array[] + * @psalm-var array> + */ private $prefixDirsPsr4 = array(); + /** + * @var array[] + * @psalm-var array + */ private $fallbackDirsPsr4 = array(); // PSR-0 + /** + * @var array[] + * @psalm-var array> + */ private $prefixesPsr0 = array(); + /** + * @var array[] + * @psalm-var array + */ private $fallbackDirsPsr0 = array(); + /** @var bool */ private $useIncludePath = false; + + /** + * @var string[] + * @psalm-var array + */ private $classMap = array(); + + /** @var bool */ private $classMapAuthoritative = false; + + /** + * @var bool[] + * @psalm-var array + */ private $missingClasses = array(); + + /** @var ?string */ private $apcuPrefix; + /** + * @var self[] + */ + private static $registeredLoaders = array(); + + /** + * @param ?string $vendorDir + */ + public function __construct($vendorDir = null) + { + $this->vendorDir = $vendorDir; + } + + /** + * @return string[] + */ public function getPrefixes() { if (!empty($this->prefixesPsr0)) { - return call_user_func_array('array_merge', $this->prefixesPsr0); + return call_user_func_array('array_merge', array_values($this->prefixesPsr0)); } return array(); } + /** + * @return array[] + * @psalm-return array> + */ public function getPrefixesPsr4() { return $this->prefixDirsPsr4; } + /** + * @return array[] + * @psalm-return array + */ public function getFallbackDirs() { return $this->fallbackDirsPsr0; } + /** + * @return array[] + * @psalm-return array + */ public function getFallbackDirsPsr4() { return $this->fallbackDirsPsr4; } + /** + * @return string[] Array of classname => path + * @psalm-return array + */ public function getClassMap() { return $this->classMap; } /** - * @param array $classMap Class to filename map + * @param string[] $classMap Class to filename map + * @psalm-param array $classMap + * + * @return void */ public function addClassMap(array $classMap) { @@ -102,9 +175,11 @@ public function addClassMap(array $classMap) * Registers a set of PSR-0 directories for a given prefix, either * appending or prepending to the ones previously set for this prefix. * - * @param string $prefix The prefix - * @param array|string $paths The PSR-0 root directories - * @param bool $prepend Whether to prepend the directories + * @param string $prefix The prefix + * @param string[]|string $paths The PSR-0 root directories + * @param bool $prepend Whether to prepend the directories + * + * @return void */ public function add($prefix, $paths, $prepend = false) { @@ -147,11 +222,13 @@ public function add($prefix, $paths, $prepend = false) * Registers a set of PSR-4 directories for a given namespace, either * appending or prepending to the ones previously set for this namespace. * - * @param string $prefix The prefix/namespace, with trailing '\\' - * @param array|string $paths The PSR-4 base directories - * @param bool $prepend Whether to prepend the directories + * @param string $prefix The prefix/namespace, with trailing '\\' + * @param string[]|string $paths The PSR-4 base directories + * @param bool $prepend Whether to prepend the directories * * @throws \InvalidArgumentException + * + * @return void */ public function addPsr4($prefix, $paths, $prepend = false) { @@ -195,8 +272,10 @@ public function addPsr4($prefix, $paths, $prepend = false) * Registers a set of PSR-0 directories for a given prefix, * replacing any others previously set for this prefix. * - * @param string $prefix The prefix - * @param array|string $paths The PSR-0 base directories + * @param string $prefix The prefix + * @param string[]|string $paths The PSR-0 base directories + * + * @return void */ public function set($prefix, $paths) { @@ -211,10 +290,12 @@ public function set($prefix, $paths) * Registers a set of PSR-4 directories for a given namespace, * replacing any others previously set for this namespace. * - * @param string $prefix The prefix/namespace, with trailing '\\' - * @param array|string $paths The PSR-4 base directories + * @param string $prefix The prefix/namespace, with trailing '\\' + * @param string[]|string $paths The PSR-4 base directories * * @throws \InvalidArgumentException + * + * @return void */ public function setPsr4($prefix, $paths) { @@ -234,6 +315,8 @@ public function setPsr4($prefix, $paths) * Turns on searching the include path for class files. * * @param bool $useIncludePath + * + * @return void */ public function setUseIncludePath($useIncludePath) { @@ -256,6 +339,8 @@ public function getUseIncludePath() * that have not been registered with the class map. * * @param bool $classMapAuthoritative + * + * @return void */ public function setClassMapAuthoritative($classMapAuthoritative) { @@ -276,6 +361,8 @@ public function isClassMapAuthoritative() * APCu prefix to use to cache found/not-found classes, if the extension is enabled. * * @param string|null $apcuPrefix + * + * @return void */ public function setApcuPrefix($apcuPrefix) { @@ -296,25 +383,44 @@ public function getApcuPrefix() * Registers this instance as an autoloader. * * @param bool $prepend Whether to prepend the autoloader or not + * + * @return void */ public function register($prepend = false) { spl_autoload_register(array($this, 'loadClass'), true, $prepend); + + if (null === $this->vendorDir) { + return; + } + + if ($prepend) { + self::$registeredLoaders = array($this->vendorDir => $this) + self::$registeredLoaders; + } else { + unset(self::$registeredLoaders[$this->vendorDir]); + self::$registeredLoaders[$this->vendorDir] = $this; + } } /** * Unregisters this instance as an autoloader. + * + * @return void */ public function unregister() { spl_autoload_unregister(array($this, 'loadClass')); + + if (null !== $this->vendorDir) { + unset(self::$registeredLoaders[$this->vendorDir]); + } } /** * Loads the given class or interface. * * @param string $class The name of the class - * @return bool|null True if loaded, null otherwise + * @return true|null True if loaded, null otherwise */ public function loadClass($class) { @@ -323,6 +429,8 @@ public function loadClass($class) return true; } + + return null; } /** @@ -367,6 +475,21 @@ public function findFile($class) return $file; } + /** + * Returns the currently registered loaders indexed by their corresponding vendor directories. + * + * @return self[] + */ + public static function getRegisteredLoaders() + { + return self::$registeredLoaders; + } + + /** + * @param string $class + * @param string $ext + * @return string|false + */ private function findFileWithExtension($class, $ext) { // PSR-4 lookup @@ -438,6 +561,10 @@ private function findFileWithExtension($class, $ext) * Scope isolated include. * * Prevents access to $this/self from included files. + * + * @param string $file + * @return void + * @private */ function includeFile($file) { diff --git a/sites/all/libraries/vendor/composer/InstalledVersions.php b/sites/all/libraries/vendor/composer/InstalledVersions.php new file mode 100644 index 0000000000..c6b54af7ba --- /dev/null +++ b/sites/all/libraries/vendor/composer/InstalledVersions.php @@ -0,0 +1,352 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer; + +use Composer\Autoload\ClassLoader; +use Composer\Semver\VersionParser; + +/** + * This class is copied in every Composer installed project and available to all + * + * See also https://getcomposer.org/doc/07-runtime.md#installed-versions + * + * To require its presence, you can require `composer-runtime-api ^2.0` + * + * @final + */ +class InstalledVersions +{ + /** + * @var mixed[]|null + * @psalm-var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array}|array{}|null + */ + private static $installed; + + /** + * @var bool|null + */ + private static $canGetVendors; + + /** + * @var array[] + * @psalm-var array}> + */ + private static $installedByVendor = array(); + + /** + * Returns a list of all package names which are present, either by being installed, replaced or provided + * + * @return string[] + * @psalm-return list + */ + public static function getInstalledPackages() + { + $packages = array(); + foreach (self::getInstalled() as $installed) { + $packages[] = array_keys($installed['versions']); + } + + if (1 === \count($packages)) { + return $packages[0]; + } + + return array_keys(array_flip(\call_user_func_array('array_merge', $packages))); + } + + /** + * Returns a list of all package names with a specific type e.g. 'library' + * + * @param string $type + * @return string[] + * @psalm-return list + */ + public static function getInstalledPackagesByType($type) + { + $packagesByType = array(); + + foreach (self::getInstalled() as $installed) { + foreach ($installed['versions'] as $name => $package) { + if (isset($package['type']) && $package['type'] === $type) { + $packagesByType[] = $name; + } + } + } + + return $packagesByType; + } + + /** + * Checks whether the given package is installed + * + * This also returns true if the package name is provided or replaced by another package + * + * @param string $packageName + * @param bool $includeDevRequirements + * @return bool + */ + public static function isInstalled($packageName, $includeDevRequirements = true) + { + foreach (self::getInstalled() as $installed) { + if (isset($installed['versions'][$packageName])) { + return $includeDevRequirements || empty($installed['versions'][$packageName]['dev_requirement']); + } + } + + return false; + } + + /** + * Checks whether the given package satisfies a version constraint + * + * e.g. If you want to know whether version 2.3+ of package foo/bar is installed, you would call: + * + * Composer\InstalledVersions::satisfies(new VersionParser, 'foo/bar', '^2.3') + * + * @param VersionParser $parser Install composer/semver to have access to this class and functionality + * @param string $packageName + * @param string|null $constraint A version constraint to check for, if you pass one you have to make sure composer/semver is required by your package + * @return bool + */ + public static function satisfies(VersionParser $parser, $packageName, $constraint) + { + $constraint = $parser->parseConstraints($constraint); + $provided = $parser->parseConstraints(self::getVersionRanges($packageName)); + + return $provided->matches($constraint); + } + + /** + * Returns a version constraint representing all the range(s) which are installed for a given package + * + * It is easier to use this via isInstalled() with the $constraint argument if you need to check + * whether a given version of a package is installed, and not just whether it exists + * + * @param string $packageName + * @return string Version constraint usable with composer/semver + */ + public static function getVersionRanges($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + $ranges = array(); + if (isset($installed['versions'][$packageName]['pretty_version'])) { + $ranges[] = $installed['versions'][$packageName]['pretty_version']; + } + if (array_key_exists('aliases', $installed['versions'][$packageName])) { + $ranges = array_merge($ranges, $installed['versions'][$packageName]['aliases']); + } + if (array_key_exists('replaced', $installed['versions'][$packageName])) { + $ranges = array_merge($ranges, $installed['versions'][$packageName]['replaced']); + } + if (array_key_exists('provided', $installed['versions'][$packageName])) { + $ranges = array_merge($ranges, $installed['versions'][$packageName]['provided']); + } + + return implode(' || ', $ranges); + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @param string $packageName + * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present + */ + public static function getVersion($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + if (!isset($installed['versions'][$packageName]['version'])) { + return null; + } + + return $installed['versions'][$packageName]['version']; + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @param string $packageName + * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present + */ + public static function getPrettyVersion($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + if (!isset($installed['versions'][$packageName]['pretty_version'])) { + return null; + } + + return $installed['versions'][$packageName]['pretty_version']; + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @param string $packageName + * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as reference + */ + public static function getReference($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + if (!isset($installed['versions'][$packageName]['reference'])) { + return null; + } + + return $installed['versions'][$packageName]['reference']; + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @param string $packageName + * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as install path. Packages of type metapackages also have a null install path. + */ + public static function getInstallPath($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + return isset($installed['versions'][$packageName]['install_path']) ? $installed['versions'][$packageName]['install_path'] : null; + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @return array + * @psalm-return array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool} + */ + public static function getRootPackage() + { + $installed = self::getInstalled(); + + return $installed[0]['root']; + } + + /** + * Returns the raw installed.php data for custom implementations + * + * @deprecated Use getAllRawData() instead which returns all datasets for all autoloaders present in the process. getRawData only returns the first dataset loaded, which may not be what you expect. + * @return array[] + * @psalm-return array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} + */ + public static function getRawData() + { + @trigger_error('getRawData only returns the first dataset loaded, which may not be what you expect. Use getAllRawData() instead which returns all datasets for all autoloaders present in the process.', E_USER_DEPRECATED); + + if (null === self::$installed) { + // only require the installed.php file if this file is loaded from its dumped location, + // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937 + if (substr(__DIR__, -8, 1) !== 'C') { + self::$installed = include __DIR__ . '/installed.php'; + } else { + self::$installed = array(); + } + } + + return self::$installed; + } + + /** + * Returns the raw data of all installed.php which are currently loaded for custom implementations + * + * @return array[] + * @psalm-return list}> + */ + public static function getAllRawData() + { + return self::getInstalled(); + } + + /** + * Lets you reload the static array from another file + * + * This is only useful for complex integrations in which a project needs to use + * this class but then also needs to execute another project's autoloader in process, + * and wants to ensure both projects have access to their version of installed.php. + * + * A typical case would be PHPUnit, where it would need to make sure it reads all + * the data it needs from this class, then call reload() with + * `require $CWD/vendor/composer/installed.php` (or similar) as input to make sure + * the project in which it runs can then also use this class safely, without + * interference between PHPUnit's dependencies and the project's dependencies. + * + * @param array[] $data A vendor/composer/installed.php data set + * @return void + * + * @psalm-param array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} $data + */ + public static function reload($data) + { + self::$installed = $data; + self::$installedByVendor = array(); + } + + /** + * @return array[] + * @psalm-return list}> + */ + private static function getInstalled() + { + if (null === self::$canGetVendors) { + self::$canGetVendors = method_exists('Composer\Autoload\ClassLoader', 'getRegisteredLoaders'); + } + + $installed = array(); + + if (self::$canGetVendors) { + foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) { + if (isset(self::$installedByVendor[$vendorDir])) { + $installed[] = self::$installedByVendor[$vendorDir]; + } elseif (is_file($vendorDir.'/composer/installed.php')) { + $installed[] = self::$installedByVendor[$vendorDir] = require $vendorDir.'/composer/installed.php'; + if (null === self::$installed && strtr($vendorDir.'/composer', '\\', '/') === strtr(__DIR__, '\\', '/')) { + self::$installed = $installed[count($installed) - 1]; + } + } + } + } + + if (null === self::$installed) { + // only require the installed.php file if this file is loaded from its dumped location, + // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937 + if (substr(__DIR__, -8, 1) !== 'C') { + self::$installed = require __DIR__ . '/installed.php'; + } else { + self::$installed = array(); + } + } + $installed[] = self::$installed; + + return $installed; + } +} diff --git a/sites/all/libraries/vendor/composer/autoload_classmap.php b/sites/all/libraries/vendor/composer/autoload_classmap.php index b9461a6371..7ad24ff267 100644 --- a/sites/all/libraries/vendor/composer/autoload_classmap.php +++ b/sites/all/libraries/vendor/composer/autoload_classmap.php @@ -2,8 +2,15 @@ // autoload_classmap.php @generated by Composer -$vendorDir = dirname(dirname(__FILE__)); +$vendorDir = dirname(__DIR__); $baseDir = dirname(dirname(dirname(dirname($vendorDir)))); return array( + 'Attribute' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/Attribute.php', + 'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php', + 'JsonException' => $vendorDir . '/symfony/polyfill-php73/Resources/stubs/JsonException.php', + 'PhpToken' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/PhpToken.php', + 'Stringable' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/Stringable.php', + 'UnhandledMatchError' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php', + 'ValueError' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/ValueError.php', ); diff --git a/sites/all/libraries/vendor/composer/autoload_files.php b/sites/all/libraries/vendor/composer/autoload_files.php index ab9922c39a..e2392f3a12 100644 --- a/sites/all/libraries/vendor/composer/autoload_files.php +++ b/sites/all/libraries/vendor/composer/autoload_files.php @@ -2,11 +2,14 @@ // autoload_files.php @generated by Composer -$vendorDir = dirname(dirname(__FILE__)); +$vendorDir = dirname(__DIR__); $baseDir = dirname(dirname(dirname(dirname($vendorDir)))); return array( + '6e3fae29631ef280660b3cdad06f25a8' => $vendorDir . '/symfony/deprecation-contracts/function.php', 'c964ee0ededf28c96ebd9db5099ef910' => $vendorDir . '/guzzlehttp/promises/src/functions_include.php', 'a0edc8309cc5e1d60e3047b5df6b7052' => $vendorDir . '/guzzlehttp/psr7/src/functions_include.php', + '0d59ee240a4cd96ddbb4ff164fccea4d' => $vendorDir . '/symfony/polyfill-php73/bootstrap.php', + 'a4a119a56e50fbb293281d9a48007e0e' => $vendorDir . '/symfony/polyfill-php80/bootstrap.php', '37a3dc5111fe8f707ab4c132ef1dbc62' => $vendorDir . '/guzzlehttp/guzzle/src/functions_include.php', ); diff --git a/sites/all/libraries/vendor/composer/autoload_namespaces.php b/sites/all/libraries/vendor/composer/autoload_namespaces.php index c503234eed..95191c943c 100644 --- a/sites/all/libraries/vendor/composer/autoload_namespaces.php +++ b/sites/all/libraries/vendor/composer/autoload_namespaces.php @@ -2,7 +2,7 @@ // autoload_namespaces.php @generated by Composer -$vendorDir = dirname(dirname(__FILE__)); +$vendorDir = dirname(__DIR__); $baseDir = dirname(dirname(dirname(dirname($vendorDir)))); return array( diff --git a/sites/all/libraries/vendor/composer/autoload_psr4.php b/sites/all/libraries/vendor/composer/autoload_psr4.php index fd508720d4..474b9da6ae 100644 --- a/sites/all/libraries/vendor/composer/autoload_psr4.php +++ b/sites/all/libraries/vendor/composer/autoload_psr4.php @@ -2,13 +2,21 @@ // autoload_psr4.php @generated by Composer -$vendorDir = dirname(dirname(__FILE__)); +$vendorDir = dirname(__DIR__); $baseDir = dirname(dirname(dirname(dirname($vendorDir)))); return array( + 'Symfony\\Polyfill\\Php80\\' => array($vendorDir . '/symfony/polyfill-php80'), + 'Symfony\\Polyfill\\Php73\\' => array($vendorDir . '/symfony/polyfill-php73'), + 'Symfony\\Contracts\\Service\\' => array($vendorDir . '/symfony/service-contracts'), + 'Symfony\\Contracts\\HttpClient\\' => array($vendorDir . '/symfony/http-client-contracts'), + 'Symfony\\Component\\HttpClient\\' => array($vendorDir . '/symfony/http-client'), 'Symfony\\Component\\EventDispatcher\\' => array($vendorDir . '/symfony/event-dispatcher'), + 'Psr\\Log\\' => array($vendorDir . '/psr/log/Psr/Log'), 'Psr\\Http\\Message\\' => array($vendorDir . '/psr/http-message/src'), + 'Psr\\Container\\' => array($vendorDir . '/psr/container/src'), 'Michelf\\' => array($vendorDir . '/michelf/php-markdown/Michelf'), + 'IucnApi\\' => array($vendorDir . '/jbroutier/iucn-api-client/src'), 'Http\\Promise\\' => array($vendorDir . '/php-http/promise/src'), 'Http\\Client\\' => array($vendorDir . '/php-http/httplug/src'), 'Http\\Adapter\\Guzzle6\\' => array($vendorDir . '/php-http/guzzle6-adapter/src'), @@ -16,4 +24,6 @@ 'GuzzleHttp\\Promise\\' => array($vendorDir . '/guzzlehttp/promises/src'), 'GuzzleHttp\\' => array($vendorDir . '/guzzlehttp/guzzle/src'), 'Github\\' => array($vendorDir . '/knplabs/github-api/lib/Github'), + 'Doctrine\\Deprecations\\' => array($vendorDir . '/doctrine/deprecations/lib/Doctrine/Deprecations'), + 'Doctrine\\Common\\Collections\\' => array($vendorDir . '/doctrine/collections/lib/Doctrine/Common/Collections'), ); diff --git a/sites/all/libraries/vendor/composer/autoload_real.php b/sites/all/libraries/vendor/composer/autoload_real.php index c3458d7606..b029a9c71d 100644 --- a/sites/all/libraries/vendor/composer/autoload_real.php +++ b/sites/all/libraries/vendor/composer/autoload_real.php @@ -13,45 +13,27 @@ public static function loadClassLoader($class) } } + /** + * @return \Composer\Autoload\ClassLoader + */ public static function getLoader() { if (null !== self::$loader) { return self::$loader; } + require __DIR__ . '/platform_check.php'; + spl_autoload_register(array('ComposerAutoloaderInit737e2bc6f7218ac89562dae1a4248a16', 'loadClassLoader'), true, true); - self::$loader = $loader = new \Composer\Autoload\ClassLoader(); + self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(__DIR__)); spl_autoload_unregister(array('ComposerAutoloaderInit737e2bc6f7218ac89562dae1a4248a16', 'loadClassLoader')); - $useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded()); - if ($useStaticLoader) { - require_once __DIR__ . '/autoload_static.php'; - - call_user_func(\Composer\Autoload\ComposerStaticInit737e2bc6f7218ac89562dae1a4248a16::getInitializer($loader)); - } else { - $map = require __DIR__ . '/autoload_namespaces.php'; - foreach ($map as $namespace => $path) { - $loader->set($namespace, $path); - } - - $map = require __DIR__ . '/autoload_psr4.php'; - foreach ($map as $namespace => $path) { - $loader->setPsr4($namespace, $path); - } - - $classMap = require __DIR__ . '/autoload_classmap.php'; - if ($classMap) { - $loader->addClassMap($classMap); - } - } + require __DIR__ . '/autoload_static.php'; + call_user_func(\Composer\Autoload\ComposerStaticInit737e2bc6f7218ac89562dae1a4248a16::getInitializer($loader)); $loader->register(true); - if ($useStaticLoader) { - $includeFiles = Composer\Autoload\ComposerStaticInit737e2bc6f7218ac89562dae1a4248a16::$files; - } else { - $includeFiles = require __DIR__ . '/autoload_files.php'; - } + $includeFiles = \Composer\Autoload\ComposerStaticInit737e2bc6f7218ac89562dae1a4248a16::$files; foreach ($includeFiles as $fileIdentifier => $file) { composerRequire737e2bc6f7218ac89562dae1a4248a16($fileIdentifier, $file); } @@ -60,11 +42,16 @@ public static function getLoader() } } +/** + * @param string $fileIdentifier + * @param string $file + * @return void + */ function composerRequire737e2bc6f7218ac89562dae1a4248a16($fileIdentifier, $file) { if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) { - require $file; - $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true; + + require $file; } } diff --git a/sites/all/libraries/vendor/composer/autoload_static.php b/sites/all/libraries/vendor/composer/autoload_static.php index 31676ac140..f6a3685334 100644 --- a/sites/all/libraries/vendor/composer/autoload_static.php +++ b/sites/all/libraries/vendor/composer/autoload_static.php @@ -7,24 +7,38 @@ class ComposerStaticInit737e2bc6f7218ac89562dae1a4248a16 { public static $files = array ( + '6e3fae29631ef280660b3cdad06f25a8' => __DIR__ . '/..' . '/symfony/deprecation-contracts/function.php', 'c964ee0ededf28c96ebd9db5099ef910' => __DIR__ . '/..' . '/guzzlehttp/promises/src/functions_include.php', 'a0edc8309cc5e1d60e3047b5df6b7052' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/functions_include.php', + '0d59ee240a4cd96ddbb4ff164fccea4d' => __DIR__ . '/..' . '/symfony/polyfill-php73/bootstrap.php', + 'a4a119a56e50fbb293281d9a48007e0e' => __DIR__ . '/..' . '/symfony/polyfill-php80/bootstrap.php', '37a3dc5111fe8f707ab4c132ef1dbc62' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/functions_include.php', ); public static $prefixLengthsPsr4 = array ( 'S' => array ( + 'Symfony\\Polyfill\\Php80\\' => 23, + 'Symfony\\Polyfill\\Php73\\' => 23, + 'Symfony\\Contracts\\Service\\' => 26, + 'Symfony\\Contracts\\HttpClient\\' => 29, + 'Symfony\\Component\\HttpClient\\' => 29, 'Symfony\\Component\\EventDispatcher\\' => 34, ), 'P' => array ( + 'Psr\\Log\\' => 8, 'Psr\\Http\\Message\\' => 17, + 'Psr\\Container\\' => 14, ), 'M' => array ( 'Michelf\\' => 8, ), + 'I' => + array ( + 'IucnApi\\' => 8, + ), 'H' => array ( 'Http\\Promise\\' => 13, @@ -38,21 +52,58 @@ class ComposerStaticInit737e2bc6f7218ac89562dae1a4248a16 'GuzzleHttp\\' => 11, 'Github\\' => 7, ), + 'D' => + array ( + 'Doctrine\\Deprecations\\' => 22, + 'Doctrine\\Common\\Collections\\' => 28, + ), ); public static $prefixDirsPsr4 = array ( + 'Symfony\\Polyfill\\Php80\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/polyfill-php80', + ), + 'Symfony\\Polyfill\\Php73\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/polyfill-php73', + ), + 'Symfony\\Contracts\\Service\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/service-contracts', + ), + 'Symfony\\Contracts\\HttpClient\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/http-client-contracts', + ), + 'Symfony\\Component\\HttpClient\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/http-client', + ), 'Symfony\\Component\\EventDispatcher\\' => array ( 0 => __DIR__ . '/..' . '/symfony/event-dispatcher', ), + 'Psr\\Log\\' => + array ( + 0 => __DIR__ . '/..' . '/psr/log/Psr/Log', + ), 'Psr\\Http\\Message\\' => array ( 0 => __DIR__ . '/..' . '/psr/http-message/src', ), + 'Psr\\Container\\' => + array ( + 0 => __DIR__ . '/..' . '/psr/container/src', + ), 'Michelf\\' => array ( 0 => __DIR__ . '/..' . '/michelf/php-markdown/Michelf', ), + 'IucnApi\\' => + array ( + 0 => __DIR__ . '/..' . '/jbroutier/iucn-api-client/src', + ), 'Http\\Promise\\' => array ( 0 => __DIR__ . '/..' . '/php-http/promise/src', @@ -81,6 +132,14 @@ class ComposerStaticInit737e2bc6f7218ac89562dae1a4248a16 array ( 0 => __DIR__ . '/..' . '/knplabs/github-api/lib/Github', ), + 'Doctrine\\Deprecations\\' => + array ( + 0 => __DIR__ . '/..' . '/doctrine/deprecations/lib/Doctrine/Deprecations', + ), + 'Doctrine\\Common\\Collections\\' => + array ( + 0 => __DIR__ . '/..' . '/doctrine/collections/lib/Doctrine/Common/Collections', + ), ); public static $prefixesPsr0 = array ( @@ -97,12 +156,23 @@ class ComposerStaticInit737e2bc6f7218ac89562dae1a4248a16 ), ); + public static $classMap = array ( + 'Attribute' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/Attribute.php', + 'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php', + 'JsonException' => __DIR__ . '/..' . '/symfony/polyfill-php73/Resources/stubs/JsonException.php', + 'PhpToken' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/PhpToken.php', + 'Stringable' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/Stringable.php', + 'UnhandledMatchError' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php', + 'ValueError' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/ValueError.php', + ); + public static function getInitializer(ClassLoader $loader) { return \Closure::bind(function () use ($loader) { $loader->prefixLengthsPsr4 = ComposerStaticInit737e2bc6f7218ac89562dae1a4248a16::$prefixLengthsPsr4; $loader->prefixDirsPsr4 = ComposerStaticInit737e2bc6f7218ac89562dae1a4248a16::$prefixDirsPsr4; $loader->prefixesPsr0 = ComposerStaticInit737e2bc6f7218ac89562dae1a4248a16::$prefixesPsr0; + $loader->classMap = ComposerStaticInit737e2bc6f7218ac89562dae1a4248a16::$classMap; }, null, ClassLoader::class); } diff --git a/sites/all/libraries/vendor/composer/installed.json b/sites/all/libraries/vendor/composer/installed.json index 4087e7253d..039eb51db0 100644 --- a/sites/all/libraries/vendor/composer/installed.json +++ b/sites/all/libraries/vendor/composer/installed.json @@ -1,685 +1,1475 @@ -[ - { - "name": "guzzle/guzzle", - "version": "v3.9.3", - "version_normalized": "3.9.3.0", - "source": { - "type": "git", - "url": "https://github.com/guzzle/guzzle3.git", - "reference": "0645b70d953bc1c067bbc8d5bc53194706b628d9" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/guzzle/guzzle3/zipball/0645b70d953bc1c067bbc8d5bc53194706b628d9", - "reference": "0645b70d953bc1c067bbc8d5bc53194706b628d9", - "shasum": "" - }, - "require": { - "ext-curl": "*", - "php": ">=5.3.3", - "symfony/event-dispatcher": "~2.1" - }, - "replace": { - "guzzle/batch": "self.version", - "guzzle/cache": "self.version", - "guzzle/common": "self.version", - "guzzle/http": "self.version", - "guzzle/inflection": "self.version", - "guzzle/iterator": "self.version", - "guzzle/log": "self.version", - "guzzle/parser": "self.version", - "guzzle/plugin": "self.version", - "guzzle/plugin-async": "self.version", - "guzzle/plugin-backoff": "self.version", - "guzzle/plugin-cache": "self.version", - "guzzle/plugin-cookie": "self.version", - "guzzle/plugin-curlauth": "self.version", - "guzzle/plugin-error-response": "self.version", - "guzzle/plugin-history": "self.version", - "guzzle/plugin-log": "self.version", - "guzzle/plugin-md5": "self.version", - "guzzle/plugin-mock": "self.version", - "guzzle/plugin-oauth": "self.version", - "guzzle/service": "self.version", - "guzzle/stream": "self.version" - }, - "require-dev": { - "doctrine/cache": "~1.3", - "monolog/monolog": "~1.0", - "phpunit/phpunit": "3.7.*", - "psr/log": "~1.0", - "symfony/class-loader": "~2.1", - "zendframework/zend-cache": "2.*,<2.3", - "zendframework/zend-log": "2.*,<2.3" - }, - "suggest": { - "guzzlehttp/guzzle": "Guzzle 5 has moved to a new package name. The package you have installed, Guzzle 3, is deprecated." - }, - "time": "2015-03-18T18:23:50+00:00", - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.9-dev" - } - }, - "installation-source": "dist", - "autoload": { - "psr-0": { - "Guzzle": "src/", - "Guzzle\\Tests": "tests/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Michael Dowling", - "email": "mtdowling@gmail.com", - "homepage": "https://github.com/mtdowling" - }, - { - "name": "Guzzle Community", - "homepage": "https://github.com/guzzle/guzzle/contributors" - } - ], - "description": "PHP HTTP client. This library is deprecated in favor of https://packagist.org/packages/guzzlehttp/guzzle", - "homepage": "http://guzzlephp.org/", - "keywords": [ - "client", - "curl", - "framework", - "http", - "http client", - "rest", - "web service" - ], - "abandoned": "guzzlehttp/guzzle" - }, - { - "name": "guzzlehttp/guzzle", - "version": "6.3.3", - "version_normalized": "6.3.3.0", - "source": { - "type": "git", - "url": "https://github.com/guzzle/guzzle.git", - "reference": "407b0cb880ace85c9b63c5f9551db498cb2d50ba" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/guzzle/guzzle/zipball/407b0cb880ace85c9b63c5f9551db498cb2d50ba", - "reference": "407b0cb880ace85c9b63c5f9551db498cb2d50ba", - "shasum": "" - }, - "require": { - "guzzlehttp/promises": "^1.0", - "guzzlehttp/psr7": "^1.4", - "php": ">=5.5" - }, - "require-dev": { - "ext-curl": "*", - "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.4 || ^7.0", - "psr/log": "^1.0" - }, - "suggest": { - "psr/log": "Required for using the Log middleware" - }, - "time": "2018-04-22T15:46:56+00:00", - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "6.3-dev" - } - }, - "installation-source": "dist", - "autoload": { - "files": [ - "src/functions_include.php" - ], - "psr-4": { - "GuzzleHttp\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Michael Dowling", - "email": "mtdowling@gmail.com", - "homepage": "https://github.com/mtdowling" - } - ], - "description": "Guzzle is a PHP HTTP client library", - "homepage": "http://guzzlephp.org/", - "keywords": [ - "client", - "curl", - "framework", - "http", - "http client", - "rest", - "web service" - ] - }, - { - "name": "guzzlehttp/promises", - "version": "v1.3.1", - "version_normalized": "1.3.1.0", - "source": { - "type": "git", - "url": "https://github.com/guzzle/promises.git", - "reference": "a59da6cf61d80060647ff4d3eb2c03a2bc694646" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/guzzle/promises/zipball/a59da6cf61d80060647ff4d3eb2c03a2bc694646", - "reference": "a59da6cf61d80060647ff4d3eb2c03a2bc694646", - "shasum": "" - }, - "require": { - "php": ">=5.5.0" - }, - "require-dev": { - "phpunit/phpunit": "^4.0" - }, - "time": "2016-12-20T10:07:11+00:00", - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.4-dev" - } - }, - "installation-source": "dist", - "autoload": { - "psr-4": { - "GuzzleHttp\\Promise\\": "src/" - }, - "files": [ - "src/functions_include.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Michael Dowling", - "email": "mtdowling@gmail.com", - "homepage": "https://github.com/mtdowling" - } - ], - "description": "Guzzle promises library", - "keywords": [ - "promise" - ] - }, - { - "name": "guzzlehttp/psr7", - "version": "1.4.2", - "version_normalized": "1.4.2.0", - "source": { - "type": "git", - "url": "https://github.com/guzzle/psr7.git", - "reference": "f5b8a8512e2b58b0071a7280e39f14f72e05d87c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/guzzle/psr7/zipball/f5b8a8512e2b58b0071a7280e39f14f72e05d87c", - "reference": "f5b8a8512e2b58b0071a7280e39f14f72e05d87c", - "shasum": "" - }, - "require": { - "php": ">=5.4.0", - "psr/http-message": "~1.0" - }, - "provide": { - "psr/http-message-implementation": "1.0" - }, - "require-dev": { - "phpunit/phpunit": "~4.0" - }, - "time": "2017-03-20T17:10:46+00:00", - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.4-dev" - } - }, - "installation-source": "dist", - "autoload": { - "psr-4": { - "GuzzleHttp\\Psr7\\": "src/" - }, - "files": [ - "src/functions_include.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Michael Dowling", - "email": "mtdowling@gmail.com", - "homepage": "https://github.com/mtdowling" - }, - { - "name": "Tobias Schultze", - "homepage": "https://github.com/Tobion" - } - ], - "description": "PSR-7 message implementation that also provides common utility methods", - "keywords": [ - "http", - "message", - "request", - "response", - "stream", - "uri", - "url" - ] - }, - { - "name": "knplabs/github-api", - "version": "1.7.1", - "version_normalized": "1.7.1.0", - "source": { - "type": "git", - "url": "https://github.com/KnpLabs/php-github-api.git", - "reference": "98d0bcd2c4c96a40ded9081f8f6289907f73823c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/KnpLabs/php-github-api/zipball/98d0bcd2c4c96a40ded9081f8f6289907f73823c", - "reference": "98d0bcd2c4c96a40ded9081f8f6289907f73823c", - "shasum": "" - }, - "require": { - "ext-curl": "*", - "guzzle/guzzle": "~3.7", - "php": ">=5.3.2" - }, - "require-dev": { - "phpunit/phpunit": "~4.0", - "sllh/php-cs-fixer-styleci-bridge": "~1.3" - }, - "suggest": { - "knplabs/gaufrette": "Needed for optional Gaufrette cache" - }, - "time": "2016-07-26T08:49:38+00:00", - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.8.x-dev" - } - }, - "installation-source": "dist", - "autoload": { - "psr-4": { - "Github\\": "lib/Github/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Thibault Duplessis", - "email": "thibault.duplessis@gmail.com", - "homepage": "http://ornicar.github.com" - }, - { - "name": "KnpLabs Team", - "homepage": "http://knplabs.com" - } - ], - "description": "GitHub API v3 client", - "homepage": "https://github.com/KnpLabs/php-github-api", - "keywords": [ - "api", - "gh", - "gist", - "github" - ] - }, - { - "name": "michelf/php-markdown", - "version": "1.8.0", - "version_normalized": "1.8.0.0", - "source": { - "type": "git", - "url": "https://github.com/michelf/php-markdown.git", - "reference": "01ab082b355bf188d907b9929cd99b2923053495" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/michelf/php-markdown/zipball/01ab082b355bf188d907b9929cd99b2923053495", - "reference": "01ab082b355bf188d907b9929cd99b2923053495", - "shasum": "" - }, - "require": { - "php": ">=5.3.0" - }, - "time": "2018-01-15T00:49:33+00:00", - "type": "library", - "installation-source": "dist", - "autoload": { - "psr-4": { - "Michelf\\": "Michelf/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Michel Fortin", - "email": "michel.fortin@michelf.ca", - "homepage": "https://michelf.ca/", - "role": "Developer" - }, - { - "name": "John Gruber", - "homepage": "https://daringfireball.net/" - } - ], - "description": "PHP Markdown", - "homepage": "https://michelf.ca/projects/php-markdown/", - "keywords": [ - "markdown" - ] - }, - { - "name": "php-http/guzzle6-adapter", - "version": "v1.1.1", - "version_normalized": "1.1.1.0", - "source": { - "type": "git", - "url": "https://github.com/php-http/guzzle6-adapter.git", - "reference": "a56941f9dc6110409cfcddc91546ee97039277ab" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-http/guzzle6-adapter/zipball/a56941f9dc6110409cfcddc91546ee97039277ab", - "reference": "a56941f9dc6110409cfcddc91546ee97039277ab", - "shasum": "" - }, - "require": { - "guzzlehttp/guzzle": "^6.0", - "php": ">=5.5.0", - "php-http/httplug": "^1.0" - }, - "provide": { - "php-http/async-client-implementation": "1.0", - "php-http/client-implementation": "1.0" - }, - "require-dev": { - "ext-curl": "*", - "php-http/adapter-integration-tests": "^0.4" - }, - "time": "2016-05-10T06:13:32+00:00", - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.2-dev" - } - }, - "installation-source": "dist", - "autoload": { - "psr-4": { - "Http\\Adapter\\Guzzle6\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Márk Sági-Kazár", - "email": "mark.sagikazar@gmail.com" - }, - { - "name": "David de Boer", - "email": "david@ddeboer.nl" - } - ], - "description": "Guzzle 6 HTTP Adapter", - "homepage": "http://httplug.io", - "keywords": [ - "Guzzle", - "http" - ] - }, - { - "name": "php-http/httplug", - "version": "v1.1.0", - "version_normalized": "1.1.0.0", - "source": { - "type": "git", - "url": "https://github.com/php-http/httplug.git", - "reference": "1c6381726c18579c4ca2ef1ec1498fdae8bdf018" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-http/httplug/zipball/1c6381726c18579c4ca2ef1ec1498fdae8bdf018", - "reference": "1c6381726c18579c4ca2ef1ec1498fdae8bdf018", - "shasum": "" - }, - "require": { - "php": ">=5.4", - "php-http/promise": "^1.0", - "psr/http-message": "^1.0" - }, - "require-dev": { - "henrikbjorn/phpspec-code-coverage": "^1.0", - "phpspec/phpspec": "^2.4" - }, - "time": "2016-08-31T08:30:17+00:00", - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.1-dev" - } - }, - "installation-source": "dist", - "autoload": { - "psr-4": { - "Http\\Client\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Eric GELOEN", - "email": "geloen.eric@gmail.com" - }, - { - "name": "Márk Sági-Kazár", - "email": "mark.sagikazar@gmail.com" - } - ], - "description": "HTTPlug, the HTTP client abstraction for PHP", - "homepage": "http://httplug.io", - "keywords": [ - "client", - "http" - ] - }, - { - "name": "php-http/promise", - "version": "v1.0.0", - "version_normalized": "1.0.0.0", - "source": { - "type": "git", - "url": "https://github.com/php-http/promise.git", - "reference": "dc494cdc9d7160b9a09bd5573272195242ce7980" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-http/promise/zipball/dc494cdc9d7160b9a09bd5573272195242ce7980", - "reference": "dc494cdc9d7160b9a09bd5573272195242ce7980", - "shasum": "" - }, - "require-dev": { - "henrikbjorn/phpspec-code-coverage": "^1.0", - "phpspec/phpspec": "^2.4" - }, - "time": "2016-01-26T13:27:02+00:00", - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.1-dev" - } - }, - "installation-source": "dist", - "autoload": { - "psr-4": { - "Http\\Promise\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Márk Sági-Kazár", - "email": "mark.sagikazar@gmail.com" - }, - { - "name": "Joel Wurtz", - "email": "joel.wurtz@gmail.com" - } - ], - "description": "Promise used for asynchronous HTTP requests", - "homepage": "http://httplug.io", - "keywords": [ - "promise" - ] - }, - { - "name": "psr/http-message", - "version": "1.0.1", - "version_normalized": "1.0.1.0", - "source": { - "type": "git", - "url": "https://github.com/php-fig/http-message.git", - "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", - "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", - "shasum": "" - }, - "require": { - "php": ">=5.3.0" - }, - "time": "2016-08-06T14:39:51+00:00", - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "installation-source": "dist", - "autoload": { - "psr-4": { - "Psr\\Http\\Message\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "description": "Common interface for HTTP messages", - "homepage": "https://github.com/php-fig/http-message", - "keywords": [ - "http", - "http-message", - "psr", - "psr-7", - "request", - "response" - ] - }, - { - "name": "symfony/event-dispatcher", - "version": "v2.8.48", - "version_normalized": "2.8.48.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "a77e974a5fecb4398833b0709210e3d5e334ffb0" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/a77e974a5fecb4398833b0709210e3d5e334ffb0", - "reference": "a77e974a5fecb4398833b0709210e3d5e334ffb0", - "shasum": "" - }, - "require": { - "php": ">=5.3.9" - }, - "require-dev": { - "psr/log": "~1.0", - "symfony/config": "^2.0.5|~3.0.0", - "symfony/dependency-injection": "~2.6|~3.0.0", - "symfony/expression-language": "~2.6|~3.0.0", - "symfony/stopwatch": "~2.3|~3.0.0" - }, - "suggest": { - "symfony/dependency-injection": "", - "symfony/http-kernel": "" - }, - "time": "2018-11-21T14:20:20+00:00", - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.8-dev" - } - }, - "installation-source": "dist", - "autoload": { - "psr-4": { - "Symfony\\Component\\EventDispatcher\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony EventDispatcher Component", - "homepage": "https://symfony.com" - } -] +{ + "packages": [ + { + "name": "doctrine/collections", + "version": "1.8.0", + "version_normalized": "1.8.0.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/collections.git", + "reference": "2b44dd4cbca8b5744327de78bafef5945c7e7b5e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/collections/zipball/2b44dd4cbca8b5744327de78bafef5945c7e7b5e", + "reference": "2b44dd4cbca8b5744327de78bafef5945c7e7b5e", + "shasum": "" + }, + "require": { + "doctrine/deprecations": "^0.5.3 || ^1", + "php": "^7.1.3 || ^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "^9.0 || ^10.0", + "phpstan/phpstan": "^1.4.8", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.1.5", + "vimeo/psalm": "^4.22" + }, + "time": "2022-09-01T20:12:10+00:00", + "type": "library", + "installation-source": "source", + "autoload": { + "psr-4": { + "Doctrine\\Common\\Collections\\": "lib/Doctrine/Common/Collections" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "PHP Doctrine Collections library that adds additional functionality on top of PHP arrays.", + "homepage": "https://www.doctrine-project.org/projects/collections.html", + "keywords": [ + "array", + "collections", + "iterators", + "php" + ], + "support": { + "issues": "https://github.com/doctrine/collections/issues", + "source": "https://github.com/doctrine/collections/tree/1.8.0" + }, + "install-path": "../doctrine/collections" + }, + { + "name": "doctrine/deprecations", + "version": "v1.0.0", + "version_normalized": "1.0.0.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/deprecations.git", + "reference": "0e2a4f1f8cdfc7a92ec3b01c9334898c806b30de" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/deprecations/zipball/0e2a4f1f8cdfc7a92ec3b01c9334898c806b30de", + "reference": "0e2a4f1f8cdfc7a92ec3b01c9334898c806b30de", + "shasum": "" + }, + "require": { + "php": "^7.1|^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "^9", + "phpunit/phpunit": "^7.5|^8.5|^9.5", + "psr/log": "^1|^2|^3" + }, + "suggest": { + "psr/log": "Allows logging deprecations via PSR-3 logger implementation" + }, + "time": "2022-05-02T15:47:09+00:00", + "type": "library", + "installation-source": "source", + "autoload": { + "psr-4": { + "Doctrine\\Deprecations\\": "lib/Doctrine/Deprecations" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "A small layer on top of trigger_error(E_USER_DEPRECATED) or PSR-3 logging with options to disable all deprecations or selectively for packages.", + "homepage": "https://www.doctrine-project.org/", + "support": { + "issues": "https://github.com/doctrine/deprecations/issues", + "source": "https://github.com/doctrine/deprecations/tree/v1.0.0" + }, + "install-path": "../doctrine/deprecations" + }, + { + "name": "guzzle/guzzle", + "version": "v3.9.3", + "version_normalized": "3.9.3.0", + "source": { + "type": "git", + "url": "https://github.com/guzzle/guzzle3.git", + "reference": "0645b70d953bc1c067bbc8d5bc53194706b628d9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/guzzle3/zipball/0645b70d953bc1c067bbc8d5bc53194706b628d9", + "reference": "0645b70d953bc1c067bbc8d5bc53194706b628d9", + "shasum": "" + }, + "require": { + "ext-curl": "*", + "php": ">=5.3.3", + "symfony/event-dispatcher": "~2.1" + }, + "replace": { + "guzzle/batch": "self.version", + "guzzle/cache": "self.version", + "guzzle/common": "self.version", + "guzzle/http": "self.version", + "guzzle/inflection": "self.version", + "guzzle/iterator": "self.version", + "guzzle/log": "self.version", + "guzzle/parser": "self.version", + "guzzle/plugin": "self.version", + "guzzle/plugin-async": "self.version", + "guzzle/plugin-backoff": "self.version", + "guzzle/plugin-cache": "self.version", + "guzzle/plugin-cookie": "self.version", + "guzzle/plugin-curlauth": "self.version", + "guzzle/plugin-error-response": "self.version", + "guzzle/plugin-history": "self.version", + "guzzle/plugin-log": "self.version", + "guzzle/plugin-md5": "self.version", + "guzzle/plugin-mock": "self.version", + "guzzle/plugin-oauth": "self.version", + "guzzle/service": "self.version", + "guzzle/stream": "self.version" + }, + "require-dev": { + "doctrine/cache": "~1.3", + "monolog/monolog": "~1.0", + "phpunit/phpunit": "3.7.*", + "psr/log": "~1.0", + "symfony/class-loader": "~2.1", + "zendframework/zend-cache": "2.*,<2.3", + "zendframework/zend-log": "2.*,<2.3" + }, + "suggest": { + "guzzlehttp/guzzle": "Guzzle 5 has moved to a new package name. The package you have installed, Guzzle 3, is deprecated." + }, + "time": "2015-03-18T18:23:50+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.9-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-0": { + "Guzzle": "src/", + "Guzzle\\Tests": "tests/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Guzzle Community", + "homepage": "https://github.com/guzzle/guzzle/contributors" + } + ], + "description": "PHP HTTP client. This library is deprecated in favor of https://packagist.org/packages/guzzlehttp/guzzle", + "homepage": "http://guzzlephp.org/", + "keywords": [ + "client", + "curl", + "framework", + "http", + "http client", + "rest", + "web service" + ], + "abandoned": "guzzlehttp/guzzle", + "install-path": "../guzzle/guzzle" + }, + { + "name": "guzzlehttp/guzzle", + "version": "6.3.3", + "version_normalized": "6.3.3.0", + "source": { + "type": "git", + "url": "https://github.com/guzzle/guzzle.git", + "reference": "407b0cb880ace85c9b63c5f9551db498cb2d50ba" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/407b0cb880ace85c9b63c5f9551db498cb2d50ba", + "reference": "407b0cb880ace85c9b63c5f9551db498cb2d50ba", + "shasum": "" + }, + "require": { + "guzzlehttp/promises": "^1.0", + "guzzlehttp/psr7": "^1.4", + "php": ">=5.5" + }, + "require-dev": { + "ext-curl": "*", + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.4 || ^7.0", + "psr/log": "^1.0" + }, + "suggest": { + "psr/log": "Required for using the Log middleware" + }, + "time": "2018-04-22T15:46:56+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "6.3-dev" + } + }, + "installation-source": "dist", + "autoload": { + "files": [ + "src/functions_include.php" + ], + "psr-4": { + "GuzzleHttp\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "description": "Guzzle is a PHP HTTP client library", + "homepage": "http://guzzlephp.org/", + "keywords": [ + "client", + "curl", + "framework", + "http", + "http client", + "rest", + "web service" + ], + "install-path": "../guzzlehttp/guzzle" + }, + { + "name": "guzzlehttp/promises", + "version": "v1.3.1", + "version_normalized": "1.3.1.0", + "source": { + "type": "git", + "url": "https://github.com/guzzle/promises.git", + "reference": "a59da6cf61d80060647ff4d3eb2c03a2bc694646" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/promises/zipball/a59da6cf61d80060647ff4d3eb2c03a2bc694646", + "reference": "a59da6cf61d80060647ff4d3eb2c03a2bc694646", + "shasum": "" + }, + "require": { + "php": ">=5.5.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.0" + }, + "time": "2016-12-20T10:07:11+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "GuzzleHttp\\Promise\\": "src/" + }, + "files": [ + "src/functions_include.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "description": "Guzzle promises library", + "keywords": [ + "promise" + ], + "install-path": "../guzzlehttp/promises" + }, + { + "name": "guzzlehttp/psr7", + "version": "1.4.2", + "version_normalized": "1.4.2.0", + "source": { + "type": "git", + "url": "https://github.com/guzzle/psr7.git", + "reference": "f5b8a8512e2b58b0071a7280e39f14f72e05d87c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/f5b8a8512e2b58b0071a7280e39f14f72e05d87c", + "reference": "f5b8a8512e2b58b0071a7280e39f14f72e05d87c", + "shasum": "" + }, + "require": { + "php": ">=5.4.0", + "psr/http-message": "~1.0" + }, + "provide": { + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.0" + }, + "time": "2017-03-20T17:10:46+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "GuzzleHttp\\Psr7\\": "src/" + }, + "files": [ + "src/functions_include.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Tobias Schultze", + "homepage": "https://github.com/Tobion" + } + ], + "description": "PSR-7 message implementation that also provides common utility methods", + "keywords": [ + "http", + "message", + "request", + "response", + "stream", + "uri", + "url" + ], + "install-path": "../guzzlehttp/psr7" + }, + { + "name": "jbroutier/iucn-api-client", + "version": "1.0.0", + "version_normalized": "1.0.0.0", + "source": { + "type": "git", + "url": "https://github.com/jbroutier/iucn-api-client.git", + "reference": "d2b95768b40d3c4606030a626e7c99393deef1de" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/jbroutier/iucn-api-client/zipball/d2b95768b40d3c4606030a626e7c99393deef1de", + "reference": "d2b95768b40d3c4606030a626e7c99393deef1de", + "shasum": "" + }, + "require": { + "doctrine/collections": "^1.7.2", + "php": "^7.4|^8.0", + "symfony/http-client": "^5.4|^6.0" + }, + "require-dev": { + "ekino/phpstan-banned-code": "^1.0.0", + "ext-json": "*", + "phpstan/extension-installer": "^1.1.0", + "phpstan/phpstan": "^1.8.4", + "phpstan/phpstan-deprecation-rules": "^1.0.0", + "phpstan/phpstan-phpunit": "^1.1.1", + "phpstan/phpstan-strict-rules": "^1.4.3", + "phpunit/phpunit": "^9.5.24" + }, + "time": "2022-09-09T16:48:06+00:00", + "type": "library", + "installation-source": "source", + "autoload": { + "psr-4": { + "IucnApi\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jérémie BROUTIER", + "email": "jeremie.broutier@posteo.net", + "homepage": "https://github.com/jbroutier", + "role": "developer" + } + ], + "description": "A PHP client to retrieve data from The IUCN Red List of Threatened Species™.", + "support": { + "issues": "https://github.com/jbroutier/iucn-api-client/issues", + "source": "https://github.com/jbroutier/iucn-api-client/tree/1.0.0" + }, + "install-path": "../jbroutier/iucn-api-client" + }, + { + "name": "knplabs/github-api", + "version": "1.7.1", + "version_normalized": "1.7.1.0", + "source": { + "type": "git", + "url": "https://github.com/KnpLabs/php-github-api.git", + "reference": "98d0bcd2c4c96a40ded9081f8f6289907f73823c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/KnpLabs/php-github-api/zipball/98d0bcd2c4c96a40ded9081f8f6289907f73823c", + "reference": "98d0bcd2c4c96a40ded9081f8f6289907f73823c", + "shasum": "" + }, + "require": { + "ext-curl": "*", + "guzzle/guzzle": "~3.7", + "php": ">=5.3.2" + }, + "require-dev": { + "phpunit/phpunit": "~4.0", + "sllh/php-cs-fixer-styleci-bridge": "~1.3" + }, + "suggest": { + "knplabs/gaufrette": "Needed for optional Gaufrette cache" + }, + "time": "2016-07-26T08:49:38+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.8.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Github\\": "lib/Github/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Thibault Duplessis", + "email": "thibault.duplessis@gmail.com", + "homepage": "http://ornicar.github.com" + }, + { + "name": "KnpLabs Team", + "homepage": "http://knplabs.com" + } + ], + "description": "GitHub API v3 client", + "homepage": "https://github.com/KnpLabs/php-github-api", + "keywords": [ + "api", + "gh", + "gist", + "github" + ], + "install-path": "../knplabs/github-api" + }, + { + "name": "michelf/php-markdown", + "version": "1.8.0", + "version_normalized": "1.8.0.0", + "source": { + "type": "git", + "url": "https://github.com/michelf/php-markdown.git", + "reference": "01ab082b355bf188d907b9929cd99b2923053495" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/michelf/php-markdown/zipball/01ab082b355bf188d907b9929cd99b2923053495", + "reference": "01ab082b355bf188d907b9929cd99b2923053495", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "time": "2018-01-15T00:49:33+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Michelf\\": "Michelf/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Michel Fortin", + "email": "michel.fortin@michelf.ca", + "homepage": "https://michelf.ca/", + "role": "Developer" + }, + { + "name": "John Gruber", + "homepage": "https://daringfireball.net/" + } + ], + "description": "PHP Markdown", + "homepage": "https://michelf.ca/projects/php-markdown/", + "keywords": [ + "markdown" + ], + "install-path": "../michelf/php-markdown" + }, + { + "name": "php-http/guzzle6-adapter", + "version": "v1.1.1", + "version_normalized": "1.1.1.0", + "source": { + "type": "git", + "url": "https://github.com/php-http/guzzle6-adapter.git", + "reference": "a56941f9dc6110409cfcddc91546ee97039277ab" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-http/guzzle6-adapter/zipball/a56941f9dc6110409cfcddc91546ee97039277ab", + "reference": "a56941f9dc6110409cfcddc91546ee97039277ab", + "shasum": "" + }, + "require": { + "guzzlehttp/guzzle": "^6.0", + "php": ">=5.5.0", + "php-http/httplug": "^1.0" + }, + "provide": { + "php-http/async-client-implementation": "1.0", + "php-http/client-implementation": "1.0" + }, + "require-dev": { + "ext-curl": "*", + "php-http/adapter-integration-tests": "^0.4" + }, + "time": "2016-05-10T06:13:32+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.2-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Http\\Adapter\\Guzzle6\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com" + }, + { + "name": "David de Boer", + "email": "david@ddeboer.nl" + } + ], + "description": "Guzzle 6 HTTP Adapter", + "homepage": "http://httplug.io", + "keywords": [ + "Guzzle", + "http" + ], + "install-path": "../php-http/guzzle6-adapter" + }, + { + "name": "php-http/httplug", + "version": "v1.1.0", + "version_normalized": "1.1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-http/httplug.git", + "reference": "1c6381726c18579c4ca2ef1ec1498fdae8bdf018" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-http/httplug/zipball/1c6381726c18579c4ca2ef1ec1498fdae8bdf018", + "reference": "1c6381726c18579c4ca2ef1ec1498fdae8bdf018", + "shasum": "" + }, + "require": { + "php": ">=5.4", + "php-http/promise": "^1.0", + "psr/http-message": "^1.0" + }, + "require-dev": { + "henrikbjorn/phpspec-code-coverage": "^1.0", + "phpspec/phpspec": "^2.4" + }, + "time": "2016-08-31T08:30:17+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Http\\Client\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Eric GELOEN", + "email": "geloen.eric@gmail.com" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com" + } + ], + "description": "HTTPlug, the HTTP client abstraction for PHP", + "homepage": "http://httplug.io", + "keywords": [ + "client", + "http" + ], + "install-path": "../php-http/httplug" + }, + { + "name": "php-http/promise", + "version": "v1.0.0", + "version_normalized": "1.0.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-http/promise.git", + "reference": "dc494cdc9d7160b9a09bd5573272195242ce7980" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-http/promise/zipball/dc494cdc9d7160b9a09bd5573272195242ce7980", + "reference": "dc494cdc9d7160b9a09bd5573272195242ce7980", + "shasum": "" + }, + "require-dev": { + "henrikbjorn/phpspec-code-coverage": "^1.0", + "phpspec/phpspec": "^2.4" + }, + "time": "2016-01-26T13:27:02+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Http\\Promise\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com" + }, + { + "name": "Joel Wurtz", + "email": "joel.wurtz@gmail.com" + } + ], + "description": "Promise used for asynchronous HTTP requests", + "homepage": "http://httplug.io", + "keywords": [ + "promise" + ], + "install-path": "../php-http/promise" + }, + { + "name": "psr/container", + "version": "1.1.2", + "version_normalized": "1.1.2.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "513e0666f7216c7459170d56df27dfcefe1689ea" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/513e0666f7216c7459170d56df27dfcefe1689ea", + "reference": "513e0666f7216c7459170d56df27dfcefe1689ea", + "shasum": "" + }, + "require": { + "php": ">=7.4.0" + }, + "time": "2021-11-05T16:50:12+00:00", + "type": "library", + "installation-source": "source", + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "support": { + "issues": "https://github.com/php-fig/container/issues", + "source": "https://github.com/php-fig/container/tree/1.1.2" + }, + "install-path": "../psr/container" + }, + { + "name": "psr/http-message", + "version": "1.0.1", + "version_normalized": "1.0.1.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "time": "2016-08-06T14:39:51+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "install-path": "../psr/http-message" + }, + { + "name": "psr/log", + "version": "1.1.4", + "version_normalized": "1.1.4.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "d49695b909c3b7628b6289db5479a1c204601f11" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/d49695b909c3b7628b6289db5479a1c204601f11", + "reference": "d49695b909c3b7628b6289db5479a1c204601f11", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "time": "2021-05-03T11:20:27+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1.x-dev" + } + }, + "installation-source": "source", + "autoload": { + "psr-4": { + "Psr\\Log\\": "Psr/Log/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/1.1.4" + }, + "install-path": "../psr/log" + }, + { + "name": "symfony/deprecation-contracts", + "version": "v2.5.2", + "version_normalized": "2.5.2.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "e8b495ea28c1d97b5e0c121748d6f9b53d075c66" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/e8b495ea28c1d97b5e0c121748d6f9b53d075c66", + "reference": "e8b495ea28c1d97b5e0c121748d6f9b53d075c66", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "time": "2022-01-02T09:53:40+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.5-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "installation-source": "source", + "autoload": { + "files": [ + "function.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "A generic function and convention to trigger deprecation notices", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/deprecation-contracts/tree/v2.5.2" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/deprecation-contracts" + }, + { + "name": "symfony/event-dispatcher", + "version": "v2.8.48", + "version_normalized": "2.8.48.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher.git", + "reference": "a77e974a5fecb4398833b0709210e3d5e334ffb0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/a77e974a5fecb4398833b0709210e3d5e334ffb0", + "reference": "a77e974a5fecb4398833b0709210e3d5e334ffb0", + "shasum": "" + }, + "require": { + "php": ">=5.3.9" + }, + "require-dev": { + "psr/log": "~1.0", + "symfony/config": "^2.0.5|~3.0.0", + "symfony/dependency-injection": "~2.6|~3.0.0", + "symfony/expression-language": "~2.6|~3.0.0", + "symfony/stopwatch": "~2.3|~3.0.0" + }, + "suggest": { + "symfony/dependency-injection": "", + "symfony/http-kernel": "" + }, + "time": "2018-11-21T14:20:20+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.8-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Component\\EventDispatcher\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony EventDispatcher Component", + "homepage": "https://symfony.com", + "install-path": "../symfony/event-dispatcher" + }, + { + "name": "symfony/http-client", + "version": "v5.4.15", + "version_normalized": "5.4.15.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-client.git", + "reference": "8f29b0f06c9ff48c8431e78eb90c8bd6f82cb12b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-client/zipball/8f29b0f06c9ff48c8431e78eb90c8bd6f82cb12b", + "reference": "8f29b0f06c9ff48c8431e78eb90c8bd6f82cb12b", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "psr/log": "^1|^2|^3", + "symfony/deprecation-contracts": "^2.1|^3", + "symfony/http-client-contracts": "^2.4", + "symfony/polyfill-php73": "^1.11", + "symfony/polyfill-php80": "^1.16", + "symfony/service-contracts": "^1.0|^2|^3" + }, + "provide": { + "php-http/async-client-implementation": "*", + "php-http/client-implementation": "*", + "psr/http-client-implementation": "1.0", + "symfony/http-client-implementation": "2.4" + }, + "require-dev": { + "amphp/amp": "^2.5", + "amphp/http-client": "^4.2.1", + "amphp/http-tunnel": "^1.0", + "amphp/socket": "^1.1", + "guzzlehttp/promises": "^1.4", + "nyholm/psr7": "^1.0", + "php-http/httplug": "^1.0|^2.0", + "psr/http-client": "^1.0", + "symfony/dependency-injection": "^4.4|^5.0|^6.0", + "symfony/http-kernel": "^4.4.13|^5.1.5|^6.0", + "symfony/process": "^4.4|^5.0|^6.0", + "symfony/stopwatch": "^4.4|^5.0|^6.0" + }, + "time": "2022-10-25T16:22:13+00:00", + "type": "library", + "installation-source": "source", + "autoload": { + "psr-4": { + "Symfony\\Component\\HttpClient\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides powerful methods to fetch HTTP resources synchronously or asynchronously", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/http-client/tree/v5.4.15" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/http-client" + }, + { + "name": "symfony/http-client-contracts", + "version": "v2.5.2", + "version_normalized": "2.5.2.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-client-contracts.git", + "reference": "ba6a9f0e8f3edd190520ee3b9a958596b6ca2e70" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-client-contracts/zipball/ba6a9f0e8f3edd190520ee3b9a958596b6ca2e70", + "reference": "ba6a9f0e8f3edd190520ee3b9a958596b6ca2e70", + "shasum": "" + }, + "require": { + "php": ">=7.2.5" + }, + "suggest": { + "symfony/http-client-implementation": "" + }, + "time": "2022-04-12T15:48:08+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.5-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "installation-source": "source", + "autoload": { + "psr-4": { + "Symfony\\Contracts\\HttpClient\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to HTTP clients", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/http-client-contracts/tree/v2.5.2" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/http-client-contracts" + }, + { + "name": "symfony/polyfill-php73", + "version": "v1.27.0", + "version_normalized": "1.27.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php73.git", + "reference": "9e8ecb5f92152187c4799efd3c96b78ccab18ff9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/9e8ecb5f92152187c4799efd3c96b78ccab18ff9", + "reference": "9e8ecb5f92152187c4799efd3c96b78ccab18ff9", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "time": "2022-11-03T14:55:06+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.27-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "installation-source": "source", + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php73\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 7.3+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php73/tree/v1.27.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/polyfill-php73" + }, + { + "name": "symfony/polyfill-php80", + "version": "v1.27.0", + "version_normalized": "1.27.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php80.git", + "reference": "7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936", + "reference": "7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "time": "2022-11-03T14:55:06+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.27-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "installation-source": "source", + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php80\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ion Bazan", + "email": "ion.bazan@gmail.com" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php80/tree/v1.27.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/polyfill-php80" + }, + { + "name": "symfony/service-contracts", + "version": "v2.5.2", + "version_normalized": "2.5.2.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/service-contracts.git", + "reference": "4b426aac47d6427cc1a1d0f7e2ac724627f5966c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/4b426aac47d6427cc1a1d0f7e2ac724627f5966c", + "reference": "4b426aac47d6427cc1a1d0f7e2ac724627f5966c", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "psr/container": "^1.1", + "symfony/deprecation-contracts": "^2.1|^3" + }, + "conflict": { + "ext-psr": "<1.1|>=2" + }, + "suggest": { + "symfony/service-implementation": "" + }, + "time": "2022-05-30T19:17:29+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.5-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "installation-source": "source", + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Service\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to writing services", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/service-contracts/tree/v2.5.2" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/service-contracts" + } + ], + "dev": true, + "dev-package-names": [] +} diff --git a/sites/all/libraries/vendor/composer/installed.php b/sites/all/libraries/vendor/composer/installed.php new file mode 100644 index 0000000000..f8257a5546 --- /dev/null +++ b/sites/all/libraries/vendor/composer/installed.php @@ -0,0 +1,385 @@ + array( + 'name' => 'naturalhistorymuseum/scratchpads2', + 'pretty_version' => 'dev-master', + 'version' => 'dev-master', + 'reference' => 'e3a9e1bd0acf65734d9fc9845749e77a4de7b166', + 'type' => 'project', + 'install_path' => __DIR__ . '/../../../../../', + 'aliases' => array(), + 'dev' => true, + ), + 'versions' => array( + 'doctrine/collections' => array( + 'pretty_version' => '1.8.0', + 'version' => '1.8.0.0', + 'reference' => '2b44dd4cbca8b5744327de78bafef5945c7e7b5e', + 'type' => 'library', + 'install_path' => __DIR__ . '/../doctrine/collections', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'doctrine/deprecations' => array( + 'pretty_version' => 'v1.0.0', + 'version' => '1.0.0.0', + 'reference' => '0e2a4f1f8cdfc7a92ec3b01c9334898c806b30de', + 'type' => 'library', + 'install_path' => __DIR__ . '/../doctrine/deprecations', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'guzzle/batch' => array( + 'dev_requirement' => false, + 'replaced' => array( + 0 => 'v3.9.3', + ), + ), + 'guzzle/cache' => array( + 'dev_requirement' => false, + 'replaced' => array( + 0 => 'v3.9.3', + ), + ), + 'guzzle/common' => array( + 'dev_requirement' => false, + 'replaced' => array( + 0 => 'v3.9.3', + ), + ), + 'guzzle/guzzle' => array( + 'pretty_version' => 'v3.9.3', + 'version' => '3.9.3.0', + 'reference' => '0645b70d953bc1c067bbc8d5bc53194706b628d9', + 'type' => 'library', + 'install_path' => __DIR__ . '/../guzzle/guzzle', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'guzzle/http' => array( + 'dev_requirement' => false, + 'replaced' => array( + 0 => 'v3.9.3', + ), + ), + 'guzzle/inflection' => array( + 'dev_requirement' => false, + 'replaced' => array( + 0 => 'v3.9.3', + ), + ), + 'guzzle/iterator' => array( + 'dev_requirement' => false, + 'replaced' => array( + 0 => 'v3.9.3', + ), + ), + 'guzzle/log' => array( + 'dev_requirement' => false, + 'replaced' => array( + 0 => 'v3.9.3', + ), + ), + 'guzzle/parser' => array( + 'dev_requirement' => false, + 'replaced' => array( + 0 => 'v3.9.3', + ), + ), + 'guzzle/plugin' => array( + 'dev_requirement' => false, + 'replaced' => array( + 0 => 'v3.9.3', + ), + ), + 'guzzle/plugin-async' => array( + 'dev_requirement' => false, + 'replaced' => array( + 0 => 'v3.9.3', + ), + ), + 'guzzle/plugin-backoff' => array( + 'dev_requirement' => false, + 'replaced' => array( + 0 => 'v3.9.3', + ), + ), + 'guzzle/plugin-cache' => array( + 'dev_requirement' => false, + 'replaced' => array( + 0 => 'v3.9.3', + ), + ), + 'guzzle/plugin-cookie' => array( + 'dev_requirement' => false, + 'replaced' => array( + 0 => 'v3.9.3', + ), + ), + 'guzzle/plugin-curlauth' => array( + 'dev_requirement' => false, + 'replaced' => array( + 0 => 'v3.9.3', + ), + ), + 'guzzle/plugin-error-response' => array( + 'dev_requirement' => false, + 'replaced' => array( + 0 => 'v3.9.3', + ), + ), + 'guzzle/plugin-history' => array( + 'dev_requirement' => false, + 'replaced' => array( + 0 => 'v3.9.3', + ), + ), + 'guzzle/plugin-log' => array( + 'dev_requirement' => false, + 'replaced' => array( + 0 => 'v3.9.3', + ), + ), + 'guzzle/plugin-md5' => array( + 'dev_requirement' => false, + 'replaced' => array( + 0 => 'v3.9.3', + ), + ), + 'guzzle/plugin-mock' => array( + 'dev_requirement' => false, + 'replaced' => array( + 0 => 'v3.9.3', + ), + ), + 'guzzle/plugin-oauth' => array( + 'dev_requirement' => false, + 'replaced' => array( + 0 => 'v3.9.3', + ), + ), + 'guzzle/service' => array( + 'dev_requirement' => false, + 'replaced' => array( + 0 => 'v3.9.3', + ), + ), + 'guzzle/stream' => array( + 'dev_requirement' => false, + 'replaced' => array( + 0 => 'v3.9.3', + ), + ), + 'guzzlehttp/guzzle' => array( + 'pretty_version' => '6.3.3', + 'version' => '6.3.3.0', + 'reference' => '407b0cb880ace85c9b63c5f9551db498cb2d50ba', + 'type' => 'library', + 'install_path' => __DIR__ . '/../guzzlehttp/guzzle', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'guzzlehttp/promises' => array( + 'pretty_version' => 'v1.3.1', + 'version' => '1.3.1.0', + 'reference' => 'a59da6cf61d80060647ff4d3eb2c03a2bc694646', + 'type' => 'library', + 'install_path' => __DIR__ . '/../guzzlehttp/promises', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'guzzlehttp/psr7' => array( + 'pretty_version' => '1.4.2', + 'version' => '1.4.2.0', + 'reference' => 'f5b8a8512e2b58b0071a7280e39f14f72e05d87c', + 'type' => 'library', + 'install_path' => __DIR__ . '/../guzzlehttp/psr7', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'jbroutier/iucn-api-client' => array( + 'pretty_version' => '1.0.0', + 'version' => '1.0.0.0', + 'reference' => 'd2b95768b40d3c4606030a626e7c99393deef1de', + 'type' => 'library', + 'install_path' => __DIR__ . '/../jbroutier/iucn-api-client', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'knplabs/github-api' => array( + 'pretty_version' => '1.7.1', + 'version' => '1.7.1.0', + 'reference' => '98d0bcd2c4c96a40ded9081f8f6289907f73823c', + 'type' => 'library', + 'install_path' => __DIR__ . '/../knplabs/github-api', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'michelf/php-markdown' => array( + 'pretty_version' => '1.8.0', + 'version' => '1.8.0.0', + 'reference' => '01ab082b355bf188d907b9929cd99b2923053495', + 'type' => 'library', + 'install_path' => __DIR__ . '/../michelf/php-markdown', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'naturalhistorymuseum/scratchpads2' => array( + 'pretty_version' => 'dev-master', + 'version' => 'dev-master', + 'reference' => 'e3a9e1bd0acf65734d9fc9845749e77a4de7b166', + 'type' => 'project', + 'install_path' => __DIR__ . '/../../../../../', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'php-http/async-client-implementation' => array( + 'dev_requirement' => false, + 'provided' => array( + 0 => '1.0', + 1 => '*', + ), + ), + 'php-http/client-implementation' => array( + 'dev_requirement' => false, + 'provided' => array( + 0 => '1.0', + 1 => '*', + ), + ), + 'php-http/guzzle6-adapter' => array( + 'pretty_version' => 'v1.1.1', + 'version' => '1.1.1.0', + 'reference' => 'a56941f9dc6110409cfcddc91546ee97039277ab', + 'type' => 'library', + 'install_path' => __DIR__ . '/../php-http/guzzle6-adapter', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'php-http/httplug' => array( + 'pretty_version' => 'v1.1.0', + 'version' => '1.1.0.0', + 'reference' => '1c6381726c18579c4ca2ef1ec1498fdae8bdf018', + 'type' => 'library', + 'install_path' => __DIR__ . '/../php-http/httplug', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'php-http/promise' => array( + 'pretty_version' => 'v1.0.0', + 'version' => '1.0.0.0', + 'reference' => 'dc494cdc9d7160b9a09bd5573272195242ce7980', + 'type' => 'library', + 'install_path' => __DIR__ . '/../php-http/promise', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'psr/container' => array( + 'pretty_version' => '1.1.2', + 'version' => '1.1.2.0', + 'reference' => '513e0666f7216c7459170d56df27dfcefe1689ea', + 'type' => 'library', + 'install_path' => __DIR__ . '/../psr/container', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'psr/http-client-implementation' => array( + 'dev_requirement' => false, + 'provided' => array( + 0 => '1.0', + ), + ), + 'psr/http-message' => array( + 'pretty_version' => '1.0.1', + 'version' => '1.0.1.0', + 'reference' => 'f6561bf28d520154e4b0ec72be95418abe6d9363', + 'type' => 'library', + 'install_path' => __DIR__ . '/../psr/http-message', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'psr/http-message-implementation' => array( + 'dev_requirement' => false, + 'provided' => array( + 0 => '1.0', + ), + ), + 'psr/log' => array( + 'pretty_version' => '1.1.4', + 'version' => '1.1.4.0', + 'reference' => 'd49695b909c3b7628b6289db5479a1c204601f11', + 'type' => 'library', + 'install_path' => __DIR__ . '/../psr/log', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'symfony/deprecation-contracts' => array( + 'pretty_version' => 'v2.5.2', + 'version' => '2.5.2.0', + 'reference' => 'e8b495ea28c1d97b5e0c121748d6f9b53d075c66', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/deprecation-contracts', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'symfony/event-dispatcher' => array( + 'pretty_version' => 'v2.8.48', + 'version' => '2.8.48.0', + 'reference' => 'a77e974a5fecb4398833b0709210e3d5e334ffb0', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/event-dispatcher', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'symfony/http-client' => array( + 'pretty_version' => 'v5.4.15', + 'version' => '5.4.15.0', + 'reference' => '8f29b0f06c9ff48c8431e78eb90c8bd6f82cb12b', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/http-client', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'symfony/http-client-contracts' => array( + 'pretty_version' => 'v2.5.2', + 'version' => '2.5.2.0', + 'reference' => 'ba6a9f0e8f3edd190520ee3b9a958596b6ca2e70', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/http-client-contracts', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'symfony/http-client-implementation' => array( + 'dev_requirement' => false, + 'provided' => array( + 0 => '2.4', + ), + ), + 'symfony/polyfill-php73' => array( + 'pretty_version' => 'v1.27.0', + 'version' => '1.27.0.0', + 'reference' => '9e8ecb5f92152187c4799efd3c96b78ccab18ff9', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/polyfill-php73', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'symfony/polyfill-php80' => array( + 'pretty_version' => 'v1.27.0', + 'version' => '1.27.0.0', + 'reference' => '7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/polyfill-php80', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'symfony/service-contracts' => array( + 'pretty_version' => 'v2.5.2', + 'version' => '2.5.2.0', + 'reference' => '4b426aac47d6427cc1a1d0f7e2ac724627f5966c', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/service-contracts', + 'aliases' => array(), + 'dev_requirement' => false, + ), + ), +); diff --git a/sites/all/libraries/vendor/composer/platform_check.php b/sites/all/libraries/vendor/composer/platform_check.php new file mode 100644 index 0000000000..580fa96095 --- /dev/null +++ b/sites/all/libraries/vendor/composer/platform_check.php @@ -0,0 +1,26 @@ += 70400)) { + $issues[] = 'Your Composer dependencies require a PHP version ">= 7.4.0". You are running ' . PHP_VERSION . '.'; +} + +if ($issues) { + if (!headers_sent()) { + header('HTTP/1.1 500 Internal Server Error'); + } + if (!ini_get('display_errors')) { + if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') { + fwrite(STDERR, 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . implode(PHP_EOL, $issues) . PHP_EOL.PHP_EOL); + } elseif (!headers_sent()) { + echo 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . str_replace('You are running '.PHP_VERSION.'.', '', implode(PHP_EOL, $issues)) . PHP_EOL.PHP_EOL; + } + } + trigger_error( + 'Composer detected issues in your platform: ' . implode(' ', $issues), + E_USER_ERROR + ); +} diff --git a/sites/all/libraries/vendor/doctrine/collections b/sites/all/libraries/vendor/doctrine/collections new file mode 160000 index 0000000000..2b44dd4cbc --- /dev/null +++ b/sites/all/libraries/vendor/doctrine/collections @@ -0,0 +1 @@ +Subproject commit 2b44dd4cbca8b5744327de78bafef5945c7e7b5e diff --git a/sites/all/libraries/vendor/doctrine/deprecations b/sites/all/libraries/vendor/doctrine/deprecations new file mode 160000 index 0000000000..0e2a4f1f8c --- /dev/null +++ b/sites/all/libraries/vendor/doctrine/deprecations @@ -0,0 +1 @@ +Subproject commit 0e2a4f1f8cdfc7a92ec3b01c9334898c806b30de diff --git a/sites/all/libraries/vendor/jbroutier/iucn-api-client b/sites/all/libraries/vendor/jbroutier/iucn-api-client new file mode 160000 index 0000000000..d2b95768b4 --- /dev/null +++ b/sites/all/libraries/vendor/jbroutier/iucn-api-client @@ -0,0 +1 @@ +Subproject commit d2b95768b40d3c4606030a626e7c99393deef1de diff --git a/sites/all/libraries/vendor/psr/container b/sites/all/libraries/vendor/psr/container new file mode 160000 index 0000000000..513e0666f7 --- /dev/null +++ b/sites/all/libraries/vendor/psr/container @@ -0,0 +1 @@ +Subproject commit 513e0666f7216c7459170d56df27dfcefe1689ea diff --git a/sites/all/libraries/vendor/psr/log b/sites/all/libraries/vendor/psr/log new file mode 160000 index 0000000000..d49695b909 --- /dev/null +++ b/sites/all/libraries/vendor/psr/log @@ -0,0 +1 @@ +Subproject commit d49695b909c3b7628b6289db5479a1c204601f11 diff --git a/sites/all/libraries/vendor/symfony/deprecation-contracts b/sites/all/libraries/vendor/symfony/deprecation-contracts new file mode 160000 index 0000000000..e8b495ea28 --- /dev/null +++ b/sites/all/libraries/vendor/symfony/deprecation-contracts @@ -0,0 +1 @@ +Subproject commit e8b495ea28c1d97b5e0c121748d6f9b53d075c66 diff --git a/sites/all/libraries/vendor/symfony/http-client b/sites/all/libraries/vendor/symfony/http-client new file mode 160000 index 0000000000..8f29b0f06c --- /dev/null +++ b/sites/all/libraries/vendor/symfony/http-client @@ -0,0 +1 @@ +Subproject commit 8f29b0f06c9ff48c8431e78eb90c8bd6f82cb12b diff --git a/sites/all/libraries/vendor/symfony/http-client-contracts b/sites/all/libraries/vendor/symfony/http-client-contracts new file mode 160000 index 0000000000..ba6a9f0e8f --- /dev/null +++ b/sites/all/libraries/vendor/symfony/http-client-contracts @@ -0,0 +1 @@ +Subproject commit ba6a9f0e8f3edd190520ee3b9a958596b6ca2e70 diff --git a/sites/all/libraries/vendor/symfony/polyfill-php73 b/sites/all/libraries/vendor/symfony/polyfill-php73 new file mode 160000 index 0000000000..9e8ecb5f92 --- /dev/null +++ b/sites/all/libraries/vendor/symfony/polyfill-php73 @@ -0,0 +1 @@ +Subproject commit 9e8ecb5f92152187c4799efd3c96b78ccab18ff9 diff --git a/sites/all/libraries/vendor/symfony/polyfill-php80 b/sites/all/libraries/vendor/symfony/polyfill-php80 new file mode 160000 index 0000000000..7a6ff3f195 --- /dev/null +++ b/sites/all/libraries/vendor/symfony/polyfill-php80 @@ -0,0 +1 @@ +Subproject commit 7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936 diff --git a/sites/all/libraries/vendor/symfony/service-contracts b/sites/all/libraries/vendor/symfony/service-contracts new file mode 160000 index 0000000000..4b426aac47 --- /dev/null +++ b/sites/all/libraries/vendor/symfony/service-contracts @@ -0,0 +1 @@ +Subproject commit 4b426aac47d6427cc1a1d0f7e2ac724627f5966c diff --git a/sites/all/modules/custom/scratchpads/scratchpads_species/modules/iucn/iucn.module b/sites/all/modules/custom/scratchpads/scratchpads_species/modules/iucn/iucn.module index 13e2e018e7..dabe9d164e 100644 --- a/sites/all/modules/custom/scratchpads/scratchpads_species/modules/iucn/iucn.module +++ b/sites/all/modules/custom/scratchpads/scratchpads_species/modules/iucn/iucn.module @@ -1,5 +1,7 @@ '); + if(!function_exists('ajaxblocks_in_ajax_handler') || (function_exists('ajaxblocks_in_ajax_handler') && ajaxblocks_in_ajax_handler())){ $cache = cache_get($term->tid, 'cache_iucn'); if($cache->data){ diff --git a/tools/phpcomposer/composer.phar b/tools/phpcomposer/composer.phar new file mode 100755 index 0000000000000000000000000000000000000000..6d799ebd6f101824bfc6c5b1f4970f3783d90a5c GIT binary patch literal 2810737 zcmdqK34EPJ)jwX4HS9Zr$kUScCT)`2(z2x+X_B^qv7}{6R~$}zgQbxXuRk|7p3e*o7rj(=z#GZ* zr?Um{l^x8X(qbl;t@Fl4(kXnmA(P(7;w);OD-NfMY{W>eKUGW*c=*Jh*X4@E+$dXB zG)-l*l2uj7q}Q||ml^PyI-9-zRH=~34taT0jmHO5#ngzG&gXM^h$Wr(hKt3qLSr&H zlqn9E);09!Mw9rzl9K;NrcfxQ3(2F8omZ6^^lH+Zif94&?iO} zYtzb#2>q%4;dI}+@xFodV5&4ytb|kRZLVT}k=YIDykeEDO6AivP!ah`*sk*ua~ozi z%sD!NzZNY_)SA)?0gWh_MnqVs^Jdr1S2b<%(j(|UT`da^Zp>$j>6-4Ime#H=0H2o1 zXVDLhs#6v$^mLWpXlh(_5j4f^nT^Bgtd}okSp@2%`-6W#Ne2oIiH6SQoqeqx?NWPO zg{#H}pf0Sg&Vw?d5gvNU=?H$yZ^EteiaBpKw9^~UmGT@{4d}^8m0PN|R4HYc+CP#i z6#CMeGKFHHCeb;Z%KIWr_Ydd11eonjK;7_@XLW}vJqpD_3mfdf^z!MmN|}7R;ANoY zIf|gcYgM|Vl-@55=JRPvhC>`odK;m@xl+-quOG-1sBHDBJ`9ztSIm!lsi9OR+mP@Y zJdvI5IPhJpnFBeZIpn>uaWpl?KWY+2?hOP=;1OZYOYjSfnZN_e5}9uj^Q)@*`j$7f z_w+S)tmtfOZ|$n7o$oDPv{%)g_fDB|!H!d=>^Eh~lpUt*h5wpDsvdlvG8O+X zpOTin>wFDE`kSBaz5B#4%~Zh^sj&de)w^gI(kpK}r`y2Pl;x&#cx)-usTsBy>`*SR z?V~YBpMG@xkij@OjL}T%#1fr{>k_2b4c&iiD^dYU7f0H=u%Nq^J$ z?x_YKQ5GQIKb+Z+b|Bt%h~|Oxxc<+6*+7)%fokOd99^XWNWI|)-faM?%ki)Tlb2Ke zcQbT(Qg3at)s%0jRK6uWmd*~Ov;E@^()}V%(wDDo|Fc0V&q--?%qf4xTQn!6n@>$_ zG36tpM9L(ahtvJ*J4aGQPR?EFvkDH_IJ%ufj`WAW{__+A78xZL44RZzs^OsQzq>{u zJ?Kpj%rqzwy|gGW10!kr2|f8#rl^$U0F7}P=!p+s`jWwbyn}<^$H)$5h8!-gf4fE? z{X%NwMT1aZ79pR;eA1cEotf@;b!CT>G!W^Old5+$KxOq+_p>Y4r8{c~(#`3A*-kDi zk(NwqC<{lLZX z@*@~BEKmC4{I6F{Sbk|KJK&W6=H9wI=`nBr?lM!pY|kzm$*oI`I0)y7%_80V(nF6l z2xVKaJU8k}a;=mneSX@k<_XKUWdp|J&Lz49q@U^qAbvxKNaV^p-m>eA!?$lvAqCRR8+a zP`V{k=+D8+9Jd4ZJ@ceRpgUbUa<4(EEsK=T4QJM6if)sBFXM#tM}N8Dya_?N(wT^C z2M*Jmkp5)w*;frnSq1A#kD@J(g1wG0MHMG~bXDUsE}TJ@v+klNW|Tcijx|lX(W?!rTPt#Drw#FmLC|HvI6Ob zQSTpiTGKD8MtbaB`#xw8W|r&R?)1o@_Rk%ZjVEiFkUscSresjcj@$0x+(xJT-Z$y; zq$7X(&I_h|`F0dj#gZ$D%Vd5d-EZCrcCsuxWV%a3Lut7E4j13sLvum;$`1$EPsl|? z4!a+t5lF8+*gMo9l;z^o)JTRS*Wu!ep2i@({1?x^W-wq+BJ-n;2*C6D=&rgDY4)i1 zeZmxi7ZonV$&~Z(j>0Qx{>F15N`7=jEc0>SbR$e<*B;-G7+ z;g3;cU4EI088r3NHECr?fhhaPa3X#C9hdyxa8VZ{Ma9MP;^+u$?^vmbpb6q3kP30$ zD!oCv`rebRn^YF3EenXG`fpQys5F|+Dp%>wowQ_0Kef+Yc0w=PDhH=yj7=jqL((eJ zi;qjpGp&krwC}}srO`Z00?C$ie}*B50)gHmQwr(5Yj&|pHYY5eSRfVo=**$l#tF9R zK;1Ud8^51iWw=UC0!*!yGW}hp5v73F%Cdv>j5pU>>v8-fpxP0c8tF=p2+V3Pg-D954`F(X@G% zbSmjS-`e;m1Gs1sfE@^)sgVPXQ|^oXf-QethBN8HS8u)5Ah&H5@`_Y`y_&70iu6FB zl7pqoNTX62Fnj+11~r8A;FnMP zx4~YxRoLBXoY@-K0n0`8NOwByAlm^aZWXW0HutzS(uawlS^ey#VmX#pIFx z{J7uWWPm3#UbImN2=`+m1JZx&@bIq_0d+$Cvq?jdzGatXry8iqSUy707>{Po)?hz_r%1&iy?N^Xy#{R7B>ER?ScC^h z$V9JFk82X^u8~?vXjA>jChD~ zbUWhB`P7J-Wp)tDM!L`34c7mejCcsvomTDo94sxROxm+&<5vt%Sj0?daRnw2l3OUz z9Mbg<9J+(Sn~Z`BUK<-Jfx^E_D@i}H=eQk+M^BQy(^5tF@xzP@qZ2pFa3MW^_{A2( z+*YA2%csVMiT07*wdhFC_{ipS4BBM7Q}MPkH=rix4dTO)j(u=9+Yb>RCT1jS<4Bf1 zc7kS!bi-wvtx_C232|wZC8eR(P5o)>6@5)aMtb&V4qj*)Ie*fKG8PK~*pEnPoAj=R zyKQKDGDaulGt`U5k$&$FJG{?u81>t78?$ORE1zuA0o4}!4Tbm0ep+~>ixPi&uK|ko zr!9t9Vk$e7rel;Z1}yX6As>z_($9SB{v`%1Iy`+a!_JCS77+$zQExg@!;pUI$o_i_ zOjJ%$m~=kC(GO&jC%xvsmi^K|MMtIswV}UYxC+6&Dh(ukdcWxj0~b|TAC94SAC5AA zQcNQ0iBEVt8k9=yBT8YsP)v^oR4OT&N;>tjXJ#0v$ZEppAh|S$-Ki1(3$m2iON*TJ zhRe=4%-~=e3Ry(IVOS;wK^|_=AfzX~wzpjaRFclJvom8slsm-wk-l)rhL;U1(MiW= zW%*DLW3d=`(sw4`v%kTJdi_4eiqs|wxf@e4MV!V4a6r`Wk-jH=>gz}R#9&rxU1zE| z9OP`K=r-x+4p^KrD3Q>cUo6>)^>hv^YyT^D*=1shN#DERGaofb75TviUl8R}qCljl zfA_mqQk6QsD-81CU3ABj9=2?MyI!jV(!C)Y>$Qx#MdXC@kHLrsS+b+gh%QnM`=b#FPb&O zdKQt$Tu+dA0s z*I}*BQl#_tfBvVY6t=*lrAmW?7|N>LeCa>ZM^oM0)D$Rd(Y!V%H)_>Aan1X6~dxNSB`R zyC)4h6+~p=6o`sG+$@bEU3AS0-!UkWMVBpWf~cWcWs|T&`sXKZyVC$g)=554duCle z++|g7)vY4EbL~stG%!i<7f6-zVOFGza8T4{uKkj||D)YB6zP727wnQN;#1gCm_2f1 z_7gEuqVuGG+;oZ^)rs=#$iA}er@x6{NY`D}+hkf)fejZz_0m7Zbdvt{)9)z1F=Cm= zlvEnZWbJ1*=H)%LKu9}t7k|TWQlX0^ha>=V!tNSI(N&MW+rUI3O}120HXqXi(z1eH zXzQi%%~+;$G?2dZ{*LDj9yB1><=jY1@;_YDut8rG_nP$IS1$RdDGA@LTuFD^>H@g- zteEuV-J8B=icc?F9Ah{$Rzk*+-ao5FlLoCV zdFUpx=L_1kBIF$LwMjQ0_o(fFZGc2No0@q}5~U#h&=-%s(Qp}Y-)%E>2+DAocIHb7`&z?cRFA`5jKE6-ty)|-(&ClJuQX_p2{$4>CIw4qP<85q z(y65NFFg4vgA^I45hN@SU7)=Li1g>9Kiba#RhR2gN?t&zurGUy$Rizb_^i}UD8?27ZO)fhG zaxiul4AOUBf6^U>nK|V|Vwe$(fRFjnoi#(Gmt6J!Lkw7CiZTo_H4xHi%I8z#`fmzo z`Cb}^wB}Q9x!%A;++zdNp2@CP{Uc-ICD7}qoq4k+cf#Cv7}y|M#2LMe+qge3|()JESzg z2Rv$}78hyqw_mkA5LxH=fc@+B=<30YMoE+IeeDNrDuh=~(gMMb?}H&=S<*1|s3THh8=_g`+ zNS}E6YP-UoP*lPkaut#At$ow;57AtaUOHpPFB`66v{)TAF>^Xw^qO=T>20s zNZ(8HeRBEmhS3(x3#RsZK_Gqa`S;ma&(yLA$Qnh?fO6~qdne60X>RSD>4x?4g{A%E z{W_5$>Af31VA~zlTeUz+s^GR#Gv`kNje%7TWzr8F)j!{G5%p!FK>COX7Ec>8;#PiM z&`6j5>q%=ZqxQ%_)2o~hNKw*%ety_GFVT@KMRo0V0lHXhKIu8D-u_n8km#V%<@L&O z3C=LC4{aCSDx8q+_tGnQgH(j_ViG6LKd;w ztZJybMM;W9Bb~eZ9S0hA>dLhzuhu|j=fmP$FWiuR>8wjf3{C}Zn5fgNPKsERJ1}Ud z^Q1e!c-PMiN@Qt&3e#z@iiMHRBTG-Ex5@!o^ApF=HNq9?ruTot#y%p`I^zKa9+$(p zIXOUQMo(Ze`J{9|>Au%I@_EDHtgyITK>CLMXYw@<(MPtE^y`=Z$c9}bL&7fx7qggw z78d=lh_nQ;6e5v!U$yc~!%)OZ_{Csnvbijhc}HaOFJQM$pB4JHXanitw`{hdkVr)Szdmf00xIS_TmfDcL*|2R!!kDvs8b(OJTSAsad+)Lw-e)H8? zcG`;QzhBITLkE-x>i)duSL3UZ5q3iRvKD9e5&z2^(B4;joz>XOe2+N{<+{x^Z8&qabr zy4Pt7`V3;F1|!dTeN2b^MxaQitbMn2AS2e_XC*14)c=N(+Y`PCTS?lRocWsJDPnK^ zVgM=*`yYvN`TiP(^x3*I?FMH>6dY*@qPQDtr<8tZZBQkv;csdg!%Kd?)TQ|$J@WEH z?5UDU;_-QLcHd4D@00Yo{r0Od;;GbSK2SiPZW8x}v~H)p8V%Iscyea`Psoahbl18q z=Nh2npb7!+!)GMv|7i|U2Q>3yQ76)~KJs|cKt@(-K288qWo~}k(?Tb0_}e}IG9Z=a zG#_tO5tm}H@AemsByBt7i)R~{$a>r_n{?J}{ z&z}ecEcqaN2`8iv&v?y_`-xP?f&{F{9b!dDdoI1;ACqjCFE5UYS?~kcRixKG_4=g- zq|y+RrKvhu9^mL}=wE`7oqejF=+QOYgdV+!U-1tF#o9#3E)?DO1)aoke=n zjV<#`c`s7l9?ndjoE}$YpA^eQ`n^50b`5ImQ%?u&s(3pMOQh9Oc^+ z8xqte(y#oW&8AsQ4~tC7^JJm~Pj$Q~Eew!8I`>YyvO-gWmRbR*BZSLw4aSIWc{>(( zte*6g=@0$Zupciq-?L@Y&s(U=l6K7h{3E6;GE`z}h8bp|CD_blg{1%f+iMq?LXpH7 z#jdv9EVp!jU5@mU?jPMZVL8}pmU}{0jHGY9;xo2u;W)=Ixm=w%?LcgiJd%F@kqZ`? zx*}#!@wgnfJ0+V-`A`<~hfmTVq|3J4X%!<8U$FtfiN-PH{it}_H8Lbfub%(OuNgit zi^lOz2rRxgjr)pyRh4FfG}+s?yFsYHghFtzqenlZ9)9##!U;p{H}ln zWl_=(j(&EDsT#@@(*y0uLScKWM1M$MO6+V`XYu{UG#DJMPOZQdP=guGaA<0T ze!$cl-{h4!C2A~FurOBVY-+?PkgoXWUN@NXQ)BtIdl`c{vhVI#?f=79E&K7^C55|@ zv#fc)TUt+g`lDa5k+Dcxh#Eb{lVQcvWSS;zz4DaRhU+pVC5FwCx9+F=iS(J{7w=$7 zPLFSIKG%<30G$x4t>^|%1Cajt#(%tE0OGrh*EJM%PJ=oIK^7i6Q6rF6UD~zEAjC7@ zjerR9)YRT*_Y?>tb~Px|lLcRXO~kGIBf z`^fa)q!8&(+iyPJ6oM3D^t*dJ+wYH!zMXW1q?gZqveT4}uMpYstnfl9MEdeW#l@yj z{8;Q6DfsoBDSjpCBe%ZW4&(UxR^`>%%C95##Q{wE_`e!#uqwU~k_O1+KBB>-H?}-^ zm0=RfrKGYB+jfN_PNDJPD(OAaN55TfvoRv7XD-4?s++X@9nWts(kzlbc+APx>6#Yq z0~^3`-JV}y9%TbbpS|V^n_~m&f}hFC{FR zUfEqH{rMTCkC=|2Y{NxkrL+EE%_`}?&VR&4SmFQ-&$vJlH=W^zIx=5_DU0GIZTj?u zcGrQO6Xw7I3^Llu(sxQ}(%~=OTyOa2^jo%cHZ=+#0C!d>h%@jg86bWBmS10MAZW?U zLW~vi^H_hMC^G4D2b^~Hqzc2lsG;+u3{}#Wj~(cl!berA8Hd}8nSR#oUgXvO>5VU% z;&F`kr|Q??iY8o_(caeFy0W|Vtx`AXvfX#Jk;9spy8XgRVIjCYJk9jz=CL;^$&tSD z#}~4O0h-mY zGjEbGiY>W;k8Is10wV3d=UaBFgex9xTnt+-Uf}rfm9UJ|H_|Ws;E|WMg;6dOc#?sP zR>OlK9_jGky3aCrFjtdiv@>#N;Qsq+RMHO}bI2NldcxMD`n&iC$pl0C`_G^KiUH-a zIjk@lbBzPrI0jS3Hj*}fGHa8=xLybs92IY`epKAOI1Rb`#^=9dYL63C6oma5=SV&x zdO~{t>>1X#;BqU>+E{-+b^yBE#Hqb1rx@UoDjb=C^Ny=PD491)?~@*%`p)}JqqwIK zTfGZ&8m_+S7#zf{sz^IFh%*F{^x(^Ow`acNEJKhnALDt{&i*3ZZKn(E{LTnYWv*gb z`iH=gF8Ice?>CK%Gc=Z^P6#^?l6GS!4?SIVoI#mdi7A|SP%GAViN8RCo2xp7QB{@tWgxa{Xh9W&}-kQ@4S8;Yfz?FsKDIkqXynnzLku}n* zFT40|gBNG_C&CNF3~!L$Bfa97FIg{i!9?OS+$EI&G5UiHe@?tV(l4|g^6J(ztZ-N6 zvbcN9)I~{rPk>1u``i_k1@_b#Xu;h?o|MX)^`C`5=u(x?6v% zJqSM|25r+odR=L#9v4_J8fzprOY9iwW4}GI$Fz^>6=8lt2xO>p7+3Q$NK9@n5nUu5 z`pK0W3>G(-CxfNqBt&{l>=}Dc!@MrHG09c0tbmQkr-{f(_dMqtA2b*%{l=BY zpwOAC-K`eYJm4etq@a$A`k&eQ8!*F?The!Le8^^d^=xNN&AAa~kRq`wSl_#hrZRgUZrk3tEtd_u3b(c(4q_2CY*r>qvc2{e*#JHL> z9rVihA?y)U@q94!hyOj(t(}#_@!yi z_SUtsG>W?lf_1%1G@Ug4-aTz**Y?)cu}GD_edKn#X5HSp^pFm=YlgIo z^tj_+zWt4oVK?$Iv$#CmZP(dimq=e3eBfMD*Xi4-8$5WZ?qE-1$D)w7A32s&QYYyi zyPsuKHQx4y)ybCj7Xy`EC%zo%ymRll-ZZrL4Xe~v7qBFkh$SI?-;EE zD`!fam2}Rv^K7EW_L|65IQ}@8$?`^RS99v7>5)!)$PSm-z~j#$Nq5=2@>18{c)@{o?ZGlaV=qmVU_G{KRBK46rO>KYt4Xsa`v9k3&cl67znflhC zesSj}?eSP((>`6_iu6Dx#eradXG2TUP|}6N&)RV6X>Uly>dxm?`Ane7jZ!7)%KE?B zQ0cY|JqxUc11=rT4Zv#KZ`s0MiY+7U{pwy-Mzq`4LWEgV<8WWgQpOenRpz9Hq+>7q z%myyE#f(|tq#W$AU$Ai(N=>A@O~3Fu)3~+xyzP3kDvQM)?w4hT6kTaCGl81lDK(Rp z>W`XZYVN}4?Wj4Qp=5c5bfC5~r8d%M4qJ4ksqK{Qs?8jF3|%5HD3y`ke$xeZFrK^( zm6@wMliE)*AK7>A;jk4~USPK%cpPABCD=9CKX>lj69{{~=sW4)yqCXdMAftnu-bBT z<2VVfUnKbZWMx4*&x6HOJWTvMp|LsBQRYdn%wJ zpOr2nef_d;+GD59_`J1)hjprowBccsc9NhL>F;+r%;r+GY!AGnaVN^kkMx+sAD(ME zX!*9`8=Dyr(Yt^}ttQ?7q<0=^>Y&%Q4I+x~2X_K`;rEB9O(jnJd(w_eAdRvhDopyT zZ(e27aGi}Z?5ZQ3B3nct$q(0UmVrk4(;rW<3?1*cI#^EcTnLJtbOMs(yiG}x{;U1a zcbP^eCxsb+`iel2Ui#Y|?lMrvRf38*83D+%1d_Dt9k1GX1SuA#pV{J(+>nW{EfyJ( zUhvB9yBQ{Gqa`{~vK=Lj-NfH=5$}8fA^ppuxz>%FTOLGCC&haBZ$~l8nAE%Ybn8zX zh0mg`rWJ-K@X`*euI%eS@feGfp7}q2u~Fkt7f}dKaWtiABQ668$nzY5BCS5)#*2+~ zX5n*rP7G9!If8hrz>xOb{i;n>oH-FpfSK=#Z6*ECk(-V%%*>bw#qzOU`ib<$EtlBr z!Rh4@cA|7aByJHI9p_&(sAB>`de}h^?`%LSXo~{T zY*>*PI8Xc#(j%&qKQeF?gsb5AAHQV_cGE2*eXZu1HiP2W7$o2H7n5W89A4XLH{&xR zQqq0rZn?~qb8JkooU$<~f(K%{#oZvL~uY`|yHy7HI|G~>8% zN%?3eyh*o;wD+l;-H~@XJc`tk#+8Ve+R4hF1%R}5@0)53E6yHK6u?@}WP3V0R2;US ziOA&$Rfu$_#b@ng5Ra+UICT{lE?SP0)_vk3k{-S4EB`cr^_2jZmPU{XHewOUnR~GM zGe>{HhE1I9ifCKgQ)iD0SH!51sfu*b)T`DTW~wVOi+n`Bv0P8Ojr zlT{ahvFjP1Vf#o=d31rz>vk*%Uy`jhxscRQG&69uSP;^AjTfD3+Hj&T-(Wdf5Zp>K zpyC|3WQpcgzsM?$13Q7iwngMa`m+mL{%EkB!8Q@LTJ+o7RDLl}H^OaS-me>eMcd8r8Q2%3Z7=o>n6@~hHG)vYT5DaY z2rTzKeLoQ`A-#L<&+S6hNi7H?HMj8yE#4B}q|65o_?dGRe-N`zdi5TMA7l9P92tjN zhHYVeykU56-6qmy>z=i7JI8;Ca3b$c#*wvAd2RC2ghnCV`JZ2#RC|0Bb&;Keb|V%H zlr!n|fBNJerag|WF2@rujN?%{{l;Dg^Ll9~>FFDfwSkC+N-|RIOY)$BgLKgDnmf|n zwp@3!;cgO0oUR?D4~PjO{oNruoVzthNTPL+<_J%u)h*w!o|ZdKA{wAu$7>bn3R*XP zZJizft@d*%dGe+odKBIAkl z!~1@0`8Ghyx;sz@4e%=QK}iq)e&4?g^u*#2u4PL<0_^6$4cs-&rr^7?W3;1Vi+GGIbYF~Ie%7Wi70wCSL=>{9UX{y+5hEM%&NhZAT zovre%{*pWo$*Pj{qCdTInvw3(tpN`M2UKIJs0Qgzj?S(yHB=rNhJ9Za`_+fOZ}8?$seuZrqB zHupgpY^2^@9d>rC;3Ea_)PZ)3_Ajwwr1yRD+AkRHoTY78Yl3K4Y^wOtgED?eAISae zNdr_tY5^cUM_Rs?2^{Ga|2*9KI!=hAB40h}JkE>Y{mT~dTtOrq&foo*VQ?Zup3C9k zq@)s@`NW=>A<-$)(_ebtCRA6L0_{4G6BWJ0NaUEnPe~V(UcB*-FBn#x7<8oZHcP`+ z5g5qhGLT81JYn?=134*PD}{w`5n$5C9=iJs16*NtwH%uK2mMB4`!z-2!XEJb$F1j5 zNu8_+5J1fI2jH$2E=hmT(VI40I%kE-OLRpBuUyLxDTg9}`Vm1T{e5@UYX-G~=Vr?% zSDI6cu_|zxK$D*TV)l~;x`G$vL&qf|Tq}U2PrguhtO2ZGdF`98lZ-dVybb$u8B(NE zXY5rnm=%<&B4&4STwet09ufY@ccu>;;0iX=2aaL%JeeX$zxKoud(>r8jE3+6!|FyE zsibf7ZvB;Ew8ErYmQmaKA;ZA`_-?l?VOk)jS{W05ycHuEG+1@52x(r=UnT&e`uv2B^XW7y@d`4&drl zxg`6({WTovRSSP$?qW72FfS>eg$8DCC=EAw>H0 zM+aUvkckO$XpVTYrRUk}wbHb3eZR9fp&M1!ZBZ zA^)>zDe0q^f3448O_HUq+(xzftQGYnJ?+xDHX<@fmb%kpDf@Qn2W0$_-u3tI+ptaD z=Uo}aUm>8T3AO9#Q^ORz}?@1l7jz2Fx!?80Tue{$%hT-gv{wP zB?^$tEtb9{z2oWwtx0h=nL^PIxsJmE&>z{L5wRqsQo}CRt6%3Yu>4D?3b*oHLq;pRH zt95=VtTY9wC!ZM|O%G@_^8ud{)<_??)ahdHezErdQAki)sEKZ*G#)moNXaJ$YQg)-w^bgtvhg zlIt?r!d=JK027|B%_>ONk--9~6whh2sBA~(`YeztqR z!KxrP9fHFp7l?S_^#s9>Z0}SV1*GRTG=9!tR`5x?5je5obSDr_xMKv4^pN|1^9ci2 zp&OL{k<_|oeUYN9mq|Mgo^JM8I;uR7ge;X8`Yq`_(xLmeJYkAgkTf&r5T$fvtiPe8-y_W;ee02J zHXEzLEGuvlrUa3#A0N~m=l}KL`PQCQ5PJ~Rhk8;5Bx%cTciKZvCsvY{YA)v`oEOL- z-$qz%D|DCoxhNYfDN#NqK`PR>KKQQljVLR0sj#Vu-2lE_Touxz6YVxs9;2=U=`po9dD^b^D@! zYS8~HFbvLt+^M~!m+$%{kKT_NzZ(8qum@mak9AjEe~d05a9Wff=zn))V;rBc#bj5 zu{_5GcRwhYq`z3T%0|m$+!F_LDR14g*Kyu`qV99j+P@uZqmEO788Uqi#svQEw2FoK znh2%DVk{Se{{7wa+KotJED4q~dd zXSuwy7$(vmPJh+LzG9rCC}-xBzYBP_0FutSYl`JB#u5vlRi0#%GHWex7(#EWx&Tt< za3#I_p2A0r{9;175zv+lEEQiz6gyro5oFR-?dyjdWDlPsN}CO`j*1(^GxxW#g{Z{*+@>^VEr9gwvo21yT~4}i-}K3 zNptECw^A31xls%ZOr_>awxwSt5z+&W`yYGZNvxSwaxkTnRI092c1YewYrZGym4Zsz z`Js0dOcP`F&cci$eF3jniHAKzU`cPi;!XP-*ru|AH%xIl;Hz`3QbZ+@6ycISnf=xR z172Q%EjaBB&o@UwFBXnT*WNkJ`X{k=JSbj*a*D+^bQJeA!6m)_f&J~K^dy<(#iCKr z_sgV1y6}TP|C^COd23}Qz(hCmfePT}XJyyidzXQVoo9pXR(Em1C*!qvQ64XsMIGrU z-ubxoW6Rqu!_DAhx}9FOVE5Pv>PM_h-&lUPOhcr*j_+*kI!txg zu8-wL#s_gKqrSh0v72n>f7x*t3G<}q-Z}79Ba;{&LJ%>HxbPdTjRqhl_!|NUTqk|B zs5yDB7A#7dZe&}>^nfyVtClA$n zP5RI`POxd_mDmcR#fa(PfBbB-5mSsRmtiX)t{=*@O8V~;?mNf8#aM=k;I!D@CIguC zwjHM09n=_e7zD&bKZDs0mO4$tGY>@0XFHWR8%J)S|1pgA)TY7q=Gyglc z*tDnTjj4_Ia|G&3U99UPeYWdvdu*e9`|8V$BKn2Mkh<8N{ZvHUlIpqgX<3h zNxJZvyp4$0RFKOkZ%}}6CmL0iERn@nAz-SLPLfUn{peF4Kh<>7iIZW*A4NvTM{*l+ z2U<$Mx#bmTFcpCGP1oEp%V1X;sFuknHZEX7)DhU~%3gIb{cjJ^HITkE-gk|uVH>$G zjp0>DhWq1JYi!b4T~Ew3*fG&~+w+!t+vSw;H?TEFIgqYAW38Q-V?w|ckhJprm#48v zTRyzk@u0y zu7wE=Su0VIt~m4y)@D@lA2f78;;)FSNZRza<8AbK0x2Co+em4K1XoF4JG=G()5Hm+ z1c1)rG2K({EtjkBUvD>!XIAbh4bhWZpH|zu2Z#ehy5hzuZHAHg6QYD=-DHkuQO@^B z7n1IL@6R7GurVIG+l62*42$+!W-pZ9Bi-`dWy=g|%#v>cRBUL$+L<(vb%IO!tw%q+ zz~Hvxb0}gl0j@pes_s)(*}oJAr&^9s(o28b@pn_f)-#UlG)ifUd$Qn?UiyVUy~W^8 zpw19;K%HL|Z6tmC&(G{=VB#hroxJ47fc?J$(a>?>jP$HiPd~{(#;pCuB9N7Ghi?;L z(ns!_Wfzrku5AQ3n&kU_fg|<4_}jl2KI8O10wYi8Br|z;T{UL=bWDaIn!n-Ppcqg4M@5CJ*`WV z?ooUB%SOU+Q?g-0Q@&zNiS|Gl^`t-E=kz}rh!{IsNar^oYr)y+v-2mWz_Ut-u&Zl3 zTywYS%;#;|!HLl}Rf6osA{0l<)};r!b;WFPl}ys4bJ{*@QxR$=gB>$OPP(|&uH<9P zag;yI_32_jqugoZ^Itjh86&~tC&Q974X1D!gR^_E?u$33U3ttO3}%cijOG) z*hC62`%5szK~4H<*K_L)aEzZ?7T9)75c-P(O?ur+19oJ^OiPo1wxZ~+(j1e1>iKy$ z8jfT9kSHv-wsBpB(pw{Ad__(AfDB>M(~djGI%aX2q}Q3oENMrK7`H=ZC{5B2ym+b2 z4vn!(g&+p339blD`%q6KkU}WZ)+ukc8)nszW@ye3s3qe?r~J$CpIDys#QVN+wGmTo z+49Y(UtOr_Gw~N$JCc5U{#=^~7!#NhChUA%#-}QXQjngt{M4%qTXDUoCFCPka;ZKc zxTHt!d~=h*orlly;_`9hX8x-MkF<2v{zDVvA;ip^^>IG66{$a&!m#~MRwbyizg4js1mB(1cxNN+o^yb30q- zD#nQrz9Po!IrxNZ8tIcS?r9@1=;A5N|I^XdbbdhmIMO{1I?L`;?uzF`y&A-6B=vr} z+nbD(7A!`?y@52|!I)3ikdgjkANObq3+L3%uX6s`*Ccv6sGQFCHVtgZ;0XOVo+}m7 z{`YMiy-ch^!f#v0{K!vj9ac`E9~q6#7kGd)sT@os0doLA(zu^qBvK_Fn%_ zochs^l{0<%S2eab}_Oq6ddV!_aR2s;@ZdGlr8Y`{CD{nmP zD;J91(vGebO+9@*O-tHay@g&v4X*?W#y!>9-qf~|PvKo(nQXbIPU`MZ51k27m3yXp z)ru8OU2h{+0T=b9^5bQm?8YOjt8nGeU}K}pYhCa=#aZY(@)7zl_bUjw}8keQ;MjwtlG7Efr?RFQ!WAGQ0<_q*5nYkya7)pdALRr$LoyWG%K! zR4A=x+5%UQi`3=sQHBemF5ZUH2oCqB^d6BXJ-FF#?G{gS4HZL&hUDpqPUCr}sax>T zZ^f1c$pAys(Ay&MT&al6Z&&0TMvh|OgBF0JJrO3f@`R+2SdhV1|Du&SsJk8*@h#3zDip!l`MtLgoxImd|WJ8k1)&Kv-8~h>Q3DilM8%bShEdp$U8tbVQ8P)S8oX0$puRA2xEKzXkLCt31K7qmvpkp2oXsn`M{3MB=@fz^g__k>37Iw$bybPx zQ(H-TxUb4TR&}+LG>JjZ-Xyl_EaX*7Ep*I6}l>b&D;i`4Y@D^i<6KViuVY1XJwH<8MO zhRwuw$k!S@!@$O_hNY{dM?)L^Yj;gp-Qnl477UjAXihWYV- zm?k>b=uK;IdfL$qw8wcI+MyoRE=xpRf)6Wg<>MizA1+&=KQ#th7t$?slUo_oD_B`j z-+~WEhQcH(o`j-?*mA~~V$W|xMI{EiVZ|6sjTEo|qhf}Qt(Jq*+vxN!au503@bL{R z&`j;*V@{-Y*dO3#Zb&P4E(@DYEs@DPv9iOXkx>)1`EJ2*K0UZ_xL6!3G$xZM*)W>H zUQk0WKa}hr%G9ehG{RaiAr1Jvn13vUsWxn=+8PpzqF*gYF49#+>^IuRZr-N0;RfOL zv5kvsR$?-0NyJ*!f=TGGJZ##wrRskrIG|setCrR{*n|a|^JlrhfRqtq%nO3kdA(-m zG75p{3^M`ge_)xUSL1l7h;JkPkl{(RQNBpU3zvnmDHl(#-mMn24aibf*0E+?$eAbf zOL#B_e}tfwZSywtZhKSs(6V~30rFgE?7-(3k1kL?6%QSy<2S9cbThGF0g&}48(S7603Te z6SZ{kb@N~x5^KE0i#^&E`z=R=|FyPeVq91~<-e6J6N{)-%xXekpKg1pzZkC$Mn`;! z)hSZ{u0x4ZY}jg&vt%UYKi{|hFw44D4^CO6>!mNZZJZ+qmV-1DHDu=bku5u z;25#sp^ej!0^W>k--ps`OyJA5o?$FAhSTsP!7)1p*fq-2t<@n0@TvPMs=#EU+;1_S z8-}IyqAI0lu>Wgg*AiA`aH&mMcrbama}s`G!7F3qLUv4zp`azIhPsEop{j?z^2bo< z2eAdgZ*6z}?^CB|T!9MMn*LjQ0Fj7v040Zb@B>+WIjs-*r^NCra zvC(agYx-d%{4vB2p<#fLZU&a(mO3h3A%jWS<_}h)@{=!m{_20SV^z06_oI{%{;PFQ z>UhN)Fb0hrLZj4?2!?SQbbw)qcb9?8Fp8Y-7XqU%UC{$AyVgopX zfsHcL+%#T&V!kU^anw&Xn+Zp~+%D56vEzOX!~R#+J-HRkk63HChz0Phv9~ z8}-?1oC6=mW`_=0Im;>Q9$<~pqPw$3Z`}Evt2XWE{%L>I|Djs`G_GY-u8ggTnGfiJ z)Df#Aju>Nqr!ZrGsK`bWR7`56wmDu0KWYt&_M<4KQc&Atfdc9$DLxh3iW~{p%5uMM zbU#&O5}|O>X<;>>RC9l&rZW{#yl;pgLq5a5^P;}BH!*Wy|25?Dj4FVE)89VpYNb9gOrCouz8PTgAK?3Ip=Srjp#7w|& zjs0&emg`KwYU&EyFP(KnDvy92PL`W20t9xmc!8wMde-7Qqqu-;@iLp75v&4~+G538 z3x-Cjs-wUhWm;H7P{Wz|1wrGd3y1BYnT1T_(gU1|$rJ24|D;DyBQjPC;F^R^->Vqbp@1Z%ke$jyV{@TRWxGq*>BMeDj)2g11 z_Kv2OzLr%hI>YtUVHXDfuS(>`ikVRyaMsx%AqC=|l4imvo%OXu51PZv9;BEc*$YN3 z0i#GVJgj1Wc9OFQ*m2{Iw0#9#H%0|IpPs^?^);m$-}&SN&hJo~Y@w9*?`>$+I#gaQ zSpllR{c~yF?L%eotE*pR56<$(8YtNaR#?s-ey4JSVIx!!|G<*g{B1$3z^X#VGLyH) zq(<8GD+5g(+PKNU!$bA*O2K+EOy#M@1T}^jj+x!CA&tfcipdl8DRHIK7`JM3Kn;E- zv8X?kLei9AkD$6}%o)-AW@ETx3&Z|6xn8o5vsWB2<~N6XhS$quYK2>g(fh6FyNlK7}|@LgxMxq6WBqj)Mw9Ua(+6YsXT(4^vf@Od@MRhdBED zN13_moj9ap8}NA6joU=bs;XYU&Y()L_4DyF#~K*okHL08SQ4I5G1!`_DsVyx;w{bd zCS;Ef{f8MtbU3|1=55BTRjQx$AJkZE2|l3g6~xW7LaZMt18vaPl`Awjcr!tM8rQAY53ws zHL^H8R2z*^;?PyAjUi)s{5(gr6lxi8sAzt}j8RkFL@d$B0w$M2zJ4}>fK14;`l&J* zY4!?iYIS;(&Xb^(AWKX*aB~Ittxo7F4o3^9WB}T0!MR%gQkEZe3)Y- zUlC(eqJLo*DZ-RfC#GU}1-0HRZw^!z&IT4W%4P;tRLQY#6h<(-dyS1xRmkRQV0g=n z;f^N#kR3`}WYeZ^eR>?G$CaYfTFA`IOiQs!wro#+&Mq1+B47`!8(@F7-6~UU700!# zw!>b#Y~nI% ztt$l_eMr~9vS~xti+}jdhz#AK)wmXmP*+W z8iUDEo~vinr(P}DFsRHL*a7h;?6PGzK{%79Q;CnOCQ+!PnnNQiMC5KV&cRs+2@{Nd zxSkJ=^o^95j;U_HF%K9XbqcVyX`wZ#s?49cdU9MhLNJ`<9gg)tpT{}Sbs6wCGX)4@ zHQtd19Z&vV*}n+azS^Hj*C_7+m9yD;O|AX}8$9qN-$*5tF{HenuLwnKE)0v&=`b1o zHO2!P%I8XB$_4V-!b)!vxikYck_}!l}1)Q)&-f1z-4p-e-%fK;F z5HU91l3paT3O!*VmePk$`gyYE8wl$C8>>@LKxwb)3Z@-dp@JVB{Bf(LnT=tA3x5fF zaN_l4ZJyP0Gwu%?D&D1)T&NU&#c7KL7%=c52;~70dQoSGXkL>7kP8|gp8t4 z;(1kKftJV z0Jb33r5R8kXjqUmjZ9Rr%gn0fKOz$}cO5pK{;%5%MJ=OporV0VnH+-d%yJif8!Zf# zSDI;7XR7g>FI=AgaR-7*j&EkqB8Dl48~kl%lLp4l^aC$A)!_ty+e`r1JglZ=RAUDi zrVCm@d?nvu>RLKcghHtp9{kUS0m_IULm+mLkHI3UKHwk5yVR@XZCqbAOw(&PEj%MA zEL{@xArcL)Z)``QxW_n^hI4-y{e~NMbp!|+pcjI)M+p?+rh(p-P)!MJP!NI7`P&O| zPX;58#-T{(>-EFx984?>S1!~9oqd2k;6fK-|N0=1uWy)I45rpC#V6+zWC6gEbGGV( zyqN{bui%taD6FTaPUKmzK6QYzCb8M*ev=KCw&BfHHLKS)_7>L6s%>oC!Z9Ef;4hqU z>L1qYDl0Jk8dK zXvhrWD)t(Gjp@iK6yPK8c0yD^2#(L~!G|}cB6LLN-%G*Y_F}q2UP+r~NrnKiB?`{T zgw>I99=D4zv|k=kF1|Y6kM#ldH(b`pQI%KcPoY0f6kf^5=H_DfqnWJIimZ^Rb(Wu{* z<&c_|0!+yxPT*!>_Ng|>ip-U`Eo~T$6S;i|M>YI_pn_DX-Rdv(t_TR=KPY<37n|-tH*WA;m}$HcMGy zV{=f(Z)EIbW1EUU;`e*W0 zZEB<1Hw;D35W-Yx9WGYu-5x$WJ`A)cy zWGq$0ZeUhdBOg$UcE#YCckTNX}Tu;`pd#)6NkUo-0* z{;@jl3H(|+v$kO-o%KZZ9Oax!rJ^W-$hP4K%4J%uHdBHRnV&dZ1dL56WsyZ!WBXz? z#x@$NvuafEPsH9UF&`t8T3(RC{EN*sydS}az|SGK{zF(}*KT2UhTCnCeBkg_vn&qd@# zugnKiw+~^Qn33xPJ7X7vf_K~a$1%ga36G9|1xKYwnpuRNlzgvai{k#8eLu6WXP!5~ z?=?zM)v}zfRsf^N*j^+JK#pqV7p{kKO4d=X;C$zv3dDm{nKhq&cv4%zmh-w}=z znP3!i-I~DOHf2xMDK8N=)>prX#FQS|Rj{&1U`7Sil}h?QQKE>9z;mU6(|ciR&(w+J z*Ln4Glz9DHQYy#^OJvB&)==<9@v1Km8C+UEDgP&&3Ppkij~UT~*%AqFAcy@`+_j<; zke#2vqKHSwx%KSg|2Lfc8_M61IYk7nmb#slZ3Dfga9RiSOZlxzxgARaJp3eEd&8MQ zSWLer^eH8eodWd$?yZReYZdm(STy~t3&*@%Xq|IJ8jIUvdbC!hY{#QF>g&JkBsn#c zHO#Q+>e*}5uw1Ez3k-A&hyLa?b=0oG;~rJRG0+wcuTLmkB5}$HHt* zb`BJIV;E^GVYmqxhA~m3icq?RdANo+ zne!|18{rUmQZBS591)1!gk(2C&Sp=N;%4{BEQf?)-7(r#!?UV{Q;Ebqj z&hQZi-gpZDDnwdR7~q&?d3J zM~wZSw3@S4;4RchO|8Kmt-t@Lj~+ntkZ{TY;_v^dStq7Bfjw~z%-t{y9>+!2V3pkC zxcIuV!q#^M19i@BP%z+b*1;ww>7T*zgCKF=>A;979SX+tte_K-F$txP6Y0#=Oe@vi z1n;(#A7E`|X~89gI>63n#IQ{0p3}tyZzE}3QzwW@!T}Fn?$dND!Whs=d^We3)z^8m z;gt%W%Goc6sXNctcK5k9|15&pX?A3!G|>4BP7t9O4eTOdZbC!=GrIF!L3tXI*W`#y zrII+$F{Q-$8POx?v-3MuOzh@V_j~2WhXWmf2V{FTfgK^g;SO>raoGJu6*cLj)39h4 zUkf@-x$|e`T}rqfO5v-i6QslU%Ju_5c6t2~6iL;W`u&}(|3s30L|~jEO|BXzX{_AB z>t26gFEt@bGg)g#;6Fd6RlTwOt$tBvoPV_qU^UBKRHR)ZK4k)NOYxJ^b05cUz@zWn zE%+dctOW3k-ifc;4owL%<7$kWdAYhs7uZH}8diMy98oIc;`qCD{}HY7c@3&lyf)b( zwUkX%8(`Zr7pNg8@ljqAXfW#+g)`gKld9wS7yWTFjM<|}M(fl7rVUbm@DUd5gU8HQPlB=C+t?4J1YR*&hhhJXp*O7Ip z)AxAU4sdUCt4up9G{XHUd`_#z3P?&4{iuw%{HDAX88No2g2P-(TGGE!z5Z85eceq)V+mz~ zRW43&gT;ng!~2JHPWX~a9ElqqliVsOoVvxMv61gVQ1LrSLdHwOkoux7LB&Jr9!_=5 z6Tj({SI$h{zk%RYbeIY*VGw85)QjiQG|oN;-IgwWY7K6K(gy{h{2Ac@Kj>6RCLXYR zs3qY{oVDP)zNTSTZS|H+3kL=tcDy15?xf#Do4ML1;^>@R9|3!3&>G$vNZCoa%=ZTT zUQ$zp@%Cb+p_gtpSmb(8HdqwuQZqWxa5uK?xFar#?2#KE%oUlqA7&3WvYSrRrgzbi z9uyIuT2=-aU^l32_L-naiNwqRk+SNxcD`vX~1aLBv*+7!=6fb$LuN8}^K;Y^)z|CQo?hoFj>HmJ3952GA1WAZoi-`_*zWHLY`b zXJ22tH*LsAa8BtvjLQ_M2K(+FLz}ll<6?XfM&G&{hUQ2{`W8);5QY|TP0{>~PV89epiekk%rd$t0jkhAeT1&N9 z708^WLIoI=rR;i6Kr(qCgn(-Vir7~|A$!_KAxIVUVhqPp9MO1hrZ;Cc{^8)6`4Sqg zzI47inkksB7?+nuQ#g;Jb4w?b5}*bx`f9x!cDbEtH9Uw2dK$0%K{H)o+a8{rh{p@)PYfWBS*QV` zMwKHzWoezyXarVeY)13)*5;FS1Q_{o&Le8%G26Sh8dj{7*0wA0wp=N1kLL4^A{cbM z`-QL2kivKs0f5bA_}CZ`(}G@9O>FRhfdQEIkSD`)BJftj186Q~H98-+lu}z{? zr&e+!@JlE+irG#G1+SRnF%}71r3dhe97xhi2bPfs7$Z%oI!xPA1Vcbw#bZ2f}!ce)e94IDl3{SKym1CtwZln~CJ>*znlEx*_Ez>4N&d zgZkY|-90U>U0vu_b7%mgQKDiZD2oR1~*!HeAN z+1jlTdT__Im&B$pz#Wbfbh!!eNrR0xzt z#N?r>g?_3~#QoN4hAdlhH4278Jt6E`uioSF6YBXioXk^ad$9MZvEdnGnhJiY=BKhX zV$hp?hO*D-U57cm6wHPf;pXeq*oJCTJ49&1PGzw?6=a85jDeb_I>C=@noF=^uD4+l zYFUG3Y=GlNEP$mscD4$(QS+2NDL=Bd)lBvBr52E=ufaz*%x#$M4Imq8jCT!|#(24j zeyNbC6!iq&HinV6;FyME)lo}Wmf$2F9D;SQI%x#|ywccE9;Q|mhVO+8VBdYfMTTB` zPq()rlk%oXM#0%-`OJmHi^XlvI%(MotX&rb18yMR%nDuZ~Li#^2x=M=MYz$TD zt+F?&#DwNTX28B2uZwg-xuNmtuV~EFVQdFq2%Ra4NoZ`uOxCXUXX)Qm8C5rCXhKVb zm5$Cn*H@&X>zkPYp~8>$E9G_LWy30pY_Bo5S=PFKbFm>Hzvwv(q*A!4!2GP=={7w%}VYZ*9W#9AgzQiD|1;)-L#&K>g|=mhul_FRq38bA5el=_Bsca)HJ7b1TzejBoUn7 z6S^5?WM{lM-&@8SDb_{MYN)GdZg1-D<}Ho5|FvypTaT>*?rEPhQx4&j9^TQVS2MQX zO|J*+0-dXn5+x97*GEjEXE_`z85A1^iV-vZWHt?y%N+jpyV+H^n%-U{zAzotocmyW z#&k}gNu?}~4QywlX!WYX5;Q&Q+alVoNY}QlY;IrG(%RSA)U(|0*ud5{S{*k?B75%W zXVb($d6monwGLh>;|e7Fn{|l66s{NTZ0f?R-LQrTz^af}mCL`j_^w)Y_cZmiH3u~+ zt}Rx>v=vh4a{SidX1S3*$T8o?edZd!_l@4!Z@N`VZFq9xXZ6w~Xs%Hu&Ar2)=`WqI zq>L+OAk^UBvmWU@)#odkIx*!f@91h{WAR!pzZa;`H7 zc`jcjBpZxwcQ$({5sWX1T!Rj6Qtx*Uv?WiUTRmnW+RfYdA=x^%EnJXjcoAOK?<{Km17+(!>IG$Q^L ztuO*DeM>t!pf0vnUi);Wa@H7N%JJLTnsGX+@ogmm!l%9fA4t(Vs!7sx0WondZ zdt97_+gU}=Ddq|Fy`ZbN^?EatRVK%0tA!|DC@q;zsq3v?*50wCsl9tOZj3cu;(HmC zd?B%Bb(mY7Vodq*-?oB46FIyfR*mC(pt?S-aVl?5OH*}pB~d$%K}pJ`o$0A*DcWYE zE@pLMLM+x4`HKpF!Qy{MYpr~V$u1@QNww@^!XDkR3kvI5K~icqw7*0qS@7Yzv!dwY zsyWWLS{z~@mzy-k2k@0$`TfQchA#wh(m50Wvs-)LtQv-A}}xgea8k!LhKSR|~Q}R+iYb81D@>P6pN!<_X_SNphNYp{a-Z3*9Zg zo|#NUCK;+EGBq6S%?{gs_qBKo=xF9MC6Wk{nb-qjiBxE}>Ryuog(Gcv8vj>j&c9;f zjb$Vl06@3soXG8|)(lg#_i zGp`e=wca3g4~xCtUSm>#cGk5LAt2U}fdQ}`Zq+qq@iMX7B? ztNa?mJvr%CO|C|!soTX2)f9_9I&=Ch9n*Se$2eIu1=^SBoy|d~A8$`fUW5wL>+j(2 z(7tfQ+q3p|Qc^u@Ewr)<$|k8DU>Zi1vF9AEn#joe5KMru@pgm6FP#(GI_7{|DYU&a zsYtNoNQOpcMoHxslYL(1r~?${duG|l!FG9)dkwRheKQr4FE60U7YltGGwNV9BHUQ& zibQB)-Sjin(A8$20jW>^vyf=5I#i+R2y(CG!+DEEf;?+3(V1Wc*A`ScqfGx~MpM=5 z$T+fAlR^j#U%m}KIIaU@&VSzd+9~5}w6;rl-ZxkLD)w1l`vcFLuW^Eo*CpBpk0>M3 ziDgM+qrFlgxfb5$D5FPo(5h!wtK*lt-%!~1lnaP!W{ZP0>U#2EG_QI(m2r9jnNzqv zPW7hhD2*ap{Yw92zn{ILW>a@~sr?<}C4%}3De1-XE)he6>LJP9qAEvyNJiS)n%djW zkOf1q5!T^G?8XgYI6jqSDSSP%)Phl-`%~gJTY#s*%c6b@OTh-_hmusFQdXQs9b$$)^oZ z;#ibDzzJnC9I7l)vx24bq&zYmTR{q~dUYT2AI#%=1JgY`41?n0s`mG({A9%Z`%B1J zLu!$Ig1W0kqbhDmZ}96z&CX&5O<*B9{-vK zrgxa%BwNORF_mZ%EGpjN3F}0&CbtndC#AGw0DH&D2mS(Yf%Frwow^vNb7ui77iplH z2BJ%W;N`=1>KFq=mh zI^OsM{sERUYNEYP{8vFyWuP>AVA`%N*IQV;s#4%RG!t!3&QgrhpLJxVCM`Wz`<&wu zh>gtSzdE>VPy>&yM&oAUf7%6Yn6;P%?dR2NlWXu5@8tnk*xa_q#Ob$b0SB0D#xPrl zp;J?p;8jG68}UE(Z*Oly?dCakM{k*xteu6cdFrZk5Wv3yhVLWbD|<0i)KoA-SLeRr zfC5Ac3NH?lXOSAM_0jT%##zNdJ5)-I%F%R6s39yf5*^kp@EzC~`U0#Y=AdPq8ByL* zryN*OyUyypS4w^&epM4Pywnx?xN$;hBzIC-UEuhsuQ5u>!il~FP$itQf1D6ANF$i&QH52DhFW3pH!Wk~R?9?blYC>ZJJo)v8prWQC3Lk>WFV_mi;*!Jp@t=miW#y| zwF-|&cLl?eYJgD|=mWNeqH#5HCBEZ|#e5k%F7YFH&{kGPEGG>PsK7NrWV2J-yKY`c zl*hY@LFFrTT38Q$R2VACcF2oT{Gk&9SD_LOuSS2x_5{iTU0fs} z8z97p*cG$crjngQyN3f+c4kL^df@kBAz+TFZ8C>XYU@kIy8TI42L3h`g`~9_V+bmP zqyy=?eJaTw3E`NQ^Lk&Zzu3nGPGR93_FMQLdqKUl(MCSpZ6c0T8-Hw;9SD2nP47~O zgl~VIUln+;t)tg@^Se{2@)dO80Ucs-AWr?SOteJ3c^RW1&zt$xU71b-V6a%%*DovZ%&6_i9GR|zeD z(Lsc(;KsVqKV63krOQIt?T|9HsRseG;qN)u6|vNtDDv{WxP0!d=5 zTD@zW2G9Ja?xppN_~%O648Poyw;n8w-><<#&6Y6wfGGhPhsfL*5-!Zbv26@Ohp<`K+agBWS|EPP{wll1oeQk`Rl=tPZtkSIIbBlodt{4 z^I9(7EKQVGGQL6IG)D#W25FCBNh}L--J1qU&({?BV#r&^`S0r{lDwKrybZay|Bk~& zUjkQn9?76H~Mn&c5o%R$f6=>FYtgtMwz>#9(;)eT08Wqfu>}tGCGz~4k&HPq{gRGyV%-X z_4{vIp|C~-;;H0UC;c?O_=Y7UI0i5B)N&HVt{1g-M4aEAV5x<~GU7XSPpJp9^xg#3 zIhaULE8xod;)}cX^>|TyC<>QO`7uDHUqzpZ@e~(5A*Ao1aX7^;XdN6j7vDTxZpFRx zKOf27$+wO_{QQgM!^1CW4IUp3f8+ng%b<419n!Zt?F;w}q$y3n1R_zAIKJnt-J_lv zgAcxv*LT4oP2;9|XUk2CIiA}yrNfu~_bk0ZT1;0(7A3{o=cR9P8nEtn@yp;45L%4e zO1vpw68CENC~=AdBxiKTZhHX_1BKT$QsQ1XbeESx>Kg&bPcY8E$ZMUY!dWMVXMHG) z_ss&pQhUE&{$<}dtL5d9r9ou*#Ko45$85{x1wnLJG$jVCG+0GEnZZ)@F!m);zJVQw z>ywieP%166vxTKaF z5BiGkKH?l%TOQQtM~QMRJQNqR4J{87(9&(4qM&9+&p>g~Y2*~4OCT;_wE_$ENDaIq zP(|@`EZ9dxBHVtF$twzT<`<@Pe?r+G1>PBE_3@^#csO2^Qb{@2sn`{Zh`b@U(d|jn zF5-43z58i1i{RyE)(zhY>n44Kts#6ZQqFS`UBSHrmkBL-(1kxtrl$z?D+={RCwXiF z;j#7fK_;{M!OK&2PQhzof`~I#Pd3C<(?4<8V~X_R;dHTMzc%n&1v-6WiD<-cU?0Hc zwHb;!!ouf1qXJqKeRfENr#zty5Iqhek_?Y_<`g)6Y^ME&1BiqYHuF6M*g*W~@z$-I za)8*v~@iabndhNjQQNJWoq7ce*=pTKOTDp|SC0BNgTo-cmUk)SR*c zYvBM@<#IfD$1M7A-q=cl^7}uBi=6(4p4-?Kl$J}76~q+#wm{h~8I*(*Y`_oI{;}Sf z(qFVIXD<_rK=%CAbZ{YtY7I0mn5yNiM<7(a+k>`+r24VG+q=0u9Sv_Z@o5N|6g@+& zAKMQSDF3N7Vx6__s*Dg)1aBSHeDn$*ML)iEDFL>=^e_6|(!cvd+QHrCo4!Hl$Ou9< z4f3%wT1tKH@hL=>lkMTf4O;3yvquDqNfqXTQ91fVWF{Mo6?YmBAduO@C;;`J!6Q#; zmr*Fl!NTuugVwxy2MXWD+gxma(3CBRF#;r3@W`UpBOuL(BEm?}dJsobsFuPX;dKU3 z3f@!Pe`S_u1zt31g(v~muh;WOKq}?T2P#MpCa)%Xr?5krN>On&gC`OaF#Z)61eT#) zTQnJuo@-sh#9ZNzfSEE-)&1(F$E1Z8JIdLT_s7kg096b<3R844fHrfU^!6aXX!+YQ<|7*5Ne322j@!?rZ?7X<%4z zlo4)*kgST>DPvYv8t~4<7Pu#i-#ZAX_cR{$`u!6o6*6jmbdAp0>uO`K-*0p;CL{g3 zO_FGjKrTodM72g-xpk`5rjlKR-b1t1{PqXKN<@81_Bruco4OT;CuJA7t)f~yuKS8` z1q&cSCYdJWlF*S*3Y%azDFIt}2JAQo68|Wchrmx@fBby_L^oNEQrHL%L3YhPV)})A zB<#ylpi*{F@Dnt50WQ~s*4EO8hz>g)k1lm#Fed$qD~*I(cRCxJ-@f_|VEDckP*^&^ z<%yJ@8O0_jA@ZUg9#$yf0Ab&0O)}s2{5l>6K87%;_g&wi?w7FhtQ7Asx9sZl@(P-V zUiD&IL)I_%K&JzM?gG-qUnpxw?2UnI1dC+gWIQTed{C5L0wZi^vv38Bc_ib4awTXK zf?|U2$TgC4GOV#kng8m9XcVC2BUG2dF1y#76t~3V_uVneM?QJh1+|^9{Gs$hE55A> zN+8a@hGynoAqf@_tRG8=KWrUBWahcqXdOVzdGp0`V}vjL_v4!>JmrK8jw3n03isk* zzL&D~ahE3p$MUt|o?Sw02Jg_phVcM{)LCjI`q`IDy!6oL4$(=_(mjPOsqxuYOVi=>4$CxZ;|f>NrYy98N2V8MI3zmpp*b z_fUAB$~1wa^>DlJGqBa{l!#1ig35}$8K=f4V^y{tNQf$owx6KnK5~IJ{M_IC8M6N* zL1^j3kT`bI_XDBlzJ7pMDVb9u8kII3ipPz{Pq3}DEsh?)5fY_HG3*L>#xTefEJx}D z`S_uIizQOiZ_4ArcL$S={_*vhR^zPM^z-v$JrV<;95kGMTv}eY$Jy30ztB26_sRk} zJ3}4$>Z4C1EdN1d3q3)mqw5|*=F8^~F8ju6EjLIKm%Q_ zF`vqoVD%~p$sv@cMr;n>p)T9@i=FK~7O&aFJ>h$=w)QvGEm7({{~i}I3?VfzuHvH4 zyK_a{pU90E6`)yf55`^Ok6)t9)U<`Wz5dAi8#6sH@Jzi(H`@#KwExLCBY<5ZQHubDHw8#yOap^YHzwA?X-X-k2Dp+T0n+~( zu1-n$R{dIi6d`+_bsSA=Fay})@o=|glsLCI*! zI=t&h?hiF0#iFUHk(ZdTLTU@b5lovR=*nn_7nJJrt>$0am+h0r_nkq9^{3Os4C|Gl z*u-#h-lsI@n}E+Wsu0#35z#DWb!j?6HYjm}DOT*F3EmV)l_ZdShGcTbE?6VO{X>O* z-2>Tt84dpJ&03GVwtu3i6u`FZJE|MOBNkwbgOIqwAnhke8XugPxWP%%TE&?-7`P_p zdJKgLwuQp{X)+A`w`WY=y9Hns2ud+5rZTjOgAY;Loih@CGy9adodc9lt=GNDy69+5 z?$PqEt0cTHVOK&XACaV$kUhkjI36(OEU;WsxQVp5UO)&Q7rvjj9({V!*K;i4x3zoV zL)QhWM{#K;=iM*=(b#e#xez?Lxi4L-)M_yA@raJT`s2U+1JCrlJ2}^xHWk(xqHE2m zi}Di{H&r?dk9PJs_5w)|yFmHD2Sw8|I06zK&X>$ZMyZemxa)je>xi5jUGxt{gw+3T zAsTkfJj&a?*}k4a?mW~V$ye08)lG&9-y)b2GQHK}WYYK+u5N=CxT-)Vj!M{`q5Lvb zQYzZv7$FN;km{5)wzZmFcHLaoM;rY~Z#=l7@Y`zcosZrlBcw4wEzl`Wdp$aNvyglf zl3x7d@B0^5tpx<{ZEo)`JZAeREb`tn%%N-!;w0tM#}DN#t}We%DPQE}3h?_sqc8*x zaY1Q73r-+P=_`!_x*AKE2VP*R;MqV3jC{IecF7SAFJDASqYif;MY9PG)U9puQn8g4 z=HeeiaN6*jU2YmLxH~w+G1;nZ^{gw-n1WY%rV|@G4OCtD!f{!6YHBV++)Bj4)(H$8b%_Up)4q^}4dE^)@S@!2)EfDTj+ z7^wK9Zb2%VZVUgqt#4%?)l4_A-Er?6

PtHH_DBr8a|oTwR%l7W5cV-{1{DYq-k+ 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 z

ADPN@nrP6ko~NVQ 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%0DC)ub-`wo$2 zEy!s*5D4Ay8jt#T=|3>)S%mw2W8=yAT<(;8KofQ^g$s)Fb^AMm!=v+ko54P0_4wrN zpuUX&#p=|A3yM!w$SZvD=CN%EBXWg8MUG6}zAog47-O*ieW^M|=L+h03#E$cQeDt) zrbBZJd+>jwnb>rZ2qw3e{wbrG4UOSs6sWP&gwiN`=WEc*Kh_L`R9YC0Gno#@AYP4k znl8c*JQ4wvo5@c+u6)bi+BfkrT2)qd?xuPHUlRYUTn}31iY!TY`}HsqCjw|ih)kix zqbx4#v8mZLgfrU?w7VT_K9`9wz^Y>2B}q7iz#aQ^)0i5gbH%c+Ww#oyjsY`2q8hdstqr)i0FhOih8Y>=yk=7ogea$TLH0faFtV4gfAgQ|EC2m zBgV75QY^Qj-<=^{zC~{R?pVzW*p8T>Zj;~-B$2|!$F8kpliCd?AHIBA-uOyjNP*@8 zj1UnA&3b30$%%^wkCBQRNY-qLCA+~cld#gf8m^*P8}7%9ssfvWS+BvqCr*YT(KaDw&nv^Z9;OgzGt&c-mf+N6EmB_ zKXwd{7FX@~_7%bio)CnVv@y~flM4G^V+YIr{$qO!dz*qdKBgqzS%<^(K_03%JUu%Z zFkx*9<>vsT??IOQ=Q;ouku^bbmi1j<40v#W3=hc03+yFZ{%iay+zS{VH^q1uNtTbT zxe~_NP-r{e1mzFpEtLfWW>wW;Za|``W;pqITFlJI&q_}MEwVevEmtCiH}>&Vq*X>b z;6Y{Vb~lP-0V?k^leEAiUP2xj&?UJ4Xs+#*`eUb}H@R|E zXJB@tk(u{(S;?lFzv2jKQ8ewHOQRN1YV>~jUKp-WFcs%x>h>ftlaO5nJa%-C9eS_& zro_hbL|BIVfkUvsR|Vfe1Tkhx$1E-xS9P=GY*MLuFF|bhBO_3oE5GPkr!XY4TqrF- zJ9(~v(89LDVwkHBA=R~TyzIJjpX$+&xQLg)qPC)w;+fh=Q=|jO#QT+%l?34uD!^De zdkcjY$>U`LJvmq|i?_$EmAO(SGcaqo4lO+{3g?R03F-FD5e4_ItPRe_)>Jj;` zOV=1jQ+E$1%)K?J1R`nzKvD9J1JGn0do!DJpy#%Y z`=XLaByrt|`EoaBR*Wd3?8d|ZyLMAHomY4Q%8lHrlzVNFXP>vvt9|fdl0JNMz3fd` zuUZjL2B+h<16!||x|p}VUtO6=Y$;3en5y@e$q=IPWU%8#$opjPiGh{BTE16;>7*oN zE4EgP0N2{{SSGL^71R0n94Tnvz-uTa{r0m;AKM7UT1d+-ZCV%YseEgPUeuO{$zQ|L^7;4@%ctSh>OEm3k+7>uU$S zV14ngrfr)(JYC!v%Y@RK1hDXon0dI*|9OOT*bbj)Q%kOG zN|!P{XOJafI6r0#)4|S9afJj^Z%wHuplia6zNu49WPMKWj?}9~U6TM8`W43nHUw9{ zURHFGpBfBknvvC$Su5D~o_;WrXW@9Su12uHE#~2Jrf3|@Ms}E9SMU|^rbZ=P5zN$t z!z9QeocCAdG*q62`Z>iwjQ4yJyQO>!5vuM(aqB;x=>^k=JpvgLnqjvi0gy*6%AtyQ zlse1x`a34g>15L_t7 z$?KRhh^5eS-;V33Q0Ny@8UjnrzARqGX3>fkBe0sM`qujoyI*FUz(p z=CxHcG1zcc7U(nTrmbbQQRD)e#)llDZG^HAQw@1gquVUR!l?Ex9{HCgjz?s5 zm1)DG*f28R5k>OpmC63}0|&?R9*`APYPZ5oC`hnn74({%8%;;&vuTos4ri<%ayJ?D z>Lwi*NPw~P+sWpZhDlnq-^kS(cAE^?pKy}VSAs*M7pc>d!P;3{w|VuruF=$;UW=+@ zr58w$lar|bBu{0>~uPRy2&cGfLk^OMU z7RSR08O;wWRdi7bdzHcvC!;COyy1Hg{dn?zU8Re6if`foL9F=q>B>SOK8Tu=Gg4vd zfTJ3c*uveXV9Tng0{K4hN}b!uvD8?){!{|`;kD49u(+5h$e+M}69sWRLcj(siz8uD zTSlvGoq*E``74URpM3&Y@)x2H1Ar;E>^9ssh88iaQYL@=*ei zm>VheaS8VvXCX61VmqM`=?0Z>WUW{zM7V@iAW`%;Q+ zAr0%a*a3hP?FKIy{fJVrNysAUFimFj@s!R&r_>yf5K~~wiypUp4YXgxCW+;-fsc7t zL3|vYTX?AsYtrD{-aR4_vp_UBSjt$w^uN*nulWCHs@X}eR7FkHn=N*<)nwYXYf09# zCa(vh!30U<#t4IBw}}^hp`$@=!~(`&)OQdb6?jhZlt9}T=kU$sq}#?T zKX;Mj1F8mC--F{7>}PQkMEy&%r-Zo8rUIWo9dExmd%*1y->lZN{(tPR+}7e= z9x zi|j}w9y>${2X@-$9qap$!6>F^kN5QjfKi*!#ll+|p5u$TXXP*R(>z|_m5inZX&K;&iV`jfxcD$_;tW-LQCC6uRaT*{ zYZio|a$VL$EUIJDi~WuB zDmerD2GgNjnlbKCWMszy3^W>7-tx5fdWg~`c68TawJ>23l&~2F$gp&&v`T0yxP=Ff z^T9iK9D`FLMnVt7N%$#hTH0^DB4!fq8t{-xlF&pi*kWQoYJ_Li!tu>=fO^V5vcNV9 zXsSOn35&giohaA2GKlRaMbYWVs|{SrEuTpYqt}R`3QM=rr^&^lg`HMfo^jGKY1#%> zbIPXYtBETAWrff+tA*k8VIp>{fJY(HGaQ1kZ-e#46Euy!HM6M_jgVz_eL~~m=IMoV*fW-Uz1w1Qe zym+AE!}=f(L|kG4lK%Omcq82N!Gom?2^xAtmRzqMXN;Vt^Yizx@E{187~w)Hd_a}R zEsPZdM{it>9mciS4xej~mc41<#}DaWX-Yl@3k~8b=1+#nf$%pV=xg>E9gAyrkv3Qa zvz+O#9RzQ?lTPrKU3OK1x6eM5;M=;nD#6d&$BhvD6$Z-$ZyyQ4#|}CPK7Y_o@cDhp z+L4vG7Qsh|Ty|j%e2gZ+`n^%6=Mf$ zM`^4R%fwc3z(Wr6G=qdVHa11aB39|qhK7%HA<)`l(*3$R)sMPB(cf0tfuaI zK(*isCi}Lq8`{3m!U|d=vai83sb(WXXtz7wAMAF#JVgn^Ta~lVAV`=K-n(GYh&~?Fh9O_fYYdtPU&H4ODv%(lM4aNz?kWDg$=;!jAFN_i_5JcM02E}tWj6`B>{1IP+WNdT` zyxx*c&5xGpVzPhV{+pg(Uw{W(RV9c9MHG$H4$sS_|V5 z3YPiqOp5y~41SbLo?70dTVe2y;s~Im&fo^%G&%p(yZbRsA_JN*p#h&n|9-*^v`DD%X^GY$332 zqlzMIhS;!}tE|*+KEC*E(zN&$CtyoXn*5s0zm#yZTynS<{^UoA?6%=IX9SbJ( zygL?j(vS-gOe$0JnFd)2eGJ{%qQZl{@ptXW)difo19t05foxyT^-)Ad>< zE~!?F3Lva_-E6M8+)iIzd%+~^Iym&$qarkQ3(n3&nVwqahsPqp3T&q=4_mnAc*K7) zyM+A^k2vd?Ev)4#xT8Zovce>3cCWvZ9D%@S_TSZEIN(xC=447@7%o|BzwJk4x<(5p zFx@N!y(F9EV@jhp90gK%MUZ%bc*lO~n>(>POasl>N9TP8xnT1v`cmZ<`AxCKbY;7R;lxx(rozL1Q@wKX^&n z!jfqpWfS&oh%HWon4O$&gci#ul_$|;cQSJ1#{#SFRoUcVV0N6{S-|dmacZY=!X-{V z$rbbT^so!D=I2-dRYfrhZ5M~#$U%~X5Dpz8DG@!h4u!NuTAEyJYnM_IA`T>>!y4Fv zJIBnPKP2vUO7$sX19pmuQ_3_OXHcf}ES7cCx>_QTb+Xp%N^4dfxFCZRoM`WRR?6K7L7!T~V(nB=S~Hy5pkl6*8L~F< zAQQPRYr4p#V#P|Lu#$_}OF`PG4`5QsKyuT};GoA64r*&pYy(*$LkpZ??r!WcB*d6a z+i=$RC#{`Z2eJQ~^zOvP@x1!vimx#rBpW4{<<>_pE%lK0-N z$jG9mkYr4z1rtNX;(5C41Out;1t~}ZN$3;iW%14OJxTh{{ln(Z%~gl_UBG4q)^>}D zvyNkWx%I?jH`z22{SWi=c!=lFyGd#e*e9w;j zN?EEr$;JMk{={^>vU@`u%_l*weXILssVU_H2Fyt+X@;5zb*dN6ruG845fQez8>1Sn z;g#lmAph$J4o^(zDRaHUiqEAl-xa+DdWnlA%>*f3NMP2Q_7PBl$=;edU7u|4OBIdc z_?XUj(r_)N?A6X>pp%D4m&Ba{D3b#Bc`!KUIkEd0mPFfe4UM@xKW#Vy{Yo{t!;@(` zW`-pH3rIe+r4fWqWi3+dAk`jdu&Dr%v)EXkq@S4zzx!LhFXm@ktcT#2mP zjN95V5y~P)yfdNH9nBH~X^Z6cchim0)5(B~m7WN)*hnp2U1UKXNp{-f*<}OMdKLlg zJqKXhTM4|*S$Srua2sS;aSKl?$s@;0;g<(}N1`L$+rdr)6;zWRzY2D%**i zuwWpk^Q3;9>mfgEiiSp&RqE?$*^!iThPM3vjvl*&%jv94%B#9jDc2YdgH0kc) z;B6%8l27y#=6CQfF6PxJ&6oJ&QW~=9M1%_6bWxiVeRHLRDQ}wEe7NgVoA>b1sI4L; zQ$tX}nOCFec_MKbpwC2%U^|mTYzqB{cx#v03LGWW)!wr~Sz zJoON$znKONJ?QDvKfI=bo3Ho&r`%$OLYf{;hI*MWO_b0T&Ud`T{b1O%4o`*Y5GO-s zal#`uuz4pUojn$mlv6?cQZGitZ4y)5cGd4zj$E-#D4a4aE3J4!g9{3XNPN-s(eFqv ztE*4{w!MC^v;LpYHg?t{8Wrq_OhAt51suS;kvvld?2lS~q-->7rvSMclPb8lz#*Ht z1^6VpI750(ly}8o?%`fV_=zISB;&ZU&1Uw%q5EuMj1q;bat6OQA1nXY?u9cHJKbk!`(&1MP! zG6NhL{r7P9GSy!48$V!_%<5rqnezv-`@^mE-Ge7vPcz@E@WRQp#BY_@;?cHJa0Tv9 zX_52JS}>GQ79$Oo0c`dKJxPx}*duYH>xiBOr4%jAoQDucW?1SUQmxcL%5k!?WNAHv z52&_9q(Opab3rWuMjkj$(KY zxz&Pau1Tki!mYDE2u;HHqkOzMM7M4wZO&Wa%^E)E0^tn9NQ+_U(|Y%&Jx$hZzf6%c zP?~nl>8_v>9uO)2YHY^5R*U}X2bb3f({?oe^{-U1gNb%(3o)~Gbb;9Z@MsZd^*uGf zMo=q4J%`lUp4qzk06mG0Lw_*Ztc+W0&8$>7(M4cc*av6NWueTvZCwS~?hxwKRf#2y z($R(Nffs;R=aa1bmN}Eekq*CnkN-_R2c+?bR)kt>lfSoZ;M*wpq!s^h0W)-(NC!XE zjau;tjttL-L<#5) zZZa1& z{f(`pVXBub7kFcpCe4HPz3*>u$8>Y^+eD2w^8aqPZIo|kX;mOVDm{LCet!0F{BES= zU(HKexFys2ZZgKP?&qmE35R4-T3D?EesyK{lP>C;%??{DLa-suI3`Dsf1d%4p7e*?|`n?e;x z#adE!N}~NauDA!IzX`%v4Oy=ir1Wpvr$fgeSfx4zgP^4|#_B3+yI@COPUyAFBz=}q zkg#$8zJ#qRY@w{v7!uJ&v*5Imm4Rqd!XC|mRX8BDlzPrsN3Q|$ z)WArte7o1T$0!X3__({DKMTw z2B>_PUYwns_!*oe8c^>FaiIh-BSA|+Ui6N~K->uBXr-j9iH~e<0wBjOjZMObI4E{! zrM_S~xvMW$dZ=W!{^04>&fgAp*SFVp*6=6T?r409;KE=+rl33_3faSoT|_gOh)D<1 zQl29>p|q5gm~DJ4_<=0UNj(d+2hpc|hNaJ?)tVmznrt4^!XZpNVup+q{#=yKu<$j^ zlrQ!r%Blb~DIthcpy}Kt%s~_u#6d|QNe@cm4>>CfAr#Jv>ska+V((P&^bcWk028E& z{s?EU&=s?*QWu7QIX>=??1clO%&Mlk0`a7(EWk5$N8J@Ouynj@j4^$4_=RE%G=;?- z!o|$8!=`6ec|aX9Fh)}r4O`Cg*6UJ9uw;rFpa;Qivu3!8us?l&%D}K7S96CjZxh5k zvB77o*CWWYvt?<`M%aRol7zI`>$a;PB76pK}9&c~$uJ0V|Y#{>te0^tkW9tcS+w+k8thZ`aozQp$n2zQBJ-z)MV3yKX06%Gc6eqNp!5W34%3N&$w(ZMr7x z7RDFnXV_6gxgO^N;fU0q6u+1yIeIXFh=;n?%Z@DwK;W$sxm z%pe!()Clq+j^GoUy6E=Mj3~r9>1!F1Qw+diQ$drD0*4+2XKwy;2v&CCZfL4|fPzLp zo5=Pmhg!`*TfaiNMd0Y5@l}Zs8&bI{jKA0K>HwlxryAXSebOHAP>g2w?jEv+)upH^ zJrla|b19pAikP$*Mlg*m2y=A+5#)x?-doJE(%ypP+0Jwr9vwn~9rrkIMEAs9Fjhn4 zg4V>FZhd|5cx~rDaHX;Qz4c`CZ(u@` zzY^!c*~`AX`+8^X!8+s=@u(gAVm-(kM>Az4lJsCLI9E}J_WD1AgPDRY+j~@7ZH<17 zjX2Uw#L;jXC*LR7pMTOhXGW0@Kca_@(sb zn_m(!>bOl0u`!$)!ECsA=`7+z!~veNT%`n_T7(2#bBL-}d_2PBdn5 zHW|m!2O~V=T|-!*$@nzgD%tyT<;R=f=qchndXAxSs$*O8C#10M$$*u$MUqrOqK;Nw z@i}k436rR!Cu?$956@L3VH&G+tl49**|6 zPxzm%smwYzQ}rkDzgs`7g`Ev$wzhW%;M` z=`YIy+tU4C-T>3Tpb0kDemNPwTw2~cT>k0v+m7nFM?{(Kr0$9*SPP3r+lGN`Wm3BhY{PmrQp$8=`8fO z0EV1QhR8s|=bDUPd=8(AQOa~KJ+*Rx77S*9nH~`pndqdy>ITgyTQqd~eye(ahV>p#SYs-DW8?W;I8Kh3Us#xFf} zdN6X39@Cm4U`Tyv9!M?&?XO@Qt7esBPWg*o6vf@QlBjpN0!<}11FXR6;P7R&7gutq z6C-2ckO<8D*N8mGzCGROR8E~+_WhNjzO#br<^O2Ba5gqgSO$I8ZI?_*1792l8L2V_ zt}_qhA23G3QYVA+9?F_2x`62S2-w1TshJcWKcjchY~6D3u{um9{ml)|(b5=;yMH(*CJ&WZHb? z>d-H!I97II8^a0igv;_Pr^grB9Oq23KemMykn%V`fnbohkWfz**0QNxFVEJCEBM84X_6hn=evhpb#|Wm^VhTNq3GLFQyBl6=iZgL3 zg_w6SX;jmZOHR*tNV9kSi?_|0S4Pe1>h|WdZ#JGBtZi?2a0i{<1R8XtwX@-6_*hm8 zfri1Hs7YNnaYQ3M;Yr)|Ft{xcB{kf42PMv!#_!7yb?8*kAa9L|K?tIghwG5whfm%!Dkf_O6`FsYD1( z4m#l7(gQ8p*BI!A97g>?%B{hqgCX!a*HdKhWpjSG{`IqOEW(2C#98R#b%G@gE(c$l z%{7NRy>~c{kNh5ZQ1H}VBc^ita(puFo|`nq1finarU-Aoq|fi3?rJ3WQUZ{cqL?sM zI8U<>f!CL#0gbMLgzu2e*?animmc9yqCNH1m?|(PvB(*N`6hk1(&j{^~0B;wz&BkZizxo`o2?*O%jkz08Rj z;em~9jA|jo?uBQsiYLvyIbLTLFby8kYYUC%_p+xQEKLb}$M4+*Bzcgx@=*Md9DJ{%kBw-tF4>B=>M4HPYt{Ic=Y8e zvkq7(LNa16?*1@5J4?4%*%_lumIomCu$I?ha2$y&Mz&3E$YJy$0OVDsHmV2{K|imzDl<_<-GnpZ0yaWQ04X{k6yc zx!ntm959kNQAk2-Ohi6nlE?UHeBxiw#hp17|6sm(cxy}|2F?4+ByL_s3QI!)XwHkV z^HvyjRxcsLSIHxf@qb1ORUa+eVw}MnUY0ZntmS5pBIf&Zl)^h;`NSjO2 zk|jPB&dx4xKdQWPgq1~({1 znL-;ztOaR9OGHdB-kege0%XPl-TnS4fc}Z4wV5mNk`{!j#Oe5Aax}On zzTxQbWm-7d}sNlKX@W&V1Pr4^p z3p6x88_hozv?Xm6Uj>jAVl=t}z$oH6c*=vsT~e(E`4v9kzU6%Q_tpg4|85mP{_+Z- z0}X#YJ8cJiiyzNU=Yw3^M#4dq0GxKdDV-R)HjWn&W;kDxMag}qMcFup zAk=li+xE#-9D6Dr_4QcxafoMwO3R*yoWQlk!N!BFC(jFsQ1w1K7x#*Cw#BbPOmE#I zJsW6}Ljkcq3qF(_r;P{CdKV)Ad_2H$%45mzVyZPn0(QncPYp{%&z?CPmxfXiLfkw+ zw_yhD+H-hxL1LXH8Jm6KgIHz8{e@Nm$hm=zbdRqxA!=qjo$3De4OVNFh)!Iq3ZAY= z1)+KR9qq9$pgygEs?@Zk%rZ4b0+A`50ZVB-Vj}lD)9&_kafZ6r)b6I*;)DyE#_$LR zbD+8m7@|t$b|MnWqS)pC&v?myM?wbX0TU(3$SotlXgFROdMB0 z-Zx0A50pP#d%A`{C86TQl5FNI_H6)y*)es3LE;SHXn(y2n-6z-i1+*#Qqg=NgPy9% znY#=+UF4RXJa}`6%^30L8)8RLZmfu*Suh(+iPKZ~p*fyY)P~E)r!;K{N4g|ty|~v; z0{~J8foICqK359I&+qj9eThPnwCG%KE30#;B#l@-KZr4hKcR_WhyBpKJP>jVr4IAgr|5zxzE~TF)_Px3_=1f{2Uem3;*h<>-wlnx*@amO9K|S?7r< zcO~6bSkt*0>ul2`Q8l^P^VH>BWqK`xLaWGn`S&XjoJ>KgTS<(vNlk8X3cQ9B(3SP( zH469od=K1;F<&mi$?@U!BEPLN@j14R;AI%jN}IY}l{*gUHZ^=V>+T5?KRoEl0b0Vx zI696Ok7vqR$5t8QD8A$}vm?>=HVy3OsKvlY6!B%xv(1oJ%MfqY0@fh_mAlSi5Z+BC zD8(;tR z@#ew9t*1}dgAar56>g2@Bb;ei$G{Q7Bm%S|j5Qib zVnpn=7CDkWns04Vb0Q%9ux~roCpB}RY=uhpKOLMN9ij||IQ~~Q#=FVXyY;~@#fS;F z4!goBft2Z86BqVXiGtY3$8WO?e6IkquSLyBqPgt~{IUt(VSw#{rThESPs9Gn{_bZ> zze*C=EVA6%UVnm*o!k$euK)F^>_ilkE_NFl_Antqz<;rI%C;lXEd#Hx9Ws-_x@J;s zi^y{2-H=K$p25tWDW6?SO|`Ji zU7pW)fJ64EA&)dh=f7W`s}GBmoO?YP5Po`Kq?OO^FZJ%83M)|zW!}C7S=LvlFz~S; z6zKgsJQAFC5?uCp9NV z(hvK#B%bs(!2&zcXD&!-lZ%pf@si~Pt}zpSH}^c)*k%#FYs^7laoVqEP*VTdYo+u| z`~KwW5~6jPx5v}-*U%LIxw*dx^q&2Ff2m7OGy)~3b9oigYbUkK+e>I0ZKmVn;l*hZ z0_i8`RLlP&#c+4);l{Ja2e6rU)=#0tXX|MvtuC73?D zIc~-JVT|6U$&AZbbiS1{AhTo(l-weMu(f@?M4DE@0*CX~+yIO4wVAu`55>=)A2IR)Fw>5j&EdFo4=C%B&S6ape}0Sky44RTMGtyg?iY)o-vBr zb#x>&D?_z9^S5dMZ%d`dndh=T{^5`SDkkZH+`LzD1z6!+u{V6n!+tz?R2(7WJMxG1 zh`-L_ksFTACheKy-+uf2ac{?t7B5Ody_BIg9T79?*@>`@<|)_bIz!X?z(4Z=rPKfd znQzC#V=qwXkCA4>Jn*9dL`^k4p@uh;@jIFOe%Dc+#s2z_X9#wJOR5ga^Sxe|+u@P< zsPrvPhx%c`X{yE~WXt&V;*v)CgVdYdI`B_<8aU(lVR`C2)yDyN7VxFWx|Pl_PBI;Z zJATq<0anObxpzdMwON-OC@|%o%a^jHad>ooad`3+5oZkBpE8Dz#D0CskaNn!EF0=C zIn)^khIET}nQ&qPoFj2EgBq}PHJH3Y!jr(2zG#qkHJx~15{J5hlNH>9?vqj)(73vw zS>Wj)2RY!4BOch~(DGD>KuevNI7KBB48s#nrr;8=dFjA0TVUGioT`d4Z$3r2Bb@JI zN^BfKk*MPU{{PXd8yBNGy&7L(EArBHip+pmo*X?7a>r3*T%9X4q?6;VD7S(Pj&hV% z&}{`TUm#^BAi65a0x9M)H?L{8SoiLOH~^duGJ0qe?qW;yP;?<-%8y|e0)KYj4fg17 zt2pl2cQ_{9dpkU=&tucVb@-$YSQC>&>>y(o4R!odrG-=pkCxT!Woc5X(y(TLlG4f9 zdKSH-=11p-H!8*Eo$w4d=<2!J1vr)Cd)vb2zLjQ-CC|L3N2HiqI#+a%I5TUvJbF#= zk%=pEU+I1I?r<`~e%3u-pJUStZL9OuU2lD@b@Ge`AHs%gS9qlFh$Wb-ZHP+NW>JSh zPQ&mdW@VQoTY!6+lf!@wGK18PbVJa%Y92PLZJ+rqy%s2NWrykscZO7(MGeNo0Ii-a zW9rAB?XUR%AZ9B+Q0GQ!x)y+fo5guki@bQnNz1#vFIaLTDcibH|8wf%D~Y6{3l*Ba z%w)@q?w1$C6EBG)3@dVp;sHqGYPHywb(J3Q7Bm?Z!e={D3q{Z^bwHf~qyEA-x(p5OYOFw1Q7Jzl zlu_aEEV_$!seBvC*mnglDx_TYL{1>x>I%dPPHAa)de?r)Ozdxf7|F+5 zKp5Dc((RuAl1jjd!3bWOhtJBsyB?p(gj@JbgrN~s`$QA9aiaD^CTf3I6ZIA}ij+=m zqV{W!iCW96Omur^>-ok*y;cz`+ci;N_8pgTj(&lWlG_T7TyLIAZ=e2tuKo|ZiKDomNJkz2{+346gsO({_E z1v-SFea#9+kGaXydTD5T$vf~9wMLpJg&@SBR&c9lOZ;0oqwP1IKgL2(CJeI#?lxj& z5_R}win5ge+r|3dF9wtM$nL}z@4_jQuK&cW%p`G{?~g|!WO5NIl|_VbLWOpz5mLS! zj*d<)py^v*nw*_1hgPmo=SO6p$IMpm-Zi7{e1gM&hbP2coIbO*_xHH~cl%2P!t1r4 z4FLyJ8ew0=xvnPvO5HM2pJTHddRu277HB zJy$`-V%c?zdWV;jS?F)&wn``Qj!0f$0SU|H5ikwbZ>9GDr)dAWNz?q(;HLKR0KOY4zGL46 z-yelCd0(#l`wD9ktsgw&MGp_xw{fS+8uGV+TU}GftPNA3Rk%@?2(ncTkvuZWs!vXl z;~f@W5(?C(0$a=Z3Rz(3XvS*TCM4%GUA$i{2-So*o%fBpOz!9B^4*YzM=QO``hh}; z{_KF>#4<#<=U9k4JsL~#qrafa*+bodHP9EAWaos%z5s8i2n(APgkj6X?T@xKg(+Ch z@<^;J(GvP6uvV#7#VQwGmMMKN^?UIU#EN%%@1wT0d&B%rt@1PR?iIPID|x0}%_DYk zR*~xCVss?u60Ju5v^M~@AdDbTBZ-JiZIFN_c3e*5cWQK8VrgV!yMrH$I>pILPz~kA zdXecf*uZ%(&Q9c|qTLN1h@TS3E8IjkfO-yhI%Xb%Lm4kB6jz!LtVJRAjZ}@L8rR^( zxCu31zCE!?eC0%%(uX8O7gs@vJ*S6aL{{J%HT##iv}VMD;A_+OqazxISVbib8lY*F zTznK@K%JD6z=H~lcw*3?YxFCjo7}4%33P*prWiLLc#GA3ZY2f%`7A$@ErG7?-X5{k zU~5m$kEU~WV%q^-9ocrmyq&@M?d++z&tc}oc*o8MPYlJ*US1siFt~xP0MGKxcK7Du z<%iD@V4WP^KtI#B%pLwfI(jvR-9^oYcOF>2O)I7Dw)(%}PVbvQX6zCuK! ztEV-4KQDj4%j@cD8}G_)-1LP!alO$I&AP;(sI&%g^9wAh;h zJXjADewP^vKC3*}G+vxWErAUdmE6>zX@jHojiP;;2QiIB&mdjWFoCtH&`%>y_}Ihg zp(^$;c25iFkW#HZUbENxhr3b%XHH{**F)B|Ff z23;;5O-*gy(}=X2ucvo<@VTvi?_vaEEviBfEDS zv45HWO8++NzcHMU?$GA&NY7bnnFAPd1!gS6kTyCt1)K0CzrmtXnyEDx8m|nb+>iU_ z@GYHo3C9Xzamvq(1l@@=-{>}!;Ov%jkrv#vG@y^4gz|6m0)Q!nU;)GS@f1xUw;Fvm~7oG(W3jDlZ@Oj&w#3yPbp-G4fXuKx{nMsC{XO; zOa`)2xFnWhar&n(LO+-zS8s|@azHKkYk;@d`ugabTuK&~$pE>Ltn{iY86Yhxe>o$+ z-rF+E37nlwE|j7`98x5=s1|(e=_aZ$%#0CoX?^)#T;hCtSNeQ7j_tvCvNf2yFFoeyAaM|u3Pxpb%yG7 zSM`+L*&M=LXG}B;s)iPb{)R!?;&SM8A^H-lNf@zZ&JHX)5hZYNI+(nMk?ozE5v2Be zfd)kJo8UnV1|!fr$rG<D3`6-gJP>6_sz7@1ANDiEgV=%Ve0rS0YebxP8&%D3Qse@m=mu3`;z?Wvi=CJs>r zql-{nYk^@JE>h<UmkiWIb&n~$>TcQl$e>bS{aTHs4Zv7w;1G9$YphO+eDaE$?=#ZuXsV zIoS~RwT$2K64x~A>^hgp*Nh3P_5%gZ#x_(*3tH%gTk3#kLx~F6Lqe(~7PP;_jejUX z1C5H;;t;|?#6mn!^#fRR*3jakVij~a6<|PEw~GmPi4E7YsaP(oh#xWf*!ddME@|?kR|mT4n*4 zxofdUl4erIRlIadtBM0Dw}gE1nDvkiQJzC_u5~K9gGB7^4l~#+EwRN79>F_7)r>r~ z`DAcq(zOfWmig2G09~_SfI2T{T8+!e-lP{TGFzECV`O)LiwuV6@6!d)*^^C=0O(D^ zQFsauMQ!2A;P)(wd4fFW{cYrRZJnX^_wc92J;EK$E33Yv)z{4VpQp#qA zChaA8cx%URhg0mr?>mP5LDj22hA)E%`r*kX&1ZU6c=-fI*~V5i#{ zv4f6IxT(R@x>smwy?1nIZxe4;&1QvZuF$lrd!MR-1^JGvnOj09;|OaUHf`-{a_dW+1T88y0N|s*@HMx z`!~Dau07oP3(61ftZ#4aZam%E(GNT@lpIMGg_YH!0F{5mtDV6sm^w(6k+=DkTM2Y6 zEV)@Av3sm>-n1o#QV&puQtG#ajLe2BeGRlU{5et;$2>`p5nI9sX~_}-mhXw4)2EU; z)s(zC53$b-4n>AyWQI5FR_ZBs8BQ;dZD!FSjLqA=5bHx=)#6R%dGwdi+D?RUV6OJEDdVj{CImXF&2PHksrfCal22}$VmjfNV5zsaA_@D2|^$W zhcSW-T3uamba8EXX2E~s9v~#zB02Ix4o1^f(qFx={?zO2VsD{dB4oBEgS!>Hy*#9N zTQa0-UYk_{NrHi#fBB?BR9Y!RlM{FL+#}J;q}BVud_^K?aMkwEJTpHl?A9BN*@GC4 zW)_>}BrB%?cLo!#!QiDr2#ql(z6 z7}TiPrAPP8)e+s^TZ{4bv_;-@ciM>{7b*1!b4CR+iiG5K9aYkilEc2ARk%n-+Tv~6 zO+9f1HBOBkQ)Wp@a_4c6d9pqB7u`Qt%{Y;PN=4}!b{w@Bz8XTV9**9h;`TyM$`x=X zPziM{1BRalnLLHx*p50w{HvDaFDyg3Ux&7uI;kf(03&YpAHhZuew8P?pHvFwg z-r2fdZ`s7Akd!xOn(*3*MwT&BdsnR{K*z?SG*RXXQBc&@VD+$ykxt)&ok`PagKU^*QNrPMon0vN)K#{#R zEi4|1Ag_FGCUZJ%`c0;WVA1-i3lks? zy|xj^J9JRxuECC3G)vHsEASMDmCVsI$1hl=QJ9<_o;8pZdrYK23?X;vET=p$j@9%^ zEYs6xrhE=1+MK|nG2?9}3=tAX=fI|D;|Ay&7VW@|0DLsmwQJxD}?sz>OU$jY;|bMUdL`GuewA%l;HKXa>+X*JKLjgu(T|X8wwcIkD*uWa~D6=0tQAnFb!V5wwS6(QogY# zzL9uEBpAKrKt#fq`f(nIz+>5zX>PO>K&P+U95&!Lm>@GK-K`Mabcyn<8D)B3h&W+_ zOFoco!%y7SAkV*=a$<+%)B(65C#>D`Rg!DKrL(LW3cu*jjnAe3+~) z+x|V|egu_CLoSrxQI)`;D<9_0Qd+BHDzaqU$cS^}ur@KlhP2yyrG~UPCH6LU=DQ=? ziXZ7uCSeauPPj;;bNz2oiO(_@O3GBzZ~%h<~HPqFdiR8mDNrN9FBPQUEea zHnA(PHklk^J2Ba#%+bSE0iO{42;*b{lSG>mSk|n0B!&~hAY6jn;T+N}_GIQp=nj(d z%0RnlHDWRwfY)OAJWJIUhl`vn2AFx+b4Hp&S7Msa&`52#T!b2P1%(40*a}8u5{Fii zv3`#Ko+fpLO%qfQc!NY1B&NbkdyBKKdh3^542@E1)DUicyn8b|dV`&77R;5;5bBib z1FwcZ4vtrP&v4W@gHx%S9>>RNbFeY9XePMfX9C@*cGWwmCc@E#P|hQd2Alu~;n%}+ zob?a`)K`e*HzWWr0xFV`wsXZ=Qy(o2v**Ypx~uCxsK%Imy9?U<9Ct)Vzi}skcLiEwT66%q^MY1(M+;izLT-~DOGhMp^df{a!ryEgj06Y3_ow{zC+VL09LIy;)oH}L>Gg@4A!%f2p)_IL z^z1;Tb0qE6^mQ6Xc+@U~DK(dMpPkLF6@*+vjmM;nU>U<@bInvN7>`S7$mU}7ZgP00 z4yH85vf2Kb@Gwb7?2l$c6miDdm%B8fBZe1F&IOg6f$gOs;xLFKPott#Jl>h;G%2Ek7hx6WrF9=<(=>@dc_~Oz;){)r%|#XrW(! zf*X~#={+1EU+cUm98#D7ga?!_-vjt>hA0d0Y;y87^L6mhV*;oU1zuJ2MvAK7Yf6@k zSccGGwI9O$XW=hxiq9l0f|HNO!%2K9+->Gko)jjXzXWT$M}n}K*FdD5+i{f%E^D9_g$BLJ4Xi@ z+o^O?~{&X@yY17lu>!C=VaNYB%in(6$+NDn@ z0{v4SSiq%B3f=pu-~Gq!?w?+2PAF^VM$ln*?MH%6_M{PByt7vdxlswRvGJ&9#SlWb z2_nWltLZtwb56%=ATmDH%X4}(1&2tejDOjzvAerCWPBK(JhVe*O?e)|nGAw-hV|i0 z5TkC(HT2dUz9bbvS(Tp=OM8KP%XQ6OV4Lt&x9Q;7<;D5sMX5JcFl5;}V8%Mr5svBo zz15QWKo{xD#v_m;TJi)%#>9)MR2ggy$8UQ3<9(DP?D_77c>*Gma-g#@r9z`7YM@X7 zZh*wfF}XX{lX#c~%*O=Acy>7*g4`$;Z~ctC1o#pw2bbhT4k=DLvmxYw0{JN_)-}Ht zc6*So44&R~%yT^JFh>)onq2n9^DQisc`$JRWph0UCOmi`0PsS8F;C zGj2OeSOVTsEkDCm5dlAaYf_MbV8oW*AnYB<&rJ}$nRd*e)sHWZ0}W9m?si+B&+vcP zlI>h(Szkv%@;*yQ1jr)v;hIKa4UCddoRMQ?e@D7J7rIz{Gm8sbKkpQB4(2Xq`A?g^ z&kRqNjePk8Mb)|(k7Dj#xF&nBy?2im4I?q!+L9g-{whw{A&0JykyRH?DO63=xq4p?Cerx66kG){*{Heyoc!0<4LP~*y@g3 zcjR(SRDp6TzhKy_OG9Uke{agq_ z+Wr#(Njp#Gg;@z=6}sWtPy2sd1JbcjkRqOaDd*Uiw6~&v38eMU6xiMjV^~8PGm=mp z1-dsY0AHHG?OOWT;w9q6;-H4q!EneZN)H~iNFHLfF5GWcX#b(Z3+S24JA3d8t~_2v z$s|13`{|pP->g2sU%1#6=E(l}`F;;1^cQ6j`@PlGKgi|tG*=M3hF3J!%DQTX`fN=< zUi)R=l;h7x9p?4WY%KAw_&F9zzMv(}*Vn|tLkq>Dd7ZSvGM{`5kJB#KCK0@&RSR+^)geU*n$cxV65i4zYCMX z3>j+|AttA2%W~}dUX#6e*J!4E=;b?$k3w}D6ym|iNQeg|#(!#t{BCp+K3vckiP`|n zj!kSKYQq<=fjl3*0gpm4mHzC>Y=#tBKV0d{QnW*N_La!Pu&}~|IvhVAj zpG?k%bFU?)N6sS8JWUpVnS}v1|>a&fX-dPhYto?PY)&K?d}( zHfmK>awaFa<8Kf~xD;++B=_6%H#KHS&P46Jx4adqRO;yQ3C~gZ6(`So(w0Dj(VFdHsM--KJ$z84pr^j5>WvjgM|JUcAGh)VU+ z4iqP5EEsfUCdwJM+c9rRE@YX3^Y+h2y=)uT?)Gl41vtWAxb`{!?*T0$4o$(H@GCUF zy0t{Mejve$2MDQ{UUioCaP!FUbTAX;t=+{L=lv76y3z+?td<}EEw%rpy(HVEw>gj` z5IOaT9S?z`j3arrad~mF3GY00%l;{(Q(u}nsk`@4T@BYAk@C|zy2y?M7HFfvYd0Hi z(w>POks*{s?cusH+;`S~i3?2mR-(ZiASsddt6c~dE@ZEbw+D#R0}YptY#^t|dX|Lx zYx6LUx8Rs3(1Mw?WFM2f&;O8bYd(s!yKc0Ti!)S$sf-i}d-9PzOeSRXn7bAiK07*N z6Q8udNE_j8A)N>(CcUQO0S(K+ z1_vFj0N&mf4!|XCdM$mBSH}d7wu5kaD_U!-18DMB-?iilZf9u!{rJ0Rdvhq|iAr(f~X@gdr$V)WZ(@$`MbPJoib!w4y z9p{;M?~RioatL!C`bi_KF<6zS2*V)Tm`#rorm-;ZCf}|iZS7uPJ3goJpnvgo#=aJw z33wa-qaiK`(Uq5>?hjZMy zIMH>9DL7#yY%_>JYW{q`^=OVw@94Ub`k#Poxq-6hEniPM{d^SgVi&lpqW8 zwpHGf%zz!$HuODmp$ZMFXM0h3k!N2e$AkjRmDQ8!0j&u%qH2Kv3&?@1!nz+$23JY6 zK@~G@W|RIfv`EHGR{Q@AI)I##^_F+pEpIv34W^b*M zqFilNp##zsaIE)_(fJK<%h4i%XQYH5 z_biO7-oYorS3}_wTud;#^=Xh-SHNBUSm+yjoQJ>;wVNVwI>HUmxcYgH$UZ#+#Q&~A z=JpXe!2JoR?tPAf z7XKKEk+|j?j%6gUX@|@TVpNh78qFki2-mS8%K*tU9K1ve**c}W%t1OkzYq)Zw`NwP zGU9`CLmhQGdN~mD?b?IOvolojzSX)5Hi|d~K#@E=R144;z!mXzn@CZ`ZNDgLXLbS_ zn0Y5`?QM9mzDa`F0#p&o)?QCvP+99iAx-1qG>Wi?+q7_3q8vg!Y0+~Rj#)h%f>%P1 zjDZ=->*cuvOI?1xgXM}6NSf#0&(wYy+wDh6_XWqc)}xs0bW$OHsr^+MSSwb4@x3Kb zDyK&%CXPHFqpKDI^Twbd3?oFqDQ@x-RBEC8->|*J4`ve$HMd)L5mAm-8etyJeoGJ= zp-u?)#w80(v2{O42)?C)RNzU?&eTCWA${;1<&`6=kOKf-!oLp4P;|m65V9Jpq3bpPF+-sjfCQ#vxYLaSDE!_5l7rYll*-XA& zd8dI8sG;vSifa3qp(H>F@wM101y92<`%Lm0XOM&rv?xQT&6xGs46*^wkz9S6u&(yb z>(Ti+`Ax(HVwIg0P&_7wC>9}HBtuZnU|mLc3@0JW z&8l9lum8C9{Nb~mt%NVf>DzjYQ3ESJQVMWgnrdTM*LEIMR~Otxv%SXi)xCW&MT~eB zq9zex;DY29_=i7>7VjP526aKQYAI8Nz%nICpD4K%VrgWb>bQTeq*bzk2)Mb~rGlo) z(Sdg$0tH8Wx3|B)_EI9O)*h_<3Ph9ZGs71^E>ROamNLFz&rL+P9TTH?pWys7s37L* z!O(&zXpV{`fmDklSNVriH*&NQbA;^yzNU?uX{A6JJjbY~?8W6-G$4I+gN`gq;s6%P znFZ5XH1icWX{hsQ(q45Bgp8*EP)ScXs3-b{NmDqMyw%F~TbgXIK2KJ^1FqURTVFpq z9rnjt<2RyM-5@s-5xHs7qpgBb2m#SN;jlBW?X{@)5t1Y4jD6z_Nupa30{QMI#9$*3 zOeN91pVnV~fmm$jEPug+81Y(|O>)-ehBr!iJTojakg+&gF{lwKGzfM;lbPSyY!@Ok zv=_CbM5^f@Qx{rZUxwC+JT?6D0t`#%{0h44@aI17NMWe>`rW&0a$>>a{(g!3KFas< z5-^~iHJFI_^|p^BQlw3S46-VxTFZ>JDiUZy3`IpLcLrc+CX$qLDVNw3 zX&p%gkBZ9tk#kXlo-32c$ZQ0f$W8VWO297c+@uom$-@x3gVS!0gNp4c!ZrMZZLVW9aY&gJzbB|Cceqw$>mYIWr-{*c&y|-Q|0rpv?j%b zQ^>v~ZqjyV8x5LMR5QSYTdE5&k9N{Mnw(Ciq{2lam!Ejxj5be021Bh$osf8_6cpG6}Gbf$JQ5}{hcpXg^lQU7$kfXY~Z-!%BAqrtJnTBzS8b^2g^VvFs0_P|2$Em=jv7Kv>u@=BF^u~mEhr{Qo zw;s1Z8xh5vw^~~ucygKc4xu^A$&JTb+5PV~AJbJ=H;v3WZq9;RqN9af zvB5!EOKao1pIU#E47Ih#TN{sB35ylxO)6bs$VjaPPhNe{`lGOA{NtPZ#VX#ldVlnN z)N#XB3Tbzy3|cP{s%<6g0h+$ScQpLPcUwiCXb;w|Rb27EWrOp5&t65Q{u|kL3(z5B z8HW@)GSZ0g2TWAI;2(f&f;{=*>Vt27SzE({{ht1Na(PH?bnTjaWg+czMOz3CYTd;j zK-9DygouAqwlNmj!imgqo)c}#G!_XSV({>sXl=V3O~fvvV~r%r->GuVtY@H~xee#$ zMQi1S2pdrutc$`(Wd3HXK;s4Alik$%9H2#cQD2SLa1AL=T3Foa40QSod8(iT5(tEU zMd+Bz#t0x_IF^IIVDh+{9bg9!7$qQ?;gb7uofEqPH+%4#nAY7co!P%XRyww*+wWsJ zZ#48{^B-Rx7Tt-dp8nW*$=mp3K{NaHY1<)%Sy@+VUynpBeVXe(H5PdUgXK>VBJ-r0 z5*`M|)hm_-UUOgTXtps$frU3H&{Xpl(HjG}dYEH}A>}`a5uTKd!((7l{a2fmzUb6P zAN=e>1xm8YIuQ6jaeF@?NtIF%mUzwI-@rc(dn6u1)F>0O!Ob)!n;F?8Ny=m8kR@7X zVi22av2-a`ESHKx(K~uolZ~AXCkHqv^+jg&myEO3nNg~@GF(B)y%%4{NF3x{Tud*Z z`oA4cJDyJJ{8y4~{0DEqb508)Lnzx|NpEyezkU0bz%te(Dp|Fm__4hSnX)zcIM%Up zyfn5*fR;Xpu=*%=u@o|yhXN$6t8nQevjuSZ8qiXW1vN=s(~bM5q!4cEiL^Jhx8c1= zocdm36M$WMhJ2%xJs4vYNyq>tF=nORP8yUa4(#N4z^xbcwvLeS9jx&ILsGamY=jIQ zMX?a*#nH1WWtpiZK}o<_m!1d$w$|`!e&|ImAC^RuR<+u&m=YW}nUC}#%eP5HVA9&4 zyBol%-YSopYXE2(Mi;4U3fGPEQQiD0i1;xTge|8aNC$kz$Ds0U=4>UOU?uK;TnUZJ zE@TyWyY}YGECM56x_;h(8H-rYZn7oTZ)xdCx(Zy7zyNi=6j(UE4H4Xkh*Xc0OhHmK zRD)f*`%Pv(eEbP*d*vc7HVi>zw}fWKc+E!4TSH#UA)=_*Jla{n&<(@WHGaYr7>MAK zkba}2{y;RYqbGHvB&${%AyLP3di6xtXQ>AL+E8g;GQ23wush-aT1#Q0|C7p()0n~< znzEokmI$rhV^2k{P32EEFvxyjMZ4nft0wZ#Y5p)6kmQr36~wm2sbOD(*yw?qA@TiU zIvgSoEsBYOJA}E)@eIGByn{!wUct(10R>aMmK70nQ@jo8fv|zvMfhipi?Bvvj*G9M zVeb7yl*B|FF0R*7mw}zNpq3PqPTFI#uI|>u#k3di*?z_Y2=w?iDmX9}fHNeU96sk4 z_Jedk8n5ap0-HOOQ$AtGzWyU=NRh=3--S(7BVHmFGA2%t1K~Ar_OgHHsqw+F5 z9OB>;=&KQS!npA6r_TP{ zFIM+we_Y4^@ASS{RTl-n08)w499g;R(CV_>Q~g*cYx?yaDC#MA2$jl^`+FT2(f41% zsDVNM5Dn_yzmI%Zh7RXQYsKL1?+&UskIsPgIZXEU+siK>pvznM|N289B;DRc^)_^e zh{Y9HqtAq>Db(enrbLnpOGzBl3P*m3hs*ozR(lmI1~z9#Qfo9?~nBU%)YpG zk%j?_&m}A>pU_H*J594ow*3zP*PLy)?gB`#=CERU2d7JCsj|ae^hjhXmY!kN5)NtQ zLwcCx0>h(^4*`8j1K82+G>F@B43j(NdPtAGwB;rO4d1|1E<5uc!Az&O|#ar2nHQ2 zGz3klC!^s4Ls-lA>>eTfxS)!I1prfsUg5$fwvWV*KAJ$Z%8gNs`F(!u7{O5&8g zN`?3TQRGbt72?upJ$Zo8hOgT_5FHV!5SkgyA|Mp?lQ1|*l}MdqMf`(^PrwAzf-j~{ zs*_bFFO+J7Xfw1bewavov{u6wjcSx^12kn7sMr!z@Ic6D;4M`^R@bmc`=WhUk!E!{ zQA92097I%mkxaHmM5ROp*&p1YDxy|;mPI7)oN9b?OgpEPKEg$|8V}~1EZ5&=7BA6M z-RK1^PNvgvI+7|*o-&)@2`-nY#vhpi=cv2r(MU$hQIYDXZ!rMp{eIYyV7((ynECim z$^`F!I$jlXs@QL|D2unMDYT@$GyhNjV%{HqpoWUpS)FU%S94Du{NoT=6w=xUH=QT7 z3>~5yS7d7bf%TWgNPM&pfj_%`fSZsHSWf3_)X{;-eF+H)Fi&8o3-uJ5a1G=^qS%X9 z4)M>*^BYCI(3-*?euOp3%?_T3={G$1#&B0q;K=G4)#Kq0NLbuvC5SM2p0bwL>T%q~ zLw7K8E`nP4R}Np!!uY!3Ohy~thv7l&XC5xN17Bd}KRyvofCi%sGs5j8%H+ebL#Mkq zekARQA^KAmPC*)ro?RY-1bPVg9s` zYSFs45>13(x3cJi;cV|NR6Z2@6xCCHh6n`wVypvqKHevt#4)Hw3_JH}ANNGH-~9w7 zYQLxVV$#ePMwN@ez=`FyQxLn3xjVaq2~)*^MD_EQk#C$U{hb63{SK0(Au!^=4=m{ll%#xntC$;7o|o$OxWk>@3_iF7SuVgDqlR z)6xyE+A&ivDVq}7i{mneBrH~Q#=8cI{AOOK>+jnsfnEoX$ZTEUlQX23-jsEKKo$i= z*rMm3q&yUs{P^-P&p*Ti--kug56wxG8M(=egMlY4^F#_ErWX`}TKB*1t(yz4whod? z=vt^q89|)%^>vyOlJr4knuJs7QtK-bVa0|=6i$7&7Ou#&v@$zF83ql+V=SG>h5GD= zAxiW3Ghv0~;lruOI11gz`ape9(nLQ+M?RJHbzE{b(1t&<@*v6_>Q^{q|JjvtK;0dg zgcX7)W|ZA+X49o4rknD<<89z7*K^$KIDg=mjw4*w3x!u(>W89#X3upj)qv+?Vp8KP-VGAS6d?XL=%Wlz#%W7yFQHug z5V3;)_Y$}7%KfWC5|B=;_tZqcZtHA#(I2p|U*{{j1n&m|1$xqJf3+qLSM%~T4M)Ha zo&HZ;LbW~;lrO#P__kY>9t~g z3y4Y)$Drp)QI{OalAzXWE0Z-9C963G$Q>VzQIm|$09KO;lTDMhGuDu8LSDY_+QS%n zPeHK-RsDN>F}2!*$R!8=ztWiwd3bPZB)b@;eKgiyCfWsk|u%SjxM7(udIdhj_ST;nEQo zmMlkly{KKMMxR=-tXAI5g3Sw&mz$yNUW8v*CR7xDkw*pIG>ien!NLiY3VZk5b$x>U zPOUa!bSt!3+G(zj8W5o&71hLAeD6m*pLWe_vD8 z!luyGB};@;gn;)1EuUgOm3$fblCA$?P*8oCr!_FB{lkRUgUh%OH2NZWWmQzk^sacy4 zCDs5vw5hqcDoY`m2l&2anF!?lB-d$J^*9>pgx2h}27%duqq1ym>f`In3Y`=sP4Ks9 zvg>lMtov7v?EsiM&wb!IY&)`Mt+qn882E8HEfn+P0r9ebq$WsFn^!jU{g`v0(pYXAL`7qYE)gaUQ!aSM!L7vZ zF5zFQgmvTwI11=0(Wy2TIg4Qh4VH+xcuj3DXhOGW;<68t&?jq111mpaU+lY#9{CE< zBM(;Z|GD+Y>G1UHWfs|9mH;4rUlCKVcz0usNw>mt_mSca|UT zGn;pVYYfi2{wCPQY{AEw5u`3_5c1X2{$br4ep}=df)s_aefY|9<-{a8n6-X9n&}fD z8vk<@5$fv+N_c`Sg(tL&&(2{dO&><`fT!vA79dbORY#h8tdh>vo02?;_HOAnp$U{& zZ54s$+1Mc|kSA_}5E30e={9B0MzYgcgSY&ETE-U$2kuV?&*VxjoEWo1y8#q-VASzK ziLis`TiOxm$}*Zn=IXts(=GNfYKCk?DN|`8T&HOx z5!ltDj%v5^At0k+a_!Y6EKAKVn55zg3*t=A5YDHkh4&PZ)Fu!E5@q{>-wqmQ6MJo* z2Z04yjgHSa5*v*?C_%ZW5YdmqHQSx`6DeZj)%|f9F=Uq6=nz3PP#dZLv&=s$`CyCO zo1#T}#!x7+54eeut!-(n57-Gp5$1|Ads&S+ZTCXeH2s02$C;xTs50&j??o3H1V|as z>k!6UnFC3=c)SY1U!(XwQ49=Q_r(6?-K_;|ssSj5n%-5ou^ACMEu1X8zSZJJTQNbK zY-o<@$G;ic!+GEU(TUP_+|YEqxOdljV`wVXZEkLgyXUw-o75co>=_u|EU+rTKD*gD zARgqUP2f70~yY(T1# zY&C%IztN4m(Xiv|BOx#XsX>MQqCQP8i9S@O2Qo&{VXuw*KhqFIaD^drk~QPOZskql z+kzAXtlLG{4{wtp$+<`cDgZ)^k_d0K8Tg3$aPmOoG(9 z^{&SeKng${qK6^DH8X&E+mCirY#h-u0l_>Y&s-x!8DMJdAn9$5k-RqCI6XzC8eXol zegCb+VJC}&xM#L?2z3@M97ls>E!l%H?76rD)~$PUuiT;}We%84Ob9nTQB1-mSW()u zU;P~kei0ZLE2YqOLcYOmFU3IM-A-@vkqLhVIp$dsuR4305pgS zccwiQkjtn^{n<7O&s$d?AUGIl6bVA%_}WG#nTVhAT2(6zQoAx=n4Ec|k&6P|`c0N5 z{Jw5=RNJ670(wR9-vYS6ELgB*M&J&)s>9(e9X9P z;f^A(iqS$h(b<^@82O_Nf1f+FXPvfxlSkYb@$i=o=SRiU4_lh{A%hSl%Vp72n=FYx z=PrjYYnQ?o4a@K&okV)(gjKCyy*BD!2KF7SGGN+$774nyW*9P;$Lp>H)f(*n^Sj@M z4slcxc|hAn=|K+~v8=3?kbUPzUjGFl(?3YaBn?Sof7-C0FggYaUV!E8KYP}cwgoGM zs~B0YUwk2eKRW62x<15 zs5O;A+MhF`HA?BX@BSygz;zVq00NJ&rXB@iA6$0YH_%occE2{hH8~TyKuC{R7QpA! zas{a=Oy~d%kedPx3eGXf&dQL{5h<7ZxWnkJ*Nzzscw`-dH=r*gfbObz@4OcYssjw7 z%dWWXJtnoEj` z=k}=(Vv`LPrE$jr;E0}KqY4?KR7LhVK`<40HE^LfBiNirsJ@FEQ&Xd5l;i-;YzDae zIQd4)WvCEJd#Z!br=9i_LCJ0Be>GsbCW;}EHMVsIcwHhzYgzl(U3c?cJS0h9II zEx$F~b;MZOpkL}^h z)r)ju1Y2SP))wGqa&sg+_zgr_+cwbVr4>e;?w<6=-!bHrX$Uil3PzSOxyasY29~-w zaVk8p0*d%ZT($A%Ub` zM4G^h`g%ClYfWUmt;zTlk-EBSzY*oDHUEkdzf<3O9u%qkjq+Sz=2~R;xk71YMrz%O zNYxHBHZ(-EHXG&VmCx%+usIy?HFl{jZ|ZP+Q=t4 z_{qpg0z&&Mr#vybb%1zo-b}2uHk8Z-mamdP6cHjx;G1<39yE&md(d@O4Or!ioWf=M zl!LuCRb(FUp?XUiS0XHaiu|nw_V?ptZw1`VVN%82kA-x?D{G|)-S$s{iHQ(@U8;yj zP5g6m^{Wwm5WYYHB$7o~i5>jNJ}meX@{D^=E$l^67#t{xpYvyk1X8^#5B92d;sJMQ zC;n_s5FTc{ytu{<{xXhX^YFx%Ia^-DO;) z*e(6`pe#Dlm8}Bn^n-ulCJXZ4awx(khhWJc$c z1?Iz>od<79>T*)(Hx4bjm0#JJj|00r?WtjwwH*uX;cUdgAQ@coi{xN6cP(USUs5h-l1R!MS`DxvOOq*7vHV# z59GFN30%xTW7j(tJDoCvd<6tV3@r$h?)QEdm5Vb1fdfvJ8dVJd0@PlJZzUh~!Ch@}*D9t3|SR zG@Oj4=m!6lF${A9|KqBf?=MyviV`x$0NR@f4ypvKa1%9;@n@1w!_Hoaa_j=1k`!aM z8l3v(Xr?z*3(7_Pxnkq(v;!oqoWTr%+?Ucn^h)HYKs}uzrf)JuDOzN0P+>itOpxbv zI(j{n^kJCl_!LSm6kJ!j%_*r@U1tkxnr`qQB;b%)um>mza>&bLxc5`P`wu31Xjvip z+9C`^uUKPcwUuKUkZ2BxJ0o|5AJ4k?hvOMo21e1PZv8w$p0i!lji*l6VGTQ=UPRkO zXE2&l;qh&-DJy|$1o>uvEDHa;>)6figH)i`^uIA6j<=_xPJFMmiyc`8?RTj3+RJf= znE6dZgHN$_cqNt(egVgqQjtHe|I0ePm$OuL*8q-SM_ZUq!%U!D-4I{EEES_7||-Md*YO21PzRd6&~eLsV@-(2LPIuABsiL?Ja`*YdkDf);;WZopaW-22=O94T*^MQ+{T z23SJMtsFMld==859y%|I=suSy6~@?&hHn>%?Xv#TXtNVuSy~g0B~!U+5Y__mb2b%j zc*CN|gg|B)#`^vhm(AWDA_A(1}oRSJ31SFk~`NmJg)gpZ$L>R<>t!@ zX?ngcIk4NC-=yC83AbkK&v2J1?#zI)xXz10R#r*1F&+GGv=NG;(s-Np`LhCn725U$+~ z|CpdqTp>&(>o*?5=;B>)N3>TL`>fh4rO6_#qPj1hXYs|V?2dL6Hb@NMYNEaK@){12dH97X1TzHF;0xZYv)_9?Ivt<9;)M|Z{jQM*qSvWx5dA*rCV4wiKpwK1g1_-b zc}fGrpJAY2tYxW4IwR#VG!}Suy)42x)5JG=HAa-mAYqC&5@P%iF-yJTcVBX)(YjZF zvoO!8eQ!7goo3OJs_pMF@Vls%qUO78`)ENU-T>+`&AQk=zm;4Mef|vLJTevHf(YMr znp&;B&M1kDLp+I`2qHSHxQu+l6q2H4IzHMmvRuSP3nQt1s!j8-lJI%WP~KkXnZ4(% z9cZckuG1rm8;dj$bnl03sTmYXzBer$871KBY1(-=if z#EF;&1p-)BZ^8|BPupTU48?|{FQ5AQx>n>+n~VZENLMganEvY)oAAnrWB4tkbFR7L z#`hrru!K$=9L>HynealtX4migI?|MFx(CiHA z*<`&lHZe~?r=7)ME9Y_#Ns>^Q$R-|Zk`5S&b`@JeIW)=I{pS>W@)Gwa)5%*^@gMdF z-w!WNCWB3sp-n0W{Q!3aE5xAI8G0M6VN>8k8V6#fbk@8@@ofJ_!u!f#I6P;ioKlM4 zKkWDp{BnM8b^q`WBPwTv0M$#wgl*%tpt!a9s5w+3bfvsLNB1Z8?e@g_^Z&XL`i4!A+h$4aLfzK(?P z(+b2C2nsn=*+7UKCe!bnlH~~81(p&tXXSO2N4`<%S z$0(X{GTwxL7q89P<}Hf1;yT@g-Uf;^>YvhXrs4c@{7iUVxs;8NM+IqY(jai?BMmn= z;=xp`q8cm*mGP{9iu&6Cv5WHf4EiM8+a%Y91t2Os`di~SBBN8}HM4(J#Sl>9wxx@r zig5~WBO56nO+OWdN@k$y?aR*AB)_tr;lU_nJYHk9+_%DB()l1DF8l z(r?e+jJtvX-6S5Ew7rC?5PExBgEAfRZ`>GAeufe}wIhh2(FkC}+L~n-_$*NnYG<#G zp7-KN1;Wj-HE@%mV2HAs5c9#?(<^;|i1G=P4qI73A&b1y5hH5*g<$$zzP}Ab&8yvwjQ8}rb0UG%RC_zqW}~QTp4@S@a_AE@QDNu zLtNi6!My9CleI0RQz+PHfjwJu}mn>g&)C`8aKkbZ{X9> zo5qt)8em!{$Jb6H)MDYG^%Oka_xkc!Laf->up?b)stt%I13u8mr0f{L7KH4VxC|tu zG9gNfLn`s0?^Lt1mY4NTaqXy0!4f<+J?Y*1VnV9x@m7l+qskczK}l??-3^fgy?&jT zH-$F}S?lWv_M8p*Q_4Uoxd}z1Ld8Ct7SAf=nz}7Wmd#GC&!m$TxhRH#BlpSc(fyK{l}@&6_&OLzQ{;5 zDobi<@)LLrq`J2JTY@V9vj!w8D~@MJIA{z!RHh~FL*9TWRoy!z9Vkm(QV(!W^$F)j zW%WJc8b;YG!6o3Hdn9r6ro%VHMsG(fwh%BxQ;WErOgyxw?HZDSof!{}U`PC@Cnh*F zF5`%T?-Dr5!xE(@7YiUggx=)uH#k_b-wG=W4@gvL-_>yXDxs>D%ormptND&z(JdsE zDNS%h(NPuoY8Ba=SrHS+{jt=)9NwXosu?Z3yx2B`V@PB4X6BMj@9?oUtq zC=Rb%HE1!k1O>YHe(E5~!f!{wF-}4&Z2PlFlqY9rmm0+zPSG{7DmW+Jk<(Tvhc%={ zWxr^UCE63a(|}fLDoS?o2arbMB@$B2_uD+T6xD@GNbAOf2t~Hrh5hMpG8@)yj?yZl z(LR73OWSS^a^p?Y4WlkSLR}=;NrNo&B9JR^j}XfAL!hBEQb|kgzfB*QuVr zm)mKCGA}(S(^!ti)u{d*S}1|mTdpPM2#?bu6XN}GJt{p64uEEKa_w3c>_1YpQb8k$IMf14FTI8(PUQP)_!Ge^YNwSCp@XB%4kSLHKDRblV2JwLh1+q?IE({8) ztwxM=SrQ6(s@EfsR3(Tl(5D7=FL)51K4^fASUm;FP&R^_C=BvpfXYq~PlHLQMAghH z?;0T?nD~F^WsnIxnMH;>@(yQ{)60t?kDny1rbyAIhH164FvP`FS^#kS`?Q&f^*@T4 z#B|*XOq~D4$p?FuYhpfOxOSPV6Sdf41)OOqi_+1dQh>01{xn?OQFoN9=D~>Pdj13= znAr^^ekJ@gB*AI4_w2MwQ>&QX65osWEry5WIX(@FX)ZEpSH5tX6m-P|7r%1r%{={a z9tgVk4@YCQ{05~5dadRC@&0%@N>p!<=q;W}yh3Ot*!yl&4yTh`0M_nB)+Mgl2__%LQFU$~vqprD?}bA`p@( zBX+`Sf%1%#aeByXw~Y&iPk1^KAZTi2U~*a~&$}b;{N|44YaFqe)3(+r-RS0}m2=+3 zcpvJqW|U@}ec^n5fs?x*xnAAdnkdhurT!sy9c&62e|=PL3)kSqV36ctNbyE=LxNCB z0n-Pepi=HNjy}!&`Dl{=J`ysOKQmm-?8#apGp!{hfPn&BX8RZu-fyRCpW6 z%CDnGH1j0=xwKHC8@8v;?NK|Uf4=7_m6&XI!ja4FtiT>L`+|bMfxD%G%EDftROd~% zF@<*`HO+IJ+)Acs9yITj(zujcIr{a@ih<6aMp$J5HpEy-@5?Rk-VewdPCSj}|qna)W zb8ykT>bNoAPE>xP{%ou?GT9W7cOX%t0!J!8%AWiT+@xT6K*=0s9ov(0JeZxTu4s#} z5@P9@(z_oWTyvdZcPphARinUblT`DjE8r6C}- zeX1Kxwo~kBHD2^n3&SQF%*rl`>4KGfqBxjIe$~ZPL+yfaRj&K}nVfc2(@#Q0 zLg@DKwa($W-6dzdp1@XfzkQW8lbaVg)bPo}(v8s2b{8HAo+lxH4ehQA$!YlX+Q=gP zwr1@w(s(8xp^7yNlePMRiW3ya@v~Oijy`r~De2Eg${WPwvvV1hnwwvu{yZ2F_Mz7C z|7%=vILn2SxUI_izFAiMUzBr$crd5!Q_}6}{>F6LztX>(8~%uhT9i6Ktjxln*tOa= zyI>`09!KXgZ>3vrAQItZoURRAj6pwd?XK)4YLrMe5 z>4Wele-=$kQk~(8YMy!of+imkpylP)1nSB2kn%Az<@IvPnpv?$0O55izs0lufJ$4K zMic{9Ml&E3o!vE$X~nB~wSYMQP@~}B$e9kvG^KnjkuF#lE}7OeDDwI9oTcaQ@Hy*S zZCO>0{H6L@)Yg6^mAqJs$pj>fJbHr=2;6NWg-bOUKYJv-*%MoxSYgxWBE+JWwO&dV zA`(L9_FYwQ3r$5MP}ARBhDOe`+i8WwqBia0 z%R{BIOpU`gr~T`{L80jQO{hF@?NATBMnU`-19m^OgKNi)!=Z9h>+Aci_B?oa%|gP$ z)3lnu@Jy{Z#xZZO*PHt0j`OXw3EwO+w7+>AxJ*KGL^S*ELv-iPN7_)DvMQD;5}Wc9 z$&(b7p^XBMG2%8*LVln{s{RyAs7dKk9E>f@PO$vra(|1g#%dzzr|GWfJA*aNeo#( zQzGEP_E(Zx!kZk?!{Y+cOTGcuGV@s^ZA`Mk@~zX!$@0taJzJDqh@*cKdIXZ89kA%~ zoOPiQuC8#HKY8Mp;BN{rhl%#?o&27F0&4K7!IEC=_+Jh0}s&MkR&dx8cqSzVi4mk8Vf*0~>LzgDA)kv!IdpDj{^NkFIvsvDcn4ZQ& z^h+O;`DcvT4p-D#^tE$e(#fQWKV~<2vSNdTX6F2bXEO;|uua>TzPe=geNb^b?H?vB z>r^5WSi2ea>A@*1bDKM9HQF>v07MBV$tGzqQ$5K@fWh8%C*^#)CSws2kSk#(fjN+# zCOW&*kF0+i8FroA`sMN=eFB(+=|q;!a9wKyYxjO>E($LI=X^X71%!4sb}l3m)`&j0 zOyMjhT%Ycfq>MxQzGg6kcA^BMPcP)aA^hG_x$Gl-5UvY>`PU8a`vC>a;4W)ng=v_y za2xgHxK%v_k}{^dsE)AbsPllj57m}PzYzAID7d15WHLl1d?p-ZPBrD7*Q4`u1jRt5 zn!%*QAe01*C72j&Qw>w;;WiGGA5AprUp;pj|kGH+L3xK1Y!BOWtL!O{Ptf# zEI6C-DI#k615WWMb$7J@$gk*_yUz$7N9lAEV;(1YD&13Gt;q+i&R6J)$5p=NVEWdd zQo4H|m~x^#KN7wjKcse0Yy@gVPbKq9Yz;_Wo4vZFXG+f>j=n$w7NrBQ_fr`1JILom zlKN~Y7aY^ehbCwZj~#0<49vL3bD$W6BPHszzcG>l5J^4E=*&h}fqMGiP7^VJEF>Ev zSxkT6-CG5B|H+v_p@ErDqa3Tf#I(Gmzk>VFW0)dI(Wy!N#NFXwS0LszQ;n2EJd_2? zVToH3mKUjH<$U-(Tz*0THsU`1;VBr6-*5=lI&HBueSV+Tui4r?>uXUu6}QH~FFrtV zow5sp>tiluJPAjtMc+%g6a(qCTa#|LI($tzP+Dnh#*4FM4-Z02DDDJ|D4cPo?)}gR z0=3ZN*rlI3fCfxFe<}S5A<14;#tdjA%(t$aN=|&Q`|`nH_yz$YB#b~mu?&&-fQ}azr0TRH%QX!YGY;_~L~O)~BZ41-H$I+^{;>7Hc{9XCBd4cVb~(M3d1`lt8od@7=zaba^eO(egRQmu?^_+xH8L z%FUucy9a6f8o~ZSt6j2LiC9;iu#?=fb3cpw~9LzRhWC5uFyj}{8BAKBMNRgKDx2?XBZUX!k2 zmP2m~ir3g&)Cq6g*z)De))z&}RkT@o52B^$k2*!g-z2(LKaGA<0n&1COT2eVb8= z##pu`j$(cN(bl&cFP`omJbb?K!{+0Iji*mGb~Lmx?MDuo3k1+#rBi5_HrYgEg{OtG z3n1q6(e+>^Z^AS{Lf()7u#$xPdI)3twr7PEgSz;xv)}vEyMO<6RWHu#{Y>#Ro1dQU zOtp)fHdlSL6=f`iKit~pf4h!_-wDwuEeZ-Y!-(IvvT4RHN)QB7vZ&2#12TC$KDHKD zhy5j!DOtnKbh%t=QVq?}0Ju0`q!RQL4d9_1dGLwrv=reZ31}Pfh1$E0Ug|p-IbcFX;m`HffUhBf}3B({MT$5y=2X`#hdNwcEOcm-O!Pd@2a0@<(li zR$^Q9j&a2Qs#T8=he&D_4cc=ewp`FmdJqd1zQ-|7=OYqc>>Y3O1^ikk*;*6A)9t8L zxzg;ZHkzSfwHd@u-NB)Vp!)akN(y~1ybRaEyE&?go~Wu)SaNP4VwMCWQG9$$YE9eC z0%l_C6m>N0zOJH-xYDC*J6zGy)zsVs2P%nzfT223Zla#kGY@jIUdXrTWiDUg}T)eHl$2T{28hmVHG z{mWCkKNA?H3OrVyk{esK_=U*u4Z0y5VV*B3{Z0t$kB&5QZ*Bk@*nD!P>%{ro>+@kO z5X8(TM#Znf{5q=HC@F)2MdBA{2g+9RItNEYHkn_hAc<0E4#{s>UlKS%_gikv{fJ&k zTjB4;GimaB1`}S{BgmJ!8*FPcv=rc7^C?g_m)r|;FNvqJu*_;*^r{R&>&!*c)MO znH9CA-Xv0BOsH^yf(^chp*p zsCYyAS3H_G8qlR2js51wbiuwvgOfu1GCZM#>KVHI>83*fs6NbBV-R*=&zn8MT}U}< zzjd1N$&&YMFZS)cET-n;O#HLfeUi`oru_z8~-4P_SN{ zi)LCh>D2r!P?NDTFKw=p7y^h)H;p{ z0jdU^=;z5Z_K@IAr4#OB{pX|EBWG=;7-aykC3y$c4aE$eO-Hy6Ga>-z^B~2woC8(y zMJHG78>hL_cjy;l8*_p&7Asa_(Kh3%uSmnfz*taNH!0kPAm`l22GgArza zM>Vqxk1$1%qh;Eu{az0#>nzJFQMfBp6oY*_JKQyT&3fi_Bm6Lqt{t)XkRvYe zB}UwMvHR@lvyDdwk6wJg?XX2L(FtX(?%?w5yb-$9&pGts-#>r$Z1-S$WAiT?--TV4 zS+JYwWO9KUnZ%;xdzQ?`S%;wsH+%E>77OxhKY#WgTbuZ4=)%r$;}o|J!vEiKZ*C(% z36|%ZkDvUw6?y=|^`}QCDE!*^CFf9nDgep8{m_F1YW6}95qO_T3`sRY9th>|e;Ho! zXxYgiR*a5Xfl|R{0?iBrY%YN*vOOKXT3?@fVEViL8LtD7upz_`AAkk0>YmYDc_RJLJFGkS|54{$v~5 zSSKRc^$Z)N#>L~Jnqt;s(`zyYWHD=k)8ZUyqyW|!cbgCwfQ0Mon~yhs_+jhm4iGV; zy#5>waVMKx_CS09-F`C~zU7~@asM3V9Nu5jzQg~yEC18G%X_}jn~Ruv&L+q4H$W)d z+nhZ?9u=mSJ~C6@?2-E5RZaA{hC`6boC*_9zP_&BW1@`aGsVj1C06Ui*}#_o@y5Ux zrbPKqmqTGG97T~2Ojyy^iPhev7zYEo^(|vrC5~vH3h0FUciJZt1O_8$aLrkiehxy* z!5IJV_xGipiL~Jl(6bbMm&W;FXU#P13CCwHH@l4XH+zXQe?5}iC2Az8o-Uqbz!0Fu zXg8TSn0rb6(mgcK_W=Qw!tcw#42o(WngoOpVh2UPmF^MssydtMU(}s zX5zoANgK2`sADVurSHQ2q_5-b5|R?C2TyQs%j6g_VQ>!ws*2MEhmssv|7PaZ53P?I z;fG_B&4>u?um|SNE+)9=b2GeD9x##Cp>q*1&%am8y6bK&Ze%X?G+mE*oww^tu;X{rkaDnv( zi4784Q<*g1=6#6&ql{jU_}p0tNv@b_Vl1t<-}9|a z?o><(-b)F#wbsG!V!!v=XQOfV{Nx;AXJ38Y;x7ES^?c{avmXwgJW8pdwO7?831!-U z0jQiq2pkR&72B{LtpJhROyCG^8U#tW_TB2gL1BFsQ$khosTB-=yd9b%86lZY zj08&u(lX!8z53c%@Lx&`E@T&XBH?bWQKupw0lpE z;4y{yGA1{_EU$Vpr`A2p1xY;?!Q+5(ZVcE~MHEgS!D0-9Wt2JLod$oIiFk}!r z6Nrf?6=Lm8iAcJMA7o^sIE15GD*$B=8-=YK7I6axm^jXR6dn&xAw*N(O?(jz1+%@T z6e=v8_OFHm)&27u8G`eoJkhCeQ}C{`x^*JWR7_X3QVS8gFpvt3?RUv1Wd#<9b7Ps-*1ZiVqr2SRcKFqD-i_&>t#OdN&C=`9P@>4hZ)O< z5K>`xyTIYWk?D_TZ;@ye$Qwy@fA)G~Ov=HccSBrP^=Lv$fLJS( zrDEJY&?Djq?1JzPvJh;?TCEm$E7YL2Vg^5da{R*(cbE?Xjw6#i#_Tx4L#HOcrvZnq zW7I=)mI_;hmQ4`Icr4rr^4Y->u$Oy~bmeR8#Pn(tVI|z*+X;_I(cKSEAzk}4Tue)a zeyx1g9Zb$ppb(rQkw#bW4i(QxE-@xcM0_U?Jk+M%!i0o>xLkGpw{3a1j(~x}F z>c8SH+oT*V5a@s*L*T8EwoZHF7(<3-QV^AkQ3MpYMuk{3J=2R+gzU+Xr=!=BP$~E- zk|%)%F_IL(m9RZjEC4!76*tmnngJ-z6Nd6IBDLN~Eyq4VA=%aNyF@5#Y`~~=Z>5(= z0bo_)fHf3Ax{vn@qrL>K6}o)o9+%K)>^QWZu@~EL2!rU4V3PwwEexFC)uhG1UjYZP zi8F7V@-*}^*((_m*YL1WziyE0d&Gf@ea*zEcMPJKAhe9$>U_>9wbRqt+Q}4&$20i< zPWsRwdFf0H06vE3c0x`C;s$EUzpQugsK0^ZG2yy@ibMJqu@x=ecyrqNd?xG;@J*IW zVBkCtv2GCKuz`DRnGV$Y?4h)5zg$}FDHVxAsxPhwrV&v+pXaUCx$+}3U?hib!JTLC zYN)#T*7uKCR1_)bD!RckIuVIFB%G&9QPcF?O`q2nC1&FGXBk=L!54xg_!+qyyhkC( zyoR<&>HUU|5<+x(<-J0tvvsV3eL@(Ca>7lhQ()K#!nz~0DOOI21{J4YbJ!ZxvwF7X z5Lf2ZYd|19%0l&ojO&ODxNN0j9o}^k_Ab&RCQc&;@V$1|n61}%j7cuJ3n(9&GhjTp zo&iH(B~9|ky!Rt~_Gxln=F3W@A&=kg;hy!`?CoTViX9Xvu?p_R(Won|gXUd34)MW+ zALe$wMig2ORxl7^QgDzT6|d!%E)Xj0(lUnXA8L+$Z&~yX7r_*%l4E0VhAjDk$7W7P z`LU6}_yf$+E}x^Z;KlQ&2RoaOx4z$E##BJ=PkXLZUXmTpOje6>Nx>}nJTXx;i`UnZ zhmW+kkz^P!RjUgXbZLcXOHijBYp$T(KOgn-CL#<#*ZX!d6-v4*qM*kjhrV0w{4+fn z$sxLi=_nB0eAWy?>zkjThAj%XYf!V^x%{)9D*9eeRG$hHODM@huP;28n2-P|?eZ0` zWF4st4#`elC!C<17O+_#MY4dtg<(lOhfX?-x)UP$K~~tnLaIUY{0V@+R*fqgDxnlG*jSHdhFULL(3Uc4_lsf?0+{(9u6p~yS@=l3xlA`bTowSn~>$oB!QRjQq5&~ z&LpF10#BEsG1M`i;+L?F$imdP;zJf)zZkzBPu`AOk~50=BDPz>k<3a(2<@iqbE#Rz zb$Pfdbr^Ywd)Ukvta)nx0iTllf@?6SR+~-`sFB>&GG2vhLPJ(n&LWk%M!uS3=V-U^ zUry8`+I51jOOI7>a;@SLV{jzrcGUSJ2tjKRGa~=JFm?z5o9&(FRNBLI`xrT28)B^V zkGD;5wU6Tf4Qwb(30v)>apYjph(oLGK>Z_n$c0P8>_+>`jjbc}DOAkwu!-g{ek?{s z&nhr0Th&(SmPu@HVr3Mf62Z}ovcm2o$3|W(y%pc9bt@<(uA(H{c%7b6kt0l`8zNeF zs_sxn&{SB52YQ=7oNSXUR=ta16VxI%8F-nRjO2wb1MO=vZS-Xbo;NE^A zwA!pd+7Txow@Q3{kwPOE$up#!$=PmYm~0AOX+vIOS*OrJ2_Tf)z=@6S0oGt2(j%5ZJL2Ay84K4g9exMp3zXyMk*xANEZA=i;p(#XsI)%K9#2L z)Yp~ej zJEjSrj|HRIFF20WrPV3W=0yIJ3L^foJQE?KDrSu49^^!j*qwxP&HyUN6?SjIit-E` zr`#e|e1N!OGmt$-U^V7Q3`PG^piR5LVh){LiDgG?w29~|4A}uj30W2-;m3=QyuGpe z7$f69`Ea|B%R2Bi0*EwXd5s)gBATP03wjEosbl-q7E*@qX-pVmpQVqzFHw4kmZt%< z5yskUz|99n5K}eBJaD{2%+mh1a34;lS4E;&W01?HxQdG}oK)j#yQ*6Pfxc>h ziv_P(c#Hi~K>}Yn17q9}T_)+HJiaJ>qwLP@+_4kh|I2n7lY&9u^qj-Pu!(vJE#$Hc zh1O>9RM4`^X6aiWS`~x!>@}c=)|yZeO;|t`LdjgNJxRAL?pqsC|R7G+!Ewa8+Iub zR|2GS7A1QJLMv86+es}YYP6K~6QdetYpeY#p@`*V$OS-B|1ngzn^QrhvM)rbGU6v} zHau73ZH9oN(b(LCkm<1e?zYNV%A;)8y7z~2cOlXzz%$fA|Ed*x)P*YsgH}y0FAy(O z^3J)6E~X9|TbNumu*iGDv9Q?&B3qfgAM+6YXyE}H(RRyiG!R*dA~=H2afFk=^&G!c zqt?LYIj0=z#*#)Ih|1kPM*Jq6_!4a(utiuBJCz4$dWQQoCIk7^-Uid*Eq6dnX0GVN zS16WralnhLzCJn~;qgJ@5=3kGvY874Il}w}h-T4Ml<|(-&w#0Hyw?U-Vcg5Xh=Ew* zk#%89-`C)9wCwCZoxB=`*bE7J?WM zHzL=z2ccJb^8j+i#jslW0|@JtlB%qNB{@tj$m?p59p1YwCN4XKbcZBTUHdDo^yW@L zjldw4#~nLUOnLGKrkz1Ay?kU&PrD4(Vf?~LpOwH*rbEOlNKn>H4NkhOasqdH68HeS zLndn@Mn(YIkH;>^-D_?kz{xy2lq}fESZq}FL)?u+sErkq%omD6Pzd-Nj0WTVwx4%w zDz>?Ue7&=)Rf+95MBZErqFuvKU*32q&>6&@`=Rz7;80Eq0^uT>N5QvJafQWJZr`!e z&h-%ac)Okjtw45eVI61v@g)nqx0v@kZ5>`==Op^x!7?7kje-A{hbh9DRh_m^vdWyWQ@zNe(BLHcNBKDh&=!ikbF#26AG5qyFE z3X5_NG|p=MA#z(cb{*_f%m;}~WjEXP`iKI+ZJ+(I7`y^o>K>g=E(dFa$jxo7&$_P7(GhQC5s{n_eCFxSQF=n~dAqRsPJN&tiM9Lun&7dx%c7oC(G!0lTYv8RE zSO?Un*@!s|$%@A<<;1OBVOt%Z#Egt&Jz%{9F4&KVRJ=v5nV~!~P9Y5DNjS;kG=Ms_ z!UHTy!ue(AWHdl*2c&^EpR4 zZ@+(X5kS5pRON=O_iw82r+U>cBIyQ>q83HORn z?WaHp)06QAR1lmb&>N4+5aB(HHg9b22D56SRa(YJ2iUQI`z+p`=Eb1b@Z`=pR_f|Q zM|$Rstj;b){5(b|`4uEMy}^yW)NsL){~5%Dc7>4qFqP=+!U|e< z6`(YzE#E>#1A75;idTA?0t52>2D!=tu0_ELp~>acpnO&r>d0_oq{**e22S}=QlyA1 zzxEhKu}JJrF?WTQ10xR<(NgtaQ4*LKl*kn3QMa?4o(71&kD}P97sd$$?J!!Vvbpmy z@^tYJuC_s{uj0O%?*hRBJKI}Y_VYi^R= zlHhL8MFz^g7yo00ZKm5MD3;p>1;rK_ig^p%=7K?e0uz2x;X`#mt?c6Z#`mM?bRy}b zBz9{1W;jz?=f#YL*qOi}&*U?a5gb9R*ba4)$TckwH_;nWI~jS>xU@ETMw3|b6Lccp zzXm9qf~f<+F(4{t=tsoL@C(f=F%+G>e)srx_fIeX_{-;^;peL>5z&yxL;UkjjMhb8r3`Zq=M@_|K=(BT=*&3%?s( z+yoIZK!RZe3+PyeQ3cxk;it}nb^IUoU>;v(N_rB2!)MKP16-M(%q;al z?Ms&YoJp)*GmXQ;>!(5GS!b=2iA9_Ce(HDsfg^T+t48&bQQj=9wtfLUPlqxcBY7#( zES2YAJei&$N%|kd!Hel>hW@vfO2n7$715Ck7En%8a)snIBh=$p7~{POn~bXXqo*uI zCtsqJO)={Eo#JhrAdn$H!Eqr1s87KQlw!cz&t&0!sh+HV7yjDqyrCtm!?d~ZCuV22 zj)G^Q7>D&6_W3_P!el(JX`|CQ^ut(zVvK|VX1?v6^TE&i{GX?zqai#+a3Ayi&hT^~ zQ+f*b=>DV2bKD;a>n`NQVrF?m&H&DN&^VBH{m&UK#d%wwa9Tr02ukoUBYS7_FJ6QM z%&W=s-rDiEDnqpEm;xYj8~ypkx9-@B*i&AH0zxI1`j>$|17fr)XZZ5 zDmoq=BfkWX$nO5^7nc0pGUkzy%Z(zIbXIh!Y)5`2`p0O3qnGxw!Y`S&!s5|n-1^(W zwu9Rm&iWnibAA2k#(!!?)&@IDW~lDX`p>QG_QSVNpKR{#q+3)=6KsP}n)d=W2x6%@C?DwqPB1K0MJ5p%g=_{T)1D6?~RHXOq>N~_8MB}yi}7V_F!@WH^6-%; z{K9J^<^8CjpCKZ_wFBab=mj>b8KMqz_zK}L(hIIA4E|q2k!F4S+rmFyLi1xstH0R4 z`j5U^m2$TG`*-&DzwGYs-`(H8#}iN)5Bwn?4JU`Clv$K1eFK7kc+!-K7ROa4D{kh& z1m%?IxfShFAbCjX=v>OefNXb$y;r@KjL|(pboyY8u_)aEisueztG(7=LD>}o9@#+e zwVhKGu$kdCqyqYvQ!lYpxdXdEEh!W%;a=iLvRBm80)#)1k7wC!mZrT6{B+M{PUr&ADcGGYJfvT zb8?v%E7iGU#<)AM(j9Pmx%F;E$Ne?t5FSpD?f^;4K9HC;`s^|mOj;@TFEr5jrld=omv$+8 zth*`Q+RMhClQu=?agXEh(yc8cxKJ)|B@(Vjm^b%y1lVg71{SE59YR*k4AfncOnfBd zmT*JBPLs||I%eG<=nxb?w21=~@86z`K#^uRA2ToZTHuKhu>5WJBvy=;e~7?^IAuotrO z3DAHcZva-GF+HI7T^OM4z@Wt;j!WP-3>9VM5$M&Le1FJMd64-W!Qyux#CCP(VE!Fjj#d=N*Ac+FX*H zHau5ycS9Q6Mcx4|9PoA zskqbV>C@IPEnYJ99OiH|37Y{TEo6(+{^&K<3*X)IA@Wi8x1mmtW<&q?3AI#Seyjew z=fk7R=?o#;kD&ZY_UaS7?<0-1c<+z>UvvLmogGicSNoeZA&enx?SBh=m|X$HXVLe8 zTNJ}#oc|rH=2Lvme@GtjpK^iNW6A?OeR4}O@;F`J@>S?4JLK7hduA|#O-i1Tsu;4G4^6)e(6@Fn4!`DCK`frr z#pRd^UHYXB5i)}moOUDve8AC0(=AkBzX}t_Oa%~-RrO=YTlY%>HYBr3jyto@6uGR22xc(%gUREviRq>~0TT1Lr_C=|QVAF8$YPbn|>GR?2 z@|51@4EML}EeueAihX8ooZ?H53g^uI80AgEMs34OLQQFXeKh+kuzB)!rW|zz*BD|7 zuBf-CXBf4$w$obs-S_+sLB2FVFv=#Da#L})07~cP=Th>bE`W&ZtgT$vLYD_ z7CDe0#o3HHxX42Z5iA@S3k=v2gGK(t*&)e92_`ZX9^9pyw> ze|fTf@aS0{vNZSFGDw-+fjWwZ8}o+4^Uj^y{LhwIa{`hLbSz)_Wb1)9WmJO7^*8^w zgW(ep`D6c=F^W%~K05gJ$#ba-6`ghLo06m`B>;--D}RvOgS45nW%2`sEY(oN3b_qA zigr{q0*VF!N*g;((ay-dveu;hOBRnb9YF}<0(2S)C(Fi-gh37jzAsU~UTm+$elB#* z&hk|Qd26CbOHmSp;YfTJF#gk5p)m=Faf{SQ9TPH zBP(Z&j>bi~`Ld-}*j!%mx zn3!N}#>v_o%n)@eu$6!q3NgV8ZQ&ZIAUUEw!I+1G8z8{kj>AqTegDr!zim~oom*_9 zt2Fg!a()Fb`1rYm5QII~7})4?LA)zYl*koIwD}!kxFl2-l-=H|tuR%F6ONYqb?=^_{fWjGarNb?`%E&Ht>Ff zVLhvi+r$_SGhrw#hz6)aWUz0@-a$X@7it?kdIC4v?z88AJJ{LU-gv%&KUwi$IuhAo z=kkzWsQ6&={taTM~6_1`&4 z5r$&l`Nlr0oQx<_%7B80je3Rfw0PE%Gg|38KShmfidAXO;PcL`Yd;fyFoE&^*0x$} zPiSUa`y2GB?cB;9%xpFVczEWvFaTlie4~O%vDVfBYnjdrduA3htZSax!YcnMG9;i< zUD*i}1~-l;?9WA59OfD*oV`&H{Fhp`(k)pXR<$U)I1#O~16rSVP`Qy;Uh~WZ{AXZU z!VZuMs}_A1X&IMhNlR|Xk5#TW4q?YbV5#&d>9ktT&n7&YgN0br zk2{~wkT?YFWhqkWp?V-l-hPO_fA%&V$*x07h)f2dkp(-==K?zvM^Kf{#C|n0zE$d>uKt zk}R7vSN9n&c3pR10k`a@12ED*wgng6V zl6heswjc2e9hX(W*BFzlRG$z;7=eIM?-D*xx0s!dRYrwla?qUxrS|dnDNM7;SgTx@ ztSEWNTW}Q^3?acym$)z;TZyF=;}WeBDjKINJ@mrx%uuf#Tsd%f7=i84XcvG!_*bJ8J&JQ{Qqxj8MFbkblB_seC(66W|`uSbxi3C=0dHR&1~kcc{P z-%30=08Y^(2(0>B<;r3Y)+njz{y`)QI3}wHdfk z*Jlf=S`1e|CYx0l!`C6+F_anfm$*v2-FN!y0ul5}2hGy(}w8ze>&#y&~rx4+L*mv!4)0&J(J&zZ?NouIwf zUYA;RUsbir`z+)f<>2^y@P>nM5>VD$ZtyWBTcmS`x{^7_2Lp*BtihdULj-3CdI0P? zhtFCbY^Bd_V?{pMKsd$puee*n8g(WvP<BkG60m5N%pwPhdv6%>MW#F%`@EHJ)2L9;+e4 zuYfXUIyeTB8K$g65ML>Zyu;j;<=pz?7em}Uzkx0*EYjdrnU@@10rIVfp8@>8%YX5o zhq@8F{dvSgJkS)8=@KpoQaKU$ zv(Cmpg>$?(8K0wI2R=e&nd|r=`Gk`+-UW3Q;iUK0)B=SGJ>OC z4M0=pr$rw#szSKEsVVp>jcpe8t**V|4Hi;&D^1RbYQOXef-{xzRC@edob~#~3&RNc zHiT3y=~EVFx|luCASLM}A(8wrF@n-#45WRWap0x_NuetvgWzq?;;#ZJs}DN5EjfH( z`2MwPpv|$ye4(gYlva^E<_<3CG>^;0Mn1W)5=uUS4dr&WkEL&f1yBZWAneRH{_Tx} zW72Z;gUJN3ebL$T$Y#apP+cU)Kp)Z+;!?Zd?C@Pc!qRb59+x55uNvXzo19TM_c$Er z#W(dZ!mte3B%lli_M|$RJ&CT;_sRT$J6xZH#7c*X(o=A?+Ihf$IN^Y_gzYDpq^;1g z)VtNGiqNT{u1n$Jn?R(eKQ}GHm+eJpkZgs>D1If>lCCsSSNPt&ks`bFFePZ)VIfmH zc2?&tBW;#=_D_#+^ktPK=?wx|Mgl8h!V|l&xY{Qi0z9+Pc#z{EId7r)N%0Ou3^L=` zLoUutr-4`uDMM;uWKcFzB`D$ql2n;i82m|RK$FxL#H2V$DM$R3#vJl(79kWlfh4_A z48%u-X%PmG`LIF6=v#Yjma z8{A(S1u)OhCPK+5AQfp-qu*&_h_Cq)Fvfc4cKwJ?F#>cVfEA~aqf=bS6i^m1_tf3e z#xRydIWB`Zkh9fdZIswK3km2R9*P4cvi8v}lDAvh_qQN8f z7J&=?Eued4Tg6}}n4mgr(HC7G#Uu0m>+p%Lg$1;686Vu!x&$?H03V-f3Z2C1SVCgl zIqW@Qe3E0_?BI>h2szgRoyU>D@)z1kc5uxP$19`W>i@!>SsrwN`8uD(+RO>B3Gx8! zA+534w22GbV8!GO zaVs5DA75=uZR=z5rXe5m20)G6b!^?)dt55n)}Ovk?L7cSe`2PGQgJbFl{`+ThQ}e4 zwW0>*`{29YiMYNWYXk^Gl@G)R+|i@%_+Y)LZvYOHP0FY;lT8n-%(cR_n4^QZR=7UJt zQeoU{AkYf-Kmxlh=gA3VtnbF{#^>JCy4?GU7n0Jl+^`F4@)e@m?r3=2$8|KQr~df8 zf>P@Jlb0dH8y=z<^OKG!^uSw6;3D99F)=_7|F#ij^`x_kdv`oOhVOIt*GI2!J=*k-Cudp!!0T4h~*Zl$llc0C@DmyVxobI@g%%mYTe@mmgZ4^AT^%>OH>9!15`dd z7C9p|L^>#hHJSiCr~8OV?;N7yK;!1}O*ySZwUC=U$K65Zl+G80Lmnmf?JeKCw?u7& zaqj;_9YNf9mcN&mSU>BDpaL%?+XbTIspA9WytfplQn)GPeTwu-HUtZ+oUL|b22;_YE@?)Oi5Y%nsRmW@%jEjXhtbF z6+roY5`-$qP8Af?MJ|Tl|(RsZ$@?M+XbHLRd3*UDGo=z z&zke6s5LrdxVN_2vP&m~Ru$0EN`P6eLeEU``Owou%p)l6IrA{`{&0;mTV zH5+gH9f4dpZ3Jup7=a@RMlhC4O6e!LM3db}K_=pZe@>v|bH`(DB}Q^OyfL@`o?LEg zeEe)UQepvaR)hj^_4;&0#`JJrZfvojOb#t@3s(>erKIZ%0Yqt#w4-0dO%*J|See z%@XFoYq~017(~YP_mTWDyL?9vZ?Oe&NauW4r5<62SUkA%&ZJL<$de?+KxffZ9}TX` zt;0{CKbaTM*fAR&G^uMb=!mz<;|w6&*G>I`1?|DTvwOmaxPlfU5Q+uVfRPQ(TZaP< zK^@SW>OgS)wrg9<` zgDIx(N(Exg5Oi7=OpkJVs)+a7y^V5`i2-NpwE}zsNb`doy)Z=7XbO1Km zZK4#ZM31BN6`2|Wxa!-vhGp%9$E5PmV$oFLxJTL6Tm+~XRAWR*(z8D!bFi6W(ge!Y z#2#>K8l6WOhhY=!fKG79;$0ESp_8ndu}6|HdQ~n0CqxLlsi$#jh2a8Axxb3HXI)0X zpvMNJL4UG;e(`OE4ncJCN)m-5`A${4N&3xqdX1ypd{6Bl8A92CMXh2>NefXvk^jslbLDLESxU#^ zddbI?ZA(VxD8=GfSQ(i-y1+o#oN&P=!lQ&XlzP2beKJt0-f=-62`f4R{_kR*|Mzmw zNEc8FOqfBSJPLTrO9Mn)8Mfv^C(>tn&3yy^;KrZON12oEhKn>B4SL{yXgL4 zKj;vXOa$x?^58nsb-L19Dzq5sdXh8FZkpJ{Iej0hp7Cuvm%YnWR5n~KZ85|J1P5a zrxlJ{`jQ}7{7^udCJ?J3Dh&>0B+*v)kd)PYr16Ejf}xycXcE)s7?de0wGnzaOg(+rqw|Ce<^-!D(i1~@t@OVY(+e=ep=CW+% zV#FT%`I~@DmL%G)11~fI^p+iZ#Ynds8R(WpDfg}|xy5u36mhD|GH!-D$fH0hH4TD3 zI_~`xl-5b$Hmm6r}c%AW^7QC_i$Ki<7 zFQ7s(ZA5G}11qZbu`=jB$1%#vV9eXLtiuiX8ZU;UE;_{LG=5WTvdzs70_45eIb+V> z1g=CZ^DtP}GlP%T@y}1isSL{Coc~%5mC(Rc%SVJL_d2!Vu!s66Y+LZ_Jnx+#BZlKA5VMI4Wr~m|)Hn-0dY32v5;GIVt$J@S>Q_mmc3ytuZcPN2jT!dF6uyVIx+z zI*}lfi6y9-sQOBFr=Ynf(Xo`4B1t;{xm5M_%D3JGyPbcGBN`oU`gw#ozZ=3te45Fr zLKq*NLwOd01WA?w0j%Epvd{Y!LI{+o+8!B$sOT6FL*WsFZs|84ylpj~tiRk?m-{f* zwpJb^k^I%__Rs5^e}^uPUAk6#@(EPTL~T!1}3F(C;!31)a+0)fD_%yv)Osqxzd~Z<;G(+$uPMQ=8Yu;{!GO zLS*l6^fpScz(YBQemMU~?q;O=8gRq`5ki#-Svv^_2U`G0aZumK#eP^HBZ~o*q{;bN zm!9_poCf2DIB%J^nbpnF0lo#}8)8NqQ^J)6n`gXEp&+N&phd-_BwWPRREMtu0RQIx zKz8|s)bY{-s}-7X^b941lpkyn`3w>DXhTZ{+D$(%^%wwmzlgBcZ?II29T@$?#rHvR(h2QbV-r~FYhrD`k&eAB!0J2U2f9neA^RAjBTJ$LX;-s&@ z5OqQvAmf1|UG<%eU~j25Ua@0LroLC3Wzr*vL>YQiM#Ip6XH9{kz3~^Xzj*twxd^Bn zwg1;SB7tx=EwG(mY|i~5kvrbU{0(#xyO!~1bUV9KevPaN?xiAsBB9L-W;}`-c8TbO z-OTUUv3+FiW}~O*@%BNKjz&+VNP7^H4up!Y5 zvJDBDKx9aZ&0l|`^3k9CAyz2rW+2#Ks!tih*ol4&N6O^E2t39WAY9Mdn*hMrpZ+1s z29jXoOc>#!x6j0Y4aIHQ<2W)HTv~g|MPJ1Kh?yYt%1)u)NoI(KmyLT+)6V%pgxB>3e<^RR=Plb6MuOB@EOLf{fh z+@34Jh@vqpqxi3cV^T<~^90cnZYUzx=|fPmtzyC)|Z1z3_b zl*n>MOeesdg%YLk#}rizKUL?>V`1x4&7Tk@91AdPGP0x(JNi8++aiiBLAyoyKYuN& zCs_uCh;W;D876KN422((aie^wkYgIXO1@@lctu!Zpy22FuW4>1t1AqVIwJ!BC?rv2 zd|(0KMpIwc*c2|FuV_?+LJg*tP{DgHM z@QCmNqY;q9ZgYGnBqC0^8)ni;r!%2RcFNy zYuoLWt-o*Jos>6Mld~e5#Vf*OG!7 zdXxt2W%?B9Z)tCDuCC7RDQPxqRxegw{jjt013J?cKbf-J&<<-w8||`g6;FS`of9wC z9&fIo2=vCv_H%C>98xu^lo+Vf2$qX~+ zH8e&>v3E8698eS+eO`4xDixYYQ$I}{pJs9Ef9f=OE;)6YHQG<%Li7XasGySfMKUO| zpRuWCkP-JwY3~uZB638}qcKe2W_q0%xiKZST9STebh5ncOm=qE^=yzLrNGk?gv^C!V_C*v?%J&2g;duIZNB2`672pw7a6?Ed} z7D||PZ;3Yn|DC@zzf_aX0>M%{t%`VYqcmTDa+DgRE?w!a%8S})G@m=wmsyn%;XJf8 z`E{?GxbXof=8Pn|YT}RAa&E!r@6tBJ#V5Y|+YMYDui?iQH~`LAL;yA|`T%bL%q+@4 z8q4bi)M)M>*hRe6`C4(dFekuCR5{b2wW4r%OWFQ4r#+=3ujUxnER_+y6c)J1(si+J zaN1_!OS1e+21YL*KCpK`y^H2Q{_ziC(oy(hpF=&1WN-BFyx4efDnS{MU4OzAzR!?j z0yE({DWobqiZ7lHF9v$+`yL9^4aQ7v3u!7v+i5wdh~L*V)Q`_GWl&X|5(vltDQ&H5 z<(pP7^)+T?eC&4P;qz?Z##}xTYxjxrQ5z^+SywZQw~2r2>VIVd<&@BgA%Wkzo3Fv$ zb&^4$Dv2PYL9_&DFy=LL!TFw^$v})kk|!SM&mxlwEr9ZBY+R=Q1V>@?T$m~#6_5*y zB@-vwH6=BaszvoMOc+&7)xgAy@CEH;N@Q>{uKZ{}TG&gd<2Ni&vD=soQG72q3#?3*QVeT=?H)sYacp91cjICm!>2Yu2csQQ0 zOcPXeZVT5>!F`EmK73W|hfD+aQZ-R;M2?m-z;miPQ)xoE!1KOV^OuFwg>K_{pK3j7 zlopvMi*sQoabo~XbJl_wF9|}7mq20<$0!ET8-M?K7tX;^|BPv_-;2~qSb6g=_r!sF z3pkX=ig41@wdw&Kypum87N@~;U+p(-;o#)`?Zz=( zU@%_)GUmTwW#HXgW*p1xrLYk7=c%J3vl^$)(imf+c%NiU)3LU|0U8M9!0oPbgv6XD zBX@=YjbNGcWQolr?)W1B%Z2?5fXfu^K~JrHC3=8Z)uDPALyN!Yv6R)(P=QSTFdLq) zNGOxz&ICoManPkmq)^=-4Y65-?UE2$0$gsV+5r2#;x&yjY;nv932C#?8Uu3Jm-msoUCRe)iLB1 zxJp$K*php*cvm#+l2;&fvjU8%Y#cV>qtS8=QV*AtMIT zFLj3pO9(OS!~PpDktIey^a47;iN0jj!MA_#rgPLAFP?V)!;EIpSe5h@(aDDTr!8}- zq6T~fBsA|L+)TjOXdU;va0-Eo!uAFeQRb(KIACk2XFipaHbXoJBKvg$N670%#$d;8 zgdTarykJNP$14|So;X~{;PXQIZH97B`n z?Wu}g#|Fd9W+I#8(vvRh7!hdWs(m`9lBB1=Rbyedh189Qg0u6m_Yi8;du)vc%d!h2 zn4siMQIf&iHbJj1`Z&e(!r~OUiEY@3JgNpyL5azp}9$ahzw4`&{>fO1?`!o9Laq4&8 zt41$-Hw^n8L$QHagyM)#I3>BWIvYU1(p*l~ziVq}*LtymPG81Dr&gV*+SR6jDDO(a z#;!8maJj$CH zF_bC#=M}cBFY);fY`Aqy{Ef%M1m&W-nd_sJeChnj-FIkEL6n zTrW4ixT9y`85N{!aSD!Jn~I@U?lu!tQ4RzJFovIc4L`_9!@}0qdfZ4UobVy@{}a4O zGfR#sD=vIM^?s-5viWc?63#mQpBG65BzT4$A_b)(I;>a*hC=M$!i(g`4%GNeoJX^8 zeub_6S>EFalf+^Tj;fW^{|HYN&hVfQ>d|=gTCoECZS|2kxYV~bfU69y5BG2h-Ah5s zgyibX#H;wLw&_!S3##+tO>nYuF*aj56zP(A+ef$^3JT3BWEUmpIx$`0R$f?wz-0~u zycGzZ(prgG&k*Q)E(Lw$)*HV9xOjQ%R^z+IR}`q`X@X9EhN7}OzA#WLf^BobsMu`85Sguz zGd74;*;J2D$%AJ4e8`g->?&e1Z7D{1GQodhPupGuQyXsD^yx|xcBR(3v3UP>S7T~f z{R$ch%MQz$!(**LV0BX4M>wOkS`@XU*?bH~bv#NRFE-Y+z^R-LAXyFGF&-S3f>^e@G(nUEn`R3BEUUuH~PtQ-i2<-5%aWZ6|fQzR+@S`?v@?fA1za%J( z$4!{pY*Kc9;*iV~M2A7aUBbh5Ona3qEG2(5? zm71(JkSL*|x>pI{-5Krgc7Nb;DT>>dh=Gc?@P@~|sB=7B^6eY&!{K0)<1BR4`PLsy z;D?g?eT?Kxeznm6ROJQOJ(AA4mGc%KsrM$!XcDuV_nZZw5eizhH? zRsihAYhG#he~EpSjGk1%aO9eWY{BL47Dd!m7<(dhG#i_!o|e}&s9&EQmr*7yY%Wp z74w6;s4!tV@TGS!Y3haT{qz1w7fFe|(fd}VI*6|jY|Pbo zd>{#+;Ws}QAPglG!*Y~hCe1jEq3_pFOrsc8hXlV#MoPUO zd28tR6bV`w+l*r;$S8OcKN^=`q0x+iUyDd-V#^i~@v2NQIQcSY;NmNA;pq#s>6i+j z5Sb}55lmpx(xrqVB$5cmz*w=QQr!i*8+V2(Y`SzVAucEfdNC)Exe*3F+Wj!@F@sOo zXr|{#8K88cHGCoe;=ed^2()o^{*W;KM15!)S|~bXT*3g>L#CzJlD8XKQHP&ak^X{z zJw~|{pvsi)|8$?oRpwS@a(}|P7UZK`2?LoGBCkH+ex2tG>JJRaQgrIB*NKhvdYy!A zg@c@wm-!hEcz(&;uNBz5F)aa}Q3&oJAfx!ui*vwq$_XZUi3cBWHx4v(ON=WRFs$OM zTyqmjjc{3rJYrRTuR5ZYYXDSAq}k^${waibmQjS?yon_q>j>kO4$HIlUy~A~Ev7&+ zWkoJxnSc0b{-Nh@FCl^Z;UA}!Vx!{j@jaSx9m`kd^(U|An&T(%;n6*sMsn#%K0W~x z;0N~3EBENxKTBlSN#zl}K)bAEzh)_w%_3UK_?xXqSRwKF62)f2X2q}JV zxrBxoqI!f3j>}TshydZf5QPCK0G5L)9OPMn|AjN&Vh9g7kH-@HK9D(Sk=vXkmH}}+ zIn8Em;1;@@r9ACiC!g;a(b)eloex2-dF~r|TmA$ntbslW-wXs0@-G0+EE989HXkm1 zDYN*<9dGdtf`@zf|Ix#}fs_Z+UVwB$9iJer?-QsKd(T0RU^1EdP^(iUfiG}WKf@`} z7GfVresoC(GAx2w?Mx~XiFU^{(^r%RK)m%V#e1RpMp|)2a|1>t-w?e%2$iH^))rd8odE*gN9ptN`580t##=qySK9-)-XX;g zqlL^mfrAJnS>TN)xHRu&`|--t_VbljPhYIk`hta5Jf-7h8%GrsDZTCjJhI17n@)Nx zTu8~(X=vRS`Oe-g(qkPvT3y-tWy%=ID-3jVWzfJm%zGcgS@6+t*jbb&+5{+O;)JV-ysVG6X&+q}gOVUpwiKF(AnOM^!*wu;UPk6_+&HCP%bK~gTL zDDkrnR(OolPywAU?q*or`p$NH{aO3v>dW=bUsCX8F9<;%6#?BK(1)iuZ;J3)d$ql~ z`D*1wdv$YjeKYMNf1}%gS@GyuvUKDsW@K*+Fl8{`lm0&T9a$geM>x8(^DIhw+=2)I z;XLkq{29k3Vh1F2!z6R6QKP8wnsGzdH@4ePcDA z=~nJ#^Y_oUw>MY_WOWno>DU2<$dKM(d+p^apeUXRUg@0@3Zs5Qa?q zpf}2Ak6xdwZpH>@Z^}XczWNJ3CsCapk8t!@^PcrRets$tsNFMyZ4JpZU=VO`PKKz~ z)aEnkioP)nz!jVfhi}f$DzCuB24Fpv=kO&>n9h6H8&%!`T}G3sZ;{+B+^qaeW)GbE zn0sHvTOPcoTr#}Ej1p#fOevqbEtj^52k?5ywr=hwasvI&ozi3?Nwwt#&g~Gm58alE zj_b32?RGP12ag}9$zrbK=lU4+jd6!Lk?$o*X=80;^~Kt&wO2o&(9iP?V&c}?`m6TZ z)5e4E8-KlnTkK)=H15INX)v*!W225Xs^oN0lN1}i*^s&emWRLunM?zAk)(mc}wu(QZ)l_Y)|kCB=wPlY-}Lg zdQO;aJzv>eRT#EktUvjC`{~N|3YNb9_cc1OKJfdeueLt?V=|Gg)h%wFfX6W^ss&hU zSsFV}u8zh`5bpR!q`1lY(tESr zra$DNj_*oUYrrP~ZHQxzHf=KPt@g&|`agbA^lHUnJ{P2}YpM`YWvyfZT`M7wT$PMD z4^Q4AP%V0hM!kQ6QAHb3y{7Ruh%A)XFhQ0Cwee1OxG$|9=`v5|>Ec2V$f&=6J`oVH z&;}9O{Pot!Ze5y~yHvvG){5$`6_RT*(ZmmU>N|mEi|8a?IjW~w^{o_icG_VZMmFS% zt^^<`wBX-YAlV6>n&S9(00{gf1Oy(%rKfunl>+B(097(px*vb$7KN>h(*!toU&Ad> zw{;>I+NZtIQI7|Yav+%Q1;w`sLzhouk&t&F9@!cycK9tpaQs@Hh%Av4h8$!!491<4 z^>7yHaHeuaOsdvrq*g^I4Rx};#rcPtwAvp}}z3d^vY_jaGOlp;`D2)<-ywa2=qKa3ibeZ{vwEz^XoFD?sR-KFDcor&(*JH zHM-3)hFQyMq&I)XrKArUsD{AHd+CF&yij*E5b>k~QG&93vVD8J=5Y^$gwzR61Rdai z0;XcO6VS!Y1^Y&fV6Rvzdf=3kT_;GjzkJWDev6ivpEq%=iCtZ6-fYkSFdL8tDC{5} zgyF=V2+&*%+x(Du#DXBhQ3rv2VmP2uGNK|7Ohd=415X_Fg5R!kM9f!BgNg@4AvOn- zz-lgS{A~Tji}jzOpR7Upf(tJjcY8zPHx(Q*lHeARBcpO_ZELPu#%AC7%^E29Cms zvnqrNyuIl=Z4itpN}dv?&FHqMV%40H%)JcXx@rD>>9$yj3$^O)of`a@p%8;d+eQI zhx{}PP)-!ZBz!g;ZR*{)er_#blE%_%2MOp_C>3}=0~{m=@kG$713-!{6pfW#`n$fl zm^BuQ1N*J*H7;ll9yPi;>F!*GA@Qhjf)gh|6aHp_gT|{9yzXzdG2rxs$^@T@SAHBr z5ja6%>Ax<1xp>E-o@8#!A;Hb(hQYgnL13HaNK_=7x_=pX+U9AD2vO;bJbL!1;-b9f zsN%z{8`V1FsEqLf%#NUd(ddt&ppCohTe3iC11wts1qtvp`3)<&cXGJAJm=m-?6~^| zcoed$%0%)Bjax=|BI93Z8h^l&Qkz_#n0)p5yQq%p>-ZZl%_X64+&{s`w7C7LSl=2XDoDHG zbd0ILdoM%@s>MkqvfOq+olUFSBd;5+zl`T;sm%2cUk)L_`@rw-wH%FKCMcyI`lm>u zMTwc_Z5UJ$r%M?R-LBa`TsXC^aEkf~K>*C!M>80@2H9XqtN~nDoM+0jxeLtSbsNo+3384HRnl72!}D8Rl0J z5$ny)<&8E^fQ*~HF|=L9c=g;us+!T|wd)dBFTfZvNXk5Um{|q$MFA^aSW?f)nK2v` zn0hylU@#G<(-fLQ6L_y};^7H+5~goRY8oM4Mp+PAn$oSS(}FrP7l;pw&aDU0{aSN% z?}-vf)g`*0k6!(CMm@3$7FU=^>b&$nj%i2W=`N(asZEB96_re zCKc=jf#z_U4Ye5`Yy9%%_{%T~?>EO>?TT2-?OM=eLt>GgS?CFL{kJ;L?2KBpUxY54 zK)G+-{mPWsi(?wxa@~zfmt+V^6ktiGTQiFKp(0ijmBH_%7Z@;djx4@tU>RsOf345t zbsukPsF7=S%PK;Bcdl>ZHO);6eVs6@cqx3cxfWBcwe5Z>bjmuCa8%-0?hp|R zGM1k;!|l*V0$mIL&8KVibD*^-u9DUWjJ#Fw8Rw5FHN>0YE0Z6Ykk=LiI>BY_Xfepu zfa`LUxThFi`ZO+(y+~unHA)B*^Ctz!IFM0=C*-|GP**t`n=^ zCEB`T{$;pB#^8-b7-?HTQU5^ATWhMj{F!wDTf#kXg&zP;MLLP>5K9L%0Mg;ecP;H) zxJbY-%fH3VPhHs6+((&#afurNWrd5m7DPkd40=mc0bAqpE^8&?x%tEDc2lnWYTbw> z5@cAI`+sfRZruGE|N7fK{OjumWDi--{0;h<4LlU`Uw+k~m{%_y;pz6ctQ9JStLo6a z0d;sxQF~BM)aQ9S7_BmyZB!HJpKu@hYou@(MoZ+2c;3@X#hCS!>J6LB?IAURV2rO% z>-kpWE*yLFb?Sx82po}VnTV-ubYa%P&LEyPWL@wE+78XsG;HcvH_yO-9Pa;&0*X(2 zCmq-p7z5%wjzkxWFI#kEc{OA5^u8RV>`D|e*+1Fed*8^;)EyDShbNGCR<&C>^EVA` zQQE;&(`XBmVXC)fkASh6WGeS1l(t-96&mv+Np=pyoEWpi+%ZYoXKMADW|2cR>Y>a4 zK#wT!5LquZZ~2+`gzZKX2I%ku1h?Sak00=fg=|}fHsTZx`|qG9OWE9M7ruUfm;Ie~ zP|e|ql@^u$5bQHNJmh^dREB#+HBM**vyIjaeUya2*;P{dp(atcxww;SyK$QoCsTK{ zvXP8KCQOCw!FbBIP*E@X1BhHftcuc=r0gK$N(_dZdn23%0G1%TQ|5i)z=ZB$^eD-0 zxYMVuFRB)^6+E>a(n-@?C#&Wih4k1YM?m1QAm>G z3oLUj)U2!oQV8*&2OipMww;CYoAx_>--?fL=#xrS+{*m~__pa~B~;;vtVwW@CP|zf zei$6_moE)}I&=UQV~Hhy*oA%*`$4Q*b=L$uN&qvNZg8+6#5aV<2KXkj6R+C@cYBN& zT8xOZA5|r0K~|V-+A(DwKNF_tYsPR_13Q3|bO!jC+bnd%?p3%#RCEuMH?!QMIh3c+x%X~do2B1>fe-FgXxn@C~$}^on#W%DS^kVpJvHy(xqC%B5 zdakDdo}j!S+0u4Xcbdt~iZbH%eGZ-)wtBR@3`vV?!A$gn<*Owjkw`+4=EVai$coZr zlX#4i+fbJ#ZkgF{)?<3RWe+!eLX&$XaqX@wd6(!w$PmBLgQ`vMsjCHd65g1`<=;l` zK}9Q%*)SbHef+wjT~%b3a$*5^ELV8#xh#!w zqjKmEN>`AnHS7W5zSI&-{@qOYcq3IqeL9ibPoFUK=n_~^h3dfN_?d(2tH1q?Gvki_ z$#zb(a^eiI-uw4hcKhB}w;NyGxx;_mdF7LkFYCGnLBYp5ZzNb|47F(6Vk zp^5ZNHMBJiH}W{u(sF7Dt-*t23WCft6~B?3I9Ttw&WA4rJ+;?8tlMc`z9z;h^ znVdtzk7&o%>SdRi(G1X^R4n5c^dsh8bAZ5Sf~FsJ!X}7aj^&+0f2EKKgUK{!G)|hD zld0~=0gIWgFstXcc2?6rqC>~$yxVk5(=Ez2l-eUiQs%Op$7Hf+`Fd_kc=RVxZJ($b zy`aNe7$69u%leCm_7w&83e%2??P6oav}yS^pZP4P#xCGQjNGJxWLE_-b@;Z!`Cks$ zph&9CkZgT1FIjeJGJFKosDgw5iIBSFUR(372hB<7QuyxehO?0o1sb{P23CFam=c=yeOie(Nci;(y^Q3FUB~ z4as5XG4zvngan+=*^UMs2@>UX#tdhI z5?y7Vx~7Mik$Oba6bY)s$}x2nnW>J$4kV&V$5dH z?cJAVy0b$;&%XBill^msHuKJ~<_&C#$D^h2?Tp8CI5Wjvxdp%Wt7jv2 z-)=12H#f`TfA-I03%(c9f11D1&#BTi<#RcxQ$`qWG5+mW-p#gjJUKnt+n0Mj z>UODBxPEx-_FE0mnpma61T?2tl$2$AS@Rir!Jc9b^pjhsG=o7lI=YY(15JrQ0Du3^ zeS#~}i~!8{2H2!54{ktY%S#ubN?N!p+D4O4apU&^diO_PL+87W~_5Q=M$mZtzJvAMC0m&OMb z8G$krODn4a9-9%d0id@SY_Kcncao^NF1L=jhgE3+UxCYzj~Z!Y`E@!9fT3;y-*1aD zNnT|K;-U7{w<3>rOU8_c;IHY@mRq+ixCq`k{KLc!)y@QW z<7|sB?~xEaEGhXi05U-#f-!(7_6Vr!`YY&{IjPcBZ+cit;KVWIR8Ay4eQ3GH$6^Y_ z$Ks?_#0O$|m}X$NppVIrhdiU?QcD0rLTQBEy6s{IF?L)4Ilqhh6~lc5OLAHOx-Fl5 z+c-cj3w%M}pHB`KzCrB>4I2^On@kM$1OpRR*Iz8U{L7}{2L|xR-ou_R^7L5_ylPtU zT6i>KoT^;%&hS*-I%7D@qON+hVvd-QzR2>+R8onlr#$MCf{{;f&pv|Jkn?Is*{M^do9r#AS6h!1grJy@D=M$Euf)tK5lQx~nLHk5}&N#SC zz`!zbEm6{R$5I!s2F#|lCk^wdc|x=!6+i565af#E<0MU==ontpC(E%|$OCQ$GkZR~q@(d}GP5ZQ-XnlCEK@=1PN#LGgl!W-4v6r$8Ou&tsyhC>KX*UH> z)qUAu#(#J;!hwd4rWx3IQT~G|r!?Nk{g*wX(6M z_SC^izsb01)xv}Oc#7$#?^ zLnNJ`b|Y*-<_Fd>c4ANI))Ym)D21u8ybuuF#$K;}#Ix){}A1H>;|q`Rr| zeTo~YXp6hi~()Zqe~IxF^o`cOjk7b#~eP z9q76VLdr9sOyV{S&B#;ePk#3v9|g3+IS%q=HmHM0%8a&%xzyPglM$lOHKAQmkeQ>v zKUo_8#?I_ECkHH0z~@LLw5OpP(acSuj^Vg6EqK}VltpI9>5>Rcjz|lwd}{Jq3aH`> zEs*MW`{eD=Nwp6agv$Cr8b2JiP)lmNvgX7LA95eXq`to2*7E&Ydc1yJ$}weOH#a0l ziPkJ^jQ^C#gyqKL&KTthev^*>xGC7@tk8g@7zto_{zQo(P#!Hc<{4Pf&Zr+M&Km>A zc01Gm+|!wWG>KKGVbQ{w63j_IC!Z?3?cP<~Mf;%5`ylTyE6e4J>U#{w$Rm;)}bNQE+Gz<)YG5S>K)f z6*mc!wU;Ui!sOi=DyN>X;*zElBWUpU71?=mq{2{GglR%Bpy$Elu+=>6oRE$YX!e(J z1JafLY%p>85Hd+<0_~H%l}BVAPG+9tVf1s1QvsuqSz4Y>MfU0%yhQ@z!@xDOiU%qi z>zjHEbJbb+{`dkT`Ao;5k_Q?dNf%xK4at2c=iMI3H`@yz2jI;)m0n8%7{bm<%t+k~ zgic}ikwS^rUg}x11O@-*lF_J+=RLCmvGcDmJb0LQt?w^oed(vl{=5U_c!Zojp}ZiE ze58)c?4f_72va{OLWAq(TMR0#FsO)VQX!xwl?r^KCTJ*8)KM&9z1eJ<%irALBrog3 zN*+koC5gW1)w1+ipGjM;EXOWQ z9a4Lotj#78S{UyHVc;*s+{7k6t>EfRZJ4A;VOR=2HM=D&1dotyEwYI2R1S`mWYKCQ z^;jQ3S#ygAsEOGAv5UT>%k+gL?y_fhAjZylWa66=dcm;gOLe9H26QJxj;kK}DRXU=&|-6u{Rz3EhPK zbBX>e)?aY(7&Q`fzp3V_K*$zpfD`$cJ1FYhSvzv6B2!mLhqyVBhz>67nS`)@^8f_7r*48>R@!e2|rm9Z%(BTvNL2Qv7Jf+HHZU_WM@Hs^Od2Y zE8@Y4D4rbcAYlKlEkz;?h!_Slqd*4Ai6(}5s0mp+z1};KhEm5jSVMqRFs}rbx)vLt zIs>fT2m_$7R!&$x%OvvW+ZzHPwb>ETUr~FLsYCy2(0*Qp>Re`-OX!7lzNhF{Tt#lyWv9GA$)^`U@ z#Fi~=m{&(1_i=}UAi9rL;im-~3WkFwknUb?<6?KSopS;YY%K&@ZX%%#X>w(}Wjalw zir(KE;ugS`b-l6#a5yd3OBO2#PChOZ5J}J1QfkRDpf{0;r0Y=9l|2kS=&oJfg|-W{ z7(2$tj2aHn23;di*WhLmkEG_{m)5TQDoI8eYLyOQ;AUU=OPh&e1r8TUe2BQim zi5&vkqyqc&tV^f2U_t$5{Jn+&IZurf67Ugn6{tDStQAkSgk5YKJ?#p%{Kdip1W_WL zF5$(>mcDxk7a=OgLY*7+#Lj1NY1E1ue}5PAO#TtW7UeHvN~WrW&p`}mm}#E1x?;-p zfB<3ja0VlVD!8N2m&0Ywix#EN0%ru$q|XH-1geFDI91;quxRWajAHm`D5nD@Y9Br= z5inl>-{wyO2BuxIS^7IP@;h}fU;(gV$zX^D`hxV_$(*zDa;DMAQ0?=X3kZO| zWwOB+f!X5FFC}oOZ5lV_hoDZ-NiV{H6bca@qU(To0Fls#)0Ke?EXBA(+L-{vm#3f3 zgb|mKPT4=zuCdY6zxGXIYc|pY#WI_j zbc`Zf4ct${8b(_9!}?HKv9cbD4gLmHYkvlUm7~pA>qH>qazajprJi?yki8;!i1P|2 zkX;QCZ^f();3-^lSaOb}x(~Cy4q8pB&duRo4wO`I%Lx?79Mhn?+g!zssehAD3G7M$ z98Zdt_O@=JkmD_lfU^(y1|Sfeqg)6-`ktx17b zW=a$!_%-jej4b_fhu@|0Vb4WDAy`DWqX60Cv3ne{Xy$&y;_*i3;0^9T+WSdwJ@Ef- z5VdhJQU-vn-YKet?g0+Kj}EwB@@LUB7+h!G;I*&T zpr4Koq57IQGg+BSQBbVwto&|+T!wd|_m_rWn0j$b7GksH<|$ggp;>gZ(Bl-J zqv#83to^9ply_s<F?2D=JV}$e?X0JsKRL*yF8|wK9f=hE93Wr1GEnD z=hSRUxsqxh$*sDuka5p~Qk`h<07Nhn(gpho&K0k)asKL)#`IdW<>r$D4p7(`hS8i> z5QnM={t??f9TrhmvQ*Kdl`Y4aZi&5%1<|^)JKrSC6_@Tj)$-y<6}9%@dwseGsQ(+s zk39*$_LF-27kf5FShvfe*oFOteyk70CoGvJkzy7Xq0+WLCJR{=uW45c)B0!B&eVIH zbD5TGl68hT$Dt!wbQRVU(8)U5#Ki`a9#!S>`!Q}HxMEiVbcE>*&T(BuA0_=zl5K5pi-z8(Jk8T7C{MvwxnWB0Dp-5Rvl(q?qlg>pkJf#Tc9oYL=7h1*r~_ z7_RO&NKT+YF;|{culVJ6I#{Qvd5USBD`nmGXu+|(i4k14dGwiPm|^w;n* zP7y>@(^*(BSHDB?4A%35D+8Bq!d{+85v))|aPP%%cotjYpcePg&`|IShq%TR`r|RI z8%e=~&)lyfs`q}d<(P@kxcq&2>GiGoW&H7oe>}45;5{o)KpgWz6B|?63UiEs&I|{N z7fF~LGr)saIG)u;v=X5=2(D&p%UBBk3uZpvE}6q5uq~TYvvGHE)_nYl;fk3IJ$oH4Sz>GkAAsbVUY8sN9 zv2&se4@59)?-4yz&8keuzqTM;l#})8iz?;56vCTbgDJo0j6*RQv`Mus9$<4LgMYw~ z)M%cu0@@9oK3ma&>B0WYSPqV#sZ+HI;4(5XU`o9m^Ff7qi?K79cwNjAGGZ&`%lEkm zO8u)>&*{q+7~bZq6x0R^!P=4|C!<#UB4UAR4{%j=ErF)=2ynB5h}sdsB3i`W$F%T9nJ^dM7PCPX!JoLYXCJ|Vn;Mv6 z_#4}C*k~Nhb||%)?qi~)VaWGRewJNQGKPmlWQ-;+h2{=+#fIXRno15-B#|M)fOn?{ zZad;m+NP5xV{88tr@Rmz;htN#^MO}lC8Y>)oxKd6r^tb|Lk+F>?m2nI&eP85&5y(V zr^A8XcIp~kZ21ScP`o@3<*z8uac3R?6>;>cyRKd@pIqLZcJBJp3 z5R^K1%mB$`vSGv*N-UWcx)DUyR;i0CEft7Wj|H^a474Q$rwzNzZiPN+5Rrkfqqd9U zSPo9H0HDwdyr20U6`ZF`Fr7PYsIUMJSw*045?zh%ms1*mm48)y6GW8j5z*Io>lR?d ztL9O%H>EveYe0BFx%R!vbG{C&o}5@F628U`X@+L z9z4RWWHUkhCF;FLbK<{pjzz+U>Z?*n_5@Wr7?B(zbr`2(YY!_4>O@U<@TpX8jIq-9 z2ECRBoGH~79vO&g)Wp;}1vtFNCPYn24hO-7>f#(<^(Gg?(VK?D1L~);(!kOf^jI)L z>=Q))ebk!7rd5j4*>m0Ikb@eymd_c zp_`6BFjf9k!xVp&I}!#=0Va(;<-ptyY)D0Z$dKi95({&Fp@rxH4Hpbx*Q;7zjk!``NNajw+Ki5Id94lqB@h9NBO-9sl(MKN3BGXptY8Dc!uTquME z?b3N76^FCaUG33nq2dHeJ%K8p1*;iIuI$!ap&p}DE>)EXsaz0OXN9X!oRz5LHf`yWmH~yPuUD*lQApV_rZQvg4n#S4=)6Ty zT5;68=iL&#)f`2Ta4Im53xY?;EK{(Ez8l$24jKqV8yZr*1`iaa(UGfY(wpyoU`WLH z^aALjI(Owj(f#ErRJzFcHZ_+n%7c?$XJ8RJn0MJX4x7TQ(mb96#JKaH;qQe9eH`K) z;F4qn`rFwV8l#i4SYLDT;CR%!_gB>9+uPgS{dTYW`is*)&Y@~FsczVH(Q(yyfJFlg zkwSNy7kYUXKVlp)6l~lWBKLm!r0m83oHw5LMGhBSs2;A$G9 zQe()Ywci>TfK^eA`AOU8GiH?V9MEC|xa-+BxICRf1@H)13y7WRV@hAA(8a`{=3w!| zR`dJs38|v{0YeW@P~5ZSu?}*$YLmp4V1l|^ah3}Y#AnyTTn_AFlM0l`CyR=M&F`2) zf~efZ=7Z+q`GMd(PG9i^yQ-mUwF;MARFa}u3WeAB`n#p1ZzZFejb}we*?FcQaqAH} zSG)6guB44c$BoZnH*GtkM9__5UozMjw~cT#Suwu$iZy6(yMa^F1;#307a7>;F7Kgr zy!$u8fj=1obm>TH9wQb9thf#fL=uV)Wtv#YoFM+fLGg3Qzj14C`*<|G5H^h}G6;f` z!wr{$S1E99#&L)#BmLaS>?KLyEb=vgnvfQ76EcpI`5w4t_8c;SS7lx0V7b9^lc$v0Dch1`H)BuoY6i&{3Xh#;uJ@&O2!F`U}pdDeOs>T;+czl`P$M5K=E^ma1l@gcqxqfm z{D5UebsB_z&>WcjwhvTzkK-0E;`|;*IL94;7k(|Q9N{3iGq~G$`f3XrjsSkt8qa^X zq#ZZq*a+P4V~0d$L%22yY3?6BgMgMaPs)s!EO9Fk=b+1r5!I$ef(v<$25?L&C$DsC4^lg8 z3Xdovd;&DAhdrrZME(mD?sTEZ$Ze{~#)b{F=XPs;!=IF8!lT~76`C>COm)Lg5Q9&( z5xz>7_Iv;6_V&+@EX6qgMeRxd(XjfpLBA{w5BCMf~v|%l2G5|fW9v}A)5tActRB3=o zh)Vr-o?X07J3}++$5z!`X&?h>ksp(P;_4$*duOas?_Ga9S#0pw_o5GVU&t0Gw#q7T zk2sW}NHpn8v+nWz2)A#Xo}zT>g4`U3$|F1-)=SC-o6az6aMCO>wZoI%p5o^oK9ieXA9pMqHy@zdE74q$oQLZ`slXZ~0n(30 z7~LD+KsX#glCFY^&mnx$S744T9cBYnLr-b;t(o6RfC?MlzZ5~ORQSJ}N-{w8Gc4Nu8fo(DrZ>Ix9$ z2LLMG$>8q9iNWBYbAEJ8|D;Ov8#v2`uZOtLzV;vpB`sSzp0`uA8zrN5FC=*4AGfP- zd9#ZhZTWzJ0dp>B$oM1fx7fyDqWEVn#cqRlctqEhJu8T0H-0C~zSj~?v|$?`Q9!~wUUuGXO?qdrWEvPHu0r9f zs%r8SFx`iN@qVEwe6w@FJfTI%LS7 zN)vd5gT$kw9_~$*3$AhJv-+|$Mf+uM(xFG?CHCg94-XgApKcGfEbrFAEf1MiTQ=S+ zAJ`S?Ho_7vzLw4x5W;S~!{dhY&P7@@#Tg`v{3m;)$h zs_F@Rah1U<53t{#PMHc|E3%`;lkO9c9TQW+Ua@W*Sd`?* zwQKTIF=0xw+$K0;(&9!Sb4^mQXtP12e+#5g{)_cTf3PiJZ;qE>ZAiyw6uyv z%MeOB<~I=5CQ0J^aWghRowU$G6xIM)Zm+C)k@^g~L$z_Kv8lgcUD41-^%ww#*o`Lm z5BOn{J|R@_91S+`7PsUXj*=+}#-TN9=z45&|Lh5CUK6{tF*@v1@93nv8$p}v$-KTi z`_!gnJp`ht?!6*cJW`A`DSXC!qAW>oC&&orvpI5?j2<@epo*=s3a6^L3;_kZz!{US z7PMz*2P8|MR@t9y#Q0^e#3wfvb+7Aw-Ia7hb{Xkw?*j}a}`N3p;GCO`^F*>0eOi$+^O=WnX zHE3JbuAlJ;UmJ1LMF;TylIO|P*-Scd9Fpepgp6|vjwtXVDge|B%t^6Buat_M6?gTJ z`p*X_*ms1SE`3a3O=;u%2^bOjMKc}GdLl#wuXLsOlAuTqearexm{9P= z3zNaQpT-)kZLp47K2Z%YO$Km0yY2}5F6%z7u`&igblo*5Od7g`3_!pd2qJJay3X!? z3-*FIkGp^UwsGs$T%Y3*f~dRHoA6@G2C#}#<=^uXq=665DsT8*NtjXexG)mI6Ff4h z+}R|vn8kf)%RADUKvJ(D&g0x!s}Ul=EtQufhH=U@)8`%zA?RNB0>DvA{a?i1lRGmE zkiqpoWqS7bQl9S{Q`e1CyQu_4MXgjAxkjL}F6;%uRh40TCK4PO!wV>YhkZtA385sM zK-&h`63c`T9M~|D>`Pt!-084O4ZrJsL2L?wVmfjxr_;e*go>Ox9*{Bansi8k+)V$x6dXlX<|5_OBS&! zQrRWa*_q&-O|^gavU9dQ{5n}(GQC}JNw)W`NC>NPY$p?a38tzX&+$swAEA#a>k7c= zg!y9DENG=W1s6K4xWkj-5W8#pwuJrShIw;*&)j*O@U|op=c}&}2x~aW4s<(2dl>aj zQRt#8K`7qcUv&GmH&jh#A9vmz9yWe$=zkvoQNo83#f!}~O6J&Soi4N1$IuG|x`>}* z7ZP;s>V|UZ2-c*zPR}-)x=4jF0lgjDe78qb>(+pI0g(2mVPJv?hF%d5ylP2_WI+DE7kHiJ*0G02QfVTiN8R9lec8HxZ970o~h z!L}SV=ow1qTt67;ve7l0Yy{^Uyx$*mMu@%%`Lnu2XhdUsK zyC}T9NIFYj+!H#v$6eFGh#}SsE_~8w>59@vSVd(GK{)#wR9?vn)HaKf6wnhpkJPV* z7UreH<~mG}Pl?D~OS1|;%-lrI2)yA5DoVL!e$!Zp_GJ;A>u)Qh(sX!^;chg`BkcODZ1#6kbp3V zD|l{1%LMRTum-%s_XF?_+>g&N6D52Bav-@1xur96Oa`qP!NKwEe|6r0&Me}`6G$+R zqGCIK;+wWGcBLqGfrp)U*~88|keE=JPg{|J8e^)d%2XZ@%m&!T()>Q8mlkR29y zIWIcSPkNJ{{a%X&vuc}Ho7ss#cyTxczcqhxdGHF?=uiFL#TMc$4thJtS`S}YnJ(cX!CF4O`~*W)w88RN3r zH-9n?Zs=Su!@bSn5VuoF6}qHpj(=O&;NA!r82*`#iZ8)Oo@mU9hkko#f{wzf0uRd( zqc)+Q6U0HDAHWZ45JWgbiS>vWM^n~&JbQCQUN878&qV&Y3F#sLtpwK;?cd-oXC@3B zc2JY|F*wfw&S+DKkv9KKhpPF4G+`0b5GJQ^(P7Rq2f<+n0CW7&VfhSU?6qV1l%*p~ zS_!~lKLzq*Y>@$VymE4O+}ZCTEd#ED_pK`U?1(eBZU2B$`$IUD(g|EXBpRyR1|soS}XLY1L}rxAK)t@GM^(~ z;mCkx55(snmuhH(-Wtx;M}A>Ul|%}@i}lrftfv=oX4V9dP(P2&hiG9l^A?a1_&A+oSVlTd+{p5^ARttP zk?g7V-dKP-%D9vY{BWGhrn+UG!W97OBVzjtXvCRgvd(ODF!Y6Iwl3V5;)F4qWK2&6 zr5i%Y^tt&Qst(B#7}hLH&<69?ImKxR((Xo^Qpyj*kkNLkI_lqeD3j7>@~5h^#`4OZ zYvhZuP4GMWi_z;)EnU=fkOd~J1{pxqK>kpP-bu=tLz z{)9l2we`IuW3Z@dTSTE$V|O<+@Q#6t48Q}ZE}ek6H>24ISwf@GJlH0yr?d7(YAdKy69^0di&R_@>-ufLH0e zzTB&vjKvUktuD%zXgec(2R_@gAySB>%&c3LYL13q*ej?Xd3Yrlj6U#Fp8oh}o&4=a zf=e)d!|Nnw@dh!Y@2)9kwMXyG{+50hPo5(D~uoMOynzvedR-z zq4AZ*Py4*K4RaKli=ZlHs%1tcU66I*n#v=<0TWGrT>`8UHgN4B)g>z8Qgg>>CY>_% z6d6d?&R-7xSpPMEvqja{3+YZ#UFDk2t=h)>I# zApl&W>v6_*cE7Q3CdG-0zL%GKFrFRS64p~-V)I%$Q@2E(RX4Klx&j@kw~Zf zu#Ky-!Pwd+V?=c83}(&o${h-vrpyFEm&0&#rIVd-ID#P^!dlK))xH>U=6o;^p50fj zSoY>^XLNUVpSo4q9l3qknYTn|9%B=R_x8S{la2d0OYJiywAI|W&tOnZ$C?75v-`X% zl(M9piOZPkY~mMO9+0$Tx)DPK)GLFoLMdKhBhXHK<_fQf!^v!=sUGiDHc;LbFt)A| zEK4^@-`cz*6HKBpE~AO$c+xa0*g*7c61`!ME;eqqZ{pt)Sgu4|!bh^h&(ER^PGYH1 zp@76fsE0k(hGJ(;aOj42O>4E#oy}#m8D#*FZR5Ungzv?ziKixO)6sPOXscov$pCtg znRF0kigCQq*7{=u9hw?O+B@)5C&3r8B1-TrsSlpDdlm$H(Q4rVj$U!!0Nfw~DR!h_ z=5|)&wx&(dOU@XWZRV>9Qqo{FK!=N)`t-P$7SL?>15J&;8Kz&%&F3W-R3 z&#QQx8JraJ1!J~ElcOeu`QcNZ-JRf)1QeOl%i86}_D6b!I}di+a=JqeKcJHK0Z&xa zC?Zy%F1SXqN7l_FHT!xhvqm+EwzMl|Nh%9QihRRbLpU{i262tJOBXG$12w_!-`;@-4oYT zLmc|i>;?bahqO6I!Px!YNzh}*jvV*{UtN>>a&_)`j<+O0JNHIV$u4N5)&(79v{CNr7MU zd|B>B*EMjk>^*0p?a^z!%Eul4L}Rcx4wQgabCt)8jg9*a&QT7i6Ou{{UZE)yh9oA~ zLx3?p+<`v&eMUj-3Cj!glYE7cMexsAUpY{w$#~hMdbJ>j8vrE+E_JvJhpw|epr&og z^!dQ%uuB>-BH~nZ6HC2;tyV^(4vU<4;3l%@+pFs@Ak4K35OH+#hW6&N2bTp4K6nvGe!B-|zD{mN}#VW)A% zFE`>jV@dkWc}wU=K_=f(iaCAD18#NXs4;pt#ixVgVPj6PkL&$pyA>v)8zV?ps(r>8 z+^EVK6_L!24V(#h!|eF3e94iZgQ)#BgC%UNY;FDg)VHd_6ubwXr772`>C|`tY`N$C zZf|8Uz5tIihTt8bEDgAWS^Yee8HsfgITeXrOr$2Vhp>eSSMvpP8Qx#A$MMlLSyS3A zPSS<|?z_XRRR6|6JZ%BBYfH*ZDBWuH;3b(BDLOtwoL_U{eCm?eTG&MxLWr>V{-F5Y zjRDFy_P`wtvyc(hkraWbN~N~PzugE)f#n_4qGErHs)t9t?%E(x+`N|*i%N(#Pf#s! zfczbp6{;`*Jg6End4j<5_~CTsC(!W^1UM4wrT(ti0d#3P*X4+Dm62-NK^> z@qKFMD54pYa}Jj3IsS~BU+oBc@6_TA(~phum=T@UF5e3Uy2J_S%!lPw4iQGy#qka@ z2q(ute{Nl}e4&OU7B=a~i&6rwMh$W<`#LB9VYQI~hJf*s5upqeOK3UTMI{AT2W5X^ z)Dh_*AYO?MH-Zy_IW$}kU>R)En~j^d8#nj%_@B^4p%0?o*Hk84I~@U1Mfu|uB1FN* zINORYCXE%^>>c)yi+(^lcXbRQt#4t<0pX%Q8S2233zT*I_Qt_+w~x?rzTFGhXeYC2H1hctRybJVaSqzRj%TGL!6(3kdLxNoz5)~({88@122fRG>lMCV*Gk)f!(fOA z9@%F6d2lk^-zSe5azfB%P_$`)Qg?3KTfjS5a0jG@pt6-n@u6ohZ;^JbXkvSj8n#9wYv`BzJ9$M>#mt6&C3SQFG zBs~aEQ*Ku9XD~j_8!ZlpXRuWhk1N>01zDU{i8o-(Vk!hmU1fEu0f#_5-n${(2Ys@r zrB&%EUMVxH^R_{DA?fC`ueKrgmsM{e9Xss{H$ckjuLs4_Y>P0vszW+{5&Xo2zswW#FPr3^q}W#SmDPlkXbbh;^Ft6) zU2|&-s9tFw+0*z6`n~$&l(O;T5ev@Hi2^^SgVLvBoeTb|Uk@$alL@x4Ye-CpXdL)Q{kepm+c3g9iQfh=$9)7&_)AQgk;uxf>NHD)Ddk@v_*qLs zT3ASuA`aTwvePomG&n)8f@RoeoWcRk7ERb!#MkuGDyWlk&(KG6kx%5327)~9OYD4( z4b)Z)TP@$tTJj`B^I0`?1Dmoj>K!dF`{8$UY4_Kah5xU!@Sk@U{?>RcXW+yy`+oO6 zqgrcrO1qOC@}}aS7BspN^(&chUt9~TY>a_D*?j63RPvFS8l7k#%@eSKE1VZddBZ1= zPko6m@sFfnSn>`%Rj8FU5cNw!GyZyYeu}oq1AM$oI677DBFPTb>0mN4Pvdfe0eBt( zph=GyLgR$%NqM_-l#}sQND2jedna zcnW7VT9OfWCe&ou8XX)e^`hJhK4}n3otX?y^hCTb%u@k7la8*bZaK$YIK4Yt>5?I@ragO~LB}`# zv1#gaiW1fq08k(rS|lg+L7%qWt1wdQBGG$%pH=$Ez7?TO+%)0ZFn43@G7bLV-j0UM zWEdVoxe2?;);ej+P>Z4|K5orroti+Fhp|yz@?+TR$uMo`wrY-bC@&nR=(c|L9rRT8 zSYr<=!VsBE`OPNmn2F*mIB1C&S2T*p%Rw4nQQ@a%4Rd@p^2y+lt!9A7yqC%4H;-6rCem6K-QO!)yKzZMH4|#I{TmvV}jRjit4<>D1)HMEH>OGL4 z^dEW!$H)~+Zj{)W^m(^4PS)Zr6G;+~erMS|C!Go1C72HwtCr-k+zBLi{M3hIyL0k* z`0j^uT<=0q0l4V8+SGDKwooBLVEt@3dVveO{8~qRBmbE5txV4#DdC`s@ped+Wx27{ z(=)vdM_iQYarVIl-4OT!f6%3;t``vzy6t!_JFN|2Wt`BGZb@NJY^5McwK-8I89Jd- z2F>ET#F;g#-e4E?3q~l!(Bnl94Xak&U_C|pXK>7*!Irc`6THG)<@Y5(S{=L%;!WJa zhVvbU)wI*Pd3+1G+(u~9Ppg}$Z^Sx4Y?i&Wj7N@7AWZLT_0>;PI>MWz7bj}$Q4bSV z$F(nS3I-rIq+DF&{u&$;&pJKGCN9OIWzb6)kz7!tvh;H>!+gQOqPcdQIe%I=#FF4B zE^(Fdz@u`mU_|DZPp5!Aw%XJf_Qx-g z4+(7lk6-`y?%v+g>;Dk91luPl9m+QFkR7MSij#5_*2owL_sg~v&BI;`=*+2=3uOJ9UIGxrhE@Xkgas$@WY7`B$#ipQw|zq0LssJro);( z8cUC~G`Zg=S!s#`(oO~@_9#XaV7;{097YMO^UkYxFu`Q*zvC@u=e&%RTR5Z5*@d)X z_t2CMnei9Cmv^o36AdK|z*&&A-)-`ASNH4MTpUt#RAJlGi8Vm5A_=LiSU@=e9@G?t zd`zXRe8_cQ+6XX_-Ew(Y+0SHiuF)AdAt!s|GbAE#yIM`2nYQ=gB_&MfZ%B0;#K)j` zzhq}E@x*P(ihJn$h(+XA*s4~%HIAh=T1{$W*_C(T3eBP?Jdvv{#=}G2$%k=SW)m0F zY3?K3PhFnrBP`}E5bDM?m+O&5Q6ygqB&XRa0xcjyvhT($_PVGbii^>Z%ZBjRpVwYJ zUH^Hj4Kcm7w*IR9cxUZ}gf-5TLI5QLtyKa@k_6C8k6w*=FrfS8{+5UcV25gM8Rn%e zgt+oAsuX#;&kP8Rt0Wt6;*@24an?o}(}?%5nTo?9%aIHap+B6GPe$0`Z}$D7 zH7DRb9Z(uvCMf&k?a6ysd(;oP+??R#d18ZUyRa2o+fUbbwz05>4gC}KhBQE+2~`}I zFQS4UG8BlJ5+fkX;!x@FgRRRb(GDxw5_P&nui}PHa9IeXp|M;SMK8C0+1g%x$q66` zhK|(T=F7ELKm7dVJ&q}Vetn;5P!iyo;J4#0m=cHbtiM1R7?PJn_Q$xX;eA#*CwoJ{ z8H8)QQ6B5PkY%S&hc_!d}845z?$zEGvZrW7Ay!7=!8MRdBF)5L8Cr}Yq&3!iX6$sn&yv=f`AH|j<4 zZ}A*dhA_Xpo2Uiw15pDwa}O(#Z!k{!+e?I~!uYmxgluR?7eM8brD1RCjxL&lHX5n@7)y@mG{S6zMOWoevC9(r~{C~QT3-Vz0 zwoqQ7b7KFoOGLH)%k(-gAqv|ozGx@CZg_E755`h2YZm(3D?KLq)>d1*^}#Sj7Wv1fiAwh!@5{W6BNlin;>U5 zBp0~OAA3o|@6m;1d?#KmaJk4}02)c|zVo%hu6<{Z?{aeCdB6KZ zkKXC*p}6u^7|M<}VXnnNKzh~Y!=k(jJ`?GSO*MaMS@4B;fN2zcS|mymD){*vU<$p! zdQ8eRsGm@VaTVGXp!VSW6p9b>m_^>jQ9N}NN7b_@=_sH3W4Wo*P&a>=)* zi_Ki0=)2}LzkEf}Nuio@<_)-PJ)(7rME}55IHtIq#~r;@*o9s=PqlH`~e%M4yQvHXvzsO>D;Lg$-0$^;No5tKqX0If9Z$rD6{7r#QEu zXd77Wg(UhpUbqM^IXyz2`L&91@Go^5V%`8Be~H_`sRYKC?=zHrYzg&ui5mbn!c1@g z61~Fv=Z84pH3b_(WT;@G5QjPP&73s`0uu*#F?r|2+F&j!0PXkkvQ>-PEgo;J{jkPz z$koTV+v8>3^XEGoFxaXK(eViv(NLB4z}e`)6Gjx5!xTjcL6@-ER&%p=(CfcN?gH*H z(r$P|qFi7}7+oBL3m+W!Pr4095nj`X3nI{pdzDr9T3&v-`grFDVKq`CuiRt^0w9%b z8p$V_Z%#tM6x8~tZYdY(dkPPGKp*I2>%x#NEW4K0vgQJSmc3W?{gcC-Hwm=DLy!{DNBVHiRM|;+J{((3|pw7vAF01JEe1I4(L?AF7G~<-% zqE{`o(3;F-?;<%9qE5LZDPMgYSU>fqpd6V?*kjc(+2MusBvdSE2;>=i3^P9nDjIT@ za7X&-ITR+if#mSY0PFEza4&%^A?plC)<-}l@CucCCk+(8?!<#eUf&&C;?nc|9M@gC(fSXo6bBty{cH08WAPvAk2AJyYw@z<98J z?J^OMFRVjK6yAp*p~b@!IH@q8>0FUQ8DYnlkldr5-p@dAv_9ty6JCQ~sNAh{VhmCN ztL5cvdJrY@fpyLk3W??8Nbwzq^H==an3dl4T0h-TB{rL#pnNeyaF*F~6hbXNn)-<; zvBdJ4!N|s@4Q5(4M^iwS@Sidm2mu)wo!iP{3E8f zMp;$;elk33$SG8hsL52QFSg^g@@;`e6O{^!PLP5LR{}vu|sVjZmbLC)6VG4kHh_^ zkV9mA?p`Utj4x7YncRJjkw1X=d>+$gp<5|8rxWbTaxfGMy2?_)i(Zpdh4^#f2UZp= zr0+Z;$ZR74v~~uR9!;?DR_u@{5dmOZEzj_`nSx=TRj+krMm-lZ^&n&Zww6pp=OiZ> zLkS{&B5RM5lfWfOEHaHL?3K)rShnFx?ds;HeJcf!escik8p~$qqGf%>*x-m#uKJjg zQ{S2ZtC$Y~v`jidzKY=oEk$ge>{_~^{VsTaq>{jE$g64uN&&1YhswS&rWSB0!H!$E zNdG2}$o2)ZTv%aFRQ}_0NGp+1I`vD<8q>ZL*WG^?t0X2?Zc}9u6+%r3g`i63=Ia(7 z=18jV0~;i4?FZVG3a(($|IgmHH?(nN`Tp-u(P7C*jxDegXLg54><~=G-gROwIGLRV zq6iS6H6T_Jwu$5W+3)Xn9#z%V-IDNo_L|*^p}V>sr_MQb-X*eFI1a8j9&t0XJ|F<> zM>Uh^b#TrXf-k_xE!tAttfG7%>kg+x>@Y!d#gW(v4KkPwe)0)D@8R0+5M^j`qxmH) zD^xJa4iYo}YX6eCunJA=Ss#oH*`$(plh_VqP;*|~rlW=r>gUlv<36fbTUkgHRIz28 zj<2R>LG_cuceXb5geJ9wA}f%p-XKC!cnv?GiN21uz4>PudhwLq)WblJSZYgM$c&>d zxwtX@Ao$^=7X}c}Q;&n}Vdt)dp$FApAR&|0btL=+RVvEZM7PP_5{VSh4?!L zw9mb7O@qh+wQsWGaPnASR_s4L^BskKdHTKVLggGPi8eWfCmYr2I6mjpDR-Q~Q3H2j z(D0P=gGR({%tK9|2x%ZUErz~X%fJJJ1x1`qEdUwC>Ji{1`9(5RyY+HsbZ_VE75z5h z^LmP7*w7QH7C0_>~E zS@Qk>y}av$UFH+@s@5<7Uz{SW4gCh+kxy8HthC{(HA5Jwe-P%M-*%}`U#qiM1{6q4 zprSO#Wd&m776;L-bmu&*M!cwJ@8U?glFnD=C)HJ`8q5-+tp>zp`cUrGe{xOdpMbprYg5ri)hU%`Muvdz2OzpgCrjCXHbUy+bP z%dE%a?{>z^c>LX~0S3u83sHk%f54c8VPzqaFMn-s_d18jsM~#c`^%fJ{x~|u-@os6 z`Cnw);YAnyFA80pqT)AZV+EvbyIYv&7him_g8zLdEx?z3{Tr@iG8Kd+CeqUw81cy91Z-D-Mng zGX6Nj%{RP}=n^XB_Ov{>!>JNz7`dlEOyrqDdmgjw1YYOl%s%U_^8CCi|Y%LV7hpF@%GNn0x8%(xmk30E#-lX$SZ~yPy3S|I`T(k zIvD{hf4=?e=esvP|KqdmUq9Qu@tNGs?H!^EAx&I)&5md_*5$}KJ!N67U;p&SH+S3D zfw&YwVk8jvBk95i!W>%*OfD{`)!Kp=!-CV*0wWjH;V+$XhAKW7Kd%Ge_JU39^WE#x z>932oOMTARfY0o!>Ss8R4V%Jt5LMnm)PY_fqK&@#6Bw-We`1Lp4_W(Y%*UR9m0)W3(0otYBe~+eG4D2qM7UD)`~WlT_}E-(ufKERz~s<|aQ-kwxf@+|d)J z!@OlIwes99?L;?nZ(C@8fEYe#VNXuzJ*fk)~Nzl-a+I3Ma3fkjX!fog#(G;l>6LW+OheAL}qd$9iF zdgRA0-k>9#$4+j}-taW?y71Zt;7j|p^ooqn->LLt-8wJfOaDE5nTk4SpqLFWDx@VI zDnIlFCy0*38>K_*GNCHGl6zQ{O$P}Ck8l6AptrJrdcIn-4U3h%QD>4DIIsI`yCoCr z@Hp%o>eT!`s+uWF_A_rxn7=PbTW-dj|08A6i?CXooQ1`kL`NPlYegor`9i9IY+gY4 z6#H2kUUzHeTEh3X1Uoh`f7@u#n^6wZ&mm@)&){0XqN`~j#A{eyf$2Y};LfoEIt$M< zc-dUGTStQlV%j>B;ZvkZ?FvirFA9`GZVRZzZwg9;rj?T7Jt()ZpxhJV$)E5CQkZ+< z!0$?b-@;hZFi==V!+Ql;vcaL)I64B~5nxEQ5nk>f`Vqj~vQJo70{hCl$o4MJz%|++tGpKh zm1iznA2*>qVAYF0xed9L7sI0Sun79yxV2Pm@mv{79hA_n%Z|Hav~+NlKaN|IHLmVW z)<8FBaeu;c30qClzes*Bm_nsM!Vzry#auB|V@H8Y6r;rfA~8@}whgS^!}tEyM|2Tg zIuc;=g8F6Txk12|0`(h|xIsg_N*nndVMxOBVhkBm8n6Sf=ogpr4VvTBNAL>?uZ18o zilXwmXn#J2-6nAH%#O$-NeCgKGfZ3X(vX8x;l=}YD+Ge)wR>nQvBKbR% zm!vC6y^fQf8U<;rAo*IQ+ZTvHP8LoW3h~1bDE6h0b^r2LL)$iybZ6=My@2gIg6`+b zVqLZ^yiW7kRF<#^)CNUO;~JlTiMsw^G-!rXfEkb#lvg&S#Hz|x=PtJ9cz}FMK<@iX z;1;yvu+LbnHugJ66Jp1Asu79pF7ok2mCZJIA>F$6H#7*dNB}0nqW#!HRF3;**&knr zrBjv*C_@C+d10@oeL@rK9*mjUXT~MCYJw`k@qN;#nWhWG)Di0Xt zcRD@_u~fq5Y$%wfMY=L4ap@Jrbv z_rc$F18_o-O>$yHhtCYz>b~G*JAw}c`^DIHO5N&F$`{3@c0`OJ7?*s07a29k5Czh0 z+VW8y8amJe(RhC0wL2(%Py-%vKwWx=vEX*zw0h^~C&n(9rJ^tA`vBiNvZA8?in{6k z-WxI23y^c707McXmdf}l{5?xoP*~0rPXLzeRs{*z_MKZWuiv@#749a&$|{mD!cv0ABk|DM{>Yp8n&2QFjgG0amz96F< zqhM!Nk`p+a0>Bef6mViN``zdsSNw<#DNdMh10{koXm6_8n%ga-hK?-|;h3Vb-b6Y7 z5MSRo9=uwj5*5G?hCj2?5300)nQ2^p33w#izNAYissfD~AaBhQt^NuWgJFyV=wQAZ zjnT=ZQYioRfSbElwkQp20> zhV28y;}Yn7pH|=ngp9zd6@R|d#l9@~%_Q7YYE3m3_75r)+dq3=M8 zD&CREXHilD+josH7Q8*jr2T{quD4{CZ;ENvF;KDx2|3+gRv$gQzxrgo`~Bm`kJeXz(i15?$SF=_-)ja0auzgHP46~3 zK3~FkK%%A6=}yccR*xVe2Q4W`96)$s{9_0s3{sT6H~bg%pCDulPta8-8isP>>A}BvXB~DXce9-W_8_~%VKNa>b4+})cz9H}Byu^Mzxm|H^UmVO_cOYfjipO)KThgzL zUD^Q(IJq)O2jKGYzm7N7MEJnv$0 zi^sk34@2g|^&vv5IS~|RN~1t7SvoWhB;bo&o;<~El3)GhFZok$>{qw$$YX_bv@NFr zx<2YymRCO=Rrlh?8g8p5>mAe!k=Tzq{)GbyQeR3@$%3#eDYuGxR{9jH{`xO};iQEz z*_2Ahm_2i*sK;HSd4NTtlC;*$M4$%>ZRY(0=js)^0C{DDbpHebIf8#|N`_(x?vYC* z5RrC)^jKbBh;#nH!(0yJe6JJDq0Oq0m_P);yqZ++tkBP(E6U?l-Jm>~^z@0SW0(!X z7UT{bATi)Lwo-WjDtwk$<`b?#i_X+7TR?E1qM0yHrE^&AOdu$4rI_!69SAuCpGWf# z!U7kvt=7p~@0cT)BCf8-nc0>2B-By+{baDGhBCJ$Mz+EoHKkvO`LjAMz@;`AP_D93 z4SG}54;0!i!%)(bk(0Z@Tt!OT0Mm^r==Yg@4dPiV%3gGWK@CN^tBRReiqQy244r;|`3Al!>E>^X23a+s!{ywbU^(;o0G&O;6q^ zx+G$tHF&4mPI{Vzz`*3RNX78*-VeU2@EDr#P*n~W9>q1{5K}6U%z4N&q971Pwc9q; z`get0i}FEbpCE0?w$YA_sdFlC(YRxW_9yGW|32%$+?b-oP>x@zwH^yC)R4!__@vA? z1o4Sxo7B7EkAg&{TI-7m0+Tdw=}$cGBox~WmLOW5nP?zsS&8$=-bjd&ve-!AE`UYj zh%mtPYpPl7S$IwSo~$1XFB~I5!#7VN2vrnk&Y9O_Bwdo%dgN@vMddV^uS@0D!Cana z54-Gbv*ikuUqOUOClYG^PzrDKJQPsR(yz)Q#*fiUSBcYaxA1mwikq#(;t5g0Ia|Sy z2FMaU80HR^3OAF`l>wu9^HdO=BDG&spO(hy)bOpjN!*rGgC@1e%$lnbvd;aF!dF#P zo{sSKF?mTegeGUFffMB9H(^UeL4h%^bsn(vSO~v6)u=>kV1Ff_h)+m_1k|O1{%;LE zdX7jxhCvjib8sg@60RZV2PBPs-aWYWWOIG>$L>#$pFI5GAE@s6@X^nk>nn1&(ElmI zNisAMAibNSMn+lSX$RdmT(WAEj0X#?0`Y!jg;)cQ`nZ}}J_(CP!j1%G+G+Rd+8$8m zDV)J|(?$)%?$*QqTJJvGTJba8;Q)$H5+kU8^W=IVb$U^ff+-uxW);h>7=#hNDDW8_S2BK56jjt`cK&(s zwb=F1P89^7249lE)4xgjCf=lRjEUToJ|>isuY+VXQxgw*b^3XtV3(EaxQ(cz(}PsC z;`{13{PkpnspV!wq2(=c_$NdIB*87WSdE?t7V3JeamskSy<0vE3___08OM5f_>*KG zxhxsaB*lVMwxr(gvjOMfEWrA0VBBm`aARx4O;g^OzYfO&ji{{1VGTtN=hi4Yw-8S$ z*^%I=v?{aa8oF$4u0PpC`uja z3E&W}bwaRmJ|A*hpIpf{AhT#Qpi>9wuT6t z8dSsti4lm5u-K6Yg2=GODnero$+lPrhNXgUl?cm2u}8y_Wube7F_WIS>^!zqOj%f> z4u&X1vDhJfTcC}=9>tRgi_lQEvGFyA^ovUuc1DtG3OVp}N{38IWkYrAAA(RIJ>Xa~ zFl4y}%@dQYu&+uotj$%P1=`STyHFe{vxqxu0o~^zQZSt;}Y%lNJ+r@=K_axuGwR5|& zAnbH$O60pi@}E7M>q4B`94 zX1J^R zuamD@q9Yi(^vIb1EDsb=_PsitN|8^#L883|zU&FYfhpI!OQ!H<#J!q^1rDq%*mPwG z&{M?{bB4Pl@>;Cg`shed7zl&n7~-cP+i>&UK_7;|lv*P12tR}*HW|{JjD)IeGCb^w zRMwZElP&Apg6^uW4&q+3(B2!loq%6XH$hh%KJ1=37)T;^=@RUxOkYi5dcLuheQo+6 z-91@w-2!ZEssJ*8&qdqv(c7tNAd-S+`1>7GM5>s_PQN#55z%+L{}dZ6W(N7nvk|RD zz~?b8>%lZpn#pWgV}2(_C@)?l%)?|g+zb9xc{+bX%k}QxS2uscMLxJSV_nqxwRQRR zXI>AIzLN*k`>nzK^^MK-H3YETkL|N(Fcv&qdqVgv4&l;<7{`z~iruKJ0BmN0w8oI? zidEb~)4yKJTS^CiD*i7_I9Nf#kEONhCk?5n01s8A?5-O->eAr_F!{j38oK8wuUc-Z zSfP8v9n>WMe0yllY4bl?AkcriSs}!W&SZM;EfWyHAm_*Eq&C0eQ`8f5~zb)J^Rmt#b7+Fz`wchu*r0Ar-RMLwwM5#9Y1W04r z7K>JIU%S@I^*KJj4evZ`&Y(+|f%x>xTc}4Z#Xe(aN(`Xi6M!I9o2>~76-dP~grj<@ zqlmy3hl3Xnpu(vZ^24Bi zaxg^?#VX^`rbnK70balxfCF&CTrl0%2qN7}{d79sq+oWlONuz5YfZ$49=wLvSc(Wq zkZfc>U9LfWe2E2agn=F$jjxGpz!!~5DEmU?C_7=${ zOes67(jjqy@*XCvkW&aqp;M6=q>sme{AjtftU@DOduK(g2V-Ig5||}B5nxffICe<# z2xW)F9>f4+ia^OIQWcw3PHDp0j7v6%eIDgkdi}|v zd%$uIu9=YtEA#zC+*P`$*_zk#+V>%;77;xbBV@h zdOv`gK50P?gItOAq*zpgz|;AUibm7B2rPAaGucz8Bw*)krmhHYNG_k7y#TyhZwh_z z;Fll$icpL2$F-EdsEHF}wAes=aA`{*mzy{i%gteqk=8WJ{J8q>kC85+_m9$ThrAg$ z{`ltMPYrK2*O5}OweDXD8x~SdS%aYCx4Z;BSYs)XilZQz`aXS7u-C91Zg6(9Z8~{c zK9et$(~s3+D-=AFwKCH-28xiGlUIbwmb8B~^HF44fOtn8)Fk2kfXeqydJ`=6bR*q{ zsw5<=&>zRP0*A%{1sUCHbFK9=$6n>wE1?kD^5o@*<_Vf(lvFLLCPjM-5`O^gPZkDzl ze?iR2VThQ~zXqpsN6-|4e6<~>hj}?*WvOz}_1Z&O3_8JYQiSrF5LD2A4d{Zga{4#R zj4b`5Ua_&4qOq^x;L7AxnrSJ(f)-6V^s|3)e{g;bq{f?oZY#hygl(>`-G8v&+1y&~ ztgdg}{_@{Ce_Q*pv-M#0%dh{;h)#Wt|4wgU-{T|y{;PkxQ}cbb!PL?D^8VIV{nVbW z-+yoelg5PONXbH`E+T(f{x0@cS~)(7Z;~bBdGm7fm*wwbe_pkhm09-$8^0XE|@Q_UmX$%V1GLCPpN)L!@c))qb*Y+3xSzG;a{n4X`(%}C3 zR%h+|wXghx&w98Q^ATHp)(^j}|K|p-HI#>FFaK_?2@-zx1bDP9O4O;A5ff|CCQtD zHipZY52r~CS=02;(hdV#OHak@t8J77p*=i05JfGiX?5PC_gl|9wR2!{ZaWa3yLE~C zu1?o_o5;{p+rkFEX&W;H=QZ813(Ys!I_3Oq91cU6NhXy5YMSdBO3kSaCeUvDNIa+Q z&wc( z7G3%lU{6Ly6*Pqd*p;}_;(2c@Nh>mDF&ve{cA)GYxKKPD0=B7?=i9upO?IYc;@*xc zz5<{eXI^8O{PYUT1kG0gjvRH8D69adVz>1O08O9>Qh={F(2FtSK_?SJMC$Q4h1J&kX`J*%_=SzAK;!c$t4({m<_I zdGe3$>d#LebYTEr-vV6aF5R>U-y+OE2aMC`ov^?xexS5jYz;Gj7{*a~9&g7DO`(%i zn(Z@GaxM0c@eRGo!(c>S(pmZbe4UC5lLvRz`EF0^@3Q$92s9xcu5pLn3a5Im4Qv)IPZV$z^ z0Twq9>HxG5mH^jKo=HXxAWx}Tx51{(iz|uWn_4C&OkVGqRfK?ZWO9sC$)==`c5o0I zGt9EC>8h3@l7d>HXNQw^8bFZ3Wi|wyGhJaVQZ1=&CC)PP9W8^#r#*glw&pvWPQGcs zb;Qz&fQ0s?Y^Fhm1P}rmYzWltNrp>ygZmk;c~NI6h#>YM^ko&T@H9W95EY5MaO|mF zb}I(Qw%?qL=TdChH{5KW@{J~@OYw+!wCDuX+6+L6X;pj1NwaCL459%{n_|I9D*4$~ zdKbPb)X56>%JH-e*oRRuyHZh@>ZNhKO3|`jBTS5+@WxgpSuh-zPZ{v-#5Mio8J!Eg(K#3@IFIA-n&MQbo(|AvbY=KeOOl8P*3@G{xsw=(LdOjM0 za1s%P{(xr#lva`$pa=;jo|^$H=g3gn)-pjvav|x)F3TtJfHY|44M$7z4ro#odpo)&e*a;X{iv-yjQa|8Z{sSkmb}BVx z9749bjFCwG!`8(fsD$yrzylIY(SB*ov4^ja7NFh{s#{U8TUijEtVm%f2w17G=dg^; z@d`#gT8hP}N`rz)5UlHaJQxEafC?7ZUZ72vt&nB`@oX`@I*n!==cf&hJkn6$Wm$~c#38;2R3cJHFo zktjx7r-?qV;a(#kN+(4WSe$GRIFYbvTOuXXJf6v)exa54*HLT*oLaaGCnh=_XMzI_ zxAM?iQkI_M$I@INhtU0PvJKM|gbbIs1OcIoCir9IjgP6ap-9jHIVo zO}cT`in#BnTzVlu;fGNOiBN=%j5zfJ(uRcd*tK-E4Sq%e;nHS=(&)lMZ63Y{HR}OJ zCJ3BtL3!R49tq!*xQxg=v~0U_K_w{ccSWlJXRKLS35zT6@t$RC}pK`kBWjW@^bGJ3oiFbO@bw}(KL;s7Wj$GLhmk1{aZLbx6LfnJaToM zZRx_yzq-}QNr0~N!!DVleXEz~$!Q2%c%&d6jdiUfiRo$91Q$JlBbPHgssr5YZ4-P8 zdj^?q&;T|dB>`n++0d@;j=(8XiP-LzsX8Dg{pKL^^Z{u?SkZ)!s~VHP#pO9C@Ccer z&EG?`-RSZc6_sdNiApV({+SBVZiFH);N#MT!5~Q5jiN@@BD3vuO#Tv2yQo&85%6=( z{)k1idD{r{ES8wwfqO^dClI-%POE%co(n*!@4~es5k1N+Nl)*<=9vUf#$QGL)p$m* z;^4jC(Q5R4|2Jy}PsILYX9LV~Fh(2wH)(UttfQA_`&#afHi!6Qo0{7P4y@D_WXOlS zLFwegX0e5S+K-0Ab2=%8=Qm>xc#OwHfT35)8bz~+YChIYm>@D&rPw}f5%+4|jrz~* zDfY|+JTu^_H3B;c+kI#RwtR6;FN-kefleJfxfIdc(>tzp{>fZBcYWUNI4d`PC8LNc zu6WYi721QH!p~!dmaxpG4vF2iCWt1!43f>Pp>)NiIY*Fy3`DNvDd_<|vpO`mm3pKo zD<^GpS=(9NiWT~X;3asus8~FWxZ_*=Tq#u~R^O-d6+!e2*s_=FwyK&E@LETJ52vRjqXGEb7nP&$~ zli|n$BXTPjr*cL$y^ki7|Wy_p1SBNlLt{DC06kZ40sPLHuDYaT7up zC&Tkcs0IOUIXRZd0?7X`PFTt-yODfXM1Et5^ppyEQ0U>A6dz09-=oL_U$JvlDANYG zvl)JJIo-;`ZZGm9#s;Y9!|c`!yaZ}Kc%qmJ9g=5U*FtAP#^06;v!>pX6;Y9JiErL+l#kI7>tnCT*Zi zNo?`Rho%IQ+>4tgZy=g37gicL8te}HPuCDiqc__0nOuwvyZ9gqAXIWV_gHb_PmB9r zZUU@!N2nNLHEi!p=tJx9;7R4^ z9#WB@fXApu23QXnqf7&uBV(oq>So zN&YMlG!8J_gay}ac?p5!l2vyVkfcab&W#SOpCMNL1zA=@8` z%{K^wDb*yx(jq#h8derG48e^Q^FT=jIdVsbKe3!=m`iWY_6j1UEQ6b;zn%9_(Z1Zlb_<_LnI1Zj zN3@H3&@k3`BfGGsn0v0Xd+= zW1c+Hp|2M=$n53*8kq~~qjRg|L1=xw=&)6#3rq`~?~9_{xh?n#i4)`7%_g2wZWAM; zcN#hF-DY%Rl)Zsu7z|sDVI(Iy5Mv#y3#Nv z%gf{Y`QYS$SI@{1F64~>KM{VtCvb)@h&rh~DtyHQGJqmmZgS*lz(hP&b=CORRvivU zt%l-5W4W?oo(s42RcU0E4$q)baQYqrh%6iMAn(Ix{uRV`Ul~PJ`AW_@pF~i*P(pSz zRz-`g&~-8pYO??q0t~=K&G_Z^+m3VP$3(o_QrE>69mn%UZT64)yr~e@kWiK-Sx49` zZ-;@HlFpRYHm;lf0_$#V_d37d>ing%dxKCY^-M}GJ-|>4FMoYme)wwf)k1>bGdH!= zqX~v_XZ7rJ3uJjD%r6KK~@swSa;99xfBs_=n7wpjs_KCbWQ?)&@(s74`UH zZwbd@i-}m)6HQ{xZ=OGYo_8`tB% zC|C9e&VcKCFn}(j&d~Kmy<0<;u7a=<99M_shD_4(LY9Hj!!ar&p{}CH-mp6}d2YEC z9(-vJVw11riHs~C0>VJ4P|xX2*JcX#^3si+rM9nAEa^wTvoz9x#P(6I6ENPfUDxeX zC{Nu)ED$UH2-cVEWP;)4>uNIeCz6*NTm{d=mwLHq4kgHQg5VJu0NdY*{5wl~zkfM5 z;)h}6hNd|%o&!66lO`mg)Trc+c+VrwCy>&+lk2#I@d8x&khfQXk#?%c1ngRVw*A$e z-DTKosj-a%TM#^xau&FLN-m;lfST}*oqOZs-nUCP<|V1PvSpdWoj8FiIIB}h;ipVR z#&-}?`iKA>gnN3(gDGPOj~9!ST+Rbd+{rKJAw-SeDHFmZ^uT`EHclsebXor!m#rpQ*jl^|9*7j4LuEEN&#y2!UY>oW$<^$s&anzc?C8A}V~Zt9NcQx)Z)$7ty0P_Mh(U zmTtI5mBl~p-jIus7jJ*@1$6~}OZBL(k2SELJ?u0LR=>9IZG3YMt}}RX*R+e>8=#Nt z_xOTwP!r{(#yo^Zv-|S) zmp5PiadgZ%>~{Iz)y{w6MR)hcAJ_>nU)UEXSSLj-PS+Os5CI8visD0akNn+t+T+U z@NXFiQTU~S(I3oK@O6q!O(u=zTk=GT9!ip`L=k(xoC24w>{F_qYY(#SI5&NmUEE+T zh1)d1h^yi9q>Y}mqf&}ouw*5`N_JFM9W~xVfP*09zdH)7n5Nse`|D0_V5X7WfoDpn)7|{D3g{ z652*slLDsj0P&QRKE1A&d%X?5F!g}@&>;gQb0y-fQ3NBonBv!?i+!f`j2V_=y>SSayh6vQ0{a5aUcLR-fsPPR-8=~%xpr;&i)-*V zJwfQW-kyqoVE~@+>L2fL2`$*Dy??#;Uqm&x&u(*cX2N&VN}t=-*{q{`~uJ zbTBBsN8O}B55Yy>{P)tH{r(-Kor9%4Y0B0=;4Gxykcn~dkDOmZgFc=}R1#cRaO~?1 z;FiyHvp7fgH=aEkK$zoq@b0buw6)Hzh*WrnkQx@$K)DFGp7xOIjrb!-=Xiq!j)xZ` zgu<{2G}pF#?HZ{$i;r=X2bwQ`A?r_&SV}$hPsQicD>k+^JGW4}9RJ*rNsF|6+B>(p zrV4TgBSB-k05leP)FHj&gQj#w0a7=BUoJ00+NI(PB9LK#VzRQZgfYRu9~%T2X$QDO zb?fr9$&?u`28VE@jf4pp6d7B1!gOWe2DIqjdF78 zkGw^gq_3*7T;v~XT^>aDi0zI!$=Gz~6pS%L)c(*>LxIl)`33i0_RHswf@X_ zP+?aLC2Na#?+|{-h$~=7|3&WeUy2Jm#1Y!!i=WSMA^2$AJ6VgTig)uJR*YQc;)mYe08`vR ziYoc(sQBhko_se}e5S!zIF5fXAJ-|sg8D>cmL6msQJSD}N_3#`2-`g$kCySj&Mkr7 zyshuB^|EQp$*OSZaWF8F;=5-EC^aRRZTZ~&=t#WaxpjZQ!&N?5R<7b-%fHwH=|lo< zws*s`b3pR6!|vtHH)MThM;2ol{(=^C7Xaf=KDyc=9`(e-U}V?Jrq2NDf2+eQAQ~3!$X_?8f?fCEmj1z`;I$#(JEH$??@k9ppFn_x;qC0f zRe(?$)Yq+BE>$sd#DT1suvE-g^cctC3}n9e0LLc0ojPOTFhAmmO~5n9O#L$)w5*iC zxB|p1O`kBAJO`PvSp$wCcYso$Fa(^}jZ18X~aEYLG-|;B44rwHiWuBf)>zaac=Ayr8!ZwIkf-Su#8#i z($?JNnk{U#Wr2^335Il}e|bP*UEjJmc5MsfuQo!7-8QdZnuQH7zEcuP`w0AAntn5d z_hy@zK#fO01#&isZJ$ETdFQNcyTY_wO z!gpp(*omT8mD|uDa|*YkFv|E;%E_{1$`4ZzW=_ZpGXn#j0d;4%jgyDA2~HE{zUUtm zG5|$VH{lkiz4L4V`mv(8b`c~DIL0YeVutoj_|!01auSIGK@Q5$3u=?hfs2$OVwZL7 z#M6ImRGJE;cl%HF+7E%QJggL(j(qKW@y`UEr}SQU`pI2c}xp$u>& zcFHwdj}ZI$6NRf3Eo}Mthl!)$(gh3_tg+AEeGcEHUu^IGq3EE#r!hX|7rM3)ikYSh z%z6+BCk`oNazVx4kNeP$FzunQQM9E4)2slhd=Xc`G`O8)IoNp;Dsr;;K*sfQa?*Q5 zDmlXJs~p+*&cDy!acPE(8HWjCpCP3#CjkY2RZ>938=Yotn}+TyZ4=yP*R}Y~7Y+So zG*sz+%BN7QYktaWt2D`{tKy0Gqnn-5T!J{fJZ0onX%t0l( zYkHc0%YO|x`da0e%10Qwyf%{~y`Spb{mnE?nsa2~olPiJo>p~R<{2ZvnUhX7sTQB+ z>2MZe&C^G9DdnX$PFh~f&8qLDoUFvEgAwX&a_i9P>oe@-(8o=)$&Qc&!o!1ZFa--G zl#*P~$jr}e0GM9= zbWfpU2X7x2rQ;L~TVdO2R6mXA*~IWaJ3fNBsEOA3X_*MBL^7p&g)1A_PeST^QcLc9 z@dndv__TiB<+aV6ZfKwrZ#oBDxoSQErfqdkJPZx{yolHc*!sZkAT4Qzk{mJ_U^z3= zrskRGL9^}OdOisOo&F@w%_e$jFRZfQUq|wA&3&T17pE7Vi-1U!vwrUf>aKU z;$`^6+NNCj!;A3eXVyow5nu1 zg{0H<3Lxe_vu{-)P*$%Lu3upH8Kupmv3GV^45@v*Wku@BdQ?=Sn3b3=#3t=?|8CyX z587a^{M4z}s8pi7sb2rh&LXNZ<-Lo9U5lM1#BeFvSY5^`8vDH(7R)2LIWVt;2sxve zSE-J;@K!`L$CO41%@_CGyOOJbS#@&Nn`0LxoklG1icNkL$T{-qpjGYl?H{xaMEB%k zq!BVxf(B@V5;L2a7{?7NjVKU{WfzyBZouFx@VYGgAnQ2CO=C;t(Fw%z6J>7I185CL z>t3IU&j&PHN#zxK@+UBcB_RcP&wC@YmER~f+7uY$?|Kv-GxMxV^jC1>Fb^YMJk zw9P&|glRDi5iwzs)s6goNKY&gwgdr!=?+jp=+JQwPC-OF0Cuo>M$eh)s{oXu4Ghv# zk;ZMfC2!*!;Le)amP`F1PP^`L118nBB|_D{Xu>NiC@tSe6bd5XSZ>~;L;zS;e6kQ3 zT>OZbmq~=#y#^4>#Gb);t!}9^aOg@aTisZ@Fv~J22KXn0!(z$0Dip__baZ{vG0SHe zMTJ}jRG6}urGp0iQ0w#`bc~`>YH;s10A{AE0w>*S=%k%=*nkCbL~OnLZ#@U@#O{)=x zIpC}_0uu#OZGulzelz74Dy6DP)%A`}oENN7b zU1hGGfi(mJPK)#a{h7nMR$gr}wfq4ZWQY z$x30eZT6q}KP1=YT;um@0jUz!&u6|@S4PKqQRpHJQQ^iVq)FO|VWJLgYSBb8jksJC ztMt&498KN;^i7~+u#Cz^>Hrm@E^Y3ro~`16q;AG&Pzqbpm62@W z>M{vNkA)}rjZ`Ah!?;L6h(&Aw&ryohzN?qA0tuRIkdrZKPXp`&>7kV0!4HU?_AheK zTC}Y;p+`R5Brehrh4W7lGT^qo=PLAl6-`my%3w|3BF|ldiGvl-VfxSzV%-3+RNV2$ z%6tvrclWP0=gHCp`g{lMyb%5;dnS_s$mGzsQgLdGnYj@;RAsD}0VXhs>HHu_niv({ zagRz~L1~Ftgl$Eqsw2=KYz!uEo^A^Fi$8(~N>kpvBwDhXMQIM@K(<*k5fCJxO%DjQbC7(%BN@sKG=#wBbRn@>G9<5-==!~~oQ zpeh&J>`%n@AS_;0AH=-)0SrZ47&;8Ol01sP_mEp~+#m49S13wjR0Wez8a?GweYL{s z40|augkZqZiIC^ILv-KF<3KCtb<*p~N|X3gUAK8`lXDNyRLdTIxiQ3PRU(zq7W`ikCHilAHZ!*#>VC%xNPIHoCH_0eo`Y?QiJf}tL zG+`0^ia(2Lf8q3erl=1DxRvM0req~GP>4Pz?Wzg`e#{>UKw5{H;oG`B!It>kvwe*V zK&eJ1$7y%v|H9T~1;F)q)Gew^PO5ht3`TWO^bWaDayeJ8124vRL>!72SVkr0*uVFG zjWiP8<8RV`qJOPhj5vUdy3WbqsT)1H!qQyo7F1@OO@nh|g>O2pCfL`& zAKW$9z#wy^RJtvHB#5amMBgV?*AXyhfZc(40&tcB3byy|iLCpGD~Xd}*G+WbYv#Xi zgC=fO&@ml>zcNa8Ah((z_wJz8458`kOLI}(IRfZ$ zq9$vW9U06-_j3Tqh59S7rXl_FS-)#tv5F9iJC)g9b&KmKoNR$L5Pqh#F4dnT-{Fg5 z3(3|nGGJt6=+a;>d9jc)VMF|a@hK2Ok+-TfoT!k?YL+Q*YGr5p0{#`8a9p{3n2Cb2 zQg-;A%|`9nlm}Iu!@K25F@Yb@qgp9=IXQ9Bp=?K>6*u~n!!F>f$zg{)O8>>w?DDJx z?N-s(B%otZ+5Klm*M$9kfs2dAVJ#4!9Gce1H7|3jFJ@JdjQPbj#;+6OQ-4?`v{bQH-(XVIhK=xF(1xW9bsUpr8Yd7<#q>A}B*{{7N1 zro7|-aq$Rv-2Yr4AYan0!Q*io%RUUy%;Dzcv;OusAHlorpw3|N>z7NnezUH=gJRL+ z96g)&p)bl{GmbgEdKvuqF2Sln^{hd+X6N@p$HBtGq(3=q6C_6OUzz2cz%OzUC4x>?v* zcrPZ}aw^%&@(k~JC*iZ?Ntk1JmP|4{E6*}{Y|{)+Y@T41vd4_Q)j8k9#!5ZQgHl^y zpIo|}JbZQc)gQ8`m#@+tDl3 zpu&SMPD|zfggrpb3o6`cYf0hMZMw#7AkrLz!eLt zU7?BuHTwNyF$^j*Lk7KF8U(A+USGVR&X^(2 z!I~piV^<3Nal?vlRP0L7bhRu;3KW+ygUqb{FB}4)!}fI%1OqkZVt43wn-^#axVxy^ zgu-0mlkJxCG+dC9s64tSB3nRI)9U_y@0QQDB32uW;;<~n@9U_FUde>?*Ta*+Id1pD zX3xqc4ns&aD-L1ZC;VeBEMn;!WcZ!;_j%`%oDoHT=?J!&lfm8ySAu%pML-jELsRo1 zj=|c(b0xfeZ%Fk0 z4nt1;KtNl9AxG|jeOKp)3BrY~NEsk9AQX0-SG|4JSzN=E;TE*J)8-gpy9L=YmDx%x zcotrk6j#~og!j$F#xd%9j1QT_+;gcGY6}ibq@1A|C> z3jO1il`!u%>x0>I)y81Uv+-cjYe8lC3dO2*1-=j`g3@}C(QiJ{A6j=XhB_X^wMT);c; z+|^qM>t<0sDmH_73&f%_n@}D(x+v!;IE&;KEkss~IG;9bu~F&`{6#$Gp$b7Q?|(QU z1)?$_%Y*?FBzBY7`)ts|n?PoeCnc+lr%nP+NRFD%$rGCxb=HRk!x7IB*iSy6im#2* zdte9{GfZ6TtnJ4opz0s(!B{yY+tO2ZDtDHi?P^QWR)2e zSSPc*#8WUtdU&b*07>5Ppo%vw8WX(YNd_3To!zYV^S*40cY3r*Um{PAj zvC-I-i)n|+m@1tGY-nCxwcD~dR~F?s<)B!&XR&1l6C4cnGV2^Ce+DWqPoe;vuix!3 zk5tzpOy*gG%X%Mt5n&>)vkGUKAAA*IBGpv{66V$zPYIVZGxUp-*Nsn(nuN5H6yL`e zlgEeOGwOdF(&5{!4dv6tf?Q>j95ZnIwHC;&$rl50c+gSxEXlYcCtNyOjjM(8TUn!u z7m(;u{Q`rk-Ng4dHI@`873#@{a3%zP+Q5Yktz*=wpbq|%pfMj= zyLs4VGGye?Nq3*90#Ugi2~})hUneldiv0QiA8mkz%obGM35nz*f1(tR;IBJBf}dDop%?jRqnZEZ)T_?)x?=5EeOc`ZLoO z3z23z(P_RW}jyVH8nyjfUNZ0WX;89=c(;}&h5Pz_JxrHT%fCP;F zjPN3;IIAZoo4x1v(G6^CTncIznGsKCOKLxnageEO6-DvDQ%ND8xw?bTRb3km_xt1V z61%s)Vd|AcikM!ZLH3+Jx($hPYCSydU0Mo1W`fiZTJ&%^J1#-+G>V`t3dS|L!sX?s zkPEQy4;6ATFMT-%O8cnweg_x!(GhBP_eZ1Q2v_93eS|9BJsrRnv)=uA3pw`p*Ef)X zf+YL-*ROh?E5!$qwEmD81xs>z!N^Oi+B73r}bc>_FvqPj$+Q~%}8>+rri4O z4VS|&I5jW&9Lvf*=x#?r*NV9Y=7L#5K$d|x=d1satmpSWDCTnAXK z?>FpN^PLPfyQ%zCLW@2z)sqtq)%;LQb>6V9^k~l*YY7*@RUf9}3bD9~kB@lz^CTYi ziCl8hTLgumgt7_`@gUz(Vh%ofi6Mb;RM;0Be7G@8B^UUyPjHbcv+|GhB2@a|9IY8m z!VBTWr-!%`aSmG7%HuRyhvr`}L)IfiL^2(K(zySkzmGe_V(f|bhMQ)i1jErV3WXM9 zgkJ~)WF&!hawcBsJ%q3LbjDR@JM%!zoJ_T;M5Hdb_{3MKRWW4|5e5*%G}r*9Xq+7e+@Tp=%eKe1%_wbvC zSud{r{l4GD7df?~2@jgziZ-~*cyNl)aAeWJmOeN;=O!eMeD9VM^L-3k%N3yPgG6`7 zoeQpC-PUpM5SLJCx#8i3$9-TZmT=H0YB&WZ&spusfQP*;UE{~IESW&|I^P-hR~RQO z76qV;Y>;~i>tUf#ha~F@{>~^YDgP5*X@syw>3!m+ADWx$2HWOLb=4^2YYRf;F(CP= z&R`BAPP94YE$YII>p8FEl3`?qM}}yh&gQctaT^TF|m&<`BVM^_bG6@`;D| zlQpK>KB9^;T3Mlzm(RoE$dv~pOIeaXMaHeo-{u6=7S}{|+wsDx`Y_7!G9?sbp1vZz zvjhI9GNCi-QY=dD*TRjr_2)^o42X7?f`0Bf0T;rl9w68}bae?FyACUZgu`HH`0ZJ6 zp>6YQ*8e^@8Qi>lYLCGw;9r zB>t7?Z&M+V&-;^_eswk7k@SP8>5d2yfokciCd4veoZ=dDbYBWfUsxO?@b$E}uu?4e zU8Vm$M$vqItQnG{-r=Nk>&~)1`(MF`-`s zZwy^Clr@(8V>)d3)o_oBlRw2(PLB`qcv}SsEcj)y;8C@FUMzT*3&n2nD%ymq?KEU{ zG@iF%$4oHeg_{_~zwTU&PNWB!(XK5Wuh09V2_nBSDTO3ACr&PXDsODP+hM`Qf*eW< z)+HP9rwY~M-uUA%ZT4yZu0vY8MaQU=G|w?S?WDWt1%QS32OlS5>^udo`I-o!3!EIWg8qOli>~ul$;NtFHtdrus9kZ z<(JAB^D9T@K!8~g*=dqhw0yq&x#7X*i=PufpDz(>TT2T8aT44JQ;l2PINcIHHcfXG zignx+pC-jy#2b&_81laGnUF6bA1Av|OIGtO!-DCEJYw6RI{L0~Yt{;3)*T#PcJ_KG z65G7UE2Ea!6u``YQ>PavlL2fxkwr;_zf@!n+%n+X!SMMRMY-{E6N+g$p|>_yx|9q^ zxKLh;y6wZcy@#ue$acca^qpY;z5n_4^Z&U(s{H4FA{u=AIsf~4(}bV*Myv$)hBID; z2tOX{nR}$a%~&Kc1KTgv`0_Eaf}?ERZ4btH|7p?&~J)3~vg{6I; zHYa12^M62qm0Jh23P?<_Wgu%3qnKnFzDvGwk&r`$_w088_U%m82Xi!!Sgi}CxcdEv( zHn*~_;3Lf2iGYP&R2GvztLJ|y!~ADJ0}~>vJQM)!pIw|{tGA1VbXB#$A$V){m5*m{ zoXjCPPxuvo%6m``?;u@PfNd)aN?2(_XOYRm__+7w*MF{1vQF%E8U6!k5{2C7t|Aoq zdvd1=-VC5PYjJ88@wynHCU}&PeDE?{;$QmP+fn!@gUN{~?HhWayVhO{)h1|$^Slgu z@1ypx%#~srU-V!WXkl3~W_qFw2OVG?GJ@q61ZZAqTT#alJqis#Y!<(6_d37d>ilK5 zz1>~v?B4S~Z(RRkXW9Q~^X2XzotL+7e);Np+Ga8CLxSkpY%{ihrG?@mlo1pGQ_T`l zA;ExjWSp6(5?v!?*{It=9iP+u@JKrpNG}zSaZ@*(*M9%Iji%;^h8fww84!|{fSeY5 zKiN1`pb51@+yj3K@y0;Giy+)8-8nzGI2xR_q?!KC*9m*lS9%R(>;R78o(GpF%|Oev;080p4BcSk04HL!f)5T_QvnnV%0^=r zTFguq!uxsiQ6bhGk|Yc|J(zT`S*WMQ@U9+IywMpfgwOg*#r^(a4?3R&Fi0te1}yRu zy^*0`iEUVKS@>67UAQ}L2Sr&p`%2L46!m+-cMgXXxrId+VjM+{!rao8Zu7HyJ39wA z+B-W-{P+6HTQ|RY^~bNMU(sn|Bm$leYUrFzF@(gepSj@z`x8kdWQ+?6vmp)zF?3qH zhn2#C05B{%afxAJ3H2OmF~vcGZ)h!oe$L%2bh@-j@+t0Keu{nnWo?TF;^BCJ%T+Ju z-+JEQdf4wFDz|TN$c(N$0V5I2X;fuEFXpFcqcvcT5Z_~)r6I%|8PZOFgpx+KB;q&M z>ZT5Y4zzF#;zWTB+Bj^g_CXCk3?}{4venp+a9@N|Z;4$bCwe661=OK>J{%oX`wM+3 zJmMz;t@-Cr{!l*7csHVidPmDP5d}?&KO!PQSPc2&94lhHkSb=S#$i2k5&9;_1cTvz zhKoM5XCVB(T&tbTCOtu2EqsH~H;jK`<){`R(M~wkKvb=N;HUHBkDdGX>28dF;Vy=i zAAelg+G-IMlNV3?#ec~ehWZ!{(}ofcQK1G8>g_(>JghPW!SV1EmmnWCOA(HsW#q_D zmj~8O!SeuyWMKvn&FSx{a7h}Pd(=G_E=GfhC+W1t7w6}YhAQHBK#h9S{VekmoL_^J zN~cXUSzdBzmC?jH`z&A;gut%6#3rAwJ9H4&uk#5T<1G?M_@f04N`A|8=WgN9Cg+y^ zQ9cUzjAE(T!4_r_r(W7Re_uCod?H$8St|4|!iLt;dT5owJejw2|2F6QeDHMe?!e66 z-{x!wqy4u$*@v5JsE^b;JL_vWtYI^1&p=oh?XN7OzS9W8z_K-Zo2x;WRhkb-Fjd43 zt(sBAR>IX)m{R!sH84Ed#441`Zr=#S_eR6#I99+>-7O{5szX);&^K8H&dTbM5;}ZS z*!xK=(E@K1Wk%z7IP1-=EeLt^QZ<|fv#_Ov5cOgmuAiYjqe8_2ZEB^u49izT-B--Tx$b8{mRq_MTpgGk(V*0Zgiv7~Lk*cy0 zWJs(cFMO|PHNHT8xH{I~7gi-)FK6s2b%6=V)ZhRZj-EoAK+2tn62h{`Dm|df8p6hD zwgIHTLH~fpPX?zccOu8Xcb29GYZIS1cY!k@W$8M^1{q6Ys0wgTbCsgCy~4I#qp9yGVrtA61pZr6JJhK1K#0E zN>k!I{AcC*S61+*|i--a9`Mc;HS*Q*>QWlCuBrO;X z>q;j4--u$+;urzwaVuHr2IijvNy!4I&`YA6ri)FZ`vS5Ew$^i#&^|P->+LN_;`d|r z2sM?2)%p~Wd5ufo#C2fOv{)<7FL+xb)S3R#5|mp?);PzY1y_$ANCZ(dOq!N-CoYW!%~9*fSkR_bym}W zD9vOnpQ$wk;ShL@BX)=s1jh3I@32cVReHJh&G6IVC%p=1D_ubb_J{%b3txFcQFu@%N z{YeL;P}!_p;)rFaR*D~nhnvn`(Ib(MA>W1t9!xU=sXEm03`_1nRTRCWERJ%(3iGZjSraPWnf^eQbfc{SlWZ7HPnc7tibu4{(=?j-=8N zB)>}uPD(bX0<-4Rr4gLXD7go#Y{eEGVCt6nhC?q$+EtMR>0Th!B5{MUabqAD^33>% zrEL&8WJ?Ca;N85u8JCfExDZsN05J|;o?6(HNC0?8qQOP)tc^x9)!?Wq?YvdE^hW!~ zh@hDQ8?#E!K)A<}yP{IuK37X}bF;J!Vlp0*4@Lu|xFC19vgflTISN>nxjJ9X(V|Qu zp;bmIs|E9w#9XBOLLpMe`l=l+^k2wb$Ft{j70w+g7QBF=dX!%Vyg6rg!PW$iN=(3iVEv~D=L9om7>D!{qx`X zBy~}2Etq?@#FHBJ!st~x@UG*?s1JYSw`aBS>uric+gM+$OEYH`K+5vot znFJ6%$vEoYY2|_&nOWy?jhKC}aQTzD7O0m;HpcjRGO%(aI>Klrs9fKIbez2r1=4a*0&H_5Z^ihU425 zd$rk|+pA(VjE+EG(UoSBVY_>7d!~m zxHbF(O(7dXcR(W*s?!*UGUp~3{rZZXg;(!_Ihs=NCTE@1w?KlyyxS79dnycz`;nh< z0rTbLp(YRXW#)z08HYqD$Qd9RnF+IiC=4R52=;3R{{sC%7?&)MZcQjZf`S!$g9LB2 zJJCFhfIF?IC7Nb#inoTKx$&AMTCv~QRk=t|hTH4Ic`!hFV;M2X7U#tEBPrnA0ewt{ z`v|j`iRvEMGZ#b?+1m#&OcrjegR^=xY(ij6IC8*}&PM})d;(k^p^!~^W!pPiacKt* zi=Bn>_*j2IM>`9`2 zT8AxGHy#!;G~oOcX^m1fk@aX{BLLAyh(luAS@=d8-F+uCUop@4+WGI?pgigAbv^(n zG3*?mL@OdMKnc*|A_G{f$DJNfk)V~ol|?WY89*k=g3qGSaYq2J4egO`m^DjYhb?-S zd>I~!>E{+VElWU`iRGoZ*v@}usiEGE|0dvTWI?6&PO#Gj;L<0VY{jED+a%#iqE!BE zFnI(V&^6ZbI`6th^z~Q#x4h$5TboxzXvnN8?8*nd$ScaLbS@5IQQz)8kmr|l3h&)M*zXwvMnTRq8V#_`oH6*gD+PqRza95TZ5E%z93fw z+f7A{szDL#`^@+^edDr}{`s3KbiY}_6MP8P9h3={Y>*d#Gihlb%!A*n`a#wyGu+eEY0X z2c!<19CpgmU|Rr;`Y(FCq=KTc{b~YT$|K)C<9iFTvFU-G$virnTb=4TM2@oelL8aJ ziRl$s1Z!Iou|=h{c<|)O#ul+A#%r+(srgL!){Q=bJ+Moi;X(%n&7MFbJ}LTV`=d*7 zby538&=Gdz6iH&m`WaMQTD(LQqs^A4O2o}VDI5)8=OHrU@lTJR0RJF?YncqH&8V?~ z`&DWDL;en$faZ{(h1fVHn&a~>qMOdfV~qX+sas=e@YFev&zBIYHzyv;WcnT92Q!iR z4PZ|_z6yGA8z->W*aHN(1-rVZIO4&xK`#)!Y@*)uBKlx&Z#+D~9V$=_ z`>g(ZDAiNAHOhUcnD%_r(mE$qnY2#kvaJ|DgR@r@;&*1ju05M3kcWW1bH)+bgUD8=6EQGsD>kk2ZDjHvNXORxkkszKY&4}iamR|X6ho-(On4va*A0FOiAH6_ge#*mV4Qsx-FkY4tS8Ora_{b=bd;;uFchR(HtXdc42sO01X83NE%VheGt^cweG1CduL&hWu_ooBN(lJ zj$&+Q#ed%aTLE^}FYfz!CvNwX0Uh%fX9!_~-L68wbvw3?FslChSue3phJ)t@J`~?;z3gFJd=Z_w*tv*`a*toy?WcBl%oi%H&4WAEyvCprz zeA$5a0|edc_U{8+_U$(%pwIx|A&af-{maF$L$8+QXT5mXD9b zKxFRSxtGTduDH5q`{VbsI1B_Xc{oH?5{+i=xW@0t7kdaS9AN)WPT+V&ZOkRL@v({? zPaJ+KMzy&CfiJEIhsmV5fJzXWraR5*IF|7t#Z8GJgP)^wf@tS5+1~#8>#x7O#aOXF zBX;wQsa99wKT3e_Xc0mOrOAsBAY+!70qrH4Ta|n;r`K4?Y461tHy+G92U8KpcV|u! zhX;zT41*zZ{&%&{atX>~C`*HC76_JaG+@bwxj`dtwP}8U~SS)BB=j@%G z+v;GZ3*!T~=Dw?eq6iO?X(iD@qAUc`A+;OktcaE~8os#XFJsLvXwr7-XhsLMEEXF4 zxhR)a^8MhzIJesTH>m%`5fX>sxrGdBFkZwyO21(%`nW>A>DeCG!9$;hi36zU`CDSB{mDy4zA;yCo2lSY6e z?EVMuaLzE72|h7jNiUjV>}xVp1vo8a*h`y_+QRxT^cJ+FafZiNi0G3LXQWSxZp}bC z*b-}UlH5EIWP+>h5LT-+G;^_F5}Md4hmqPksd{@TwgfU#?2WcVaC+&Md7?zV&ik|Qr^7}rs>F{oC<%F~S^)j{=f5@9!1Q$m zY#(5Sx?%5fk;)rq1aFqW;t|NM)l{skX>0yorz0k;8rrR1;tMw@Q`WzCl1Eqt_%!88 zJusg`s6*^Z`KC~)IE>IRS2JZP`l}&h;MPSlLtG7uziN1QI?Gv@kOHX_W2di%^KRa# zR|V4P)B!ALD|)^GHgO(e+JPo_3W@PO0IPAstKq_2RrP5NxakG$=;7RfEwK@@uPL@y z1&FEyada}=llUNu^g%EX1O`g1X2QVXNjZ(OgZR*(SlS~oZhFR)Xx)<5BOQcyi1&fq zhkI|OT88w%#5D{uQtPN*c2nh9b>OmU2oRg?E{yuJOaWX|iM0;j-0YwOncolw8r4Zj zXE2)Z%5BI?ENwF=D=Ry!-!i{lLCYn)BSUt1DGVWnlqed;9LqplLJjk%+mhpnu?YZ% zQ7-QuhoOv0a1P>YnJAuzxpH+9X4@~8d~#+QbxqS8N04z(@wpjHiNJIMhFB8B!OjVT zG2**WduIPBu7sIw84KpHdX|`@WlF@V^(92C`e@KaMY!i|qMKKCoH6@nN6TU~BT^%& z5yfQ=#7Kg;%z2|`;JSPIG3_sdf-LEnQ`5Mv`8bK!#DF~$>iWz@#Zref4&&g))dRa% z-#-{*kI%)V4tuPI7RvanNRPlKa$bg!SpeZuv4!YX?iX}w?zAdzlH;yc27UnB(inRv z{My5Jq?ces7^m^PvuK87VBG*s986sqZwa#(Ad{yfe_JjH;~Wwj2vpn&0Mt9Lqn*+j z8c)cCuOKiRA6NFnIeJ>tLd(KryO2}Q>Ik~jsE4{-(y>yw7@^EWEcOVL2MjeSctlxr z_)RAxISY9&g5#SN;efe{;b_RC@8PyZ8n9YY*&e(X@x>%G#v5y%ui3z^R9u;x9$=P2 zX}mugpzy_PIL!dhmh>anV6gle4}TtZ103=&TWWO(oOOGBK&8K|F*S@7mI|DcN|Mkl zJqqtxB|-qw**~pb)#&nI+;1!Xb5*K)ypJGSUe3sg3t`!?+`>~LIizb}AI<U8`uI4=^U@V%v*VXHOkw8C!#bf8yY4g4} zUPtxK`cV7+9@ieX{lhOEyAEofT#VoMuIs2Dde`6M+T*T&_@!glO&oT9S2?NP5vPwA z?b3SUB{tqCgMZ%(*M0x6>rVDtSI!E^NfdnI@u z|NSgHp6NdBhg$atYW;gZ)8q2f;kof2gzD9*$m&_Vp!vNBv=IX3!QYo$8;lQSmqv{# z0{300QG&%JD;y!71of3aaFuA)F1;k+DxhbJnd?S_GPcTXaY|W{_0ZxrskxejH z!($1q0(iGs0)pGcg2d5eMk{Fss+*Nts2?{8BXzr6#um2lpL{cZU_hmm$uYN>?n;f3 zlRYH#;T`hdh3Pk`OB2VCsje$&nGhUcw@|)OJ)7l}g$7@m&91UT9IR>rp~~ zevtkIoVoIv9$5FGSP9YAAwSoeYA;;pR|{^s@$*9VR%{Rz3!9Vy8)-Q z-?kwUZhk81wgG26lLZ>X67H&7I$A=AY3-~TXo^ViW!_}BtR*w#_p$%+1TXlwZfrdhL(l@gE%a zp!&Y{=qL&I`X8&@QU1QPxx3-7baU(Aohu15b~)8iv?;w=^n`MJAK%yg6H#j_oFb788fp5nZr{*>B7?lFP}an0lF_c7@m zz0vpXovlp4t-Q6zAHrgX62=@>J+-+mvwk{eBlg8;dr94F};5_wfU%l9=C#(syNYcD1mw4z4atWNKhV?$+Nh|2@0r6^!%}F;}f+{E*L=Tg)p& z)v=nmgalHY1b=)Y^|;}0U5&1+rMAc32=^6C&yCI^VD7JPY_6}ZK3TuNQv5*Mq~@|4 z-7tv~Ic*_w5Wg|45&|jj!CN- z?fyx0K0ujYh7M65T`GP?T0Q!CjzW6H7};g8k7#t;2$n$P6!s=~i|a9^exa2T%!9KO z;t}Rof@&FJZ*jX&3xx&j=dfr`V+;#{AyjZ=?q#!KBy0by4#j2$FXf0^l_A(Y%(zyK z0GiUE#sOLVEU<%|wfJ=Nkt^V+TMutSo~^_iub8VxT=|5eQnNd4K5-m7t1t*lRfMR_ zhwp3HceSI&Xv;7{y&qQN$j0++n0WwagSYlf2s3F=`RfUhU(G}WHkuFJ;j}&J!No8W z=RV#gfUzC|C+8?K@$s&~TxI{v+hD|iknd;z%*z;G9HCyvKQ%JdS4$vI;EywpnN<_C zIX-CMPxCvJn<7uc2#vg0BUUTNK;1!SWq@YlURd{T_0$mf%xnLhK1qRmP zyzVLrh526JUWajUGsW7g%=2_G8Vxm?Y5H6n7=4++$G_5@uEoID-RIphEB&;!f~E<$ z1KN&}I(>24%vxRtt=}2C2=cwU_UhU<_fUK58h&sSV;e_9_m}m}t%r|)>OQ<*d{caV z>(94tU5ERgZ$CRmIcJ38bx{^<(!S2e`lQ|BeRs>Zmu};OTe$y{aoV_cV5>iZd9qk1 z%D1ln6{9WoN2B7~qP6Cg^1fX9YUx&SfE+yJTf%yd99N^Z?n2Mm(WA`7!n6hq-5kTi$_nk3q5L<-$_2x`!i>CzuYi{`k>f6&}dZ?BZx`w?J`FfiJsQ9f-RnpmBI^vB2fG-(t}S z$Y~XS{K46D<5PhDVDG3!Xdbd8YrnnqV9YkukYqS~DA4@TZgc%$~`NBy%Ss)?d~z%nsQ6P;Bh|4M>>iO%h>k zxuKOn=||*5lonZe4VWXb*lu}f>g#lXu+p=mHI^@fh*{qw?@HJZ6Mq?+EFOgGFd+aP zK|;ca^saE~=X!^}&Di zuV=jzT)b1?pUlg84A`ertw11|EAdt4YoC_p>Imp-uIuQB1X*i3%5iVrt{Uc*b$4f4 zcY;>H)S6j25{J*LSONn>=s@6EEc#czy#}lB7#ufyGWK(W|wy z(<(lLIOy2!?K{QoT~hMoc!Yd-mQmh$c!m>XaIiW$!nGM^lXbbS6-Qzl<}_qqb>z4b zfAJmjsuyytIeCX%=7w_2LA)BHo&v6*AC8t5$i2#VnMgyv9FSq8FRaGq`PcG?6(FEx zPGC`hO=)X(KYEDDk=mY4R+U+lP~(atm1wEba|5O*d#{x=1aXv_oqXyzdOc>6gV)0| za^^Cp(7*ECtMpMmmnsJnCS6DO^G}>aT^TeSDW7PhbP$*2gj@i|Ui@5=_1lYO*`K>& z=u>_{vMoQ66SrO#fs~mI!+y(hW@Bg7lLNC}qoM$buB2SFgLG7SoUFlxtkQcfrS?T! zQ4Y7kxPAiFWzFcgc5$mg&uK44ohMmFe2BTbHq(_$`o`@R-*yjix$iMl|3CeSP7o+X z-DkZ~JD3&FW~qP1+Rz6r+}-)Z!$(h`$N#eW=pk;ZUhjVY`0=Cl)t{~x_ljf&#fm6K z&0xVkug1Aoff45&`=~;|)wm!pagn?>fgQpc((RdX00snA_>>!{ICryX@#0I_A1;l4 zz$o}q59e7amrc@iV#pmdJf4$LYO9DDG~%qROwly+{)%t!>KNCH8)5>g`x?-2{&j)a zl}Wp4nC-m#T`Xw+#>8}ryoA0>XE|zLoCT;>;GMh^CQ}FT5`;w>$#iH!_%&7;XIwc^ z!hRbA;JiiNru)y=+_{UwX9qvviMQfJ%vYt4*__ii#zf2!Jhi`xGt=-k?;o>}X_N#! z=Lg%$a#X!2bHcpI|D*0rn;gZmG~w_33ZI@b%WP9-iXBm7(>Clv5=bC~geu3%*n}1k zNFWe0d;j};K#R*g+#@`ys=IB%GBVty%ej}koaH&oZTf6q=Q@%1uix@80RP_f^A=F~ zsJHe=-D$Vy;c2yAPiiwuU=y_y*?fBCEO*oCwcQ`r7lNa<-V`v4?TDKn9|!8CCDZf> zd+$#2wxREHH-}sQq%+(dl{d=ho#ZbqO4uw-OST<(3w&Ow0FP_S+JynY#eG}51I<^o z^e1~-1J`U<`N1YWotAbwT08r?G9OLGZBM})H}u|(yqTN3c>Q?6VE4Y?hL2f%BWrIM z*j*Svoszet(Vr0&yM9!2>HLjhgBW4RqKjdhiNh zt`x`D)dV}`w-*40CBYh-&fllfgflt#@!tU_Zpgqo!%N;Q_jbEK4EHyC{Hl+>F)-T4 zZZpuEl2zahxAKzA?T~<&dib>i4g)qh=R8;x(FSo})j=(st*8eLd_z4<5 zH9}#---y7r`AZJzH-3Yd-pMr3H_*ABhYv>oY6V;$y^X!WYor#Z`Cy)a=6xLR=K6Io zAZ;)h5V$)+7Y1}cuh%L2rqd=C;xEDG*s{cCpTa!d^@+#%f$y6Ix()3aw|s2L=4f9T z_4|%t=f6d=zux1=Gw^16o#!C8hvYW+k=rwbVz@oI< z{L|KtrqJErKRE$#{O+2-ha-7=kUpdGI7c_O^TEEmg3ouftDU^}OZL|IyPm9eqg=me zH5=FyaB2Hh+`?3WLIso19;4gD-mr*w9W;;q#Gd=o4nRNI0bksa=zrCCt~>H3_ug7P zjkbP2ZZMCbn}CW7m14{A?>9x|@6GA^_1)h8_|5NX%tOxKHNJ+sKbZQr3Q(gwU7bI# z0{aWTHDKECG7ksd!`1eu(3omhhs^(a+hiIh;KcpY2FEW*`-yu5&j*ls{Asrk_l;(6jX#DN}-l_Z@vKK&D zJ(~RuQeoO@%ztTYg4@Bz+9Ua=pPs<2{-E;apxlSoidcB;ri)$?vbK}!j}XM1{rcxu zJXBBpTu0K*+r!TL%FOY#1y@&r(1QOrO2vcZ^^5x?v~Ja(oXbb^kLRwP{07==+1qV$ z?i}vMM{l40?U!bFFx~C3efQy2J#EMTynxs7x6AmKrk7@=T7h{t0q)CjZTPB2{FCur$NNX>$x4u(y$ajZ)c6>|-{*whMVwm)UBfX^eas0?U~>IqkUFr-O5c%IHOOGfrfaCrEgqtpFi_h zjoZLXcMoCTj}zR??!PxI>9K2WH+=s7A8_$!Q^Mw5ug|RilgR)c^qtQBv90nyesZtC zOhGiv&FcOORO5k{em?6zxBdS13n(f@QUCqM8DX=u%=Ak4dIrC=>E4rRPGvKc2amj~ z%D=RE*ma{I=WmaGudM&$vE6cMg8%ZtJv#p0=(SDg+`Nqq?{c^RKS`DB% zweagN|JK|F(OmiZ*nBs{;X6G3^>n^v0{;!?ve{ifaj+kX$?f>v=Jx@n-C5XUG9C|r zCiC{ZJihq_-1xxK{#%w|((;rWtNed&G2ZjCXI=0gTa)(`|NEB4Y!U>TsPX^zmgNx> z{;xPCPsINF7DK(7Z4CnV-@Y7oo_>wA_!6hNHEQi`Sd+#P_76Pw>@oXq;Z`Qxfpil_ zE&BWV5};Q-w9MOD0e=3hRKC71`hWlSOLLvkjql!c>(3QQ)2&3fmb~rp|GW(x{sko2 z?d$6W($}@QmOZ$FzRiv1-~@YV+&axoS=W4{=Gx*$XhJ=Ns>$bS0=2_)2DchrYZL%o zE{-2Px55>I%5P8KdjS-(y-ykFU&|OYAKd+kU_jrlrfy{O{i+M7$&fdJ-?v!j*Zinv zt{P$3`c7Kt>*^eQ%v+;vUYp!7!CN@=Z!f?1egnv`4c7TZXalsbYw>1z?kLQ25cPxh zEpGaO7Jl&j1G#uMFyFUp8N_$3q<0ew8prK8eY}01O^w?|`PB43hRquP`j=+K8*B9a z;2ImL`R?K4e7ycg0L`uEcc9J#>4NuA?@&^^Ge26pHz-lFezzgL`5cCIy!Z$_K|QSYCv2^ahX26j(*ha~_jZeFv}bJKxNw#~QR2)K{K^J@yE9+vI<5oC{Ck)_**u20UnA9e%C3_W10}{lWU& zFoLIO?a#Zp`er^o#SOZ1MfP8(;)z(Zx~)U&mo6OS8v+h4qQ_Z({q|`;JzlEW;*Vec zb_41jAN>1E6T8=((Hl(k(Vl&3{ZH|m`<(qt_|4NxHJIi5ZSsU&JcfUL%?H%{I@|C( zI1Kh+KHX;(Snppv5E3G|q+Y*#ed0-sT~8G93s>AOA^W901Ft8d$xVUO-&MM=yIC67 zus!47AuFYac1-}^PfbytZeD=nulu0UZqMJ4N3zkM5Ksx|QViiVH_IUZsGCO$%r~v? z1P6J#f7p>9k@#q&4DroK-_W@e*PEg*@#Yd9zeE|(tN8c*I`zLU6r!+`p)1eBSpx520qc@I6lFs6XO;n~Z5O~f-RwRYbV#h+5NBxu z3s-m#rD@b7{J;%RdWBPN?|VxOC9aR54~Q*9CpS3Z5ia$}yLah+?*R?zfbmwOOZ70! zTe+0KB2l06{eXs^Y4>|QJPv=-`1oPhb-#H50!RHI#PJnP2LZXF0%8@=I2I6LujjG7 z)tAWojaT4X+Pxl5LD};jvBF_)SPW+v91qB_N8S1>PkiB##s)$h9}zi%Hmz$j|f zTVehR4M?oF3P~1Tq!)NSpQbVIBkE0nMbQNq5h8fPZhrJ1n=-$9ZU56x?SCRam|jKW zpMOqnvJrl2gd^xY4a2;O*H_8g0GywQQ;5bye)*UjL;Nf`hWLz}c00X4Me%;*KO?Gw zEd2A&SBdvi#NP?h@AskH4)4$Ra6k5+(H9!N^HXG7PW6@?c($&r_s>6@9Be4I_7#fY zO>3IIGVb?8AxC--u1O%;a^5e|Y+HUxXmU&&%l#{q;OsU{KP4qY;3&i?d||^kA#$Ky zz1irkZFqe-87!Z?h7{H0YsBuYKP3pO(Oe|_6>1J|{yaBiGHChxscHI-M5C&X;E4QiAX{h%C0~n{$4(oX_ea|S znGGKX=5%Gm-=p>o$8Mj8Sj9j89Hr6sp3L%N7eem3(6$RrU!QA^1-N^^ze}2@VHvIs zAG6TB#1wqCiTk~tx2GIjqetk&ORH`(6~E{0L+xz5(rAbDfFu4~1fh3|`mL_}-5vZe zP0sBT-z@hl$!Qjxzc24#Bu)Fy!57w|*G^4CA#<1MfQ-i-d6Opt$m{LsaRYjsEG|9%bMxvkgR zK(t@~{NR0Vg;%C+b%-C75ox}L>!uoPq=~LF zzyH(-+MrK}N_+sjZ>cx2(hX{=wzpz6e5%DNnva?t?%&?jyaqTERcX(riFMeJw<}KU%(Vl8p-kV^(w!=p()61{OFJHo0o}t)3 z{%C?pJ~OVqwUoaEwtTOHe<9-Xy>1@Z#oY$`_Eo+FyL_pSe+GN`QU|TAcelj8eUmQ% zFkkQJpGIN6)YU%&#eA=We-R$@Q5WB+_}>F%-k((f8sQp&r@SIyAT7?(oZD%uG$F6Q ze;*&gi64U=pS5|1WZ?A2q|}eG3F4EUug%}V3JxLUU5?l1TkZV=+X`pibR)bq zldxvaUWv4?4ZGoL-h9jgz`gjUK3jYKzkl8zi~+Kt{^L#kyYI{IU#_e119wF!(yE52f?Bz^=I|F}E< zX2N$rK#t${@T4#N1mSOisD(N{0HMF>>aBkDGd;Y)q~FthtB`)6M|B^n)H6=ql4(H1cgRcoI-o5+8*z_7 zuC}j_49Jh-to{`dVITGNI6*%V7WNUHkH-8D#)f@~{5x&zX9L8()CXV>A51$$AhdyI z_qdI#*Z2`7_D@iLrF5tLL2%bUyK6omdGGBt)a#$^+i`G1uYClc){_GRcC#LZedg!ru;=Wj1b8f;cI{M!_}hFbd#hMuo~gYe&mw0A1t zL&&ALIhEVHV!BX$A6#&8Ljh8`5txxQXYWjuDkTiMIwwdV_)DDI& zulLymDUzpck6XwyX`X3FuhKY9t7dvZVGH2YfL{Jr1hL>N*GtQ=eV6OPLc$7!y|mNa zENKzO(!5<$a|jk5{8KN@l{rm+&Eh}5 z<{!|1UwWC2Woyov(u+EMnV4f+6(#ZI-?%CK{%^m%%vGyDo>?!@!sK+zc^S(uobJ2~ zRb7PqmIYizI-R~iC4W_$j8q9eQ*~i96IES#;o&)bY`u(BO@-S^U##&<)8UPjD#3H| zizb;uA1HIYI#Mm?H$+w~y`d?O%@^(k$~;-BFdK2^%VcIw##0Hp7lEv<>aq#FNSdTu ze?YJB*^6YuFE7(RHySlvA>0f`Z#Lt55ylh8RF%H<(jSlDMuX`KFTuz-ek5JHf|n9T zoT@>YDNf^*W_0Gu7^s*?)9gCBmw8`mK5F{o;6K5F%PE@I5XQP?!mq!u_xkY+mJ37`%{;5i*?S~XOfsct9eu_Dl z?JM3J!)pG|{|W!ST>bpbVhh|$lgsrYgs=ftP+zNny?$-<^iiAJ1l$2Oww7KXklk(4 zKho7-U#wI3GKM{jVJ|50R}B9vN&(Bk@fSt2Zgi}MMZkaV%Qax2>2~o4g|aXHQ>V?R zHx28#V{luyRsNeJ2m}9B;Qqb2j5Y*;PyzF5bF82?K~oqEE^un^fb*M6xSq!fcSki@ zW^cM{h&7(TmtUJfG@%-Q6i{gFj~5``9M|S6NX}_a(5lJ8z9()ax7WW0@Pq#^4FB5D zS{Fr@|Fzp)M}OR!{E;4`E@UD#*LvjL+lRm3KIk6Z>i4FTE>xIvPcZ}^g+5%FTGZWr zo_FJo*Y$U+s_UJg$R=bmbe~6gn^zUy=xW(rv)tayCY<((q%}9{!tC4_1Z#Q z4J-sMWa#5%Ivu^h_U6InSs~ru>Mpzl5ZiaH8lCKZf5fBwk;O0f7T6y++x7^@9jEBOz7VTb&kJyr z^{5pdG6d_fD;y%0A;!z2S<+<~62nNkM|mjDG7vcpf5{cWtX34>TQOKO2n12$)N|t| z&h2)1I0Rxt-u*#-Z~pL-(wtxkIFzh57c#e>BATj-fh7oBj7MCh@?4~jc&`1!%d^6* zD5DY$XE02c>bmrG&X8o@;41qfjWlkjaM(=Z*P3ALW@kjPxCI~SHqWJ&Bn7#L57wTn zXG@%nyuDNSxOA38ZhUQ%TRb)>J;I0#R5~jcZh8G&v!q>+BhL@d61EHkUNkH&lq-}P ziyWni(plSbl_v5od{)C}@O$+5Oq2byaN!0GQH19li`xxk&D>RT<%qUn45c&Q_Y`*H z%+HoP4^FB=;iHAV)=aJvqqfaRs)8w`2%xH@^O~7gis6U|JPdsg-aafpUZsj6X`^wd zB1{j;RP_kaka*6RaU7}gV#PJz54jZP+7MktJh{uQPj5-IGhCJBc|&Tk+sV{^S32aQ z#FmUH+fybcnFPt@d> z^whnUba%b_v|u-@*>o}(ka6MpfkIoR(l@v8Q@)>#*QaQ?DeS`2ZBowW8`_VC<8fIJ zczuHS*wm898Lco?sE_6&-l?g8Je~1#FWR~JV20DiWuFtlD6ULO+RWzKJap;vaVI5$ z7A=RYtQ>aR`BFG8kmzD{N5YZ!_*k4qp-i!QkKM>A(>d0&jP33yal%C!>|ZooafCb#2wc(@I{@*#)v?r zi#1KV-3vRta5^9D4(r`UVJoy*VHx4Lu>ePe!}gQ?N@8(T#kBx(P;rLQ-v9%TGir{ITiWawsgU!6&@mLM1ePo@G zhqLUQT9!Art+mc6$eCAtHPR?FXP3gD{TkE=`tg^|NXzO{7eGH z>Kkr`NZ_*tp8m%k(yJ#>4Qg{rKnoyyFVjYJ)juI*k1Xi5M`U*H7Iu`iMOZ3v;bHKa?l-eJ}%MBR~m2-x8^BDDOK6mHxX0+Lz z2Nt)z5w`lmdOMtQ=drb;RLMRC{Q>3^#>q=;;B@D#d3HHB&f0}L-`J;S4R`Mw>ao8! zvt<%*orRfsz~qgEjk*e3y9<+2g{amHSmqJ(MI zz|~X*)l8$3uKaf>RfYWK^+lP}#P&2x!r34Ym>~kVwq7Adl>564SU zx_}sJp8?@>cy8CMW@O-Y9Vl_)&h<6?TY4Lw4;E(X5rZt~@1mioR=OBvAcc8#`+mzv z@|B(RxJFF#LNsi7%W;N{4(3qFPlv6_`L>)Ghap;;WmOC_qfI#~biQm^(_gUEt(JDMnmZ5=6Z|W;1Tb^;t9? zDuEy*mkn29YXUMrePUM;%)(8kM`UeImDoNlw3sg^{)S#gmu+{1lT%;DH?!Tc>kDb= zB->@(RrbvJtjq;+6z=kR$>YoYX+|Dc5=(4rVO{3Pel(g4jR`fPO(QQ%^%Bi$+c~m% zVrt@P>hQ$ETd^HVknLz~aam_ksF{)|p}0B`1?tX zSY7g*tO(ev=XBO{xoAChIuMbK$0#JfQ73jiGhCo|U7>`X#aMiCfIi`+347 z>r4Wto@3F@3b`z>g(P%5o{Qb}$UfmbN|S*bqVqGg*zbH_)-R}PnOUari;A=+mpMMb_zC6>N8t%ubi z3$s6}TR9v6wwS9%@47f#s_+3T_n;c(fYv(JIp)Xwo;4@FR&J z2A4*m?mQ#{7jfLldVjoUG6$-wX5^IsS|uozic}2-rX@{5A!&IE0wQ8yBytYCqX3=} zYo=C7*T$flW@0!W#D0904%$?$RIViiTOk%etwr5AR{F^-GKhEs3~4aC${N1aX*Hf) zh*qOO&p>IGNL_R)O-|u66}x_>UUX~Md{)i3Z`FNdI-8xPEoWRsN@@u+Q1$yD1?FtM zv?NOdd!w-pgtInmdVh2-f^IBaUk_O3vUxk?{(7KgQ%+*n_EjE5t=ccbT*9lpGeUSU zLYi|k0jUZHGZor1G}I*IR1c4dh%IPgStm!E!i4>X3^#T(QYsQVjfZvOF7?iO5#liM zc7fN?>5>dLPD!I1J)P2*?NMD4oH5OLWVS!40jv4jUPzyu4dzDsz2AvUW4JOeTDI9v ziFjL`l9@hCXPTDM<@`8s!eNp*{$U=Hlj>r#-FPb1T>(*8yYeT(q~b?IK0iUC=pK%B zMquSOKdt1{lm@EAj=1VDJuMQkQyd(*JF)^E+XOqaY)4$`C6}KpBCe;pH97Zoi6HwtwaZH1p_y@TT5q!> z5)SV@hF7S5oCv#~G{k<25&CS=?erfnSldVaT1*9ZnNGv{;;7?<|#x455sjOgGnv zKSCvEo=!LGl`R|zxfJ;xcG_tJ2UiTKn_q@{XRJAdH0u_`JUVZ8muX(fD08H+MHHx@ zGFRR6(qC&lg{-|u?k+l=rKxPi9w!u&^;Ey?B+T8N!}X?{3P)7R_C$(;#JikBc8SB4 zC7W<1FlsiSKzTDq-U_R{dF~<(gYBqwhq#nty{~CTVOI4_U0-(jXhWV|J=vAH9o3ff(;_RUk=BflLU`>Ly%Mn+0m0Mc>Gu2v)+y(rdQ(IuT6$1YQ z&CT9D^lM;GOZXCa-0Eg2Hau>ARTouYrp}YPSVdE4JK9QxCgcjzFhpQ_yH`E&%87yR zeOHyfacUfpky^h%CtD3gw76x}j}LWAL*cV3^0)`?(ZU|FZE2=c$yeC`DmC`1QVaaR zOSBWB0iV~exX4Jl@Ss%tCwR?=Z34<{X<)ns>NKd#H8W%8AIKMhd^BC2}~q z5R$Y^MmscAj*@HTo>>j%YOd|Onv-7;jy(|(k#|(p3>j}Q9}ZU}If^;oTFEu)kK;N^ zD`99m>xE~sdkhuIZnP^0M~ew9{bWQk*A<+D#Eq*85w@rUMmakKV?m{6i;*-B`#1#> z9@Q3uZD%@M$toK7ixN9gXBuUbwTwEhIH!lht=HcU9kDZ5DC*Ipd&rhPPvhO`Fms5V zbdi>{MCp6>l;`--F2#7}`xzbXI|i{pgGf*K6ShmLb-zPplMPFXyX7iJ^@_}*KCdIk z%SIVxRM)0Ep-eN!zLuw|-NF0%cArMZrV~tDT+46rVGL(e;E&c@_4%$nBcG_x?eh0c zP8x{ZCgK{?jeBhDX18ncffw7I@0>MD1)Gt>5ba{r^DVb(_D+AQNs7qLBi>ARV{@Mk zmsX^O%JN{HSrix`Ib}WTt)A`<;8p2qG9$$X(r|aZRWLHTC#jsHjflH}o=cWTC64Do zd0*$Mz4cF;m7=39z(vv2crURUfrT_kA4n_<6NL(^v}jm}l+)RmA~%Z;c5hkli`put zXPg!09n5r z8>*%$9=b;c7md9>-OvXntM?OjZur8EFw5Z~-`7W@OR!yhPaN_Ptp>g#;ah8p%!!R? zpK4#vwS-*v@g=$oc88PJ>6G2@M9b4dU_=vPzmN{IQQVUg>pV3=Q7@Fqgqw}&y*lx? zILGv`1Bw*=(+Q7G#<;&IBNI!|GFbScdnvnBdL*QHZRb{ZO?Br-qxU_D1@7!LNM8WL zZqXiaPhPz)pGv78N+zuZcn){T#B(33^2|F`zHe+LPvfr`IUJ8EVuA|aa7=&0>kbG8 zR{`WlvFj(qxi4~o1*&@jf@i0hX{FrQw#dt(hhybZ={0zHggk)|ppW#fAVgI;07LTc z1WK&RjeuzwsC~H!6BK}pN^8}Bu0n2lKM7~+()LU%1df@vq zx?HG{HJ1TuS%YVnfLGi~a5Sz!4PRCL87}9`1Z)FQay34>+6GYtz$#mKxLGy;`!n>U zTGw;k7~Y!F_#CEh<-l^vTOb#3fL3C^vA4b;1CZ887*X7QWuC||!`J7tZqBCTa_6a0 z)l)ffBl4i-i%?ve$8i#}{fOTBy$L=WZ4$0Nj;P+43z%|G#e?3ISRMPO zu237y>@DJ9(5H6%wvRYj-HCdB5?<)xz+Z91l1TJZ|Yy!K@Iz zZ&d!4BU90hbk(WdzKQx`T8?nN_5@Ji+T+4*ooVwkS~e%+Eh1+>wh-di)Yea`N>JWf0@umxuo&QLy|kGs9%P{dwj;o$1%k#cIEGiHhO>{!(VIp*+7 zPEX|vGswj>fn%XVq7G5GKFHpp*v%U`Y_){X~GBy zhS%q`>V##dNGZ&#kL74PyhOC%D40tw7UhO0Cg%f@C!nN&8Wt7ifsmET4Z|W zI$m@)G(L6@7^l>BA=c*ZEq%d1kP1JN2XfJ{8|ne?2)wa-4^Ol{Qh;*a`AE_8vx^jw zWEu0Y<$Bp@)lazl8((Pr)aKR67=Z@VxI!y^+qgjiRv(L?7F~Uzz{0N|!e8@dhnI!R<2 zwXzOpUV5i}XEE4l>|$KHjM=#arq68(Z5%2!5-(?kk**e)H#Q6gYVUXZP+QKEI_j>A zE*97>ADftSP|<;y6ABS-d_V%1?npEy?0&?0@}ZvMont3l)CEG*T|&-F?=qboDYMHY zS$8w@D|%L)RVt#{+B0!J9wm}Fmi+`a(U_C;O&%%6C=G`Gs>tK{vIK8^aNZ*TpRXNt zbD0!M+8s{Zz9*XbRG$&3iF+Z&d$EDTRdssiH*Fl}0DS^PKaN&WIzDqgcLcqsS z_cC51B9rE_xj4kkWSSo5onBhc`|HD`Kd*O=6;kWHc7Z47`;E><2UCl};TrX_{eqhH z)7m$gwPS?LS)T6d8sYUJtlEMVWrzY(QWbD_6OZc2QU=)ma)7H}+0%CJv*W_+tn8Co z+YED2`u^sk6DVrbx)E>qfjWnG50L@7V#alU+ZpGmh;ZJufW_jV`i2uAT;?nXUlll9c99v@umD+8B!Q*|g^f%fZCXxm8KV zj4+#bN)|?O4)93V=f?IpTHC2HF|8Q@){`}c%x!nuan}P#?=HpT&P@9?;hsQ_`UyEISKzE+!sF3Su`~lsk;?SOX`a7RZVYjCd8&^`K0)2bDhu z%cLaYb?;L7q?ZT_8IP*Y-kFZjWxAGT%ABs(>mr@(B0@|w2Tggm{9oHMalNvB?}I|nsgz0(XWStI30|2ag0?sCm&5RvkrYr#fMp7D{)uN zrBTvDY>0j2Y(6t62F*`w(HURdUN}B8Y{%SU#zCkT{9wJ8W@}Df26I6g9|g@vPv0{e zzflc{e?T=*_JBt5>iljh3cL?4!v~V^8b0--v-{7f2AB7$L8}hvjmP~}HLy5b7tQ}{ z)j<0h)j-rB{$OqoE=i(KEGuL>j32IyOFUe9lT=P46tA%5ZedM!$4D}Us4%@K$5O>j zSC1k@T&Fw}a5rx_5N8?vx9sdY8!1*4VXFH!b`iVD9_e&ZLuC_y#wJN z*SoQ}^ywmxM%z@tr&ckbZz8_zZlh|YSQ+tG$EJr_71C@^(D0(S<5+fk+ zc*2;eYiLdy6`6+q`u6mZTNa|(id z>h_nhwhCyshxJkN>szUAflICk_S?%1da^CgRJx&_t z25M9USW03{Cr8%h<+O0dJs#W`0iI91sZmaeEYGTNUZ2L&p2pS989&M?<}KI20Ltko z(nmpOG{CEFMM`w45xYS)#F+~+8K1ZTTVmPH(Dzto&yCU+?SXoR)6&~?xO14+GjTSJ zYlbJmEv=Gkb3EwQ5Y2*96>G+{J%xy`!E8Chk%`yYOsmT1JMPjN9zh*aENXnj6ZViU zAPT0SNa48GxLQxQTxUzs>lk$+VOG~_RA$rt+?xyTkzR(2Lx%Kr>26jAXxGce)FBGE zgg%Q7FI6rBux7A2k#=uLsVBNm#~YYs((CQL8Ve5lQ@gK4)Ovr}*9?Rf$5wv$|2 zQp~Q`-_`S-JMqD@u^AN1p>QnUGYnrk(!1VtjWI_vSm*3A5>Q%nY$sI`Gufp5m8lKr z{L){Xkf=Maob7(%$*YZ}55Y-GVMG4pmR-~}y8R*KtutF(A>C;-HzG5HxG7%^HWSiA zxAPi9jlpQ?oOimr$Pw5VmVPD~*Yb|YVM(p~4Xl}he!Mq2VYS=gkxZC9gMnyQEm1}E$iEQ$!vq$2%pXiXb)37G^<)>m@c-vaON*vkj%)swsUhyG%5-k|ck zN7yA;Bp>;TdE?!g9?nF`(Rk;6rYFb@wM7d>qg&KT8wIIN{c3Ozv^i7Oa;{^ow-Hci zfE9~iPQ7{~TU5q`;B=`%`fFd;M6DWI8u)?@aAXt&n6R~t?VYr?W5*fPn zVwXU?;et7UF0CZ|biQT>YyEgG2ACV#9fqr9kJa+d4tF3Rj1Bd}Wa13qIfUwSBs+5G zVt_I+xEoSh=}qmpwgjzOSF*$I=uTf*&zFa;ZrFsHqv*k&fi1|UA-UsNxD0?jEr;A* zTm^_YSnl>g1&m@kBJh4FFU_g!^6V^+M+c!lI3N2(-5cAEE=_vFqUz`nud`rstUMfX z59`{KVN5T8wFXNZ83^mkJt}>2;n=r<=F`YWlclTI@*H1mlRAQMx(=%`RevgeD+WJv zq<#~Teh)}@B_TVE%AJK_R9?+7nb9MuQ!2Zdh!8Z;58d+ANpQS-uIyqC%(*{-5bo4; zxMV_4d9SqS*@#6CPH)=@6&mZi_+%_dtK)3TlKUa6AIq(6^*Sz+2&rLM@${?{%cNq; zU8S7z4KswOot=@?Reni3mvHY+HN&nudy20*)0vbE2}@ZldizYLQ)WJ&FJf^tKys?r z87BkhJWC2EqRyE$JdAqoAlT`gbk^Zc?ZIqdild%&tnt;+o7ygumGs`Q79y#x*R`|l z6Box&v-J|8kNmKMIddPUb8F>q(pjhIFVe!Cu3}A-!(NeEb!`i?r45_toU8sBHBOiX zNY!XtvA|)S@fQbDxV_aWzn=_c9BHBslDP2!wt&NkCs^$IgJKS=pP?L@(5`X^4-xBI_~B@j<9gh-zZK zsrS~41-T6-RJb~t!-95;KFvh@+S?@HGhPzrap4G);SyIAHu1&fx(L=>Iv<;H-e-pi zKy9R^ouNJ}cPyL>;Fr0%F2qrqI=BO-u%-?Qe9jo+S(P*@SXsF2_Qn<%*yX-&oqDte zT1<_itMNc}%zE6zWeK?`ooISUX1PSbbt@1?vscLjqY7cWKuQQ}vnFSFKAmJI zb-NC1TJ*33Io`v`2pMBt><^noVLIBntMvyf-QG;fV~w1rgw+kq_|A@_We<$5s*)-Q z`T%nU;%^20YzPoP74jS}a*|ZN1$Pw)RlI`0$St<<;h%gH9H}iQ=@x~^ZDX+4s13zU z^Q~QD0PT4YD%L0=LBBFeO@Pxva~YBfp)z>2ky_Vd*l*4oe|c3xuKvuq1O@^4HXFC4 z#66FE25+DV2xKCCgg#y4%^}ii3@HK%zG9~93ne7Hmj;<0<0A_4et)9j?jcWpVDWIJ5Kul{|~M81%Ymp5TXgzMGD~+-9jIV3OXLLg%a5YN2C`tThx)6 z_A{{(05Mqz|x0CWk3L=)0wWSU@Z^u&Oyhv#Jctt;P%IngBcQiLdC6vvzXv>XIJEqqJcv= z@-`A!3cPQqO@`%YTr4)yLA6G~W_-kY(}~Neys^O-LpvUGrkt2GBp@n0dY&l|)v0<; zK3jQM&oz!DI3nihaickIc_ukJ)>ot`b`FKx9m-s$rGOpKHJH(%UlFH(!CiKrI7m5? z`b%21MettCwWSnRkk1H_*r%P;pJCgHv>?eqTaaDG&UR`?3dV=dV&xfo+5x)=;b3p;{vMnomzZ0l`s%V#E*mR{6OyafBDE zbp1)tNv2LD%SL>_smnTaJ#~o=JyjhaCR;(Aqz2MY>N(hj`k7tT7`Ud0xyQCqpRawv(m$h1>{dOVudhv@%0S-`5*I_jZSQ92R=jDf^4Ahm_0S zcey!~_Blei`Z)LjJO3e^A&E^;NeRQgYWJV<^YjCL9zXIH*P4&a2y^Wx%WOG6?^M~0 zAtdhv@NK!oe>8G9W6?c!7x^YjA1WL`1NEAvFA!S3?a&JV zBGh>SftWj@N9jy25*5|&rf$hhhVH7Tjn{Z#If%C@tlcEvWg;#0228r>T;c(0LBNyK z4Ou1{mZEyZv=Ztu%iK~6x47ev0q5+%0r4v%UY-hK)d`m8-C>CgOmjxpUcX25mYBs? zTe?%3nayotCl^u0Tz5zU4!*AU+;Os-RCp=$6X7H}ES+5}-5d8@|A<`% zMB1HCbwnn)ix*SF#d1IBry;sGj~q59Eq$to1XZjiVRz0FoG*(p@l+V^yH5b!mFGd-1WeD2Rtdr7i-$yT%J{A(eUv zyuKgt(?@%Ny5BuO%Q?7`D))n|k=L~7)atj+iveWo+BqaO%s}5x5ien(jzy4xUHeE_ zfY__*Za5F^#^dpL=o^*jna3l~`O$YK-?oADmmh=CYO0|@HoeJ zEiBB^#clvWzsx;r750J_7q; zNzBkW0L@}O8pOyM44lk8K&bk%Anb5gmr-OGMcSKKOfaJ6i$zAUt6i`THHa!?Fn`$u z_oFLF{s9l`KC`C-hZ80T@;nd`NDJ)?I<6V>Eanf}SambL7|hnO7qh4}JxZ)wl=@{e zvG#gtQJ6mj_0*UQ)Z=ur*X?jvgmF2d%ge}w1o@iQw0 z19Z@+^|r>}(%09TW-9UCoi(mO;|j;T7Csq4;gf5EPt&FepWYj7c&j!wDnz|z-a`s$ zAwgA?tmUHu&-+A!fI}jb6hr#mYq66nOw(j#KsdSBWRr5$RRT!oRvCJHxP3e9pB**w zy#!+J;&F<_I5G%TG-BC2?hUqZsF)AYn1I(5HBW=Vgw zuf;{bGd$xf(pl>2bdGhYIUixAw9-?{qgojZM$!(hw$8tyhTB4<0pI98gq0s?oB()oA%ai53|G9ET-47>xkt zEN0GWpVdmx!yzaB@=bP-{GGNfbc#F|bh*RlX+-yk&Zy{(_1Y)0T|DX%e&RXW$rOV5 z@U-rf;6&Vq_Hn$rXyj}-+5wWn`-rqVJCv@m5gN<$fseT+RD9`V^YcDAO2v8~Io4`b z?RxRpB?p@-DlPmnRQJT9+uM*?DD^vW%#f>y=*fFz>gjNktvVQ8z~YvB&rj zj*qQ2hJsJ+(~2|Z-r<7Al@EY;Z*=xZPw6CPN|RC%j|-vb9<+XeZIGTv8Biu7FQFz) zAI~*C^5?5d&^y>g5th=_Oau|DeW~1)aslcEYUPipp~qgGZ!MA z6om{pEZ@>|!ZDOHdeD;8KgNXOWk=B@(rIMSAGkH1r+IR%y*jyI%_b_Yx%3{{M=cS z2l^D9o?8;6$RbdFX4gmo2q3AUMop7OafVpf@T&c2mW)CH7(8w>Apkf6O$ne%;gQwK zKNmK6I#{ojO`Z#zklS)CEk@yCXjv&19%jQKIH5wLc84*u9*yHT=Y^@Lam(=@v9jvUk*>N^SKqmeuSXWDqQ>DQrC%uU2t}a$_HczAZGWX@2aQ1S_GW#`cLr^0z1-%h3 zT(Gz*3IXl7JB`s*$*;%*!<>eCfUby)1f^Ma6$>#~#q@C)h>2$O4 z_4Lf^1d*@38j7Nfd3sw+w^&##I@_H#E(f<5B_zeQ3hJP}scg~))&LOoNoMAo!X_L` z8M^J6)~HC3-f*%}&X7*Zs4Qv&V%6*`6W#KBRLLnfdv5%`?Ry*p!h_ms4ltOLuhSVM zLqL(U0+aRKK8ByV$8|Z8%*iKZ3X@PQjwzEiJx}ZAT{s_rtxlPlV)%NZw-W z55e0ytJP#V)^-bV0|_sFnT@k9KCvk-h){am-P`$~7tF^bHbJ-G=A6(guTZ>|lX!D< zPVw87S|CN35!0*#&D7)E=Q*{57DII)PxkR_w{acF^i&Vxs_f}Y%Hg4c$T%8b=5%iI zJ~=}7E;*ZZhQ}S$OX<8PQB_q(FS9rz_5up4G1CYDGuyTnB)L}I_CY&@q`Wy9}Md*S= zhZD`qu;twLg8!ekKTB2|-J&(oceP?a-ZU&Pl8^vBs;&{LL2s&%P=P8?)r)LGHXu82 zUyt2>VDI0 z{Nw67zXfvLchI0ocXO=Njbn4n60d)28BABr%00a9N%=S9_X)uK$>HVjo|6SaFuiJ$WSAJ%B z*B@>s@MZi9(D?YubBd@_oR!pAG=wWWcZhDiPQx)St)P4K zB1#KF!#@v=v&VR~1|O+hlGPQadZx$LskbP*yiz^NeUX4mbC-Ej>uAX2VHrwi69L?% zb5VsE#oD8nJk!A7clpZrjNw{O7B03?AdTz&PUpfAtf;%YmJr9GQ9B@Rv$eyHc#u?A zhiPm#MRE)cUD!JF!_2&JLUWxvVIRMY^QQuC*uU{X{xN9$an6VUMCE4+i^gvzyyP|- zCDDm}Rq{rrUsMsWBlpc6oB-Aelusco{PJ4lrx*VMt_0!c2e=Z*Sg{CfjNK`XAL}9q z*;V!yDU#hAq1I_P&e@Wbw262}FuCmdfcJglbnDVB?_&_)rt9vDC;?NW$@?|c4-tW# z$T*2AIWQT#@}>H2l~mK#>uDdKj(Bs@gchlnv;25ucXh{b1m;P)&j$yr5OSL`^mCX2 z*rHgB+dZ(Z>vA}T;-029dYj%?QM=N21+1mwMNoG$?7fis#CsR=XpfHxzpi)6Qmrk? zUVC=1FQEzk5Ci0-LP{*s)h@luVvCRK$TqA$Q9K6H3w&$uV>U=ikxh}2A1WS<{uN$t(cJ#KXilNECoS=3>{4!~uWsm9 zGxYt<|JgWrl6A|Sp#J?{MLG#yL3i#G^J=>B59|*p5UbM;w7si~R}Y}yNzjRg76N#^ zSu_Ls-bVu&N>=C}KnGIiGT`ay*k}LXm&b}f$#Xf7DgD&+aCb=y?(@?p&XpG=-THQw zd=OiMpN`bRsdtwTCg}82=Hmq_QTA5`-+O}oO`m(D-!J%J@WFJZ-#D*ZGn8DeygHA5 z3MBwKTP0rlJjoS><(KXlcqwag9lcP`y%m^#qk((&$7)c^oq+n?bcTcDM-0?Uj566iB zrP{L}XC8o%_nY?2He{ad{5$x!llBh1L2t=7@jlXz>Tz6Y%h>wEmU)_OhKvc0)xPDt zRMsEx?Ol1KR$(xAo>p%sdQ)P7yt>JLQ$!t~*7PxazHTb0xON%8jmll+%8~X|p&;cs z0a>a}$DeY4Q;XM|565h4LBz*3=3Ezol8yiWv2pnN(In-%-#!IR`4l|VxoYAgrOV& z(&i>U0aar+0e%Pcx9KB63b_Cr6TaxfdKIYt^gNRFBXOw9xS^_*Xp=PAK$G(d6j`)& zCFvYZCs%Kvqjdz``Ubd%&_M;eq#%|C(xxuQJ?@}e$k`(uYu7t%1}ogMx)RzBCXv?B z2tzKc;9I|Fd3r08arWHHjefqvo`NdU<8kL-&`UU5$YHVRzH5CjqLBS%7havsHCdOc zjIMFPMqv=jN)$_X&oIcPob^nmb}7z=(sP3!%Pi&k*6>99N*LlD5j$n)>RbLwg8Fx^ z;d=}Eb>P1fTEOJZbidmM{DyCc{0?yFgLAU#tjfLY3FCIA0ke|sx&Co9h(B!ueta$R z>x-?=Hh#u%$LZ|Q2PUhs{9-b`2}cX!6@aV^OqRV365yzgSy13*UoL+{1DZxGQY#o8 z;EXR{F7F31<1swYyZlxoWKwvPXHVJIKCHkmPSMRR z@x$?K9#n+l|Oltsh;fK<0adAPkF+vc@dM)B-+?u{nFGU!Dju%DNaPeg`PB3f0b?G5;vOcrL)|#eIj{Eo*1fHN@=v)WP@kVzCf3 zi=$CL0Y#vO3y$(~eouB3-eo4}Fb(~1Nj!Q1V%iN+SNd+Bpv~cA)g^S=ORyCy=>oDd zbgEDTih)6$LCtjK+g3~VReFj=yzWwaC3nW1cH~SlNBo{%ai&L3E~>$@o1bIWA*5q5 zU_Nl%Ji{6vw(#0afzC6J*F$Z@?#c~dEj`Pl9Tlbd0;ZxBp^HeKDMIk~T2~#u+kdw*%FaBx8fG6fbokw6HCex)VoWrf-#Z*|IDHb~IpL>LfPw$vyAla@ zFW2|o`}@rssPT~mJTXw|e7`}%ehUF$CEPl|3*yj(4l|?E2L(<4^|8M^nja4a`JpiR z?~mq}asl~dfd9T+n9rhgqfUf=i;3k{aJ6e3=WTjCZ4x7nTCLc7tK`kES|vDdPY}on z)kk4PZ;MVgye<%6Yva_J+iT!fq+WM2)GEN6T70`c$SrMa;AFl#Hf+f*ul;l5-}yt- zU!8*)ounGsY}@o;;pn4-0qtJ2mn{SL%Dup5?uLAp8Udt)o#O7ciqz_bdq=I~-npm3 zBcfqbr-fG&lN_I6`&N;Vy?hE?p=j^m0=$G$ybCH-T0udF7G-w0?)GT5?q~nb7G@Y0r#pP{Aayt10D+M;T$gZ#uM^1c&3WZ zYwf~DPT0=ljcA1meCV2Kls&j}m8~ zS(jzUygn3q$6dIbd2z$K-?WO?(oUz)QpV%)0|m!U?>)%qPFzVu2*i?SCq-|VY#`6H z+F!3MiERq^vG9Dm-GCoin`^s)3PS0YL&2>z*Uu6|&8vG8rhq-lo5+$RhZzvdXxbCu z+o2c&lNJQ@YxzPkZj!do^s0qzJ2nsVCPtzJw3(}g+C%jKroqb=om+7dUIViONzYN1 z+B!z!w9;|)va@FSmO!k`qXDyhCl@CDTygYOlRI66y=1nJg`nrb?0Mf3>rRZV8YgGQ zKN(WVS#B}T!tR2w#$GaPU`c1_+2_wYNH3AKaR(+>5MF%%KMm&JmJ5Jt`aKU>*mI{T zwU}qof{5S==M@@HZ2>;6>`&#w*K3jAz4&j+1t2&7b-93iP=Wqe$_42xW-0wZ@8}sJ z6c^ZdEpx-@;etVaL|BVqU0ylKQm_i=_oZNi%KF-Sj4zA5INEvZD-yIMc&u}pWMsPP zsvNG;juMd4e+0MwNyG!->y{qm8XnyBmTWa&gke|U1y+o_AE=q_18k<2!$?|vhRhHF2}I%#3Vbr$^ke^N1}PdF}#vNck5uSD=jD9_&lDf*zdm zdHz75=}@(dTy;4>4u`!kDp6SKoOJK5df*q604W#Z{TNymoPgA(n*8mYmZj?*T+Drf zBmi<9iYpg{0M6KBjD9|eBH^1o?qgUal|uC-5(~eiH5PPscSb-&!{r2+N!$uL2^lqd zJeIJ%cf63TxO4~OfL1T14bK-PFU3$gWUKEarj^joZkr9xKApEG%dB zB(H)4IZaOhD6p?9Du-i|niuOe&BBJ+PwiFVw}#$ccp9g4b}O}Mz%-}s#az>{QNi&F z^K68Z>;3CwkdtDl`nfxiLE8(aULF6?$nD{M>H~e{#i4#nYO_u7$lfiVS zeKzB8y1Z)@87X%szaSN3BZ`9~JV_+6mKtFtx6uQGzpH7v)vlcLq zoIZ0}{^-k2Z-a=w^0V>DwFZsan{Nj=OpeA*z_)`A!cT~%w=L#9-}+v(2-qL*@a+v? zv%vs*^b)Dp`M|<>*Cifz&sSzJ zkK=*Knis?f_%(Rd8{%q=*J=Sy%~blhxDpI+nbwp^1fKLweNKaJ z##>1&4qWJl;0$av>A~GMO!`u>xQWK5erZP z3*=tMas=kkEU{|{D_z*KO6;GYSPsUNCN|jQ>8V4oL?d{$guME%Hmkt^qJyc6cjx_^ z@%}3B1eh8!Il~_Bl$Nus*2hAH?7#n_jBNKbqbK6^@n+z73`{Q(aQyeYy z#=LzVE8T+4BU7%|0{Pm_eq+{s^LTh);N1^yor{6F^ly9~(3<_I$p0c&fO|SE zLjWJvcBe{I!-MNQcl%)1p(GU(yjP{uPW3m+1k-t!vFsOO$0^tyVml_S4ab6kCYsC} z4)TJBhSl_bIB3;&cWK8Xz8H5ve#3dyX{VjUz&77>S#X7p4pSa z{rRp~2)4D(udq$?-ROpf_YU1Lg2U+PNkL#4z=FGa4v+ zDE>ZI;KTf!{$c$4lgZ;BSK0Anm9@XGvJ7^;w+7VbO?9G}vwS=kUHf}|nEJoG%KpKr zgXI$Fp>IFn+jD_@gEYJuq~FxPZ|xVPxHu)M7c%kDyDgwaRlJ8_B?eka+%K4)j_8~8 zD}w>ozhC$N>WID$2zejTeU9R zT9vNsgcb8&#m=%DcE1Wawv?-*AHWRN>~7g+ueU`m1?gBEZ}H;X{UbGmTS0n;dSoII zbTE{Y_%esP?~3}1IFs16y+1B!30ohX)N$L8I_p-Rl*d`qf^8|1UKlIILl&fFOf1=F z%_pNw43;6s6KEuqOHXwdrtHqb)W!0Z#xjOs{WKt;zz^H!*i@GD0D{(qEVq7l^fZu! zXH%q>PMSNQ+8j)*TkGUi0<@_jiEuhanqqnjE1y^13|AMPdr2r|ZFt_r~ zLAn!3zt$m_`g$E|s5{LsSKS-BKB*!709Tza%J_Hk$cL*g{#Ivvg&(~EpBi?O;52$& zcgpY%;b84Igatw_ir9m{-%vNm;`z%78B>T^PT#t* z-AQAsx)cYUQ(!iaXNFj7@E%|QE90K-@D!9SiR{?xY1{`=LPyv8iQlUZ%db!11&VUi z>B+^8=Fankhv0UblX)w#i+?>qFpzK>+K0aiAq;}NP@bsc93!Aa<7#H^ojj!XAgl>o z|4uXA2xXWTaF+a1RPq?TV>OhsJa5BDoo<#PCGsw}{A29@T`kKRy1^Mg1c&}N;ROG* z3iHyt4K{smgP9JDlkbgE5%d7}00N`SsDjq@?_JkF06;%n*T3MC-$93e!YBWUo;>S+ zq9=d4A0VmFM5&X5TIXN!$;wJsUq!N10!+)Lc;|QQ9w6yM{K^+f|2sDf+#z3b-f!uO z@AR(q{l_IwPW2AzVt%~Iz>@yKyM`tG8}ORZ?SI3&hJ;1ucf4zU7C6K~G05!w1YZBZ zyME}b`{7-Iwqg3iui$S(6h1PvejV_ApYNF;^S$pGTPV+J_nlUh*0^(HKJsY$itg*} zl!ofXfm(NSctJYVuF%#a{Zo&7)~m@2NFec5o%<{?GB1HTKcJ#~X& zP@c=00x%FE0QvYV!kvh*e1J9wmGK1-n4f@Q-2^;jpg1vUJXCOI_1aKV-+5W00q2nR zv+2Xse8bn~a`H}li|!n@l_L^p1g#GlTR+Lx{vnWyQQl|2WF7gB{6}t|omG0D7kQIN z)@0m|<9mt!m=gI^iA4A*{~Q@nfP#6SbJS08=eIfZNACu__AmL)@0l@uhHPsh+l;{zH22f0qWL|i6%_5j%hFZxkE59#LC*yuW~@c||b z5KD*@hJS;+zU*94$(lX3SvEy=C79wpRCbA%U#v&WXU8YoDcr@nnI}aCZ2B1>IFqPk z)jl3?dzaIQsXaSL7|O+3iZl2jJ!y@sd(jhxc)kll)gH{9bHm5xMV~7N@2growKnZ< zwDGt;LHy?X4ow?%C9-GlEzP6PN<8ymETz+^o?@E6SV zu-6@$ssJUZfd$of z8iSq(#)lt^Iue0C?CWxH;R)~oL-UYae|Cj>KVAu+MgMPV`>!;YLtl=MCwA*^`{wzC z8(i-945_*aX~f6<%?g4`ih1?}N1SJNT)I8=CAlO{rFovfXqi^<%l z8v2&pYl7(MVw+=hoWZI~a4J3SY0WwooStriU{z^nhJbT(ZZ9V?J*foyw4=^FQCmb5 z-C?;Y1re6g_A$SG9IGNWDRYYjh}r0PaI9TwN$c;0a~o7NX!-iGPA>&=<$QYEs|L*b zg9sp~&k>o_R6c@SYrvVb?}!F3+#y;m0bQbe_nISTNqXV2P!{B1X1$@LR&2%&wr}#l zpiJr1igOjJf|67-hb+ncc&d}34Qve47ZUDwn0J>_Tt7o>!K=LT%jAV@2EVc8UljK| z%l=Om`2R*0$KKyXe)fkIPeRiXoThXvFs?(3X^k6!TF@$JQ6TTRFwXD2AlOjz9}4_8 zsRH?bs=x{L2FW-9-E<@Z~=We8EKZe~VViCdl{2e_Hcbcno5?|5)<>{1M-u z5cwmZAp4)*!@NJ@`x8Rn=J$J;|Mnxk@k;N!ws*4blze1U#^1gzi2Kgm9z3WtU}Ekx z;Ek-^FeWjCAzm+FqqZ&KY4_Lmft_Lg$qXbzOPdQqm`!l>mRD$UryBN)PRD%q6?lrOs6RAmR(A> z%abT>0NW4|0WgZ2j;n8G0hrm864iVK1&ey67w{X@w-uX!NG@&!PU>h#*`pNwXovBd zwv1>UP6h1LBd1SBkxOaB-5wIzps~7$ta%bi@(G@@?J?1~#`mb&Hj?Y#IuSnVwd@go z@0b454xQ`XebV;(G)ke*hnSdPyyh0$-}P#sL-eyTstj!SJ%gt8D{6_ZctGnuQ=bBg zu0He5XH-1M;C1#3w~Lcdt9=Bhe!y%1d;kH~`UFFzcYrGB&_Ag*Uk^ge^Usqe0QLSD zpb9%$tAW%FGTK1|$I4sZ@KTUfKgnMhhDTKYH!V(14$qLJ`V7~T-gyheUm5FmG>sNMZ^-&o9{lGo`lq`-k8S4)fO ztZLQ;W6#{X+oCwS{Ivuc_#=klGVZeSZKqfVZ>VYdARarF^C`6jWUXM#q{V%D0#!BS4)CVA+H}D&SUMQTHGR^$X ztn)472+2)7EguqYcH6wc#dN=gu->g&VW-X`pW!A1vcwJ429Dg15U-F&-oA3{v%!zW zr~)KnzrKe$nzXFL29fJAgdC?WCV}$R;c}^*Y4JLpF*qBul{wVHBfiBU$+4B*IL3XC z$NS*wIMqvgCZ#tO2e;piWr3B|3pq13Hn$K@QgP;VrMj3m*(-4r229pv zk$SoiDf+VL9aKV+id_RA4*CVDj5lnDIj7w#x*}Ls$`yXDCVM|UQII0C5#3|%^e9H} zbZ-LYY{k;Jg}(l6i=J`GWF(Kbjf1Vzc^8_Ec0;|5#u(}r-1q(h0H1(Ifr2)sBz{S5 z8&G$!zRzyE7aCn&8z<2I!84nlRr#B13#bM6+ZT8DzsPI-r!`lAkVl9>`c5A+h4@Yd zZW8y0`-K(`CG%8|kKcAhVT19DkJj~8zJP`GlSTtEY$l##QJpji&S#zoEDj&Gvv1ni z0-7#~LO=nxrT+9+J|U$pJN&kOz5R=l3(VH+$JC>6L%VOS%@=1hvj*j;}`L1^-PP-Px(RT z_d;5#ovpBzTuaJ9#Vz%YRL&ai{dCK!Oi54y`$R8ewRLbRNkPc#vc800eg2t^`tHI7 zjiW=W?Ru;B7z8#9WV2EeE3P?6F1Y)MbjD1$7ghz)KcK{=9S@M^ygF32j$goh1T>t- z5Zr=(hTY(ygq=vQ=L%_>?6WM5LkBmllc;mBj-QGPq>J@0V=W4mvLW8AjL0cbZfo3a zE(x(>&eKQ3qyh1D>7E&^dfo)ByR{8kS|}(Ab}}&(!2lUh0qUU!;+tI`-frOKvV^oU z>Aoi&s=qwIr+;i#$?k4M?$(X~wbPWCu~;!SZtU&L^(AbB@QyK? zV{(s}!e6JVIX|d@%J=7MhgVl>P}=!;H%mT^$XY!=Z{!(e_G!dcbfDP}2bG1;HEA1P z^+(tsm5D=H)*l!UjPU+8f9IBSiyMMGssf56Qzyqf-vDN=ZqxyU7)gD>jbU@X=5YW( zwq~_@JDZ7oUY0E~j#M6M$wNFm3Fo>qASi$jt1FHW!rHF<`u-kK)&Wah> znZU!w_;E*1Gz7X@!k}vs!dAiY02(t+xsal8X4)Kd;lJzIedF}Ak-@oG$pE!ldEG?j zHCDLH8kmAJD1wPya1zJ?@&+JIOsxmolq|97=FW%IZLP7W>MG89cX`y&V`{o!f340_ z(0Y$Ecdr9^v(${YGsUG{J47`yB3Q0Hn3OSq`8gXdNQwqOnJg=bX+J7Q`i*=#E0<^E zN|yJa=iufwlo^Plk<;ZB^+naPxB~g=G+ykt2MAtx zvU~&Pi%oQSYZI(j26m(CMm%KtWZgowc|ZzF0NU925bcVv@W(5)=@Ic%@5xuC?kiU1 zaM6)o@>5LZ3EI#0#oXh}{uqGa1(bk-rjj zY1I~Q#N;GG7SO9*au9>ml6OaU764D{r2>FaV z`uRbf`|EfAyEjLGHTM_q4j=vB>ie`9LO2dGqPU&$OkODDKR_+aHK?sYUDrD$=*xRK z$W5`$JUn-E9zTW^aENc)TTM1y4{>Ag<#H;355T>%Pj8Zh^uT;^7aQQ6k| zZPc4r8NK}7>7X$wn|b72Q+YOs3~II^P;79H^m@8*>F&N6$izyn#OnAsG7ORlsuQ() z*$WM!F$J~>U{m-*p7XlAinL>pOG?< z&n3N!ISVyDn4$)&1Mm$z?dSO>NB&T%5=`Ct=JY6kcQU_&zG2`;Z_s>EwBmByNEc|> zfQ9pHEskCez{@j^6dI&%N)^Quwu+Q#+oluFrWn7D3c43_0tLTl8ejDXBwO1eaeknq zOU(zTB!bdYAW*5|JTKnvWkqM37Vv@O$q*_JZzP6{VlfFQDY16ev5e1+9M zK#>{eU{*L^ieACC78u5oXAtdFC%nb8nS`>TZKR<`{q$CMqy|4f!E%U8il0D#VYm<# zTb61hYj)-8iHwX>a76ezhQnsw)5;2>X*A`cz)0<9ycy&2G~I#qlXH{PJs?*dOPq4s zP9Os{Ac`>xuw7dfd3rgVR>WyCFQE2mk~4Q%1GQ7&aWu$zsn>q;OH#1}>v};|?(cfT zFznNsxcm>@hmYky|DR8w^4lf-zx_w}|NkG)@bN$Xf5ZRyznfcb z^-pUZFT8uj<9CRK2;l`Fzo!2b1kX-TZ^+`~$L$Wx%w*5`Z0>yGjiI|^KD_dJVXAm% zei)`cw>1qYFvu$5=kbaUnGHG}*hs+o_9=W|r|IQEpwo0{*J+QTNHQ3C^r-sK7s}~y z>SZ?nY3%obH~t|7Tz&VexKCajw3vqXtkDsv0%na20^;UZYSYaZEBVtZU1pFlW4&Xf zzTWloP0MdH#0)fl0KIVB31VmGPaR+e3^)(~et!^H{p~M4F!fO3q)|x zfSmhnHUDy&bM$N=Fw@atSl`S1vxa;rSnFx*Ya6IlRvrz;K0drcxj98eGA{%uI)YTJuYjsbg6Y)Tv6+_UT*$gq zP+ad+!ZExF&*!)@XNVEUA2X$ldKj=!BXC=93zYKR6%)0z^Ckqm3fJ|zj}GxkgmK_> zN&1xz=wizgQ+GM!kmLP0_~x;JXg3`{Z-kcH!qQA(d)OXBxCVQOJU@ikl_y;y(vBf} zYJk`}tV*D<0;ML?V~2ricJp=*n(aFqV%XYmyXy`5MFqg;Jcw~TK%(v|UoK789dC1O ziL#bV2t;kB_UYxjhnP$@W=IUNpG)DzUp5EP@`a693sg{pOGQT8eT}!-`4|Ds*pp_Q zQJ($AWfGcmB(1G5)Sb+gw%_0E*L;zEvu{ml3wl0K ze18UoaTCvMr;olC#FZ8FPvoQZ+&;=Ji@#26H8F?P-Qv1SZ8(hlQ@$f)D}7@bsn6{p zpL~GbPe<2evx$M$S(pZ7ePr$eoHwILiYlhUK#?(7!_~GhU1X1qjmy9{l{LzjKpC_r zu1?WsH*nlJ&k(sj4L69LoUCzn!ycLS7eGByVpio{K|RtUI)gY`Y!^uhkaIox^NVcQj4^cna8t(wT%vC7@3$+g{U z(^_89BoG6&71I~wJRDbCcxgp6_CBfJpkaT41+zihze$juhzq$qd(~8f11h3@Zn_Vou$^MlC)&|mx0P~V> z3%Y}FZazX|tA|N6)L395fffH}n%bY9{{z=!gFfSP-~fyacSc~fdJh-o)2FrN<3V@~ zJvs7XfVJgUKklzb^W(vQV&s3~X#QfAgGvno?S2u|_2(n;d{s4FK8glppye6nQM(68 z^ccC#RO)cIZOPVfyn)=A+u?Ga9_}s>F@$bcv+(-di%(|W17ic0COqWk(9|0^071TE z5a<1LEAM*uts^W$$L#Fm_Y){C8cldTBmQw-A(}DmdL(@1=rAbNMc!|3`iaPOJQcF}X7^jNJqyxchi-jMZ2=_yba<9Q*&SSo=whF0<8ZI^N|N)g1HWkieXFL1Fpm3k7 z9P*oG4ybv5UgrKXjSyBru8bhP>TpMLs!Fr#z}7r37yJT!#qp)gFP6kx;lPBNL-^V8 zpiHCJ`LCgUMJXxX@xw#C@BIN#2m6D4gY50y0TwW%IzTL49X~KNP_|GZ2bVB4@7q|e zm(bZNq2SbW3~gjL&7YD2m4n@*4VWddxMG^S&Lmv!-KPx+Nzr#irp+DRP8tgG2twXm zQpW;-V8+Uv9+8E`KS*JsWG2n|Gpi=bz~@eRg=&gd*fbZ|!%+!FE7+*VV>=+uws}XK zde(40NyI7AT;4u-%)_*FN)BJ{y=#q|F3y|%&41eQSnZ!Lg4uytdKx6uxo@>Sq*AO0 z>|TLnhX`*jm2+IR^RI+5OIHS6#^_GhW8nWo`~K57@5 zJNjAc_G{7mJ<`OQCh3VB7PINophTQ?MA}xf1d)RW5r|o4D9tq?7#3Ubc>h)SC z(Lkp0RL>2}3TaI=dhb4T+dbf=O7dybx>BrM6gtKJ;o>kM8P1Ek+?O28te&3qw7At? z*R~d~>t?$P^bX1PISzc_8?bK-v+>XJD}IS1$d@Boc)AwEM)27P`za@DDOcTLjEM;j z8q8Ze+qm0-hm?^^V1X`p+k-mwK+kpIy%u>8Q3&WcwkE`)FEiNP=(Ks8$e1FnN1Tr|*$MFFNZ2aoV?s;JIqr17AaE~-; zra=K<#{FEEL5ut39Mmd*ntOBl+ExvLQYR^WgLSp^$U)Lb~y|!F##) zs)6wLk8DHqskax2)%<$c>2H?lpqOGUu`B)ph< zo?A&(x)G?y4#I{mB#+CEhESg={c%T6q?B?cy2p?zOEr~&Xl$lFnRcR>#vvuaM`9#P z4le6G@R0si2cqBOz8TW^Kk5H&ulMZHd@tm16s7*EmixA?|EI-~XWzPAkR^Oe+fd*- z+L)7NSnDzPwa@cyBhUV_P=0LWVG{9~GsOJ5k^iIagK7RZbgBG& z9{G5I=sw+D3shZl{DHHcy=RrsB)+``^FIh*6+j}Hp-yCT0_ zxF7jUpb!SB0ldI$KI55;2T^QKMW1vcuHG_p9M2`-kdzVPO8wK+2e8r>CmOl=IMB0s z!!iyzPvWjwso{9>g@j!iF2|i`e%}@F@9Xv%$I&A^z`XU=Y)fs%mrjD|rk3WUGoEX= z6`}j72e+8b`F!yrw?}b3tsF;x7s9>CG~8M_$@EjAk(u06gTcvRFklkWKCbur z=C*;USEJD?jkNOvD2g+1c8v=-Oyn*3IGl*4oCR>hGE7l)T`Tr5L~kPUa(F>%69n#= zy^dXk4g55?*?3NLS8mHP)(9reN&k#ksLm?Jl!Ai?DhXq|1R7q;nLFt>lhgk&7E`F6fQp2f#r`j6bA-)c(Gj~O19 znLfif8h4Cth*_TXbNs?=*2u+2SF8PihTN|Z_s!CZ9ro)TnZWlxbl*HpeBtt5e zN%G2w$^?Z;6RV+lrE(P{wWdp~Jht%M_PL>jq`EN@Xm2;#wmjJ0{USTl4aUtNCM1#P z!dok?Gv0%J@QR#@EBJIFN=6)X{tQm#YXZ2_@pLnq+oEm-A>X*bzLO-6up!<45aZ_> z7jb1`R}@DUS6lRFvcIMg%#?RfO+1vES|gu#F41})Z@a>2_Ao4mqn(`KSj^;8(qXFG z5f~Fj_=Q9BLj@eMt9(baOY3$!j=R|qKmB0;*2VV;c>iAZe)6lV-Cr&A|Fpt$mpAFX z{7!n;7?>ZxKaA|<4y($)-tgaDn+Yhqf1oI49o&)gh+J!7_$puD)$13%(glMv4;aO- z-91}=!=D4XDgaK!AJ6H|*KIoHPq(Ir^RfR#?vi%2GKHHDstP-(o1zaddn16>EXv((&@(rgq7w#@ zpd+RMDJ3gVUES|5*x~BtIYWY<*U6+*XAMC?(7L^AGnH=IpxF`SqjEsD2m^;D9%|pj zRzNMtd;4l+^F=&hFAcz=*#h~|>N#kQEkIM)yF~`LdIf$X(o3ehlbp@hP27Fv%|JCqv}`lA`ZXEu&&R#7Tx>wF8OE}Rsb1yUZ0`@xrx#Hh{*bwf6QK<*X$FcbCiEwJq-;}8PwMp< zx026Mx6k$J_og)v=0Tid5+?si(mz}dkWc;ik8tmSZPGWlbb$X1tN*aF@E>}Q?{@^c zKmOI?_?N@2?@#>G@+k`M@@cz#709x?nGT2Xwb;UM_ezudixa{Ekj~qB`Awgy>xL_U zfep-TKE#YdfEVc8e;iO1AA5B^XyY2p>yk1CwFe|m>UH%YD;ljtndfnsC0$oQ_uvK~ zdcJiJ-hc@hIO(vAdLJS_n47(w%HJQ;g2nR0V`!TpNz|BCh?l3Zg06bh@-=TLIKq%zD$`)d|>76Vm ztB@=Q0g1)!^CjF2cDe{me*J`?Jhb244+S99rk2k(|8pA%JS3E;%Hz6T9L zn_)?LIFnB((lW!=wys5r+C_ix1a6vo91fIwA;4EfQ3d0zEr7$u$*$Q+b3!-bn6vhJ zp3oMu6a@{8qGfm3f~@vNbpnSD=}puq`RQM=JzMVm(39D&2(QTDyzT{A*H80SDT`!_ zL8=TH+a$pe=(fJ0=Q^^`twpaG?Dc4BVhSP?h2EfcGUUT7PQutuvomI{>m-C{Pr+rO z1<}1dLs-K3p-r}@Yd^H|QP#KlWq-`Zj=^ucs5&s6cy;s*xqG4KJq_|Al?-bj94}DY z`HPISfJqK(U84fs7w7ssyzr<#)+S~0`fe^MZY2mAph3m7TJ%UnUJ@9nj2?S*Qy#Z_ zP#o*RE&;^R6IH0N433|+R0rO0 zx$Bb4>-xAqutp8fk9%K)d^B7TEvOSnKo_awTAe4Kr6bQV*spMs`BljL?VkFKoL8^4GdQ8=M6A}H%VcW8o{3{12G1YO}~fh z=XcC63lKa|zz7T^XA1YzcZc_9VnP5!@INmpA9-iMT$%3BDH1mW?WJapLZVu4Xt zN)y+4n2+7v!=KbnsgLKY-s~IJ0f3Ai!~&Q_GGYrc7Q=YuPJFub=i$x_L%OpPNfM~~ zmFQvfY%F95?W25rlw)(-z5*NH2xw+Mts8m3niRh*0<`Chu6*v39H24F#pf-l2O-JP z7Ap9)7$=c!%(JwfRS;sSz)0ENW7xQ}nXs7ZxwK)h>A;Q<{2Q3?)_bvBd_VB zP5-}fQ~yx6{8J$!9N&!|3X&l|b8$^tV|P9r^5wA2m#wd1kfe6|(`aK5K>ADk*s*&% z>;*_kbiXZFJj~-Y+iL}Oh`su@WsBb;mlb^M`dj7l zNxNDA|cn&3r`&+4)AX5h$g64C)Fyg68>ds?=GYjn~>QD_yU(V;OXSzQ@YU>p4N{A|?t zi5InX3A0d@DCzlvtN_JVcaZA0Eas(Mqk0#mkDS??ci0c@C9S?%SA2Gn&AKGBEDtEQKs^;Muh39y{X7^eq)E6!dwX11 zP9qUR zuGCOyWeFQ;!-=SPGQqrs*>}wD*#j-$bD?ah7)^p zfb8?D1}hN@$T4m+rm}Su6Y!%A`eOw1Ca9y28rU^92&jTPg6gUXO`UGCWLlLVsSh3=F_8*K$#RF_8#Qi15tk(-S`O77rm zRrc~}Mm4!xgKzQ2=FXAHnLFvH4Ry0117joU`EHB7LJl}Kj2l(QF!Lc7r4*o^cq=Fw z$N|QopjhFyK7F9eX()^=b^}AXX0vf)y`N)S{l|kdc+&9O&cX%q&nbgFY>@hD9 zC+NNYG$>%+V+`nU{@>iaNw?Z)616+$SJa&KElCIoG*sXBEt*MyI)nsjQ1k0=aE|S? zU3NOFzPfjC2f5P5>9h_$#QR3X-p_`68MA-nQ|JO2tmHa+naFx0G`no{i%gV{#tF*Z z>eO8a>m=#{IT;O{A_v{P%3)e3AT9MTtlP}8`3i(g)AgzbKDpw1c%M>PnkSnZ4@q9R zB^`X3S##vnXV3CB3VE6kWHQ~KbJ>guUpKl81G$ zA8%|s1C&y@-o%?$n5jV%_bYnuVkNVk-XJzjq}V4ifo?aUToFHk4r(H~Xksqn^JQA; zLW3d6+OfM{N&PB>q0w^~tI(6lbLJW-<~+)U7pvRDlUOL*`6!-vdkGi}F&_Z&*gM$v z@bcw+IAuq~ZouKnTR~OLChfKH!=Xg4$?WJFGIA+O2)s}jC3w2y7VP=uvfB=zW4<~W z=H7;u-ZqJT1lb+bRX4}0GyMMxzIeCBgvk*#Jg9otS+T0Owao6!Q)2@i#K6s z#*0|@_Hts3(8z-m3{{%wG~T*6u#gdNxs!I`A?gXdBt$`3_ccY>1imN!p8F%V|KgI% zW%=3nc>Rm{^LM#P-{vCUe(yP69evj2Vt4~C%NmJFbwXZ4=FlB=May8 z3JcUV7$?Au^4mQ5?6I{`ff*dQCZH4){aLJ_vHGY$t;hS9mm>p`0JP^@WtN}dkY3*|t9NY#8}diWC@Anh+2CPDZC9|%XE_)qJk#7_?o ztdi(OGGOX51L>dzPuItjtH6GfQWWMyq612wxA}AW2(bnQ^xN7cen_XGn77UjLJ&UR zhi^hTsKfw1iXpO8K!1WAy^jJ(|7p+-aq(x!dE?LPA5l!Jb3Zu zGO*8l*u)9kUlR$mPE$Sm=jXM)NL_MmfO%q`4|6GV2N%P2eO!doO|KysPYIPtbOvsi z)QSQGU3o5Pfodp^*kQ(SQg(<2sE6mF$eh<|&P>l)bkn(>a0c-6y668IbZ`ENW8+sx z;KvEbJ(I#d+wlULGbEulj4zC`F8aG1x&Gz|ge4yv?=XzOvNvw&%$+h}Yfh*6;_eDr zCam~$MM0{1-n@+Jp@G0691qq-p4WjZ*j*kRwzPtc)~@a^*O3~sD-iI&F+r-%AV%Bn zJgT$U-88z%t)^|T?qLvMb0Z9@@4?a9czCSv6mj#SFq*tBm(A{;%fsMsFh1raMl$F_ zB-gEa&khQZ;~6y$j)}!rt@6+?9%fR(#yE;8!Ku~314E$bt(vs=5jZ27BaSe;JlZX) zZL%QYz6ky?Y^n$v!E1#b*wIom>_`w-_zL=9Cvuj#(cl>MR0xp0W~N70{2kJ-m_7i2 zwJ&p9`HVFC2)?E(HQ3;Y(G`vd-Q8|P;bvbi%~Rfw3jeBCoGe!C?>aztNV=?q@2&q? znf`O)Gyd)$`|o6Mzp2yJ|Ef+a9$kH{(-K4;ogxJ+2jF67-~Mo;O~GFT)$sC&VZ$u* z5cRq2p{b-lF1>&l@zmgl-{gK-I)3jH|DkSgz^d_?VE3hP|FgP%{AJyS)Pav0{H<{R zv2MHmB!8YbP`76f{2@zxuWK+!;FY+7SnNy~wY}g0q*R-X3;g>%ret&o@a8i?D@Ae9kN5n>RG_dI53@pfAJVaaVhUK^{5??ZBy%LF2F&j?PvbpKh=^_t%>zjr9A35*7)^TY0{_15u3b{1tda-SG9)rvh z*0wvfZO>%KV14uZI!-%b*47(uGG4WEJKT#cTX>Cs$wNOH`S!NM%E)fmRt1)stKX)H8bI?jP#`}4*zn{lS@j0a=z_8fPR8C@?HsX8OcgS5! zw-a&jUqd&pFCN8#v)%7(N<2&AIX^-gk3mPc5So{PXdx$4AV8zTa5-ZE8=yh_XWk)p z0eK0rSI5H5m8J6@UFEGQ3s~J)WFl|p<+#u0>q<7#!Gdwz%q%-NO%ZkT0FHe<1?tOe zPb#h2ufW-}F2vJP0wrB;VGlw`>jH8i7D5^Dm&S&Uz^((GXBR5t0NbxWPE~_G4-X~= zv6)90jdN8J35#=o%+_1*bUSF;M##Kt(0^9QU9h2-=WGAJtLXpy3VwJ?Jce%)kKbOw z??0iJ{_89Fe^uJel_oyV0~i|CKa}?Gzfz+G7yPyA~G9UjXod#&w@9*dTX_*IT z^j~%U_hr7H%+LEi$FItdW&Z8PyVv0K)_)Cd`6qYxUzL2QEMHWAklkta_H*kWN-EvQ zHMTWN!Gw5PL|cg?gnpq9v(K%}Tk!-xfv@bgJE>(F)f1g-@+*vx-KIcpwY>+apEJ1k z2Unc0)=9H5GeuI_z1(-l`4M*IxVtc3w<7(0KR-z+K8;B=oNBq2f*0r+6tG6^tz=Vx z_+;8$uMDJf;ef3V3rzKZ`+M_VO)!8ES|8j)Gf-XCH7`yg0N=@;pwkX*xdVg(>QJ!% zd@yxtYt}|n#QS5+u7y)5nHZbx`WT)-2r^ibWzJ}0`=l>t2t_I7wkrnru`YZpf?efvdbRnQ zM*L-b1$TEwGoA^5*)xr$;X-qHnJUHpqtkvbMuFg^U7&z0RB4z7RA^6L-1$*hRd@lYLhSRNnb>!LM}om#x; zc4#;sy$86Iz!;lxYu12_Y4Yz%KAmNtMfs8#)!y&d?p!AIcblAVbsK*0$LWdpmX}oD zvQ6X*eiq>>^XQ}L=Y{BpGSB%%34hBwzsO5kokfK^TsxN(lAzu>P2VR!b4&~Z{ts!% zw_K)lhYjxmF!-fmh5hFE@e>R@__s6~3@NASORyviKt2+qzP>j7?n%B}`M*8L=D&K9 z|HM*96@wK|ppthYj43run#5&~8v=846ipix#qq+mH}U5(TQkgLYat`~=LLerM%9vL zQWTTx6LMmSXxgdMb1N9pZPC*He5$gg4+hmbcg8tD9(G6iq+S8DNReYbt)ItJmnNui zw7Ou!LV5*}G2n*o4-N^VN&G=>IUO0zZCr2le7c<4^1Su8hgvvX4O*Bb|6ZCD z74GlU8R_?Tk2LPN7rq<~qMHk#>g)ILLR5lm+lZ|BWc_Fv`CbmR>KU^W-8-TkIJ|Pddx@cR8&60ZJj1Jw@B*dZrCz>L^TW!)Uov*IfzwgvK2h6!)TI1I0p&d`sLE z<$ilU$oI_Ode1cF)yESMB4@w9kUB~N>i4}MHoRGQ9!?L4bICB#JUBVi$42!^R{$Zh z3A7@n4LMw!XM~DMgf?wPybkbpq!@61V17b#JvMN!7{kyBIq{&tgjTM64e`-67a!Dd zVjq;YdOW>m6C{u;to2_Tbb10x$l94(VCd;u8dv2*hbZx5h^G1;H7btE4FdO;JGOm$ z?iySHoa&gJUzlt7f`d#tL z)RYd7=@I5GXHwX?7^C?W)JL`U#wat7f9p5-zP=fs6#25r|5x=buQPc?s-)BL zGvgcs=y(IXL5SQh}M%`QzuyFh39lkZmiMWyQ{pp$Y))uw?Od4$tR|F zxHjyphXJ>lQkQ^|CmGJOb5-(g^J5xD+N*T#f$L~C@6IBX6;m5Q!Z?js*WG>tH-}lq ztn+IZMk&=0wj>%^x(hI!+_u8Lk*Ndq80b6bgC68Mi$@O2Un^y!!*sU;5fbk&ETbAz zK<4SD*rLO>d5sm_RnIU{#Ph&xFBos!p`ULq;v(TYN>Lf(g{OukEZIxfg>Hume^=ia zF!~Y13TryDv^5umWFW1Qz%Hc0H}3yMefuJ7g8C-cQD4O56Qp|Ba8K{62XJ_M5%^v6 zFp&QB1nU}ar8h4#2TmS0zThW@PwZ@*&kuxwdqps5ZN^OwHsJb*DHKp*2f_8%2ah(8 z9>nxQ96ZX~V;$`iI`6h;l&o(BW(y=>0Ns7eHO6w?y$*iQx#~5)LtqpxhL!Oo>d-H& z{PvRLOo*gw0IbaxNg<4my86l>&agr4a>3t8mvDz~LM2Y{5O-;nL+@z0o!0AZtkOJ_ zuR;xm&34$2kV8Rrb4A<)&NvIrUfIFL^LWjf^#Q3I^Ce|&WZVVR28o4N-icdQu~LE0 z=C;ZtC*DIJe|^Pj>ETHH$oEQqyuoG7#1dL?1&KNBZuxxPK9k`^_5ZlOS>5$sj^E30 zzFpq_baNA&_xfb{mUtnjr^$CZ@y?E5xU^4-+Q#yiwoHFlHv1=G@Voi`!_Dn4b1ugi zKi}MJ@aYao6bxUh+WXQ53J$on0lww))@J`z)jnVQ?Nj_*)!vsj7&N}`kN@x~eu@GA zIR*TkI>fTM3V3;*o*6BB2FTV1X(9#2^F5OEusqt|VIO^zD6-}BCoZFTNbZMpd-Zl+ z44FQaRn^dG1Vgi7RN7WV>Y@UqyvDMV%ig?U6t_1Y&kB3?M5Vnds$x|LTNd_9qu3fj zpU&w}#>SF7+bhw<+p2;^^f}lNS*b5ZE$?#_0!Gp6B7@r^y^_y50IsiIy35X~prn=w zb)UBYGX$Y*XLN3Vw2DMoK+@6D!Eiu>IOrh{>2ZTjL&K-{hjJ)aHc9S>@p)Z5|VDuDrDch1l<*gB@UMQ4y@Ngx6h;E-!B^xw8_V1m@6$LP5Yu;07{}g zRGWdVy@p>72h>CiDAGXPT_4N3ph7mP{Qe&80U|@+UZYi=nehgYb;8*^D88KOXJhh3 z-y;o@C8HQ@O%iK^{pIlB$XkI>L%EfA;6|bNT`Cl3pl060z)mRJa=}hnR2q8hDMQ}4 zTTR|dTlXo&BABge3vdpT4JPN?36xOTI#A&fxYBDJW}Q~DgP{10en46qy@+UD#=@OG z+?#_-jOcztjuRtQvmD%R@cvIYt&lR;GwVL2?qsA$ItNZOyXThp!P4R?)Dlt5a+hU$ zIbpGe4?P9%4|ARb5Lk|+ws>9EFa{vk%ddt4l9nH?YN=hpQZg911=GIr`$2r|m=yIQ za}v<{rscFLkIA+Z0NBuv2V_e=pY3LAth>IstAJ|wq1FE?KKU5fDmeVTo^*M*-6=op z9&qgOa0WZcTz&5Y+yGSP?~Q;z2~{`{-+l^J*fl^=`*horgt|i1^77i~r6u=ho%r#* zQ)P^J)-Z^oBFuj!xMSwBqa9C$A#1sg3!qD-1K4sFsB|7&_iBL+cCm)3DXD$k!q~|e zh$FYVd63?P8IN3k^l8@VhblA@+!{y-=Ic@0D9bzx_SS*~k8F+G!MQT@KI$@pIhx^e zem(fSGT05tVkZhq^i#DZ?G7Xm!o1b){yh>5Xm^x-_Jot!-5DdZGeLGbAt7h$DqNpw zr+l8h!MG4)y+ut^Al;2d7}F!UpCLes;%96-=Y`w{L_=b{tX!MB0t^Z@7SCu^udI+= z?{_g?||^eSdbckLo1H#`CTHkEqlD}^LwyihA>!09x&R&k$ULDxW!h|1^KGi zM8pC)MLdz&`|TBFY~qCl6IlPEx19cPd%~(420%MnJQRI}v_ig-=ZAD^!+K?IXfAHo zj_IlfVGgc}2lSO1%TS~ zisq-Ab+)Dc#_d~zV2WSq||9xV}J>NN1*&R{$ukc|HA;X zcPcOD_#fIv%+9hNWN@8+Lor6^e^pJ-8-h>uIg&T z65fQBKH3wvtQ5z%8aZ^-aBx{tFO&luj3)rhi1067QLuGX>BhP^`^NwS!hetd06m|Z zbTHWf$v#LAV+erYxa399SaJw0?lXbnE7*teKyC#5vR^@c09~d++xT5j&15u?|8axp zj)=`nsT7!`J65#aQBs5iebT0|B)FgiS9sVIAmZvl{u-!t^V<>H^JqgH3h|ijihTpN zMqY^v)bb$(mPDu9685B=)n}>^C^kWSNlT+Ce^^8qZpYYqoQP9@-NOW6I7vo2sru&n zhXl8G{>OO#*T0TcU;A9eRUftK$r!2-K3@nLei zgy;{Jvak5T@AIc-AO_~2_fg#wePHS|&iniMb7Uz7H~EbulrG>?>%U812I?XwFChGq zA?#iUY5duEfBHV-m%qGLfiwR*=spP2ZLm6*(8GT93CBT5Jg&fi1i_fU`6^TRqEg8H zglh=ADVxNSU>bwC;7p)dvNOg zbh_@+d@G_A&2DO>ijv@3h+KV1?hY~MLfmoF>{i7Q&B4I3RIMx=4-Bd4USy?5lCWkB zV2ICaSZ3Y&goG@4AIHk^sBDzO(x;8eNgT7_Tec)GT5*y5_~kyYSKu}<%qz0j`w)PY zYF|7L6s+&Ms(>h+c?S`ngmY1E%}a(~mG+y=v}b zI43IZO>)^w`ckPru+JZurv-9sJ4r*hKh6lO_xI(y_^eOB;`$ z-Lc36E@qx#ix57Jg7tadS=i?8VnQKzwVgN7g_d8`zB%0?Kvkg}sY+YJDQ0|~-5aAbTN*;QXBqq*r>22hDDzB}1J}4uYRv9= z-_Vs0&J8vo5RjY+p)A4`Z&*Dn>-WTcxb2UD1e4ak2Z4JbOaHvc|4+h{|H^LA7F!gQZ@lEC;=*%nh!O@`KCD4fDrXnG6fOD?WBIS`Lco^ zpa%1enD8!OUvRLn&HnKCeqWEj7=H2(pO4O+7lYH+kMjBk=YTf_ptfIwF@Jd4x6|m~ z9{0a}S|BqjuN1fc?f3xJ*$+?qH5KzaHW2JeKRPMq({#+M8*m-J#gD)aQTGqp^ob{Y zX26%?LL^8*UXEdC``3f-@CLqpQ4u|2X<4`tYD6+{VIlSID)97KMI{)_ZtNnK3QsRD zovfx4QY!(#Xiv%ZMdTwdo9R)VQ%TDYtu>IS*S&Qt?R3lr3md#S5sZss}Z* zo3`A6#%r*rXxv-HO9^sU(=avdk~$>M5(57|6)T7@vn!-;PI0jIUAlE7N~r~;z~`-U ztx8ypNWx-m_R^`o*Km0kJj79$olMEg+?&u{ARvK7)-Kw)r(8+Zi;lH~34t?C9;F(o zzo6KwlwpekGxj9}en+4@$w=EfoKfZD%3HSpJuhcm?X-nBg4mwRVE(TAra?r+l9 zSg4QC;a>n3-(1;mwe&*@^~+M~JWT;IJTIgcz7*?PyPW7$70ZQp81{ZsnQuO4O}_BS z+l0_zBfzZ-C@j;b#L9d2xpBl}Ld4U{M1@FFbN_xX6Q&xfwS*E7*e zn>xWAAjr$%=*F$-`6q1^ba60m z6F!&ukPb||y;3P?g>3XV!oAvefd2g#M6LIv+Ne)>ux#F!PPj+D(h!3bDKJETM885@ z#GQIw-uKI&^#8xo5Pg*pZD@lI%s`#}&LrpH&dvhncGZK6PmDGrYWOnxT@CK)SYFx) zJb7%f;y7`pfDW<)PFFGbVz;LC_VU7IN?ZKrq)u%((D z&sCQ^9v6S02ln1$LVmLggL%STRs?Q}L`X#Bty_)UUH7F5QrH@rfA}QvS$dxe=O$i( z_kD6Yvd9AUmPm~-gl1bcSq-dNX~~findt~3NGw@@ijTTmffJKduVXr=k%VCChBpT@ zFHR)Pz;quG2h4S+WHRi@MUTP&`zaR8!&(3P!6N+>p3$Xd;^MfmWoge5=ian<^}wyx zZ#v#z7uBBzi<|E>M7sGj_)e>Z{(OaK++1UUx&R`o^P#SLR(mSZu2bDdc)hkqprt*8 zbF0i;)w!#0IotR&;J} zc_w;W^R|7>+aeoR|4=cE0&L9aw!yQ0%S3A_1qbnQSR!wilN4-|*qgcQu*>;$FOCDU ztIF1eC6T&Sr@LfW%#%*XR1HpWFwywB82!;FpPQj^ME<@whsp~M%F`iGcMCz}3+CTg zD%Wa?LAuPihe6UH2e~8eeuhA9CHu@ht*l1~n7=!(#6_?9j@sN`K_pK=GH}j_TQv^j z-?Yc!gu~SNQmO44wS1eY7fRXobl0Nz-)e}?Lju#O4^P&$8NM%+f7{@Hl*&I9O5vl+ zC%@e+1u}{`wVAbt$g0uz*Y_xrOy-sxX1lhr#z{sdD=Y zORO}$v9|{uV!=^PL1;%JD{ge1HK<11dP&j^x*_*U zzyJie`uv509#TVBN>%Z^e4^zb#C6rienl9Zdf5|BQ$j+aNp9ARp?EB#ql*ghD1|<& zW0nh#)a-EOvB*fy!UPeLpUQl`FzZ=fcB^{@M1;dd!m3YWHGuj$h2>w4WgAGNDg)b8XL$l_h{@~P_3knc$e=qQ9(kkehH18S3yM@R z+HKl7hj;B{69D^qa*v(DZyEUvgpf(w-v~#NwbvG-4_=}*msxu9`Dj2keRF|OboZEF zQ>{ym*0>YW>#^SOnHq^VrJ&GfM9SmL(;1{`iVK_r7PYR3XuZT^@YKSq%y!b~qWs?X zuv(z<`Kk(AaIR9qdfWo+3^{BH!@xr@PjrgQHzRqR_^0JQti8iZr4ED{pWoX%Q^zt$ zK^=^}@FI1wIhNRZwKHr!i(K>!z{O-F%o`uZ3}p6Y3KkDr^sBMhshR z4?~y?hEQO7q78utz%lLm5+CVWcfX1d#iPMeYZ>7h%M+Ulz$)y*`gk*PqlHvKyTief zeyA*Uo&vDw(1Wg0cL>G}u?GEiVpY1xfAe8~tilBCv;i-*+~CFi03E`0Ev8z(fy6O| zT$m3+?7uNFrB5{loB{bHw6yOJrEKi-6*kv`2*QB32GMm+C7Kx9)p4vE(VDTQ(ubPCcBJV z!}eU9!Q{Ja>HU4(9=0R!70)nv1F9-?Gfq9J^G3xLHc^-SlxZfi7(P!x7I&YQPpFr zS=ND^E45*P*h7wuYKVd7+ZuZL(Md%*7 z1dl|@rP@iSWaM{?$#2tbfypQ7cHh4&>Um)kQerJPf*6bx0*i)zIPP}{2?sq0*`zv> z+Rg519OtvPzbG;=c;p2iydgg{=Goj%#S@>KVlc5u{vL_Kzu zJm!4YLti7M#R}ks3|4RF`baZ6>=m~|K5CL>(Y~^;$15%4ReVR*YmKIZAG52X90Kju z5H&*S!#=Iem$bdvu4seX`@S(9@{=MUXq4OoQWgw_G?iD(XK_@tJXXBz^y{&yc6xmnjwfyXNYTmxb_c3q-}Q zD**FK`HRi~aO$02pHKb<(wm60D6H?Z-Cs}wP;|3V!F8ZI zf1mB43#fu!fl+_Pp@-ScAOF^WI{P`qn#2{AkOB6l}QN&0ZlJ%-Gl3({~9;F+}H)&z-+Umt})C*-Zj zQW&Q0ME5)f9cGK~ta5-3q#I<*-$lsLRJ12^j_$T2Ff9nFvrz*J4rmf=H%qGBdz1veT4nmTi)$OTi!Pc~6*MME!WIZ?MKY z8zX&XvbNugYjtPqYiwQj1*uvX$D|jKq@hp-pOA?f2z#u}DP?NrT;8mX&_bj}=l3}8 zw8(kLtW$vNS2DKYI*WKXXoLpA>Jw)vv);7;5jS<2T5hu!F5I1a5>twwXeERgfhg(r zK(KJZyi{NJqSQs{;c=&r3pQh##S#e}s1BQH8QJ0|226`j2=7LE9d8Q9wGgjdEv2|o z5*6=W(Z+v50a6FrCYi_xjQvrt_9tJKKzAOc#GsorRzU;wg7Bd7JhcYEGC&UXFvv#c z+{;UfW~@9NGYXHTexq*>SeiPcE>>XkINf_GxLyyoBr9bB&CA{kXY0q2-EWR+Sm$lN zMFi@1`m$xpTdM(eV_nDBMV}~g4^(90H{I#KRhG|)98iS$7{olJ&&jN1LQ~W!r8(tr z+~RnlK-_+1iu4enaoj@eEt)*))gtfa-j5 zMM3tpTxZx3lb6U&HM)D*eRzo_$Z%QZfPd)vBQKtm_?+>`| zcSu8rqJ8$*-2s?-xZngMVI&}Z6WxrH$s!WSj-qlnf7@-=m*-1O^(!X64ldsj^qJcq z-A(dDUWbApyIfJr;@n+cWFN@4o5mlJp;H^LUD{im9n^Dth}azg1jOAy7Aaqg!eG}h zY%M0_SwBzO>9XMyi8v$2=Gxhdtj^nU$H^|M$xLUTT6r?ZI-#~P-;XL5 zknoO5xsvBQSC2>&mg~q8avPXHlP{{Hsux)=D40W!uI!Tp&JFkE(ubzT?5c5ydi{Bz zpU)LXBlsSp0OWnEaJeO?heAEqmR$Nfhi=*h!iKky=iEFVxV=Se;o3t8gS|`4cppMA zrY%4igC5|JE_;QyIvg*W%Q1;)p49Z3SbIY~0LSx~mLj{E!kq9@_Y{h8x$;jtg4|eT za_6fPp!f5#?Og7v-03rk#m?htJqcAu9tc(7mXLViw8*G6c4{>qZmqjvz4>xJtJ`bV zvBfUGun2Ze&Xb5L4=PY@&&9hXVgi@qo3)-WED25dwV&}D*hC;ln*~AKm0>eUnU|BX z>6APSp7h_!On*G5@&BKk)60)N`j6*ym1E|InEtzS`t!B_x6f(dX#ZE|v>GMs!w-EE zQ2#aHy((Z|H_!CwFrZ^%lOd3yZFQtguN+UYwj|W63$B+Elz3CAQTx4hN%N<9J8Qp7*fAvz@RU)Zcg^p=3`zZ@;t&Aos{ zR)U4)67*H;AsR47*DFX8&DLAG*GcQCVQn}r7bFlTxE{n^Z|iHZIb`?Cs}Ml-1Id-@ za1q+)WXyBa_z_YnClGhPR0bbj#mCp{Mu(sg!hntQ&Vi=O7~L_|6qaUhXP zN!%R{py+;GsaCxP8zjl@r3?2qc|ucDt~BcAils;dAQoo{fe`HlJo&kHHWd=h52u7L zA*ewoc%gdi*jAbgEe(yAYhu2=aV9^A0YEE?=H01|3Rl}p1WLaXEQ}9(uH8{JsES)W z1j{_|gm6qeD!y{276#|={VeE03f-IA#V?EAL=PwyAI6j;(w^WJ)ox`dnX{EZMGQ5#?rvb}3tbA@NQamzAaBur#dg)X?)&$)Q zG#Z-rsp5OK*Ao0cKd1TYe$9|N-5&P+IK4b@4=5>!AjQ`&!_Z(g_xp1ih_03G`H-%t z(5E~1+E;;&t=oQIx%Q|Eugj|qg{LDM%lf$K+G1I`+E69L^A72*^m{1$!RGAR3k9R* zV>dsyirM#h#zq8^uHZv^cEmH?3JR}JGqz_BB^YGS1fH;RHTiu+M_=RGmGcOiPtH&%m562qA8@cs)%HBze1~Zcrj;rbY zroh8vb0>mQ2N+*UX63x(W6mXxa0nSU3qS-V(o%B*o3Vwx#Y(A9Ov1$_$fz6QwNb-5 z3Lm3)z3G!US<$I4Xt16gH?irc7$JCclINvJo+H$SgPmmzbAF*Xp11H66>KUqd%_zm3pB!g#*Ui;j z0ZxtS;y|)$Rx#nw4F`ZpM`HnvBYO6eqJ5%Xg~|iWCvxCP%?7sf7C$}QpHGNW@}!aJ z=Jhoj<%WGA(iorr_MG zw4Zud2Q;DTNBIsL031mvxtaecSs zCgj_FDndXF@_wy?C4lD>A;cX2>C5~2)xjq9V+H$v_2qp?WZ|m=<=%h)<^8N60eKeK zwSJP2d{K~4m)M$4rSekLLw)`So`AHi@Ko(%Q{<* zJ6uYS0XlKf2mr`=O=>v@G!@Ge2%Jn4Um|Wo9BuKeQ0%-OHC`_|SFqf4q$7kIf#Qug z`5Ob&TP}PyV2zk0fRo?qJG@s zWM`@*wkQU$aY$k?gS>I6a07wpT~rS`-_Ux>+xZi+A1S5*)Wqx>+l(A~c{b6`7nJ)j z&m9&82+16ZBj+GtK9lSB$K2O&v40yK{~4?9Vl;BunLCk93iT-pR=1%S(SIQabNR(N z_fR5zv}u$h zaeja^MZ$9@<`5yqL|1T$`%3}Y(BdbLnj+c00Q&{8?mC>G@u(Lb?&@tu$jG4jZlT8O zF#0+2ViJ4`ag@yOuY+Y`#1vj1Fx2odDZGJR@uFC}@$$R_!4AC!kFCv-th<~b^5al! zfb}go6NF}Fkw?KUB=_LN37?Qzf3&S@+5uKymVxBC>PJS8wOBOu@|EANVnqOv-|0BP zAv51FNBLfFuGF=(;zV36`EIb-^Y+*!B8=nrHD)pzla&iNE{{A_pESpLv>4-Phj6biQ}~|fQ7GB_b&|w6w(_MQc&gjnEkCYfdwkeM(Ou-k&$7t z_z~9u(R7>7{0`V>a__)+9W(yQru*TwKRwm^p*H`Ur-HZXA4kd==Hf!0wX>zL`(lx1 ziG)5Mg3-Q%ej4%`C#HRxG`Ha$6!+;e^ZZ9!L-3DJiT;T^=fM-X`cc)Z6~4MW0A=$o zb81M;-r*e2qsA>zxSb53Fn_-rTnT{vHH4`MRGRe*$n(dC%^YH*^+MM&U8#aZb=G4Z zf@r#+HT5~_5ji#DoGpnyQO@F8?fLmm$UG_845$cAd~MX2R+k{sIm!fg3nm%CLp{Xv z%CM6C>|l&^@ad(D`x%17YT+5w#>2k901oR8cRleGQ{}*-x&;E&Vck5oMdLrmK-w~* zHI%pM_(;HyNRxaCa$@I!6tHTPjAMR&hIo4zdf1-FCmSWkl!D{|ECRm2c7Q~b^Iws2 zEDcOhIF-WRuF&2xdj~Z?c5oJ+fS>Rq)%oQRteGv{X)%<|-V%SA@ebfi2L0T@WsEFo zz@CHwX*} z0+OfCo*bkAU*_rW*YxG%=gh~C%ePDbX6mrg@4fZoH-9io-oNMovCL&>&hj0>+WjD{ zui+_n>F$ec;0|&E+@4@g%CsEx16Ei7OO+1eqIZRF^=pP1W@+c&K@q3 zf`OJQJHICf0#$u{KCvUnRg5B9mF)fb+?c?;Ki_{ToA`uONZV+)axzZ3fFBmfT;fcD zQ68|*2!K1xbei_iVN>So%6+qwNJH0vwsD2t`<$(3qtAie4ybT;+tC6B{pz+scgV1# z4~=E1(p9-8Mp-p+R@)-y8l?m|sqS8&9x!Ud5g$LWC;Bw6k_vcfk5{Y7F|xcKi3CsJ}la z_~4%$75_k>&D-zOV)@~;K!15!2=sq`TBsG>e(|{Ivj6AP!U;|vCk7b2za~C?Ju!U9 zjE=vinSMDjK3~)SKYp^$W8)|D`>%1rN;4N6pw7!Ju0trtE1LNAK$I_~q4G!OZm#*G zsjFO}3dXEmWxb!Es=0W>faLkSsCf0r=@&OqohtNODkc;kcES35+qL;tO>>LA=zxc@ zu+tzFu;qccf}*fIp&a+cA>@QU2#1^;b^8=Ehe|)FfJeNToQA};*n9Qv=(Y$MKW$GJ zuHY-?(t(vmoJzOe<`ACde7}lk&y~C)dv^N~w)k^sC;QUC_V}^DW7I_rZC5*w195+} zEva_6Bz96ESnL*(iis5)=i~)e%^q<4?W@0ZvO-%2WX z;*Nux?|?4e4uDH!9|5U7Pj+bNir-;hIRs4A{y2d&7F+K9h~WLn%YjD$C%X$IkoI2;kIk;na{?S>)j# zrf8@>76ssJ9$y;>23WvLg#xt{IM!cluRW$P%hXWAwEp@8)Oz(fV}2h3U-rZB8V%Gu z_&Lhgp#7qkj}sy|y?+6Y5SoBJ3gxia7pm3!i1}!RdmBy_$D;tQ_ZZ(eGVR?x+k~+?bzolYV;Iow1}W_3iA7AkG-z}Dq{@1x&*}m~4eP@1m=XlW;7^LwV0yh;ff-LLguY`r^Eem~ zdFg3SJ$R|rT^WPdW}=ooy+HiHoX(oy{w;$Ux(W^ufB9#x;rzQi{g2X#^B^1Geecm|BK=+V)a;i#|UNYfD^(J2q&C4SjH!_sb;_(x%Y)a> zd07U20tS+kYzXGsEEKkrC_0AZ=AKEdu;IK@JoijeN4mbKy+oWv3ia?|T zaMYxak%lo7A6|^_R&AvOZK6TY|EPoE` zjR+qsF>dVf-wWR16yYfJpj2Rxt&umFdym_6aJr3A63)MI>b%0O!sYG+P2QcWPv z%hlQ;=bx`56*A#rX3pi&hSPE3!kuj{hw!+;ZU~cc^H@0s99pkVH@GtdP6}9>)SfWR z%N2Hx5JS$~%0e8qCpy4KBFGbDPn+O?y${Eml+qBCyjJt(E=l}=A5Gyx*9n}Qjr$sy z46-`Nb(k#9H@8}Uc*N-qHa-$WWsnC*DeT5jo<;I_GsTPO1`AtY_>5wkaggzREj*Ju z9oF6Y*oM7{T0Kwl6prnD<|@MnyxJG17qDG%USDEB^pH8MEW>Tu@>}f4bs~{l5UmH- z%C^%d8OAF+g6xq?(%OIt@m2S@qsw}0`|$JBYf`#uaO%NEFucGK2{-#yH)}FHsui#q zKr|{0i3$6iS<{2lr8w@zk>}&$<=e%m%hW}uwh9-K5`8xBD}>{W$64DiJ>LQ1m936y zc+Ef$X2m7Mw9L&zDsRE&u#E@Mm<%z3uPHM?T)I`eMfP|YXYFqt6D%7zv>ao92_64* zK1(PxaInhyx`2@*V1Pbu2J$dLNBkq9>w`ULSARKcLGQ2FzQF~x!a$H#JOAXYwOzDk zY}5#K8=ctCa~NnH&X?EviGAOVZ5ssA@3g%yND%OY!LS3yeD4666^^qBUB4hfzP+aN z^2`I2;%j%v`U2dnV%PTT1X684M*VRa=hhw1?Cjas1KON|hMEti$ z?&ilxUN(IuVD0grQMYWP`0+p^ii_(PZb8FyKg`F>SuNI4t?WMRvmx*^ki>;RI3{{{wCLSE|rhR)(|Wy@N{UkR4-;p!5<}HpJ{nYKkKE|2RqTL=3;p)l;hR#Vvqx< zGnXC3P@2$ikleXO4Yr$0>#pzErVTe{TJ%IGvJx!JFAS0rjU92;r|aGncaUflwd6+Q zmT_*$05mIl@9L2AfaReX6y0IErv!>?59k64WM1t@WwamIzAW>TtXfaren~PqUf>K1 z@`$jnlM!|ah?XD@dlflGSY9jj3V!9yW-2%6&r+17MP?5Gl!%E^I4)q;ZSZ@a+OKv;gXLVG>Bk@5X;|!g%vLN0IET8@#FoPmDA_niX07X zX~+S#Z4PH;`;+UtvQqsC2rgL3hJ0Tj?MRo;PyX}sdgrh&e-B4DR)WwINKyU#`S8gD zh8*DwE(@aDRB_2K<@dK8eQgfa2!~ zK=g5ieC~L?fBg&i2k_JdR%buY%z!=%Cw+MdHjA-*^d-`md5Q1} zY!(o32s|=mA%FP|5PxN}8OuQy@`Kf9_RY#yHk&!fPX3aq48O>LOl5e0&wIFi(sAws z2d4$jzQP9()kq>kHrl5@5AE)3{q6gt6H5a;o(&q?Uqa^?c=AtaI`6&Uh!=lshzx!eQL=Lo?)O9T07V-iqr`-2OY8X_f)ck~lHB9`E)Ak_jw z+`fFN5yL;$pfUK?|M;^W($`a>>|EIJ_NNwt7UxGio-8x#%_z;Z1|x7{51}J2bzZv8KJ;17Gnjxl#}*}+OHcIg zczCuk4nZ=8YT@v@9y>NM9m~_i-ai<}r7Yw;8FfUu(*W+|`!Sx31D}$9BW@-2yyDVqv&ZU~RJWdg`=}}>9ruDNkg75EG!CkY zzdK>iUH8^ydo=z?o>>*~Tyz0tV3*mfx=1Ri*+t$p?Q87lvBT|Ixmz3n8qlSpxxBY@z8_xS?<>70}B0gLl-e4QJCmI)(~v$#hDRXDS9_0n;~RY@#^t5 zqd61jFj?xTgD#4N)2*mJZ}*X$N(+Awa&P&7^hjbI@{0Vy#wJN1*Q*-cx#`}}@*#-G z9{htTgqe-LDMZhbikQDEalb%kzKW3kPA&XTWv^&|+EVL}py(5?{{cw*B}(A(3w^*B zuRj%@4-bKg8^%}==uzT4&})CIJQqX$O>ZlJ!xkL%U<3MSfqXIhw4%OAhvpyCZv;YT zzhdz!mH@j~Xffo}bL#791t|er#?oP!EST5a^*N{8NtP z%j58GoEW$r=pV_CHWcoJaNoNFn6ju9U*9`Xp!pO2wl4mqEQTb~w?b2!kUky-LqT+q zENC*W&P+g@kWS43E47@R3$UC84HqisC0$jl=S2Y1Y9S+l2%)juPFc?*I0A_E?I&;T z9KXCtn+3TZ-3?>){9?sEG7!;uML9VF?*XvP@Jsc0+;;^dzM^Q7^uSpIXM3};P!nUc z3q6bUa}7j_1~)$+YT1e@IqjU9IGi;H0j3hYj*+cmjlmpA?hyHNdl;TiAVBRTLP|-X zd1FBjtB$-WkkvJeFYb+|kWm;ilJvZWnuH+tY}3}jr$uVT88Mye)T zJg<6XdZlRXQ3sEF?nSW_x2DyLrFSkd@J4Xqy382_1-1*}I2Iltn#Q9O6+c(2hx}!* zi`65f_Pt6XG^{>%682H=yggOkCmMpLji==s5JQ8%s!DmqEZateKzL768n@#Sd9B1N zjZbBh=;!VDJniQzc>2r%~1P?Tu z$a=859$8g?_r?Q<3kS<`LN+~8Ds0?5zS-#}oSK7PrtvX|YH#Waxdh6Klbk@nkm*o` z@>@P}?wK~xz6zEOsVgI!_1uMCz4E@dJ0LBHDFpp?6MA?2-HKXSm8Y74aS2Y=>&}oi9A0CsEpo9Zb&9_4d*#f%XHglImI9DT z?&((^lBK_t#Va%gK*0UAyL8}w7eV$HO!Ge|fxjrEz_1f;E|*PcAcJde%<5A_*$~-F z-;t{*bw_NG&Ffvq*E_Ue<@i~d99`IHdV<-Jw49`n&J!Q#eIr_8(AUe=Os`TNx)#;c z2er`JPq%-*Iz%se^PZvtF0;4clB19tmKi9&PFD!@Itd!lHZc8H%y;FXjWUh%LV;6E z;~DfqEp#BtG;}P_^G>rU13CCN$$K^aaoH|}=7Soof8*J~CMof(1i>;+LU@aPk$dg) z&0M4sLJA#)@Q%PUeegu4u_1pBPgK;s?#S-urI74iOe%FeT{8<^H>xIMj%@Th<>zi^ zpAXrdLA$cK9S@=tal0FFpH@8#NJtX)_Pcn$Je=$+;w#ovp-lgJc?Oh&;ggz7M0MIA)c-IbaCUgdD zA5B3(Zw@pzU}MgukwJIYI{@A2%S6#FFe~$w1(uECvw;!WkOXRqz6 zIl+$wYk+56E+vWVs>QNaPQBcR@_d5k63t55ZeIc3*uT-sLfcv9=hogYw1dC^0%=Pm z*>=jLSA59*yuGFc9Z6(+M)YVKn@6S&h#M}fPwF?zQ(=f}2@fjNUGveLZ^Q1{uHYxL zIRpE9A@Hp!6ZZjLs^t6j`>*#;?WS*-qOU*xaT7j&j!o<7d+*7Cm{?SN@}yp;kZ9MQ zuJ5P5#nF%Q_8WZXOCJib85e^C1rF?~2zYDtk0uxLd4MSl#W+k}eiQgYkU;}DAgL)X z#1W3GYVmzr`)qY^4E?6ny@D=4puWH{KmDh#T3zMKjUQmo{)b#qQXTy2cJyKpP$u_tsb_2!uZr6V3+-+&GtD+=D87W)V{I zA>g$)!-FIrU9Qn6>EUd&s_ZYb*mFBtyvpwR8p9}5j2VHHX2_Ntnf)LWJWO5SVW8o~ z?Dgt_*9eDSDL<$or3Le#TWwJzs zfG}|8>77g|Gt{aDi)ff04$V?{)7q5FwZC0ovfPYbswXE*CTgBJ4-mR37?yQ<9dKhj zz_^zQ=KVQ5v*N38p$0c8p1G3VA|ns|g_n~SCCY^E)8opFI^Ewem&w;i(&{!F=ZCwn zVEH$9Vs{;x0A-I@3x}kD34=<;jRF@dR1F?)5)rZd;#sm=hBfMWMD+qqy|RHS&y zkX2a$8t#Sk8ZX(*&y0Kj+C+;TLB8wdN^2M|bz*p|VEIl(9zj)J6k~e9l785d`)~q$ z2>}=fyWU$MVVKgfBgO^NXyOQ!T|yUJb0Y^k-=o_ICh0SI(p!Am!V_s(2*U@7n~$kTXjv*A7I;Xf}kUocg<_yctu5+^-nV z`^Sy<&5t}8%&SsmZ7i@f|J*M7r7sMy3J{O;5PKl~(&3payrx(kPM!N_fbeEGpxyKo zL8%M<-(NP~Z*V)JXS{3&y&+hNn zRyS)q50F0HDQ)l`7p`lc*rx^Izad^}aOm$CerWl5}EL}`iMmLF+Vid%8r#Gbmj1n9}GCP2&gVAHhrTw%A~FwV^eTci~4A3 zFWODa*QGQpHmmRMk+RUeCT4DbMXnSMaaK@NPkV8=$Kbl!yq6E=)#|C7!T;klbg1Fp z`9h$kJKX|wI4*px>A8hH9x&owp^yPWF!Sin9-_Xgfd{8`sY68!_UkubU$T(wPYNv= z>$1=0rtc%bW|1cwWaAdLYK;A&kJtHiSIfa}H++^$=e0!{fjpgCFdhyj1Uj50VE4M2 zblbI@a0LgPbR$nkPhLR2qtqt{_qk-;-X4`WPJPa&YSR1JHm;ql?*zZGXe1&+`>@Ps zi0B=PrZ6Q)u;(MX>HNtd<*2^EX+V3w<8i;@_>|hj?H+?QVR1Erq{kvIP1k-e$wu7T z7ZL>GW#v|||H>Mb)^|@i&^6UUsKC0zb}`<|L6G{<-)_9Z|M8|fY`P!db(w!>VX@O# zJO+RX{xWxiBh`a#;^O+mviww{Z*#nxQ2b0Ka}3j zNCys;F_*Su@!5*u!bZQIFdv4AU4X+L3J~8_015u&tlSCuX8jT$`wzc54#qE#>;yW> z2<+88xcMQ#x&YoZ4qmAR_I7{^x{TPBfb^@_`s)L~;I#dDV84T3cn1*SH(Dz^0rCs1 z)%-lz5QlwlxB!IxijD`O#Zd$`E*^h*Hb3>MzhMA>W>bHQkbzkgs8K*80OF7u`|9O| z&_B4**nZqL|JD2RXKxSk-+h1n?CnARyYJ7Ry*t>$^Mq7Tc<{;Y*sX4m_Dh_+f7(x3~uQZJl?orZnUCRnAWo)V~1l z2Jf?K1iPZn_Aj!&9`OM~1ax_!{H_cf)Msj75G2IIc=#&Ne^5XbTEPo2yTTRdT@p#I zcR_jb7ak|e+hzAml|XtYWxGutJ)_cw3?p>_{I4#MGnZ5LIrODVR~W`v(v4@;`zs&6 zo7V%5NRGc;2RgLDyl{-`N_=jt{p?4(m&F}4RWB@fdY|A$sH6zcV}}jWD19i2ri?4v z_SmH7lwY?!?&h@Bqnf$|IXJf?&#OcNY!zEqh)wYvcg#XwP{?CI2C=`6u&Rb)c=l7f zy!vB$+VK{taYcN7_Be6ZB09hG9(o^+=O@-N?@PkB7kBjQ;u2mC6B*z4t!GtxBm=6} zeo^NI`>q)UUeB*YuM<-djH+Vla)FwQR@VXD1jeO>{pIJf=Xc>YzY@Ll$x2^!XLq6l zv)D(cy&1zB^@j)99Diov0>9TE)pK8yaA2sl0U9RFM5^|2RY8dif+1z|Zr32U1Qv|c z^VChdEg6=bhWrc>5VcTH%mwhi`wrEllLLI9Y?92w^Bm!0K6p`>w)j2M*S1YS6G>+< z0TtnsP|Z^$Ec$Mu*=cv#8^x|VBcb+;q&bAKPpkp$r!lA(RGb)v9e0g2btsB=Srj=I z*0_N{2%hyo*(9Yshq>VPQB9@|zcP$@at;^+Bo#w?v0b_gARF>=*V^ESQdcUy5WN8V zEO6nbLx5}L4y?*&fh#KP8B8imqEZUp02Y$ZXnpVn{4(DjNJ5kobUhB-nwlrppEthV zWdi{tkZa3ENqae}_cOWhIUZvV7z zXg*dwkKFy?sBogQ!_CtnpLPcY8hHc+)-s?v)>;o&ULkH)yBftcu#fy}g!4F9boQSf z*n&Lgjz2nG|8E zv$sF20(zc%Ja34x=U2+ckV9DsbW38|v;(VMnxG`j6yD}u4yyU?$y2_Zbqfhhcly=3gu-CDfcM`PS-IPQ-Rk7yc zc?aT9%3Z;FBZnvkh@0`CJMUl`T6QkVllbr;mXTGK#;)!+&zvp*ORtZUiQOM(b|)h5 zDSKQkY~Ns{yX;Hnq75+NGpgKcWZG9tywJRGxGqhth$xwK^3%fS_txKD{_7cv7X2Ej zw{&_hM+QTP>xAajxk-DMKLD6eN#4(@)9x0Cd<^mZE@~V?`9|K+?zZTF5zx=Y){dM}y; zLs_WzN{c;B@1;=CXlpl)PPVwb(LlJOoG8BOsYVT*>KkO#m^lJCF)k|GvM;k`Up;_% zCcfp^pA`Z2SdbF$mVn)-LwFmDGz(|(RyJ}DT3i@rliP)5Mp2Y+kOV+MQaeKSc#A1- z-I2`39bZZ zo5-%fCee#-c<|{dPr|kM=Xuw0Q)Za&1mo)IS#1ltonfo@%)x0a z&b^%lTAb#8;48A@j!0^B50{`Rs`wOvN33*G8$|VQS=bxTDEnc|6?C$F@ zb)J12(v<+G_zl_s$wyzOt{=+_n7@7z9)c}%`vuYnTg?yQA^GtDnEVBrz#I4T)O9ts zZ&O!D{E;j6`fXqeG7%9BJH|ImKI9X9lkS7(1_r%f^@hl&M-44%;mcF~g(D3hi$APn ze}hu+5$KDC>H1*w>TUIJqc`E3B55=F96QQOSX8QS`?ose2dE#dI)B{y^rR{je$)Vn zrs&7?3i7P-cBI?)fb6LOXa%%=F;K`h4;jdiIN=3=MlVRB&+zmPr=A`tL`reUt+vI1#1<=cUe7L z)219D>_~jsAg48>+NMTJa}LZW|yOfcWwfD?WwxMSa%~+v0e6lrULCbm0`dr zfcaDRo8q1yM7z8_<{v9r?s3fTdysC|+fZa_ks6&D2Wik|9)_8bf-Qwf;h)3gLDB}F zdiFzQrrjED5MKSgxice7_Ci>0_*aai<_jj#pp^gInYAIu4U*yQNdKb716`GF8zAZu z{^SH8f#m7;2LR+ZVm;eto0m312`z3Z(|Jm&C0?jB7?bGtdRyl$(iZeJ zl2pom7jz+{30oOS3~mUBDR98$aGNyw)Cor#b&}rH)8$G^2-$MGDK{+#%wgJajjZQ2 z2IKSb|MFs#3Bc&{R^-hl?fOm0H{OKWqOI@KX1^v{8#Afm&NBpeYI=sC&$sl z6GRa7<8cADL%=O8JxDjP7&^c|t!|J=_|tu0WkbGX3&GIL0QHLTU9kOSsZ$nU!jdNc zEyD4a+YA9h12ViQ(;wbgYaXZ zxiu|fneGebONnY4%#&56fwr#{sColAA&+<3=F@QA4jYYLV`G0k!)5(6MUuklLzqu9 zeVr9Trl>nr1UZdZuiHRz=PT!w(YCknSFB;XljUv4P*Ab%-c{hd)Zo%`9|dL2ka%k) zc?dhiz46{6CUOQ^xDQm%9*T)P^2%KnhlB_`f}^C=x~DYc!c|KWvAbv3Uy9KkV>rLv z4K*4F5D3RpKVr>%H(N3X)CTy9yvHORrCo#EX%;#$lrZOk4dUD5az-CXI#Vs}*3BrE zMBA}!oJ{M%am@3>00&A@HLc|~TOGyE4x8%dO~FA__y7mQQ;n--B!PiY9uUr2xDsJEfi ziiEr67~d;~El64Qvj}#wGG6jW{dC&tP(158IBDfEa_~^t@8Zi9j z%F>ol=LY4+eD>B)q0hsKf=Nj6crbT@CzH}+Wgxe{)33A!{Ynf{_n4Z~_p3xloemC# zHXOo`t?$T4qL=BiTX<)=m4cEAY2MY@bW1JgNm8@avgdI-Rqy_iZaov*15TI1SsI-9a1aUmz}&^TGk!xjd>4}b=9S_qIv~S?)eUk;=JGo3 ztngsCi@;?#uRSMe2yy4)JN>=tZwcfom9YneS^IfD!9+b#wp+(6tQ)w>i(4oRMz*hw z>)|8q0QrQH+&HutAIL^TGNInxjHZ~M3Q-J&pAlEy@OG1E^`%Kd-1j4?&gC(_0{&s= zsp4@P76xSFCu(%cV;{eawAwK*YqYdRKN6?%Vm`yyN!CDZf**7!)4^Hc6>s`FBHn2lto@u0Yp`2Mb!ss|76DP!23K_!SoN+)%6 zQF$*#b_S3=dlO{Xcs>4|rE2grtN0H5P5VM4XY6)mKv4$JE#;D;4~0BL9K-5N^lBf0 z=S7!c2-V#U26Y|F7y!${vm_|`Cr;6fygD$~c~UsVf=e5s zAs9;xTlQ%rr?-jT@8=$UK$Jbq7ugzum+5nuE=CJ-Y^g06Mm)E)W8;tG&b+IzB3rNP z(UNwc*rxGlS=`q(|D?~i!pjVYU9m6CjHBT&Ie507!I z3Xr=jDdlaG>ZV+(llCq++|o6Xy_scmahJBkCnbI$I&R)xruI~>Fu$qzD#k;(CFqxw z6nAk`f`?0&HRdKwmWWxYMy8%68zN_)0i7L>?!Rr@y zNF3l+G)`*MM@k&OExcA4M#@S;kK@}>37<2dRT*{ZK`OODxdg16ot zNLtsu?ud6lJf{&h8DuDdncB3KP*?9s&(AA-6w$lYnS7kIah5!9h5=F?luZHg5W7bq zLBmqk+6;A0!=tQhyx8YYMnfQu&U`{l_}gXUC~+W{ZQ+ec=A0dP?kvGkDlO;?od}3F zxUJC6!=xnG#jzAsH(>dYKs=#B_Hlb)`Vm?7gaQrE6;ew8cXoc4(M?$|PgcGSc3SNV zy-FCG1dg+h4bJAXKXjhZvk znq!uwiWxwBs^}Vf{4M8CH?r`$l3#RG9>Eq10ZDR4PPD}N@7*!F#EPk600V+hWy=gq zC}Q*q{CGUFML|ot2WwA2%CoK2N&-imrlOP{asTXZvI z|GYvS&Tv&F>sCd9a>Yc{n@?WeHZMam&_zTQh36)SoZ_Dw5f*2t;#QgS=0n*&5-M;9 z!r8F6Jduy$Cfyk2q9lX(GL1T8g!_P;$!d&W?)et9JlUbfzqMDB|GB+de+MaBmK{Mt z?q_oDnE~S;#=%V>>gWH1y&4*6znrU~kEsy9?A1SR`(NIxjemQu-qxA2V~Tl->aBxt zYR#|WT_SAqi#X7T0xG-R((L-{^TiA%(S<`_F4K@~a*EzZ&(n5d@AP|1>5Uz|f#Q9C zCL(1$Vcu;WCu7jeNqiWP7~<%N0U&a*h$sj+;z4hUxi7Umb%sV>+RZ;|^XU;}SCD8t z4sX_Y8yw`m^3oPM7v{V$ufR(p1r>+O!+FX#gnra2b2(GoCDDqv|Iz8CDX1Z#PY9_^ z%403q{R!Cg<_oq>A+S8Y`1&dlaTb7R>r5n>I{=55r}zTqsygt#paQcw^o0E z=mW#w!+{aN-|UDoXd)LhN0qF9+01*U>X=3!*C=1;R2i=L-Oz!4M*}$Fg7|zps6eKr zyMQA~=&)eBnmx*IF}|kBo)_8v7(|{xB1<>2jzD8M zgY27ff1EdH!A%SFRr(p@O5GckB%3Qss0PTJ0GTD3JOTT;QQ%dj$~M4~>Do1i6KfQ4 zLX(uUHZS*CB1X1vIV)#j`m&H8p2T$x)I!sX*bk-WIAr!HSdQ?pl^D&rhltsn7A28wi=il`iEnGcj%S8F&E&%L!bzl%27xKr$Qad- zFq@@9^N^#4IE37*qLozQb~P;B$6YicgAFdf8C-)AcYI@w&D?hCBF|Tv;=s|E+N1+k zgzS9987+K%_e1|r&(%wj+W=X5vbD8Ja}tN{WGj02IbZ!)N&dK3^G-qd2`)S`fCCVf z>50-r+wyf-@+ilhPZ>9s<#%*1DpiTbqObu7+j=29*DR z&IV4lwD-L2J%)I+ovhsfZv!qQMdpr|r-26H#!WB43|+Wl?eyTPZ7wqI*y~=2`h&r| z6Unki_oRt|l;n>j8jbFhz261|N(Y-}7E5P)`v6c=OZa4Wlvfha<~YbMt#}}g5NyN? zo0g`B<52;VOUGUYKy1Z}Y1|#Zctdn*9cGIZiFc3`M9_J>o_04&X)fI?03yemptE9U zEn#;0k)II$+5;EZvHB-D?#}ya5U;z~4XxJ$B%y4zo8$;+UreK->u}S35Kc!s`o!f=wkfs0k2!VDpzShrymmHvZ)Ux@ z)Bwvqmki&67Wgc`58)k%r#oX+yOu`^D{T%2Bq6_89Vm1aFS?C)I)V&naK?6j=Y#hl zp(at4T(Y$3LA_+D1w;@xC1u(kB;uW+d~sDUtPBxMmfQn-*jp8>NHQ56N^P2m4l@|H zb;Z!(KfSawu<=)-E4FT>4v`#y|DL`Q~H2@96LSaQn13 zuIDk@$0(FN%#I%**U{a6RfKf7#tPaGrZk2lTHWWDQhVQ~>2eBflFC*kWdEgq#>S?WQWOytS|EO96a7yL@ zG*{tiJMW|m&H=Ch8cvHTSd={8uGh4L349QQkf0S}#lAco-%lhJPliR=M)N|+b++zn z3(9+8PRooOS)Frp4RumT^%ahCFWf5@a0FK{0wx*B5-rV~Z&7}|cYJ#aof+|i_WdBr z{Cy+{=)taC$oAk;8+Kr`0!s=DCQg{lpBaq0Kc_vhyEc7r^ls-OMRo-}WrlTpU&Tu`dq$3X9-F_T;%!X#~eFyR2*N2i95`A>47x~hy zf!ej?dw9c+wX)#m7uxR~*0-{@hIQN1-5$34f2wI3qLm+_!$XP7qN6fmSG( zYw>1FI{a^x7VFdZtFxup!M<1N3>R((Xy35=TK$Fr@(2={nsavwP5_%_1a8ay*^n5hh+wIr@DP9V_>VkLW z(+DC(u@eMb2N2uK?bmV|Ywp@?8;~0_eh{ww)v4Z35)-XSWi*xJ>&;jJS%pay|9oLU z>Up$r8*D(VI=#>4@eOWdRD&<|lk|4Ns#0X1xxPd5qi^3?l%8hc7?4$N%q05cPB#W4 zZ&WnC!d#snum_o2Y8={Q^yv5h1bhF>_4F?$i(kcW|FoW-oX>iCC4O5^X)!?yGJoj4 z>S#Mt)wmbKvl@^zo<$8GJs=d>?T?ER@}F8uKZ_Cq&H+$u{8!e}Zvh*Bv6g<8(r-pS zSYZE$Yw2exMZQq7|6~jJ_tsKl&VX74nc>%OZz?ci@0S7ERjhJ1OP00cJp(DKQaEm~ zE(F$OzWiJ7oG?+$_4jFaurn3Kqejhvc$b4iCPkdEhJ1q9JN1Ma+B%eg-HmNP)apLY zl~-tG-AQjF;z8z<`&o~k0!Tl`p||g{RuD!u4i)R+MBdD|`a(3X{hux{I;Imn72ieV zl8B}WM&sL+Hf49Hi(kJ_v)MsxY7KF!O|OU|+rC_v^fB*nuPBlS%(;Hcm2TelBIv>5 z{Y_9IFLr48W#PN?xrx*&KH@hUE2{cr$TM--vGYDqt8?F#&6Kn5*ZG zOq{8H%`1YxFGc*izQGz*C9N;k{e>I* ztMg1wj3&8@Yn5-CQ90=G%=RnE(Cwzwy1Fvm%6f`T3WrX>0tXRd{)VoWT{i?(0vD^@XN$ z+E*!o{hXQWiI~#^%7+Id3kG=;uar=W^z@DMDqPnOkm-BiJW|x9^fkFx_@*ZOk~q(u zpm+9B7EW7knkK|o9@;fgPovv~J7;(lCz$>no#wLUlS1sy5WMwNk=k46{qrSB?b{ll z;T7KN+R^*6?U>*}7bqnXY!527~X8C;uYo|#wFBvG$2n3zH zumPKTcUM2tay*Z2-0M+8$K$|CC+GwoE#*>2N8)Bb2|?uwBMxGSxK0NoqH$F5|v@`_@;gcn#=CTTjbJf7T1KF|-L8L+uK^K{=p2$Jsa1-1 zj>OJ>*wvx-JM*?G?_FuVAR)rk+({8u*s9)?c@qgDLtA@z%MM#g`4+?UZ%9%WyC8uV z91nGujm1uV&A6;pM65I8bz;Jca+cx{HzhF}AMK9c4D*5-H#lCaOjV9U(o_^K(0+cf zAU06uWCQ|+L+Qe>6-`Ntdhx{dkgBi(9tH^MOT@Rph#a_g6zx@5!in?oopEt);T<#; z&tiXB>lu&_SW4?ExdS1Cws()U>LlK+*P&bk@_6n;oLJNFC4{1PW$LFineo?%%x@UH zW$z}#j|5Y#<|5*NOwi-Fr8m>d0XIJUy?yb|*UrDJVE$?CJXs%b-8J~Jc5=QSye$>5 zPZxZqZ3|CE@ml?%dimYD`8iVkhpHEL{w$l{s+WIQH@~Z<{$}0$ESrG9ICW4`6{~y9kfh416r-m zgLxiqOFtbjH6#wkW}le82yju#dur@Z`V9QJIb?w4cD$m3UY}rJzwhh-V}|Tpi(*aO zRE)P#$`*)aC7-m?)8a5*taE&vtKDo|Ap`^czLr2;1xR2A9N76m;5-(9E`KhG`E@Y? zZm!Z>ZLwtKWY)4F1ZIzb#QWeLqWN?OIO5mW4onAwPgr&ah~mO(#Np7+JE0yN7@+Z| zdmCPNu3|n`MP=S?so-{zWEC1yE65BJkKf70%Wf?)0Do9J?j9_p4+n8Nx2`#{t!H$>a#Cjp~LDVh4?VM-TUL=wO+%om&!l%V!UB`jnIqv zqDz#hzjO=#({(4-A5|jxLzQTA@hkOVBCU9OdA4uIp3Lx&|FsJc0Fc zYuxR7ZcCt+*o(Mk@oZ^G^~M-l2fIG($>a*s>v*1o9_cqcdXh=9C3{AqY;)n^qFI2y z&ZPq&OTyG4hF90EYazRPaTjpj;_|qXFFob=A&qu)6N*jaU!TVZQj`4YHGq>L=r?Gb z0;$lp^$|TC=9>VJZzc`pQn%-nDS3Z&*(_S?ahsN#_bNT~BoS9G^WLG@^`TI@Jz(%w-v5v5Tl_9ohqi z369Xb8$sj;;~Hw$A}UYW@!-jqU4+g>cz_VMV0^!lNh(2+ir0SLfjzASLN8On1IMgO{nGiF}hM|BAU#~{~zXQ4N` z)u=0veNoh_pC$ZTJ!k-7l!vA!D693cAVLBX3#f2Wg>Oymd9U^v z@>$!U8J2_8J*Bc%k86gyH$a33`(vA#=jtO$tji1L4ah2DK<~osTZfm5{e-X%v&8^a z8;QFIKoYuvuE^92uDSTs@TWqxxk;5f$HYq59`4>$siS-k6?5k76=60m)}PG%nybNH zdWPvm;hkZ{F^Ctae-@F^&Sc+ze`wI#~k&T^-dyqGSN0x1_S z^tg7t2;yRSNAr3*gRfw?f-~4KdRO4>yV@*^HJ|r`BHJ@Iv3rv{iHdXKPUpfm#{J@E z5c`dDCS-i?O)Lzcy*4m*TJU`W+L_rGymcm8RHs2DK%>CectnE)kC0K zVXDsyzKa@Q9r~s0>F(n+UYNKK=j=@j#vvZ5Q9~rK8;|A9zn~>hLGko0RTsD*^h*M` ztNhg7HPF2d=K};ad70Zj>B1!UCT-kZY2+4877{|X?A*!H%|Z@DjQ-u0e>xjy{`@cK z?fiF9A^!!>tVn#yfWr@Pm+fi8ejUduMcEt(IZ-g5rS1={i%%JF;)}FxW>GYd@M9yZB~#6 zV`E3t!yM~Nh~!VshTrmnVaigQmInx04}xGWU}*Rdo<&oA-w3X%WROZ*Bu`wzXu&nrZJeu>`;jsHTO;n1S% zg5oH6C7>QMNpZtqN|CAcVAAGMAzAjCE*^RCXsysJmps`1K;&1jSVSPUa_2+XZ)Eji zoq~BEoaT^23y;#kk90+2t2N~zMnvh|bvp1>6qq+Wk4tUluboZOowU)A>32GE4vMD0 zsVa|FzHfG84pV6olH*xEC~xDSo7PzE?^jV9QDlEJr=3|o_Nr17FfNZkSH8Oq+*?|S za%Y^Yc&J-q>D#=ZT%Hs0ge-V(5hH6}n?~r~QqHd>O(bY9ARUi}U*dL6wS4fV7}|rlAfI-D|0ZCzTmB_#WRFz;wX`;S!%ya15jUB}52lZ~B;U zsC;=mG4XYPSaWGShAWRAXrB~X`Y5(HtqMvPo1RTzU199?KB|Gv%uj2FO&ZyofM1v_ zTSw0C?qOrft;`^!Y_S$1eHUeLqN-C744j>H?9g3rZwpLw$7h@ru8tkR4u$isYRsI( zF=E6V$8$+F6QGRmc6sRowkEnqRWHpU7I-}bKKb<2~U ziqo0ao;rX;odfmg*jUU4^n^&K8-^z|w&Hx7>>Q)$gqcp#TzsT=!aA~K{2SZS2yqj4B0LCdevu{X)(-iaQLgX%I zfQm(gL>Dtgg;?)(p#JN9zrIo|cBG{+6D=9J$W8pGL1kC8=Z1$qgGHbfGJ$(Ndu61T zoD%y@U6tlp-K-^O(@;Cb%_WOvu|%!6AKmEZS(Q_i>L70O|)`{ z#&$rGyH+C;t2Wt+p*{@teJYj-Lh1!TB)*(ye3}YRhjegg)km4VG*9`hE6g>S%Tk<| z_YONjY!2-(Cy<856r>Md@dwxTl@eZfFnww1LzaqMgJaE!?2g){7K9tWzJNOsHqpyY zQox8li5TPXy>n!2Qv)$!EAs4TJ>OT0`G%{a=G zc+0Cv<>Rx!nVd>c`A)6Q`^^Z{5znzzcd@wI-B}FCSKQ3BfGdk<>h69RxIIv{pGd4O zH03J%6CN_1_l-xaGCbCnKKVWPBU1ZZz;Wo7xkJZht7Pt|d?km~V3s@|v0BG+b%OMg zgs=Am!T=}dcx!@JN9+~?(`ssMFylnXDMfp|TLRGibvkHTZawZ0WCt`J%zPS?QxD>y zxIu6hY;_PI(rs5pLG&bcpl`#OKE$%teb&(gBjLxtm3drT@J)ZZ3jf1W!MRuABx_ zgDnp0MdUyB`cEZt^-fq1k`S}GfpRbjo&8T{L=6NM{w0wvcL?$oFOBmh&{q-Q!Q||H zZC{ICv9GTWWO%CYdycPuOJ4kA#ernf7f9KkR~#QNZf*|3Q3w$yNGHcUCMqPtG$MOz{dt`n^sK(I^BgD;R%lK7BQxq~ROL(5dTCA@(jmG>Nzay+ zzM%quB1QR4>^by|pB*p?h#lhG3umRGiw}FavD>4mx`D10i{8F;Hj|`_Uj-ynfsT9t zL_B%t;j2p1#x|2WgpgSDn~Nh{2Gi8I+no{ABS>9O3Ew1@{i4ubvNy^hyS9T%Dm ztAY(3#UxV1IbaO+>ey>H^M2gB=w-XvR}AvoU#7<#rzf)OoO5!`hDthlM zGzd&j1LK}i>$$;V%SaEKC4I5yfkAcWcQkDeoE2-bNp+s$x3Dj) zv?$YL0DW5LIHuQwYERxkEeMR~e(pLxC)7H@y}J&3SEFv%J|P+2&NR`l4@`1+N^H>g zf8zHCGU_j)AKv86fdH{PJ@ygo|^nxfzKsSeIdsm!> zB;cNyx*Pt=n^z_kE2bR8wWZbld5iKYj*@>KiUQt};XML}6$sU-pNq!J~z|?iWPEhKh%bU%J{N zZ30`!f|$2Y>Yzf#$E!@kI(i-ZN5dDdq|~#W1blNhhDq2GbJ%N9RpbC>rCZmC2@zE= z!I@3Z^}@-C%>hmcDB`7->l-$lN}l8F4bZjR{WUdjRHAwUc!@&ZO;WnKiN#r4)iL#uI z@#YNn=$=DY8Uc9jU<5g2BkO_J9XW7=*9HypFL0R1&d!w+5WJ3Vkz={+Mgz$Ly7P8B z`uA77w#E%Cq%V7!>5gh?h`^P&9R~2W59MyeW2qRgd2@wu zJ0DdFeZ;o`;B8E@DtVE}UK<_ROJZ^p%JRjA`L+>M@_03(mp?V~T`rG*H{JNX>);sX z|6;)LFS-t&lgz0H&`01aY!YN z>MNigk6P)@jwhg$@HdTu@HnCn)^)nsMbl(0p(2+6!qsByRRhfU?rP5)`1uEsEna(W z-85GXy;LtCC?o65I-^Airflz<0_!K)roJciTEJnZID^O~;i@fU7vZm3E$i|#tYRbT zzo%8eigx~su!^4*eY4uaZmt3a);8F4E~BpZYKFOxXzvliRp@mcJ3Z&`?Rjf!x@4rz zixO{-`LjfP!~X9U=HqudG@yu+se%wPgiKPPc*lO(F1yRF}WGPLiK`wQK4>%=6UIhc*XGQ@yLH6l!0bGeEi-N;NJxU z-02J`!9SeUKWrNQj{*VMwrkc8q_=m(hhy)I-1qb>5S-XFZ>((rgvu`T89CDS-pjfi&B1n zY*xw~GPi!k7?BEKjJ_j^WR?aO*Iz&LOSVqUx1oR{@haxSofgDd6 zWHbr;T=IlGv}CfskM`a=pK0poxNz1nSKb#gZa6lKUHC*HTP|Qfn0oFp6v&8HkL$Jz zQd}HPxOum|`@3VDqoGC?sTsUd6M`T5of_1Mwo3vOJYGItS~C(wMm(QKEz>>5Kd8kx7B=j66`ayp% z#dO%n3(spuOz>B;<`g+Bb*KQ zX~Sj!n+h)Vw3xk)J0Z@w?Ht-L2!-#q}LFQkkT_;FOBr6iQk#(}62=Ky3YY{`dmJ|Rm$x3Z3 zIh-|T*GT&=6ywLxojM`+J7&5|B0fn()?C9JpDGB8O5v@f8t{Vm;H%r*VD)^O5UNeR z{YkuoT`!;u{*^gkd0}F6j|NY$kUrqV>LbHWv0ofg-DW<92IpQ@UUA! z(~-_u)Q-c*a_q_?wSYVER&wL4wNGnE<~1jxsBV~dcwP#~!%>v1w*D|@f5(#+#4?{UR^t$)R%}T2u@ANy7+oRe7@V;vh!F%|U9(X5F9$Z8dl9EE(ri$1>5Yh|TT_Gf<6P8VCt0dmxQ(8a> zFZg%?XM{qIY_PwbZ#H+2sd4o#|3GM!ppbvg=b5&y(`Q_S)mK&j?JrQR9~J#yRrO!s zggTS)AO?W{*><^bCjR~sjugL4vM!h~$NB)eGJtS0p$O<$hiMB@&{zJ$N3j(_jqow{{*F(-0RP|C? zKhf8}JXX>_y`pa_KXdcnctu|?X!CwW|G}KQvFRqglJ_Ids=~oN8onm$Q5%76At`R|d|8W8hYVe#urQQ< z61|qtfQRv9DQsHWj2F-$Gy+eFVNDaFdwbxL?^GMe9CX)dd_G(zd7XD81o&Pi|3;Qe zUosG9siE;o#E`SSZAi!q$gJlJxvw>-%^(j$q!1c^R(-e{g0e0f$kjTVdMP{o9jpQe z|K|79Y+M-&aN*WW?Dhtd1GM4h7t}@710yw83218=avz(*Sz>HdRLu|ML|g&D?cbOy zMkpSC!LBzTgiT`C?xDT6^iaO4Firv-(^#f}SWP_YdD+v{0?-%$iHWeU^`@y~2W1|h zcQ|F6myq;_Q^vw(@N~lht?F)=SW@|M$j*eN-sSDpQ>y(9fw(7j2ZDZTvFfRgxd%<8 zEo?H9WOh2T6>OT&Hv9{r(E zzcu53Rn@<*;@>lI%GQHfz0PzyjSq1|^bu^-N};I@{QGaq^*=%sPTTR$g6r%)91PJ9 ze1m9Y1y3FAP)!!g_K$3n<^7-?H16u8 zas@QnBr2g!;~67qI!2K8!N%(uvTsk|Mx|N@gpqz4S0ZdrsYjdC@PT9A=uzx@(tgR-K{qj`kL@^oI&hYb%x{|L>K3FxL^SN zfM5!)GbOUxzOEcmfN15Gj!2I8p2Qo*9eY@(dFs8mT`Bi+G~#%2k1xNKPSD>xJyG>JYePu|HATBI_?}JSU zJys+2?02%+0yHF>R$%I4GEyPu{Vk~N3JksOX}j20QBaGbzptBM+aEaT9b&j|S0mKQ z+euV`iFW9wgMRbq0+~-Lj-5$+f1=P1){&X;Fb0`OX5wP4arZ)WVQ}Q;jg5|l704Qf zhtS0yjNankoy&i(-q#Uu+3{~?8V~LBUl;Iyb21O!Loe(Hpu!I|9KcP7zp3Hse^|rY zS^h%!F8oh5+!ldsFH|L*g)1M-^&2<{|H$?IrH;qA0>12@>-e|FO8Tc)^=0n)o6h3j zzpD3(`l-A44{G^VdI0c_D?kvMJQ+BS0Be8QTmcr4-rHU=JEe@6$Gq~-PJ~1&Cfq(H zpa|;a({Tz4LTe9!w_93ad+E3vgyH!qmlE)H35!8T*nVL58?iX1+rzNx;r;;316jtH z<*7mb&_NoS)XZyS4k4v-W-eEqWOSS+k$6xlGfH}KX%S!mGAxjc#7pQ&n`aC;TeALw zyg%O^O;sQ}U=?7>A!}C+ZUW3B9uK&>8*Ny^`PM}(80c`fVYJ01QxIiKJz7|JG$+oy zZMx<7%yyS4@!60rp~%N&=)>4+4PUhan$BIh7dbY{$I#eQkYLdFu!66mOa(U|%sj|* znvub1`f1mioBI3;g4Rc#H(`dGg23*knX~&{%Nav@7~4`5G5kGo?$?=*{pYUYygo=2 zo81B@ygJYxHb{wmg5c+7(^e$)sMh7UTweFn*4pkLob5)+&GXs)wrk}w3#y&3hQuRB zq7|YfqGQZViEOO|larC2FBh0-JHl(pHq;N+%wG|6$nA{E>UHIbe#aQ4=2w9a7N}vcXyIQ(!@C| zvGuSBA(=bK6G8)7gB@=6;Y^-;r%^$9(>R+e#D}k{K58c~ zRd3i094)8toE@!@m309UcXumZ9Pe^vq5gGIhmA0B-I;EJJW@PrmyexO8q)D9atOYh zy<^gQ>Wqm8p_*$>_URmUD=E6u`c{LnF8zA(ag-|F!Q5&5u&MWGvp<95om-wpIfm== zzzaOC^49Fuc?C?P4)VE{Yp=G6jI%+rSbXo}K)W5tdek>Nw>pM5?BoC=?Rg1@;Q+~HDH8u(E$5oy zQzzH9!(XkefBT=`{u(6k{aF6}i_fQ{IYCnY7?lN+VD9>^?aOz?9K`!-iu?y#-upbx z&{O#t6!2EaeF@|sW5#}usWyu;`ldhv305&`a z3x(qX_NgjhAZmJRXHbsGKK%&c{G1$sM($yJfA;T91nBpG3Q$+2|0ju_5`$%LD%0j; zTEO0Cb%l6uD_&H~{uY!4-}W8(Z9(9|`?2!x->>)EwfS$oU+=eT^WS>E-f!3Dzx95- zaZrDVA3W*y3E~HT;XyI`{1Pl%l2AfK4))fGe(a)7c@~atKv-SPYc2prH|P{HX>?qD z-IpIG9H?>BGHfD_hd@)N^HOB!E(3jk2x_YnXQ#b#YwG90gwu4~ZnKGL(_>ionpb=4 zMig%jF1cV%60m*+lH&g2ya^$FRGP+tx-WLaY5QA>;KtFRDDZqy@Z{2-!M(yhXReZ~ z@nx60b1d5%7&3+=lZK$}@?PVr*9++B4=eNHJia_9d%EsB4y61{O-kApjc}R^^eMg4 z6pGt85??3XyJ*4V$$<;l63)B*^VX74k4?IJcedy zuUHwnlYf7y1Yh(4s{?Dd*p`7&o@L{;d8cdt&VwR8B4mCv$kF9rWz%4ve>c-znl`$) z2=*I{1_Ui|-9tyTBWZiP)8`s;I{!Gq`|Z4xtKBR+rEYx2k#e`tJB- z@qP=0J^ZF_p}hs`X;y3+7X}`^ew-i%D6o*4#kU=*xeFh6_!Bgt*M#BrS}Vj)TGgyG zF|CBroW^?-wD|#LB4(wD?Lmxl?22qAK?|4OV??D9K>|f_RVI;7|44~#Z%rdx^@MU2 zY7wGuV8tz4D9-6m+Obbbd7vZV+BL;h29K@_w%- z|KhO7AQlvq_PgOrJ;~uEHSZs1L>q!S5$lgasOguatG5nn^Ho{?8--=_RaySJuyC*^ z{&$6C^Ho{C6&6Tz<~~N!PyxFCw6JVGDvPbWzGX&(X5-D*2fSZtuWHqTjzfW>^7$AP zmiJ>28GZN@NRB^m_|L;Ls6}u-YCZ?!7lwXQa$xQKJU{3cxV8xp=T`hJA`+LAm?fmtQ~+--0DOgHrcWVDbFxle~YLx6@97W-}3>W3Pcm z$J9y^j$bb$P^r)_*v1f5X?p4@5>mVP|M&q@n; zDwqHfns26;U}L%l^CD9%LQ_tlb&p(eI!zumB&dSkDHQFR`Y}thOEz9^#lqnA5~Oo9 z1qLgHRNOxZq6Tm7VRNtZAwK5QxtsITvj~O-rsj*2JqKD6Mnd?zE|=k%(}!?G)2yFV zBmdN^^5y&1viq*HBz`@`r0HsWf5w*7OL1gsABjk;3aUE=Fw1_iD-uzi9# z#Irpue}V9T2Gdni)0T*zNCF z?`5#M=lBb#uZ)@R&rWD>HQwFcrg@I$kpbkIuRN7Q@$8N}hk0Bqm~h$6lig4-uj+Rk zxc92$nnL5Zhsf=6JxedV>t67iLB*Al;i^#%&bpH((bc^DxdDT{c8Aa`#U2c^TS!>X zu{z^%?CBTS$yqxrc%74s+d1{Ng!u<@(15 zNEA{9uuOu~;T0eb((;zNcbnS0)+nd8H$zJ_&)vZa#LkBNH4Dhj^x=H89!{PqP(s#H zaTKeX6h>8umypCc%vG@~Py`ljBfp+2`*_HA%eNd;3#c(u}5B@ zG|2R(dDb0ZVnL%CRm9}Ums!TLRHD4P5V#;CeM2z?W}^ zF1Kw+Mp5ecm^DWXgG{v{bF$H6qcWRF4u`7Z!Vw~8$l2!3)K0nqP>euHpD(`v-CJdb zazYt1AO|uS>Al%B%oXVX%R)B z1pF>a02aCXp}TO?9Coz7K*+%#a1!4`vNk8i6Gq6@%nwrvdl&b5qd7>!=HmI9Hs)|0 z3mToo0@Cc$Ss>RH-`&TfU2HK}pA_H;PD^yKUss(dj@>4W`;)YkwOB%#^=oc!f~yKx zli+aGbUl>77dUQdh>ghx)V?Bs_)0FNwhng9ERR#vS>uF-ujaT%?Qladc2^Ips9Vu) z5p^CGl#Z787j3UMNCu~4nj*je43Q97dqUC4-ukD9-C}$l98kT!KObZx)JGcP#Dxt< zz9s$~KQM?|xc(}8fnDb!aYno4N{ZQv{}pw6`k{>*S?lIT5!YsFqusTI6ac41VeS>AyAhUTG?Qpr z1iOWRnn^nohAmbrmtRz}UXqKH!;l`PBQ%We_f^YitkgwH3Uv=z0DnRS2KCJ)WP{k?k_$)_uDg)NGZh4 z0sPAHuKA*?M|n9Q!RgeV-s#L+h?6}LncQq|I_}l4PQp(BuV*&to!uYsquBg(bARAOPd27iRfUJ6#)7WrXB_8%RZWUP-zDiJnnEd^aw{Bq?SNS z{P12v92o=&gWGchK#5ji{IBl+_Vo|Ge?thrjD%wW$B(b>R@4ic)xRl8)ej{p_flam z3PHjjDO;=dF{^q%2rbLX1KJJyx!b?1l45}L?UTO=&3@vUj#`;phg3{ymf#TZ2FyX1 zUtQr3Y{R65Xy<_V^{bzLe|=oyEu?1tekuUj4CKvy{{8RY@QufW3EGc*%!GMMXMSX4 z{u3f|OQ$=LYwz+9dYe9e79vQzH4X8Pkpwgqq8VvENsm%hNf4242A!}9kPFi1k~#S- zw!gWP@N!3k#7oElLU;>GBJYXAb@PlCG{@SD3C8H|3W8@5r`QbXk8%`JZVkceA zJk19W=OGK2dNf-`3&cZ)5=RP#FvG5QhjCP@`{syNV;h3M%x(E!Y z>+xvIdVsDz?8MK0MsuCFWWy62r8|(1d`*VQ>JTWAtAw;^=zM$V8JHEf1>ebIF z8NwiNS1@pyi zTjHq&lYP}ey(8lZe5FeNegKedF zqrxIOG#sE|F#c}rQ}6KDfj~sN+Ulb@KYWbQsoiThRSkhZeNnU;VPDY7-x^-b-~{NT z-wZfZ!R_`B0SfiHfbYy+wH_t}4|h}f|B?qf=}TxNQY!Z%^^zI*h5(SZp8`G|H&F7e8!o5btguDw6X zuvAb$nW~1m9h_}gty|d9{^V1Z7k7mBj=AyrLc)IgX2?0vB-L}p9xwcb1yZW|* zS7k|}dIvM&5DSSKay_lx)r2rL6$GZ|(_u!f5Cc!yy?n-fB%;s?v15VXGT>%Tc9#e< zg3X5u-C4FRK@o}i_hP@9-9@>_Rkwu^L9=VDW&Y#yl4 znz20TTMM<^MKId7r-hw~vMQ#ZXWoCGfdHDbA4W4jyXi&H6d?sdzz>!z z5sy^BIis?uu&?X;Oe z&f0@nQQJ$!YYt9FA%=zW;Ai5fuERfMAiVQr#`?DpJu^^D)pVFa00wu&-~Mn*{ILuD zr`scsy>E}y_uHc`C6Nw=sA3QIMMam+%ViZ-R=+Gku#dHA_A6NAeQN}~c=kC1Z7sop zn+$9$6XB3Kf>VY93G2`!h~piAM19^8&nw)v;Fd_AV2yhJ7*pY!2xudJJ1hl4+L!AP z=C#oWx=eJyT=+Q}eotn>@q|H`#Kdu<8Vn?HeYt1*a3Vr1sX$Gx+qM zF!eo2V||!$DWnPoPytutj~zBN*pTYP{?JM1O5ng99`2=Bw9s|{xtUQ7;fmYj5qL@l zr+xZTa*e{+U!8aQ{guK*Zu57G*>|?i55Wo_Z%5){*{Af|cyEEVz%zCLXM4UY2MDa+ zH|T$z{(eNfk{}27#0qAT=c5}4F)Ed9n&|v-L!m<)5bJs6U(YTO>(Rbc5;sQM$3Tri zm289YWFAvaXZi+13W$d95yt2m^F3z5PWSn76CcPvI;W7^%4Cd}J6+c#^J9U0a~c_D z%YM3Zq`ITb)4&=rkd;SEguW3Ghq44@C%UXhx0sKFEvQ2J4m+|&Kw#i6eUc(j!pC?J zdH?uqe9$=E?n4Rr$ik)6QQ3Z(#nLsiCL5r;CTD&k#iVJ@`yC3LL2r%&{r>2g3ut9G zqh$m@DB#Qqy`fL-wm;S>afWDgId~>7zuO*`L3=It{P+S*Hxc-C&O3++<_DS5hiLXw zmTp(PZt)$q5wyLu_?Tyyb)Ymhqrg7v+~?2J7~=ljLDiQNO55U$vs7vo>3rZIlaIr^ z1=|+=i_+=pnH(S2D*y?C&GFU&OCNYs6ym}HF`j^m!V zS`(!+$z!I-au27dVM{rld0eXtV>=M;GNv3wY~k0p$%|EXP$eYe!Yp1LQ^DMG;!kFn zWSV;qFWT7GNO3(6PY?>+rPJeD!cTA-U&k}7Nn9tLf&0DeekBiQo>oNRXY zTVLDd>mhRv+HCKf+aIp> zw%2(GcFdJnHv<;bh%vYgGf;1^?&eexWP1h27#ZRZLya@T7+BbIaKQePw`BA7cdy7c-~j_ftBSs+@Br_F^VAwSR{u3#I+r-2q<*_Aw0cnn?( zPbIK?CF%YIlIg@p8AEeOqurcQ@y=2fXUR@}M+=WT#ht>q57?W(M(xm|Gb()jQfvhX$@b1_4zj=?bGlX=f6o!KNQ0_6m8Rgh_2tQ28RDFW#GroH zH*5sez7A@@c>cBP_!MhBei3UyuE>l1kZF7p%Qo~Kra@qmidUXfxNm5J93lh-rw1_> z9D9HR0*q5IQVs8~- zlzh0h-rWgIFh87I_BSmSG&ry(e{b7VK%l9m0)OWO!7}d$L5%fJ@5jG+J2wCJ{rER; z$NO;muilS;^LD_GY5xap4a*?%9TkY?a0MNZXKi)$3HQL}eh8}Pw27=cZ5kam9FC}E zs@6b)UJJwxvh2UWe|jR8vVRsly@Hb)^)SV!-VUA^iMF4Z3>}( zJyGU@(b)<)a(q}?7)-725Ch2Yfs-ahtJYR)yJ&P-aP7chkTlDt_SqPiiMjY?r@3DF z43wcqHhb8j2PuvbCeqyGL!(~$2SiG2*SV3%M3GS`uRu*G??gb4E8w>^AkwBa6^m!D zp|o$^kYdi*o&gJa0vs#-hEnGp?#EMClgBf2YhHa{lcR?O5WltMY;|Wng21Z$^dSdv zxs7vvFG*4992o%5doYpM6$J1spPPb&RbX^ALeRhSA_*=n6c}j`+TwG8dW;R`lGM|n z%4@57Cs<@;x6E5eY3n6IxdkN^;rAdhcu4vU=Qn~L4rj8`W*98(BKMX#ib+O-V)EEa zf8EMb3BaflTx5&Za*hOV6$s59ss8W|^q$;hh%}yerVG0UdAqrdXmD_N<7Q&@B2FdiYC4<{~%oB@BjQE$0>_(Cwp;k&vF z!X@6&Gunm6`F6>Ig_1((ob_RgANUIbiK<7XR*>{G_c+rUF~w&sU@-CwQiV#8?w z*KnRZCy5962SS$|cHG@L0ZZ-A$C~f9#-$?1xO6$~8>O}QLkt0*nB)ykzer#`WJuvq zio{KOFhiiYPO{`IEoUHGL^doKjN+}aypWtPhvji7eDu}SO7AF=LI|^yYY18KuzBfa z3mapnQkcDsZdsS@Ec@*2#W|8p(GsA;*(9h;+^>B5LaPv++TnNsZ?kPjYnkbC3EP)Y z!-%=+b3e3DqIS=_f-b>2U^IqZ0;()b&+?H2Um1!ln*`Z90eJ)AviqF2ZMj!Xo4D(( z-y0WVVu9ySJ%xrwAbHVBF$iBSY}GLJXZ19M@OhM>WeUrK&E z7xJQ3Y@V}OEWr3s;I5nlshhmV4zf(}Bug2O1~Qu~Y`0^pAc2&g4jOk>*p}y``HT&+ zL;9ZLA&$NT+f@@HMljxe3m2JE0*Ri2zZTu;l3TR4+@#3U=Md(t0Q{sl;R+i=i}T(E#xLbV5eUX~)_=nYxxY7LAZ7aVj_j{GkdG-1LuC4Q**%bNZGG0i zCgj{YNH7EK-vfSs0q(|@2dobM{u-Nyt{y8=~=oH zL@Y7R;pA|}K3IThysdPw{B(dLpYDV8Xr>F7PZnc8_9`4cj)!FS?A@T=_UZ1yG(szH zoa%81^$|X?_j@E#ppyQk`g`ld49xl*E!#6tOK8}<{po4?OZB(;Ad_75f*~Lb@5E`% zWxBJ|7DdQ7yOa0s7u6pudcIVDH;u0)qU~OV5vGsIv`!nLNMog(pci)cU3w#EPPOus zG^J#p9AR*DeUQGnpkDA&G)b3cI_2?GLbjCI-k_U2gVwG$_xPP(KrWHQBoBb3oImu1 zuJ09&%!s;z=<>s}xM=_-XJq5=?l+QY2%n}@?`OI3Kiv`d@O?+XzaMyvlO*XY#hSXN z0^j_aja$gcfw%XocK^7QvVIU2eUXQ>0`5rh2sppz`H zKA(Sxp?o9PAXX1Qi=P**p1E)_em{@EloY0?KuS@FPX{WTQGR@^q_00tOy8P>chLAl zVdBpTXbzDc;820n)Y}`A*;@ycJs;^3KV34$?mQ*l>BU+3eOVY$k(@A2npiJ^Vv zt(+*NU*8}4+r;Jy&K~BJfnxhTy^(kAS-fIAds}Ogcy62}i*t&KhvR{`!+;*r)Vx+K zcS$T4=;UQTDZqa(amcG|Zar0^+{ESe zeWHstc!f3~fa>LXD_`@p#~xA^t&o}lBS@++31nA$dq`RViwBt;YC9`!BqN@8P_RXW zGGcb+l@de*4ef-ng_?p+SuzIvP@WD*i;TPEQ9=vgS_fI<>;YNlsYNLVY^DHJ!=9N% zOF+71)XizBGs02s6nzb-yhgc;DwiyBhGxUqbpSjkEl6z;`^$KqT(Y1Ezlv3{>XQ*n zBEp#@hw)w)hx(O3WG|sp>DFL0ej0bjeYtxfHbaxe6%ZH>iAcLD!?pmJrq~AJ#e?y1 zLaQ^K6!Z?l?BpS!_u)(@4_juB@( z@chzYg>I3-b--#MLY%oU30v%S60tC^Be&5ie%$2cnBF|O;WCGCQw*`a%wcBjS|+E7 zjwA#)b+NgFh3T{k`;peU-HW~N%ynt3rpymCqn+`3?{$w*JoFKt1I^ct|nb}4icEL_3V>(84$5Fdj-E%{&A zTgZWL>XZU+;)!1~xMUXk;ct#(9YuczsCnC40HNj>N56}frQ=zhy@()2E3(>%5m^+K=9F&xjR0D-Z^hCi_J-D zRO&&UL4l)Qx$3bxb!2x0gqMNCY_!b?X#(w!bm-sQfH@-7j;&G-*FIp~Xp(e?}Q?~QbMuF&!@i+9i6qvvfL=X~BetS`m365LW zQ*8)i12L^}<8o7WruFLBY|b1cdIj)CWibjLlWXP~2H|x(D!@tth}gK90jO?%oxlZ_ z!VUA4K|pYWrUpTvz;XsVHd28*c{ioYc~c{IPd^^PX^$dhPI_&RpBj%Wq~VzgO#HV> zk`v%kPg3w3uPWC67>x1<>E1t9?Cf2!x8v7{^6CYestECRIlaYtiJc1&oO!|^p&0xN z-%<+Sh&6@nf9hd3r~5nnnft`S=sL(ra>2UeHrT>g%i;os{#GyNS=VhC5Z?K-ThKrJ zosZ;cSXI6|WzgV*f#zdp`Hr53-6rfe-@D8=AQci+e|h%5d-Bb{d-i`_z38bB2XR@?ADhniRo0=tB{?hi9u67St(ji zt1I&HaCM%WXwYdJ7)Q7I1z^<1kmzQdl+!~H+-5gX6WH%{DUHK`JikZwH)~}M*Zjo~ zH0uE#58M(gjH6;N9sSFugc!!FT8H*9;vhT)`0?&}*KG#EV3q`aH{98$?h}9)XC&BB zuturqXeJScWrUTwPMlS4)_=IeYf<)FLt9>1&SjoW*5doY5)j`0lNKlZGBsiWz> znygS`t)Kyd3T5OyRk;exGP(mLmMCPQ)Puty4G#@JB8v3`WY&$f8ML~JeV&@{q*!&{ zHfaH+C83W@*wgmj89XYNkoZEXoj;m4gaGc9LQ`5)z%OP0>f>F3&%`4&6^WQKh!266h(-8U<;{F$&iwy9j1eZW$?zOrn2&3<~?H z!Iw_@PGRjCr2QNmtTLDkJQd~+E~s7rI{;5Z(j%YaEvN{MSt}0Hr@8GdmC(naCI@hu zSMhSZ!}4ea!T&E>4cMA|tLQYqLwBfBS`*7|8lJCUJ6^QV&g3_BmEA!_Gl&n-UnL*| zxnS=mHIE)o#udC^k7Dmf$WUL*2)x6{fyNE*Rk7D%Q4dJJVGoEJq4(fOpuISqXAm#R zqlQ4zfxk;><19eUPjrHlWqkvcd3?}9c}kzfp4;=<0h@RUVpocg=0F_gk$f1a z-a1aeE~XFG>owAx79ojGZKPNE*te8n*`a!{rhC|Tdt}id5TgrDW(Bsbb|BEkj&z73 z1*oHXnHS_V^GNNM+qK_i%jJO&uSG%eWOFE(Blv^MZBg#v3IHpyeu)k z^rC*#qE?3`aH~PmNpJb#NFVP7{4DX+_UfLKCkR$o>kV0V^H>U|ey6*sd|EL`#Z=rq zJn5}70Kwt_?72jPd(zzA4M@s-9;47OYRI%!5|=Q@gTn40nC?;@Y+(bDxQhSSp+*LY zpa+TqqFzd=I% zQ$+_=v@A5OwcQ|Br(xMalgVs4+1IvtWgGL!`Er|ElU@^op!J97T}GN z00q9FyLg_~N6=T^Xa1YR4q}n%$mRFZmiRi%zGq`!PFWms&+P@y)t?od|FFt_;LX8x z_x0EeV)~16x_>;;7qjB`HE3(WtZ?wX2kgAc-}?!84*lyDeI4*WCQ$FU0TlMXj2gZj zrMb_ewB9mM(Lo7)ITM)(qg~Z242Z@^i1x;-zCs|HxRYg3b4p?y+y`RkjdG60 zO3c%Us7pJB%-=f94L96_}%F%f%*@+`=g zoJF2-(t#l9@HpJr0>2!0eC*XAdVJWhlP~0Fbt0nUL|t4ddc|=HQRV!O5N;Ctf2ez} zCdJY8+xMPNG53mn011I~Den>T<_=_#gd8%+eER*6Syf%GyL$Hi$2oB(qGx)jtcoNH zz3*ez`Yn7BA#8bTF!WXe9Oj*y5PdPK;%<#M5OF>4yKe~Kzas#@HSWJo+CUKhb<*}R zXp@X!m7Lv4oivD185zEc<`dY4?~U|*c?tj`j{brG2LCP-5Jh}7LI$)Na4=i<`#IP2 zZVa07*d0m*ms*zI3ft*XI53P?1fT3~b5gUCgI}}_!xTlu_!o$0!ZC<~O(jR}oZ~gN zJBC4@aw3KtWJ8rV5P%iMu&w&nZUBV!escOVa%Zh0)&^IKxxM^lLulS|az_liJRM(5 z67J}3ohRxNuiVzjqw4eEW&}uIpb!qZd%^T$Obyu`G}k7K;UbzDQFQU+Vu{QZfOzf! zHh_zclJ!L?pO76?uSZl&VOd2=meAEq)7<#<4~19XzK zOEfohGs~b1LBniPA!jm9{6wJG+oN5&r*EdW2JF>gFC3n@XM^O3u#DGBz(0?n-v`wH zbcX$^=KRwc#($h)Fz0zELQ{9?;|$xi0pPa?g+)9exd4&78x=S`eku*#`EdWUXV~&} zw*Q+m>^Y;M?NgXpypPETV`BXj~tYvRvb1D@fLbJQ0VD*0VX{fT%9Bc)jM$JLNCGe z=bedXfxvWbgB^gPQ(r^Psc%mMM?r?g8)WI<_c2&d;jG@Aky=TgcvL!jsVb0B;@csY z{qU@2mhjMxUL|{Ag`*J1_1x*k@j}aX@FGDj00RZXYdOeHAy9J{+cCUXkY5d=GTKUD%%}cw4i<86So)m zmgei>R8ic#dAtbK-CEbaDUXS3|g1z$6@=ECk6Gm9vz{{`3Mz;!)zsK9oSWxB@N^j&vov0mb#O zpargy;o(ST5S^wY$-~G6q{E9R58O^|fNc!!gBqc<&VAJ0wzEs^rxd{v(nMahe% zJIc}>l2I;hCcfZP(zFZk(ZcOMIwHQaG&?*!>ANNv%JU}Ls@mO*@23dsu=7|$(}GYP zwzyR0Y;VTESd;H^&tT`;nd3#$ZrqP)MQnFD3OK>CT5t+4O)iO<6xm?O_+Kgi{}TQS z!cUq=0M9|Ts&+AWkCLUWeJPzMsb=~a#FxK0A-*&ZU?-yBqC7MZkh<5fiVU-(jx^MU zIx#*F90>1OpsEBcz+*LUMRMJG=Q&Y4z^xI}!Ppr+1h(D+6?BMKsyUn?3&ANv{6)g}DhO%-B zgxZ4MbEwM2?j*y6{J}k=@z>*WGaFYZ;{)DGPt^0ESSiS!dLkoNG&OL!xc!XgcR_Ef ztyqpg-se?XUv69)^TdHH#b>o``X&>dr?snvhc|a8S&s~i^0=F~xf(asA%J9E4fN{p zNa{?NRFv_gtk3?V(K~r;EAfn{s=Zs)>OfD+0=MB|MiHqAnVM8O{DnqGl5T*Jn;o(G zbRc`HdO;eJMU;0#Z~^Z?1O1|@(ON})>HxI3REzUPoS|t1e~OSo=z?%8SC2aZb@Ql{ zzBFSSG>AvsR&5w=4|M{i+?i&CB|2c^K+s0u99yG1}0IT^*Z-K?INbLYv!&H7tg!CpQ zpu0rwGz_E#0?>zJjxO9-Sn2>%n8**F)h>--5}@N*r~A{|My?KBJ;kG0Zn`mP^7pu3 z(=wcjZW9j2!YDv$_){X}SK!QtiTDRN%`XRGHw-VIZV%X|V;O1to!PZ?X&3;6&#n z%m&dZo26Kyj)G2Bd3qw)a`cmiY@>>PKF>Ev&UoLv-L}cy0ETFE&~?&3nE8f)lvwS^ zsxm{uax2f2C%s1iDiqvr9DJ^ENI{vzS2Oxw=}P~gv-mOS?&tRJeCg*E7{dJu*^<|* z`x+zS5DqTl(Ld=dzy)=uHktrt751r0sX(TgFzj?PDD$m%#P`Spl66-!bf;x46s4S) zN2C*M9C9yWs6wN$^Y(IfcB^-nW=e1OP| z7__^$uxSY(B;@ML16X-g5L4`LzVqgWIxf((T%Z_f)@}l;`&%uga?lP0-hTgYA?cC>_J;;k)OD82rOF{NwDV z_qzdb_vatH0pRHSPj26D)4|;3?Og-idIB3N;G}^AuW(>}1^XG>edmnr;5En>`|CAL z5<%zHPYgt#e4VaAv$J_t*b9x7{u*S#VGF+&G%e#!_ znzXghAAczu_~D)5AKPZ$Z>Rt94@MvFw+Y;8_#1u?3nhsLujNl2J@+~6HwbvD;R`*ivNsk1PpBP0y|*|7KiEg3bVFu8mxSC{=f|s|MZ38iD&sI5 z7GiUh3OrvRQtx@$pAnNPvU`~X=e@YTwl}5V!T4|(bQCOidUxYiLX6qygy!UmSkLuE zyW~;Y?WJ>f#-?ZkdYCLRfyCL!i3hd@^NSsLs^SW_7C)=O=xe$ytxUN^A1$%(;i{F!MV?(OMp!Fzd+}`hq4=` z38n7@U|>muK`hg34=J}4PmuQTv+DO`CsvKT%M}P+j1w2zgPOpiGVy8SRE2XInkspk zZ7303RoZ?3l!O#l?~pV}=>1uSo(6l4v7`EhMUtY{f|?CUGXH$u$Cg7Zh%m~ zT<`;;9Wes5?&UetZQ|VVrxc6Ed^q{J$79kKKrmkLjBY|$3wYJ ztkAXW++K0hmk#|g$;tvXHwkv4B=@Xk2(~bL%XPwwD?Oca4w@LYz#Z@x1dDTuhyoJE z#`b|E04p@Z%Ba;fC4nI8T!>aW_=LABHc#BF5CuE2Q#Q)^e#=jh+(W1DXbeQ#V&L80 zQMKDRK+$E)i%(A=VE3QWrAhvI?I5rq82)f#Q;sb{^6Cej;+Jyl^N#t`#lk~V3xdRN zTu~`CDu4T;#be$4Jdb}(K?UM>~prFOYb z7j|94bu*F;q{|Qw4)*b2P1XTglNenOrz8yH5bC1rc7VU?awa^vPld^{Z3hZ`Hr*qm z;vC4KI`v&IPBK1@hpi1gGrlDc0U6a@w`tQMw4Z5L22-y4bp}e@%c?Hs$cY%Qam6#z z2N-N{{Rwyw8U|IbY;X!5EeA!15JNN#(<%ohrfnPYhJN@wNT(y^Ip3LaugD`nzPxu8d^!OC=g#amCD%4apI5W^unRBQ)?hS=7) zFtPSlxM9&$Zlhw6G=VwEI};-$2AP}FF4^6y5maxoWUyA3V>LTNcpXy+Vlt|$sp25w zC8_RY4(ON|xSPV5xG~Ygv!N(kQekF9$fG(<4MUtsS4oOjV)au)i6BWSr`x?Z(r^YI zTIlbHn_K$0jsmZxq#T@Zts^~e$gIf@WII8?WWY-aA7j9vA{~SkYsMr`JGZ_5A~z!5p?({;!`zfD z^;{n|nVz4Edg7DjGA4AMWk8m3ckp*79^Kyd0hP$;l@&`EP|cMN;*w5@01~Yl!#BrJ zZRs;un!O8gOMAfIo@;d%D+3fjV&1M17fi2xx2RZE9)ca*l+_Zfi3X`}d?d7yH+5nbURT;?)1Fcsd*V`bCNny zQE*mQB7PznBu=-Au@Q&xik)I%t6v4Af8C)pJ&wbFwFy+3i~_x&iR%${SCIW;Z+`HE2XyIfRu4q{&>K)S5hkk(`>lS(?c8LCi$J zBSvi;08vq&`j8lu1m}%bT>m$K!-UBJKQSNWu)9}p$x30h-er8o4td%5y^>WK)dMsy5x0EjxNHGY8 znPI`@s}xXab!CAivU}N$m47K^F>== z7&dqL$Q<|(7f_Snz+@Y`xeV5p6ZZ+t6Ip67z|oxDJPzU^%N?NqzM)b8zI*$^4`JV_ z5*r6A!J6U@U<0zj-TZ+5(o!GO_+93t%F?*=k!Qi3UyuUz1D6H9o(5Y>0}zrHmgbKc z-q1%8{N`KnaV4SS-=`t!D&LBS3{A zh636`^uy)+9c$(-cW^lFJJ&bF0F-az4M-kr4r-H6(>Fa$>ib6oko0#)cLqoIW0qMe z|5|ql`|rQQGY;Kf2_W^mvm78`8ydSD*M0!FOcSj0KXO3Kk00QNPw1obI|D-zN~`bv zLf@5BP@8=JGyJgrynpWo;<-Wk0Uhxly1DP0x!2){m5Iv8ncQh}4J6aM^4wR?4Uk%k zt;C9sIO=VXHQ0DAdmSae59-D(oFCnfA%b{lr;56iJBP!vQPJ$%g<=aXQcbBSG zjyLzPX^ktfH|T^hx<$rXYs+A6pFDTD2kg?hfwZXl0x|$iR-Yi*oDoX2k3jJ2l~5U- zn|0dllWo7%n<89ypaJfm`?g9*`Gia&oYG^YmhR;`pm+Vjt*3}ZE4hn4!_uTf9?F7U@r?7 z4mn=LY-c(EiZGc+*wA>Vh4>jtQjodM^bJxzWrfE#6${X`*LVtBT!LHDIIWm7%HuA> zoUJ6k^r%NNH{#EsB8Bvm6E%2rl*9*o$5@r#j>44!wYT?qPQK6(kTGPSxYa$W zc?G#_ppDAHskZiy{Z*tNQBlXygd2~GEq4}nauV5MUqyUg%dIFr4#YuvtfkRkT3hul ze2c5};~#fMzr7QFm2CXG&QDNbq)5}rSK)>vU&VBR3c~}>+D}TXkIoP3*gd*n7GMnPuVEvw!AU%KAL#B8}O9IUE72^)`Wh$Cd&};PCy?WN#7}CxVsn1JET2 zCXC6X(Pj?wcc3m!{QhYFku48%w!cCR-f=tWm-RDbnor*MhvygS#aqS-Jt{ZAJ@7Fd zgmx10kc>B19_;L3#0O3LnD9^4xu+;t{7z!#pCw%iuvYwR(H&8z|OZrdA}DNpU<4cUt5#rvC|&i ziPtu{u18gS1){o-xJTdZH`^9tjtO}8A2a0Pi6PF;(mhWxL6T96ZRg(9p_X;V83whY zr|{SW=<(Egfz~_nAp1Ddg)+@kG0SndlxJ1Qz|!xy;DHQ>1O9>}MJGlQz=oZm#L0OA zCX|;Ga$Ur8xLxn<@o|}+k>*uub+sg`ff%s|_TqcvvPmqER#GU>*O>3J#r!C500LL?!#-nAg9lW}M-rqeKPvyI+o#Ll2# zuB|@l&Rg`Z7c4Y80%;s1D-k457eV57zSmd^N-GfAR+2uqpXRF%%m%N>gF2?tuE)~4 z-8|a#)^j04kmvl_`iSO5&ehEg=GmyHA}Z!RZNfE<)FU*mh^-lgG+4%}Zg&r@ngOKg z;bYmfI0aEC8+5AGw;2x{u9<%E*T4(|91ypx(R0E|0)-V>XRNvM4Gk0(@I9FQbADAv z%52*gDF-ts`Wb1P=^l-3tm7PJ^iv{YOtrDX#}RH-({(h`=H9C(m3({abkWERzU0fiOKd$$|17QOp^7Z#WmDuBQQKt6b}!4eq*Enrl^9Ypb}4fy94 zhq`NHDu%8|oPCab^oH7%5!tRvOgq!^FNVQ*7pEDg_8QnbT&d**LuVN1_r;do^PnP| zrKEXH?Z%l)RI{bM65y&Ku-yGB;B7?mfYV0ddWe&c?<`5!HT;FaOI7v^~xljUSFo|n%h0_DZk_TfiwnCx9Owvs5F@V3?Nv!%-Lar?~ zu&;7gajborbc$|d>ZrolZZFhgpjFr7D?zfvRLDZ;=;K^x4|*Ra)$xvEo0)e~8Cxyp z?4siGaI+nSV|K=KzHd~|Xm?Pz=Y^Nz5iVu+mY?SPibng$^J<)ZKsut{hl5F3&swRX zO}krU{-BS|WF}6-sA49SoLT#$})zjhdom3r zs|fjUu#OcE`ll}mh?`)AvB(dEXOpV|POu7lX|K>~y2V48LD&)p<3mBE?+-U@*uNym z{P?w-pT79=@x>;jlBIy)w?Q-xSm`f06F(Tz;Sc_4A>H$v5BnJ`p=tZD_i1;kh+2$L z9)?LTXKJ>KvEMOkZ#iRVn0GXe4^`a9{&hP}%k$h{2NORR7zB3jhr_tpj*k$YD>j{N zloGYxyKxykil>gwT~@hltMG;?OLE&=wk84sTpyXhR9OyZajf_CHe^;*q+%BPt5(o`P0c8TRf2d@P(35o(F=`bNrI ze{YLBzu!QaK;nz!9W}Z+4~0ND@R4U#`~bE+2zN}L${5iKGDsBus5rE|60`)O)D}GD zVgV?oWL7I}!0M&rC{|1(2Ut zN^2j#E3>Bbr{UfkW&#>Cz{7r=!0-`w%`y@A^}BQs7<9&9v4*}2sIp(__0R-^mzw-g zqrAtC5rlz!Nxgjo69ESC|9Q$eN0euiW!Wp1X&LA}VD=q+%A~i@0 zc?lw0&u3&y0XiDyV}JARt{Z26);4N&egQxGW5)Q?5fAvGADKolmbE`EaKB6( zKD=LVuf&fWmCNITU9~QaUWW35?ZLsT@a1qD>ohxTy;rNRN2^55nUBTmoMBfCa!f+_ zO~#hl@a;hj`!o1<4*vb%>+ZN+c-X-QFDGYQ8zS0c-MH0v8{|yS>?Mr?p8Hib`(zJj z*AsEmy48UB&rUCh%?0J9(~V-md}go@_(s%0KS^P^U2m&jMGS+Zg6oN7^7a~yQb?)s zt8wW(_6?%Bbdnq@yIbAl^Y)%>nnYi)kei=NAJZ44w7{@-47`W7yvV~4x@U0;Tdr;9 z6S&1sDTpz2=|~9vOIiUg{m3)M+|83gUY`#r>xs1+`B%0riSk*P!2%0bSKi$!T4Z&3 zMlM0vw+2aN^~8|;!~kYS?P>uY{!Y@i2UE8F=T`0bfW^X1N*8YbtU>m3XQNzIbcp>u zftFN#0#q?yPypnS5H9N-E4n!XYw}!;{Lws7J&AGhd7+esr*EMF#!^<^+#Qap%i}gB z9H~;IK;>kpV9S@z#H!t}yGy`}2Pq?t*{Tn|8#&e?QXp40%8Qy(eKXc>J}x5z4J*lw zvpV}Nn?9y(81vmBi^!wJyz+YhJmIpHpO;ro03Q8@y+FQU1>BZ#u5IqN3n33nz2=^a z_?`ZOi!moQ`*{d2%u559ob*8Z8VC@Vt^TMGt(_|jvA2Nv%eZ5B+%9WEJ=REVtG2!k z0(R*H(1*;7WJ|K?0WcX-eYoSijgW}BJ#!6#1q>+^9Xzva)X;`&`$a#I45w-N!iHCYC(o;%DI9 zsMI!$PJ@}7<9w_UhrT`1(ygvU!o_qXUgPR;5*%2EEc*5K6izgb(r6TMc99PlhlEbL z$%~A=Oak9J3C#7c%S(6i>m@$b=X-y-H`&7M)=u5>_wX^t8o!Ss59w2?0QhEng>ojq zQ6O^Ju)Xig&A8WP7DD%&GtQqL<`#rFhfpqT5|p(WnN6W@pV;nUACJ)`xIUJ$Uy~8p z7dD-ro(_vOyg5ozHpgZG8E~Dki$@&+Zm&y|KCG8-QgJfoRgXq~ zd)l9+jW>=IVP%07p%Y#DCwV39hsd-vjr;QHp9kjus`da3Tz;Q7lLoR<9%Z8f%F#At;G)ROX+Xxj(3#+6yyJyy5n7Td?{o!?f;y9t4{TFq|yX^S#o&Dd}9q+Q^o!jy6>W()8as6i{{)UFw>Zi+c zZLXnpNi=ZQs3n+&F64YHGE8HD0v}n{v3OKCOq<2B%sUlOey8vt0b;*C9zd3~FS?-V zkPX52m+Q(~!H$FUPYi@k*Rv37JDOae;|sym?73h8+&-Dhok=Phd7Z`G5n$Wk>i>?vO zYP;V^L&3Q6V<_@>c*JcDAVaei*h_F@yiJ1ac*G*?N4ySrIvdxS7tBtJB!kXJAhY2} zai($TY>gp~iWB#|!$kG892rEK7JO*RzbP$X zlXQ2=w>DFP!{Boe^(M%?MVAN7HddH9y@i;tMT6JaZ?FCB+xz2MC{|<} zNK6RFd+A^Gy?=SK1_M(Ne935;-WE~j8AC7+kEmw={r<~j^Dgz^%B>G=kdqg6?l*f`}Jcqmm2K!U@N zOpmmAK)B4g>6puUx$w5W2`2>dWKd5qUfcmwyDsKQ(Mn&Bu{U1(SEnDhm-`u`!Qan$ zSc|mEgV4q6BWR9y-8}*Dn~W*Gx`g?JZ0Ms>&{kkx!}JLb=u3GY)P-X+M{UNue_<@% zJa#s0pv*~|a^lGg5UkqPTAQ7Xh`NE-cf-))bLtZ zd*{~~Y(|H?2`p-tY{Tq~c4ADP8H>LYnEz}SLuiRi6`_+ttplp()rJ&v;MI*Bgz#~R zax4H8AFub!?vh2?(*C2mV-*}Q*!oyNIA^#QPl>YPE>J>X;e3gD<%Oc!Jm;99*+8?)lndm>{E*vyVMO3maBp1{?I5Gig;z*M zy(8ec-pC^anu_Vox{sIF=rDmu)Ba$M4*SS?=BD<=u*SSA(pW_U0%ziOc1cUm5prKj zCp^t^qTPqn+hj+^R6qf4M#qweSN}5OX5XIVPV2IqauLRHmnT-UQpMQC+xOQ{@cQ z_{)o(6A>%w_DbC&74QK!uo*nlM7pD-wzvXtiY$555Vg`hv>mledJmX5VpkCrHusTL zXex$@b-H~Fj&>rSLXFq{@szx-x`fNEb-b#{_D)`&3FLAIRWiznyBu}(aZ(Nz3H#uS zyls1qT{k*!t=n7NEay%r_8if_MiZmgy27z+nD$En(G#;$9+0GWK!wB7kzDE);KO>I z>DwLus{ElW$uefw(}uB~F~KK$YxWmEiif-1dMq))atYEjL0n|^t#DDmFLn;Ofj^8e zIIWn3tCSQvxk@J&K@WREQ=*CjVW)dxHg$1 zj>d{&+|CuV?@po3>GL?PMuR@LvyO%&> z5Ke4N+lUu>$VJ4=+@fRbQB_7@u1OitJ69qCQM;@Z$!uHhyx(LnNKNj|$e0sa<05&7 zN6;cU-C=x48v>0WQLz*S>;VT=as->G)~r;{+%C9Nmsv6}Hq#F^=ii#%2i@}|@0lGy zS>1WJH*C~nSJD|Od)v*YoU)3W)w?I24~_t1eAW!|2Se?@txf)${sbzNKUF16`Cg%A z-%#+9%2bgz-v$r~{@S8^2YUX7d4kdC|7eQ>&BXFoi}EpO{2Yy%Zt!o%qd&AMU&f=* zl7O!Bmp0}7T0w9B-x!bXU_bZix0|N!`|&96Lh9<9!2ZV(=|8k8&?8bgg@VOhX)F_m zylhaNOgl=vFEnsu7^b-c*#!2zL8_t>&=MgSM?5|>%9U=>*4(C)3b7l&I*jOG*-9c_ynv#>tkU7Dnyp;}7C* zZV<>dV}O6E9Y|Q7LaTD8J`BbNqu>ul2FT5;-J6$i8O%<;=+gz*sUF?+eN4WP{q-0L zFHzUlrVr)|yG`d13-tRN^%xM~QpQjTARD$8-ZNaQHq}Ymi(TiD>uLg@<|x}sB{Wb+ zZ#9cW*D(w_IXf0OjnH}3%#rnnUCC#@yOrPnqRRcR|Cq9?)v0tEI);feM(c|pu1Odmd`ha3d9gCGDLzd^F{fxvFD$^_+_&2R@;3g#!}OV3VZ$Owc;HvOikja z&Kxq>1z-zjYc%)O)2#gP4!-*}u(E#pwav%d`+$S%IpaDZ$Hf|2YVDC^o zv9hz1Jr$v0Fd^Xdw6~O(U}};%v)6M(UZjritDF0fpO|Lc_|0pS2km~X#8-tMA(DH9 zsNs8cHb5B{v~;u@FU6W6(e*QoSso1Kb~YH>w<%u7 zNIe}{qC7Z_VNJlGMNZ8&yWPiS!$727crTy55gbGK&vc=H5MN&vX?Nv8j^V-BVm6gG#zw zjqH4ix~qISa`9nbdBM{in~M|^RMVNzS9uWp=IVUXrK3HgbODPMP+ z(mheh;TR@#2#GME-yfA#hXezRqZfz^>VWTv^u|4Fl9V&n7}vVv9>=HNR%ccTD;+GE zy_B$pn0CPiU^Z=1ERR~oS_n4)pU95LoGc`{P( z3t*oafJG)KJrBA&emHAdi5v-~29nAKvwORk747igu9n!YjTpBxl-KD&mBbew2+O^D z3d`VE7;sqS(}0ZBHg~Y?UAbMAnSq$rk&M=e;OYHlSA=b?hbQ_>j&xO_*u9Z0z7mgE z?|{|e@vKVrgo~oBPXXqwFK0ouD&Z5NmXLLCOD`|&TZkVs)2$8Bw-hjwmkXyN07a-m zud_lIrV*>!ySb|$Rm!=5P&|S3i{N~Awffc#Ft~eKpCCet5MlSHs?OOu?gFjU#eMjQ@tAZS zZ$^fV%%-a8wtB;v(sItHDstS6s?q(UlqbPE;6VGjJ(-3GR4|6&{r;}{Oa)22ZYQ@i z^^XF6U9xQMKb~Ykp4jrA&)fcUFexzL@A|uY{_EWFPxn1@eJdXl7@vN!7!ApSwoW-+ zS!JNi8__lS?s)u#u0sz$)xzw?V*eU20!J32C`&tZh7yEXITTf&x9Y5gHoX2eb(
r(v+k4pS#_}7On&|PZ-1b~eU>{LAf$XL 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
buperM*IN4WY2m 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� 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

w?* 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

VctV8+1a!Mui4td*e(O8_su3UkD zC(UtaqaYgGl5mxPPld~HX0N~3|D{S{k-*k!KY^Iii@LMum)Y6u$&=oX7rje;jzk9p z@H(63nSW$AcY0y$t>v({gdRD{+Mmj=`w=q6Gb&$_75i^Rb z`rMu`dW^H#r^wVo#;hDKETU5kqe`dVn!%xSf&O!EGQ@iFje0r+m#|CS_0i;dnR85g zX)7pfep0j{$isi5PAV14&Q%CPe>xQdEz>p~Zsn@%Q7ZMFVHV9Ms z4uYTv2V@S4LUF!5K@oB&2ZhV0MSJly^y_nQpnkxc;Aa}Eo?Zr{A`gjs8GN3^_dD-< zO~k^2Xp<>k#_aC*Cx=10!!ZsWaCeP20HzvDT?FW0J!_^HgiJ|<+NsHku4Utk%E-~v&FwzFYdiAj zzht* z(*G14_rM6m`2bUPPLz3esLp4`3}+NmUrFAlFJ?Cpi-&L{VU1oX=thzXd9U-cjK);z$_X<@VeTYR&dBUW|JZgnkb?c>1+D#6jTc46=nEuO4{k zeca>T<;`$37+sr<&!|i=+!2*4f`c0FAjHd?C;O z%jbK&9Zaq2mP7T*TXTTW#Hb%XK0rlJ6v>gD%dpC(neWPw7xI1KyMSeF+vw{)KyC`Il}URj^5)vDldk3Kt=Z*xp=fKfZChV z#Wp!`7ohiJFUYDoK)%bxQiCnS9LJX(I)FQOlRckM1|GP}u=QEMi<$Z(dK3>~_JGGQ z^tf8;3L3#HsL1zRYuyhxIzlcQB|Je!bSN*R~sQrHxzV>#ig6T%H`(_fj4T zqf%^N#CV_h@+KS^z8TAt;^Fp1%5_5A5jM%>sL<1jri2o9v_!&7bn?5ijmP@#;3ozn z#X#rFi^8WIl^rdPE$uR(c5_cU!ztPtw7>Sw?}tF9g=}jlNIS_yAQLPWuOzw22V982 zBpl#Yib5)(a2G}VC9nqeGpnnv@yt~~?#@ObR&FfD%0*Ex%t?iJE+=!5?xs9Dc(_}i zw-)xMiG1bU#Z3{%o&HDcASFhcl*~XxXr@wgE+-J_->eD zVff$pe#|`h-#F}F5Pl-el?@+NqcYZ8V_Z&(Yl83SKCc>weelPtLLr_*mJOPP83S{% zFC%|CyWp*X=3!Vuzc!IhxiI`C^b7Ed^q}BT4K0VodqMOp)s2OR$)*?0TUd=39wwXX zOAV9Y1)OVo~pcG{GN!lmG)MH*N+{FPeq701!ZtE}aefR;3Oz3eFLnY0_U>V$h08c2Ld%)REa zU)MzD$}a74WadpGeBm#e76Tm8O*_jefL)67)dM7BT1wI71Z1kVc*KRGI!RWilI+{? zMR<|!{v{~(eZM@wM@ZG0-c`TDBpvXBvP|S_s3%g{awV7F3d#3HkVxesNK)TjVny@< z6KcdJpO*TYwhTq%$w#k`lD;_#4ZIHpWg_qb%?|;(8lNlWLGM$zfqlef5ubp^v6?|Q zZ6h*Yw4=ctvUqRliCbJhte?2WNDuYtNY$hVXl+fyBWY1Y-WqfJ7W)>}S2H9IA%B}z zu$id2YNV^)2X{QZp7h$k1k={hNSABa1&qg0dnaj?)RP=$l<$mYII_q`pY)vIo2Xog zjpM6H=i0Y@QkFvV0?=mD{vZy)ap5xX)Keu1>#X>Y-se2^|K(;j82BV|(J}=7lI9Bh z5}L?jGhM5TlhHnXT(k^*t=D{4dLm1^h4A5P+!e%krYF4?*P7%Cy_L2%;^dbE;Qb|i zp0EUX;E2xy?mFW6z|*{yRuvgo{e)>Kv6mqny+kGTz zFXh^{+VyZ3q1C?}-M}fDO%NZe^ZO*;-d6z|)S6w~1&WB0W%RIJ`!%s!-R^wS>|J`Z z8ZhvHQIIRC3YVg@-U#v`+9-+my-T`zoSC}B5~Pi%L2i3#mpsUqFLB318wc<4KxSat zbm>yuP!Ov>tZxQu`4DWxRSHbl?cHYeRWiVg;*9s>UWKDB*Dz@mf$NlmqLA$q6p8t1 zE{cLPbQ`bVJqaJ+ZjS~?>K%8;ich+9rJj~=^nofD5rn`GnUsmZCfNZ|F=q8|dGQ%6 zE!uq~4JLNzeJ>-`_jKb>}&jxI;I7ba_7${*PW&CpDvBmjton=9IUpjeq-;_`wYg0LJOhof@M?FiqC z3@-)s#~@f4LdExGp#B&zJD~N)9}7WE^JYo6eMyCeI9KV~ulbZ%yZt(#w*8v767gY~ zggrE?#y5#;lL#6YN5zjxw%e&Jp#Anq)TV1deO6$zGb*NJYP9X|RlndsBnfyKEbZBK zXHswN!&D`*YW26ED{Kmj#o^m;!?Dh84(=lDUUR5clEBNTS4oEHOfqi~6+NyUwbX;K z!8Af&WMeuxCHf1nTuX3V5Bi0I$HzZw3s8vO`cd`cq%$7Fr25K#TtAB5sxM1H#!W`S zZ5qc^Bs^H&RXsjz{c_SE#R=%;^610n`nQjg!VKood=@8C3XdnVIHucMRJ3?mSNr&+ z`l>fU+H%sl*Vt8B<%^elQA5f9xm-c z$`!xq+8vj5JE0e7g~>_OmeRXM?mV(8-^TZ>vwft!@-M|MRvp#V$b7 zVz=)&VU(u?sS|36KegvFHJo~PaSZSR zhs3J4BfMCP%eiQ8B2K@o(?JyBGEiwr5Afl@vT2)g9648yf9*|1!J&IDTB=+0KGYB2 zCEcN4<%5P(_OJuv0+!+Q6*z)Zx>38~{2pJC}th zYQGQT7Qa$a3Q4*0{4H&E`>k^`8a)VxRJbx-5OgpCGC>zw>HV`8h!+LR^!}d+!X|eD zsZ>KFpWL0na(n&H{w=HpbUHBeQl7%oLP?AUdJyr*gkC7}@lRZeca1+aq zG`PO1P9JCwl^PT2iBYGZ{VpPMWo9t7^>;H;J!xBKK+Kpf9jick{;XzQ} z+_3Mou#~N=j3@mMw4z?sfC*$uapTi?g77BhE~#K)7zh?s0Kd3`U)<=1#ftcel9l=@K_&()3Fo{<1W27Xt7I*C;Tr?wWLc zGbZ5Pq`ZoVm-?HLzDHE}d4#EYiUZ%cm)fgHp@TF2c~81te>k4w=ys4zUhuhRO>^mg!Hs>X5e z)3mfk5K{IeqZGe(!?b!K}Sf9?t`k{kI zy; z);YuLQiZ`zZziLU(GXj1XHC4B3zBM+Yr3jpYIM3>s*&}g7?U)OacRxCcL@`?USdtx zgpwT;LS950(aWe*Ci6v`tBB;`@55G9f)Q1wt3t-CQi`^svcRoGn8V0$>k4dqltou$ z+mEE+i%=h91{_YW$}74X*hXOvm|mD}qLj4MH}wzd+4lFWEglJA}u z1U8Yd*aH2{wV=g#a>LQCeN5N1E32f=wBT{!h;(N;D*iHFh?+*xLm5&a(HcIY z?!4QhFmst1u@;dxT4DR=%(>5BI67+`AMIB_dDEN`*kXm$nZK>}pFVl#{Mc}RY`$ZK zn!HA{Isd{U4_w)M3f+>qgxr&)7|A?dNWaLbn2?hwChc?!quN83pAjMrIe1!<;>`4gA% zLDE)GuBB6ws(33ZGM|jx$e8Gt-eu@>$R=C6kt$sn@Kva9?-L+Dd-kl=*lqj@qRTJK z+l`&(L8HRK#Q%!=T~Lc@Wh>hFx*XN^j$YTI$I*6k7uSM5#xCJw%q~*5{PLNu{NnU% zymvM}Sg*eOb7SqV%8CM}I$P~`x1uR7RUJlGlhG|^ta!>b076yicVGNzI-dk!q_qGj zQ)oChhwwu{I-tBLzua=clwbbMf~mZ8;J9B5 zn_|wR!;Od>jG$=5mY&toU$ETr@saZjh&3q!;aoug08sQd!`jN(qnFX=$Sd-G15(9M zgYBF#olem7Fgak1l4%tAewkJ&D5KiJHhl5~DGsi*uW*%9 ziWQ;?m{(5w9#yPP>*nV-y-xQ9w~X))%u6u20%&LPqlmjme^?G1wd;2)m6rwzLHZn> zQM0e9wn1hkSK)7GGRDkEOR4D5X1){EpzGxpQ-=S7kDm`)1eNJC6&;LuR!KZQ45Ut<@YMDlXXk(JWm6b~LUlB#jv5M;UuCVvAvhsWR;P9Y**5{S~%%WCQWu5W)n{Bc99oM_KHm^(ry5utWYtoN=yXok6-GEf#AX zchWo1Kc0DVg#JTNvWrc2hpU_Rp zQTehGVc+(9P932L?8=J1p&z5NA4fNM=m2^D*AwhxQXuXMdt$`O%bU*R8i6|C?+ouE z%5g%2(Z#gWAJUql1G2_gcjhk-Vroy5u#l9Au)!P#l$~ z*g$=#vqv6Q7)ci=Ggh!Rp>O&VN90GdDXm^H<#4og4GWmd!K_O(&@+kxmy8@k#}SQJyq#~aLHZe4JCn zwb;d@fQayJt*jj5Q|AJKQtpcgR`MS?4~GT7L~xv1rA8u85~t;&i=JW_@PfT4GTlM% zW*1mMW7&vA5_*1;@P*x62maTMsC~F|{I=F=M9p?|)H;0A+-__~4{L4wez?Z=?eOF{ zLXB4K;P|)ba0ll3x9I2Q!S-6z`1PpOXtxoiSZVGb?KK-HYaZ11PPUtPY~vN$9ULAb z?hhRX(Bs315Gqj32HNdJ`;AupH6YhsHTRmwzpbt8;Oc0CxdVHS?BE*4c>QFr){2f! zTDWtq!LtLbK=WXy1uTvI#=-GAumV-oc!M8N`*m#(uR6YNtkh19Umv!JzNmh9^joXB z`}#O~jms1oD16lbWwlq>ieOaeRDG}3++T~fYx}ibq7JEvfYMr_YC>J~_H~1b2wx5V zUuOe^ZqyGCj$8P-hF-RgP1Co{c4IB7wVG{W$PTh-)>ep}XaVQ|f_4WDfx=S>F~~>+ zRp|E#I<0Amwi~rQz=C)VxQn_nq^Wjkusk2&Yyn4zE~n=dnTelokYzLfC|nZk-~8I` zUCgduV{{C96J1w!bB=Dt_0jOEe+{RR|Ao;&jK)JKZ-3Yslq*}ZJVIUTmr|&;HtZsR zgIEHn=JDx*P>Vst??ZLep;#yvF<8WFKP8fwN6XZ z+cDnhEWHzA79;HlRulFGVV^HSoMO~$41#)%;fHbwCxu(}B#B^MYTfhZLGv6}&>c_& zjQZ!#CLW)|#SIo2J`1;^m%P9w2{Ip_N1k%%5ZAObnYI!6gH_|*s&vMu((CpOJByRc z60Pe~tA<@uLc<-7Lxcgjot-xMqqCGt1pX|GWvOF!tq{jW7;7u~FzR>FaU6HJ#OC1E z78I5QqBgfAPRB7qZh(O8s{FAfnnt+)(gV>9x9kzQz(B;{GIRXd(dDFfc7(-TFhYe~ zJwDw*8pct3eEX#eA4jY31=gYq+$19@zr~Dj__lri2DTMm+4-xJ=H50!=63V&Vd>#I0Gv;HxNrQjSH|zP=wa#XtVDkh zDV24U$!eHmEx?Qh>-UW?tgWIi~(b$NQ`U`5IZ>7q4=UJL_jIf`|&<`l;zUnuTFpe z>fNKS2sn2`!Fa$tWgxmctMUh6_#G?#{+`(PaK<_IWP)LJ@5?jPAWsRolqRtbAd{NMG` z^U}H)Id?1@EfXE%rqpx(qr7^KwHXY-DTCmcEo(9bgZ)pSN`On(NylVhlU%4<$Sx_H z%`U7^Xv?Ks;wMQiu;w@)O>m~Ngr!EQA;yN)G(k_};7uwqw%nt{ipH+Q!X%h`TlJ%O za<(_RrvF{{Cj9>K73|{QSIFc$ANP76ddRxqe=5HQi=r82)^L7Fgif5p4?nKK^Mu0v zy4`qnvU?7%dV8|+vk$41UhFl6 z2Zs*2gAx2Xat6$2=VQhv@`eFaeT4@Rw#9P6e(!bh*-7DHSJf+l{?=%PC60>siT3(> z-JpmV?iImc^bW7CyfWH!qXI%5BqX?*J$mr@H0W8CY2`c|<`H@Y1*YL9Ho*}cY(nUE zq}5Sg5)lVr*qk0>9ga10d6f}V;FI9aN0`d;Ej>p<^~$8g6-01#m9L|XCr_SKFvg#t zg9@TgKVYx=CB{MHF41@S1GA8E1lq?~TD8dgR6rG9QQKOKRF1+z$v7wn!vUKgJU*;* zra((E(IJ0X;~7c|ZUAk`XhDs}YmqUT=LW)4$KgU zIOUHpLxK!8b2P?^LL4PXNmnhKMvEsCoD3=95kZ=9bR!G2Elesvz|+Xe>KJc*;6w{a zgjPp}j|O5Q=xv<4s=&S6|8=d`tNy6=$C06zs|^Y(7s@0Ky4Fz9ux#LgRsdy>$AX(5d%B62pT zhBIPztY%5V9G&e!EC#@7U{J;t0=?3YK?x<1^mUyO(o|0Wg+8byqJn3KseC6Vp)|U>g56a$l;CC3pgr z5Lsk%iDwtXr5?>SmPtM?BQ4x7KanXWDt{GM$e)ykYmP!7tb~}*ZXSM`#|-*Deq>=^ z<&WB^BnE=W+{}Fx22g89zZ2>g6l;%VtFDhJlR}A*J&9_a7g(;`kj8-CP%|I{ zrlIhlT&8thMb9_s(qbdTnjW#Q%Cjgm3z1{eB>}Is1KQ*$6Qv=Qa7Y%Reh$$}%pvT( z9-ecDj`9PRVq)%`xn`54f*CPSR}@)2m;)<%n}PP)kgD$rr&bOOYikIV-a{Bln`jLs0;QNquY!SwJ>q|A9hXgUyo2S z4SJBKD`&cfXqJ_*ZNmEyurFp+l-?vmrM$u5ge>&RQ>gN-7O!&95T#n_{R-KvOO9*@ zrxK~vmZT)j=vJS29`fW)ep(6J0+G~!O;%kg(f)zByqHHoJU^p`O&jn|>XP~T|0y07 zEL!{(4bPkEaDWi3BF$zE(>~eRY5sbS$o=74n%Cs7b6Lxn)q$#oaA%t<9U`RPJgDta zl(21TsrAQslNS+4WyE7!ok8L=?oU1DP&&{BYbO24v|)FHPzDvb9JR<*LUIsgc)~Dn zL|F>uFoIgfV@^?)AnhY9yVJ_!_kCPN;4NV^JTb>LY@mHeZ7SdQG3BQLmalKbdvGhP zUJm-+Z+4fw5`TDvpY(PB0_(B&jrP(caPAeymssgf{UToDz9s+7>_@N~h2DL}S_~1c ztNtgQAxvu-DN&(9nqcQ{)~eiI+OWgI0Y^CKMs;$uVI>f7V8=k#XA&<_M=WM^Yed1eU3qwoYpEr>F$0ub3w>YJ%Dx~9 zf3L|0YKId5OQT|Y&y$cHP-IGig9=zgVdCTaRA_+d-Bhaq zGGn-?sn;M}xu8k_XIbHs4`=DawUCAl+`0+R0vhYOANIt)5aVD3#EnWt*m$ zB!<&(-c^V?@pqXzga2DV64Nqw=(VM-@@Z-GNz@lpzfvBf?@BbryO!l-HuQch?v%_e z1e#nF02V+n6A5=qp;VY;_SDY0MrYRzCf_`q+GKwoLG})wo3?oMXiy*Qct`~)vy<9C z)pL{$Yse(l)q6SqG^R#bA_!)y=fF*y2hvTtnjEn%LqecA21m9))2!_u`KLPB04u#0 z&!g|3d_%h$usN5v6dct}z!|gsC|}GxDLcI07PD>zJY?0(-Ui{DljNY0()K#x*Zby> z^C|>F=_DYD{yN9a0YfnO7Fn66bc$)*M~aq0hphs55`4}LR#MuD8`AsKpYqkv3d-dG zX`i+Zi=PPAXSsF#d0r52`eD=JgePgrRs?c#W0+8o%v(7c66mjPxXes-8EMB5AeG(4 zyrFwfVDJ;{(E!D&^W35IkCM=2!LjKsH-dRzp`e%Rk^9(>;ttN5^PY%+kp(zG*K+^I z43~Ee-`lNZ?V#{dUFaaHuyRvumNGNHgc~8Dr)aE!gr>nN6^;Xa>;UF?&q1&= zEE#Q7Q`3oVScz~n6qV2^*lYrG7xTfxDY}1-!;iGEV^w{iI)$|wj{**oqXEPlV_k|p zFtDGNbi^vXywgl#)zJWGZ6G;+vk3=xan_$=} zM~HfvBrYSAzB6;o(!yJxd*Y zYCtOvaC<#XC~d%0Z~O_??u1o$*{kfj?gLhWd_>6dT5qKOO`$a7fXK$;{&vhfd>;LS zm_G5tOh&u7~jz6{-B2Y4=7cG+D!g!V6zCVYGPSO)It<;%cg_A6-QTG^{%x z*QH?r{$ka@+2@j)kFv+V<9x>1DNaw2m-6{F?!K#_jQ?iz5-7)u@c8c)aWhE5LBzokH=} zJ)7bf4km$322l!4Nvg6|JoH$pHLr#Ccw4Z}eZkH*{{qW|2D0qjQRvo`cj!jrOI&u* zTkr9VeGXIq{D~NI>{6q)!mHeV(If2G+f(Y^kP<`$Tt-K_LBNb<;vIH5yNcBSn14zW z$twimTffm_5y@>dhp7=r$AD}$SP36Ll;H7jHo9xkZ5)fog0vpb;v4ukH%Qm;(ze4n z-gH`;=@llmldKFx_&E4Rz82Bv^fe{LA+YA+e0wIH@i>Ov*gUrUGD)kIJY{nGDa@pk zyp95woJvTEW??f|bVi=^6mx?c0Oq|a?HVVRQrfu4T@?u}6A_2$%14TVx(n`Uc`~MsMV5$waCV zSinKFaN$K!Bpn~GzL4aCGBSLClue(QJRY7&q)9uXG*qmK*cKIRVYQVKtR#g_ii^YE zu#J&v(V4AW7(8Qfb|}RiIEmEEU08OQ3rFM&ML>XnGB`Qlg<&i*hBA?zeL@w~<_Ufx zm*8?8S-fKoE8*CE{GKOsqO3nur=(d+)n!#sb?J!C?BpD+uv7vomR8eKvcnuKwWObmf%7b2}ObkJnsDw7-poFo>^Lw7o%Yu=tC#Sbg9 z^9$U)$0ayAPC2HpYW1J#+*9Mcxl6&=^IB_{0+etT;L;=hk37UxZa~J3JI!BAgpeZM zN}TlJv_w?nREKLyvY0^q@EUQ~HS5e#&Gb1S71kyky6|! zrkdgIBp|PQWpd2A&px_ zR5VCZ*LgvWF4=N>{}CbsIMGTzJ|#mZl$_w36^IJqmrJwJ_z&1nj4`vD2J^z?0A zfdqPE#}hH9pzpMDpU`-@7f@1<-vh3F9g`mp*Rc+M} zaBF!`w|Dl~0zo->Fe^jk?d&A@5qHM@bDaC8Tfom@QO+-?lfk({EDVBx=GH1}pg7jr z`v8~t&%ZPsj%>qSNZ=AL)+B#;B38tUJ0?34o&zF!vE*P9*J)cJoM14k(?d z41r?`Rawhr;}n{0X=$!j0;p`-S+{`>UP|5)CCAMqK^`NKWuv}e%?l$#C!tfa7cdH+ zu5ZApFTKFxh6v@%XIi@odgu%?g-ewCN>hLbgAyH3Q3YzLN~~O3T8~iPG^o-`G}+0N zN&y_jc^w&c(kNIODg)c$AZ{%k_7!$x$%S4{@Fq%0CbpVu*StO$+-}+kOPt*mg~aTZ z>7}a5f7?IY#(67+3_c83C-F#{x`#=DA}5Ic)I$?2PpzhVD9|WSt4knh$w}7}gcriX zIn3YJ9`OlA7+rJ96zA_CWc)9IOh$a77=pEEysw63Ua9T6CJyAyzqRAL|GTW?|1RrL zcaQwvWgY*UFY6%x4F4z&As2G|gxh%ddD+exRu(X1SQSqxhnDiIosSI!MW%OWRB|-p z6lXa%>Q>RW!DZf4JEzGTxoa1Pl^PmMqPD6t()qb^IQ3O0?pe|iTf#pqbaZkwZ$p_7 zF+1Za74{MRfgv^+wzha@>zuBC0yF#;I5kU-xGE2lL*4Y?9jO?tsusY&Ar}gO$%4^- zFm-UB7Nx7IwnQ-Odzej$@KJ-#GL;>H(0MwnECmMwdCJ|zUzbL*G}&NoQtR$pTxIRj zq_*tjGp^$W8xggg-breVPz+e`ccu>B=5RW~5|?Wpfda|4ic~+Gie=h?qqH7TAci`i zJS>{^5d+I<7?u7D2d`I3s$W8s?&M0zc2d6B-)Ms$bdbdDh+=*91zZj0WnJpvEFN9T zE`E(CC9aBExSFPn|ER&Dl2*^jMLD90hqNP)L6YTS0v1*(~`u!r3d=&-}J)(dX>am9DfC*_;d`eg2 z3L+ER;5Ix?KI?C%hOd$;m8GSzzDI#!#5PHuk+Cx@jF#`f&Am8RTPyB2-GACt5dziF z{hQkXD*OA20QuM%;5rY1j6FF0Sb{35(A{29S#2j%J0eGTiA9a7<#beh_k3oURa3|m zt!@ahG8CnD=ybyR;B<=5D^nMqZ)$C`0MdJY(r=cqTC59K5cfocK}UIWxZ@G3`QsHv z$dhIuE~hLpIzS*yNmw%+DQ~bU{AXFBhJve3VqFzyyzd!eQenYRsOrjA_nL5>9b*9r zGM-F=NdrMZx~a;}3nCCb5NHwz)jLgJWVWK!N*WSP2r0QsXb72=#fK3t_gROIM~4@5 z>0FB*Z;o*yh;BT=HT2w#f6*;}&m;QJIi5zwI!u3+MI3*?{>Sv3yIPieXwH6W<0>CJ z^~yTghHY%-jKD+|&rR>PqvkU_z(DP>ET{3SN4klA1}M*O<7>SVR~YC>Di(dXcQ&Q2 z%*58BXI{pyCtgpt$|L4gMIgIZnsc@A(A<}C(3B9xPpaciKuJEabC(24yD@Hj($kr8 zpaKQu&@-$g=BODR9aVhSzQ#}1EeySoKD0nPJ+5692-Oz|ayF5wgMfyetQbordZzKX zwT$azTp)IPu;#|;M7T4zc+8zHE9^vo+`paO(mjOyMoWm1F3OLPB8aOD)P0exzO;PT zL}^zEaT!IXcn~2dz2o7`8>ULHSj}=k-rjar&oAIK?|bmNH!_ z{hJN@|DN@17fqME59pQrhBoL6cm$Zf7DCB9R06gDrm4(bl)tY>H$^F_jxyoMWy5&q z>o-=Rr!?;n>Cjk#PqvNlim4ZH0-f$7qMl%We_%pw(uir0StCQ~5cNY4J?hTnbUH%n zyn%tuo=~#{3A0OfdUR|_?MiCSvtEj?A(%l5KSsh$&6cs^Hu^`tKmOBK=0pvBcPV68 z=7YNLpjKiO`$ox#WRa^cYzLgpedZ_c>SML_A_8jWEe5K%;IvLt(~+L7{4+dkhfaoi zr)KrT|Ht0jZpC#Z>B9fHiY+zWRkQ#J$?dlBv@{ry#WWHOANII~npJ{AsxeSZ71-9; znkzg1-Wz+LCqD9P?_Cr~a?d%dwPxB>?##%@$cV_u$jHc`i>${;i61}-wc>4EkG)W} zv@G@@Iam-rd@=rfFqn2pqS`wZ@=KjC(&D4IQZ>{|28ZZ2CXygw_+W@IN-vB9Vw4k~=%S5W;VpM*})}-R2F1?ZP?X;06l*X6Gt1vsERf?IbGgqcObS3Z&G*alICZjS zTsT^W8R~e;lEE8zRz_aW+wf6M&>ll{;SH7fOjqWyRz`OBMMW~F=65{9aI<0Lb+Y6jvC49j+n5Yk(f;WW$Eg?&d&3dO+X5@ za4(_;Y47{(b;Kp_G8bN`_^_ek>el)WB9~ZyH?8|wL*13_?al8G@m|Kt{^nj*`-_I! z(d(4JT|%z48nSB}yG%iSh!oV%S5k~0Hr3vG0VH+n=hJLeT$6IYb$%} z$PoN&{rO5n2}$78XDY@?oWNk1AS3%oCSBZ!lQtEy$E4x~bai_+yBpn)hg}F(9_~Xl zXfFxgH_K6=aEVbJ=x`SvGhD~Hig=B?=2%kilvWqlU|tJuL|JffIEuFg#HCp4%B{2p zTFC0NB9BsjsD(z`m$oC6Rxqz7DVn3OQxr`TS}!~nq$V4mu9={w)#j1v+I{r3LjQ`P z>IA@l+i0LphFk4D0g_&6=G?cPDgwrXDQDA_d=*xLiaBrkB&nzR$7^~mk`Q%z@tg@( zAbDNgj0Tc?TXG{`ak`1IYC`(LHXadDOtN;{9Vb--Oxv#I0hrs4@>1;;S| zc}b@rNGdND6SYBec8_2}V_N3Z|*(XRuM-+JtQ^!bk;IqZF0fBUV+j$I9U{f$Q7 zu$DjvaV1&ROhHh7ZZFk!RZ>rEwl<3r2Jm&K)w!9eN7J&TL;WHD0fbHc2J|}O?Hzk- zUC*kyoiil;Fc;CEU?I(rOaqk(k~_@!D)}oH^;B0r0C9xs+hKT<0h!v2a!ejV{IFHV zQ&L0^y0~MHIep4A1c~_I&MP@7NOxmO|KiY~aN)5|d2SXB5FxT1BJyxI{A+7+)6j>% zv;;nQS7krpNY5)6MrA# zpPU6fXD_%rY-)XROY;x$(eMu{nG6I!S`2DkB>{Z~hG?v2;%Nx}0reUyUhqx`!*@7T zM5=mcDEF-aR0}x$Onv#t2hu1emZiZ&C2QbT0hr;5B}byVhbYNLl=%k zbCPz}{JOzgq|m?_oftNLNJ297HUC3Ag!RMYbgHQISUGwO5I&Z47mWe*ND#lmlih@i@n?<;Lvj2?hm zGT<;E57S%QTHWWvY=`E1!h=BGi|<&IAh_o}UL@Du`;5yWQ&Z6t(5Xp-A@O(p*o5W4wq|O=?G5fQ<(4>~SNz40$_;C|4 zso4vuk~a&2+(wdh0M473**d8wwWsrDs!%2(sJxIjgT_*4R;^{=%8nRrTuUvB4{j9b za<|G{3W4{6l~b;{X$D@7lbyDV9lalTJw-Qm%|VqBJ9jN3f;60vVo&@?S~WCfq@fIK>V6N;U(! zo~1f24!*rRn72-<;f1`4*$9jQmG-?I7Chs4xpNwDfA~xFKXrI@V!$>kz!_^0Nih}- zso*@1CWjx>2Z{*d`GzfgV{~9KzH(xKOU@Lv?O!^>(pdwDoHaO%9HWfE+JK1u40}-0 z!A((R0xIBDe)nu2VVru8+~i@d zk^^eyqjXo@FijH>mD-ae35x?S4Cs-nWnDlvZyd0a_`*bYJ%)j;ot(h`tLhc6LRX-9=Y?>D&h>2~^t_1?|Cz~k8PBYZDnaJU#bD|+MDl^#v z__Dw|6Pw93si1Hh z!CI8T$pWf5mxgBwjhvJICiG9^J1BMHQmWFh`pw-6sd`rJzVKKL zLh%zA3DaW^%uC}wJwF;36`))rGdRXEFkW-_Baqn1FL>q(HwZd7U+s66RpT*-4$VC4 z-Au;*8cO&0KALZ#v5ozH;cVg9!jPNy%NZV`YJjFFEd8SiIuqX{PggHs3rbLt{G>*;Av zl9t-s7Lu#vw@}}1$0|MDT>0h@(UcpThx;!!RwW$@2a>~tBS|(vJ>(yn)Dp4tdJpDi z`bPR^#7`W?R4a>)|ASS`;9>#RBhJ0YLCp*D>RTY1_}ocQS|qGSHuT2~CplQNePFtscxn+pS{bbz0MxN>Dw&MS z{Y4TWC_7WqEz}lI?dR&e>(3<+5^)U?Khl>5XS z*(HP^6FM=gSS(%lmJQr@2r68{YW|4|Wpq2e|GMh2hu26M{)7H;)#ratu8!W0 z2lzJAd4sFZEP6BQ_s{vqDYwe_?^$m&97@S~??L~x(Jdp^-+$MA*;_byu<+%pUmm^x zHT?bgd!&mW0HlrgI`WzME+)C~Ge{i+tb;-E6(EizU|ZYQ7XJIyJ^Zzd|6fFUEkQ5#W9i_(*;!|vYh+>Eqk!0;k@5fX z9g~rzy%Q^e+m4&}?+Bj$acl`5U;ng4GuUB&ii*tYMzC;sL)>iTFSS+pL2VsFo z!(`aA8CKvDL6@WZG%m-?vsfMu|K^@W(YnPw(EiUyr&mmM@Eq6NaJO6}kSesOXIIa| z=ipc?0Z_ZTjb8l=JcjPWbO`N^v(9tgbhkJft8Vc$4L9D2E#$E5ACjtZ5WL{dI&!LA z!-6stHaZdCPmZo5H9eNehM;BhyAN+I2vkx6iFoyaN9v?8cdhDuQmTM-XP9FIg+Dhs ziCT$7X<>kTe*ZVpORb9n{YV_mF%THJsCQO;4vbR*$moElvqD9OFfE^3&I2g;E14j% z1A@@#*No4#DB*J}$o$=oMKhl#j&ZN}kgl}&y&C!ac<|$3JV27`uisg8=G(y-w)5E#dE|pjB#b7jjw}(HOshrDY}gfP z@1lDBPEUxPTN7yD*yLAagS0SX*yMv>q-$D+u!9-EtlT=8W^`vs?dt|7Yma7Gu~zjo zD*+spXZR5Zmy~5=i{&;n+Z0NugzuBe$$@o3PZj<%5RMJ8S0Q3wCskdxOQ@%RJrwf` z^L>Sd9flX4ju5BrQngTxh6tRC1@~i{>W}enn6+3jj=)Ef@#w*#-za{7+rnZ*EF=|> z)5bgF((iwxH~>kkdH@q+((GHW{u`NKW%OaY2^IrHi@S!PsM;5p6#gsE(q&F@b+DXH zw4-Zke$oZ6xGC`3#j(Fe+60(*#7K;HT=BjP1T0!rwzd;FjO z;s5+z?n>D(LRDZ0FOJx zkO29&x+`W{fZDms1R}OgCn5C<{<6)8{T2f0-&_jd+72)CM9R2yk~+Ba3FAYIL#Gd$ zo9ikY%%m}54Uq}4nbmBV11Nfx2T@ufTDo>i&thB;no^D6zHIO}&EiM1me6%ioQ*;A z)A`m%VqTAIZB(Z&22%)IVE$II;JTK$cT^u2(r8drT*P*A6tGWv-uoHBHegVO2&RIs z7LbWMCN|;o3A2gmJMru?A+`o_qo$VN_=)%eHthg&o#rIBSyhuLS1ysWWy7M=-I!j4 zXJBAAtl@VOyo767>_L$SC8!yKl2+$~Qt8A2Q;PDjm^V};{m%3hXc&Bz@>!!hZ2Pm?BC-48SR~zPOl7TI5yi z*TS0JzLz7Rjna}zuxu&mU$=uTBIM`ukFHp*tM8^@)NtW~1k;>2omR2kk7kObpS=!p zT>NV0uXspkvBLOiNJe`NWLn!kw@^Y<-SCA+8bnh$FBbdv8rW`KUPJQU&#}8{NS@Wd zCCOpDHz1!uo|fcD$12YoU}-$prSTfbO(aiyPDq{=w~;)pl{`$@DE7zLr z!rnUTh2q-PR{2xDyF*q8pX73FS16}-o7!h1=kdG)^9LN8@1mz z^4LKzohJ0)m}7v`TpWTR8dXo|Pp$A|JOUdAC-7&yfo?>=qgZ9W0{B{6jz9*c!F)a5 zqzv@T6GtvD%gWo3o4y9n);O`>b-2MIgRua%Y`i8Cf^Rjup`b+&NxJ`xzJu|WWPTSn z0dEcm+@)!xU@1o3&T8+RNmdQe!GtV31?x%N(W<12hd4vOL}0F zkXE-oB`rd7jXG4xt_;@_lN6kcxW`(RJPj$)Dg*^)Pn)2`YpeBU<<+-ueTDFZ4k}z8 z>5g;`=zC^{PS9T*2UXrRjR4gg=aO@TXLsf1)a;#BJ+t&i+fgav zing4@WzX9In zpKL$eDvlaHmeG)!-#S0o=k$Kw?4Q43)-detjEq1q3*|4)*S$eIQ-s6XOPIv4rOKEI zg&_9t(c2!=G~=n$3Rh*{kaq%3!2re+T#jcEF66~{LCaWy+PDlF8Js)L69$<-{^1R# z8supo@5&UT^gdfh=@rsZhBC47!z6j}#8KMOhwhw`LkOH90SC-!d@)o`l@1R0AJbjf zltW3I}$+Iz}Da=FNkbM9y58I8DZ!2X6BiO_j`*w8FwJ9He5KcrTIR8ohY>}zP1Sp4#GlOs40Goy#s4bDYD zC8c>vJ>h{im#4H;@w*0LbHizaDdtpPV1VIf)6T1Xcw0}~YmYXZ{H+Gk&i;9xrn)HB zB?Y65BB|%q95Z_47uo(xP=6QJopmT57OEvdCYUz2P%yjDVo5fakFH^G7Fo#7BmsaB zr^+Mf146-&dpMAte%4z>Iup&hqDgLWv zj$JP!w_{7*)0YQ1st;M=uz`li{;Qr`aM$;I0X zS55!er<(WAJ;%tmG&sWMmZ36ssBO zfb5s_e0u7obpG-lSMOZkP1g8_nFs8|{>DPUf5!CTjM8_OBFh-E*0wD{_}v<>ll!DS z0dBGRx4U}1Z4wG(^I6m9Eq{!NOWpHG*mZ8R0 zFr-7=oA?>yQdgpw0)BzL#COWcNJ_;Sh=i5kGBwPdEXXT|I`X8lVOjclu8v5-aTit( ztNKDKyG6SLB+mBv>h+cEgxw`$8N|QCn>JnV4*Ng-KO_@T2yh=+pom;WWEbi?iYAlr zZ~MXU9-7$P2isWK#aTc~2gg#+NHdR^g@PSmQ^Y_&q9HMmmlx!=z=kTdAnZlrj5k1T zW^2k$u(c61IQ$xdSbVs%$mpQ%DaszQjwKO&n7{CK;jv&w*cT|bi64$?Pi}-3#_jVW zb>sy}=|$|4#+cjGc|87>Vud<)x>R6ua2@i=WbZH~WD`rdsuQk?dkb~cweu>&Z565O zI>6uoTZF=AQHVdS#A#qJH{jPYS<1x5ROTE()+mIMBvG`&*t5uAbh(bBCzoE^Rm5_a zYe_9`8q-0(T+;k>X3lvI{@4%t^tz%mW|0i=a$Qf-(4}B_I6fU5#T+xh6IZK2+LOU( zY*l&t;6L&FKsSZl(inRSv?l;V=QshPZS-|2`5TwOlWyjKZ-OC!`0iMGzl*O%uDhsN z!j}U7sYgm{q~YQGdC5`TOxJtla4vvWk@iI?m7d)T_GGA!sTL%laPi zmiN92R=$QN~oMQLv6~0&AtVBgCci{;2WsLttX3O3^=`CFV_^(jx;4CEOC!90*PgezKl?F~H5i zun=4hH#FCnuc5Nzby*)D0%-O{dJ2YSN`PxSdLb8!GH9_MfYdibpo{6*gA!!$c93e!-@u%NH6 zGt!5Qszc#rGAP%%nJ_CdT2buGw5A#4SRIXwTKig@a<(&+K$U{y5}XW;d({JRi8u7u zpvr23)Y=nyy9>F33FCwb(-7#AR%pTE!*|M2yNshdzeuWFi)KRdioxZ(gJc_4uM*;~ zzSX@DVm<;()@twjD-ZJ88Z0{dr~Bq`&mL3jav8;;-Ju|d);*0Lvzx;lRzR6jO9`$@wEBNdgL zU?mFxH-`(4agfa{o(NB6njeydW@oV}TpgaB4U?!#Q$B${_=C`_w_5Y9lkE_++S*jI z*3_DEA$BKrOLvKxT+XH_FyDbEc7;;8A*_ufuqh38N2^tkt>i+|vCG#;zLaty(>=;Li?0l_NH>z#pp&WVt@$ebp0}-f0Y24%mK)hWcu`)JZvK_n#4() zX@;%j?R+e(9(aVLYp;n;>g^99VvYdYaN z$qWc&tJMXZl@2?3|59uCnl-7CC%p9cdfMu?p*CsNq|>TR=Zj`{mEelAsS~3|^mv3> zs=1pREQq@8Cu>APe`U0A1{B?m>V8fh?<@k~BeJ$%^ihx-qT-we^45#Z?+A|!iak%fUKC}A8*tVW^DA%391=D1;!t+1y@uA<{ndsX}3U8R-6+Z!*e@fiCy6jRaM8{6In~5Fw!V z{g*US)Op@j8S-%$nTjv)Yy~m1%GeNayVdNEozC}@d0Fdgf6a9b7NoB;@%v1(GizFw zv3){jMHQ(Ko}oruluW0ARF;KTn~_K2J|YTXvcCaw)KZb%Qp46jAYnYRV^x>;Y!Ywb ze?phK!U-m}hnBSMp~#RFmpcwnhN;bKyQ-%+S;vkFH+%Ko3GlKlgk&xdG(!MUgHA=L zT%C|+{o(2c9xR!-HJv@`UA=i55B)$)n&D|0wlE9qNoPU&oN5;8*AAQJg7EUW;7FZ( z!0Evn$q7sn1Kbfq_`@dw$RIxQl51{&_ zgF(Ushn3utA|CIGQOSKbLYk-GhPvoUPcrG+C&-#jY1r7uz9-9_fT_>*L_dY*iZxe* zjp^P!l4rIy{6h`ii61O69hEpwX1JMNZfXE1HBtPaA%RQ4rjo<-XO5CK&;^WQf_n6ByM2xYd=(X#=cl7+eQbpC2^`iED=!fxPvJ|-oA0U+ex>Rg1+|pSx zY|HfEM#paLn@a#p&?4 z-<52+?ziBxa?W=~22>5r@VK1vm##KRU?@{{JMdJ#OS&2G<36tZ_P5QcB}4_c7;+5d zRc2-^SKyPkNv(0>|ZzR!Nw!uuKF3@MS%4EDpKrFdzeXop4Sx9zn_4 z%RfjK)g*=u%2Y`McQu~FIkYx}ec$QG3NCAIfHYSB3oF^E&9N%gLM~ZnvOop7HeJw= zL$=mt5cDp(Gdgxb7vr&Z=&mz|kc_ZO<+EX-v6E*Yjyd)VhP%YmOdL)~6#PF+dhLdk z?)=XK{h_&;AO1;yAxX1p2I;YFu+w&k=L&gQ2>r4{`|*H3esA5O-`HU(oY>txHTLf& zBsE$^1)-)*L+g^3VO9)8%4lJRp-74(7I@A2Z4OGN5?jesa)i&hngquO=M;jBvJhRP z=dI-0tSywBa~o=lYQ_YVcz_-$8Y^QJu#`6Xl2qq+@=#<5!InoWmBSj@6;bY#ueN>< z4=;!Ht|H6ENy2$}EN;fNt&`G^16KBl&eVB0_fZZlrYeh_h|*Apq%v;n9M_pz`hp|| zafZ>-dUZ3DG7?&f(92Z8m7yh9G|z(27K$6977to$aCY$^>p9*h4WK_NQi!2MQm+6z zpj77G`WOV*m3`}P+1K;_@u0^o!Ee;*@CxDkFudyBwaxGc(UoS^8kcum2(5S7FomVE z$t)w92~b-9wu?8D*LQZdb`D=`y;z^G(lk*>+w$XJDDQ&Mp+t2!hUUbXIIk2OOBN}4 z?l^(6w!Xdh3{Sm6{KmaE>_UQVkTHkPYa#Iq6U<^SfQ9}o{PnN#SRmN&sNX-vyMJG~ zzo&${ySK8pe)xQ2_xZ}+Y60Ge%_ri|dZ(~L&ftq9M(yBM_jWc9SD&ry@z$(JXM?Atx=v{3@@KDPvTS@etRulic3(1wRE0omWhDWhiYYCEcVZJ-^zPg$+ju< z6VUo38bx-!x^1raSW@UVx=Z(iO&(i|zpQTC3l5}4J>*4H7IJcG2&8{YXYRlYq2X4U z=$I78km43o@5B(E+sLyJNeP375`*tG`M?x4C=6SlBJ|_mFnM4r-@npCas&I)p?pMQ%g#l~BlRL3AU`iB`{Qy@3K&3{= z1r&AWHi5xzwjD~al5?Ee#*2OX;P`rI7NRp@%&n&JsQ=Rdfs)e>TX~~-zoHKDR!Bbd z%x}&Kinx^<^mJ}9FXGjxO^!Gat<*Txe!adLoF1>C2cEC0u;AXfFoSVm@KVH{$j5G? z>o_C3hdpy6%(t9acPX2eUD^GdP zJ?Z}K*Y3-E3$Ntc+%jG|X5%@$3=BKS|Cw^c3|tP5qRUJZC{UhkKMm$f^Z4CKavy4V zo#2YA-h6KR>H;@$;Gokc;5|K-(n}XcSv|`IS>?6ymGk30WE&@!TU~hdAhlD6Q7Ka7=V6B&-7emRG}xs#*WkzR61+CBxXh3&i>x2xUQU~1(;yK zTXu4yP63edywjWpYmemb%tXNI&8gz#NKQFu15?stW?x= zA8$d8sY#W6NDX2Y$&D9#>pL%2HV?OVw!Yn1Ly)H>HQwLZAnSo7xE0nrMf!RuvL6T7 z|5*xqkvf#wY0&Hy&vF7<W=05OJ%o|qftaLYpiCB+*Sgwi zSv8(Op{9gD+M-ba$^qLI&J+h(1a)1OrU9_Fw4Z51Ei(PvV9G)9<{dzF`a+R(6sX*6 z&^Q$Dtr>`Z^@XQ2DiJ-SoP*eqcz$K%_0f+>AEjo$4xGdqH9gP8W z!!)F)X@g_4xXe;e8^xNdaiJ^)P`MDdr`y*CZpjrUh<)Ue3by5C%y-R4gl;jU=Y-It zQ@{J5MuT|bg2NhN)I4v*tu$BL9WdF#sDIK&K=e_cd;f5LeY1`qg$E~Y*>UL3$RfD@ z(!n9b(-6B9@_wutBbyb69Z;~l zBCS-8funSnk{k--Bam0Utz4rPqZMT;R#i~0>#dEjzcYP_iaMtHkRo3CIUzCnq&u@aAT#0un55}cGA7A1$5Z+1ES+vhdUO|#9lfQ6^lieI z)yb&$h6gG*o*Z{z#0@LK1VJsyGyw-Bcm@U%pWt^jZo>l|vez>6U15%s9Dwk#w6F#%>y zgRs3!u->8B^k1n0Gd&9V8jEViU}>9*rfY7OXalu6THr@aZJL0KrhV8_nJYHD68l8p{nHsIYMhO+{ zob2o2@jFnVBtW|RbUDA{&Q#wDbl_S%MFzG3t{YRUC(n(Q$0ps*(f-b+yt5;D9!_x| z0ns1&qJt8xL;(bsh{!JYl(M?xTo-{Z-Y55xGx7@}IlMqH-4WuBR?*GY_TC}>1A$;m zyi3j|O_iRludJ=_Fa}K0=e5?`S>M~=d9k;%@?!VtH1(hD?QJ`3-K%w0j*f7q3`Y{Y zK4JEjm(Atye0^!T95oWieN3yCuaD7}?CXXFd6XK+ggZ1ZnYEG`9h+E%KwMm^+a?(0F54d_X zMnVJ`OR>YxSd!bNhHcISEQG(qWAb@I+q7eH-`EZDLMvqV7%nnnB7w<=-^C9>E|HU#hS8=pR$CUz-8AuDr8CoCf z8hOY02_I-$AJV+A$-?$tH4y6HEVy4_LQyA&)H;lUywgVJqLG+yQ>lh_h|Nl%@|VkX zBoGBbIgT3Mo(?ZJB6~83C?l4q)BY@1*E*yLT&NxH=(8$FkjwlahyoN(ov6z)w|bs4+sMZGGoeV=x)I8l zDdegqjwdJ^RRK|5mWNW)^mKFesRIpqos$?)E!1Vi+2B|HyTZEVT<4^8Mk{OOqNN8e)2;k~*t*;c zvg%3=+mza_B&ieze>M+@>wcLj-Q)%o@B-R{f3FW-Oi zyI1$-mT_)=|2}^6=O5?r>*e3&ukHc=w6fpPG@N_#D{cr7Vo)By8w`F1+gie%leiEv z@SkAQSyYVLkYXk@)W?$~G^+b@_rUD6hp+hh$kpZB;RuOp(=VU#3*Ag<;h$b5k#whc3R(;byXZTxKqhc%B z$brx_qhjexCzBrN!(5obVR-sI((6_ENeBikWqGSP|BND{4t+Ys?c{FVv=^hEXdbdd zwFm>{iNqH)R3imQ*m|FVka@SlB@R~Jnasu*TID211E+N<3b0#-r$4GNper7K>}S^w z1-}^{`EZ@t~*t<`f$gIvA32gB}HdHm9P z*1S5T33{aE7XT|sBZAQ`T!b^ccgjhOl`-Db&3inE7~TvJ<3o5{@0OV!5h;yQ6reSa zenL>4)@Njfp-RfT&C8ji#hPaQPU{sK!&Z+(U125VI7{Bo82FCar76Fe4$0oRAbS!y zL0~o2RC&KXGw7RtZd?ESJGOeMT8BbZ!1FJ_S&c;C^%@5gJN;96pOWzSTFuu6+wXsc z&alZ*eh|lLI;zO-aZXc|;`fqZ@e3*7MxKp3=$>LEKm>K;Ka-`>G`)6wqh#bJb0mT& zw)9(|*g21}8F0&L^qNG<8F8|*W)@>x4uG`Gs^ey+ZKD!)Az4)d|wmjfpnc-cM3 zPysl>(GmO=SXA#L;B4_=Q5-6Z_vijN_eWVS(}92xx)6fRkTrM{H(-6 z4Qy8&cEw9tzsprKhXZrU&ZTxUYC?UocjH}?S{ukha%^(&G;g zrL`*nb0)0zz`%i06dn0jiXhGw+Xn#eqYXu*Q@8Ec4_oiMd($ZXK%nd%z6Lysbr!@- z_IqK3P?H$2X@+o4N7azhL4qX`^2>>l8K-IvCms!1WK2B?W>PVvVohrL<@UhT4GtBk zJ$V=4NLW+Y2WMn)g_a(oyKY(nia*ckM`%P6ORNMnW9Xnu{L1s82eHfajznBcuTg4Y zUG2zB=%YIbUV!O1stNru(EpYW}mvu5+7bV=t_O-1t)70nrJRcOF2 z&(mrqVhS+h!e&u0L&8FEx)N-mt3bksR>_0DWL)afAlqTRgVTaHRj|HmG2$JgBSXz0 zGyS#h%T6?N6VB0qCGB2tcLO80<{Dsp<@Zyh1sOyBsJ(>uc5nhC%oc1;hp(wDvf4Tg zNdZO%K7=FP3>;0s5SD�}4?GjH^O_5#yGq?mzxDHhbaI{bylSv46^{7PEKUwK^+ zt{%kGy6SdkI!`@L>#jHh>eS`N@osb|(tm*mtIp-TmrBW=1(AUy0W%Rnp=gxLsgMC0 zmz84_=0;ILcoCw>H0Y&jK_|P86H#%ID8&_)Xgra}9MK2`pM%iUrYP{$n9GZtsnf0O z{LX3+&{8n$^RWV&mBSYM+gm0fl2@YRtYcZb@T~hb$RG~u*gz&oPN?Z0A#md&yLe}f zYBFZ=Jx!m%y3NSN4W~6jS5$hO>qrve&J=*^us|}z7xI8zF-7x)cge|G`*hd~!Rbl$ zmfA*+X1$fropcmrBMYS2OT)9Og@8#?sHo6LWEnWvweK01)r|2#VgX9uDM!9qM6$lUjKcpwZ&&2_6|fed0a00pQ^96C?Ayta2T z)o!5}#WHQ1CLc~zfQfLJmBw$;@#3CfTPzxeCyJv_>n*l|5<=idSN5{lU}H;?e2m9F zqN~dB3FfQ6g=>4Cs!7fAT*UsF}Md!plpUPnLu_d}lFkGU5KCryWkYa2Uhzb4 z)2i`r9hZf5@KoSGmH%|#v^uG)dFn6WSMJ5t(|)`ZE#L#tzyC{RDbaE1N(r?xee_h*J-G9FRV$Tm!H<0rvfrSH*6qcd+?aJoH+R9#0_hB4<$uvJ| zvhjZH5mEGIn=)9!M?XB*Y+5X$tea)+sehFQqJ!fd0zK}NMQm@lB@cV<^bw6b9`L}~ zny5;UMEOW6vzl~e#$=kr7Sn_5ZghvpZX+Ug2g`z>Mn#}1G_-@xvUZ3Wwvm`3(CtCE zd}1hZy@50KnoFxT0KMyNKe(a6fZQ{XRoO-7XFa=_vAjI#b}wmCjsm-j3_^Eq;c;?% zLs17ZP(U^e%sF$j4v;VaiNmmL7Ie|P4jbU{9`3|G9geyhL(yZ@c5J!uSg3{<2?0!= zWG&z{VHV1Ko`IR91+9%MnH@{WvYJeiSI0=#N2B4$la*hc4`3|Gn>ji@@zh!sDB<0$ zojtt0^v(X}$_^cPK^q5x#0JlM;~zB2iRz)04;{QGxpmq?0^HV6Y6LMVYbt*?z#aCH z3#=w}l&$F?!>pQFKauJ4lN_e0ZlBYrrQ&PKwUf$*T9OAv*_9fD-@ zGwAU@;dEyStDg*c%*3H#zV$ie@$$^f9zOM64}a{-Sd={w=Ll4V+%f@z1TqGK2KH(E z7N9=QNVq#%cxp}vUzpRGfFpYDCPva?=o>{gy# zqq@7Yi{ChP|88UN+1CDEg&Md~v-f?q^|V@f@qP8zjTdY4)%t&I@2v0cR$DtW8_%~l zH`YFJwW%k5UW7OcaJpbAH^k3k1q*4ANmy!Zyw^CGK^*)(e}2DomI zN61fS`bF)MXz_**;pjUxe1icC-_|L_U+%9TT^?Q{n0@@0CF=Q`_LG7a>rnouTm-P3 z*N?SGQhbirusI;L~Z>_XvsM~X; zO_g~|r9G1>Z7Ta)EA4E}ZByEt!#Ap~sry>4Rin-uq%BF(d}~S2VB9*|zf#E}$bAIA z*Aec8+I=RAlc2*7=Hkyua>c~mbUJ*~MJEz4xV(&&cjue+7pqvaBNT5h*06Wa|CT89 zaJ>&F*SGI}WpjN8%WZ_v)^*wQ5hD2qM?C~s-@fzJoeh{JE4;QGfVA{qb}#o6KcsJQ zXiU*@o#wFPY2trK=P=%2d3|$z_vNtFcKwj9VM=bS!kW64;x%3yoSzIoq-U6#Ti5qn zj`+C$`s#ydy|(`KemkXGOdZH4REIj=EM913^hUO7!O#n~P3!9c&>a)uL!h+_R|jYS z%>f@?&cox8w*oV6*|XDB6W4)$FMhh}LX0^#_R{t3`HI;S)Rh|GUk(pj8CqU}!%C!150a9WzwX#fL zG_Va=wL5#t3k|8h7Qc9tX>+}(3rnRkmx)^g1~+2dOve6cT4CDYUj3(Ob&W|`%Fgss z+{SPJG_6{U_hk1~QvK7Y8nvok;OK8t_jmrbg=dJ;;$-D)&_Opujlhz$Sh?#A{T=MV zOCR;FqPTW-cJ_`R(7Gf|^MhcW&_qnEb|cICeHGb4oO|o0;`n4X{h?9+7st z*uhbdJ{JX-s9=JO_2C}a~YTcqCD4Na8`hQ*ZNIS=^m<3F?&Otc= zFx#24d{an|T=&m)0H_bPvolH?B<4w~CH@yi=IGakkBHH)kt5hB;?|AS_=WTHp6^XY zK(=A4_h9~)I&`tOr-1AWeG~frtqFU##HZ-Oc8+!A&4*G)Ud;0;AcxIo9d>Y$)S;Wz zJ_UT=!Pde1T3v^(cEA+Cbr)O*t$vO=VA}UnfVF*e9j-Yt>VU=SZ-qQjU+uje4SyPM zotuwCWI|OXGzlG}*C;8}>cR~m!33%$pffv9heJ!GSBfZ=@~Wzh9lbZhRKPdsQFck4 zUre^EUoc&7;f;G=sn}J&oD7GHuY05SRqs7;QHd^8CQ!6mrbc@sQKJp&P<$L!LwC@5 zxD7gDFCR;PkvJN4gr)LwZ@7z-cla5py%_g%*2ii8v1g(bjfsJ1Pz>*b;FS9L@aPAO zgfT_Ov@GKE)vCosk8ATmv?>D4qWvMni9tdIXe?js_vQc%$%V(N=%Mj7G-ZhB9_aX) zg~vMNZYM1{C6;8EnS_GFUGDakhy z?8-)R)-cm~Jsh42XY)o9Zq+i-On&nZ{ANhU`5WbZ$|{QBU6S7q@b?KWr`WH>Ke0I& z1os}}3e%D0UoToIT-QlR*PBRnn2MJ3ENWL2QlLDpCLS{e4HFW@k9O7>k$$2yPDG$({3~-W4OP0-kr; z;P7%A_Y{MpP`BH8+8dk#b~CAG+{s*(m}0u^M5$`RfD?b4iMZMRDBm7})o%Vz_jS~% zAPiT~Nn1}_9&uq1ZQ7@U0rYQdUovh1X0{IG@%4L=gcFa>h^=usLQsAqyCtmk^DKXiDeLm?MoZfOY7p*E4>P@orlS^k>}rV z7qA>5VpMjNNH);KrqAxn8Nh&rj7oCYPXNkmlSCfWlls2o;@Ya}O0S@6t5zlfb7E<^ zq_WCGA_Mk&Z+n;9+<%VKq^xLXnOF@EuHX_dj4!c|SMt_QpsUXl63m3{O-M(nfxgWb^{JPa6krs~*;Q=cK)N^mpF_jAxikfJli3EK1oh;8Ug#tr#$5H751+mdydt*;MC`8d zr1{qk?9zUHA#D6!zpgOo;@5SL{+MHHx%MAAlGIQAu}l`nIV;zr z21m~Y6Rze~1DULGA*b8fQQg+`^q+l`C;5Jk4CF|3&y?>~+kanOu*RtP)u?ZC@t6s*L)ZL_B zR+z7|Hwr_4Sp8e?9D&)RHyBF~k?wCt2%H|EZ1ey7KSw{HppPfMSm&<;JZ9U!v?_3& z9rw>aDCm5qA?O@YhttL|&M;@;A$7)Edgm&O|hol6KK;}02Ze*2*F+ea|Ygz)+1 zp!3!MM#f%zte|s5hB8V&1fBop@ryGt=)Ag3(D}@Sp!13Gi!(P4I*;*-GanLkK4U@W zw~Ak!sRy0kAbxR11K@8GbiUKa6EFwcxQRND6PFnMfi2?^Ze4IGx6k1`C+s!K4|?pP zo0-zGJ`#9(pQsj%Qlg=|8%Kc`BL4lVf5l^+m~!h9z9=wucxgR+!3C%*=1EESIN@o6 zL%gc;%Y7c`J&BGS>jymOG` zsY$+L9vP^72uu0#{C@2^YsMCp*43m- z?ytGD7nFcyzod806`oih3M8QlE%ov>Mf(iu<0>=Y@@_JpiMI{}1w3DqnTS$|C+lEm zz)ZV$PkKv`1c+vV&)RdDgfi8habY*C2Sj25Owl^GObRHeNTATO2{wpIc+Od@-4KG1u~*! zz#ls71V}+T;%P;RC79AsYp^6#$Z5$6T7q$+OGQ5nKKiUe5l0{#4-w3_yf(x}f*G1} zeN4*#-7kn`MR( z65JRK>R@=jBTA>`BhV7^`eXw4>|edtr_&57kg72OF)O$D1V28nN zjas@XfdrOGso(<@{9Wu^wb(dD!^PT#c~6%P5pR&J96+krRR{R= zptV>O(h|2}^`1S#Kgw>XQ)8B`*6{ak=lkImxaAx|ROta^l}k#E0CKR@V=@EUy^Ga8 zf|v3pK^0n^Ej=!o6MXS-&!Tw(OjGGtpr(x+4a$y|TQgI9IL8}F;{un+6RDUk0iqez z4zl7f)})o&V=^8Igb9{Un{8y!xN#NQKReGR@tR#uIWf-I6mLZ%v#Al$kzH)`2pU^1;G zjnv{26LTs9Jt*Z(oFAKbd{ljG>0J_o-89%WIyzFoAS8C5m5`SM_y)=Q#2PN;SETqg zgHb2dV1N+h89|)yU7!uNN?*Tc^wgdT85dFz?JdFtO|g;D>%t_JYEtcv-(gI_qnU^N`41}KU{{;|easRbRClkv(Z z-5-S^ak}#@d-^GLmynTXk6xpJJ10Po@F$DYmg-9|HXttWYrbw4+fTd*&|VTVmzm8B zA)UR_&XwHOu)xe-NL9~u>qF>7vTByzmS|y6w6J@@1$8%x?q{p-js=UUIT6hySwKj2e>Q0-8DwsY)tTfk@mrMPyJr? zYE;DW7DRn0@hB+7sDI*>L}saW@Ks%cv2pG#wZ12*x0nRoJC>MF`yphozf-D7q+N|8O6oRFLH}oD zl_1|FbMVt!M6|(q>$FO|x1<+?pQDnuO1>v4X38eI>?PC|#&cUF(5H#NOAu7PWbGy_ z6AxsGPe9lHFdKa;6lBqfO9}RgzyWf@B8C!>6OLYEdS&-X5`k;v9}#6}j9#V-4LlhB z1TBSqclsx%kYb!BXq+d5Y!o)0Y=xQ$zm^!7s;bms>y4qH@1Fi8?6KxPPBv%lSpVJ$(UbUoxSdlwoY*Q4oQWP179zq z>(V5X%Wzsz1M(vxC<6ty~8b$3swwZ{{*L@~=Lu{aD!KY#0#H*HlqJTSjyY(Go ze$eOJN9~bh6l@KFJTweUEoRatHP=m4`A&<=48?;CckX5*7l=mxn30);}Ky5sy{ z-#acTOBzJwReo((Vo893?Te!^kval}RegeuQl?ydVAoh8Q#1yTPT|0d(F5DMZl$Nk z&Wx@9ElulB=~bf8U3-{b4kr(!l5vzcV|r-|sqVbC1AtJUr<+JdwvX!j)XfxxxWqN} zH5NkTD~PsD6=XTFx|~toYvb=M90#jAnR*Ml26HUMpLm5WQQ2H zR3xIWv#Ou5&bJ-cJE5ZcQ5+=MW+T-P;S@f0(qttjr}11rJt#8Ww%iI51g*w-vJ2pn z@(BWgt|7DX>i)UfipNS!SQTNM%ge!Up-y0PMhL%f=MoP1TP6aMCQF`GElk8HFVo?m zP-qp#5VscaZCedlARLa6p1jgDFV{Qn*!5(OTSl}-#!fli_KXyYe6QFjo*(39(>^kH zMDAG(?vMXByjhJDOx#sdt?0TuO<>hB(RJh7=dJjfOwS}G<>=b7e^Xgj@PfnAZ@mywY5m%iOLI|SWXT^uE24NTZ>+l<6ORnB|O)t0+P^RdVf z@*?-XwL=0LvQajim4Onpg8p;-iVpTo7 zpx}GQYb+f-UI963B=9ADItqk-<#61_J+}Fm+rUQMv&5AbzT?3kC!OG(jF(&LfL%=(wqu znzEBOgu`A03q=j3I*q-$P&gmDfAWD(7!8r4gsULp57Qthk8&R=(2A)4xF!+=;W7OQ~*^nx7|S-za5;wxe87vB2CDU@dK2$4rkvEe|ipVWlgS;n%d`o zG4mVljk6;7+v;lk$EMZP&0P3(SBD?s*YqufQ809 z2QI0o)e=}1H|y%Z>Aa+&jL0UlVdI#=DR@N%U(P(7*);!8u) zezUQ3Z1Nr;1Fht7I@rpKLrvG9QqgEeE7!KrFoF zcnrJ`NPikXXJhg{Oa;3+&*$c$n$hB8F9u=0!w~xj5=<2T2Q3JJ=q>h3^x&YvQy4S< zS1Mt{9b~I5ALJKb`y0$HJI)Sda#d%dHUY3q*R7OrlUY*Jzk%~1f29^SDZ>}JE8k?q zw5pdHfhL(Uxf-DNWwLpr!2^MljJVMPVD6cV3trdPHEx0?vpEy(0`-GvT$f!1(ImSv z8}0fC%YfNc9^j!T;~!#h1;#<>@KuvVp9bJ)9ZDIP$6km1mSe+AlDPj@%3kZBKE5>G zsH@w_r*Mz1(tdrP7QiK*_-v3vCP6@aIR4Et)&rcqBW%Da#;3p>I4Zuv#r|`KOC<|= zHlCVbqONqzC2=5l)v6@2ORyrGmv|@@dQkgou;i0ma^zU!#@Lgi7>+Z7_GA|g08xyK z;wI7AkTRf>(aW|TkVEeqq|qLcid2$B_^`R)`u}5Kb!S*NGi>Y+LtGY4Rgx2WH;TY1(qm6UgJ9K$MnEggC3|AWQ&`+&g>J}!f5K5&!lc*{5w1i z$M9b<7K6Hn!9}s;w~NlW4K}U88q)wHx6^%6#BN9szXfT49^f^NONlg*h>jwX!~70R zA;u_pYmZo#BzcQvo`LfahjSOOU;D>lhs2(;cGM&UdVr^>55L9pxp?;eaAOVj`JW#w zJ$OK){lIddbvw_Vef!)isV`ITsdpo&ubv{UBm#*W_{C+ihF{tcFTsyD?8-O7l;sUi zN+(qxpR46qngKAXn~tiP*}<59bi&3jFd@h6aamE6UIZAtxzhp>SLRrwmJC)t49hQgEjBW2Lg0?l z#w>pbmH9!!gA6asau0`;%gM&N7X7z>?gWPx6Rj_rm@iOAz6~t9yo_@Nc4HUdh0*$F z7ne-o!`>TtBbt_qb8oEE!O2l}V|f}}&@=(89X$lKf$T9GU3g+syjytT8(aZRi*#!j zd|Mk&tmFB0d0tyWI&4ZRwu(koSby2IIyfx;~|n*$(%4C0<)DfzrbPfB_=4owACXToNiB9@i@)cilIu*>;o;iG&f(!==f1ZdFCrbe zyf{;Tsd=gTH{{M8AeZ5rH%NIn{GfD(RhiE4fAN#dAMZux+ov4|cU{lVqHi(d8CmB%31HOErJ-pbyv0H8LY<;`2hUf0@uHcdO z-MjM@A9m;a!FU*C2QS@!&)45q@Z9`gH(spGS23O8%x%&c5*QxhU3~_~{^0RHpJR?Q zvb9rjhW2;YEBskYXP8Z2e2$r#QU1lpuy*^W#|Qrg-{~f_JZuJyU0A!iIF*dVNQob< zC&Wx6yV1D95;w+s1>pnCW&VR}^-}DuYWBDf-|{K0X6nB(VyyXeeiqs7!h_8QK{n|2 z_sb3{bi_T1KX6G{?tHeL^*at`Gu7j%upVJ_4M+ZYcWQDqHeb_Gwu2B4riV-#EoNp~ ziBA{J`0`kmJ13pD(s|)Tk^21+XbulP-1ckhd+V!vhksrF{typ%Ki%9|-P^?~3>Ue6 zU)kK;`tERTe;Z#`_SRXE-yiz-?UkL~^~1fb!bqF*qn1=j(P=13L5<&?y&j&@QWPIRccvq66-ie6hsbjOZ@XJBcH{FH zk5II`w}ZEurN(DgLnFVXh!14E*nj@@`i}hkJk-D&U^w3x^)$QwpQYO7rd0aEDt(Q2 zI{H0`Lag^CKJD%7uS>lpzQ*x%WpmelE?K)hNQPE$l)vQ`U5kkF`qoqZ;RtVuJ+EcN zn8RG*(Ww>@>tFOwBKltcbTps$8Rx!-FQ;46R%TJvz{QSaxZf@eo5{?PblGNuf95&&XP@g{)5D3d3f0l2*@J|Kt z9)CX67R&rok(lS7kEHHP{;4ot;m_Jgpv>Sfk<~F7i$GLHeZ+HHz#ud&`ID9id@cF2 zmPj{R@}-viDYmi_A%xahdN^M_1ZWwQ6-|YLt`9BlMWRO~-qGOXFbyRF(34kW#EP#Gd1Im&dsFWli9?pM3OcM?( zfRgw*rYy;UN=XLvO9D-%w*ad1mW^MEal-n7tV}n0aS*VGIBY@y2f6rt86XGhbT%r6}(Ie(RHckQS@g@ce=!7ZNU3n zOmmS!5!)nvmm{bv8eK^#h zWc77-slM=PQBGJ{dLJgt`wun5WP%>i61$atA(18sB^7Kc1>h^;@<)l4SgeDv>S}F7 z_#Y(;P61T4T1I9R0k_&##*Q{tbT<<>W~my4HTrq9q-!@MPT?HF3j~ZI#3`w4{{iO# zy)${HKKdq#igdvE&A8HYgd?oPxju`dn)mLO*Vq>xcd2oI^%@?jc)$Fu0Cbz&PurnPY zfDai9VVDb)g~upGae=a?rjiDvf%;qfAz|}z5C}rYadf1lINXq6GmT>r} z2SV9X8M#!vRDgPr43~!u(4aThC12xB`M#aZ1@hUsDH%JI6|;d!(c8|Tdgv@e?~;31 z#|r@SZi!Q&8ykLoAaUOhal{HpuuH?%Ru%N;sqYndnojC)(}p;+qui< z)^v8GtBLJUj<4!(O)rxvhqBXy!MWwjxa%H79AX6C(=*CYM@~uba$6>C@{HSj#nl!1 zT8YrPi|c6U8V{^;zT-8vyY_@PJh~KZ;6^Ms1m0u57Jde8@h=r{9Uy)i5l>Kj)jQ;I zhqw5+q8}c~RsQP=u6G>i!Xb>5>pYM?9ztRI2NFhDl_;bS?fr6h@!pf}0sg)G``ye)2T^ZdJai8%QSzNv&dKj9TZem_I>z<^rbYF1ELh^*j2yFSMrxHIUs zj=1tGag8F*zLM4*zCRcijC;5=0M=eUI7f5WilA#gDPu(7Zx6=U#=eefCP3o0j+;?E3C#u)I5CMJjT z6TneWMIOX49pwR{a#jc(7!D6+UK*o;(-vUmdvXHY5IOplzg!A>1LLen;U=0Una|_^)m0J-K;6JM~o{5 zJo!y7F}X~G{!&d6EL#vn9WCpMa)Cu$;f@imF&+=Ns^iX<*uRiJD0^cCJi9uN1qnLI zi^XPCCE#&>HalhjN(|l7a@A@H8t18XEhq0PRNy~VpSmy7eMDv}seFfex15*Bs2Hl;4p)vcYs0yY@Yy4Jvr&PCiADWDraL~d66|-+1($N(|0{Ln}s|0xO z@st{s`4Dqx7YJQhB?;X!$GI)Ij08)#~N z?U~NWU^KowtSw+Vr8!7t2ZGcKeppGC=3c!_LA>HphlT&90G9wJwj&h9>u7j-b*9U; zk;r)RCYTwJfZL6M2J9jv2GA!>l1Ujo= zK7wF64gJ1=g^t29ixE>E)>)-WuCs+H zF%xCiI_h7XB8!7OBhOu9xAO~jg4_eXOm?rYer3tk%ihAt%EHqJ3tzta<+Jy)l~sz! z9AiVG8*_;_zZjlM6m*9>S(PJ@4Yz?zJNjyt2C+X}7C8bGupf79v|Ire@!l0vHfj(s zye_pG>uC*@AM5DgpIq@(>T`KpYZe*IJO)%;KC=g*VRCSkVHkUi8xXNtzBh=^b=1R)SX83HKE>Z+ow zZsHXc?zzOkY-omdBuVrA6`=x@4AzaycX1g>(0K(*P*?{y_i;o2Q0^xj@)mysj;Nc` z@LIrJ0X}HHbowPpkPJaA&^i$kR}l@Zz{J_yYVpEjSX2|~A#SI9F8+M8xiHFI$DUJ6 zFP0>3;(bIR*=#a_sDcXEt+r0DGKhOsa;%zyN5vDGc{K9wUa~iHl$*;4ryN|2`k&5~ zl{lyFhTvpAhb<1n29_YJMi)kC^4BDu+5H%e@(5mcM^NIEfn_BI72I-|m*`V%rfyT{ z?M`~aO%n5=C#%hU61Yq&-1>@nwgAmvVcCdlPb})RDX7}z9QS)?6oYv3|K$Y(Z z7SPiVl_?p?` zHbS@X_!my{`^@$EPqAxMwYL$woW6nKe+$9Gk`(&;YyX`nd}FS(yjAefzMU5vR zV=P!kneFjqQlmji2{Wz#@Ig|)&F(1;oQF$OEvjJ!EDa`VoECE+E|_iV!mx)6uhHtH zQ5ZJCxRXfI%Ri5E51XfkEreY=Y~;is^|^JpL+LDg_xOn@0Wmr|2X~Y5yRrCn zEIv4TJL-M`w5-KR@d@Fb;oEuqriVh-W|WqX?uPo8Y58R=zn_%fM{&3>YZQEYd3Jh; z%L9YUcZbIaXyBpGAAa|9XYo|dbo{*2DS>boY_&QMI&+Jiui&>_f3bJ?@B3SO>$@o3 z-+Q|7h2AV}BYg0M&z|e{Q}{#n<5+?`TN)xp5RIa0U%tuVEu2mdt3M*)kJ?rVXSFm{ z`^Va%85vAkg}{%d4)tT&s%-~U(O1#;?qvR1*U1kRhSAYlSN2gcE zJk`V{YR+Z#HFmcPTchLtXyJKp^ut||2|6ARzy9jw zy@gj#yyW0D0Z3F~=a++T?slI%rd(g^i}>~B-wz%v9Q?d=!sSaFW|?f* ztuRo79sn!T0ixn8L1{2E+b!}Z7$Ld;BYcDsSCq472>VEXGsj2zdw6n!Yz_iL1HAO` zpH=L)o7{PkIP;S_dTN*y=&1rZp}c%}%Get)pIXx8AtA2#UrK0>o6I4Eho{uyX{a$G zY2bcRKOb>fFT&O^SUt&B*-dnpiXg0QqoJu75)fQ%P8BO86>Eh`N)-yL=d?p%ECK0? zN+e#aA}Sg`C2&pk9N=h7naWV=rYB<|lCth(hf+%X@Lo+k^NH7-A$Eaw?9A8Na@O(? z2TUJNMt~*s;Gww%yIkPVD^pdYgf9)}-IN>~ zu@r>hcqm$NB!aQ}CIaPIV-Cqz&|~Ed5RTG_YDN&46Av(DIUtY&q&C5H807*GJp%IbCP{=$aKZxGSwz9&^V-Zwa{#Rnho-c#oIB+YZ8Y zc{h0RU|N^&WU-DILr+po2Bwl>zD&Mj`9xGtI7Mje0XifA2*eeOGLS zKR9EtplbYt&eX;U(47_p$C=XKji!}}4CW`a4!C{g@@4>vlpc;LLy;4l5zig2dqMPFDEj_s`I;Ug=;>6;fhEF1!P5p!oSPZ~UFUc_B0i{Lu5 zq9yK4S+o?XvEaC~B@Er0dY?;i0KWCGopIuTi3>|D;%)!jl6hhBIYEqclZm3H zz1i+vH}2)q6(6!GlEK8t2G7HS*%8JnHbgC{U@Qjgi z3np-NgPHoVQ@B|EoZvkK)=&tm!;5#L!JD_2RToJ)A3ngnvwvpjx_F0O#qfA=!vFU1 z)aHC86EuE1UmYVLYBWF|dK5zb>Bl=C<8QpXc^}pQgdwK1I;qmsRZ@p>`A}GAxG{nM zNMd+K>c53YEv?Jo{1YT^9gN@hkN=sW3t781UaW5JuOYim^))hkz1Z5THaDKb-?mom zZAro@OZK&~zWdJ%T@~^zy+H0I9g@LqeYfjrQxCu1-`HHUhoCOcE}rsb=)Y9| z%KJ1(i`5;!8y~_Ea>(C@#{=AWq(6r%J8dd#Dy#|bvf=$1-o0T&Hw=FLAUm35K`ftF z>vs0fB?KJm;0Rew5eUbwsuSR0sVrxe2D;wr6(t)R2?oE)+Y8|19Rm5*nvN4!KB8a5 z=sf;Q^~D4HlRXE+IuX^4jnVJN)xs%XIl~$qw&?NmUH{IbA#RuCN{j64{oS-#txaFe z+Y6dWMw=91Y=s+Xi>$Z^C&a?(T?KlKfsY0km&4H*DRhCBGKo6R_3zHb0I5weJtG9o zr5S0$hoAUCoX15Ad{Dpf>}wcEy48ceht-Z};(MnTN@x5prt==s@5qB67!>kkB2_I< z=aBOhafai;kNph@K@WKPfAurPg)T4w!4#^TVBaXp59SVCBZILNX9&nsdL`G7Ia6+O zvK1~IeP(VNNx7-hg%3>Q2FlQ5f(tNn+J|~!YS#z}i1jfWHe?1GW|D~5edge8LI>GJ zjRpCYunuut14j`w!1*O&4{e6=I2ONAfZ{jr+PrlPe>&=qDX9GG5V26q&CGjI=f}V) zpLrB;n)wz7=>m~sdeWJMq;ZHM4e;+1r_n)7J}kNAg1z1CJjLZU82D86yom)HpOjwgKu7ap z3n?JLf;yR~AmPC{J=R3Pc;Xt51Iaog%!1kWQ(52TW`q3Kt8>);A@HO2s_RqSQkF|p zla8g}!gLn;V%{%aj{ue8jJvi~ckXTyVJq^;Pnae6DWS{o_agohO=dePDP+O52bT4P zb`S4!0_U@fF)#`TTP|Q!?)5XKY*)kCNVYHD68N@AgwQRJt=KT>3+E9jP}>m%M7rSE zmvCVGkml=N!>Y&T`XW(i+f-$PCdLWQI)um(9No*_P_t?KFFiWrt$1PpeWBVhK~nTZ zYz~}J1@9IBLRt%x6cx%;olXMMS^Dye&lZ**Elc<#x;=*hu(atqog4~kLTLi0$ByZ+7L z*3+lE>j1K{y|=%!F4#>eWO^ho+mi9+)$4=KxI%(O9LP)21=cY~op)|PExR54fH!%t zGJ<$qH)E+dC?WP&ug%-fX7}9T0~LC_FSJB4vPFT_1TOqta5~_O=IZPz*i|~x-S#yt zLUTEH&tUaV2fguTaey@m9MMb~5uY=pSUv7t_HGI$ZwE*g04WH3W@zNQpX_1?6P zyd4!mI((RjVCh{d4hB!if-6@RHsvhE1%?x#gm%_KxUB8Un6)g=q z-LeIe?D6g_w&g(xS?nOt0c6h{@G=NmQXeB)OM_(Xc;4UsK2KaSBeSZy8)VOA&$2sK zS5;mjBO@asBO)Uin62w9HxezNb$O6Ovpi$9I@EBe;opO4R8xvcBr#1mvPaH+I7L5hnb+zBvTJ$*G6Zg0y~k;!Gw z)TqNp@hVS;e+-w>Xt;Hmj(=WDr4s`!*7laW}ozpqYxn}s3U>3j)0^I1=2f5IYNlr zX#k?wmB2h9bA&d1HK|hwa|Z=o6!L|IWgN=lStD7vsWsy0Hb3^iefabtw2gY5p*!D4 zbag>Pd}+rN<7u1QgFu_ z`Qaa24s)vs{R@2OWEV~;(jy!tCY^Bx*7XlLHI1MMFj(+LNbHDG#H1iqANIm&+w{xH z?$1<))nL+LJ9{QR|8FP&)6$PK08OpJknXPj$JK#rG{n_2`Jc;3kQ3&#_kx!dsl}PQ zn*X_sWNW*({vG1odr)#VH|aE3SbdJGRCeG}*?~{t*`IbczJ0RSd$PUx2se`T9u7if4jt6!t*Vm( zVb*Y2?Cs$34jei=d#UR88@uawdaFAdyY&4$-r0V7XMuOSpaxn;BY#6z$1e3XUVF;x zFrH1$>DA`{UJcKaJ>pCGxCV0}G;V$P9(R=C7A|P{XU7aN#vi;Gh?Y=yiMIL#M$XgV zHSyqJXn&K7-Y)R|{23%tUw=`lvp;hkr*a#ZKP`t}Ual_mzn^b-DgNv#wgD=zws0SX z_gOcFX>-u{v$NdlL(Ho-$Db@$H?;x3o^`n03Dv{ryH*Il+FE)1Y~2drS0^}s$fM&= zD_2AGe0K-i8o@h%c9()X!ApJSeb$Zbu7CTT730sYV$U|x0Ql33HPN-Yx%1T8Rc0zD9ePTJ482IxH|_0_ZQS@iZ@`m*o8{`DxSbh!xNcicm(?hPh^7Ok-RQEg2ja= zaIibMOR%f(ja(@_lKqsANR6D}BR>g`^isSTMOeg7>bcOBnHRzhm5~C8;Kb!jrX>~*u z;09FgbWhECf%h8GU@Aj92+8UZ9X?{lGsDg)ttd4>?x3MxgBRaw2t%V3&jj3PRE81_ z2%+O3$!nz*u|l&vy^`V+ulw z#*~55HtR+rm_0SLky`9i-3-gr@5F}~NG{E{k)}ESz40x05pJ*a-i=2muy$ELk%|on zmIGC)lTOT!b;k*t%~@7oTx%@^HJrU14{)QXA|u&ePX)5*Q|m%Pq!zc0*1+8!gmHkr zX%`V&B)r1%XL2TC3wNpdQXT+5FL2Eo@Z%;$HLGMaIS7t?Yqag3x+8k*zlZU86NjHv z<7a<9A4&|I=eo;pa5_rG4@8#@4&^+Q$=;AiMV#UKOh*BO+-Y*-T>5xDacCc=tIxHe}y1UOj<%6tid-W@5B@+^Jpwg`vQ390jMTi_;z_l)T$e`D+7D)CSN5`8e5rC=gf~*4kTvZhF{n!+E@(dP z?PEwx8%7u2EXb_jprcO`3FF#V38h8ytn&Sx9`j%Km){+ogOqtmtV8d>5Lv!_wLiSM z(;G-f2!%2j#7J#|mNUkLOqecjtyMPKTAC6pC!HK#p6U4;#dQ+rFnvTr0K^k4=NCY~ zfgxc~9#=P`rBsK*ZC~M&qqn0&(-aP|hz^h8k3Y1&bkTDpyQ|MO^r+wQ=|~Ry4bs$? zMCqtAOfyKMSyB}4wBUh84ILu|k}$buq&LG&smZWZ>j%t`SyWb5UY?KNP=6BDWM7Mm zpobvW(OE>AQh1*+R8w&=XzfxsQWtI`E9~N*!Yz*`AI^@AXCgw01Jt_32fgDSS}lpxL3(&{N0r$U0%Tv~Yv?Wx9dpa1bT5pb5~any zq9K8v@EAW3Bw=kro>T+DK|GF_lp>9^5`;MQg?{P#X;Qv0?q6b!XzyUrPju=ZBLTd= zQ$0Y@cu6U&n~*q`!sZ{K`dvkz&O9qds3?z zzj{B7&PcU)PFhAn5n_Sl-oEE0TS@wsWR0-xcgbMqc#L5*)@ARL5xsa5TAUFI& zcTFtER3cYmLD4OG7nYR0bty=!cFlcBG_tN?8v%L@|EBwX3@2g#BQ;g zkg#JD79)2|ka@GcBw_Wf@O0~UKMO_uC{dR3Q`Z(oE*R8EJ+CDYT8y5ftNO<3^BMe} zpZ$0?et(9`r099={l+5p#zw$XQha@Q^KyLl;b09hJqVjTyTGrr$@rAUEqZTvyS}pq z51jbF>cX9?I4;Dv7+Uptt3A4W^Oi88DsD)+V&x~pQ&uLp^Y*uS^j{yYS_Q_ED^}T+ z>EaH}Dpv0CndsacAK`F_&w)N&r47R;8?c`n%p*4kmYOwpFJbDRaC%^&olZOJ6qV>y zD9wg0@2*r)(&Amcnj-z`_4f4Mr;FiyRew4jz9lkza<;}7k9l8BGSkl-Yic1?nR{we zjVhtQ{C(2mH9?f=@FU=^&u-4@9XmK2$$-W2VEgg{WSE|3RpbIktw+Nbm#(#na7g~6ix$>s34jaugJ*0M}KK}QmuE6;!$yz^v|LEOZtKN&l5_yI4Qq+A^vxcr+ zXqER!2r)Oohl-DQ_E|2{yFzc*>1bQl_pDBol%3h>({<|14WV_Y|IA%Wx#EtP{Leoe zl#jFE-Mv7$kGN5I0s#^VSZ^xl<9i|}@qG44b8;wfba^pe zhc~JDMHE|GEve_%!cT=&rFTf^VRI1L)gv*C^~>dkFOP7MW^;6U(W$*}L5nO}93DfS zqtM($R3xX%#)rw?*oi|Z1MNyIKyBQO-hJPp5#1=?f4=pHt?lo(c$UKUZie~zQBGf| zQ_bov>U5b|I9FLr^JbqMNobD#6VoGL`+}oVTf`&;oUvQpCk00)_YDWW_i+EFI3mjh zy0S8w>|Va5qX$_Z>};htm`nT_2xa8}h?Z_c>VXzda0?67#z z+GpCXI-%0ASz_5doSs&%oEPpVvv080n+APt3Z?=(0hf|OE%%mx3lbgw{oqGVvxi7)pz8^HfG!sl8sCS=Cqd<^6jW9aF`6J z$p~C}AX-JBn2{vec9(V~VS9^X(OS92Xh^pSKX4@Fm;xk4I(w&HuHQq=#RHC}ZEUUY zevN&*IqvO}S@HNh88Y*fq_I!s*S0sGKi%r8@%`uonNBQW1epjSQmdnkLF^Z4G+;_e z2~?x8FrHjT2PT#B7*Wf1aEb`4iCYrkBsx#h2E^eC}upw8Zg}jqXNBR zN^O_t4&y;ReBs52&Om~bVM}o|&q3b^S48DbZqvJmOEOchI6NHP3{ig)Yz%n|a)wZq zfNVKltiG2mFFnAB(zIDC?c;o; zPQwf&=Bva@*)7_1N@PtwY_)Mwu5R@=g!(36Fru=&Dq?2Od zbdYy!$*9!qwMKKUIy*}993j*67hgz6VRG5QPLC8B7iC$b@iukF8&xsIvtN6Lo zQ%08fpmRDGI9&CC)h|83k!fUogt7}oRQIfd{r=LDsi{zV`%B;TkzrRuP%!I>bxH!D zQ>-{$e$a9@ajO11-Is`XahpC)fYMei3zwk>5Nk^hULaf`)Xf`nl}x^9(~=BBl6)ny zC|%o5AecJ0g#j5Wu$4IPz`47TQNJ!mEqqv%dYMz}w=boksh85yZ6Ls;X@`fb5Oe&N zN5!pp4SqE;9nP|M%p=56y#Kwfr zoZz10n5J#F+i|)b{x+olZg2-WDmKNcco_y36H%ws&q-A1J zY6EiL!m--@*n;p`U-C>e`ebx+f+TGsi1T|utKEu3Pr4&mehR(x!UBulcWC_xo6_nL zy!dsM`PYV%0BB=)+9b!oDdz!*JQ*MtefaL^6plgD^^ltg8b{)-0vNR_BPF36l5U;{ zA#a;IRpk!1R-ewCIDxed9NmO4>#3Nim35fUZlL;~7pVYKy6xm(vX*^Cq{fRqOE*`5 zZ8hygCTmBRVsZmBF59b&2;N)^BU>w-G6SBOLOv$^cX$-9JgizIC$K`^cyqj?$*OsD z)+HMMedFq_w1DW1bi(Fs%mq|h4GmD7JlmKK1e+`Z_r0)e;O=~6@3Dy{KG1dA zl{R`${+M>I2LQaT!(cT681{ycRL!*lQ;-}0;(AJcCS?|hJZ1Dri|;DsAWA_a$)r-= zLVhKl8+>qU^3`4WK^?N{A->7233Zetp5`%r>zfg_w+8}-XR2!@dO&eHSLyRv_HlG_ zB6*&{WaxvjJsG%=x7$Q3s#s~`WXUljsnIfnHx8Mc+Oc@~9z(rQ&eXD70e)5Y!LW|@*Pk=-7AO@?3J7y%UB%^V+{ala%98IR6-NYQ@rp+{O+25ABm zoH<0W&Y_+M_DQnMy-}{co$P0{CDN1_ODh2EHYQ)jQi@NwmXE~f+h|_Clq~|qPOzz$ zOeZxf1y~Jww_Xy7D5{kNVUcr+FA%+^tDzBcl{b$O<+5u`H@;&3GPxjfRSmJFB)t}Q z#%HW@HtHOa^FbgzTSG9S$V}sK`Af#PAzjq*5gj{-Ho&IY`2jqM_&7XAp3c*Y5wuGo zhDr2aa#xH?%nte&^sV3wI&Kz`Y(N)Mw)bv?W9dxu!=tJ=_=x|%>C)r&&3fogG-TO| z^=jm{DD7ji#RX20-k}W(;=n-*?34^kxoHxBS_*;pSVR`I6^~peQCDa#vRj8fGsK$a zNJB2KoX6HQXCypz{%KlrP0TQ4o^R$NC@`n*#h`5DD~HnpTWu{)ip%|=sz!qrEagq)Tp};%RZgwx$E2b%XHNgv^0{Y zffqC;pv%Ny`;>}^*-~e|nnJ7voIYmueg;#?jpF4JU>RgCU zg+$ep$qW z$uLxg(i5ur{Gv~3UBJ(VZKb!YQ6$=FiaKw#wFS&A7AB_XAfHD`sE_30C#uyL>*z{F zp$Fj7V>8{eNn&>{@SHYoQWB^3f|dB_%?r4;$Cne>hzsObc)T@P##P6~gr_yM`r^g; z5aN#Up2=n%4Hc91 z`pTUAsk^linSQ{k2`WyHv2AZdOmVib+a8c8 zfXqb=pZJOfXgLv8LeZ|?*0T2iGaB*Ti?hWbNY+%bH%CwDN^&vhn8WZh;{;$v#qF}9 z!WOBX+31S~lrgy3FMfn)>GBM2L_`R>LYM4aZvW5PTU4b${toXyj6ib9!8>KID35G@ zrb(lcy+$m$Hbt^>Nt1|D)6v_RT!X2DN|qXK z?sZVW9{eW&(AL|O$5g689oy%tx`&pyz5nF}IV1q!jo<10FHoQ`eh!>iNrn#=-4`)q zZ58osYuNzrt+iB0E5bVTT}dKT2UA(JmD<9pCf>bL1CYdp)u3Y|sDRB4ustYuE*^?r z7>`8GV1S78J}4}qY(N`1ol(+sLb{Z|T#4|^goFi;yD5%NWfT_u*~1mi@U1T%i8etV zSJJS-!Ngz4&HfV-u%ienWbx}Y=_b+NLdvNyewZ(EW#zquoO&?~OiOWCO%STAOvi6d zJQy_0584Q0O)ZkX66i)?{zAzG&HWIW6@k`d3SMrquc(*|kIs*Ap9E^esgMjOHnq0# z#~#ea1|&gN9KJoG8Byx#NFg(NeI~NQ79@-U)++%Aa5PSsPB#8LVqaR(iH zZI-R;rj__O-5S#xpilZ$8xlkj^r1 zDGh@P^r+_c9Rm}iGR-S0GYS)>L=j1P3UV;r#3n9Y$Qb3jTw4xO@fnAJxn`WoGCuuH zWHd$1z|hHMYxk!J6fbH~^xT4A&Rk~o zEd`Q9yz&erV1Jo${sObwHLtRA%(Pl(G^`k0@;b@r)C!g)Ye^GQB9Ve28uNcDBK82{ z1HVk;{t`J$@fx?KeA!!C;%}A@vZk@sV391=v8xp*eyAe>(z4X=x*lsQa((ilIH1uT zYMhg=_$dny74Bm@Qw1omtbDh=^Kg3?*BTrmA@uql1%dG%1Uk!ckv%^bg1ADk>=arh z)GItGL^hUzR)Ve?3^&vCn|@J+?y;XWUs=)XE0d4KAS*kOwXpl)Z2WdIl5M(`0*!2e zN5K|M6U-a~Z^Ty5i7;8pxcKY>r>x*hN&B`Y0uCbZYv zzcslv`JpFql9ti%p6ui)pyjStgWe-e5mE`zsg<4ZcpB9rxsuw}qw7_s!CtUO3WH=G z<@yxO*tjXxKl%}l5SEC08x=>m*Yx*CXUL`U2DZP0dpApStXZjHq&@|Umz>c|Y(41T z-9K9Tuhpghb9nI6zrI{L`03@#ANCLMr*H2q{ag6($)}-*pYGjT{^Xkj-e`==F7N&H z&Ce_QDE`BzpB^mU=C7lrm#a&U@q8CcGSLg*<$^JhE$H)uX^U_4l3z(;2YqvZxq_Mt z7J;Ogn$?*lkVAK+2LseVE9YaYn~2>7dq#rGT^~X2pLe;nB^W`x|GtTb-905AiRZM7)oi64#m5W84Nx)&er^pWQv;L@GDi zL>6kMzcBx!`^N>a>H+EEsq&(M-pL!w%3^!z2_b}A@6&JX?R?dZE8kTX2cJ@5w0Mb9m1Bf5OjDnSWOdo01# z5!Mk(9G$CdS^~>I;pDvDsxg-&9mosF9%wCMh_)a9nlTZJpNkLQ>aPlq_2b#CyGu%VoH}p&XXB+gyU^1*6fIX806M?{p&=tr;E=)G^q0=c z0ZEL%L=*`YSr7-nLU)t*BRK}WIC(uhJxzKx$W{*^L!pyo1G`Vwahp%tm8qxe7-F=y zbRQ-|ClnwqwK1rssf8i+;Iux%T`khkb=tB00{B8~>9cD6{fe{ypO>TIg-rT?mXm(X$-qH?ng*750?RiwTv~ok7pAiM zfN+25-Me=HhpR&mJs9z|mgQMuZ0Uwye;%uRu6sqR2-<6Q;uTR%$hhqg^1M`NlhiWs z&wbo;3_1>ADt;XFuW=oBMiXwgHRh7eTZWE{O){%n@2csR!27In#)Jx0Bsh65@FyLG>2F{$g3u{q8QjD!MHOv%=v< zB3l-zKE}ZI4LMR?Bn6bI(UK+=aq8T-n(uq1HMJVFX9rS^Qz5hbL&UMc8X?fHZP{9> zt;NCZ!e)DXK6=3$vYC*V26UPbuqhFEtgLs3U3P8a^kdc;B_5A4DH5U)`s{4NdDV;2 zn|zE3XHMjJ5YHLYoggsDgEZ2U)7C`{_CHn*DI#mnJliWPU|YMmKy3Bh(MWGKQ4`w) z@1CmNPu3C4S;ON&pRapMuXr9iyE*n7Y0T8DqevA}h_vF_yp|q#7`rEy=6)67dB?%9 z<{oT^hpjaL;$k;=Nuf@qjWyz=L7USxfJ;XvphdpMk~7Enktpj}IXfaF5O=0$ia{#6ABKWv=YIiN45O@eQZpv!1sGy^i(f=|t-LjVdh zn^)HCWCloUhpZK(AX&FJa@)$LkhbaUP`28+e<86dEnZM-c+7lo&*t6d-*m<7dsU2Q zPzk?#SremyhoU8+0lB+n$4Dh55{b9;dSD~leLcQBJprpU_d`7R zp9bH7Ti2cuhpdS#0PR{cjrGdP&idZ-ovp*Q?MLf}k2ZFB`S&A=^@PI0OC#jjg0^+$ zz%ZmWmJ1C41}_m;lgw;1d8&@i%o~C|&*6~=9Fug|TSsM@JmE1~uw(|t&Mk}fRDR$% zzTW7VqjFRjyrFQ7#8Rnl)%Vq*hj{m)SzXaLgFcEY}g3&mtNz)0-=6` zNrCwQ4!5?q*75lfvd*01(mNK%(Xw+%ZTSiok261tvE2LqHJ;AK{(=Y2v;^5jqq3Ub zAcAeJ6seS!`*w8J8|#2RZq%oOKbC|sD&3HC^Fz<9&mHaB7FrPV?&o*>e{RXT@w0Kp zi}7Hy!O4uC)`m}&M>X8cy#kLI5qCyk_i4`s?Pv1x4H6<)HJtCz%$3Xx`ni0S)0y3n zGO4IQ`rT9zeF};1-cddl%YLPab^;MQ8z`Wo?e%Y0_sCFD;_>(#_U{44hzXapl1ocw zsJq=;-P+x-k)b6n|Mu=$4R6yhj|@IM+j=zlQRa2zR=an@9*$zk6$nHf?cj5*Qd{uY zCFJF~&xP|OpEcys0KQ^`qm}npc$Bmq)$7(PGX}TmeVxkBjCHW@p=6s0P&rWgLD>c7 zK(qF14L~J}zAYKQe=Abk`^z$SJ>O;odUolg?|~yu4L_lK~p)#nXlp9|H*E5qz3gD-sW;k&rSM;Aj0ObG9#9=t#J zLbFGCFi^_NJb}ZhE50ni=}^-GoKT3R#r{`c#i+L$NN#5GJBoCvEBI9J6HbR;RYo`xNGgg!JO#H&I91yI{NJS=ZCy zJHUNBtQ<&j>eaE<+6swldcNY=@1`Cg&u=fnnqeDE0#iSOw zvoqY!F<6XM5ks|T243jKen&M4|9}({Hmrp?daF=Ntrg5MQB;`Fl1SNag-a3_hH7*v zW}X^{N`HpH5?ic>+EStgZeIhfR3{%1)==Y1D=k%6;s6W8Vj#*);8euY9~2*p$RUi! zch!j=0W3}rUR<7^X+K}}KI?rF>9xmA?vE3>f;i46Gs$o7>>-m7c1o+8y`Sy*@#gBc zhij|5qPJVi3ZEW&i)};!d-(#qFq>nr;!KdaSadFTtb0+eP{fW%QXVAF2Mf;{xnGK^ z>~1u{UaHU)fwIJGeui#wsxfm#%l)6>R+C^S-Go+saFTt~MuwbrLWGL~!Iab@a~S%i zLq^!1Bb~aH%d0xNv+9#c={ex6Wwdo7jWq;rOMsIjRR1;6C!r)kTtEhKEr@qV&FjN6 z#Nkn5e%*WX0%<@P(Rk=D!IMOsdQwI4KM_|pTzowgP;x;Huu@W3yu5h%yImE?(mePP zbj`N*`z(sNTlB8cf;iMYLP{P0q{WiFd;6z&tc{h`;iWGwEerd0_PP6`zg1$4*i7OMqh%P|+e0{q&5p$Vw4>blp@ z)jY^U-jFYG(wglE<3__6F~IkU!j0u4Y99!!^oj9N00M0iHJtk8g_A!c5Gq}#HI)P4 zNg7)u)!AxV4#RgU$+Km(IX9GjBwA9wV}qGe>B zO58qd+1hJK!SvUKYg|%zuzG>1d;wDfNCJPq+0SnV`6p7>A?4uu-r?Hj>hA9C9~Lyg zXmw(u0m!I1XS^@QKSr{-<1fGQwplH{mz2OEsEuQ4F1l7ik#PyP!J{6?=5)? zIU}$!>lY|gH@s#!DVqKQRSOxUx&sy`2>>5eW@-))d57g=fKa^1?B}jronvLj<6*9} z0i2tAJ$h*eP`D>IMyqpfeWhIsg`opNws4&}UE2V7({D)I7ITH&5|_+vN&^cvyWfs3 zUSGagmF>&UP;SOK=S2HuOKS4aI{p$&2&Z$`ch^>*tv@<^1TzB=iJ-Y#65H0l_1PB) zn7es!ev`X`{;hkDr=PQz{vC&C&H9{1 zz>2PfCwIAUUvCT2>_1@XjuvnfYqEB9jV2Mb>u2vq=i@U*@;22AmHLde26K?eqi;9% zo;-hexVpxK;JZdlim^(8lTg-le9cT+5+*9Vn-Oq8qO3FDbW6-`c!TYj6#OZMk_nE_mmSH-r?(2g@~*19?+u=L$kO`9IE) zj=?*SBd;o$MHm;Wt)rzL$NBZG$sMXT{1-%sf65MQ94rcq6@$n;MuVz3UN*(Z+57Vp z=BhziD6CT&jw8G#qX(m$&gGMr@nGW&=XFOX?vqF#l-FYW1naiS^tJKnII?-==(sFt zd&hY03mm}tQMl(l@X_<(iTu8g-$)@fJd>x-@bm!@l;ijE_&FY5AcL+v{1y*ykS7Yq zf8_ZKJRg5J(u)6r2kZ{u`XICZS3KFqQJ()z#e**bh3({!p_6JEmbHlws`m zv2~JPAi%zy#FwYfK0~xPo{<)Cg7{oTS}y$CvhZ(N7@PbvoxNP{i?ZAoEQjD^G+MX# zuVu-9Wl7A!@$l%h0`zxf$={)59QPvkXs^zh?=W0R$P-C1vB;$qH!-<{Y@#y7fc(Nk7c9whVKj95eOj`P_ z!7c8sz^SzYtQMY`4m0j462#Kf0V+>=7R+4dH;sT&Mi_GUAXz=@S4kGqLG~d_L&mU# z+wOgTr;0ZX%D3UjL?L%)YK9Ait)?<59vWN33!aX2vg8c;7C<}5w8I%p<@JnIU4#Q_ zHjzoCvx!`UO=LRK4%(@yf<+4{HxSg67ZvZ7#f4wSFtL=0s&x*A~UT@#qe8=1L!f z(AbKiw6@&@R^<#3y2%(;E!{$A;=!7etwvlgMvvk?lWHWwa`L6fZ@~E(^($p;>#Gl3 z$#(eO`V~(dYke+tlI2d@7b=&ZWVhZPj~xSOwCum`xBU7nz0*rnWjXze98+!S1=jcR zdvOQP&mb*lFF+CYXCTK#Fjx#2#WAbj%0cPRafsLC07NB?sZWMKt#L?u<5irw8N43; zbb^3O9j5U{;wUnF)o#Q=Ha=G-Z&T6TsdCY>rhr>FUT9GB^l|^5jO^bqGVP?b1?lVK zF#RP>H0|O?@E@2=kUna&QS(cXM2ZP0^^GqD_>@m31nLFVK7Zn|R&t2!i&Yo@Re>*Id`6i2!$|X1(!9y ze8~pi;kYp}lto+whFoF+5Mu77<|jyDb`PB|(!q=WyOu#w9e#jo`hGJ6$kw;fzj+Jw z@$BMYO=CT?Y55=-k!Mwbt=aC>K`Z`KqPwnDGo*!`RX=T)^X;e`3|V@K!@~2|?UFR} zw@_XqFsq(M6CI>5q*>(!m9P|qiwCbz1)d9Wa*WTeP-$)~2H3V`yD+O0wb)KKaA)U* zTWZ1IVZPpdzP7f$%RM+&mFcPaFB@A3z1(<&FE}v9zZ~jn=3idi z$cWM{I0$AG32E+7()sLSi2nEYb&@oxukEJ6fcKze|)hcT%E*h8CD_COl3oeGp1A6e_ic z+wtr6pgh!$m~VBcM5n9o_{_t| zDGQdZyzoJ)BpTtcd9_Lo4AML6>Hba{f>TxANctaZ+LuYalw>=X8BeRTRn`;?C$fXI z_EbGLuc_6S^2k`LieC}w!Q32$aSU=t(?1j^7Aj9)J3m!krqH#Q zz|?CfLW?bpsLJu?gYX+(@ce<)Q&Lv>F_!&O&=%j6m`a)oCMWk$A9f{rQaFc>E^&H; zCQf-Ngkpi(0!6ZNk@2fv5bA8V8h}9Fy0k$uVGd%TyRC|VvfK(4k(yZY#aUp(21g=; zLUTzwyFi7g18OP%vaXYA*a4P-u$83dr8A3D9O~I%P^!Cw{-1EY(VI&sXk!>r&PPa8 z$AcT*(`}sExOhE2!M;-717;lWxX2f5iNHbjx`>??$gy{l6e8CEy+BNB(;g-Cv5gb| z^~CbT-PO_UZj0$B9Mf6C(Jyl`J^81f$8P6MWDQCRdcq92HA2s^R4s=$g~enErOOLb zk#u6V%if#0298fhhu{H+R{Bs9fT>wU!2Nw|0XJ9_eRwDRM@NV$q-PU)m1vIJyTbD{ zNF+P3`(GVnkIiImI9AD+h~CZC-fl7;4ENAno>9$(^ZFI91)JbjFuPuRa`{#wb?C0? zJv}{o{%@IoN@phz>o&T}NIHTdtmrDxzCh4G5Ry z<0lbY?w(I{ZHz~Y!s4Z1CWaN8!5M9L=H~;3ax5Y%Y_?V@InG~QN`O(i23YkeD}gy^ zmO%fydQe}~ANSf>CGi>}BUI89tvL5=LgQ3+E)gLJK21ZYHYQ(N)@`CTgfE9`R?B&O z6ElKaoFbnbf^RdV*(=Kcvn)B-Lu`!fGqdM&N2BgBkW0Cs2?l=TvelE9gWES%)?mu@ z?xr3iQ)skAo>Ja6uaXlt*~2vIdYy=v=Ix9TDyIn(ya4Uew5m)sAw)Qi4!#_l z0PEZ>fjhISTx0qWayc7Bg^2uJ*k`qInFrMA4F+Yr)(5db0tRWpF;ib!sQFb~t{YVG za?OC*P#()8KY_BUf#q+4|J>RjRFj3%P99_uVq@x9J@r_@AVMD;U|e-e4@yLHpKa46 z!i`w~WA$>CHWcmFIvp!9BZ7F$@~GO|B%hnvq%9av1dVBSi0?XwClrh*E5IkqJ#LDi zsJs9l{=kFhuW+uK>IqnuDkqYD8b|VBGSq8d8v@G&dx^0O_zY}d9XpYy&>Pxg%+vRV z!a!#;hhd!*q5cfeB3aM~YeicvRZ9B^Dm5Z<k*0iao8i!(U^wB$>&Q#l<_)eVm()@~ChZ83vYR~50B zR(`@+U^xN2@Ro=Rj~#$El{OJQxxXgBMWe1;w?IwfV*-~Z%>XAK2_XNy3S`n|6C`cM zVQJ7hhGcX%JV`CMk=+ER(#B$SKvt*^iZu9Sw&03utf83lezD@vuxun3<$4DuM?my3 zTI1OvnnsXVWtm;x;@O?_hRpfUU&AA~XxBqK=7!e2VNDO%nhF3J<8HB;5e+p6S1(;a?&0K|ISIs}uBwA}Uuly!G9iP2||5{B7mMB0c&K=MDtgX>^hKlkF8pd(TH`&C1jOBK$MHYpSkk5S9 zuw-+S09VjF1}&-y(51}-u7G4z)a(Qb!*e>t6F(6bIdMwm=LRVk)&^u1IYh#1T;C_3 z6P*%F-3rTlj4J7;?E})*BZ+-)RH=>ed24> zi6tYH7=byp^P@B7EyA3fk8nOhGHb#1Dr)+R;qei}qhJw4UMNx8?Rtn^9DA3^hOwyU zH(FCw$9t&im@8f|e(q_r_>N}9$U&LB3mHgRq5k6D4&jlo1=D!ULgW;g2g4Sk?vZa~ z$8ZcAGP0`XCdros5y%5&+l->A^gT5sL9j|GG9e|QCd3MDN7t#aH7wmawz6PDZF6^{ zNEh%Q`~PvUg1dPJ2P^jL_9u&S=S-sByxZyuy~+#K_g$=~eYH2@scrVsx2^?J=FqQyZ_L0*+6d2tJu0eWsuWydC=hyWLC;(rH({MXE;N*3ZZi zp^ZqKR!8ZekFb)AT@DCpN9084=^Y)glAf20%OW!o@1G3H6s5k(VcGQ}I#Og@$sb1( z$#sU#EC7syuSHsnZ4s4&Cj_cS(Zc=@lybiFYAexF?bF_VAD1A#hNJF5uZ`v{Dhn#+ z0uEI`yIfg`@Tes#qd&R9gX27z*F|$!5KDq>XQ6}OtR$=;eI9`gTWnA0SS5;7tcyQl za5Lv&>*LJLO*9a~+f7Sppy4)-aNWG!zg>bcqUDcZ@Y?q&U?6O|Yq@5 z71HG#XE5nHkw!r?KO$=u?x0_OwcJB`*=L8}Z|^+X?Ln{|oxgh50Oj}+)~+)F=6E_V z=xz_<4jF)kKe?;srgREtsQ{5ua-&hCQKD={j6eWojj%L8n0$Ei61UM2AG>pbzyp)K z3Euog8R0yoAj>X!na8dt4Q)t9U>D9Z=ll)OaAoq@%GlAa7y;+v(0tp z4mgzS9*}0Sckp;)6GsRRC4v?CUJoB`Z*Q)zZgo<%`^ba{5^GGprizy(Kc}Z4NeZaTrD0aP8(Okd)(+q@uadTtAZ{)#$vU~|CmSVn1nxX2Mjizs%5Z}p z5D--l^AsA_+E)PN$|BuT6jt3PC`obNyL?xPGbJ^fr)a|_Dkx2@$ljq(&1v>PX z5&SzuRL|0zrIX&1mC?$Cw;g_U_kk1}oxL1Ckgjp9*MnZFHn{ooO)mD)F>1r-j+wS) z{gzi^V*TB=CYYkqHUT&ck*87`B_z@XV5efiPS|fzDIH*x+v$>FfD8+H;8PP%VS8f$ zvbDjih3VSnj8=&96YGqQPM2H?O$aPXkr5l28||&LAH}Mq^JxTtSJC8oTUd`Yeoxhp8t++_Q~V%d5Bk7 z;BC|BC|znnMFl$yG0ay0$VOr<;Z2&u7Li$gLK6A8Dq*7=J544BJCH4@gp33HBG@k0 z3^wtqBPl`#Dy@--0_Gfh5U=JST|potSIqp-1-hCGk{2!wZ;DHqYq^0gcZugId6fKy zVw@5K!djXJU7;&`e|qM*3y_cS>AFVXG+GC}<*JOxu(aoyOMtSy1n77y@w7c|$|jOk znae$~g8uRW8uXi0(HY4IyaCEDV&fr&H;?}716q^Uuu548CnUZGt-(fRAC&v}Hry6U zAo?_ODx2U;jx9z!Nf%zK&GBmtu^4P#w7>cY8QqUvE!iFLBTE<94&62hqh;!ZS%Yku*Hm-tp_R+wN@GxXt-X_lWj=i+ z&L>Xy0G}!cCJY~kdNsre{KkaKz_Dtofv&8)JRiT=lkH~;!UQK#?mXy^ zWY3FTkj2|Yq;Nj-O)w=hQbDPL3(Zw(6y8L6KwxKH{SrLV73*uC|EN$_gKjX-#yzEJ z4d6@`DpMAMBy6yP6LRVxk*HdqWkVmC%1<4j|hF#E4woWV=TJn|I)wX~MFuq)1Ua> zDEY&>NlDyVZ?3qG!5E}zZ0`yNWA?5tsM_XXb})ckTX(Ia;V%-vEP9kMp;yX32rqzk(XL?hg$?WpJbr_(zWQo?`^f_BWoiydp%rJ~y|-w2 zG49km!K|viMUa-v4KR4>v_hH0)jmkGw*UtLJ)v6e=<;Hm$*4Cl0gGtG@frR@vwLg{ zX0c@Qy1l%xkTk$uIXkJ_JA|EcU$C&$`~%1f3z-7tOeQ@RN4;04h>|Q!aOBdWW8gjx zdc&jnZDE0zoK#35GBw^r;r9QN)GFOKhWFz#Xje9I?y{}Q?TUj?{r+_jTd zNPzEjjG#?PUMVn5;1b5wJfn-1xj>PLvbDNAc`ZH32snR`Ko5S?vlh9BmXW{5-A~N& z$6?Ea@eAnq|02wk^)mvjW}e259%#*nH=A;_Cn+;=T{|0vpNdv-@p9k%|L!gmPv^%s z1q^O}nqA!`Ip4h)-);7pDB{rv5Gk%KkRAUNl2{UCtU6#Ih8iZPXgzByw52FgCxKEy z02~e8E~aIOeNpe`;@~fF0Yc?V>{ICF_Dw><1!s;Iw~gVlP4fVW;PKZhE1C{+72;o< z915p8e#DbN?NcldJNdq0Bx8Rl=SOeobab&|{oEmwZk z-XKp(OJ%Cn9c0&x?cp|2=|7BC>h`G0bo)F$(u)um7W7mgVo07f$c$C(w5pKlzRY9- z__(Y8I!Fz6QT6Wv!x4AnCRIg@$@y{IH@a5a+@`hpGJHOLn&qIkR(%DV=wz3ZPcs3# zvo-vfyy@%SAud1MIXr}}xV(IKXQ&Ht5?2KjNFfW`qKW3I7r;atE}PR)ChjV|%2qz9 z;tlBrkpWAA-fb2PYilrCj8BJ^!otWeEi2Xp*(_@jHM8kAlD?n2OWqONUq6z&EI^-1 z=!~nm(?jl5?9dzTk{2I(yXv89dZDw|M88ekd4lyvyMr{Jm27vMLItqXlZqQLew0&f zd0uo&;DeRn&=iou0r&fV`3v2gyhT$DqJRA>I_6u;9n!-KSaXw+m-`ApmZqY)p~N9Z zx)*T)8Sa$f{({>H=ooPDVSiDPP7CO5#srHKNZX@Gjv<$s?crM1m@pXxPKL|{jXhVf zqCB#vSXfFWNnds{%16==nfv1Mm92>@ZKc|lMQTiq6yr-=K$3e9jcyTgkTKO*g7k2i zu07cAb1jZ$a04>VXD39b=rl6(d@zDS({Bi}<)U!|w2YPH zCON4ItH8@GKm+k@`f9$1%qF$fIQTN}etVR}bd^E5gs=G7>fV!$t;gGk>wnx^$NhlY z;wHwKM`%}r+1%kY?9Z# z5Sj0w4=MaslpySmg7t3NovyMO03(>Lwi&MzKNfk1cGPmUD?$Kp9TJLk>ReSMkg^2& zU{J3&xcCXDlYe>xb?yI7#%FgJUGwJTHoL@^*2_rGkKRw~&Q-jzUvP@@X-DY8K*W{o zD4k|0q?(t{9(g@SF{hmy(Ej@NiOKp1rx!dxi^+lGU zFE+-~nNH$Fte!cRlEy^tcpzaCZ`aiA(OYRr)o=3~?C!M2>5`z)nLcCTVTfE|j{3zO7`&6Sf33*;j^;U_rqq zjMdpulnc~ua&688*#!8ZNpj5&P|q%);+EoXZCR_3O#MX>_ZCO!4bANm(o;o1dkF=^ z(l*UTr+SQ}`wczG)UPR3(^?1R!_Xzv+hHlx-Ju%)9y4OaCrn6C31J$k1aD)*l;~uH z;h|};?QBH+hsn+ggxNOqZ>>K)kwtc`q@`J8UqM+)dyZLkRD)?xC>l-gG3KN#Lc3OG zTxrJ%we>S{cd*rlbv(uz`v!`}4s-3N16O$?;cE@D*t5O+nMx?}mP{i>zC1oU#;W{q zr}vGXno6*M$UV=5i2(=;(U#;moC$sppz?EH_S%kcVr2TOMNLR66>xHCcFG!q5c?_9 z>l|BvFrto@_&VWPYqn8lqbzTmm>8;oNp&6%KDZm=I&jz{KPTlj(0ZkY@n26#%7yYjavFY{w2kD zD=4Xl1WyoSiWDk)74AVWYe+Xx=K_N92zR(-?XeuSSAv=;34dIJ5xA0CS@kp}l(8c_ zPFiQJX9zPR*BSL!*bE_>{X@ZyVjEp*pn&D-)vQ}tNnCzyzR{U{Jo`Slvq1i zA81@oXs20_W7#vNAyv}j;PXy|9|doity@_-X3i^gQIJh;%lYsbjFVH0TB`SU%|uct zR$w;g0XDN}c&<~(Sh24JUz#i9!QyUXv%=Ds8)T^NmI`CcttgG(c26REYU+yZ_7cuV zGQEVnNC{oLyf003Q3Fd_R;2J-o{#*_Z7Z>cZXOa4@_4)6N2Jfbv+xqWrO*Ptu>fFA z3uFe!tvR$yiUC0Utco3sFH27`3p*JE?Ekv^^0mL)X4?_-Kh6qqXZvw=-RdVjA1g!t z)LF3!Yb<6=R&jHGPmLfQ!cNxEDiDms8~qNn*Sb+W5M!&~fxG_6TcnzS)qc9uA(d3O z18j&XW10s)e2np5fo^HkOl5yVABJXdnbKkQVoZJa9SN38Mdr0K0CqhX}lj+`qc8`VmLOl3J+L7}(M}Cdxv7VhG z!A$P}F>JvD(d8GRD~2XZaMHh}dnAp10tiE(b6 zHiusXA$x+7;hPu3lany#J#4+thUXf`)$K1@T0+wfKyae8uk`EtJ$?xX1P~F;LI?}{ z6+99B#8_&VMcTxLB6M+BhNX5_h<Q~>Dh)Q#4(Sjv#@gAz2NU22lpbu9yqArCG!)@-Jy~M#Ze@zRJ>K6;8*iW4TNKW;wQvhm6mz0o%j~jQE)59i8w7Fg?J>Z@Q~2vjTFp%XWn* z@%il0XR4!_#u*s%cuNAr*IV3o%u`;fYoT z12uz!t*gq^tQqCCEYAfZVpKXsE&7bOBmao-waqc^Vm(LC?aal3Ysr|X(fPk!?8S>G zHs}=^$K-UZ8Ijh3f>R-}1Ca% zNUhVgYi%Ycac;2)>#<_s8M#hc%(;$!7m^g1z)M=7!km^<@1n-IB1uC{XeCEC95raR znW0aTEuM^#5aJA}7)5qf3HqGJS(gbB5)0ynsywiLfrqCy+lSPkW(>)=Xptne*j6JmE3!bc&Xcy(flpA)VyrRmy3x(n{VYkS6eJEC0JoJWD2Nx+NkLML=6=ORV@QzXxJpfR@4&awy_YqOr)3uA zqu12K$C0bWDgnOf5;fE6PL(ojmQWB=+W|JX@8s8f#VgDYj! zgA_8Iprd3_g$7&)CJ{-_v%2%`^QY@ud(86od>usJcY_!5#354(&-XSe>G88%Wi0u4 zW>?S@K)t?|=f2$7Z{~sNIVf=CtNU_Q0oV(80bSoC4uJ{9!gc~t=1m4+Q7e(%fY}a(l3U z(7*Y^H@64NpW;8?e0Adhf42|r55D<&k>4eqZSG@Hu|l=W)}fvuiwQ2Y2+q90)mwn$ zoDCzKGCs?bVSG~?n>sj+6(N4HWg<9`uaav6Q0lj(2l@hK>{I>{A>{Y?oJXnc_y7$0 zv;~$#9Mw{j#HW7SO^O2XpeRk{5ur%feu_h!XBUD2fmJeggOBaRFXVXK67`p0 z{gTU)<5!+F#71>t-_qwuW0(nD2SRB-pzU-M?$@y5mp$LCONoz1ux_nna6<(o7>7P1Yn6=Hv5WnSl%Xh_MzfCv>RD~?xvxDQzP*s9Y(t*Znpt28S8IXGR=*)pG(f)MG$!@X$DGCx zri**>mVD`2!lt*6BuhBbiL>A@5HC zTwr^>rQUrIL_vK;BcMK_6ZaC+fh&pV%U*bFO_Lqu?zfdXxHz5Q^;Mv*qL)Zt;wbj7 z))G_tm9Ku)iLJXb#bf#e7mK|hQf>hp_fr-_DNWG={3636CX%wsnY$E+4`6uEI~dH_ zq~wV+0xpcnJ~+cS3O&%jPWDql*3_E_UZ7{#2T4<2E>W`1slTqPx2yqQFJ6VuN^|k5 z2ZLU^iMFTAKN1AqkiOvE5V7)p+QS?oCmo^)im$hMA%_a?`hu}pJ=rQFDt$WRQhI=^ zD~^#iGsP|FvXik~0sTFsz?wecc|^n{g zXI)V}y*K4vswnZlwK&H5%L*!`2u*DrVN`h%ReRI@_l!1)BDVR>%tn=Vwb7|y3OeV4 zQ%cHOs~Zd%Pn8i$QOS3zfVvhSbdb#Og(4T_tf}c#F%o005gUZ35>7{HMcFK{SL4Dg z0QW^X3-ppJs`fD*_}Fr1`3Qb9xB^<;WGF&3spYbm{n&$i*C}DbFZxBWLDEcThJ9BI zLsP1%LOUy}bG2E*z#_cxsSG|WozW=ZU>#A=yuJ_$(?K-L zrXiPQ6Q2i5ncC5=#nHy!6R#14A3( zV`IvFINj`?6l@f8u0|fC#m&YqaHbL=8M2A>O90?F$9})}1}c-g=gKLMXy`#y2hduX z*x}*DcvpAkV&TIY0tP4?Yqd-#NK~+q9JrJh9538*=e=ceTPont=(NN8JqL&CW(XEX*?`y;$(wq?Rlrd!2?wP@-9UB##&f9*!6kPLx_ zCL>Nwg=n;1IL`Ac(Cv0bTd8C5K3Q zZU?0U5aYo?RJhL3z+7WC^rkm`eRULQ&j|K7Q^gw$LMSZGUniVPxMU(ru(P~YSzr+f*xYU^oIje_s0o(EqP|q?z zPdnnOzRripHkO;#@|E+(x~*bp$tfxyyCzM_tpz}f%-7RW*ZKiYTUy}WleDsN2PFui zIdf4c)TNd30wlIw50t81$#S9HXSvO?B#d9{aKpDXe06leAQ{{Al}((DJ6a*@FLVB! zo(()N?9GESq{dQ~g(ThoUC0b#H^q5m()imE6Y+S>K!hxAMO^5D$)wbfl~~U8l8CNL zh=0aynRr5N)7pf1AY5HqLGP%Wak8JXqm@>K;nkI+F4d{$<{qw1cr&@#`a9^DjNf3U zInV-QVdUK`Hda)WBy?A=rlCJqueUcme}g={7elM6nnS5b$$;eh1k+mT7V5CG#BtPN z`2yo#K2`)kv23fbyYcPT>Sipj@k$-VaoN+;jV;{t6l);7NpMH!JF$u+ z#ZSeJlCz2ic)}GWCpx zzzFRK6KAp~nG3zVRm4I1!GTx-h9`sm?%tz~E%AaJ$WQ3gFUN0(NCfrv_1l%Zccn6Y zTIZiy1~}4j4fvb!yW#V<8ga)|c-+X|&uXNxK!wn+cJLgMHxGWg|6>2v$tvZdR1V2r zbb~DnucO;XkIAqczk;K2Djv0eb8&{+p1q_*qg(bAKm$* z>vq03UXu~W(O+@4zs-GLY216`XLem!?A%=4=i~QR1mnwFCr=0oYQlSaKTXEmFino$ z4HHB&Iu}a+*PBrKiY-kSbu+m&Of6Sx{daS>{u(u55k2#_#=m zC+r=q?;MbG5_LmFI*SI1PfmOXFj*KN1e*aQ8-6lsxCgaN3gL|KUzW*<0LL?9( z(uvq0U-B%4`%kU({d=GNR=8T|&qc6;qZi&2G6Jk+CjbSZL2TSRymbPmxnoRI8D`*U z%!`aPmSm`cforK_$f61wI2oQDf7m!&P z<_L9b$xjEgL)kJSh9%0$)q~yP`Mc5a(6W2^>n?EGngR@OLGX;5*B{StQ(Ouvzg9~I z)Fy5>fEe04dc`KM){;f2KfD~B%E5{GQIjJ+yRLx03M4zjm$-KfHsz~SqHFsk)YSc( zng>=-a3yHm;1V~rL5=fKOheoj94nP7(HN>XK(xv3ZUun%@bYZ@3?VQ$hEl$^N_a^J z9#%@tz}pNI?GS)Z)qB@Nb~ro-Gj-cWhOMP|C>O!Cl6t&j`D}cM03bFTikHJEE7&)j z-qtvqd%dqzejLJCbqIGf&sKs3ga6?o^Yg=#A=(G!GIvDZkFIfdg-tod!WYY*^B>IO z%h9V#G1hOf;6Alm8Hdl9d4-diQ$k5H<*E)Rw8SGEkPh(=sikPZUSZ31- zR5)9V&uL57z!W04E(x=}k?o|7z=)E&np|M%0d~8jzE?Ps zrCe*{B;B)<>B4Sg1;D5IE>9dkL1XkT#kW{u>_c8pQj_*3I09?1=#Lqls59^g3BTwI z%+Bo!YG^QzQrsvKx1C@%Lut9x9iwuY8IjJCy6Q_rpvh-P=U8u079XN2VuYZvk5AtX zzoUCArWO;6_8Kqm1d*Cz^WD4Y!8m=0utZ}RH;z;o#9BRU>(w%&0z?DBV4=O zSde#k1tC?31QA!f=f(tv!T!=k5&c_aAGi2l#q7$8(%8}EsV3T#%R>5o-(jB56h!

-u}Yhr#)Wg@|aF-Q@KMyL~U~6a)oS)9L^;#E!;mr!|fP3&N;sdTZbnrK%okkr;LQ z^4j7&J`=AH+$@Zwt3D|!YWaqEld#B2mZfBGJ#S!LH{{T=N;L%IV`<>RJ&+fdrt>b% zn8a`C_LmJ#Gl)<=8N`wn74^tExMpO=DS0u~nf<6+l^rawgVEU~#46dpClO#K4b)F- zGrZSZH8f6V;qnZr&%jo64U?CY46(RJgfZqis!)jhaUXB_kv{NJFCi~;5+q?{kF8S{ z`K0_7z7iS+J4RS45tjPJ2y+93S^T~D;bf4xRu{)o%%Mss&@$?>Km?#halkBZDa_r;`R7sO-^uND!3A`?n<<$6=gUROhuwb611wFRprmpl-076 zh+HjcY0lA-u38udd|v? zt=6-M=V=5Q+7jXlAdxl-hIc-Eh15B)RrQq}@)lC4Td230wJw)tni$D9my>)=J(on5 zMEDiDB>yxigFH==U7>BOI*42?F|6APa9}y&C1e0Y<8)EfRYN_-VMM=&n?4 zig8nC*Prwm*P;G3@V2W;YPm69S;^2Pn1Q`&@JQ_wUx5WTCKXI7>6L?e zFSa~7nP7T@q%urh$NBA~HlCf+AfzYi+Cu~D++4Z+#s8gEJn!^vwi-vtLGL4Lcr{cv?tZt&WDwz?(`(?h*Tl73H9Js=q0zCDGxO$|)x?HN{tBx?&> zZyL6hRVzHr!hWvVBkN4Js9&43AQV+(P28o2VHQy4O?U#5Ybm6T$$;X6!Z2QZ zEr;(LP#-)Cq)s5hE#xiXf!}jyq8`;Ff=W#8BDgFER%sU~6z20Cm>qdEs$XoTZ>9n1fC5)!L z00GZQcdY5gx(b^-<&(N~x>||`QJ#^|^lZ#65KwaWWE-f7 z1ms7X0XJwkFzMU5K;Yo&IJVoM>J)EY6s1{X%Sp9S>9(C`0<#VJv7Eb`AU(8U7#k1t zs0YOoL5hLGXHY0gJ~{NEJuf6WHw(q8S^u+Q;YcL+U@6m3UXgOjB$akC4hsa^qKrzr z8c|tONn5ngWMoT8NCa-30Eg129GJIPm}Iv~v=RX)DHp}W2_kgAz@I`S^>i6&2?yaW zVZT2*!CseP?><}DhdWT=i+LXst4~x{OqlGi6IhIVb+1-#AEsWVa+|Y7l_T|AwmYoL zW@>Q|J7;$hSKtsuisphik+1X|$uqWm#F~i(PA1SJ=k8#11`{Yxr?C<2BzOv0<8x|3 z2v3&mfMrI@flQy#gzhxNeRlRkYb|b-g{aCN*y)eN)1bm3#<5u9Z@&Y4VyJ9X$VNJA zf*3k%e#Ct$yW2Z^hr4?_8(ZHNOS@TErcGuDq~~*q{K=t*__jG7K^CD8#Rxhu<)W;_ zfPVaMpx1R78E0vR8%)x0?W++VrCrt)`q4PzSrycW-+rag!om>`lVi2^R9|%^6^CaS z18k|K^4sL3gXE*yk`BtHFfMsrsfs6lmzlzn8u8#STp{>^j#E6#zbTiLMmMWa4SJ!* zeULo$7v$>X{pf-RqlH~iV(`K`Yc3wrkZJO5<%&yh2e9~xqX7nuMPJjOZpWafHm~#2 z-nf3dcGHg=rd|n3V%H+gO5(N>6wK%s z%rW8KG8+GFvKcB*^Ewm=_}_5v?;%S%AaaMB@VkP{lUz=hF-!O~^%KMq`(eN+M6=tmDWr>)__J9Yslqjur$q~e~TH?${W?mehCqAF;9SzFs zjvSsxB#_4cX>|@E(7%tyAZr%?QKA3uxWzZmTFUg5U)93CF^dJq&+6TMQ(C%z>KuBr zb=whDgI88Ri^FGbsB@H`XLH9nP?4KcbetrmwnWKHV6Vnsr$c6KDMZJr;AG_OTiSRr z2C1mdACm~3g4e*N2jTY|ESq4v{LRK3QEEjVPWD5Bdx1}#w9(;#(sXl#j(yQGk|-KX zF{)-~Iqt;}x9&`f9DBY|yM`zV(N#?eDiV?Nl6Euq_D~thnDl%C_=80#>+3hVKjM-$ih3C(-uaVw*FeTSr$DdGne+3s!XJg#%$@&rt&670_noVl9ZM_PgR;np?DAfo(wWcgov#F5G=GSg$ngpE zZNLq$C2nuM!DH@%=lCFVUBN(pHLUP&>U zAOc5{sGAiq7aL7>?2DHaJg7-97F*RkPG~L6jTA%3;Hw5fy*emltVrCxvzgX2P9(*RnQQP$TTn9Q0i zhjQ-1*EiUWj$j-SwE6pbcKUK28#l)w@aty#5$@&w&7M*?;eoW|Ha9lP#AMY7+m3?Q z!?R^0w7BbuY-Ij$2D}IpU{F{8=Oq$~0#enXC!rl#seF-55s-l9Td|lQ)zKpAA{1)eK=NDQuJQRhyoOf2Wl(M(xOcC#}jk zBD`eh7eKWtsF@!o9Vu`@*T_mqRCA}IRIxOKm=(9uZBjORpF$a1=*umfIzZD@l>9Wp zjKYj#O*%&^Gvyv+9TvqorFNqq5sZgYKZgM+qxx_*emfb#MJ|bmRnL?!em^G|TY><@ zz3Na>_`Sweo6Uhd)3|fLMO7KIh3IMNl6LYsR`QJ9Txl?Y7f!-+;587fph?hiF+ zgHZ#?(L$uC!uU^X>d?zN5A>f|4hvzAe;>QZ$5_DJn)3AM{Kpd<^Y&E*SBl@@m|tP@ zhrc@NLs#jG;l~-pbYqz<&?&piX!6Jn(~`K>P0@(q${*5VL|#Fdv{zOVP}UB0g{EI5 z=a4B{!-m2DQQ^TsH*GYkze6*bWlR_E4#wxOX+O{omlP z_vij{PQ@BHF!MY2zheIR2mRa2H~)NahQvAxby+75*kR8}*LT~k>0CjoKXFshrgG2J zKBPJl{Xx6#+Le%}7di|CR94qT?uJ%MoFN(D49$I6vi4S6bAK4Q^mgv6-YOOuY~ zCdIA>S_pAflCWdOarj7S$txEdif(EGB}?Usp1qaEK4WT2zWpd6NlBEa)|JgDor!g2 z*#K%nuI>h|wkUjbvX}p;B@t8;VT1WOZbv$ohtMtb!}rcUxj*q_peM=hE3}k~&~Emi zcTXrg*u4kjoxN0wXOKGg6RDK#D2_}aZ?vhL4^0tL8cro!z1g>33=7szOOwJ=a1HM( zb9A-)MbO(D6viYw2ln29mTlcZ1*9~Q+C41T(4!?zacE6W#bD}I%(qHV-Q7&Vo;K8h zJN@V^WftDFbi&#%?;jA%>R4e`mwk;I5mnnkyhwq}1@gsW%_lW&ZJ^>%A>Mr+J@LK4i#6f!-WmZ&M?niG2 zafPZ4isIsSkJg03r55;WN@nv(Z~xD?CV&26@S9tc+d#1|O*SFNXR?cDg*rcrD6xzF zAShnbUK78U?i2E{dN@SX3&S@C03V=5x=oouYo4pLcKJp=o{+{lZK_<>N~TO-vNiMo z)I@c2*(ZI=D#_f5rsXG`(qyy|exS6%5e&1tS|70UBlL4So98H;oW5 zjfRN>D(VlpIgz_AuIpkXf%5DTvxo4=TW=3Zsc@`}So^0nC_Wyazd5=<$eG2XrRQ13 zMde`s_?AeT2l=NFT6?e{I%}L}Pi&gje32C!o-5nB$`$RKE+JINT3U6$8*t8RaHHk^ zrm~nwdfyVYw*-zvaSv62ErVesX8F}II328#4|@Cdbp`9^hp+DXdvZtRN!UVyLJT_b zIp%#_1reD@a#-YElL2LXadi4UReSQTw2Qdl>qAlbbuUo~$6{={9MRDtMHkGRsXb{% zpO$(D;6kTJiI-e%fft~1A*4tXIo`R~`)YLda{S=d2hdm{yEWHqLuUyCL3 zVto1bEza^MWJ25~B3D)4+cHz!w&*KIr>4xO_V%Hd$-{F02NO%6^Eua?E%xK1)8ori zM2+mFdXNa(BXb8RC_KErI5D4LWGlW-9Abf+MdOChTJh>eNhd4>D#fBhb%s;HGC_m3-O08&J zrPp+VCf7_^V?eCi$&`3)(`i~aSQl@OenJR#dehkU6&&3bJ;(}MYhkYyaI4+BcoAX! zG*7AIN|=|l3>f7dH+oW;LJ036?CoC6?#^ZJ;-CvlQ&hKWNJ(s&3EbijjMBgb_WOb% zQvf8=CZi0Ss%~JDVYr^gG=5d4>d$$WD`QB+M8_nQb;&86J7T`|V0zffRr6w%J7=X6 z`>EH;)=V-2tYv@~!R+?T0}5Nmj`R{OFz8s0@7_P&^Dh0W#H^=J97f%qbn z)sX)_pb4pA7t!K#cVzhP+x|`Vhfddz8z|G>lP2ty#{Fq2?ki&ovjiq*|neD3~Er(wVVM1irafhu6&E<8Vae|Uk-HcX|Gq@?x3Ulta{ z6hQu_YXo|k-a8?i`@#+^oX;38tg7rdF^R~Ms&cZ-jd=zICi67kblkkP^cMx^ue2|6 znj_Cmdx`k+QRakzXwEf~%qC>>awEJg0brrFNRpg8So}?)LWxx>I&R~-HB!A28kO0Z`DL-EfbFmE=pRfp! zT$q_eL-|rmZ>yGaaPnjlJq@fagl^N8GB<&|I)C>F-l20UV(2yY0`mQ>$g())_xMG4ylf0%! z8FG4x2nG@AOyF(}W}VDvjHL?yI@_5tu_hRN%I&lQA@w=A(cTGxWQ=N&!pak-#tEc+ z2{QCi=E>LehPG%(nZ5LTiI=wdM!PoQ;L2HAwWGdRyd5fI!+NFibE$SR%cOvXIqS|F z2xkZRqiM<%!mL(gnHHwFg}2yUr>trsq-8f^RRv+*mmYsY^Ysv1&_aW*%#vFy&uO+fDw+2)u7P6k!b^ucbsLoDyZ-LXYQj&86_}l*>to{4$_SWI^t@Yit)o1IE4tH^x#uMBx`E%pT^R11w?MLepSwAjR`m|43ep$h~;4Z{pCo<~3m#{_Q4)WRWis7hUb450`)siVZK3f@u%?E{ z(DgURu8Q>L@a&a1gYP4(lg9E2$68)+m@S%6m0-$*PZa;_F0b+lD3MwAh6>-2%mJmF zfq`Rr|1wvby|7@D$E3s2R}67BxV;7SOJ$H!2MzBZIQnNYmAw+37)NY`GaZ}|n4gAQ zLS(#lDMXY=C{L?0gn!yF7uj3>eU8G(GQ)o~0OgNwPF)@8ia)6jvw{CrTjNLLW4S?W znwt!3^Wf3;Q!h#ec`|;3+2>X}ei`)dFW)0Of4=v4>30m`V?kumgkeDEHbJ zj<%EsqeX2fE-WzRA`)7N&*Wihz@C%d*LAo$64^(RpGngxIJFXj)3n7wdA%Oy0;=lx>ntC=cLfXbZPRGQW9J6uFI319@#Ybrz`6OTsKfPEU9~ zpUXC{^*B{&#~^D${VFJ|9v0%L;xWR}se>(f{w zae&o7pB%j!(w6-Tk8ui#y@qBxg(&!0slf_%WBH0?P#>6Rks74>k~=?2LM3q|U%9fL z^LHQ-!mM2Y*hFL4z<-t}LPMAZlWCKOA8A;G}K@Xg@f{7iH;hb1Sl!@I)D zKi6dsMIMV^kky`##}`miy10$qsi^FRYj*AkZ`=(lE!P~`c`I0p-^o$(xZF`Aj5bvbqDjC>A6n!~`nJm4VpCXXDyV!kJ$k)*O{kWV ziUpYLtPWI$Jh=99P8+ogD3#8g7nrtsQ$}MYbS$~}9AZHTQJH46E^#*@+NT&K#usq* z@XpE0Gr0oV^Gug#jjtSfZ-PKfu$eAP*)mh}p94Tux33gTJr8J8wfH%}3SzO^O-A|G z9aEr)nu@*LMaFLyttKm{gSw{t$1e#@n)-F&vi34OMy}vPx(#YO#jLz(I-sjrT3sVR zV>&WJ+Zk}IQ5TO9__Uh!=mZ1r<7Z*E`bke{hp|;geq{owL#h zm4~iAJi(=9I7dPsM`lRnH|!B;L|_Bzb4-uIU~}^o1BoAy255NhutEEh&Sk@~m|F9c zCMLpV{;DyScmkC$mHnV5S&spO&C-g)>OCM zG#E=^Xn;auCWnu(+O(N%G{4XkR_^^OFHgm5c|GSyZobUAjAkpmOK5WV0_PdCX-wugm7)J|f9|wG z%(vOq5*z9OMACFh$am%?K4Q_*>_Pa3f9S;H)Jc}17g%g*r)_c_1ZAWR^I3h_nKOV& ze9Q9JcP@$k)=OXI-+T+dBY6{Wu=sXNmmADJ_~Aj|LOR)FqcP~PF#k;JGIf5DMS2Py z)nq!uq9`{qBEpz(77Z}%>LoAQ>FDWB?;h9;n(y`j0b1W;|fwwyII>1Tx^ z2L3S_c9@?gyC%J+Nh)w#u%VI%7cCl_m1vo^4<-L?E79P-ZG}w#QQq5DKsayPhs<{) zjg*4!in2+T`HkV5w->mb&RMmb*!(A9?Xo{D=Dz7X7FDr8Q(^}ohgKh%qJM6#+mVNR zF78I=ChoihRIOB19E6^TNC1r$-CzeuWq(f9c+E}2;I zW=42;czC#bczAeVnFQ-k{$X2m@7QjNvuqN!Qk7zn=hVK*ORTW)&-UjKZ#FsaZLDp* z+<3mr_?g{DPc~N{JY9PP+TFhWg*N2N5%<;X~`yozX9AE|P>=(R}-(4-; zhz{Ted8SCeQD3bFYv|%!KJ08AjYsb!e0pA*x>f%qMZJ0O_l-dZi@s{&Afk3sDp*ni9*I9EcS( zBfD?u(jn_fRsvRzBgR3^zpTjBxzz0cF6VwJluqS3(#ej@`A0+ScHmx5--{NrLTw;uw1I<1-lE-xxv z9PKykmR3`v1>_a<{U@5?PlprS6+G|@S6@uVJ5NV%_`f%UbY<#ghIBselgkPV%fPIc z{C{Hd$(yTH(}iHEtg)YU3T|}Js-*p?%i zwezWQER?=vVpPM?o7Y;9Yi`+_+>7^CqLpDsr#bPlO*>Hgsh`K`V8|U=+IW46pjJ)PmU5do==@`}Xzyf;BB(!Mr)fNDj}3I9RdJ zUp?)K<=q?Y?_Z3OUAp%Ud!qwnIOUBUJUF_qU0UuvgCUMkz?I(IUl0n!pFhsQrL)~1 zyx$*4AgA7k1& z`5xv*3iiRI3bAt_qlRF#WPER5^EuCu#?;UU<2V`%$23vcYgosMqOiT zZR6RK=c`+5RjuHaRI|KotOZpu@NNF*h&iXN^zKhYYM=+cK6#GK^itXw!Sy_C5<>A> zIF8k1ua-g$YaE$5B^<#fq}Ivc{AhH5dtjg^u{9^AmErRVR)Pyk zOwml|8Cjo?&L591P7l_+RG5Nr#lh1(JXp1$j8uY0Q3uSMI_bsBShVJ9 z4q#N+zuNDVyRwu{y?K-35$}`(!eDQM!eXe&Y{yYMgydvyG-MimI~-kbN7l7#tKTeI z!T2$(XWlDPN0_dxTE@ZKyi1tC_*eG`lXo>MmQUOks6XMzf+mCUA+qwcj7u8VpcIDc zBbPnSEIO#_KIvo^9Ew2f@TB+g4E2*>>1WRj&vh{#f`1cvI{R@M+y5k3DlK6y4)OWAD#@-hhG#oidhL{?iOcV^6sad1Pw9-^EbZyJOp~| znb&k`<}Re?(3t8_i$eKYr~1??&?n~~j=2HSBJ{AKjab12Q}iLi;p|Sj-}IWQ?oQdc z$wo{qTGF1CI)8wa$x*D_l7c8b=_YXXS@#24=%7uiUT*2z(cVN$xPCgNg;4JZrhUH_ z5XEJQSE>o`M7mCqdytG&_FE9cK|#MpOS7ol#N2nIf(@idP!op}I09*(1+YI(wnYOo zF{5BHg(FKWF>hP?i|zE|55YUkg@JD9sG)+}ReUdoRtp4B?7`mt_ix6uiUQL9=okl; z&7PQn=;-<26G5ng3Jf%4q8OAm0swujXc?zG>}qmp&5P5hJFU@A=Y&gqK5kodnhVNI z=)CI6N^a#0^xYWzz^&D)Ot3n5BKB}%Qf2!%G7&tPMwFDdo9EyXpi&!uFlUe9#S)`0 z7d}!R93)>0v1E`SKrQL8x+JXa))Xf>V5BWV3KU*LZJ;GSMXgG5NT66R+$=Mv%QTrR zAc!Zesq*zNZ2y@VPhS1-=f#EXyZ2r#-ehFW{B6FRzk}aWFL_sblfm(!r~lj;oB|^PUKSN^=WJfm?BM!EcE7&2Mqj(e8Gl? z_j-(#+DTD~8)*Q6Is`l(oLSn-TUh(a^*IFu+|g;pn5AiMhoz*+>^26eP%Hwag$6NN z;|nSc0JKE!#8N3^Svm}iQ-ZlsKB*6|uXB>?iM}|}Fr3^uucpnWDCRzVb?1zO@56DY z0Z7KM0fRS2Oa<{~Jiuku5}sPu<(d~u%c<2r4481ez5YL53?|&_p9IFQ6sEVHI-0hw-Bd3lOh(BUb3mO=(Nw~;4t z8T^=?46KHwlU`k9f;wrn#}lE9PqpCc=>fInLUO$@Qn8(Z${MxBZ>|~1wA9>zFrQ22 zUh{i#Mu&%BEet;rCs|Aa9wc=df_6@E0)1ofX7HZxw)d9)>D9vh6;(-4M!));IkoU_ zdGS}wwFXT%88m8a|5=Jk0d6~-oLUNCSpg#WheiX?rWVeM^&+ z4n^L`OOZEvvS40bY~O|}*0qtnx`kK5n+yk%sDH)=QQg569~f=R35BKJZMonMyDKSq zWDpyw%UD9J=!Z%on+$f|AaziO3w?n?Uw9ckj3ErJ^IF;g z;Xa#36@m}V7-D?s-q{E#haEiYaV4ue9x^}KlnMw`Q6vY$$+ zxnyRpaG^)Fz|$?pN!zreI4?BW9{zh*Hd*?2sl#`6?&;G#BzWwSSuWO1;WngTB2z%9 z4>7fmscS*~i&fVBHmfOLagcG^loaUAT>_l^!kCmwS+IklZzU4nRsySk6K48XuRdS= z3V*-E--R!Km={aks)BuNzPMzfSX$qBw6>8qOTLyyln21qT?P#2LqnFyDYXz`nipnr zG(1E+qrS!bmmd^*Vlamh;I9Ezq+0**4F=bzIm!!b3q2%M^Kr4zuE0Q01fP@Y{nyih zl?(sNfE|qohs3Q$?qbZmA?5PvFH}ytZu*ftJAa$nUONjcL66j>ZvYqfC5(?t+|>bq zdz~6B)=Yr0zrn#r6JVHTH_);HW5f0nC>H~jN~GcSqFj^e?`kyG#i{NGg@}KW#g+39 zBE@-Jy52eMT9lE@6J2noEHS2Ka`Quf|A!U@R4o~iYhL3cpkA`sXQqj22sLOLQ?sz9 zQ`D{W$;2*Xk111TY*!bRNOwtGOO=);bQ4!m=hS}SGwY*Jo%Jwi$7#VT^tPSM+P;x0 zV3fllEhrb%grTPeVRo2?p)zl3Y`MS}++wYk0Op6ms#GV@jL|Rg=R*I#EbQ!@F3$fK zvcIBmo}0KRaeD zx26YktTS1v1v%CDOUAE>9I{-7Cqs(q$x@|H?`~q5r(x-KKoa{m9rsc*reJPXOtK)M zrxt4FRN(eVMjwGH4F2g3NO6h{W3RN**SV^`>XIC6B>(C) zb3sQfR2TV_Wp!7xu}oFF!Iqa^I-G9dZg3+h{J*l#;pJ|mq8={{mygua*~{0lLy)~R?U=ox zg^)ioHhD0ie*b!mUHU^wmfSghNzpBS~?uBiXOILH)UrE>p1Zrgz-Aq8}DO{s~}ripe@(T zyC5w%|9ejf*cOz(Oh3W@>oN+By*qYOXWdUjYO=#+R98FI`GM9<)0?L_$!UsOu8Em3 zel*3fAJb@uZ7RN zz#rD1Z>~RG+x>QJWAn-S^S)e{%;SQKz3w9SdUtN!zJ1I5n`8)absAc* zW%A7+lvRbbh^EU#W@km61|esaRcWNRAUH)jv{&e!W?H_fm|CX7&u`>JDAkmW@X}23 zvyPjC@!R440OwxO)i83jbKTaG*yp*)R91%wBJusiqpWWk)ERFi%Y|?h-&Ti<^B~Wu zrWitonJq)$Ktyn6W=g2|mX}1N&omE}%}c#StkeFm&q>&7CxHg9v~AxD5tr&EJrXck zFBGh5Qu*z`+t`~v=UL!q#G**EoFgh;w87lLX9P*~6cj=hiZevfP7V)s4-p@$ZD6G; z&BVR^quzp+LIf`I%*Ih^EorG)#5*{|78pDc_qf>y!)kD_NsFCAl}NZZOeY@lcc^4S^Or=FCF2zy=n~jwJ0#5@^qr$i!PS5xFQ-Uh+Hv&a0^HCps=S^|iGjS%H$zKc0 z;^oq9?@Fyoq+*x`!-KR0j%_D5T9YGk<0Xv4T(C>y@KPPnO@)q*#s`BjyukN*pMP%R zO;NetvuUL9OiG+ZpzNIKm$zXL|L*HIUy&YP9=`cXI)4w>rS|ULyw|&nH~exBg>K$6 zN2M&I!SN5Iahy##;6|AujUU#XP*+XqfusZ&k;$Z$8m})74+mqvmO{dM&{Yc-YEo$Z zL}naJLiRe{-14q*|C9bt9Oh*3 zVbUwjYMd67j-O3w0Eo0S;N#QR@c}0{hX*~kL2nF4MhT2WXucdnX%{4kEIoPuK+!B{ zj2>!72tA*IlDM+lIV493i0eR;O?=y%gt{`FE*%IZ?<@n+KGNQA8wimlR~ibP8C|0w zjef!?bl@{(91Ndg2tq7450GB^uPMl2O8JCqm17#~H@T2>H1HOt`Ke8F8g8s@nq;#W z%e8f0k}_rZb#!_^{4lMM4mS3)$QDS=m!^9{hjZT4vyUx=pz?GFH7N(>Qy~Zw=H2s9 zdyMaeSoA0KxPe0JOeX^P@KZE@snCkfn649bgOkf5rji2jJ_x6{e$0LHrXLckz{RHz z$v*Pl_o;)fPoQyb-lII1AY4^i+pWUOG~cVKp%c#$;+G#|!!;nVD|XeafyW-Y;A77S zgxylEW$RP`o{@4i-bp{h>gvh{V24q&Dm#Ba8J#8wSE%@GZ~XlM4ugMGzxSeyVdIjI?V@_ptH8#x){ZBRom`bU#F$K zQp#Q9iDZKT_1R)bDXwSd7^Q75x$S@;8z|~$SPq92dU-mzIMdYlybK)6j}+r-h$Qfq zU@~JB^xTBiC5XWyOC=b*ta(0C=va`}dqq1*^>Jgsg5O-yD&{wOkL=>?QOX~}D0-g; z!LyEl(NU|*K6ome$^Ac zCUUl_JrJz37l;&=i6QF1Gb;pQD=YFo_sUtH(PWr43%Hzh9Ci$(OX`l# zeC=L3!x`vNAV+huc>^IWo8j?xW#!S!XD@c2KKc5at=-j)jnzLPaJ&BcaQ)e{)!ogt z7poiGT;jq)+mOh@hYT`6#t5A(N-EsBF-Zq9D7V}IwlE{F&%X#A*Sbc&)RUkmdlD=P zje;aOBrskneU|R=4AaKo@EhHN(ydE)3Aof0u?cDBt3GRO7Gh5Tmd;uM1LE)RO$I_j z^|NAVR=i_~7v3!Q=TO%CM)6gN zjHz^bzN-|32~}XCm)A1F z;PUxR>wk>F$B2(J!5z{Zq@C&yo}8wFgf!JA%*JT;Hh5~&vE9Z@fC3= zQWdDEXSS+#uN(=(LS~BZve-MBAwKiPh2UYXfjT$Q6p6*+S)KI1 zi0dHcXDw*MR_ZbiHzm+~=jG{;34-*=rSl#yVA)Jj?@LcigudRBv+2X>=?*NWWSB0u!7{ZRu2Yc9_z0up}R%*f=<5K#X=e3YyF!yFH|N84JjE7sRde$1wV4HTuaedJiv)})s2EGrE6x8 z4X{~#Kn%X>oxH|)@QO(smDiUJ$Zct{4YQ^r&fMCQ1`I7g!`p&Z5DyMHiN+4H*_iyz z=;l1}H=r5kjE!I`#X6^(9>}anW*X%XM-B_+f&r(L5jkO;$JL1a%$V?}Vis2y+5&o} zD7nU{dB;wdwwG?urGtx9rEDP!6(~B`pRl(krRJHNO7(zvA+K#ief96+=6MwCBRS&s zZ9J!^J9r`pOv2mz`db#i-9yG?EqsU1cw&Ly-eQR_etS#H9AOU<&%e7P&qrex;YYsw z{cnG5r4IH!D5T%r`Mtct3zn1;D9Zh1^v4r|WZ2UhO~EtTXUz$^^sgWEvJQo`?w~0qDTLN{@JqlaV$Kk%^lC8_hK~;*Y`q(v3(Q5&-`G4*6C&%w z)vN+t*EE0_gGzrxIg7;WplAca$C7eB^G-qJCqyd06&9B|7lEVrx{^=JjRzh`6=S6Y zqt#qvrNeWnw3sn|W^eAM#5`|r|M}$AuTE}aFI^6h7-*;A&7aY+WtkRdz4vLBO-t*yW&Dc1=F!SKPeAiMY+$Xv zL4d&(jBi24Yi1k5qC!bllQnn9%l3({yc7(D7v_WM=p$=@`Wb0+@rS{! z`j!&fmQ)z)1$&DV__!Yuyn;JoS%KFZ(n^CX$6hEux^j!*CJd!PFqXHJyaAIvF`WB(RE2ep~_{W*=8XWLuM~)YH*9R;rTK zXL=@|vIC+QT5!i0+dOLA0-qW4voEcG4JkQoY291~8GN#SG1^#TpXR zc0meemg)=g->C2D&;iGYD=^4v2JDm7@$@YiwX^(!5q%2mE+ey5p0O5gp5vWzJ4xHw zo5#qEI7sQ&yIS@7_Sbd1?-JST)=>m`?O!b*;g{|XPMP07$zrP|=F}w5jUI0CIMyo* z`HN>*(OL*{sFHm0g&unIj0!E$d4OFUGO zi$CV1ApDEt!9QLM$Ag}GG0${#k;6?joUHstn&U}eo$e1thd5>}&d!P=VjWmvu)hA% z>os_FpO&^9fyuDtEKxO9T7Ob;p%I`*V^^@ksEX@zPf;=w2oC(jjWYbzJu(@FY;z4C zWVaxrJmu?}TpJl{WGY8eiiXu3!cg8^Fri}){WqWP$puasU?lc15EQX=&+eHbCEm!l$-TREDmnRUk5^)m^8hSfC zfHVAjWJF%EYjpG`QQRJnD{l*$zdJ&Tyx!q(jIkK#bq?hI0oL_{u~f!YiAMy;nerNU zg<&G#iPJ~N%SbiX)9AHwn6d%LqyZC>Wc>ZDiwT52&_wo6?{HHP1~Olx^0WvNC?4-> zW4o~3aLn5X%#BZ-3y8_uI~#1u(ltKIlrXd8De|D8wH!Tfm5XO^k>QdFHiP{R`4JzQ zxXS{}3Op@{v+StY$r@ghvMg3;PhRd}ned}d!jJcHp zwU0k=k2xkNwwk@%!SFyVSB`2?9%x20m|$yh(GM`6I!=sfpE69bf0)3Ry~sx9v#v5d zI4jfYL{w9ge~wR6!p*xoI# zGJ^I%*h`sMnG64we(=fo)%mTHq@-UGK`&IFRkle?C}r>t^3*gbrp>zO#IUhk8iY^} zuA5?GfE?@=?GG~*j3IL|{5Mv$Mw?jOV)+Z*gaNU`I=Jd=fCz>dHDj@C#PBoZUz52D z;fTs`dGHw6l!@2I?S@QHU+$68_%5Q$X;66rf}bd^{h{t0C(4g9TinkQbSUJrIqm3t zi%39>sL>8@H;%Nc4@yh%5V5GrF5-6?L z%j)SA`m{xh&5lit*HVZxwp-2M%W7@uJ);)j0Fjvo)I!V%<389MW87^l_vH}k9e0NVr2;m}hOPx^H3bc!=f&BFP(uY)0&ETmmYKKx4?N+oBXd!bpyaFxswe<6w!(>(gwR=ZTbHtQhv|;NWn_bm#a3e%! zEF=OfMs#G2fYQ7I7*jJkj_nt@Iqey=3C?q8UyyoGWLlK<{ z?|LOvl35_$1Pli$aifQgVH_lxpE8QKu^oHoH*`Jzd`ocEq&oP(%al0GV*LPT*E8@B zMit&=piYBXzc|5BiAzlvja=Fby0?1Rd++L;*Z>NrmIgs8U(!8=Bg) zYRiz#dp~lXlKn#9@84Q*3Qud0ab-{b{#0xNlHfm$W5eg1j9K4~%s_)6LyvgJF9H;Up3Z znY{$U`i`x%A%Gw@LBCORTXr5~@5HUy)K8l$bthrnw6)RENp$GGgZ`O%<)&Iq7Vn%n zT2jZ-nyKy^22e+>qc>@1OC`><;cAW*6TDZKHVL+(z!^B9#J4<$KJDSe56K1SE*-lqpedrn;ud2xYH?Qni=|&(^Jxz%_8| zS9=>>fHz&9@VpyT1&}P^fXQ6nLWH!T=90Pt0OWU9<8Himw>>!1x^*IXkc8?%l>W5Z zW)-w!?#;hYQ-N?qJvL}ywnSGqV7INy)eS8WED%eDH??rwr{@OBuiOa{*5XQjD?&qG z-A7^yX6IXgHd^=%7IRu6Y9os3H;V|vviHfj(^T2AZA*h`v3K3ecwLpVEn~cf`+yKv zJ2=RDm@7O57`y|63nVv2(re%rR9hjm%@7O5T7ZiB&*fjR3w`~yOGS{b|I-sHW>n2H%QQH|bH;KFQ&)5JvlIlS&kU&q zo>8P)%)Bd+#{_^AYgU{AR34F>-mUa*i6axk zZlPNKO%f5}M7%_5$*qaB@jna)*ec_lis?NTI7aoogSXtD^zB$xUtD8gJ4$6hYyTC0 z;i9akRsY&oM$}Fq4YZP>;JVkG1C@ss;IpM7c=?%|IJOuC3mTww`T zYZ88#fgXVwQ<_xFN6JAa(>nd1J{KCfcdG_kO_aZJ59L$I9LP%6%|7+B|D|;Ir^X?oqK^^+^;qNz1kC~irt+qMlu$K$XU&C` zr1EL+SRmPQUQkRO#${CIF>1mIFG(vL%C8^YEOi;If-4a;=X8)TC4fN6im(Kfb4gI$ zAxlXsbUN!k!_qyT9p4%u$Ir#d>B4P*HgfrQX`Ena9arC2_odzh8n(38yAoV>I!Ov^ zMO5ZJGc=8m0e0ePr zBq#5j;L?!Rtx7HPxp)>L>x4Jh)V-Q~NN1y%W{246$@RE}2}GhGgGdc<-vn+v1k&XnK3GZ9iM1Q5=?6-Ew@JwRzd~pP^liaHty0LI#={mbwXNfe;n2Jeoi! zaVTvpd5Z)QQ+CK2$?A~V5H2`PYKYt^l*K*Cf`b66JscyghQ{|`MIIsY`0N+0O0(G~Yt(3{Ngj;PN|pjaX^~zEh@8{BQw`SU)Gw z-Q7TN!}}Dy#0o+8Gk&RFMuR_JFS9)wG7!d6PuEZDVk_~U=~;iRy$e;cd%;K+&2(p= z=|Dkr%?z+98_xC)7GgAfLWu>H7%?{UaLA5nwRSeQHg;dEKH7b{_IS%Xgeb7fIX_xT zjH&c6f6WXa%m#Dp9A&sT7*m8eab`J?+pi?gM7GXq6WJ+ZPHrHSEu3IlwzL;FU-oMcXz$4QNFb)7!$~qArSQMYuYVW#oMb2mfEq5Ha-DWwGMtF9DCPw%C%87vp6b}__!9` zuR!Thq=s>sxj$f&$bk>K(s1;f_X_pq{&Ia`dvEEVc9wQ|4f_3+ofTXkd>y|R?_d9M zI0q%@;matUw2@y8Ol2fXB2__Hwa}K}8e(MyQst5`9EXAnhHX`^7sc6voJ%l)1;we$ z64m6C6yZ21PCeT?0FfS&Axf?;u0SiG)`AW;DcAtRGf9vD?5Z{B4Itl&*i%d|O*082 zaC&~Y(7%opEU658fC9ukX??!I3(%O0LsZ84gNVbg*b$*(rq}hY}q?vA!aR zNi2Rfjq%Ce*+MpEaA9z%)?z@slZ;qyPBbm3ochf*vL$sj@N|ca=k(~%joJf1EM=QX z@fG6^9f$Nodc)KMb_oR2z)LS=Jm9bM6}GL*1%ejmF(EH0Z?axXOEMA$5~juj-^sUE z5X1@yc4r0_8#X0WAZiNsqX&=6+n+JdL<*qqIxu%xaC(AJntPYk)8$8dmVC{T?@IT{ z>A?^w??w{e-Vy-52_C^>$70J(IwCETR=Q46D3Q3-R8|T;%H6%w$xti-sF$q7wFr`* z0;#u62s|5LX2@$l6N0JUhUCI9E2=SHTsh zI4!>|#b4^08D~j_SX^~un`x%NYWLVGDbOPQ)V3Gj(G3)W-58e?L@A|^fynl=RHxy6 z3`%m*%Fc;Nd1xi;6w9haZgm}nrb?SH6%wMbs&@dojUx%L%y)M9-yis&o4kt*8Y_)v z^g=ZZh7H4x*a9DI{Z9E!U3mxF$L`&&ix7!)l&(uQE%cx981&KJTO5Qw?_rY$Pj?W- zXx!Bk40?sgfD?kq`RRcJj8_0&?{7JHRd}>w=I;AT5DvHL816*#V zruc%ED1c`ci5iIvO`K(XWN(P2{lp+RpVT1?jV3t#BW5TAF2G&YU9);~C;g_!x;FysJKBNWeaQ+jZ?*L_3NR4{S3EMU zPZn#u1Uu=wDPDHr$VN@?8u`&qRZGE3$asbBg*<4q{b^k(1}Y>{hu8P4buOVM&_u==js$#XmJ zZ#~+1fBV7qoj)+bq_vQndt|LD#KrsQN+B=Go;12K-9~DhozY_-)!w)|&c$<{TBudb z6an0}%^AwP)v+I6lUL9kS7SsfnjLzyh$iA7GfD<1mhug9v+Q``vZTxj6_~;$A4T_VJor&$bf|vVs~vY zyRYWV+X3CdmOJi_vLwCUj!x(*u_TNx{Bt1hNK~<*!Y$!hEprh z^vOg98-5bxivtAvSNj6?Cf~PbpzI7NxzY1(^ydH32=C^sd9{Apq(i!<6?#yWX5Gq{ zv%a8*Qo}-K@8bA;^>p%%n@WKsq@qPi3a}i@u6e>>SrD6(h^tdW0&QpoMZgqwHexKR zwWQ5+q=~boOJE~A8c;Ap_&kx>U-p&U?o`*r!DtnOVH&w@TL8P3!#FJ2!H_Y;jV{?D;8 z&XeQzoyth%QdFvKAuO^ejyExZrM@iyZ9-Q|_B8Qhe3dJ-*Fq-7{Uz%E(Pm`EE)Xn6 z8?g-Gw8QYV^OcQ(GIyeB&e@31MUZJWh+Hod&|&tJ%l#LmMsnJu8oQ z0R)FlJj;YogwHN@S61NtBHdQsB0sDgbJN{e3?FB}-th#2P8NX)k>TW<;lTkel#!x3 z5$BKO)y7}~X^os?Iqzs`|CCWFqd~^w6i8d+Au>wx)SbkUDK;4`iV|35K{cEp-+6(9 z9OW)wfscIl)Mg4M4tZGxW3#W1gV;I8D?6J66k=((Iq;R)XL#!@q7(NCH4wf1p@_1M zV;tFh6HfB-x6b*6{wi))PoCk%aOKpbP*Aj#W<=Gx5o@1%tnUAnh-aDX!B3{E04(rrg8Zk^%>Y`WY6 zQwhdEzEDlE>E{gc|+;I`{#N z{tayR`Fn5{)|Gbl!g{8MfVp2j|5mVmTT-!IqM;}l@GWMKf!Ut_UUT_qpSS1VDtag) znze24gp>SAz~H4^aa~pQWsdcN;?9m~cKGK@hR~;|`&lgfW`J8*as7NSf-p{M1aXBR zx=L4F$d`*1XMm`W9a+6_RzqmIx?$?v1Vn*ben7!&5}Zf{4`Dk!NIMOe62l~1Qr@BS zJ-N=%T}|X`=7q*ht9$P~l!x&?DMV>|clY7b)y>V_U3BmrLy>Ulm*%|Q)tlIjr%RTD zG;s=Prs9liy#fyl6-U&CcM+?4p!fU1BKN3vz34TNtgZ|Nkb>wFxzyF8z2thFiF-d5Wi=CKa`s{A9`F6=D=)yP@2g>z@mzZ?fG=t--BS>Daj|6Fm3i>Y zwzP3>pf7J_UpSO_YN&5%i4joOQhT|7_olpN#zaui79F_dk%fdK>&h@3y;9Enb*3a{Ka5od~EO0V&+o^9Wvqmx#1o$H01K}pht)^AK?VW@lD1MU}Y>J+~ zal3yp9`;jud3Fl0=#>q#5&~oX9bZy%Tx%i%@9ilLyDSYb(TP(w?qI;j=>(r#uOl9{ zh5peolpM*flPF5r=hsM#_dTOt-W`E+!0!Z$-#E{(@Yx|QCMdf>Nn&4vB5)1d5;FWJ zBZDdEc^t->f2aRLXb|JdS-rjEIN`UCt|KD~L+Uf4prYVZS=swNEQzq+8@4;=v_J00 z*^z@p0-hOfv${Ri0#;RuqWEQX3pw{;ztl^XLTaA#;VILqiiC!^ z!XelaHnY+IHj;i|+*P3A)~O_|5cwfZBB5D&OL)oP-WiQCVZdc~dtcmPv&3g^=iG_K zUh=4R;zlWv!IPDfc2aoJJBgtH52XwdzR^pR#I}hOPH`TN64}{JM$(sPx^7kFc``yG z`~lCK9Di7&^KNi}wa5i8bx2$;q12^Xqd2!ST&Y2fbykfm(N+qDs5bItx?ezy*KPM5 zCse9CkC8?QVh+de3Uo1qde3q70n^MIlIdwQM5Ag69uAK|fQ8b9q#(Z8gSE&yq*!7mVA52G#){OfU|nUXf`jUT z*$2?q5Z&J^uNUBTmSmH`tsRB?{`a@$dJJ5rpqw(Rx3Mk!5Jj;VLsB_BJjx%|^Z|GC z;>%-w_~I3=tLgm*dt=wZwP7xTh@7+EWG1{qaMh5amG0gRnG>vlH|#(xQ@R&RVr?cJ zG@LY6u{r1~%#n6t+u(uh+0vk}^@J5sZf4KDx1mNScsSpMNgv zTPaSYWK|n_ERM^s9+KC=Dzi!;*Mt)U!oJ|0QSgvUeaS7Sp|2)E$j3G>KSI`E+{+bX z|L)Q8z5a4#Gp(%X9R*usI9{=qdH~yLf!>j{g?(5(gfNd8|_AK{D>(L``H&vFV{dJBG zi~;%AAPN_Sh4;C86;xWJnv{&FioYAYC#kKn&?Sln#*~ZJ-Fft6W9{MAIxaNZTzj#) zv5KD}?6Ar}-Q8V$-`!pA-&|h436J~e0*5gtH*u}fP4$F}HN1?%T>r?ZU>3DH^;`2OhZ1IGA0FC)>qWKzY_ zUg!W%0G8%@Za&X(srr7f+@BNH7GZ!Q^NCFNdwyt&)x}FDfa=-jft=D+Kk6o3k#1d_ zV(wu>FffFU@^KFlBz4ua6oZ~Zc+0k}J2z0=@~?W9Ed%oo>uX<(C5z0QTg49Tlg1u` zbS14X>aC6ZR5=C;r_1F?x$e|NG`SP`5Uea4k+GJ%(fjqS|M**%wO1rWqnD-BF`FRU z^AJ49(y2qU?|KA-$ei#lsUT3mWV%v72a7ZZ-e!BtmLY&~mpSd+CeKB-iG1Y^e|6A+ zXXtbSFYnpLFquE8eT>kvz4x(c%#pE$baLoNP1tb(W@(9Zp+HGaE%E&bxzt?9d}T%_ z%wR1f6IS>8oK^)=-=?j1uDhWHbT3^t@h;nW)8U@y636%Gnl3qDFUrgd)1@a^H3afy zQ)Z<{$!k%$r5lEt9(BG@)<0t_q$hZIIlHJyPq|?4DGN#b5Av- z2cm|x%oTU%Uhn3?;O$LL1a94^&4wQgF1VO*PT62iqv35eJG73_$wfwkA?b~3itcHV zKc8h&-Yk8dq}AQ3`F&<f7r-`N}>reL1hFFiE9*o{0J@MHtD(g&8mZ(3Y`{aoFHEJ!??^v`y8l1?SclHO= zyqmfNH!g2s^0F)F2No`5B{4jGOXZeMn|(>gT4O=XPzN{*+oT~?7_`3J+*hC&9{U6< zvLX(a8*iud6NqKlv7ea%DA1=*>gjyXC?S>2&)~^_1V=vj(S$Wx&vTyW&T9B92%whUQ71Juetw$y@NKW#g>ARK3Nm^n$1tX7rM+#|8>f9 zs)-6=3!p0-<{0WIQUt5p(9!2Oezr3_-4*W@CMe3^z!BOpcMyOo|9jrI~GL;cnW&4tMB`3R0ID#@Y{N1uXAI{;)4iCkSo+fHAzDgY2)GsekEC`W^q97nmM4{l`xX3w}5{#a+N~ZNUBFur3P`_Hr}F6w7|pjzth#G`LDE z&6;3e9BvglT)xH}#=o=XCcoIp7Ac^#d_s$Nw$MS%buzO{C^G~gHylARt35D;bNU}` zaLUaoo6OnQrZFMOSD@8=Kfd4NIkL$Fd+*Fe<{nq1QgD+4c&v>MH;)i_wvUiDvl9K7 zmH03qm*v1x5+fGGvO))FneC-_mw_2_bFTbYk(M;$iz{D+7X2u^q%CYUt(D%w2e?H} zM~ioFN^^)lK+q-c4;K{t_p3ky{q{f}oasVkIWTpqd#7d`OvA5;TAJ>~1>VW&6}^=) zRo9k8GzB`fW6aQ`=tbZ_TM$xy;{c_)gFA=lE$qu$(7$Tt} zl)wT-f1|33BB<_Y>y+8&2i_O*{8*);Ny$|&fq|f>>yo)d5{+5 z8eXM|Xl4kMKDP7dC!~QKj95&&g66_YO|V(sa^+u_U(U?|8sly=Pc6`dNfeQ3Ff~3) zvDZ#ck=$z^84YA~%c+nnnt}!hmEe*|7GzEr@ua{p%I2jr5ynH>>smqv=xmS41Cd;x zVK|CyTBJ&K?PJ3RuO)NsO8!ol4(3m0y5IH9-N&oXo;>|iX5;lkAU?zv<>&v^-J32H z={`K|#pg9@z4R6Qk}RPL$fK&*&wA`F4xr(Xmw4+|$8Rnu;p4+)c1INaTbyb8ZoGGP z#w9={EyKvT412BT$IXhU=ozajgsefeq4;05qqc}23?yekmUPt?k$V$bLKKq}zBgWfJY$;d3 z8yp@oNBmo)8D|P{iT`vcESNQoJyfKDT|d;4c1$i>Qu%|f#pk>MJG_I1Do$w<%cu)^2d7ezm#RKmH$%=T`WVbDrhNuwO>m+ys^p;a`+@!*1{OiusAvGR#T@x!aNs4g2 z4=mhM^3k{!UGh{F;!OZ_N4FDU=#Xg$e=>F)2HlNo!&AI+=2mO_F6gK2p)UEp5UUxb z>>L7#JvlN)4w@OclwfE|MwS@DY#80q4t}r^*RqMjWURmG)C|hGrfT4 zatC39^3jHqJU;>F?p>Xx%3>Y(>8#h5)$1nn_z9-ed+%eyJD=o;Y)M7M{J(l zOhbtrl+y(+?>Z$Ks;ZGwrY8PbwXnw&^z4GcCWo5j%)#hPn29iy@(daiW1wVco>r*a}% zl2tKc0n4TLbkf1B#LWg6MiHrok4jvzYr`>x7ADtc}XWGTtnVgO5y`$B7!ORAzBccGbh9e4!{6_E1=v>}W z9$bpy%8Hmjs0APH!Ql19o89vdXPEFhChauPe6CzTiw;p_ozlp+n{lYjLFq;Z$JJU9 zBlnHiDZy%Hh6t}ElSJbLWgi?n{9ZQ>oadH7u{WWqzo@)4L4`(X&dL>4IYXpfXJp#Rwp@>8qveZ~^8HKdL>IUZC#^{|= zXo@rmEFeALKAwzl(!0JlDG7{5@JmRHb-jgYax}WY#hco77fif7L1cnR3C40zPVfyS zLR+gL1a^0yt!;g?{%ChM+bOqM?iOt}v@RX&)*+Jyd&m11#|S3@J>)(i_oo`1k^u|g zG);@i(8c@^5<+biG?8AFgO=<9JcTS0F@zVh!~Z5NmKGw><~i3+j0IF}VaTg^kQJ1e zPrAUGwIh$y`s=cWvZdNbH1tiZ&+PV4_`B1B%_H^7p5-OxQ~3@u%H(LBA<8^r4&ow} zw+P(wQ%~hAB?nL*IAx=K;X00J!?sYaGZ77u&fIE{Hrp8^1m7LglV(ie70 z5(!eD`~(dy2;p}=c}uie3hVqh*1#GTpoe;FQ6w5AAXswvD&!#_z~phBBb7i$d_rSB zOLGsgT`2DM0CVe=y3$9-le0a90QYeBrW|%KkTkBMFnjI0o!lGNR=o&5ssucdgQ`9N zs*16f>BE*zsS>cN5GxE(qci_`xq26=C{ZXx=4<2$!^JMK&X7w9q<@!Ha0P}ink@?8bfZsSnHzEMXEwFbP@!C{AEsgD zNqEeW*>qjF(R2kg0X6t7W(h9>1=$MN=!Wl=7k$&x+j{%LG-&_U$&h-I@nn-W-f^^g^y$EZP@eWU!&BYWG)$Dcd3d#Oc6A zAx2m|SZUJ1nVlW>r{RM_GPxKVBqu1uW>P%u8*MaB13O(Qx6A>2SX&L0|xsTCtKMzex3{u>=&+fJ(9O-xuiazRG_r|d+O%&c&mK; z^{c|tsPU5h4eFYo0I#a$+a)4*%EvD*#VfW|ejfDxV~Nl08vEM`!A~2m3h?jBc7Izw z{zh7badWaa{{BMZW1Tg`vj!VkS3j6Jx4v|*An#O^`Sc|(an%B%VhjqyVp5iilJj&X zv~JS4ONWOA9U+Mu6{Ae~S*P(*@3v$ca)zb2G(ML3%SkCQG;+jAWP_?e%8t0PNTZni zGIoDzUPW~1UaurRd`${}mI@vC#N_5VFMHsYo-d|xdp+9L!k9pc6W71i(+;fcE6RyV zmuglNi;m;r$&g_OC5)xsuUmSnh0&g(*wNk$RDdXkKolWT<(-DJ91kN^n5nB{?6GP3m!ATo;Gz^X;*s1dS^tZqAf ztRSkB$5`XU98r}0+AMDqqgr;|hgnec2gM9{B#R;7@({D!`JF&ARe*FsfGjRoO)R%m z$;!(O6N%^9D!1KC2RajbJWCo@@kiDw@3GmZz(_GR?+sm5^#$ar;B_^X?2)F9FZY`B z^N+4l)10gE2^H)%QBsC|dmK3p>VqsacbGW0T>s56e<%nBF0> z6UQ&(13F8wCGI6Gec(l7?zKdMcCxm02SaUwIWr<4tzbaf8dnTc7ULmKG!dsgbqOq| zJ@efa74x>eA})_4_(e9=eSzU)wwFXgb9w0xI-=ebePs18RswhH*BCo><7D^jbaXKRmJJ#18k3_{ONa^K zX(T|)HjP27oBARU^t18OJ5DT0?enpG2PLF(o~eQ&TBKc?`KfcdQ?BC=-ch@NFdCk` zpaaQeo0=gbYiXLKdv3bbrA+1`e+x!U@HZu3%!JW!y?(5BWJjN?_x{*4x5^pQu&~%w zu`}otQt8;zsfQ>fvQEmR&M7){=OJg>mM%Ay*21?h^kq1RTj@&JT-FIJskSMDQtaA> zFIBz?!wx6UM;aAz@ZiJN-W!bWXL9$5^O<6E=!?-*M7pLN3MfT6t6< zsEZkS=K$*H{QPWJJgd71q#xt-dMj*nbzSl+W4VfeRiNDeQWG#lqX5>wi*wF^yWE$6 za?7p8c?cx%{}dTAn5cgDCNel^*Af%xiUS$75D(1)+LYJAR52c1`l^SRw;yHVw17T* z^OXfDefc_mUe;Vkr70-5Oz7Y3s4IGbk|1N$R`P;pAADFn9=?(69H>!G^)&oqc<8;^ zK}@9z*eK0)Ks>;(bF#~##xR&-?{zxmlj72CX_Z^FN#HBV%?oHKwJq?2BP%8aeovQb z>_y7*WWaUbL&XThN83JuD&2Np5O6!;<*)`#i#C*VwV{yrb452&Abuv$!M@KEabe9Yv^B0CXLm>WSfA4H?z>|m8Q3S?=#?65SVX=C$ z@?dVB#U@9V*nl#zlve!aI%FcGx)8(g&&7v=<~A9B$xIRdh~7Od!o-jTJBURp1%XV} zJ63OYO$St$e*|U=8*3|OTg;kdY=gEem96)MBX)Ef6vu}=CxJ~kFjy`F7MWG-vzF4< zbX7bMQ0wU%*A3-n@Z{^RjeU(Sc&0HHbL9qG2b?9f=DqcI8=GULbQnN3V0;qi$R z)O-OrMk8@nJPa)S>Jbxd$`5C=nS0vA<56ts$KVI$7Z$mCP{sq|6G$SUtQue6T5XaH zDlJFu%N`wAoI>ue$lx%SVrLz*ib`k%CjT1xy-=5#cDMC6ryqD!K5(I(4?TIWVcY1e z>r|)gQSw-eRF;h+`PH@FRudiL3Oh|@B9|qfEzoiwz5dV4)1F@A=={;>Afyce!#v%^ z*lvoEPwy@0o#Mhet}o0Y-UK`8kmux&a5ik;Ou#_iYtSEqQ?(O@Ncb zREgA>C8`6*gEKDLU;{8AABJDOUAvrt3Ic9ydWoz{K=2jTk;Rll1MOm1q;EDkU0-Cc zL@-N;FD9;`oC9YSY$OQ%!grR{hpn7Fq4YE=OFKUaiOG>p5_MjeSUAq?UgTnth^*Y! z&+4d7&0ciSD%Xk>-;M4|w%;<;wxf`1i;h)VTdlZ`xvfryd|O>6I#qSyDg`W!Gh)y{ zg@KWQ5&q%4bYjRHMd@qGypuCY2W@jKt4KbuDUF}9uVunSCPUi&0SU=>iWgIFKo*4^ zRszJ3G97m-rz@h;ov_B)E3%14s;hj#OQM^Lf}DZly&4$JZkAIobq+AqKu(6AkcDvg zuT{l91ZgJ=VrVdV3oFR(2doCr!8B>sV|?%G>Acoa(~4a+ml%J}peRDR`2r!x{P7-Z zrtR{J#URzCPl6xQGud1WyF`18o2^I$AzFiC&K@!-QVw>r?5a^Z<@N~a1g^p#Oau{K zIo|{oE5`m-?utkZqMxZOh_FZ;Qd5exl2l|Vpew6$4xnyWfE;XOjc*ly0#jX)u4dY} zsgs#n#6hAGC<7|VhKNphVC9+>*cNjtXd@-1L#i_+gShF>*3M%^0W~bu%8tmqg;Lf` zP(I)Wby|-$(r#VR8F?tXqf9E>#o|nm*++cYq;f{C5)5n=RNZitn6c_kmogC?svWgFZTODC4f{Y4oxA5aZP=QItFof42G zBtOZ+aHDs7)|3*ylZpBE*j;VnB$gIoyD}+;gKEJdkKeZyEhoiqr*sGZA4G zGP&FF3K}K}XgbfRnPj^cl}TK9Z)7BmqGIf+cvB6TSv>Mu`VJUnZ~dw1a-L;kXa|h} zyn!(KQlf&ug)0r{ivl6tmhfH%KM6QA;2S?PI@Ry@PKLNgL9tibG+pCt#2XZEZaj4h zUJnhXf>P+MEDj1&T3V4>Uuh&v-3SiFp67R<<_QNfZ;S@)ATt3)a+n{sK1Gx1`!%_( z=bMO)Rn}F`?0?l?)bHO8#%Ol$xcJ)hugForn||}a+yz|iwRnw-Z{&bEL$3UGJnZ9C zB)pYY9QZxlDF52>E+atksz)`C_L~qG+&Q! zq8nZ^cU#zS+7NXN;lKRze?1kgfBEPC#=qy|hZMp8^3VStw~+tkpZ{C^^Z)3d|4#q> zpZe#2)Ia~P{`qJ9^FJqqAB}KnFUfjUwEt@DT!Q^y|9frnvQGR}QTwZ+{n!6NYW?+p z*FXP@{`tT3&;O)<{#X6;zv-XzmGQF_KlV}5fY z=`_LX$%m7}(dmaJ;ku;EhR5@$Im?1Q3r6lO_&Mj9@H zc>^AU?xqJ<2KKh_jqI+*v+#vH$&nBac;oDJo&^Wiq#PL$R~(w)g4Aq_tld}QhEVn8 zB^^)OtKhl@^|M8e{``LM0eeUW3!cjSt=S6NzpH!Pw{Q;aT z4oAnwBV>1d(=!aPlLjHW)2CD2HjlY%O@N z;&o$gaA;))aemCOnb2pKqz%J2KWFb7A9C0{`Yqsb%-n3Mf}}AX#cq^!2EFxBal@jG zReVRJC9jNdLU&!YqjkRl{B7caSwZ5gXV4WEm$0?UCr*$C*8&_2`PsnvEHQD{yenR) zwg8L%eo4rLr&(fwp^Qpj0RC$fAkra95$!8hWr$;bwDx%Q<)jF>W#MrvWf2Kv6 z+fIe}o+?`NIMO7w6fbNRV!GifKLwXJSzP4@-JBV2Su%Be@bSrngJ`{CJ?(=knSZH) zZd}`0-PlH93z$bx z8|lZwMcyXYm@b&L!=0aGYm0D0(oJcKSxTc~(cjiCw)L^i*Zh$^bZINY3#h9hbsJO66}f37O|lX*%DSm9n+Yba-AQyfKlnne_{dLR8A0c z53@8zEsj>_b`DBX26bO6I8y~?4l#tAzcfZ*Yi-{!qFtY4a9Tg2Qujw4_shO_wX3zA z!y6c*TIaV0%QtGH1OOz)Y%3s$=S4DIP!AHhsxmdhP^)dwYz<$3qGeUTyNY?$z@cel zHNmR~r^&$9r?7{M4#nH5H}T%P5-+aWkEqx-b8X>sKgH*)TgCg2 z=v`62yOTw+seM(eN|znc$9yf4^(}wRKIP9>=}XaOliNzLQ#cI^17<4F(y{E$(nFo6CX+0Ie5^Pf5vaSPU4X(ha}@&v93ul4!p`@t!0=7|?@yiNwN zw;M~=+!Jd~+daSsVRShmpkco;uniY}${mXSTg!-`vO4qfmnQ1&=Gw-$Ya7@f05rY5 zmcey#Oyy{Ji0D~<#ANgJp`y(};Rj-CH>l-U@~%U}K<6p0i#yl39{D`M=UYO81?!VS z&SYw8N`D2LHGv83MCL;PfQ+_x1T;0TXvhhYn8@p5*Wuc|)+DFinJgbrPB)4sN(rr_ z!=wSBRa<#0TkIS50Qd)Ha9&bKA|bU6qDV2V>hr`PydI5?D=;fPzBHj;w7|Fv+}(W zr`V^|ahnk>V#T3k4=sx-9Prry*4>tSRNI@loIuGdmlG)QQFp>>(rCOR~OZSl2IBtoR3LuDVMB1?DWg+gB2rzA$ zm6q=9?e7oH&SjHW^UON$z(bn%$lHPc!!i)EM>lZZEEuXHYgT)cXFcuiCTxmTsgT=R z(qvgac|-ua?ChjETXDimW0)-rya(A!@=CZrPIU0rZXV_sGG8eQ%BhVZ8A%<&vrs;g z>R*xpOsSDC3 zG!INiz3GHZtZcMX`>1_>hW2SvZXbR0Lv}jVU}eaCqH;@v@>A;^450J-s##F6lWFt9 z;bx1LwrSdA{3i!^j$cnk6BjGZdxd z(!FS0)Ee-arF&9$hAvwEE2`4e4@z%b(sOA$l2Ux$g0LUrhVQ-815RC3uPrW+$eh+?XO0QtS`}e-pB){Q_DxT0)&xT%7ZrqnL|G zAx1-yg}_nM^Q6Le&X>f@=Vwc&rTr5eBiVZ++8uL%d5`;BqY+MJoqj-9jiY(zyX3Dh zG$iGcTFI%5-;UJMQWly!*PDab4zdDp$IAmE8D8E6jl_EMRFY9+}bLIVX3DC7^T6x~6I$i8l=8Vg|3AWPC_b;%4WOCMQ zdA}+qH`)pV&npTarhw^BvKi(RP%&zKwv@`bRaUiFlL0K7nbA!*nz4ASm1}7WIHAl$ zQDropNQ#S@mOf282>P6%WLPn0rDrGRbPG&PTj2^^0xx!&NyHX$e`aE2z1BY;! z?EsJz_mx|$WeG!GBCT8Iy5;K@Tt&o6M>r@flqrGfMj{KlAPeRb!#kLduY(f9$+wIo z-WdGj#Sk}2>tgZa#%ruFkWZ$SFNH{nNx~E=dB^Zsm+7{5toVfO>^IE7tgm`uMx9Vj z2Qel9C68T-CN#)sS1zgM3psMqCO)nj-NNu0cmAO>)IxwITTei-=`yOAoT&)+^#zGt#D#v(><> zT0`=CPcjuv;NG{iQWMBGdP{V3a2P%-+AZmB=ZBhtrBH@MM5kNwMEQJb8%^nMcdH!1 zW{a-R3K31hP%`A><217&kaPBRuV7YZ!|i-oCTS2B^-{u_{*XAN7?RkQR~sH&Zf_{a zT;tZBe-QEo?nOnOH{5A2(`G^07dc|o8ggwgum^%axxogKW(+6NCI@Y5aqOV2^=)z>CaYxE`e(R@Zbr(itMp%UEnP^O-!D zyF!rE;gi>#9ax8p5_b5&x6y!cw&NfyX+7{EmP`tDuZVa(E?59|jtiaZJUi~=@zEot zB7=iia4?3@{NcCY1PkRxw$h|mCTe`q=2}e)7!hY3Tkni8XyP0pyvjQH=_1UVrZXr$ z7(rFH)!2v=$H(MT_~H{;z&$L+CE~vkjQ3CB0gSWyPL}8Qumvh=;H29N1^l*6 z`HHocEkEA+a$c2!Q6y{hOTAK!W6LXw_v%(8iuAE+g_@^izU zmeaL$q(J2#wCyk{dZ)QxH4SbNfKWSVbGWygaA8H61%isPj_2`SpOQvtm3ky*6&_GG zX!%8anHgZFJ3BYcfxM78Z-HBK!L*wbSK3R?K#M3#9nbba?Pcz;hGCf*THkyxQ+5WToYCqlucy(G`)e-nD zJ&-!K(>#7PvB(9ouM3HJp#h$5VYkiIG{>*Nab+b@QjMAEWx~|2<|e~D_H4tjCD0>t z-G@Z-?Zs{RSa$N?$}*_l$;uZ%h);Zt`OgG-x)#!95LKmI6EM-m%M<2OIYg598@Zaih5VEG|Q!TvdN)uW!8 z$x|0)v#r1=#AH83=4g_?G{qW184WYxS_N6s5`=-H`uz3q^k5;Q2-KND!+aSYAkxi~ z8F1Fr-YcnHwUS4riU&~}swq~^Q=@QOM>6EV999A*%)~GwUco169KsUAlwrX5l(Z29O93pSOGdkEqV7L0rjVOg zl1!{;eUgQ^1hF?b9bLRR3TZg$Ef~lZ2@=4WfGi*awlTWWXw%(nZG%3Xh+xn;+H!s* zo55lT0Y=UEh-uDYs0Tf(moa|!mRyxRb^(V$l`#Ss(-xYxEXZ%tNqkW7&m(ml%aw}8 z$3E`rC2J!~)@&n`a*7G%sJ8B{n_1vxegfs^i|$IQxDI5vyMF2*jiD*zGBT-zYaS?pfsCm>Bd(oPeufOF%gW@hXSITl4P$;2hObDHM0w0~3hS?xCP zV*+_OH*2L9UN*IyR0BI9dmimUax6%@FW@cDA}+X+ul)eWeauWD0L`grl!}V_a99?Kib+c3nll)glXm zOqJ^k$^iZ4eW(cu<~A5j;-#AZR+eaG1{`0YWh*jQ9vB{@gzPx=HrD?5^2x^9BM=PN zrWKzwz2@pue8Rm0#iu8myOJ^wpMH(Te332^kBc+)ug#x~m{2AjAzQdrUc8y?kB8zj zahzL*J3q{Zp~-wrFw|gIq7@**g;z@caR81o$oM8H)2w+b%26+s2jR#{%>ZgPKc_-2 zJhaJsz2C`9lHzyuZn|VXUv5?Tkn{wAgw_r<8?dZ+Bp6U|QXOhDmSO^3FjCt~!`KKO zb2}#iRt@n8T3Z! z;XFczX$`FEQGgnHV$=jZ-gQB5cA$k!6&o2H@}6?bMZ5vB(Qe#F21`s*PTPh4!@X0< z20b`O=$-Sc8a0FWJO|c;uU}mT&OIwD&Y{9beil13q9Q(zZI>*cA;ndt1+s9bDbtkM zMu?Z6-iy@=b>h5ts(}!(VvLNZ{>3ndlRo&@5pvwR833I5NbzrBPQ&_=+A%v?A;ecg z`82J-MGRKs?h&)$e22aHC;kp>jDNMtk_9bJZ7h_IGp<+hR_5);WU~}#5DBv$!0wW4 zpPmPjowh9VmBR`I2{i%(K@TBiio_h%2XZZL_WpQ}p`D1%@aa;0Q@d#IxaIRmUHMrB zH#GBe;=YB#s1E$Ahb&iJvY5)fHxMDX2oqf^gs~mf&izgyl56lPm%K)!0f8OllIGwb zvyRzg+C0QG@q_9N;6NtB6PFnBkY|dQ;ZfqTjKkEeSk`n2!jFML@fouy>$nd956k%Y zqT7iw+890?`azQ>(Lt32&WI8~YoeDQqQ!!tP=`19=+c8MrN|_>|E$;uRLR~N{3 zDytsHCS|o=E^6HG>{ZBjQ998%4pJKFcr82g{v-pbeasq5;~^B6V1^b(&ua*CtolAl zjHg>%MYOLsjx96)Da%Z?Gc?$_Q(yZ1RsB_J7nKS^GHeK=_GKGx-+a z4Z$f2D;v1N{aTYG^|3YtG6=$vxec4vAc;52NpQcn^WvKqyHB2P;bM$`fIBAQ%q6fl zdNNxVHtYma1CfrYBm6w2LuS}3Wz+-fTe?Z5Rm{^=Y|3TrS|*=jB3EK9AryC(QxM&Q z0J}ry{KSKJRn#fAnW2}z4!a^ONnn#-t7pd%z01xM2M6BP3>Fczke8M@=jZ1VW{HxH z9Bn}TO(9V?>votK1#RLe8rAiRA`mc5G!@2xHSH%IGZA0jUbJ)%EVhShrwyxV^-2j> zEx2<7LV<|oaG(dBTTBNnlp&^u1BGnSlUORW_T?ABNhH6GpdcLaX=Q{a4tO8K^R>|b zft$2h2lMh|24_%W-{`P^gc9?gGme?gT(6YZzk>B=gD1A1gX$baFW~#nX(6IufI~(G zvqUK)^zD~FD`kzP9mkv=35ARSDUQ$qv0EJLq3)D1#>UJa;m3jJf#{!(mio)hPHN8z zMh=Q(5UxHhRQTPKDg&)UVl>DmQC37^sJy4pdpbTw2D6P?w8^?G=UX=$GxNwweZtn= z((JK@u2*yhBuax=SvFR5sqPN!nU3xl$c`Z-;ORPC!L}J8hF-V!15%qF0h@d2srTB@ z$~c_tkq~R??Q_o!PymjUYi;0j<|)88$q6RXem~BFW4$FuGwX)2XH;`F}f>de@*&mZ4JC90d=0$P(dCuO&Xf{W)r1dt4ze%^ za*#hPEx{IG_uab5B!`}m0mx$P0-f#a6LKd1?-lF&>}msrcOszh*YWd}14qo)o8C}< zk{`5Pb&qg6FD)$z8_}J-H)Mh=(xHWcGBC~cp*79gcVQd(nl~fIa-X;O2!o}s#paq5 zs<#X%%gf6sZl>918Nk}76tz%k8%-M~To|%DgysEn`lus43ZfBLfrz^nV_qUIz4#)| zQ~?yR`LwO8Uv7)2jy~1xmBzMNwPV&4;ozR?=&D09Q?bpsdf{hv*VSk=r^A%9{WZ`A zRN&DG)wegndm?3LXs4Fjmj-z|f^|jA%7`FWiqs{_`R8z2DeV)iw6eRaWE(X_6_*we z5$!nT4m~$SXhe2aKqqu8p^hAAYZq+9TY)w!gvaN~=uc~>9)T(rTJf(70Tr1H7BSt7 zcINE=x4b=^1v4q}u&%&Aqk~2>eY^Vf<=XD^_2+AtONeLcD3D@K7}?ZOD8ec<#tEfb znu=JGuX?}lg!Fvv>(#B*2Tuj5-=bJ-Ws_&K4QAJ%*((OVWRC~4g;3SZk#9ritc{80 zZGvDLL50ACPmQTU6J@#=_S3Sxh%v}#H0WtK^fObkn!1!b+O8I?&ON}9h-=;a+t*N< z`ocG=Z-eeVSNqhTj1GZH@}7GM_CSml93dBPYIWc&#Gqa>t@o@EPO)6lOpgF&N+*0q4U2oSSUF|Y1Q_#MMq1=vRf10$gHPFdpHG$ zC@h;jf};{Rq#Lj}N1|&haWEQaoMN55P$qDJ@pCRs)elLmjSN34(n;EuSYy8cc}*po zdY0HuWsczm9qQf@x5@N-t|Q{a7i@~nSf?c?m&1y&2j_Rye-1gAZ`D~{%0m%At@R4( zHQH^Y0nDPk8w=a+MjBvJL-|Banl!~%D(}RX7iYtput>#5bOSS4Pmfp3;GH)*NZKJ*Psii^iNZn=&ADde97@VSEF_-E-iV;;d z_tOTnzB@wx_zRxG)CfqGz$+^z+ldr8=3jk4tBcKxiCix`OE{~_^I{$svox2QII&|! zAN)^6`kv;vLn68SYPze#betlO9;F!Zd{&rPDk8|cpxDkV8O&WuIW9`A%&U^8W(58w z5veP(qDjtH#nXptRh1w_RU_UP6^dD!*C;+zRZM!6#a*Q2=rn|BS>@X@Z)??xeAUUp zoC+;f)5mKR=%2RCQ(c8cfS%XyJg#46I&1i0n#>8Iv?+&m;?RyRn3^T2Shb|6i@)un zRq4V1>H-v)L1amYV~_xT54~UtDyvu|ZmfO1_7A(bEbYnjuOWf^3)_F*nY{Y^{^G*@ zm7U2C3-|ASwu66*bLrh4a)5F*&hGZ+NW)?Ig|)>lBXpF9fF_%$?c}NiOOEcQLy1Z zArrR($xVnlkx!Jx3RI2>X_a&dvmPvqSQ=bm@&?hO+5IzG0S5d2#dv~q3l{myEQzK< zzx0HhUPSMv0UTc#f=HI5OrULBLBvB~wWBBA;MCRbY$Kvjo04k}=(&zJUpAA_=4a|| z?o2+vxk}HwG=smA@$p8FH-;!oS=!PdPpeoxSr?dCE-rb?WlGUe$dXus6)l5(zM2Cw zC@+5ne(o=;NueBH@Z56$^Tk>4%t({+T6z-8Wsd0C{sht2M}rC7ZM);a*)cMTENu6I z3dqpk>CfpOeVm(P7A}5qKZ64M0&j7-4w4?rhFNiIkz<95^A1X6{_*$F9%IQ!eMYZj_`QI23@hC&*;7hua~r<$>+A9{x!9 zvXkA}Q+X1^zZLfx*)01!n}JRko#$6zURWft-Agvckqilqc{n=zFdn`+I`1v)FZS-- zy7lX&JGbuK={??iJ;bbjf#fo$`@_Nb+O-#hu_UQLIuD%0I~t4!uRrwOjQ5}nac}wI zcpx`p?jK?M2ktD8fH#O>BXEKWqt{I3O`)^b+eeGnaEB)CyxwGVc+SH;D2!0>$z-%Y zM8=i_9PHkgV=Qv%K@aX5EUFi{%AVV_8gs}mf<_Jod&f8~Br9x*-|#84g&_d!$qMn7&sGGe|TLqMGkfw%N^&~xmhjIam^dTu0|@XfoU(Mj18 zkm&GYd^*IuJ|JcXBalp5{Li>}gmsAd;pq5y^p0JFr}sdvR-CL{yS9Z-d#^`uV36iI z$~Zv>RE69!02m-2Rr@qK!i6k7xCWJA7z~gXucABX9j7SPFMPqw~Uf4S8|iH+6gTYu`UKkltQ z|5NV|Po6*e|Ji#N=tz(2Jg|oki-JUnC(+naB**`N7IK#v9zUIH*f zj(}kkKsSIU0W{Rzn1@Ju<YwsR8n_W9=I*Dyc)Jn1=C$gPr z<+Xjx&1&t@k@iG6*0GeB5%&A;t*XENR|A+C$&M|M7Eg^xFge{eIzBZvTEw%_iQ%#HBeWRKpx(sfY%n%DK8krin4JvRpaC^nM7^`YcyW68 z93T&!866v)z1Z1yc64@vFwag-2SdTs(Ddx+@cFT!>0s*o^wi``5zUVP?8NBAS=uGV z@#4g6H(JHBpm+gaf|+weV`FTpZRk9(pC*36@Z{7*x&1XbH#s&^#KSX1U?zTTX$qYh z9vd1R?+iwU#)sZR?4|?U4_lmWV==`pc=TM6kJ#Q2{vVzlot$7dh9@Uxr}4EDy_}wP zRUaLlDRu@!)1xya$l2-1@y<4q6Ey%GKu~X@s8C31A&?sp6d}RRqth0bV5B%S23Q!L ziOBF_G;hYzL`3OI1vi|_@wur+eI5_yhfGc^`;z;n_Fa~W)#G!v6l#uDE?2N;+j`G+ za;pjbJ3kEetgTh_n={6DSvgTl%sTv|7O#P=eF<&eNEhRVFL-N`25)s^xfVA|i__B> z47{w=>$N(KNVgvXm4)7T`yH1n^~GAF+Po&j7+=^#y}33^oPhx_@AP|$6T=so;WE|_ zxDtxfXC`ML6xb4)9v>R(^lA=`6~ShEtu;J73Tfh*_&qjfI=#nQkaUPCmJWNY2cSSM5uoqmQy1$Bg&> zh-DOUW0UdT8wD*vGWj@!qP&BY;D|U%4o0*jtv&sTFG$yF z$SAT#ZOS#0UIyp-3v;50n|7#Iq@y%Aqt~D$P@Pj?Hy6zZWCFoO!_(bfin}gUfty%9 zLuL|OorXF9(3!FYsT^bY7am+wC`uTIE=+y)E&>Ub5AB?#3QV~4Nkmqy@o^UiUXl8H zQn*Xga~a4XzuE-k23^w|seHk*AzWIg9Ol`y=89FZ=sI8F<-y?eBb(w&U<{l&~I6NAHM2Vbi7$;?&AmK(Y9@4wb(|?Vn1?2b9?ALrI>Dz0IH1y3w zxJ_~Y&1$V&(gVBl7_0d3dJW(!Ea@XR*Z%a=MB>?J+ii+f^uVp`iI>ml^CKAF+3I@5 zSxUQs*mX*osNkv(3{0OL?(gq^glkpT8awLuX(!kSh7}4RWN;p?&yrYON{()g{1ea= zfEImb=pd^%uWf>LIS{5Y)uI_)@IVvPcY_~+C+qE-^Bt=Wpi;6n*q=A(DqvhJ8 zR4Fcs%TY+fsF0_1t-4X6$QoHS4bhF~Bu161Ez99jof4LnQ5p)VnSo-F|ta4?kRu^}R4R6XKhqZioB45jLxYOL#W>{;R z6@$GJN3OZ5)8Ot+DH6nr(!9MZb?2XoCD%q)VHmHIi z{NI82KpdSM@#tduQ*F41oXySC+lPpoI-#T-HzJ@rK@dkbQJgA6vZHN=5*aCCec)c3 zDJR^<^tWDT(1bVw!Ci6P(oile;3$V-kIHZ-ZM(i%eAfM434qDx9MXiE6nH~GeYs{*Pfg?($f>B;cW#P*xeFn zYaKX@abzNbgo~r;SX!+tJtfme+q^AqO~?G^>Sk$+!Bld|p!-(HH}F7D^I)7R8a@5m%QIU{ZgEB#6prqpS08>-eQxNyqjTw^E}SRDWksHFGQa578uBspt9 zEM?7Um^?W+RD(tfEwg%o{TeuY7y}4r8F##{*al6pdBl3aUMUyyGqWS`mP+^M^)vci z7>vLH?*Np66P!krNoFgeNi+K;9Bb#J)Nc8Vaf{u;qSti`pHsVq=62t$TQ_|{7-$z0 zH(@4=xa7jsP}wLq;=W2yOMc?>Q(TQ;vWyqmovKK!C>B`O+(#X9GY)a>G1|Lz1GP1T zbCor0?ab?+om}0s+yvTKfInS+BA)HYl%Hzya_Y1z7R>4xLUkBkxs?}RA@HuC7^<-)hlI_nb?&!piA_@Avy3#k{TEs*a*oHg0NJnuGs+w`Xg10 z`2u#H7$c;#bd?WVtS#q?PxH}g871MK!X%2*N70VynN1Ic)B+Z9Qo)#pNdwNZf~7;Y zyTZ3D5*%+Aw-%foP!P>}F`zDW#AH-pTrM;!b{HV6riZ)XMR18Fh&Ph9NxscShi+t) zNEC>mli+fzhOJqMef*t8Mik1%*7_o@;9ef1WY5-yscO#FYwJ<*qO$i{yCba{iIny{ z2@dQK=?`i$4OGb6hS*lc3P+nSTiYollc3L;>wK9pZv++dj^H+Np=MI*K5Gs+1ou|DLK(untdr{ZZa49NARd^j+ zK)jMD+BdV)lHitv2C&k`?IMgjI3?6)YJ?07ToDamztt=2(5xfsYk{-NQ!Z|5rrQXR zT{K(!d=uTt2j)lgBO6pSze^_B1ZGmT;~btl8Cb61ZXAwkeub^b=`?d+Sj=JujqT8c zh6ls~Kk*qN;unaapJf4AB^z_3VDMdsWO*zct`pU=v!EbbhS`j4x>uquGu}b4=}iiI z@3oXQ`Jroujj*aqaqMJtBck#{ZWUo^(NyT=Q2UBor!)vMS^=@b8Z2iz9(T>Y|NLli zHmlicp>cBw}$+r4LR zt>%fYz)x_TzeYMOa^b+ZQQgfpgc-0HfztF76@^*S82#e67%tBOT&NY7H4S)jCZ_8h zV1{ax6{(71xOnb=SMR&y3XZGM2)0=wm(bN2dN-^7PP#RY)ZR&vp@x%yBL!RFt6B_U zab^K*6?7V#h_5>fR+j3H1IntOqP63kC|W->NV`rsCe8+`{fp@KI9L;c7MyUakln#e zn*mZ_yBN2nM%k?&q<62uNhG-OPi!86h6bb9E~_@gO=Bu`Ke0t(fMLUYFp8(gdF zAVsERSeQ=1GQ!=?+o6V6zP1R2&sFm}G{P5_W0O_zCgiu|YvCDm!7(`dVZRW$wBCg6 z+NnTSh_>D}EX@pyM3n$njH*FJJ~0EBfFCzTjQBBMSh#`121~1qD+7u7U{efK-6rPBnz_s0GskyM?atgP=1~TI< zsdZ5h_n~lVm0PDmPl|QuILjqvYtDkPYfVF>nAs;n`{(SNS0f2_klF808^W0}T(;?& zY6*{Y)VMw12J@2&5pj}M){=`6TRC16 zHqixZB>OgoZ7NFw&97cZPKwfRD&YdE~TQ;5Y`Z*skgaD)KN|iWgx}uyscay(|r`{sOQ+jSxD%e?G zpgR!+qkp)HxZUMy9s1X1thDJg%+zm+!k*Ai;A-}S6i}1h(S^8$yMXAqfq;00;o2G! zC)-#Ou^OzaJ~A}U^nm&4;Nr8JY9SE1=aG~x5KVjRdtxG;uyt>KRyD)uHb2;7Yo#F4 z)8CqD8(cQ(TBm=d9{OVKA`o(Ud2OqKjU&H=*F($BDflL63e03&Oity}OmP}h+gyHP zvNSw7HaVTg*>Z$ai)+F1n1r>s0%B&Hg>mR6<5=g#JJYO}@Z@ehjCo*A`rqk!y$YWuHgZA@{4J&kc`Y}ENoUwIL=wFU1^jqH+y?} zN)2V8{2}JQ7|bpZBj};F)Q%5L%#7k_AfVxO9v=iT+du4H6xX*{#uPwQMtwb6hBPp7 zHKrdr$fasy0a4a5G{}8_Y1Fi;lUd}RL)!i`5}~crnG254ufCREU%09}rLeH9=8lGA z$EQnQH;A@&AUcEZx`U1UjI;5k4_x&3>{1)sovb{rvML;E2Z=Tj>tOUSPG+y+h&Ych zR&hw#HP9Wrr`8M(mU+upsWgrwv6CaE67`2g!EQ^X|QGuYnlJUfQ8sR>j!>tiI&2)gXF=13(P`{pG3{aZMkk8MB91+62Qc4LC+bfJx zY>B93^2Bv@$zc?A8N@IeF;aeaoEO0`d>_L*###!)dx)+f5h~NDgdofV zUq~#~9qL$QiO#E(4>9=(W_yDeWZFjoU%l4w%>bv)Q6nK_c0SXUUnF7A<8zO9UV0FT zwL0a)p$9q+W12VPd5Ez#RK83pquF>W>IC1#%Fo&waf~fQV>GxHhE*ScQZ_Iy-dPVnt2yH^wskBVIVq~=L`gXZ)4ZSSc#4NN zF*~dmLSkJOjmH2I)BZ&bxWZhNV!i?VDFkg%tnu|yUh%PIODWH(W+h;+kYOTF*_#SLjN z2FmAgX$V)G2C_$J%BQJ6XWo)~+&NjZm~};ju6n566pujA*Fs_kLYtR#(xxL=JMIpy zfFm^NSOU-<$0{z60^QI!jdPV{tRg$@DF@S!0XtL)BY*atphI{K=?hcrKkuDo6<7tv z;C2&NIOqE1qmyoEFDEQ53FfGiwv;B-*$I3tGr2}_#!?2w&XuMCQ6KKq!64Z{z|)^vOX2(MpI4ub zc`r$JV?}R^1l;=aZnqGkYaJ^tx-7H;=db@PD|;(Bc&~Um!e0}vR?B5ca2a0?>Wj0m z?-m!9p2EnHb$nRAVLOd}tbXaL_~KRcoJWX<)&E~?^c3$kkmC1tr0f^JcNM3=%T)nl#4+cv8ul8GlU zC~;s6J8O7$HA{%hq|(r?3I&cTT1S3Hza!5x&HflXQqC0vm-@|Sw^ePN6GlwwtTJL) zHFf!9YAcpngF4=`Ru|Ivl{zJ~%c2ri$PWbyDfB0p3oa)vvDPf#k?y`IHn8%RS^415gJm~HEiguBs2}v!HD04&3 zYz9c0q>EUKKq%Sz1Vq9A&QLaJM3Ugl)^YJiy+IXA3D6?L3pmS+29>aTK%o=o`lM-r z;7`-xQwGI&h0u(Q1Ez;kvlgYhccR1ggE-pZcNju*S~gJ?1a6OvxZ@~SR~D4ib#Xz- zX2yZwG+QRA5;`;@{z6y?!ab$x3cqs&8TT;4m7>-NrxaEA$<*mAyXqpe(d zjFSDFIZCNw;deMmxDaMYw>a%VHE;P6a(Za`5w1Zvd`QL$zwHfaue~JBK2Tr{H%n7| za-oW~I$qQ>2v@H!T=U?VC5-X&s17%GaOoFS|5^bW>Vw15WM-fL_hP~!& zV$0-lARu$-j#E3x)d{tTA+vi!D!VT!M1Hny`~x4&-I>ef?!xc(b}sia{_e4D<+8#_5bGfm7cjrDaUYoSfUd!FF?@jyOn9H@{$@lBiV}Big z@567v?}PgJj$H0;e#MXdn|lIZK(y08odf*_e?$th9xKaV_lFO2A8pU&rubnHpudkF zVn2Afxew0{8>~GOL!)I#e>{E`28FB z^?n8kuU=WJq2TBD;ROjqc7Gv3$leY}knc(UW=KYIIM?#5M&&y#<;z5kwI zF!x;L3%ToGf1&t=sSDftb9ivQ_=V?Q`!jyK3*TNC|JU35ACq#g=UyoO>#6Od=kRTN z{}}$gcyasaJPXP5C*=8vJiq?A?fp~u^xXLC+X7-1U;1CUaSkoNdi>s8&tPurjz4+z zE1$R{ms|bte&F;H`1HlK{=wX5@9H>$*V{)|@#Q6cT@b?_0eA2;2LQ;MG_EFFbtl8I(qWwkLn+#yo+4^|(Cx z$SYf&&mZN>)vu%M>X-4)jh}_4xOnj+uQY$)v&GlHy}iBtGPG;%D}R80oEPY67Y>b z17fefJpO@PZut>(2N>Vg|H@a#Up#@2FMOB}e&iUn@zz(~`b+2D{v-e4O_2QNJMs4p zJo`7#z5Ju-_ggN1zW>)B`SEYz<5%DE+|}k=F8_0W=Y;dtzx&DSpL}xPOGJNr|7I|l zdvbew{~G>Yy!g_Of(kGFIR4q*|9L#Qes2?>pTB2&|BuVJP5JiX&+$v{rK|XMK8F<>yYm4G?o%Z(jW;KfwB*K%t8_R^-inY-{U{Z0H~JasMl?-gp{szVZ+L)AsiD z?eo{a_9I^_fM_qB>qT?-j_}|8gZ%dgPw?NP$N2BbBlvguC-y%ff9{=?KR?(bf9~#- zKL-o)r>g^he)=cxehh!E7jrL-$=|zPn!?`~iub%U&+i|3?e*>L&))s_f!jX(_aa}u zG=^`Vy?YS&1JnV|14q>#uD+ z@Z8n=?%e9QKKQni!QFrR9nJkG5Ae@hPPX&U8&2j1|J!|Aci;E|$kzYLrBB_JTRrzS zw#ddo-|d6{5Feg=+iIa5qqKNO4ou8}y?*p3@O=A=7oRVF`MGaxZ@qbY3&ZdFBHE#g?iVIt|_dk8|P8&`Qqu|Hz+Se&_XP-+cYtn~(p8`Wvr5cF&!^d;HJpZD*c) zrEvU<^&9=KlpddpfKz;){PyZOpubvybIhZz29=k+qSpvQ;uEzFYg1n zD3dq-7>zym>9-5NpxbwnD*JN%e|q7@@5-Auqx<(>;LnXu$%D5&{wm}{#__qY-6s@& zuJSER+OOwk9{sh6KN|dJ{|v&ief+b{dq47O^Nrv9!b=qRuRd|%h2pm$t|!pd>szn) zZ+&Zf|C{l-|LfcP?=yejCV$ZvlzThN6~ED6{CfY_kAHLP4Zk`G$X|WaNB(5%4c9;O z$=B|C)7PIr$_{+Z#V{UgdYET|8L&- zU%t7${gpppVOS~$XVJp-*^YZ&IP>P~@BMuL=Wcuyudl!Nb$t3Ue0q7UeK7Zx21E?39zB7V=|_XvK!AHV;xkjwoMet#Rkzlh&Y;P+$r{W#w7 z=RczEYaKA}4+0i`|Hiv>xiWtL5q|$6p7W=W&*eUi-!J0#27bSd-@geM1^hmQ-~SQc z&*AR}@Y}@ikKy-k;@Mm9o5SzN@cXy$yN2J#@jH#*v-my5UzF{^-~S!IFB_cL=Ev?E z5kJ6jhsJ?V4ejy>gx+Q zRWvvO%0HWtgcbq=m zYvJt`BMX1^n|@LH5yh zy;&+V=Mk zURztT1BXrE0pWHYu0uNGb4^2 z0N(#3+EOFt?88%co!?N**aGlGWSbe*-2S_uOEX{p)*YkBLa&6JLRcTd@N%6x8S{dBcZO?QG8W6!!n4(-r{qB{_wybuv%(0Y)(Bw3upc16MoL+VD8Ef z!Rr(A0DpcA?d$`*X0D#A<~DLGfL6r{paRGnx#e69W$1a@hmDRS_NIjo0%sHOs;CRKhM#G?S;&>~HUPvrp0DFm17*t6W{|srzfG+AYq>4_4W#{r z+*9&)1>cDoaS8BN|6K)4eyaf9y1ZXNtpM$80BT?$Z3;ZLyNEYSfM_9T>)IZ1d?@!O zuP+w_ZUb;_A9{10sM{0ym-UY2-i&&rmEy92cGfT^3&09f4*FCDK4pB~5G)lV^3V69 z-WZ;*NR45%w3)jm)LTI&7XWMaL2J zVIR1A0WDjsLk@&47U`SCTlT*J8ZQ8|aD3i)*FV1N1TG-1tS{nB>ecnNjmF7#+`K!` zK*-PfLZfTFx>T<-KZ|(AR{t!G%)v_Z13&vX+L#uTM(Q; zFleJWiYq>M39Vo2p(awvZsEqIYXXOW$i&s&Vlkgam zp6OM3C)*bqNL~R`q23;BRfm>1ou6z+>{_GJe(J^1frT!=?G|~NlR@cdP9E9)hYY%u+#mV+@I||(whGAa~ zPtUdojMEa@KhoXP-PeoTtGkc2a|fLrt<{HcW&>6wZpEnsz|~_9;hjVmysvU?mRDh8 z(3Y2uE%q-yvd~-UJyz*oBw6Cv7(7oLPVYhZT>2W;KQ%QpH8jE-iywQ;eE8n?zg+a- zgg>|se&?S1dJgm)?m5>4k{JowRdw#CxANBk~&&xf(((|dF z&-Hwv=gU3YJ@@q9-`mwY*!$kz$==!C%e_zc{%G&>y&voS+1{V;{l(tT_5Nn>@AUp& z@BiKV$GzX^{g1tO_O~``x}j?EB-s z|F7>GeSg;XrvAU)|Bn8H{XP8?{j>ca=zpSrxqq#{(f^_TAL;+`{-5msSpR?D|3CEq zeE&b`|78Df^#7~=f7Abm{Wtpmq<_2rt|M&KR`Qd;6@PB&v?ql~Jd&jYp$BM_Mj$Jr5e{AvC>amSu&0`-r_Tghc zeeB;JyW|m8(AH|R zx!J&dI3C!;@Pr;VN1qO3F^%9R++&YZ=lD{t95z6^SDWi=ZEdB-a_Q-(agq4=IBrHS zO$?0}gJ+*bgzeLqzX+Aw~90>2&9!7h!35c3pxs%SAs5NK++Zt;pAe_E>PNo zOP7ul%7#>64-FO_1u0(w+HQAT^qG7@t@!tI*c(NHQ?!7 z<%gvX!Gs@Yz*9)lGsFAsaWARqJ;w72d8FfML2qvG(zC~da?KI%%E^ubj(}}6fpS-n zKe(6&eXf|=%8r^<9>)W0tnk36reC7~t(;jU_gwx+oLH-A&jGjxEAh{h4{-fVmniszkHh*@{=J%r} zY;Zeb1NN)y6p@<`I)W#HP%_!X?z-SGVio>0PIf%qIDB}19{CG# z=;`9r*u|5Dj)I%cWx8L1q0{v!A%vpXP7Ur_z~}aZA0%f%!rO73heN+qT4V4v<{0?R z?Mwp+Xqd86FfzkZzT&ca;AHSX`!GEJw3_C_8Ed|s2HOU*O+jH`kE-0={y@4&pL%y+ zP{C<@hZqOIKJ-P%74T&~K35~c-HKe$bv8&hvVx@DbyKM>XD}5ktloeXqIsu{ny%?ptT%=gcp^8f&-pOVpTb7`ZwR$6FW+gK_oTwF3q z8#3iZfv0F~UW<4LKMq2p7dS5t6=hNEbSYNs6N5Bj`Y+e2WrNiLlls7%go^YIDLmx6 zNw7fMsfrvD!?0b=kvv6IONkYw+a5T$uFq@|2%eF%2RI5ya}xz5eKrRSw4SA}!$6ZC z#4GYh1DIwvRU>dQ+CJtM+sd5UT!K|1@Px_$?O!iMsYH^Efwfa*OiQJ;O152%f-K=HUsuf zbu?w7Ns;T-;?P+|MHA1UFto`F2PKrqCD1Uhd=+LqgCJ@C{KWevCLf*fleY1&UJ*O7 zc&(h`R3FaT@KRsAFg%sULD+9LUSF4t%Cwg_OBCY4?1Xeyg64)vMGozWrR2dUw89gc z#M7KiwrD3>u`kF$_H$Z037xvNQHYL~xfc+CcvR13`*p*D`w`@JUGpA@KpPt^Yc!Oh zpm);M<`Ti1f?hj_d=?5vRZm+7cG}&jqG{Z{;*GPHA#MV$V0a*KH-Q^gz*5PxLvTC- zPHrV}bTmaRji|cKBT!p_ceo_72w}LKzXZ1fO8E$Ooq{c{I3?Zyib+ghcrt-|r*NFt zaL94WikPT*q8B_OqZtyW>r@5-vBtox4(E$IC>X*_Xhs2y^TtTlezPfVW}4U5kQreI z)p6FQ{BDu`y0!8#7ujMze+6LzjWY|#meIO~(Iv>X!@w^6=6H9Pl@qn70ovN!?F6cm zw3JYy#pOz!NJkA?ITU;iRTDu18RJ6f+f^}SoY(-6;ademR2*bkSGP* zFBa(w+CdFSNP0*1bRB@W+H8*V$bAKwJ-4ZqOaIBbnl%6$TRZ#@d6b*nlH zEi#90#(hV2NFyaX{uzQEqP;BGkZhAc8fa@D0*ENiSp9onk_^w_OSaGo*%k>X(n z-%MO1*Fr%g{CTzOli_FqHDH>OL zC+n9892hcK`XKNOi3HN;56_~B)Rbn^Sufr)i{6x>K5yq-nTw#q&FwOY=>_lOe1;A6 zwTd%Ty+r&eWzAK#yDYti1v!jm&Fg}6p=)|oDdbgu5}06-<1SsvEY;YOIE9@HN_x_r zi(Of$)1=?IAe=$Cb0SKhBsrI0Ji?5R7fke(3sy^+Rm7{p`ga+f>qzgP+5d&cQ;z;N z6_(tE%XLXCIhXGMad|qdah^2{Xh0Jmr8Hw57?O`Bnr|cv96amHZI}YgYJ5NcpzC%D zWhG5x3LHrm0qaB4IAS)oK7L)L7?AN1$gp^{8}Y(W8)9V6G_i#nHCtL&7%E?;)-djb zW@Qu9$0mmeKvpAX;n=_p7$|jX%AmC|?80(y`U0B}!Zf@hh@f>3cCw}2j1@QjcdBS? ze*Lq{!k#6$%NoYvftX|2ZQ4_7vlQ%Iwj*VSE6l~!daO*hrTmQDLf@&^Ultz8-*dj;Si0YF-*hwQju!AB-`x zt7%o6te@Rep3BL&n50tHA!GBg6`sE~O?4b)dW<}CU%G-GsxJu674_ffZ4%%OeB#N(9Oa?W~oIH*ZDdn_Qfot5h4!N}+h!g}9Jr=QscLMTJ zxzb*IQB3wYFc2F;*RPP$0ENRM1p~J$;G}VAr~861^{0U+g4VI-k4nTeC6Sd1GS{SA zWsBH?f6AFNFjDlilCF6<4B`m14;yr7B%Wf^V!6syem+1pZJqZwxn&)?Qte~e=(Wj9 zg)5B3#f>H}&!Bx8CK1$)?GRHY37oU`S_3(>Eho{vH%0HoM){0%4!t*Y5$^$4*~)Y3 zvCu*el3DKKk8~D)wR`-aeZnX02KmH*vgU@PbX<1Q?Qkdp17H_e&$Zc1_xTe`aLFTD z_SC_~iNmKrbd_1h!UF?}A+i}(!GSAhY}kXN-=RaDEx>sWp!`XI*BNSHKxCp|ziG_P z2#zix=u50(rE7F;3vyYj8rwFVZQ@bYf;&Q`jh0PL&Nlr1E!r z_-rVy17l0}7OfPgr*VIuHgtti{A>tOZM$kFvE*;j(4#}s69~~aO}Rc@gVkQUva8mj zeuEZGO-#-fO*4$Q3@T#n_?bM!W$rv+6)YhcGIX0=wY zeu&|LYucW1H>5gkT4U${X(Y>S##{I!DN_=^B(Z^G@W&OpySqE6{Kd2~eSkWjgC--D z%*Rlvv8*UGF|VlE=^WK`#J*EBx;AAwI}jSTBGt8x$OEiz;3Q;r{AV>p$YuOAjc8xlLx_(%FxVL7x~A=#bWseAn@&4tVCI_Ks+WDb2Gk_AQuO z+`EQ4~ohYZAse(?Vkq z54vt5cT)81ZSx_L-|fm0tizG93*&-&foa}ML7d5LaS_@Jh>%ClOJ4Ja?&(Jqifz>Z z-6T|*F3I!pN#rK|m%Hi;af|lAxJu2m7Jc`f=pC4NFYZ+ID!TXJ(~`qAoZD6_ZU#I9EAd==$w6YN%EyzqleDTUETBSw=IgTh^u0*VVH-J{09eKU~}aE?ZAb1H?m&3x))%$CGhfQTFsMsb;U z818FTq|DOeGnxDgc9I(0FSAu4P0H1F=zVU`RZof{cT#CTgg493fDuzfIZ3>RXcs4% zi7d_-UB*te*dbaV&3}S+{jJ^)Mtr1R-RKL_1)4K;mq2%I5D=#K&N(5ELV&no7thd7 zFb8>vCjQux;D9~#&(bp#uBgY@kcd>peTAx>6c{LF9q79KAd7~90rXe8I>NIkvX(34&AHt! zsnE5uh3hV5*3j>0qsisbIaw8}+qaNEL3e?;gyjRvTjNMKF=PpD_XMEE0Yw@{Tsa;4 zhF~do?b$gDa}>`3cwdd0K43a(Cmf>xvW(I$^J!mN0;I2BOR}M#bx?I&i~|3Yxeb)$wvhKWq<3 z32srAu2h@2=G0WT&C^_0a#(x?BaBUg`o2O2PzhNvirrJNV^FZ(ECYTrr*&*Qade}) zT6LcSu@gcXGAii0yJP1mH_w2$7C|%9sYlg56nOvvqfc*Uqf6!y^idW%R5Uo4j-4Cb zeMJk{=Y=WA4K(-2?yubBZi_;b%_+`hWO@33gj?AkJ!B$Nsf;a8#X~AEnmAxx$j=>Y zT%tG6(l(CpJZQ95;U=1yjM>PjbV?6q@M10(KP+D12nglpEIR;R5;-U1*#lw*njJSH zY+eE9j9o7`O=?qEIochn3=&(AhaiZzNPLSonzm5Ms;*(#0#Id{di|XTG+T&H`T|@4HmaCvR`X}7+tMEO}Ld5{*aiT{|m=5?$8cjA$ z9+X(~<7!#kEodC#Hl&IK0}H@JWt@yqm&DdaW3{>rT`Zl!R)kEyVb2r+k1FyiJn8jr zsmMmy;YGDVQ`AobpbvwGc2?4P;{chCFNlzASDSDgDodG#5mdYG|mL3hvEz)lr(1;yUH#> zhpd$J_Gw%QUYIXGc<8G9TL6vc3*LkILx)ax9F}cA@Tq0;p^2H%VeAu96Tl_ZL>4{u z3A7mwbz-4RZp+N|!bCAdHiL?bFp(>cG2;qm?#Lx>+sEo1xdiPPd)7x+gS+PQfP%&N zUugFB^q3RPSe{{GaIa#lL2_7(qvSl#>>MmD!sQxq?9)+p;s<$A_#h4(@iyJK`mJnd zt(Gud2y0_4k)%kn6zK--D2QaJfhfvG=Chb5K_(|9hA)!`zZ*zMg0;g&8QZX>%cOCs zv9-Bbt2c2SEs31@9?3`$r&Y*ru5L0{RkI9%07vDofPYlX!c|6Go->EVusf!e=Cv7J zg?IzOp&PEPZLL#1rgA-Zle>k{k0TqGlr&!tS1Gh1+&GIH{*4W(WnXfoDd9Lg#<((= zj;A8BcwBMlCU*9-!S1_*7P-vM7RE&oACfqq&5DZbrOvj# zlxLupY<6&9Uyu%>^@5ua4QSK7EJwL>d2Kfls;g8zO9yDzqoXbFe{lc(P zn8wbO_7BMOaf`Td4=}P7gy-A62yyQz_{A?|<<4OnZ;5B*MdMKB5i;-2T-78+y}ClA zDKOIInN_$@-9c8gWsR7bbH%Z-67tlYnVcD&y(mh_oXpoXw*W^4I;2B7n*0Wa8;$W| zTAs_pyJB~)kadMKk6o1#Q*p+QhD8b;85M;FJBrHqIH2V zG`o9NZK_|zp@8>RZJ9z;{792SrHv6%ns~!zk`;Kgf=KH(wRqX0m$*PxzAlKw%uSAt zovD-72Z)E|mFlfQ{Zh!MjNr3vSklv+EEOj%%q2=t+HHZE;xq<7y^OQ8I0iR|&B=T> z-90h>lTpY*j$}?&Io=i~tcdscQdSjCmW+zuPVpVZCN#{twXD}n%jt}VB!!e0IPb0Qp`3^Y#C<04@!7)9 z#=Tnd!bzn;Rrb&{%L-+lJ@7{Zof?il85m%uP06N>dDFu55eJ+&n66WcSSwilq?wdiB+{}t z{ItI4V5_w$e)+C0NV-1pNGl9s3=$nG#@WL$P)Af0OeyoMQeOe`1|CK#jts)jwki=^ zbj|b^NwS6DX_iJrAe|VturLgpd?y6c(}4-og+3e1FS|1;bQ-hy=0qA8wkk$Aru;T6 zg*`{GH(yd)==5kx3%3k~))7{!tnJ%EQp6XSu2WdL57k$;crd8o+C9`Jd@0zV9iO>E z2ltkcY)y>v@cHl{}$G=j1!;?h%tD}Xu|W8#9B+cU`?gpSV@) zN>Z7%m0p5g|*-lBJB;&SZ$@c_=JN9UrVYFGskold~3;?$@h^@hWr-wB@ z(?Y~V75br;zA+LqL`qE+pOmUPU}4?l3@>hS>qrs^X<{5Ii5RaWMR`W&7;dEA4tX|H z7Quxw4TXo)`6`wnYSPU(b$FFj9Xo#s73@yam2Bw9pJ_c3xL2hzjBbvJLp=^O@o-R)v8tu|&>z&#nTCh8(v` zHD$&?`5M@908|T@UPd-E*DLF_`Ze7du?Mg*9&9hZut}^tqDmj*G@`*@$ii)DI2T#1 za3s4RQf2}r_RZ42!-FBKQVpJlYu?J3j<3wM;X83h#jAR>i_#T}Eqs?+ z)f>=g)o6$s4Ftr7t|6j4mKq1w)XQV6Jth}5u|Sbcp3`hrFsSIutLJ+M(SDI#)KLbu;$LBl|H%KdQbJ^{9PCA2Zsj zKM2*L<}a8ejLkGEn^Bqak#pv<37}S()|=C9Lp(7gVg#z`qlGw3R@LhhQ3P#RFm7I} zOE4I3GwbW=>*>NjeTjR`+S+iP^Exyg7?d^{7T*=z&$Cirz#bQ@>t$q(;JIl=y00J{ zoEL^|TnoUexYRnRE%Le^+SLnKJ8xcVLrKa>z>7GM-=TuSWw>4&wWTVIxiYRNSrWgO zMj-P>wnk8(P2N6ZYP4fsL?dO~64r*xZ}`r>a>gR|j^SvK(;Ey>Us~HL69;Qcu2t8o z3>pN2tS)3|v;ioVnaC&wJUasfGE|rOm+o6!%Er}9W0k83g2sJoc!*ekbJ-f4y&L7j zypRVwk+>RUvkJVWuOW6)hNy!!Xy1^i!6#Q%dCisI6Od>b1`3+Q6;SMM4J4BmsUuj2 z#g}VLZ^5oD)i(5Ip~gU4+bmu!EY>d5UtvcX=`C>CoRC`vK#v#YQ`3_dMn{SxLHp1Q zzT@oBqocFukjObeiRqz<*^9yC*(3#OO?EH4NogJM;q~lC* zc5*rx3Z{mpXGe$6j}1)+Q|G6rCTEIhegt5VMC9x=S}BefCuY0RDxL+!3-}VuoEsV& zV^eKNp>b|#9CFI#cWnhNeenNRYGB zljEIjBqwSBI)I?wL{XuT)IuOPA}B(FokyoFF2P6?HA zhH@KBppfQ^C6IHvG*SfT4iAAXMH%nSC&qj28*R!jj}_+d4WeK-7_>EU!wTA?WjMYu`Kmy07`Ta zy2!m&2}S4uNnK}j$DH^1xUv?;=0bDTJmhRk)(pKzc6cyzMqjX&I9a@98+!+JxrI~H z(1%om%j{@3ebP22(?i*^MPBN)AH=O{X_dh((wLca?a51@92O{wvh%EAE**+ij*DTd zZT?oAWFzUl0ds?x@T&Q@B6t-X>v5>Zw`;$+L)WQjNt?!H?a_tb6{Y1 zlvc|Lf)+;}3xFwfE5$3Kz1^$ZyVbyiBA!NRH%3m({6P>Mk%>IH@Aj~dZue$o+;KQ& z<|_92DYYZF7ty$9DMT>wrpgEj?zlEmG%VQsW)TA~rf*)PvBEqbCUk7pwnQp~tg+b} zkWiW12}nmiY|@i_de3+O2kSdc(QoUdawSbtH`^oC)=G7QP{a8hsZ$_!`b_=Ouw?79 z^qHt!sni7rE~pl8FR!ez6v}90tcJwrc!Svi%fAwWNabEqctl(?hiD4u84S)Ln>B>H zgm0Os4!9tMor_IQ8s?>ijlFy9HkVk)z9Kpj1sHn2`6w}pg>cG{T|3(Fqy;Wc#N@FE zn~8iv$T0S?df+T1&NAI+3wwZ*SvHi`LtRyvQHJYJm8*3GQdjGoC9sTlBsF^#wPChw z@68g6t|N`g=&zekp;8-_(pDY0ynKqu>ht{cSn1s4%&Z&}a0K$3#U$KOw#~Aa>r6%N zgW~By?}b;H`b$ScA#m=N?*}?GMwnwR>2()tR||PrX^R`t5e)+ihe*`#>}lE7V}pT; zjFD+mhAJ}YGwvG8CM@EyqqM*!C#*@#;y$_jvFJ9Z2%O-w8d}p~B}+z!Lz>Ci3*%T2 z(V{?Lk=TT-SW=foVJ^Si+?3$!xx5?vFc=%Lt+$C_2IrOpqkP|9f^{rj!&VX)y^1k$ zN`WF*YSyrgL>+;I2@+(EK~f|kMmAJj-*^vP2@_tLtg2&_#s8s#*emPBRDi?wEOk#S zrb((WksVK1mR8~EAd~vM!;zwc0Ln3Z4AqIlj^Ue-t0DD4Pj7FpC>LqcC;b7QcasN` zB99mgLTO;_6-c-Y%- zih3(r(-c%sA*a2#Ev6b7o5V~9AuAm@DTDKX4?p}cC$Z4;r+(ySZIof=bkj<$LlU5f z`Xm!+3@)RR=Sz`O3b)PBsC?*+vNX?8Mrr)ZwoRH(Zn1Dmi@@4Fr$UHu*DPtSs*c#S zRBJ4ttD$USnLQ$$s0N$J43DGEw8P!$dzo~Qs(or`_8fBUv9Rztk3!ER8{%wXG%Q3;|%vp`4&0 ztlsJzcAk5*6VnPL zua?{@C3a$0O`mRPN3w_tWL1V;(P1k0QO)DD4V_t(Y4VlQ8U&W4CP=QyX-PH^+%VXE z0rfx-te*^3T9AQvpyu?G9&A&pm21=3!9LFek2{S0ZzW`3zQ zrV&U3r`+XA=y`J(=uR&@80e{OlXIv`zpZrPz6L*8IfMp6h5#g6lZSk&I?Nn;5xZ_{ zgGDTxh$X*cUOg&)wuC`dJY%D28yVia>@Bvah5wRT^OZA>R6>62P>#>Y)hfiK| zNppRZ;k)1yXU{|Fu2G>_YIWxicjvod(SYn+S~cs1#f3)Yp(Dr!MCVEYn>FRydP(NB zLLZNG=MVEbfP5DMW`LH^IzkN7s#<_@muZZfaR0O-hNxn*kN7O^@y0pvpx~J|*fZo{ z%G4#b_PU_ z5incog`AG6iMc$=$0Qwbjj}PH9!Ri~TW5e)icMtA&he4eo>**-a662REf*>xj)Z+m zdV6tU9%rPjxr{(TPO-^_sEdS1h4{vV$>at|)f4Cdr9zfJ*o|x$of5)=AWVguoXF|~ z8xbeo;VDQ`RwaUdc8MF=U55xJ(U7a%&y=Dnb1o{8()b zN~*5LbLTM$wQkv zx5OZ5NV8bJ?+$*rL-5@jK?Rl?SfSBLxUH>DDM4N&k0VCG(gc`B)UcqQoQsTGMXBFX zWk9tW>sou~#5lS_>gxUq(Y>`HyoG)fw;lVfINQlUNF605 zshd1$xh7p#?~F_l9_yHQAc)}|wy{@^0rI!NF>We}tUv zWa>pI3+(3dHC>qNdfPPNFSN}gv0LI2uMLoIY_F}?&|Aj9c(HCR;V654ZoSIei|s4G z(3Y$#UtO?uriYb7e2#K3PRh7qsTqIHbMA&}b^)qb;kgOT>5Z^uA?hdcRCuT>>VyIBr2Bg~J`o3wK*2UbwEk{5D8q@vLkNZ;9berJ zSB@YmJZ5}0m?&*Ss<6WnqTb_DXx5xDF&v?H&yrO~LmsIeLD%=>#-ljPg49F4Kl!k z=>dn;?sr9GOS){t3!kI%0LHYHQa8z1!S>ZzFAgb@XtEs#OC1*BJ9Fkx6e7U@KOJ2T zbWBvkTB(}GQr)SVc%Qt^6AibQ9-(ze1CSgb7i$PtB;kEeb;4DRKu+zNWh7NAXVrLD z#Q8-$v#nT1yfv1VHa6e-c0-`Cd6%G*o2Tl8%-;AERgLkYF0nKqq+J1bJF}CzK|vro zXF&crvnCJ273N2H1|=WLZupg~XH)W#A~+#noB(owa6}x!5>dl@r57 zy(MBxg+hzyDea#Bna4_T3EgDps6T*QKf6Ze~Iwnv(60XlqT zX2r18)Dc@>c&ai50#8@gu*rkX2|IGgfWzmZm}6_AE-`pk&MlRzjiE(k;zS@mEAZw6 zTaYC3%w9eCK4cPVgfpen`V5zs+PI7e+jC+BTg7jh5Y_Ls;OLo!jp;{h`pF8;3Vor( z1$B48#6cH4c4?QHN@|t46UAf*W0iGSuvl2QzP2dt%IHMcf%DHuS`5X7RMbsuUem-h z)+C&6q^&1Tq2aDCa2FtzaehEEAxUxM>62%N{bzmhY>7$hOW^U<1qCfPHng1%=FF`S z0-!VKgF_Cyg`K}m1T=(2FSYU3X*R73Fz%Sa!qq-ZkquIjhYPK-wZ;U>l8N8V{Wd+j zn5(IBO|QHcx_KrEpR^%T)@>N&QA1qwriRca`be~c zR@JoarYQLYv#$|T(+lnLoE>&(WiWs@ev)}CKB%}oSVa`5&4p}&%EAsiSNFNb`_uj96+4YV#t+@PMD*nVSACc;W&8p zbUBh%;>K-y|rDyr9T|L`eF%De}U^-EQB_K1_#b z?nSt&?RUUTk^N$`WTbL}(NutpNZMC{1%7jEfE{gY z3e9C{$D^1!?HEB1+wU%*qatI5ZedHf9w`3IA6LsLmQaxF+i6YK?M5`RMPd+0#k*$#psvn0v%IBwh^RIwoq>E(me(?ceZeC=fB+96fw-bZ4ZEhfZZRJUSgd7BQh0&+agTFm8M_ z)L6D&CGCk;vn55wme&?m=(B0P%M3*st&1AsVV|};UKXhRs}6Yg8noA)SNGm$gbu+1 zGb#=(n;kS;&i=*j__@)skMTqa2Ag3O&bofCDRg~QA@rP_u~ot~F8N+t?R+6;%b zYD6+Js4>u|cNpcYV1u)hV`Gz#mS!%F<0#+zW-#OI$SoF~+rTGoNFZgq;ggx-*jbNF ztb@!1O1go=0O$tRu&g%Ak8-{o7k?l3AL53Xlc~f8wm^w-h)$8NEjcFW7-4JR^jTH|vwZmfvrgoH8rfxwV1nG!}m6jZL(45|l^0L?$ z@%Sqv4A#*cl5dQT7Xj;u80Ewns#rrqC95CX7p%zG#%qw2$tpxtpMtHlGt}ZXHwAY| zWX~iNIR|&=DI|2#tl&z~hRzwpBjD;n)_nX1g%>&=%^T*ZPJFQDOx|?Q4dy!!bfVh5)XOI44=c%Xq4B9(i$&u5;QC8n^3}TSr2vym-IN_ zh+ERc@Mz%D&noYFlCXf%#@0F_AXktj0{!cQNzL`6B+|lD1g?n@E$a@%+DeG%Y856< zeQEWYELJp{h3Sao{m@4FoPDo>(hvYq$lfW@h7_gf%h?~hC<~viFhM7=Dg&HZ`$GL=GnF``2-)9wQD%Vh6@p_L~O${ zDz=0{MxLb`Q}8$vxf)1#P4}`R=ULRjSH{XE5hQEI9}8eC?W^c-&vDo@2ny=O2d*xJ zCKfHcXXH8UWyzS0G4I~msD7|oF0408j83E~kv;xyaFL z0(132^~^@ptBJbPb3u~XUxq;-q7ROm-vwH!ti&ayyrnGTM1=xJ`vn}rp3r4rJlqVk z5(}QGko66z91iU~{ozH~W_Hf4A>;RgIBjx=Pdv9Ujths{gY~V3oOCuCN{%hrLoK>g zSb;NVgNm?J8b^kL$&pgYYbMlt`Hb_|)hSpG!5ogwvU9gi1#ftKoIq)(hj( z0CfXdKCT%x!~!ms)Sx88=b;=1V%q{7$#6mhr^neG8BMWzo~@k)_`kBO-htz&jL%G+ z?4a&rvz-y7-E((1xR$*c!8|%vEDcRf6_MWrW`yE^CC1p~@cT-|$1tVhqQ9sf`7TKT z=TV-{a+yfqB@5GvH)SRiLQ6waWN4)L4zRr zqQ~4=Wcvdz9S>f+Zly+WxL(1O3eRhZhEBv{b`aEVm?5;Bp^sn$tcz~aKzXF&q}z$v zmF%fzhGLmH$03U(=Xehf5i%-quT=91D*~!c%!FwMFWy;B>3-k5d@z_VRa>PJL^}!L z(lX(WF-Dd&XQX~puO=#~=}|9~N=V`}GgB(T-Wt${GSAYZtMiaGy4Zn&%S?lgc>0xT zsc;S^23w{wEdW)*yo4%lGxYd|xhITS~q!PK8Eh(#@Pno}B=GyvlZQ~mAmFs-zxgf)= zR+;%>_LQY0p2Gzwct2v+2AZ~nZaip6{Me}z3n;98hR2FS6X&N&v*(a+jtSf_1@!ju z2HNbhGov3UvKY?3*aj_7qqjTs-g^|1O=7RrXn`4xNO54KQS*clkjh9a$d;_9a~Q(D@4PAUo`|HH;R!j|lgAU~t~ zS`)_Bx!QVV#BMDV5cR`6f@U|K?k52!JLayI%Z6?u2SDq&j(V+ztKg*Iqna0d8c*o@ z(WYegIIIvx_e>!+gqE8Y18bBKGp=Dl@(O2yQbBW_X(!eQfdI>gF5MLv-I4uBzYH({ zxCbAM(33pCHaHxK7jS~4D+-4^Yy;2D$%;q?!%r-6=4n z-Owi)pQH=z(7duI4<#%ytH^!JD>ux>vqZiP1Q(H(2Z3;xtM%Fj!=?f_C(XJNyMAV& zS#50OaUqRfQH=#;dzALsnt-#$txTVxSp4TM;P7448_i)vX-gUsPHRSZ?5xDtCE5On zmE?vA0zT5-rHB}y2xD}>xy>9Xn69Aw@FkqBh2ib{Q$eq6fW(BC&mxi@MM_*30>@|z zZ7l^K=_U6gLNRe26#^j4Fw#TwFbz}`2W)Uq`}AJ2&_hLHHK8bnZ0@8{O{3eGIK`{b z0Zf4#;n;BbFWFxuJ-7NV_GSu_mBXzk2aEl*`cET*LN7e~hlwaGi>WKTct!Hfun6z_ zwqcO-R4_=bJc%8DN`|2 zRaaniRbnLKtJX^eA*RYKRKwQ7nrj*GwR_P1?x7v&j<7k>N{Gc!RBoWU?lve}!gaCd zo_N{)@mS-65t){|VQR;D`{zjNVZ@Ra79S=7PL^-u}V2=dvzhz9EQP)5#=`2%3kdJM!~gjLw}))pTmCkHUn#hsQA z2+obpgMe^*O$iNPvVI3gZeq)D5wRCq*#>F$1i45kW;SagHi^e#(Y7s9YX5e&6m0F# z5(t%RCNm_C#z;pUGZ;QJc~(5$5u|jQaVnwJhYSRGLdPDkiST2nD`*i+k^0`Mh04;d zpxt{AIb0Cz?hFj;%6#mC6c!N4yU``WZCqX5!Fy^=3cN(yGFqffa(!+GvcvWvqnLe- z4uvjH|2RRGzjIC?b8U-}uocc2Ax%{Bn*m{b!KE)$w516?V^M;2+R{pRyb%^~EmJk1 zi^kTmMfsHGjyWt4s>Db=GnNJ^S!8=iiP7jIwO8#DkyZ`+gv`+^f$gyM1zbU4(X}`2 zxbM`$s&eR~2pYJY5(jfP>$S^B-GS?MLQOyfRfRDLB=QA_T3~$)kE6a}TlUlqA89Yn zWR3m1m7Y3uk()2GgVbd>E^AVdW3&ERASa4(1JW|CdxH0c#K%QUqVy0uCj`(ey0QL{ z^OOM{)}c0T#Wq$0SVB*MPRa$48+G2hunP&g@C%{|L(x_x&lpi)>2j?(Qfp30Br2Gc zD|7rHZwM0@x?t&Wp`hbM=sb}iXNhE`V4suoWegPp2GB%^3-Zy!TDFcZied$xZZp}X zqtAg}B(Bk6YAlIphq}=to}Kb7R--`uj>q;Yq(#JG!n`qDnd!N#YOei=VGtUsW-Kw> zBDunYpomUg6keEF3BUKQ)pv%%#$t4=h7}IHS-7aPwqZxx;>ef`V#0``)4KS<0vI}T zld8=V$+)IWQRN8asNj^Gf|E04;1u{#u%NR~kL&uD1i+%tK8;hBC|XlSLHkMYW_UE#h4!>LgD6W$!^d`Q6e=pbutFw!T}8$+CG#&6 zl(g#ZS8#*OY1$((zpAe;R^K%T84N>9Rbb2%@68z6rEXWrvQP$xJXxzcADYh%jv26m zK`1201kEdXI&g$+Mq|N^e+iNpss-TyQ3?EHm4MZA_L=RZ;72VHx;|Wstji@-h=8yc zRF#i`Xa1$h5X3gpmW&2c23uc(UDCN`ux{;z#Lvm_(4DJ0zaAb`CDuzCee5BI*gN(W zOJ+0M^P5#9UO7iX3}Op9IQ=06Gv89gFvQ2{E3#nZCxaKNjktbrTof93wdE;}s#|lp zB4Cd+bUZ%o(7G7~4Pda6{Foj_#YW4h;G`J8@JvvM9Lr}!$zi19Tiwd;cAmE1IAY$O zIyx;8;82km+O%0UUICqn?RKN{8UW$Ti=9FV)X9rI3eSh)c6TuaS}4lY!aCvkd8vfy z#YI|1cJ}ES=n=Kc1NWi{-STRR9h=Ll*A?{2`DUR*Ne<;lYl8SG;wFfhc`w=<>SA^| zMkYO|&t7maJ@poC&5m@0yInj>JWxa@KOM@1S4t!OcaJ@(rb4X53j79iH7=w zqr>V^)G;o83_*x?f1;QDu9qIiM(q}1*jQ+ki~hAcBbGz;FL+cxK<%*O9`8veb3O67 zzKB~Un>eSe>CN$w2@9BxG7{55Q8QeP8Ys}L;j&aF0kMT~pdV?SP*2sUooEsz^|kGa z>w|x{90PDEynDN5jVTNyR1Vv(hz!>GKtt9TK&MXu**0xXKpqbt=$<%?PfXJ%PjVGw zCcIQEwd3|6CF;-+@#Z@T+;+h1xueip(F2$yEXWDKEgl4 zjJB(9A+aDf?-z8zZ7<`6B`uHJP3PK~@ygyJs!`!C`dSCVoJ>_@&LO`oEp&Qaal=vh~%Hz)xp>|=hpx*%UMQR_g1i9D}|cY2Iw zf;r?RXNdjrlLZ6@DBcwej~h;+A0>kwUrPv%a2x`N;UHLY*m}2@AQozHGbD*}uAQPC zNNh_%#$jcr@pQ7&xcv%iLjIc~$$x>kx&gV6-B{%d+g+-eCHm0vr}6{gn4>!|=^{LY zRgacCnJ7r@;C0oOJP|K=BBNXa;)x812NETjQYhNCXWMa9FfXT+u)5wU@F-IxxOX79 zT&tFi+Va+Vjz_q>J4G#gg8!V7dp}OdzhEXj0p_~u3%#hB@!4Ve6|;?Fg0dj))Dq(1 zbmJG%*7hmo-F~LT-j*rT-f266OpD>fiE~|#R}Iu&mfOdb?wk;7VUoU z^dbE1xW5W|i$LO!0K@q( zy3Nj*P*rYG6Dy-L;!~LCY+oL*hgKF5{q&Tb37@LQi}W2i-WDmc9xuUrUvO$AS||lh z@BogjzhE0$T0#z)5xG3b6>zdP ze;!VLBqwWD$`=;awh)K4gsgc8>cot6Q_}AM_&B%|4imNJSzcf7N`;7I#b4YcB4|GA z+*fRMD-%wrhO&=(#p}ft*bX&apXam5u>*)vlcj5Z7#T8Z>j=oj1N(5dbveIIGv@9l zt23+OEz}1Y*0##wv8$WIg0aZ-zG5c3c>oM%zPZ|C3m>|+SGx1?(k;+!rdDtAJbKt{ zIHBq%dlfqQ)W6Vv-c}h}S%JBT;Ya!fTo1Y-@E~8t>Xb!tF#4W}$?0P0!tjj5M9;to z>4jrwYZ19Ck!_sE6HSqk$yrwf+iYn?2t`XX_1;*K(4fXcKass{)^i=nxlWX$kKQgi zI$)4Z@VsaIc}2M?{&-m=BT-rsb|#)SFffUS%e6Il<(N~9ugq??L&w}B$uXg?Bw3(= zHz|)b2pKOhO*vpVW0Gi|CT`+k)rf!M1tozu$N_MiYQq_?=#)(>Kb7`e(LTR{30f|} zfdXS)LlxH(dyMq*QUhRstYU;LO_w*gqAQud!xv`cYZ)1`nEoc;%~V-k^ee9GckGkb zn#gM*FBd-~KD+LfYE!gq!4x7|9^Y7%sat|r1FWE&5>H-r29C~usC@8C2Q<3rBr{m)lxJi_f z{7bE#iVa^-Wv9?ZUfnhf(5tB|B+R=EJnY+>L?zBCoYk?V#<@LRly%ytDD#si%JL#j zl#}X4YKppTt!?7S8M8%_KMHzAT0?UtWuVMUk3O^@Q>s(Ul==(<<}xQvx9J_J^X`+u54Wha_pZRg{Quc|*C#8hYhC<1 ze?@EfZNLBmUcZT=T@0_Z1DcxhdW8TLz=6vUzW4slu zFuJM%;EbW}SgLA*%lE;rRrg||k?UT25G?IHy1=chZh+Xs+Yt}O*qn-$w`u8^hXl>4 zPJGSgRj-YjR<5*s%ot$4Nk*wQ7R?4h%5SV;wEP;=_`*t3*HH+z;ZKukL-@{(7RrzJ z+>7Z8tFhWs3zqB?8)vUx+q!XmvqjPHKTuz(z1H;cxZj^)Of;yN0H#c6Q? zt1cQtflD5IpkdSkhubdiL*LJ8UdbbZ;r%s+qU&eRZdkmyVb1a;^X89ShicGPzVa0t zUHQZ`(fz7MvC6e9v$xJX-fd^*uV~!td+Qh6bFH?G41l{g&9|E;_)@|3_wW3LbLP#R zH>NsNce1~?kBUMU@ul`Pp*Sr6{8nEa!OEBYYYbm?o<18^z8qIrbZJCES8F9PDb2W} z!eUtIW?H!nfSt1Y-)mR@Yx7FIpYxVujdvKlz4H~uKL+U=`W_nHvf`;hMOHMu7PFlP z)+ths6~dw?;Zk0@*ERmp)oUNNw%`{Z+j6CGr3Kbr)4X!xgnRB^c=z(V7uC;3tPczJ znQ1dq_AxK}W;L@!tf)5e+sClgtUS@hx6)Vir@-9)dj?Jg8+JOME+Q8!En zF(2jwW^0pwXJSG1Ms+@6#;34&PH27Q!gVXx+GdTdUu`$rzHP_DLUtr3D?40Q&lzik zr7xa%oXFw`j=r+{!7}^W1%evD2LH5W^=daA4-H%L*)5eYiqv@ww_h!c8>T+6s00JB z)Oy%r(=)Du4LcXUf9<0!D;na|VB!QgA2EKjec5hxoo(UNt$oC<+*ssvJB#;dyY~0v z%^Oy&wG(hZxb#S>+(|>=d^<0m%bQveya6??{0`7}u5teUg2BvFw>D$fY2KPSF^uub zjE=z7N5di4!4k(bZ3l@;U1{{Wz>GdJ?dRu9qSqT2S54Hmv{rZG*qx|?Rwtq=?@9bx zL*%AyrFh?noVYGZ`C1itmR`^B#>J$`92AwZU<^_gQ|{7eF{8*LezSqaUGRx%v&V)P zCogUrY$+Et9kJJ~zkx=J_RB_8*DzWr7bm}}9L!WptG6fre{9HnDz<;C4ZC`u4aFxr z)4*UJVI!jtc%!Y!u6P*IhVS(k-+cnazLt*k!__NW^N^`o#H{p8IPjguHQxUsi{!SvFD zBO_n6Cz0Okc}Lw`^(wYG@>BDN_3V5_=h~d>QL%|5wq0u;;oTIVt|}>=MAWQDep<60 z(L|zs!Vf0?;| zOVzsMyYf_`73mHSv?(ruFqF7o6{m$+7&o8UOZf(6u?p2`bW?az-4AgHS#hz$E)3co zl0x-IN?VlT#IR{@krthmQiZ7d` zf6?va^}qDRR{Yk&Xb zR$AHAdhs#nd#wc!)-_ByeIwYQFqGKQ^waF{r1NthmopDtFE7`UXjj z4v1ok6-tFv*NMjmqZYl`&Ks^Z!rt?qS9J+~J~>)HyaC)?u)$Y&x8~=G$M=uHi4?$)RK=Kp5=8OM43DOFasMA81OALOER@F zZWcVad~w4A^^5B5l0g{h%KPRMTU%GnXq-A}R_m-sHmo!6${*MT4c3HXiR{BgF)u5l z`0Ke|Xj6s%_+6)a%wLH;Y|B6XXg%zsoj*XbVeO*kN1O9^IqW8hr?Y`+29V7N6gyAr zR$r(+ys>5VN>4w&R>H}O@5nA4y#n)bq1`SFE#NIId{N%4;F{JnhPAT)E9!*>edWTs zx?>7$i-mQqI#>`Ei_3`kBDOtaS4E3;s@W%|Rn(LeDf@R5XZ`qwM*ErcgJr*HT+_Jz zp__h(%RJDRS|wVwGv97u@iG*%bPwVQziV9IxMtRG8`mJ4ELt2;Qx@zGT3vh~&Ec(i zi8O)Vyv|G7R0S^z`yr5~7UPdm+Ow>#al=DD9G_albXw9(xWVt_hdV2t>D!6LtScWQ z?ELtaR_cg-=FE!Li&?y4RzA#cX_%ZNwn@~U6Lul9Cu|ok4}MvM5QolfP8$Pb$k>}ji^YA%E*Vif*+qIFhz(^cr~@8@GxtM zjiQ-ClP8+~_r|q$4X9n`)8RcwfL!S8T6y)pwKd^xszA8uOl*hE7newIzNp~W9J zOqw-mqG5hn<3kVKGzpx>hX9uJj_+5Kk^@Z|#5`e5SZ3d}56v!Kn6I1g3w^+TYE=`a zSP2vGkFAfB6VcQO4?)9>$vzmVE!G69yQ<%K)1=?bnEd3#n`cd$^h1K&pBY|mzIoDA zm^J%7>4)Q|Pnw0|u9Y)8z-`~MU)=oAO=C_oi-jXiF-dH(*upF8LWBvmnnM1EH&420 z!lZG)uZ{e;sTO_E#v2}J_GM_?7phV&TLa*5#!SM@@wR249>(I%i~ye*^>!B zu#PgL$dI9TZ!)K~u)gD)TURu#tGMiIc69c}^{ed;r%Bm`-UJLpI^dw7N<*IK82c@$zjBRisl;=A(9kNgsVPP3k$J{ zv;NtTIQ1bn#g)xWi$yC}(^t*HDm&B4lP?es)vTVjQ-fd*sK-q=$s;X4vgqT{APPbb zo=fHj?Pq$PgZUHT%^+1NXCO}_bOx8Ii=KBh3gno)RHE1U2iR$>E4a1C081!$e=pRqWX$7hYwFC=`2fQMFFnbUoovVzbKdoPT@{tF;r79os5j z{%v-NQw}q)*agGv;qj=LZ}pK*(XAmJcbL{#N*u^f_A9+Sqe5nwzVdrblzKr}1R>0D?iPO*^&8$fzQU&obRj<`MAqygy}u*{ z%$pmb&DXP1N~3FayfMF z2$U&(aXK`MCt28w^wZ&*+JJSHeK*#&V9}_Gl=J;oUGZ3Zcuzndy}!5t;TB&Ei}+&> z?XHd}p=JfXe{~GCN&ziKZ~`5RW~EaSE}A{(QPnCPeT4~*-$g6P@6Q&qo@}=P?31){ zJYqvpJkTsCVIfgESHYrA26oC_wm6-yGrx(&JbaCm3YcXvD{bmbCBo$VEv=o5kPID; z_SdW_oT>P&A)Y4LjhwrZMU3WtpA(^5o6*naMpBImPt{>VU7RjsL<+8xEq2|(Onj^D zlNGo+`72(7Oml8L)84C%5q?J1zgp_XlYMS=haX=-qoSgKfBbD4YT#Z=Xd7Hrg%R%&)_`QCJ~=y{CzqNkjYJ~Y4^FX zlUQ`F`X`18+aHw~;c}Fhnbu02Y`3xLA~W2Ff#A`>rW-qrx{l)EZY!+d_H zvTZ~w${uegL9B_52(G^2j3REJ<}xc%S;Qg16va&0(UHEJP^&WovV5C_B^FT2K0X?82qCjzNstjCHOs9`_lp*YNwI0#_@n zO6oo2hu&Ade@l~+mXmq36Ks8Rz|XpdcYy_UlULNRXGzz2(Sw^3dDYs|ijk^maMJ!j zp-hcQRcX$>MmZzfHQ_YR3fB?NG*6|(E&j=hh$(K^MUbS6^X&>(%evLg516~Zb%ssD zk(ucLf5vQq3U8~vu6HBDLyAS%o?q9B{*T#s%~~EA>j{i_Raf}C#>nryoPvsj$xg;5 z;SEWZrrZU~=aC(1jx!=?dj~=5a&9nA&bRBPu{9;hLS55qa#TJnYb$)Y*4cUT#K<43 z1n*J%*FIi?Xu8cS^I1{#LB35fz~OK^ZavAJMI2;chY@>PcD13nJ~6H@fILj`^_^~v z`JEj{0kS#Pw5)lb+kt((>K2FQ`yUyLV(GnG_oFOe&3MhPa>8h6#)~HemqlMpw}gTA$?|)9Q{Yjz@xL?&Gv!N}H>d zkX<#@8=VRYKE^G-YE>lR%ho!7D=WO1P)6-=KMNm@q}q{$qnz`uDtLt4EBoQv`Hyui zf)UMwk#yS*mBU2aVDq@tf1_Ut9pg=`tQ=dg9&;Lc2NMykFZ*YrA4=N7UfI=}E-(`m zUZIK|3-R1_UV8EQ^_HDi7%`{h$tyS^LlD&NG!B2d)n!d2{G0x@sfF9^^WDXkOZEvZ zkGDRtv3|}{J2GKwxx@66hDG?Q@A7$buyb?gv>DUPInvgfHL}35<{F&Vc=AFYPq_IP z6Q};>mdQ`fnq(I!YNWOM5I2qIIipq4#^Q0lRoal?a~JvU$>a9XXx&<}w+Xv9FzlIq ztVOkt#0Vj5l7(R{zUEE1dD%^~md$KhZE+@NK2(PjqlZ)vE7}(UX?T|{oA4XFJ3a~D z&1H7pW`9rezn9_fhx~7kB0S-thdB1>yodLZsYPlQYL=gxpC8w|Y*p1G|I9|!f{SOd z;uUlQ%8(3V4Il;jUEQhnZ07g5kTsiCbwxH=-jR(naEzbpS;2BCh}8k+Fb83J85v;@ zL280e`f&TS=3P^0I#!v=+WIV?ju-Y3Ld5lDeT9%QzCS_CRy*yy#}aaEj^QN_j0Hud3v)O6EU{wiXWrCT7=rf2&^6kqe{ zd=poRYPqgh-n0QQ@BXGm7aP0N;%~&Wmo8e=U>;QUis~sB zqgXoD7$ji4Rv)B9`1A`#1A@rbUkGuB2e*-G0iAEUc9x}1^L|;nVBXKzTZ?@*a=TJ< zTx;d&Yg|{~G266cwWzEZ$^4xS=~$(i6O(iDXaeX9Z-6b|7rVClIvj<>mBvC2!*ayk zhmpDfZ5>}o%!?AKxVh=o&6JRPB9rf$iv%5sTG^KR+PF=3(6G1ex#7h3TPk6(&{62Ovc=A74vT5nS@vM`!Y8FayFXDX#aSN)XVa0Feew)z4{pofvNGjGQG{K z%}tNnu>6aCT40SClGvZHK7)<0n$zlFs=^tD%P+U5%PmSs({j7z(8R4g%fR7pG%Z;#d;x*2OWfZ9b$o{&e`dBaYRL2+uB>cx0 zV9L5Kq!9k@0nbb8 zrOMrIbUvo<^lmDfROkx6eU9b!4ho>=OZy6%77CeF^sm#sN(HJ4Msw}0(BipUiZHiu zL4$P^AT@j$o3{+t3Xbd57&}&W|qU0YX4Oo zK^7aC0wDfZH+GGA9Q_x!*8zq7tH0Ht!hmA1T3YAe`UyA9Y)opL4o`8dEDnw*bc>r) zGNWM%!!<n+CU^{z70 z{~ZRNlQOWQQB#YMXxDhFK;-yF_(UXd_I=>_x@GAl5yJ zdtHzT#M-(!zw?aZ1Ast?i!B|N+i6E|L(^mC57$%|jsbYc(0_IQH8psgZqTBCX4^8&{x{C@>izs@?IPDNN-IZH!yY{tjwap(R-Awm;E!hR^E6& z2mI%>)3$uW+U4ZMdrM5kleuCv)!-y+cYit0(R7|!e*0A z1ip|;+~kppIiq!UlYIuzmL~DalEi!u0dW*oRSJ!|919)EE2;dcVO57)M?RF|VBzJA z6n%qluiQ+t20#zuoxAu3l~!v$j@1=wlK09?Mp&?yZ@zHk!qU#9WzR=;QD-#@&LUOy zdBl3GY5|41X^x4DMJ*AU#9av@S5;C=+`=*NJP%}&&NNXqc zZ5>$jIxY(MMI0ig$fnOGHt!9Xo;Mvu1zWFD9(<4A7q#+F(#6y|wj?Y_yWBYC)H~U* z_dOqItc3hLWX{@Ot@)KX7+1F%o*XWlMA1gBrBtIl4%LE?jF4wAOx`j1Qm)m{_D`)- zD@(QYx$zi?pgKoHQG+g^{Jg=sJEB_TRw2uCjT{^bdtmrPH%_fuw_&w01pq8l70L-_s>I|VguD!L=VFgrKw@oNR zQTw36RZmgB;TcICrt#Rw`eNPMR(w4GA1zIlOfzKI>ro4`F7lf)Rk;*r)~X4*QqnST z%UEk1hr=ZXaGlv{8@O39a!|-dFX*x6P060b@0W!6U0;zwwp^BtyC)1_;of3J5*0aVat+b> zBZPvgS!v6`2R2qd$%s@={#ATTq#ch@rlLVG1{U?Yd3tTOM7t zp>E=eNp*Hx>DDPTrp=g9H@E5GmgY5e57<4vH7i=0*N+?bK=b;?E#3ph+JYl7Ak4OT6-(lRA-SxTP?xbhw+s=*v2ON65+Z{<1FAY zrcryBcw~dkzs)AR(zK*??TQxL@wQJluU%nZ#IGD|6gS#&jTN%e zR#oHdr3BC7Q5}M#tlfyyb#&wk#7jVOc0=np%hWV@GV3|H&Mwxs zJhI7t$nPRHPElLaPjL~9^QygGTLNrpIB^OgH+azo9&R_cJ5Z&oVE@&o^8#w zqYSEUePhu})I|o6wROK*Ww$^pT4G)Fh((HTX$i@i(W8UxC!Zst@g|K5hW#S7;y`Dy*4hPru+>mFFN@F(-;G|Z{Hv3{}9H%_km z>AWTPE?l~#&LkGqFIe(m-NLzb^$Q-XyKmltIg{%ee)hm3``$<0!bRie&41v2`!L1i zx_Jv`-@kOuyao5v-EHX>EL>7||GfG0mRP1s7S^FaWo6&iLuT_E7GYOqN&VgP?w_~h z!O7$1&RenodCpz9sII=wV&pBEH+$**^^58rSi0zeg^L@k_&JvCf_V$(F0xV@=G!fh zsaC4->KcAxMBU+ioGMUAUpYPDa4`qj-|_`rjU=G}AelDc~r-ap6g0M^~zU^T11 z`~C(m${IEM{`z_IC)drXpI?6uYPYCvq2;n@9K^hKbw9ng0W6ePZ~x6+Vpj>#jM)np zEU|AdESYSrT(l%5{pq~L4U_BY7tLFY4w<`X;rz+t(4CgTa<@z@-2(g6AaX%hvxCxz zmVw<|oXo*jEIK8`5U&!bsW% zN?t7&HtvgTEOVlO>UXo^Yn+=>Uryz>#bQpixxCtdI@S8RJX~dy+wsyso@1zS?ri|Lc!O_E!C|?r7%O|`A^;EHeh5iN#Lrx_dQm= z=rBm3)&Blu?TMr~n2QMqgZMIGIwzbuZ?Y-FDFC1>$N1)f+8n2>OY!p_Se0%XtDdl>t)tTyC zG07{b&-2#??*nUBZ?eX$Zk&xS31n6781az}XZn#oRBv#uKo-k+N!a$@4D7xuV}Dp@ z`k|K94iPDr+jTwc(_z`L+zw}#GpQ$zLqby-U%JSCakNLa>ph`T**Rl zpsQ{z|Dt}%&zq+FdfJp9`F&;9iv2a##;jXqw;BGf8gqRuS+T}88UAis()h%*X;T`X znEpuP6L&mveGOXKVr%!mn+9FChS^ejorU!KJJGNkru^>uM#Jv%nT7p(X@)`m`kG-& zku`rmN>z8gqp@z|niYRP&A4Go$D{R~1?*u4~t=Feu zb^-kF2q!i>USBhw`1`WH^;70HO?hNA8;t2b-ZO@+TSi}$m_@+eGazLP+;APZ0m~5@ z3;U;Hr_N}uFtDbxUGxnWE_BND8F%7ZGReE@X54lgmUh~}(HV_>Q@xn7rd!!`cPslB z+k<%Y(PlfH!AjEB%&Obe30QvD$LEN!17RyNdy(H@n;-FZZ-=M0$&TF?+n&>Fl6F!a z*=XOVYc?OP#)jqgez<7)oCbRppIvY7*<7XiR|fKmJEu)uw`yHJwZ0+jX`IGPD7@KD zpsf#yy22B!O>752MXvLg>X|c#3)!}U*-KP?q#+SpIg%w{L zM=Y!;bnU{pbf{TaaZxd1VMU>97sl7CnuQf#yhbdnD0J<@_!3mJu;S~{h=mn}u3Z>k zRcaPie2E#cu%gfy3d0Jcc438Ajgbp02wl4{uJmgbR(uB-v9O}hwF~2dyk=p=rTU14 z6@{){m@k`amR7N%9jk8f-ib7Zq)F8(zu==*w)w>)7hM}G_RYeXZ~wYU*@ z?XGEBJ-WU7P6dCTw(mO>{QcU&?@;jfYZJdi!QZca{0;?wzqax_6#V_#&F@n1_i97G zL&4v#J^c;^f8VzCI}|+zJBuS8c1_*lO^-jacFm^7v_74;X2a^nd$6(F%+tz7`>d#a z?r7qK#ShM(yKupS%kNoK|G>R=$h_Q6wHGh37wttqv$yVv6CYWceK`yryuOuc1>T?rrY`SfX1Z}|~F5YzZGd@CR-Hgjf;YT&$O_)qcva3tq} zkWPZ^Sf_PEU5)JQ;0K?j!MDGDYrmJ-j%MQ%zp?u!v!C?ebDn(Yx61~1bWa)FdHkUt z+MnAuPZ@mb4-ef0=_^MzUpaE<%8}kH9~`*y!I3K;?!EG1_mz*fU-{_pm5=(a96fmD z=+P@5@4NDG&y`Qwu6)vd<=Eyc#|~XN)_bM*z?I%3SB~$!a=iP>iS1WT9KLd*@5;%8 zS56+i(zowQU(c2Pwk!SZS59rda_Z2PQ@vMCAGmV*NNMxl(&p~cmhGi2hf7=fN?Q+> zwjM2Q+gIAwQ`+8E+TLDzYIEtSL#3yBOFIsfb{r|~+*{h&U25B2YCBwN>nrU#SlV^8 zw0mD^cTZ_gTWL>wY47IJ-b1Cmy``rQl%76PdS-9wneNiF+e^A-=~fg`0C_Lg4gF1@(D^y1;ti+!b+4whazS~|F| zbg-xNa$D);_R=exORpR%z0zBH^+4&>Bc(%oONY8kuWc{AcDVFfU+MP;OTRx_`oq4` zA9_lM+e(MqORsM(y?&_ldT;5C1En{Pl-}H1db7LKzP;3bxYXWP>Nr^HI9lr5SL*C3 zb+whc+DqM=OWlV`-Myu^4wT+HQhIxD>Fw^)JKIa|94@`nSL!)f>N#3^cVFqf2j0+Z|TT^(vc&j5B8Qm=q`P@z4YPX(uaMej}DeTI$AoquXMDh^l@A1 z`1A1Z>hJtbbNd1_~Fv=zS4<C-;?3_LTbCN`396 z{>`QSL#6)S(y0TbQ%6dt_m)m~mp5-OZ$4b!+*jUmu)O7HdF#IN)}HdVw(_?2^7hT; z?T59M|XMW_VUid<(+-ywu9xiqvc)u%DZ~XyW7gU+sk`4m-ieh z@98b?Jy70zr2O>W^3&brXSSE0Ib43Gul(%6^0PNRDQX) z{K|pyD@V$&?k&ICT|TtEeCTlbP+$4AgXPzbmVdvm{QI8rAKJ=)XfGe$Tt0lLe7Lv# z`hoK6N6K&PEx*xSesg>I&BNt4`^xPH%k4+Y9s9~1J>|}}a%X$FYje5lP`RtO+-tt@B<+rz&-#%P^yRZDt!SXvt%RT$bJw4@j+sf~@m*3l5e(zBEz25Ts2g>gs zDIeKeKGI$OV0-z4!{rb9${!vqe|WU~(Z2FWJ>{cq<)iK8k2jY;K2-j=xBSV0@+U{i z$M%+wb(ed$mwOMFd;7}A50;M~EuYv|KG9P?*;YQ;Uhdmm?mJZO>n-;mDEA*JpW0hK z)m=Wly?pv``E=jV=7U33qXm{_>o&!UBjtuSHJG8fZ z=;`f4Paht7x^L*2gG0|89eQ@((6c>5``U)~wGZvzJhcDN(Ei?`=MD@#cVy`Ky+hA; z4;|P(bl~vNfxe*^4i3F=bm+x>LofCWz0@}JQv1-s%|izd4IS(qdilW6%SVP@**o+~ z_t2}`hh9BA^lIPGp@Tz*jt;%HZ|Jq2q2ISXG;7)5Q-`Mv?sx-s%uLsmq2Isx(4=JC zZpy~Zp8uk8-EU^EcoP3Uf`5O$n*XkQ(oz^Xe+|g_&9IWwesp((|DJOPf8TwJ(|6w{ zTF>v+^Vzd!P;T~|+w}W({+^?BbChn*?77aL!`~HF*~*@HZrbe|!G>?t=W?1^K%r-!Q%LiM#79ue&`B=?3F9 z-1yLBtJ9f2)TtH!oH>qvzuLyXKjgo?sMEPvGaL_LZ}E|#&~ z0u3MM>Qe_Uoo#(!n@kMLXfYG)m;qkqxLb^Ud%M`3rqqU?Em^dD_QItL>@rxyY^$3# z*`jQ$@%t6i;$UK0-CdHv8AFmyP6ifW?S~Rb#}v33Y@bV8-DKPLt=!;3WRwQogVDA6 z(Js`aTQrf37iog9mhiqm@6tqt!xw_BMg%)sdW?8OhG!{}9okhZ^?3M1;9G9VON$&9 zh^AJuYvq9OJRV12B2F)SIB=KCheJ$I#*n**x6uFLFjYRx^A^yCz){b#0gVJ zS*4qw^Ti~adr9Z z`UjRQU4;8=z6YGzY#|H_wa$ZPpR8ka2D@a4%T#F2IJ*?=qhF;KUF1)L8?sWXqaM#$TrI9qGTU_XhBT-0+ui|CenhtUAUl?<;)B&T{3sdoj2b+ z@16w<7c~@y*>kqH7e7Ztvk*Cp**jL{`I5Q@Uue^Z6^PNhEGb5iLuzM`sfv~t9!NRR zW61EDW;oRxhLkE%_`=`lIzg4-r;$w`rkEm(w>~J;K{{#7;+3^ld#R&Wm$~!Wqp?*| z4rIw_dRe9t$SJ0P>??+Pn3f=albJF7);n&!^OoChy~7JLf3D?>!fKcHR-5i$L-pBA zS9$h)tLNIdVy(r#vxx(U9o;Tg!dDj5#?HQ}>mA?-E>2+7)vDv2A6tag%F~?!Qo<}- zs1rY7Q8A`#ET(|3Ds$zff0i5bA#oP`LzY*ewM(IAtL^7H5yigtsP znyci}*)i7(={dI6V^(5esG|Y_gYRoBahfR!Mcj;5w*wfex@15Qqft#S3FfoEpY9ai*NaxlnXT(MfufSIdFlKi?Q$Y z!Ddw6l8$VA<*$KCwvRSOu*RH=>4b6LEP~+7nRyTjGm}e0i&D6sFbqHT9FXY@yr}pW zOe{;4V79Z$TG$wwbu=xr{Ki}`@~2%^RZwY_z#v%hGEgOlyG$b#?*ruTMx!_1-pW)K zYFtrjM31QC!rIu6eKNJRd7inGnIi>0<{ z=vtH3=JU#l6XxG-3s1d@FJBDbDqE4+T-R9Rf$b9~_WcWg+OSBSZ}^Qn{3zDJkCKXD z$qGJ9JX!ndG_IG|sBBigg43f9SZbNALd>;?%9(ixi0&HX6|jCWy-I$Q)#4*&AuYfJ zp~Z!Ug*Ef14@6kR>ggc?0@_$5TQ!qGG%aBXMStnbsb-C!sD;;BC}K)rnrpB9(#Dw) zDFMwG<_tRhwtw8t`-d~=Kb%4T;S72$XV98=O)5@5i?{S_qtcxOm)kMtO8bh`1lwd= z?Uzk_)j_L=Ic=mjAedP#P`=BBK0kl80xP$lxKrZg#|dpMfp{fB#cmiOtJpsoEw>~y znPFXY|8L|BwKaw|*REdLKAT*pmj+fbE+?kzi)oRjt>&uxJ=KpV+UD;jTjj2J43nm9 z@7OQxfMQBXFW42k&&6v%_vq-muslqg+_kfgZ0$Gpa}ecB6YTJdN~yXwgX0Bgq}J$h zMQeOIv{Lf2%(^#U*sM|WF=R9L>RM)_Ro5QZu5Qh@sxef2Oc{>3`nC=qrS@EbUl>QX!7(A{y`c>5IML+PUpVx%D{59@I~Mgi{4FNyTAdXA7AC#AAMB*k zsx_uoBP-bWnyvP7ta(vTMQg_hZ!5s^84=(9c;c9v>4C;;PW|6$sL?L_f2@s0vDOL2 za>v~EYp)Pe%hlV3vHLC0twvW{ONFIF9zSLC{VgI=1v4HJD-`3e)4;#A0*-C*ks9?k zYvz=0hNi@Z%)RtQc9!icA#>S z461VN^xbTyucUIjqMVAkA+673DF3mg`j7Z}5jF82@%8=@U+=GquUF|-vS97{$D3B$ z%3LAXanxr&9&9f^#2u#Mhkh*Z;r!*efZLd5X3li3h1we?dS7kX`{vXVo?SEF*|kLP zt1Ww9weW(u!zy5`svH;09bQfFhKUU;biv%=1rD#8F$6W^0vC&VRgREc(>T!x8QF}cdPxr6?vH4t*Ddze;e9kdEbh3 zr~_o}_YCl@Znq*&@a_3+M$bU`R;Syn{Au`YdCV}GJ1pPZ?Ee{7pIg!Q_WvE0Zkqj` zX6dF`U8Y$%)9m>)%VV15Kh4UTX8BID^wW@^wPTv)G0oCVw{+7j-E>Pg-O^3Bbki-} zbW1ng(oMH?)2;mJR{nHLKi$$#xAfC3{q(svLU$e6)pu4}T9X-AskM#mL&Rrv&BU1g z_@OvW#O%0PXi;z0#Md?Kp3mdk zGY*$DG?HfX?4~J9p8-C)5@`D%93g+e!t_o0E#jBv%OAe{TO5FfpGVLlDz`sL}* zittN6n32XIF3mStAZ-7vP9#yJYg3=XEA#r)JHeiQF$8)YjV2e>k2vkzB!Oi zL;|oyT%ChYa!%k!4mY$s++r8qH_d4Vsf(C}qN{T)W{Mfv^UrxUty#OK#XLub<)@0Q zfWB2HTKo9J3=2McwZ;CfE}YH{k3Zb7W`)Jzw(U~8B{!qa?m+nqm!(78)rk`x$Coth z`Ba>CF1Kjc>v5=z*W_7scI(JC1e@)f2&?S_CZ3Od0&S*WlD1x|DKO#Ahg&Sh1>rj$ z@n5~#E7eoF>vW$9__eFq=x>Zib)WHp+Fz^r(Py)VjXqO&i

K^xi*PIV>`G@1-J6HVyq|BB9e9+3{vx()}?;JvIxwFIjWhB zHP86MB1hQ-$9mJQwzV44Ey;L`7r)wm)4zc}54=@wF3>lclb9`eX4Or%VNBUA^T+>$ z$F?G}#gJ~gCuacM8Z>58>xQKklnk|sqY*pRx7YR-d#=Z;ajj3&P-@4rl-7I?W+Bj- zm2X$etZET40U!t~oGW2N>Rpw`5 zhUHXf*!UI``z2atvev7=lumm$!_Oi*BsHFW<8@Cwc8IkVo)`8Z-{SXTI09C`ijgq? z48v(2ijBj1oE5{!V^rA4sEBtMT+hr7esBYFQjxy?)RRa2+6=JH3a{jSz)ke;Y z^Op#gZN*g%mvx-rGb}qBs(fiSWrLo!sxlX<3I-943h~gs@1SG>yHWH(C1h_O>$3Z8 z*cnhl%j&QH>Fq)yN3affw@Jp*`Hb^E(QXK_Cfx-q#97tpC5 zp#Zkrt-r$tQjNy+=UEshWNbNn=ZqWKLny|pSXl*gd*P+vCCp$6urbZn=~gk63(~$^ zYw%+mr#(?W+gw3@X!Gs;bvK#s4C?ojN%-?l{JA9IgxF!s!)Z@6Of!csOK{g+b<<}| znkw#{nR~lIWSYH940|TFSHN+LT9MY3Cym(D)bmqmHz+caIj@?&R?ws;gX@NN1Pkv; zD{Q>I=Vq*60C~@L{55Snv2I@)`HjyTO&cn5#j7&9eTC(S4stYAFl=3y52`?jC5?XDwkOrh;E zr1nC1x4k?T45_^q{>WYn>u*U05yK%f%#>VMpv^Q+9HW9coH#z|LxD>ex^-h7me^(+ z@47T@c!GW>%7kINrtMX&IjtiFBns1snh2G+0i}5DZH&zKkU3icEhJC@C>*igLZ#!4 zeZTPC++?fAx(Vi}XOq8qa9T3tiCkKkg&YKuqWyBp6wkw49|8Pd@>^)Eq3i~DuEFYq z&;*da&OpP3eq#k8QdgaX}o8Qh`u`B~lQvN19zSa{PJbzKB{WQ-SMQu#J zoHlN1z{WKeZTYc@6K3CJ9pz(eeFNQ?IFaPC=?|I7pE2XsNp(NA05vlvSi_I>GAFHI1i_p z4$J*|OiVUK>?3MXHkM<|(QH-a48d$hni1mVjbG=8jU9f_qWKBCCtzRK0%1d)LuD>% zZe6=^{fg$+n>Vg$S!r*R<|<05sS_AFlX_cqFPG0*5j{w6)akRQ)tlDo6Qs2xD&=?l z!t1_mblXqG2DX3Sci7kJ(-!!xRr|wyz@6kfYdyg$+;od?Tv3Ln!J}JdR6X*o>*_+Z zS6y9#ZOYWj9y`y8`Mn>cPl{g<0#37=X>3XoewT2u;b>0+_%lMIDUpK44m~9_n!XHD(CF4v zLZj)+AO#Io@-vGDgE0z{NSPgV{)|Xjkw_wC_Ln=AGm)|)kwnVuFZb#xk+LF@M9Q2* z5-BSaNu)HS1`-<0i6k@{QUeK%=0p-24XGN0Msp%xLIWF8^ACb-P9%ZTkg7r8G$)e4 zxhFNBzd{0JR`;fY2&+|zB&_Cz zK7U3WwIq@_nwPqcIBH2GaWpSgQaB3DA&%yy&Jm76bBLpPsR>8W!2g+QMWp;PkwnUU zsYOJ}V~IS5wD%{iBXCwHlEAq?2@^Q06G`CQpT>y5S)IsJ$az6z^*OPyCXvL#g2?J~ zVqr}piG>A`)#t>*nnV%{3!^7Lr+TkVB-MLibnNF;@3o1fdN2HPk3-}0eW>KxL~>qO zlqyN}UY|&+_o7rus`vUtQoR?YN>aVoCz9&DIAulkZcQZBdvOfe=Tz_3L{hyMr-G>7 zt%;<1FHQwfy;~DW^)q7(isoqOdw^O}0CX(vC zG<7@Gdt)N0-b=sK^z=E^dt)N0-b>SqHV73%`8T zmGIv=WNQ=2AzPYA_Qu9UvN!4@t3OiPn-WQFpO;8#drKmz?F$o0ZC{&6n);=Qq^aMS zNMhmBLovXABp5#JOEl47&ozJjk#Ml*K@$)5T$9ot2?%>0G!bFXwFdnoAz{yhCMG`J zp2{N%K5b7lVemy~qS>imbS0X-`st2D6A7PYc|zfyRGm%?{-cTHis`;Y5(fM` zaq#IUi6#&}eId~V!lxf6nm|~X3L_5ICXz7t;=LqI9DMP9qB$=-m`KhGn-a-+;orJL zQ_oPJ&g@S#73$0jiKYsjc`4D-pET1z$fO>AxoWzuu9a z6VYG4lAg0ZUmr~Qu|8kF87qpfSs#0@1@za%z}LNrrsezk<4FGv``>8Q=Nn@6>yM(m zZ`hu1UQEx4xUWy7=bVl#e@#!{aEm!L~{jI;%AK%h|SbKwJd%iuGznf^{=bNV!P5hYtM16=K8^555AA274A%5(6(6l!8TrU#e z(%jhdplNTu?n?5UroY*lXwK7LzmsTA)Zgq$H1+lWcKyfmZsPwh`_JeDqS=4{(UxfT zpFMv;&)I+fu{%9y|NZ9+LH{T5_@7%6O?~^%=Mqi*8EEewXz%kJ2ij@g2ilJmSnP#? z_KylIHguqC=RoHvCG4VY0juk!U=hv(@9rFU_eHVZrNsrSClbEv2_Y2;-}Qv=JvZ=v zu->Do8F+ukzp?4Sckt zz@l*?D-wR>2_dCjrh$*V5+Cg@B=kyrykp>KRP|$*`gmu7m83q7Hh%0?J@)dzF}*Vl z93x@}z|vY};21$_zY0>sDW(3Pjy^`H4sPxk-29%i1~*eX278_w?D<%%9!`jZJv$35 zwsEj$SAoUa5BBUSuvpc>o~H{e8t1{D{RI~7)nLzw0*i~!!JfVXi|ft7o>Q6iE{9{V z=X8NZ+d25|)&h&uN&S}{YE3UwHD#Jf?vAy^U@z7G;Y>6A-Cw>L>A4zxIMZA{-2LST z@tnQ&aHgptpYDw5Vz8He_i0yVa6vIAm&l%6nQ1D)oLnNib!DbG0nW)KvX570nhTCO zu{Im*ty8zdtV2AZvgT2S{sBp5oKj(P@tz4aHT88_x-t_tN zq`Nc|^K+X{;rZj4=49Oj?B2Ow%GP&Slafv}T$X z;qzB=0-A)+U(F2KgwGFW293h!uV)6W!r<{YGKY3y@c5gVL(8xv)3gj5GEK{{B-e|U zVMC^A8J6UB(=cqvH0{EYtYfqb8!}D1u=LAz+2-T43mY>{yRbA_qT{p+8#7J2ur%jK zyRb3SvpKi$_=?xBDp!!1adQ4`(t~p6me|j`C2y2r_T|-=(L}C!wCXu>^$To?@Ahb;)bq%p?5{W@@51!ncdxr2H zJo$9y5a9oQI9H7`{=dJT8AQBEq^fboH;KgHgl`h5YQM+o&m7Im8b+I85{W^ZVG^lfv>7Im7_=D==2B@gHf5R?o zv=htP?av&d8#1YE!W%M)LwwtL zNGgfoHAX6l*d6RkTk?Z_1n*$q%ef??7cwbdU$C1@;t;=fl95UxI*pM^B02~AvJvPb zJO}%-73d>AA(QeYLLrkl#Hh*S{v=9GCUb~WJNHSoAx@2vYD1h3_I2l05~zcHZ)J|p z?h%l940W&14O_d(_mxproiYD4H6Bh`l39qdcTB7=RL=?D9c=h|?thfFG)vpr-I zhx5J34nlS%oy4KSI+p$)K8w^S1GZH(yQe#duz#&^Ht zyFcT*-|=lSDPPC8$s`W(J=ni5^=CgP@WKB5nZp_UOh+!W??-tPc6PRf{Y9_)WP zXT#~&jt*0!2ykPhMmfOyGr;>D;Qblk{SI)GNhLYJO(t<1;Qblk{SNT{4Dfyj_*=O) z4)C{fZ5-hJ8Q}d6@cs<&eh0Y8q_Q30CX+Z0@cs<&eg}Ae26(>%JY%)r0iMn&2m1+U zvxcz>=qGl~1SAH5YCuQ~!qSu`F$hBI@Wc?}Hvj1<$)kKfH42XNnenZpJ3VBh7P{UcBp`bVHH^p8MY=pTW)&_4wApg}E_PE-#X)DlOqF7%IJUFaXdy3jv@ zb)kO*>q7qs)`k8FSjP+UDTnn3X`Awt&)fY+a{V0WA7lon?K6eCIP&v^e9EWjQ=jBC zK1J_OZ|0|H3v9U>CmyGcLvf4|w%`C_^%*KdLIZZ6v?VMBZ?7O;qzoiF4pJCj$AAOWy^z9Eb(;a^;`N0eIsRW|ddsr6rF>wKD8 zZxTt9Tg=wkGlygO z<4m(}?#eW6;Mra6nL{IZc2}n{;;f&dl+V+Wki6P|eTo?#>+c z&Dq^=WsYmk?sqar{gcOTk9xg7$?PGr&+guzIX(`%vv%#_9C~(l?!!IA;o04}5BG3h zJG=X6&PR3rICEI%b6fVD+xoIY;@lRh;JGbN7g*Hpb6cJ*uxL%sZP{O7(R`lU@_d2C zh0nPyFBDj`spq!5RAAAFp4;+rfkj-N+wy9G#YM}xEw2?=w43L){Gq^Ncb?nwdV$5> zJGZ61z@jBQx23bdqUk%grMtkQtvk2n{Q`^HOx7#WUsUIFTRtpE(VUT$q^RQOwtQTW zqDdnwNztaA+tOQ*qL!1Dq-fX9Z8=$xqGcm1N%3|1+?E|#YO4>&)TTTGUywS5b;cmj=${R`m+6h z!tr5$v9|X6)e0^vY@Ch?E^XFd_HX~d{+Ii({YCY&-yc?RX-ogI|LL87*}qpe>x|;t zpmU$rO**4!)c&&nx#RYKs@Zegl@eF?k&L3AiQ6BocusA5vG*_gUwl8fFR~MiWBC@i z%D4UFzwB>M`L?55EuMhBmr?DAZT{Ev!>+dljVbk~S{&BfDiNd>LU70isjdBb+d&7Z z9iKF*R_*cBN1-D~9|fs19J0~gZhs1g zY>;mS=>@$Vq!;u~ke+jolPyZCD^V?Kg6@&EsJ&9w7PVKm7vYeNazFH};E)aSqaeK+ zM}zcg=$`0WrTrwHdaaH-xlvgOW*b$jlkwEE(gov|4_cI{{4%v7rq`)B?q>uuKY>+|< z9I`El|his5S3LLUQ3Mp{N1}SL3AseKi0f%gmf(9J2K?)jh$Ob8B zz#$u?OduSxK_2v;ghMt+0S6A*AO##aWP=oN;E)Ycz=1Y>>hc z9I`G}54%r~3 z-f+kUDfNa!Hb|*A9I`=5z2T4zQtAzdY>-lKIAnv=tO18?keW5%kPT9^1{|_M3JY+^ z1}QASAseKy0EcXl!U7z!L275_LAU&$3mS09##2E94%r|D4LD?j6g1$F4N}m6LpDf3 z0}k0nn*TMS0f%gm8ZtO!gH&(8AseK60}k0nn*X)tBWCqSX*(RU@l@Ilhis72b~t2% zl(xem8>CD<9I`>m)Wabgq_6;oY>>i&EnR$s|0p!rEGmZZ010-{AB6}=I766#gl8&L zK*AZq1thpA_@j^k31)4%#5Kc!GmA$TYjSaGG75OtXs%3vX+cR(OMhHnI}l;Ghjs zcv~c4;SCPjcq+WXK^vs-1_y1BLK_^kK?-Yd&;}`_!9g3Oa0Ul$kU|+8v_T4EaL@)R zK*2#9q~HVxZIFV3fr+G`U{E3{D8NA*!h!-Ev_U2)2&!KsFa#sP0e+B3lHd>w!2wR% zcrG-+NgJfVU{94^bA>$@L$d|kw2?rw1>Cfa{FC(4@$e_9r-R{7Qb#+}fTXn1j0}FN6oTB>SCU`%>Oy83T^h#*y^Cpe^~tMmj1EoHT19ZxOw zq$fC@OCjx8#Ggwe&){AFo=YX|@JUrS&u`Yxa%`i7M#XY)T7yDgaO9vdp)WXS4GDe0 zL2FE`1TRs8V)p@5qWOM%iB<=c_>~X`3DVIf9ID~DFlW!5RvYa?fzv_`y}=~XL8d=6L*EKPHhxYE zLH69+E30PDot9Cv=UfthE30PDotAN;H#o}Es({|$ptUTZH#lhNE4{&uHs5dR2fe{T ztN-W?4qE+3Z*b7+KYD|MR{zl(+-URtW`6Vr2Q7W0H#lhN552*ShUYgE$Q_(4^?}^M z$x<`r4h~j_Cc=UC7vLdImcTjC{$gTD{|4G$N-U}1K>MM@k`@lMzn)l9#DVrV5=*)` z(Ed(hNgW5;-w&2~ql97>{I$2R-=j#KVUi+>nvGa|@itIbz4_3DnV4(ZM zfo@OOEgLY<-5ad8q9?jvOswdM?w1lPdZPPKVnrLf-wD>cnzrOokJ_8_ytmOk>dA^W zx<@@((Z-(F2YS4!??xMY-Uybt*n3{?caMbTV(%>XyC*A3e4l>zWJQVYcMq1i*n2|v zy9di$?49L)_he}fkl#I7kq}PyJTHNZy(e_PdrCzm$b#!VxaMW=g6?`xRtUQ5Jy}s( zlQP$Pu*}Qe6T0g?SyCN{TTp@$3O%9p`NEc z9Pq1v1O2i}1E)R;)+tR?NGPdOvK0fTo{LJHiq1H7D6wS022Q=6STf=Rr#cg>N1b!( zoy3wU95}U44MMB}cA)|oSVs@`Xi29424qQD6u^Ki>3{+lkfi}v00XkLm{0%%vNRkD zU_jPMRl)-pkkub71~352VikB*J%9mO>OuuDAWJ%-00v-LxB@1u|B|QEgS|@C6tpZh z0vJ$USs;J`X)TWtz<{*YzzASKS{;S}2Bd{H1TY}2-m_yOU*Zk+Y8`Ax7Qs<>B7_0c z2u}!MKw5o^5C){B=LlgyS{;oL2Bfu8MhFAaTJ<1=0covz5W;}8Mg<`ZNJ|qD!hp1F zmhGJTGHc_$XyFTi3@EP^0s;Bz4xfec7% z1%^Ndq_qOG;gg=kd|FQCvI_`hKzZ2(gfbv4 zyMRyzq-7Nl%7CtLrL!I6EiLyh3bLfE-RaAYH_oZ!exSV_T=oj_OvmMuGhum+@MClJs`a|B|@NDTyH$cQ5lLq<6Z z#E=n3Aco8~FI_N(j5q?awI{kek}m2P+ANmjzDks%%bLk{o2eC@FBC!YcJ?SMjV0PLO4VZ3H}zuAvjtX z*paiw`J_yOojM0civ!4LRJAyOj5t~xKt{cxgAz2m;4V76w5ONXxz;2m)zY7X(2dE!$!T>Cr;j7CTE1j%*8L)I!-7$cQ7`0vY8j z+X5MJWLqG!%}bYUfs8n^Es#+QWLqF3j%>?=QMN40rl4h45DtNDlvP1E1k$o82!}vg z76suDNXwoe90FAAr&m)-44)r7H3YHsw9FUFp&@siKu`;Z>&@&;hs{efS^w)v4nm|MFnI4 zAQddx0ER|j6=VV!8i5R107yigWC9=)EZG1`sRA+rloCq_XLtl=E`T#U0vW6+q+#CJ7sGEF`i8kVMA6@(A?9UzvFX`gOBnuwUpmb&-UyZw%!j{PHSELAWoY*jETtW_{8 z>{T!;ELJcpY*sJ}Rx4NqyA`Yo%N5KD+ZD_T>lMrj`xVRz3l_`@8y3uL#eDO@Vnzhe zc`s$~R9G>ybCD?4Gl(O>(Fi+crY~4wxDZK#GGVxk5i()A5K4kFVY(1Xf{a+WAe00d zv24-By1`SiXhCcVO2qnvxAX=rt_0h|P!o7=VI{nQW^pr1N4GwdpgX|3o@gp!}z=u5@C2=3W+c}2uQ(lWN-!Ii=o|A1b14urQCnf$Vok?>qtrIqFW2zyXd$+wfG3z(I}x=Mgx_5#T%m z2RIgO!&5N=2RQ=2N8lhw*!Bn<6S;}0LP+jct0=#2RYF*jKD!o^b8|#kfSx_Sw`R>M^@S+aF7#y z#Rwea2!>}FfrFgr8%E$DCp3oKtQd(Kf_E1P;o?I4}YSIkHe^ z8G(Zw>A6SXAV<3H5je<6bw&gZa#EcUfdibqvPkDP^9_&^ z6bR43`YL9JWpZwd;GmctWNErl%nq_NzbIx0Sz4?rW(QfCUlg;0EKMhh*+G_ote72S zsiunAL6&9=#q1zU{jZoEWU0Fqvx6*Id&TS^OXgiMJIIQ@^OzlEN&OYGgDmO4Vs?-v z`>U88WC;_B*+G^puwr(QB^#`m9b^d>irGPyjILsKkR>atm>p!v&MIaHSQef`L(8xf zWJMQx%nq`oR_8os2U*g5#q1y}dSWYMJ&>iD=$yywAS;^fF+0eLW_!#IvSbX-dCU&7 zqKzK2gRE$y$Lt_WOTTj-vjZ#(&*5$Km>pzo70Y9GkR>B=&SQ3vC5x(<9c0N&C}sy) zq4x;Tp}-khXfce30%vGlrI;N|7=4d09+C9b|8VuUd8cSC&(FWeGykjYvWDk7RDZ1%F6?BKnXBzGjTc|$ z`s8Z6)(IC+akX=`UHWqA;AyXql;zUz8Q9}$yNu?gH)*f0wo7v^Z>2}r)pn`Pg>Ej7 zuD0t<|H4r^ie7D(4qeOv0}`d8a!KQ6YZK3cM0I>@^#SKDQeFZZfGnhno? zywA&*alUZ+9j7%DUV7O*Ao4F z=$CBzg+siFeYIWl;f0UYADU$^?%}odtL-v|m$yCR`D=##^C_ygVWHlC6uW|5{01I;iqxO)tGH zeHWZB9edy9h2_hKgckw%^1FI26kk3jeboB>;v4h~yc*x>yVxQ9)vR*ylf$0BAb;su zjmK%>X|K1pL-@aZ+|zdm|Ciq3UHhvY!vFac)Q{_PO{FZQZD;s5f>bmF<%A^e};!{BCDJB0sBTlHM{ zKflw*w?p_pzthL3Bku-nfe_^xQC;VT0#_el|@PF|I-i5o`A^e};>-aR- zbNnv8dD6#E`2X`M>67q({*>F_4&ndLy>34`g#Qa4slNjMFSxz!5dJS7)p!g4=bz>4 z=hY73|NQHY=MLfj;vvUlhwy)Czw3X8@PFxTwfgZs9-rSjg#QcA2oJ*l#TWEk`2X_>-jTlAA^e|zO8q1JpWmta3jY`0P+gzVJh5t)$YW#)&z(=R>f8OqQ zaXbV6r=>r_|AnU=|D7@azo+^O{}+!)KZO5_TCa2p|9?K|>xWL^|NN_NuR4YQuveXd z|4$sRox=Zx&2C>i1OLzXeA+4eU)=we*I)R*cuM0d{9oE4eG>jJw`shD|I4rXda_ga zzx{~uNP!vBS1Ztvn-$d~#=3;*Y(FI~d_g{ORd)+PL3IPCcD z68>X;?h^hlzvB8EuBzwTT;IC_|NDHs&=vFl2Rc|K}X9U4j3Xls@qPirdew!2bv8|G@tVUk`T) z{}+C*=P~~ubNqJ+|Ce^E|AhaG@96pePuH6^*O6W4n&15^$~^(@+btKs(&T&HYT1@0 zS%YO;mVMo~hjK}>%B3omC0Ul;Lx7k#fFOx^V39=<8~|`C0tCSU1S9HtxGbqqeH~#E zC|V($b8sepIp-(z%KbiXo;)|ABUn*bJ9qBfx%YbBwK7j;ZuqZXSH5r+{_AUs-&f(k zKBo9|)$w09H(FO6|8Hl3(}Ki_-{nu z&-8Pd_+NcUe~ACp5sl9>{8#6&;k$Z4dbw<#7wC_9JwksSuj58+Iv(-5en#m*J-w7viaZ@YB4@_`pwdi1UM==HjO6!_UFC^l=$} zszb_8m*J;6!hFI{mHPwB@KYUA{9lHj>bUOr_zn8%tax6ApDNea%kWcOp#Shwy}=~KzAW%#L$(7xm6sPu6KeriXQZ>+#iZASCGVt&xW75J$w z>ikyVr*={NteBq-jqi&2xhMZ!fuEWd!)mR-PkluFUxAF--_dBT!?4>u;Td1{k0Xx&$w_-+u^Go7rMzNe%9kcG}-W0kHkFL#Ls#p=E)|0 z)?*-(4UhFz#rqZWc#G}kk$if^JjScaX@B#0pZ(3F-hywfz++>8@rK975ykrzcx)U~ zyjp?B+$MfDCZzW(<}+?2rsESo8HrAzQEAZU7b5#4ob3?0s zv{s0p=6!|u**v3owc_~suHxg0`9CkcU4j4FVdd{D@ZY{Ph65o0jiU-ED+XMPoVQjC z_$UqJfD4cn06);D0$M8ue1riv;G;|^0kS%#0JLJj#}uGe3^)R?V!&r+G(ZNN1b4-N z8#n__09XO=>cw^K58&0fla(gS0rK*6vR54-R|HV_r7oba>wH%My!ucAy$ayfhe`lf z9UvbmAzKCT>b4wi)qoFayjKnQhyYmy@Y=8(Vimw^2+%5k*GA5(zW{DQS_SYL7c{E? zUOT1pT?O#kX(iOF0A8Emd;q*QBZpoE@EQSh)d7_owyOYMd*_V$1K>3R-YS6CZU}%? z0IzWaW);Al@UH@REnbz$=4TbaYY&wWtOB?Nan%8LKmltNz%7ug4!GwOpjI7lkIJ7{ z0la=(0dp0=>m&rL0A8Qa4ZBqUuTLuhUIp;_w18U$@cO(2aTUPp^GYCB0o)13DuCC| zaJ~Ru=LeZp0I#1_LK63}l0VkZilD*c7;I)b|D1o<}ob%MOX@!`LY z;6^h#-j)K!D*RheSK+@gj{m@a;}qiw|BbWEKm0f1&U2by_-|ZcJmKGhx(fe|RSDcG z{5v69h5rT#_A2~05a3n#_YKlj_-|Z;NBFluUxWX~O^x?8__sh`ga5{s60mFV-#n~@ z;2QilN2LeX;JtxP}6>mrhB7<5pujWaXw@JbpS!_Q8N8 z?HUqrFrX>Fh6J=PYXzEC{ML|mXkT7e6upKBw6BupUqc0&TDh@x4Hal^OC_&E^|OVi z%3H64@w2t3N~EsC$+MMbaO04vYV zxByt5z2gGtcy^8gf86HTc^AOLvkNW&fM-iCfPiP0T!70zyW#?T{@GO*;PlUyU4YkT z6OktC25$ds)dl!{43NC+296)2E~U7E=f~Mnfa^!cQbDXkK`@EGw1Pz_Qv6K_iNCah zg-Y?CDAfuUsdO0kh;=Hof`v-)k$eS!X z3KlA5(()B7QfaJIK?|&4q0+ovRONtC(6G{tP-peLCY=UZGONwS;6J85jlJ2(8j{3~bj^2;0^mf!E zc6RhZp_HHyd!@Ruh2D;OEU91ELZB$@!+9JbWW<(qjMzt8Zk=b#!M#W6#BQC3o`ZWy zJKDF-6HX3v{SpP>)IL9)63{4tr-Mf+E6v zqX59Fx~~)k0LF!U6yUAZp;Po7N3BezOe<;tSo{3ETZJ&L{-X-fr8<(b>nI?)RAVBu zslIid=u#a?x*Y{Xm-hMTcc?;qsZ1u)qY440I+Aj_C?LXAN7Gej6ga}1pGjdcN(eF4 z(R3jf1%#ODsCz<;Ie3^dsi=WER7cZqVNq~Km{fHw6a`j_l_T%cR{~S@NJ=lG3J_I~ z4p9SsDpTGCbgD-^dk|ASmQtc<15m2R#cCpTiE zfW(tKF;PI~S=E(T6p(sWPozt^C?KX*Pq<@fBEJqt4Pt6_JYAkg0TGq^J5hjOR>#u~ zjwnDYtK;bcEeeRI)p0{ZJgtsPwr`y$o^}d|r=0@gY5RQK@RFYqQ0s@KF}KbWP^+g> z-V?_q6Rn=|#8?&2-$)nRQH2m&J(cdZL;)gO)zA4+fC^Q0e>@5hplVJH+EYDk2|#kH z6X}XCc0p;X6X}w(eJk#5C3&t+A_*6ek?K@RDx(H6Qk_l-RTLm2)tPjkAqr5D>I~;{ z0TrpvrZg{Vpdy_DtB5YOqlQ&vmeapr6`5u3B8OB@=Ti0*yI4iuNtcsRfQoQKFA7kR zsxBy_z#_7cZdpZvMMRgNQD70#P3$PJh%8CQZ(Tq{s_&+JF>0V8)pt|A7zNglP_g77 z7px)YP>c&`NHyF|H7p_L6^w3Ou!LO1$u1xv?F&t@#vfm zV+pwgA@Ng{+PLKDM?$Lcw1##;LfRJ=?J|zK7tX9w$08Cxp=c+INZhkl0U}aeMpiFa zM6Ra9J_gMqvT}u85Ru9m4?H4LIg|3X*u^4p9q+zi5xJf&D5Hi&BriZj+85&2LiNll zatjf?U=_LLo>@iooJ+K76}e^CL`B*cu6rNL$Sri`f@S1APX#hk)ne<>Dk@Ta&m)hD zRL-TVjHqE1(F0OZfQnRg$2kftBJn{~ot;Hw&93NF=EA1k$SSho0dNL$VbfD#8QJhu zSVlIW^`d2DgSm-kOypRb7u1uBmXS?P?;-`)N#iI}e$i5*d-QQ2OG#dUlvI||P0y%- zl(d(Q69q1!CG9Im!2Tj)Qq`j^v6Hn#>(WO7T2fg;(=S>}^t40Nu$E{A8ZNhKael~bT3{D=}qK}p&d&ye?BL`rH}bup?SCAAS4 zzlfC7Mo!QwQc}~M(bxqk>C`|OjO6^-q z2ubY}iQy7LQacIa@pMt*sIrV0Eg>W|J(m-0ASAU3&|5-CYLg7n5<*g&AW{ZAYBYYt zNPf43knrPMG>VYaW?TauX)g_sLSXe?uYKvgDgo-o<)2+)mEGlwv z${j;Qc$zwDpds9yj{-EL_70N2gof14a#gp4hSbiIIWD0gasLmWSwciAH#|a!NbOxf zT(XFqvxguewaCmgcZdi-ghtO0k@nJrPY)HTg_3I@i-;a3k9{m6@mRiUI3Lli$*5rw zi3gii!yJ)jWT=PA0i}?9nUUl8$jEc0E zLg;iH%g7CP9vP|J_W4>yZlGREmXVuWqAXcP^lM7=%rbJ5i^)hcQ%BlM@k@}7YaO|8 zhfH?~9ceFJ^ZZ#z@~$`~Sz03^#{&gA!!@#}B`e9g4}_A`Zu^WaBzL$N@Y^W0JBYv~ z3yFRXZ{PC!DEvGW1s0MGq~?-^WCMOLSxD~UdwwUSy`+a!q7js&cIS@9+9tu)ucy==`7kI+d+8B8 zUUEvZqa8Y z{U}6ULQU%W5igoWP3p%8mY1w1m*&YYE}XY_*)TBP;2}MmRTlN9eq&`iMx`djvFTF<&eF-_KPa9Cn$t69o76(R7cw-<6 ztR@#nQK?8A9jnQuZRFAKwX`p7dr***_T|IFj6Hf%iFepF8R*G@UZ!o|^4l%^ zcp3%hN&E5;*hUbh)GxV2iT6TN>hC~h{+`P_oMZl?OJ0Da@WXBN z1xev&u_!=N>b~RPmt79@aB|c@QVs^8)UUg=FY9^7sDh-_Chsy)hIukQ9C) zjsg_rUOGwh`ycs7n2@ z_d!&65Fv)ZqH<-*E=r2hzB27mK~x%tJ@{x!`^v0GA5m%eMzLR+X)le#x?h?(&=2D6 zTYhV%F^HyIAxmit59xgT-ps*i!h~O(X$*75@%%#)rs^ue$FI(`ujtX0I1G{!C9Z>} zw3kkjt@{OClaB)GZ#?O9clLpy1bN8pkKt1yN}n z_xT_yjpJOt`AwSYaWvGg(lmU@>vw4yc?~3`ah#OHZ__l!&?Uc4)4rl*jABf<J-kAmaxmlWlczKm$QG`kD7;e}XN;mW7CajWN<(jbM-2p}vB*`z74)Q` z_f(<=deV4@xOv5TqBjqs271DiOHqKFG~Pifu2@aZy6dP(;~WvguiqTZal6g$-!$H3 zi2MRhBYqlABjh)54(7N<@GCft3ux9=u6P<3a96*C)4u8f@oPAZxJ95=x#DTWwYv(? zl7o4q#Bbs>mJnFKic^Va5wr_eJngG-(@Z7gC+({d=2U`|94sPhej(@J4DrElgQ)t7In) zUu(v%9Er#F)h*H!zn#-QA3wsYvlbJ*?-8w7Ozx76_zj)L=6mddn6%I5V{bKi$W?{k z(&3@9XcaYSFVBIAU(|^bRO>3~NmGxm$4=-;a|qb3q9@H^RQsyaljV0jgUCs9n2Q3x zu+uzB9)1-yX^z>INKcw$^PC`R(mduNAU$cGvI`?7&C{MhvXl1mEqfd3Npp6LRxKu1 zybog1oaOp6K9QjLTFz(CS`x1zX}Zvo<{9F!-`}a;B~o8SOPcQ(CbXoz;%nBc&Q4a2 zl2}|tO%7H(jHt=M3Kvd(kEi*rz1-Q!%4srNzshqEk5sC&$Vq!;5?S`!JUrYNQ%rJ_ z7x;aiX8c&68u1G~?Uh-&;#Yc_OZ?p8cX|%46At`RPctrN)d-h6&4?{3AUSDBiRtztR(*fXhy*|Io{EsQ5r!!O%r8Ta>F} zD7Ik84KU;a7@B?zO+AJ(QVh*FhQbGiW*S3514A>6q1naI%wlL#F%%#$G?y5fMhwkh z|DpbDKs0;({b_VGa~O&P7)mEGG+Q*Fnd+3NXapj6ViYb97F0&H7PfSR2xHzjUlzhkWgbtq%kDW7!qd;=`w~i z8AG{UUp9nN;`FC9IQ=Q{O@B&t)1T5?+EC_(A)Uog#)ctz#TAsPVMtYFusk_Nk(&6* zWCx)Si`1}CIr~7(E16?`Sfu7ds7WsOA=Jo#8W}}uJ}gr6VUd~-p+@^ajf^5F9~MFR zun5YBfTDc>MFybAD1!1Kpd?NC5Kt0N9~MFR5KzG1}HiXpvV9e8Gs@KP-Fm#3_y_qC^7&=2B63Q6d8ab15lFj&qd$)=c3~N zb5U{sx#&CpTvXhr%xN0lr^PvaS`@#hMe%!T@yp!uX>m@U7U%S-=QLGgPE)U+dQLNi zddi%t2cFZkkLNTQp3`J_PLts|O@`+*8O*7gE{fmNqWC>6ir>?s_&sG#)j)AhpBCrz zX;J*17U%S7aZaCF{L*P#{63S7L2*u>O8olL8A$v3GNpPd>FY~-OZoaT*N^uWGBN4P zJpU=m_sQ&Uq%R99@y0=_^Xcqw#4jCK>eu(#tX$%!ztO(b2c$3c0p&}5u-3)C@zz5U zw5$*DQbhVY>%+66OFqLT(`Y`E7xrbn*e$BeZc$}+iypaK^vK^T3*9A*UltA;~?`Co;lvVngUmuwN37$TEuLX$0G6GL!PO>|-i zPYm(NJ^-4g>R%yL28bpv_*aq>Z85Ofx&2~5l%{IG0ACsKoA&udF~Ix+nzf}EV17Y> z(UxL>`9(1~`31>|j#Erdep#H6UlwQNm&Hi<%VH$_Wl=SMS)89=GC%1!zhr(=-+oz~ zpI=(dvH{uWNq>#+K2Q1ED!Tdp`J$ii^DMt>7M*+le37+%p5S-QqUZ0QFS`Ez`J(Ud z^VGig!~4_8?eloPw-qyzeICsBwqnTI=LvjoD-QoY&)$1maRT;vFj+3S&qK+!pgfKA zKF=kwjfawGlP8jF73F20XOX zBiHPj2Occ)85M}M2u}k72D_6{&a5qq@B)&U#96lKTFeNevUipJ zYLEM8?6a)irdoXWBGuxR;Z)A+vZ*|NV#>}umGd%TD!)}Y!tLVUN(DZXrBnO2MaO5e zOl3cwnaW#=r=zmJQ%iR=n)+LH(%&0}I-3&QR32}QW&8HMoMn*v@rlQxrFlyA?n}P= zUQT_#Fa5Tq_H2#OMyeSPq@)&8ez*UC_OS}>KcxNsETi0KDbPMF+Q*`OSzE}0d}#|o z#=={%iqUm{3y(~i+&rUP?T^)1PLn#G^Yp zr_v?p*I978lWpAVq`!P8B^`OCs6(Z=Ltf}DpZ}l@KK@~gSx@6$T0w`fiaJzU(Oh~z zv_`#LEv6VHK4(BpK4xI6sHLS*t+~a)7w(~ zG@ka-*~X{Kiq>40RaB(X`a;*XMOpH=^)p@8J6$#5iQ_>h7Ib126UNfIu8-7bC5ZZ4 zNs9hfilV6#lm2FIIbN3+#}6RI(d^=Dvv_Gy+ALmMls1bO7p2YO)kSGj zKF2I<7OyW#n?*$|#e+wMqi=GFq0!weUSpJqd3bpI)LPt+%X{`iCDT=UX;YW@{pqes zX;T;ZZUs+DH`q&?U7obrP`nf=#VcmTn#F{;6z`T5w-poO(x!aQZ5A(6O7Zc_ z;(lF9v{}4NENylv(Pozt#V6B?t#m2TW|tCeb}7+jmlDNyY>KUP#iC8eqAZUoZCb}u z9Ne^y%j(kc%`WBMbo9xxvQoSlmyRd?Hj7zsX|t%~<)Nasm4}KRS&pY;vQ{#p%R}-< z{ay6N@=(zm%R@zPEDsgEu^eBU>a-_cRD1G7{ay6K@=(za%kg>I&M20LI+|1->S$6q zJ_1;*g@>e(C=V4qq&y@K(Q)M;`nwpX%0ophD-Yovv{$@bDi0OCqr6`9obr12wxZ{h zH#8$z+$nbvRPG|E94|x`&TO+tP`Qhs@@A2sau-46&93q3BB;FCH9lPgl{bq7mAeQk zcM(+XBBt;IU9VK)1HI`8DXXmvSamYeD+Bf`S@x^!!mTo3ze-ko z9wTcd(|~Mg7ytcYq3B^R;>GmN*}RBP`4=r~Om;Re7SNc~;}`K!;bP6=2)$S+j?jyR z;t0KnZ$}mPD~`~M#bS(ju~>`|FU}M_;KiAu2fW}TA{hxUxRsRt=0!dIU5qg=;uF4w zUc}QSMXN~2i=|?WdC_&#DUQ78y6N(w>!!<#r6M6OmWnavMb}N27hN}9UhuY- zk5G&;FL+PO+q%ZbcU?4ByvCty;sxuF*s?#RIp$798Zo{fp5?nQc=j)qcU_gd=(-Mh z!6((|ldnUP>b+R)@*iJosE2&o%=-~*w2jZ0vF)0CGWFz^`kwvWx{mK;x6WJlij&>C zSDftDz2aoI?iDAyb+0(tt$Rf|Y{f?h@`tiqn*~PmdS54YN4#VHU{^vxsh(MRwb#d5Xy` z^T?9sBDQunwXCMHXc*gQD4lnhooLlQeXTgMVKTH-y;Gcp_USv_mG2biA+{FdQv39S zVz>#@+L;Y^`}CvH!aRj}q}wc}^|AF>*N{It-ffO`?|Y(q>v;FplilX2Zu4}}oQOAQ zvZ>Czx;=5YFl-Zp8eeT0)c9t2#<#kPS3qGNR*MOsFbBKM&IN01lm4sC5p7YMd>cwz zj_B;QC0vKwC~I2*?=MSgG20*X&M=cg^lZyb4~RU}C(e zs__vrNaL)>yIf&nyvr3P#*2Cx`xXOAn8mC-%wk#|W-(^ACt^L4&Tvd<_H=3!#emkH zm?~DB8cKVq;%sr6<9py)d)g9N1bKxfm0c=ibJQh4V|f4+ZZlx87UZd=Q_shHO)v;kJ)RQ z4-Cx)hUNl8Ga-Yo86r-ze9JI5yQXwD9w5!Sn{Vx!(%I|9Jg7Z;y-QGMAFHQXsAe*ioZ2QM zV3WK|s(W^X#LQ+_ggHARUm*^h9noalj1MHIUa{{e@?#VEu_+H=}Z!BoQ*G|6&&E~DC5j=kI`qFg4LZoCXdEE$HK$8 z*`>bc;zM?Y1D%V-h`Y@HqN%RIIXBQXIOpPJ(XM^FM&n$3C!%XBqoGqcH_#QK&J7Tu zyp;%LlL%$=ba%@W-R5MsIn`}WcbhZa=4`hqLC8l^m;SkVrn_?SGQWLp;B5ESF6})x z@NW0kVul#|7E{D9i&L_8kUTn*eLGOR~T@pAif6^S)-Or$24e9Q7PZhJs2|OfT}huNmsuN$;AxuiQB{ zUQKlsvwQlIq1>+L2g#5y_w2-bMKbNq5ipF(oNIA8fsqRsIJm|0py5NmAAGJAd}`^cvtlNvnE(=kKsJK|SBL zEh+E$YU;&5X@YvbntJh19OwJ8G(A0ES4|05&)2n0y4CZRED;iYNIl{_f9LO##d&@^ zSsw_kp6_LwK47;e>E)Ua*e$jA0ek&0)nmq0NNk09ByC-^c|4htwAh;72K$*LEzI=N&(9=jVWu~WeujrW zN$>moOq$j6{nXAyHJc{tXOgd;pQbGmubvOmyGS46SucH<-s0F{O7YMgW;DEBF8h&$ zzh@`C=kt+tzh@`CjPlW>4!LtVJIqH@w%Aj$-?MXB1brm(dp=5o@{z#r`6#_T^AS_E z^B}dvTz!I!|%Ff4TfL?YT$Y-K%@)jqp8Y zbMGiN^ZEP;HdDHHT=xDw{ABaBcs=Le{PQ zhfHEBx{!*t@ag@nG_U)QIQahdT0A1Kuk|SQV;w3rzrEfmEe#iH8{ zO1z}uB`!*Oo}@Q*OB;Cplk~b_X#*)M-AT?<+Q6IsTrDAG-%|o}_m&OBIgn&}_QLcIQ37TQ(1lU|c9Z6IDx(o3eLSa%^g$xl=n*Du8lo6Ipc z@Ox=TX+!x~&rW)Cue70jtY;_DSlU3^BoCzxwC$rKY6^9GT^90|6uY#6zR}~$U0qV9 zhJ2OcJ-gIjcYzo$p(({znbKBwfspSm;L87RdcCi7ms5W-nYwhBbAL@1XZ~bHCTIR1 z)60aVyPP>b5UU@j!+uwFIq@gydkv+#ocNQZ8>PFP`4f%sT~7T;iXEl9ocojXDrD&{ zC;wzsEpzgvjTFsFcRBkHG_`j*`zPt;#L`{PzN9ZvmG0`|spkXwh3Gs7F zLsaKU(!|nT@}(!5`Mapl6V3cxWa!C;x`_@wNin8$7ajUQ9L7>#X|O&>;jwfVCHlu} z+7~JM;FwIb=z|n#OLv*U(jCEf7diSsNX06hYC7dMrMnMhrd*;FZ%AvS)Z#As^nuhO z7D(1soB@GSm@GX2npZza@0*q$;5t82LQsn3)6(8A{d_8QJC-BvvLB;xFU3n-8CQ?d zxCqiN(Y0Kp2~+XnWU8afxm1$EXz4NH5_P;6Ui#9{r@a@Bnw78@z8WRoi>c011dhdQ z=Sf59aehX$!uUihS?|hRFDuh75BPkv4b4tjc$tf3*L1d`nod?^q1fz)USpxxSm-qt zdX0t5mIu;Amj@8Xmwujnx6GAb&nI+Dfcpd{z&h>yjlR??MMc@Y2WS50z;L$dHco#7z5H>B6h(DSn)ClJa0NTPP2bE&Nn9 zr*%Z-E9F6Qh3{!2nZg^g@R4-EQ63~yNcl;bADep^U?lHt?PtN0MX3C-ETYgK{wSIJ zj?we#uct^_=7)^1+0N|zwrVpw@4tu5jJ)>iDK9DyGbgY8`d_O)v+|FEdzguM|GnSU zW~L%#W94C{;!E1bL|`GfSWJY*Y#5>a`>WcKdHJ#|<^_v+N$GKUn0e9g4l@y7*M3aI z*EuorvTtZ3qx}c680|ll#b{$8`B-Egf0PpP^6&+D!Z*J_f5<()$!3y`KU2HR8y4xn zZ)qDLf=NLI<%-iL3dA|Q-E3d!-16V4bpdB)VCC(lvMzYfdR(S;XOP2}d z5u8us5TA}tHPxpPJn=8pDlU`V@R5Lw;5J!_WMc%^$x8ZO)^7MuR&+=uu-)G&!K<=5 z(yb9ZE2~2Zm}hsBC;|0XIwd$@){Yc!1SkAtrvyjL>PY!UaK@}P ziuVa!eziu0ZUjK<|!w?n> z{e*pC=qKJ6h{C1<7`^^eH3mjlMCPAT7pi{heW6NXHUdFZ4X+Wo@fGnj^H4j1*YwdFyrz%taPochhZFCkLuB54^oRlI zqf4B8AAQ1e`sfs%(?_pxoPKG^1dh|&_iH)@T&K4m8{g?YgpKd?zJiVG^nMZ>-|77n zHr~_wX>8o5_cPeIPw!{3aiHG5mnp!9di&f%+^DyYE>5D&y?yj?l02`sk4{dKO!W5A z%SrNy-afiHNy69LM?d2WwQ2Ht`{?K-8sFPTPbZQ2-hO&IiPHCdB8}uEQs0kB&T$Bn zq~jIL6FS@{F~?0g`g{Kx8~l5Jf(`$@e}jzx^!^mv3h=ksR)D|5MgcxA zuZDjmfRoU#;1)}trI}N3n}mENfL<$53E(8ue^G71yprrmfd8_#L3_ICT%H2;ucU2L zaIQehGdDd7raW}h^L5n(@86dN+e)gZ0Q(QL4XTwmPl5CwX&Xd;Qx-t~u`GE0mMn1o z6Isywr?P{mIZ)+Q@{*^39{h=&4 z{gEsv)x~41RFzu!Yi)y2B|B5_`8V1IoyuLNVDoRa4KmZ6zVZ}YDh@6)V|k|GWW#HP z|Hg0V(wr9sdwvrWI(`%THlD39yp3neD>fjv95=nQyJHU+n_9fAo7oQ`v$>t_y_UD| z_Jex^Y5NxbexPNH%3FlBgL`+iov3zj@3CyW{^0(CZ2bP9%wuMUmd1>2&ify%X0k2m1e0rqU_B-epN@){mk+f98^bV)gB~N^(f;35!K`AH<$7jOjO72t39Zwo|ush7u6GsvLT~-;+|~SsE!|& z4IS0-bRW6QyTCowcpNeH7ecBhIX@Vwo@73tqcdI()RJsSsh*jWjXzZNdG_)a zv{d84@pL?Rsm6j6$%dF}tox8`n5o7}3dx3=>LuNw*n*pCd=NWrhn(t-)3RZwdXw>m zp6V^e2Y#yWol<=WVxh6}7Ll#`9`%WAtSVOCBC=I)-`4&_wradwmihx9)p$K6*+jN# zyhxR7B3m_HSV}gLt$Ke=HvClYV5o`$X0!LNA+R1sxKdww}@?3{a9O$)wAV~ z;aKT-#JB1r#)}A7eI%aap zz9k#}Yt!ogBlxe)9?^FAug%Jz9>ITY_L8>4e{EKL@Q`#*ZBD%Llyuk{|3~m&i&rwT z{_&i2PwgG`p9iJGUeqq5J zkKn(q&&rn{!GHaj^!5?_*N@R(_^%(ouH(Uf{p7H0_^+QlCL8|i5pL7?!+$+OUb5l8 zK0PlR{_E54%7*{?H1h-h^=al4{_FajZTS)W*JmfSKm6Ah#quNguWOn3a;ztl=BxfL{sI5>cku`Kug44i>3re89v{k1HvHGGNDl%# zwHGlvZHNDQtUsS@_^)5X|KPuVo8!TMUEg3WKZ5`I7UvKD^+ywG5B}@h(%(n$UymR9 z((&NG5kGt-8~z*dgl4kgzcIjg!+&Ey{oxA_J&kxbAgjL(|Bd)zA=&WXIHGvMS08#B zN950Z_aSWBga5{nE$vVIZyZ&Bwu%1@tq51%hX2M<`O`M>zi~|S9jBefyP+k2%iHkZ zIHvj8hX2Nb@|$h=Z!CzvSSv{T$J(*!eBi$k>#rso{`rP@c^m#4mo?wp@ZX5l15$nX zZ^XB_lMVlk_yBRT;lB|t?Jos-eimz?>Z=R9< zY{P%^EXRZYrj~UpZ^M7{tn?sGJB>&4tn#h+KELdDnQ!=SzI#K*hyNz}u?_#t^Wrat zP5WP@Kk(m-?>nXb!+$fr+mvkhZ^jZ2$%g-CEZ>l9_-|ghE*t)vT9mE44gbw+(r5qv z-@Hcq@ZY?ycvfGlVAD@ZSs&lMVmP z04wZD*V74=0lc9iHLeUW`&n&*;wRFq$}nDDil0c6jhC0=t%qde<)wJ}E7^E? zDc*ZXHs@7}7ax+1mzT8cOJ$hzEDh_thVk-}zB^tS#>-2iNrfuIczJ2`w%WtXOIp6P z;@{Ouqe=BD{$Z^&nxb+gE*_H?l(ay7#Xqf;hBZnfM7dJ@sGIe71TQZQr!2QJf|r+u zHC`ilc`1I@O!e{dQvB+hY@%FgSVQig=SuOrZ`zKRmxhxnR{RrPX*hXNWdtuT4JWUy zjNs*^`29ESkC&H*Ps_&3OIpyN5-Y>%e20^lRz`?&rT9H7?T?q2Mw04P{1aelBt`X# ze*i3vq$pDv!OKg!>aL97<)wJ-D7A-|mqyZ6U&X)sl}3{4RYr(%rT9@R)hEi8M&wT; zM7ff#;w%2WuQVcm8o|#?@yk})A2%<>Z(GSG%9Tcvid9C4a;5lvD{Y5@(un+LgeX@U zNh(+I&wQnLM={kW%9TdsKQSyC&yl2Rl^7OmQO(O(J*DAk?N5{|#SiVNJ)&Ic2>wKr zD;>d~h;k)G$w~~1+CRel6Xi-rrqmu$u5=__byZ?ms4p4tPkyE0q(YS#7PhA>qT-+Z zN=H&usKl^n`!L&c{?F-n=6{&;&H2ZF%>OX{WB!Nnhn#=rKj%MXC6$q!f9Bi#4>RB9 zf0+3;|5`7t;-3IZAzW#^iE^cQQ6<^tKfupyUMcG-1(?Y;|5{_Y5;u<+FZoL>zm2W- zN6mjYcG}m$oj1q1Zh0)KIwCQqb^cq`nx_oQ2<{gP2y#W-J zxlZa0q$nYVL>>DHQKUDpq7HE+_L`>TGJabfxj_lBB-*jx5>0yJrZ;tnC%p&v&r(7} z>5Wv95@Jek+)AT_sL~rZrzjz=^v3NgN{B46*D@tH>0w;LQ9^X-jho1na23@XxAiC? z!t~b8uTnyciIzP+M48_DrETgEXL_rr$0;Gw^wy8vr-WG3+gP*n6K#6ympvN9o8Ibi zpBE9Q_u%0Y?GST%s}oz45OsR1H?B}Z-07{K@m`M@je{+ZGqI<)v2Mj6`ovr?%g3x& z^~=oiG3(XA7Ng2b!acolYeh#tX1$7=E0myD)fwf5$55;4{dBjbaty8FZfzxQ8YWe& z-=sc@@QrVNA*tpu#Gt=VCNj|9FB1XiKO__M`(Kd>^!=Zd3GV%$k_qVjpOy*Y{hyHu z-2I=G3D$>RmI=^@=oKg*qE|qCh+aYMA$momI7F|U_;I0g3~YTNbIge!7b>wBa~d#T zm>dIGUnm>{S6?U`%M-t2>N==~z{qyuN8*3Xi64pnF(-Z``p2C3k?0?D;zy!?%!wa~ ze%vLazgaBMA)WiBNT4F{R2)!Io&jc2Kt%wm7@#5uRRmBGh)Vxe1fx=a6#=QVUqw(V zKX`mE)uj+>omrCv)Hj!OC$Yf*)uqvFcZ9KXhFE zK_f!NWg`-y;j&SQP;lAEMCi9{bRyJSHbN2FEgPi><(7?9)MHh`EcHB&R@A4{4a7?P zE}Qzt4~~`Ns73Rz=028T)%Icfk6u*cn=Pq51f!-^hbyrfajM@uuT=dwiV-@k`Y1*< za^AE*ic!0vOQGW^#=$+M&c{&h=?T{#fW$%8^wrdB^$YjSS1^+s9w^gWGqsf#^aE5{Wv0VNV*;? z)@rMCJ?2cNbp1HO@QQT(IHK^1bp1Gj@QQT(IAZXMbp1F&@X9Od79#M9bp1F2@Cv;G z{#WP~yuU)Pp!^kjMR@+CdNl^{eI{xQ;`{RgkUx|c!2BzD0nmRkFM#?_>AV2$ zKa&>#{%7+7$nSeOFM<8OPG6zFuhUuh|I%NiaT^2w@B9!O{J--pZHE4@e)D%^Lw{vZ z7y4t+Um4Vmp)u&M49eTapuaMx8$V;vUl|l_W6)n2)P?^T^j8LTAsK6FCWomE>PF5O z^j8LNt3S{m_bz2ae`Rn(HuP5pr3PcrUx}|{l2j}2K|*G(I1O3>->h455)S-*t!uo2K|*GspS~-SF~nh zg->9`OQs_NDvrKpEZ)Yn>U53WSe8dV02oS3MWvP zAT=FayqrK`Qfc&6UT^}H$#&1WtGPOX(xgVCKSTK_p*d;8ji{a<6Y)vyEW{ktC%86VSZ)c^5V=N5M`NepLV@R@2ZBl9-Eg^ z_n3xU-(y3xHWY!kGI%xbdM@`fsZF|&i+;%Btqi;De3_Cw07xp3AU#G8VcweWph<9J7w+MG%r@M%DU#Gulc3-E%NOoVR$H;YGr^`rnU#HKg zbAPAL$Z~(D&xmnt-hNG6QOs|@DHEyu_M5tl<_pX{-+uFuwxf+-=2$3V_5OP8=xNnJ2y62< zoCA*|f$>zg?`w>se6{(LwKZK#jiY&SYpA+)>R@Zp_kd2Kd~bg~9pEHl_ZB89x3h(f zzP*Wws=a}UoPFiD)D}ASHB1ETiS(%jV!#0f1X4X zUbBrP*hCNBu#FnLSw1@RBaJg^kV?eBN#p?fPND^G*hUGyW*Z&&qHR>*hp&EHM?eF@ z?i>{rpp4@p1aH$bRNyU4a;}{%Y*gS)OjO`)QGXHTm2QT%LmUuh|CnS3mLQC(^-BLHlq0 zl}u=VP2zS6+F#SipMv(^j&X;wQxN~#KcGF3|F#t16vTh=Z`C}!fA3Y9@cyN5$b|RT z-;xRLU-^nmX#dLBWSaIL%Y^pti;7dw{(YJP_V52d+HeZkzyDM14C`hO< z`+=?NjOxEh8*d<&&rZ`;ya1S*-b;64TQ?9)n+WD!N-bMA7$=*Il1;|QCL^?$et~S= zV0>(%mNwB!n@Huy1L`$WX%j!QiJRHP%k~~j)Go6L$$^zh?!zrMxdm_0U8LYv5-O*HW1#AJ&FGJ9-7uuY`T zCc5`=x^LFHf#}&p@oXY@Hqp9|hcwOzolTU&CQ4xwefxM+`yy^OQ8t^%noTtA<0IM^ zLBos}Sksi*L^W)p8XrqyZlW4C5si|<$4S0hH{sVN{Mv+H zoACSbx_Sk_HsRMM{Mt0XQk9$L7t{P=nqN%#{aF0ogkPKR`>_=1Cj8ojUz_l26Mmo1 z>`7*CLU5-5hMynSIm56`7`6$+HqEf~j%8(gKE^b|m}VFghM%WM*t$s)V-tp-PrBbw zoEO~k#47Z}TeI>JOiwJPCl-^5eJ-AEF|jt8*ykek787feiG40Nzs1DbWMcij?ba=( z_W4*E-djwqO{VsFdc>o3i>bBA)IL9^eVJODp4xQtq;<;^i|J{_^t57HnrYe6jLEdd zN5ZFfQ(n`$MXp*bVtTWp=jlh^7Au$a6pNVVtZ0uRW2%cqOn2ImNnyf^MM!rlVseo0 zVi6LaiZ~)NUMxb&QxSuLoEM9b^kNaRo)zseUPybf2zf6SA@R>8wYQLXn@GG(Bp&lU zBtC0y_j>Y%_mKHiB)5JKsZR%$N4DRR3K$^tWOvax4(UC@`^P1itScJH|WjoH2L!=f^~_gw>x*?r&|D9r8y*C2%K9d-??%VGD! z+H%-kx3U~|8`hP>o_(vzVb8ubWzdaUQ3la(SWO1qhP7k}0gbPOD6bxJ*R3N% zK8jUj$X&OF4DBgbeGdiM8*)F;kSM^DQIROXlhKjAVGeuSN;1r0Z(B!(IqYq#$S_B_ zZ4DXbD7UR3!yM%{`ms0Sqo5yAAeXw0enf#>>NfhZH{zq9A5oxlxs85AfzIVN`ms0a zqo5yAz){eTDBvjQ$KI%qf__8+M?pWLfTN%vdq;c}^keUc+dw~}fHu&ND4-4WWAC`z zKtG~@o}wR7Ku^(+P-W@qZS*4wI12g^1suit@!sBhNt16|KW@7K{n$I^0`%jhy%T#A z+6Mu7X>Z&mD9B5DCtZSsytH@9C1}V?d#7E3h`h8n;bWm9(ckG^<+-=fkyNy%5 zk(-hT z#k#d-MX?C2Sy3!PYgQDC&|0o2NbXukQH0c*6=jq$UF#@{#&j)L6nt?lR}{u{t)nQK zxwVd>XvEg6D28s$igMhCwW1tnD%Y$ihJVe9a@>c_6$OV`%N2!*TFVtBPZ8HTnlg>< ztaVgn8s%B*=!ywkLs|ARwv>*qSzDYU#%Dp~(@qgtB(f(@5!alX7`8R%Cd+QrxrsBv zHRmSI2-lpOI3rwhZeq69oSUq=QRgPk_tu=7IGbB@ZsKfi&AExQxiu?^v$-`ZiL<#i zD~Xf0H7kjew>2w?leaZ1iIcZAD~Xf0H7kjew>2xth9}EP;%sisO5$v8%}U~IZp}*K zY;Mg;;%sisO5$v8%}V0rZOuyJgwVTgC2^j$ZY6P^74ILAnvrL%TS=@}>sAtHQR`L` zXHn}`5@%8CRuT)?x|L)YKV7$yIB8n9k~nEvw~{z%TDOuoXsFB?#QODILp(&sf%(^wiDzlzz2%^5;(GZSg4Y6dbcQk|=))1o_FX;$?b@$d9 za>qxphTL%tG$fL9<@4)kNS@EHqak~_)1V=F{<@BaL;)Q`Lt;c2A~d8kjA+Q-K%UU8 zqasn{jBXtri6WsFFYV$QmiWRsY)ZY8tt%6BvC*Olw|h~pxi-8qCi&$cTkcj(3Qa*lqAkpGkgani5f}`@1P`cSAYVP zByIvwfRe-&Jq0L9T(8qplq3r1DM}KT=hQ$+qJSDGNhmxuP?9L121*ilC#ZpvL;*EW zk~lePpd|U+-a$z^bL&hbpIc`l`P`x;ow;=;lFzL(k$i5gBKh1}Me@0|isW-^70Kt; zDw5BwRV1HVt4Kb#R*`&ea~1JKSw-@>btaO}tuv8)Zk>tb{ct9d_rsY;-VbLYc|V+q zSl;e96S10kDaHJb&mCtXPJr%MKb!#Fv3@uKx?}ywdusj2dusj2dusjgjix)+59ca( ztRGkRbkF^c^}_`lrXkW!nA$K8k$6I;7bA?zy1d9tM3Hwi6_KF2$XrB<>b6WqRwT|h z%tlru(l<;;^vbo&N1@1sL>HjQD;8$Gf};&n5`*tfn3I?wH*Zp+$UB;qh;*)HS_(zx zB`eYm=?xPTMIH$=Qz$Yuk>a{}b5kfXIg#eNtqwc+*yr?grlZ4;g?+;Wg>QSH%}}Ap z6h)Dbm~+%AGD(qKd%Vn2q}SfjG!=@>Q@E{9+e8(Lh|3vMZp&0K#p3R>Tp_SXRW2P!_jh^V5d2ViA#gFVb_LHnBP@;)r*N z)^U^4Q+t$6b^uyGiMO zp-fqkcSM=88>TKonNks>iZVq}-Y=A?ScEdAB6dWXvTPO4@q(hMh*~I9ma)1MC{wY> z$`n61xh*SGCfkgvl_@Ktc`H*U$kehjWkuAoGKKoOmX#?hqL!5@#=*6$OfieDWo620 zv7?nKBAtt@OrdgaD_5pYk(DVE78@qDJ)jA}dpNj_SlF z%9Q;8<|33S`)$reC{y+epWCuBWjxXeN0^PhVV;ubx z8;TC=a$wVQnKW!ykIrm5&cs3TAL%wdpSL52KgxydW+&Ff-B_+nH$A6EqoyxtHyv-H zO~BZ6yh%kb?b6wo;*hDam-f0Ea45RIH~dnJ$8f5zYvE01e)snC6IpTkf#Dv>-v1zr zJg=g!RJZOSrImp}ZAV9cLlzSHZ)KsLzbXsi{G2Q_vobI%8^Nr^QmfgMo%c{n7Ex{8 zV*<6NYU>`8S02z)I`^2l@<4jbwB=RW|93UUY+*4?z1qh7{F*E#=Kqw%jQroSn2L&z z$vjjB^f1RgM!KR!V_Wwa-O9j(_6MrUfPC^EL{$cqcE`FL$ssEPdI;k_EL8@ik@o?q zq7Q_(?n6;!U_7;dAA~9cCuPG=W#E)-u=rnO5v%`KS%m4AelGUz6QgsR0G-=J=iDYV zYlQC;m9YrOSY7UQ|G#jr`?%L9)T=Icx{o`_)$Vt>(|z1Yu67@1vUkJ^Cuw9gQQTEa-WOE4=&Gz5y4&PyEiQ9Uvxt z<&+I2@f)XXILYpjsQ^^sCr{bHlHD;=0kp&~pt7MQyMLwvaETv8Wjh89xBy;W+M84b zc!?HN@Bm}+zs`t0Nt z(}^pllU7V8tjY!+I9bJXqN+y^9+0H&Vv?ZlD!q6>a%z*rbXPyRK7eMMz`U!6a36rO zO{Xtx1#E967Vh5B`GUDkD7OjYHbH!ML;C`_P58D6-8O-HSMO3h0BxI)jrq`AtMWs0 zo!%H}Jv7yrW*XB(V>*Gnm-_tB$s?v&#x%*8aJ;LB_hVUx6dY{AuuTZIX@2#<@4D>i@M{x(ZNjfj_}x>s_7Hw;!mmyEwF$p_dU*dK{Mv+HoA7HBe#t2x z!mmyEwF$pA;gR30MUmrn)DlCGoz+PNUWiV)n3tjZue1$1l`(ArrxKVg*i-_u1)9pHwjlFA^r#ug zR6@4}m`doj;8KZ~SDjPh#`v7Wmy2(Ckr0?Wr5=% zSJOh2!2kj zK%lb4Edczyw!zR~G1fQ5NKVNfzLJSr**=o-DBYiY%!6 zsw|-Unk<<6x-5|UeOVCq4Osy92eRPp4`qSdAIXBYZ^{C;Kb8e&-;xDlePv+7?0bf76b{NL^KVF}VLDZF@w7d{q_!^3P>K;A^rV z@Y}K=@H?^~@Vl}g@GoQm;P+$!;Onvg@GoT%7vGRYSp2>$qT-vf0PrnY0Qdu00Qj~n z0Q@Ui0Qf^$0Qe(W0Qh5B0QlFk0PrWW0Pt^Q0pL$%0pQ=t0>HnM1%SGK^rCM6m$m_* zt|lJ=;Qym-09d{MQ20z+T@KLSKw{XYUl->7*6jFq8>Y7ZRweqPJV zx$zym)+3ONuSTgpP*w&n%LdE%W|VBej1St$2F=P4`vYfu%1ztBGrm(K8$jbTKC(eH zzQ`jRNck3D>k*j7hm^D(Q2Cx^>k+6{^gYYgBVdj1;i*2j#%GLV11#UOYlP?o@L9+xy2WkQv2Jm%*Wwc+t6Im_bgjp&aE<}D}FEM#)s!xUe1kg ztF^qGTYMr)?ZY+S_iA}LH@;`t@^Wr`&$8v^+~PA^+8@68USMk*!ug(M%geb{^cAL- zmviHLmaT0#=X;i|ZAj;PmaT1A=X;i|ZD{9vmaT1g=W9_dFXzUGv0B?O&*#os+fZN8 z_bgkn3Y+c?>62b9FXzVhEL+>K&zGxO+tAN<#9Cg?Ej}=&@qqtQtay{%0NsZF_!zcq z_%99XGQi8ZvCva%8~*t|dTSg0S?H;?4gW0k)Y>K>$Ct&`KK!%LQ)?Uk`JQ^q%ek@8 zQ)?UkS?H#>4gW0k)bes}EcDd!a&9d2)bes}e80W54gY+Q)?Uk`QCeLn~)t}8drVzXQ8LoHvGp|%C()q9bZ6~ z4gc}^bJ_48-#(WO|MB7TWP5owzTw{T@@y>h)bjFdrC8A@^9L`_#zIdmFV9xeLQk#7 z#OIP0dTM$3wUQQkYI*rJ7J6!V`L$S0L+ulbS?HT+ZENP*q)?;`qX`!dqV|XlSp{Lekcr0n5r`BV5W1*+kV|Zhsr`BUq-I5l1 zYCVRZ_b=-3;PXDC2R-j!*H(h(OFti$4Lt8(kqJBRD|9^uoid^BF{qTM2UQ(Z%A=b4 z$ArqV)&y!j29>hb1Zq7dRF-3LpER|E%JQtxe+(++xv^|}`|RlfX-!JV!z8^+8F`pQ zhTo<#30}&{V=Ecn7uZTz_+3n*LQ2b{GES88@-WHrzD#A(yp);8R`R@*nuke1_$rmj z^ipyjTXC0#n^DNPR(r%QQym}Gq^Q4f<4kTUf!@yL`_hlxX`v^q@u@ik5#`A>;;Y(@RQ!&Z{N z?_%P+DYuTw_-;zB!^C%Ar!u~q((BlY>!$oVOkDT-RK|5vh87%A7JhL0Bh$5SUW$!+W7(2&JVD5et@;}1FW4NU~&FQfW`SI0T$<<1X!GZ z5@2!uNr1)qCjl1cp9EN(e-dDE{z-tv`6mIEKrjigurLX*un-@?PZuPxFbS}*FbS}* zFbS}*FbS}*FbS}*FbS}*FbS}*FbS}*FiF6Lg-Q5@g-Q5@g-Q5@g-Q5@g-Q5@g-Q5@ zg-Q5@g-L+L`6mGu=br>g+muu);+r;Rv6cgd==% z5{~f6NjSnMC*cU6oP;C$Bplf%;mAG-NA^iLvQNU1eG-oBlYnCpra%KXn*t5oYzj1RvnkNP&89#DH=6xj4J3C8H1N79(7@}aKm*C00u3a03N-M^DbPT2r$7VAodOL! zb_z7`*eTFJa;HE8$(tp#EC8%o09dmCu&^)< zu&^)Tufer_r209#g8t8D~X`sV_r-2R!o(4J`cpB(% z;Ax=4fv15E2c8Bx9C#Y&aNud6!-1!P4hNnFIvjW!=y2dzia0qT4m=HXc3_~h0|T8M z80hT4KxYRAIy*4X*@2U?{T#Lm3V{16IiO3|QgMGhl^3&wv&FJOftv^9*qi z2cCfxTy%!ehKtS++HlbsIKo9|;0PCuwMWyy;-WKfgp1Aq4lX(aIJoEx;NYS=oT z299vi892g4XW$4Ioq;1udfR#NKtjITJz{(B`R(4>pvIB#a9T=?az+ioo{IfuZ^Uneu&OZxuIR7lr;rz2ehx5-8%g8roVGtH(32m@2 zOK5|ISwb5u%mOSd%mOSd%mOSd%mOSd%mOSd%mOSd%mOSd%o6xuVHSR2VHSR2VHSR2 zVHSR2VHSR2VHSR2VHSR2VHSR2VHSR2VHSR2VHSR2VHSR2VHSR2VHSR2A?}AKy@G{V z_=Sa8_=Sa8_=Sa8_=Sa8_=Sa8_=Sa8_=Sa8_=Sa8_=Sa8_=Sa8_=Sa8_=Sa8_=Sa8 z_=Sa8_=Sa8_=Sa8_=Sa8_=Sa8_%#dgYZlfM2r!zh(h`%>w+I1^6`!@M{*} z*DS!VS%67% z%)>7%%)>7%%)>7%%)>7%%)>7%%)>7%%)>7%%)>7%%)>7%%)>7%%)>7%%)>7%%)>7% z%)>7%%)>7%%)>7%%)>7%%)>7%%)>7%%)>7%%)>7%%)>7%%)>7%%)>7%%)>7%%)>7% z%)>7%%)>7%%)>7%%)>7%%)>7%%)>7%%)>7%%)>7%%)>7%%)>7%%)>7%%)>7%%)>7% z%)>7%%)>7%%)>7%%)>7%%)>7%%)>7%%)>7%%)>7%%)>7%%)>7%%)>7%%)_r)fM2r! zzh(h`%>w+I1^6`!@M{*}*DS!VS%6=&0KaAde$4{>!omXl!omXl!omXl!omXl!omXl z!omXl!omXl!omXl!omXl!omXl!omXl!omXl!omXl!omXl!omXl!omXl!omXl!omXl z!omXl!omXl!omXl!omXl!omXl!omXl!omXl!omXl!omXl!omXl!omXl!omXl!omXl z!omXl!omXl!omXl!omXl!omXl!omXl!omXl!omXl!omXl!omXl!omXl!omXl!omXl z!omXl!omXl!omXl!omXl!omXl!omXl!omXl!omXlng#eZ3-D_e;MXj`uUUX!vjD$l z0e;N_{F(*$H4E@-7U0(`z%MK;!Y?c=!Y?c=!Y?c=!Y?c=!Y?c=!Y?c=!Y?c=!Y?c= z!Y?c=!Y?c=!Y?c=!Y?c=!Y?c=!Y?c=!Y?c=!Y?c=!Y?c=!Y?c=!Y?c=!Y?c=!Y?c= z!Y?c=!Y?c=!Y?c=!Y?c=!Y?c=!Y?c=!Y?d%@g!LA;z_Wu2*0qf2*0qf2*0qf2*0qf z2*0qf2*0qf2)}=STTcQl0`Py@#D>wG32Y$UnZ$OQ)14V?K;4_YZ1!me>xqJD0HGcIPrS;O<<(hTNU2*r2F zo`K(;HMRrr&i}{WyLQKMUDu-D@hjR6V}LOwp+OQ4$+2$ZWigRNhNLVzk}d~Cf+B1Z zU;xmPVtb5)5Iy9?Aq*j50uo)wA>kxWEEo?uK{p;8LZeAavYnjVaqm%LqSmfmReP_w=G?n>Ro9x4Ee3Dgl`RTy{6w}myz$SnMdA$w z#4WLSV^;e``+Amsfepj}5U3}LY zppiLtu!iQ?f#P=TFp)cMfY=>(Q1p%+AbvYTZ;9Z}&|6}-GxU}i?hL)9G1?h=OM|pC z^p-1t7<$VUKn%U*3Lu8was?1WZ@B`9p|@NC#L!!=0AlDZR{$~emMef5ddn3+487$F zAco#@1@QK{0*IlvTmi(;Tdn|N=q*-?|d^5JN{@0mRTzR{$|| z)D=Js9hCya3>}pOI73Gz0nX4-Nq{qS)Fr?h;1VDS8kGdZ4sZp~7&__-pfPmR6+mO? zs1(2%Iw}QlhK@=BoS~zx0AlEIxu+j=BPfp`)$-?Ixu+j=BPfp`)$< zV(6$8AZF;OOMnJlJ^j=BVhp`$JV&QO;CXQ)pCG=`440yslm0i2<(0M1ZX05LS} z3Lu82T>-?D}Wf9b_Eba(^7z#p=qB4$iZnzfHO2L32=s{T>`uTE&*a_+9v^;M5J8- z#L%=WfEb!~1rS5iQUGUYS_D}Wf9b_Eba)2;v- zL({GRVrbeGKnzX00*Ik$R{$|I?Ft}-?sd?D}Wf9b_Eba z)2;wwXxbIP8R`n)40Q!?hPnbcLtO!!p{@YVP$_^jG?D^1LnA4GGc=L{I71^TfHO3b z0ysk>R{#+tas?1WBUb=1G;#$HLnA3b%+Sau0djC832=r+k^pCD7!d#L&nUKn#ss0mRVA6+jG)Tmi(; z$Q3{gja&i5&`1gpGc#2XyghYhDNRcVrV1a?X426y;Wkhw@Re;R*BQzDpA^7B}RLzL}+i7`0T9`oxN3Jv$slQ_Ew3@-YQYq zTO}rYt3+gPm3Zu}5{Z#Y)Ny;UNuw@RG#R*ACSDlyhuCBk~E#8+!oG%LE=$Bup( z&5Eu+H$9DJMOXV69rlZ^KmRe~GMW`T?bA@$FLv6;++d5H_CY$>V&~8QMaPSs_5mpD z7d!34Td>7W`K?W0$)#a{a$9Bk3oK3oP{{Iw5?!4`q-LvFCeVEbqo zY*E-gT?bnnwvWca7KwlUkLr(DY#;u^e$n{n6WT8x+lTV7UqrTV!@(Ale?F`FqVmse zc!*}j<$v|_iOiNO8pn&x|F@05(X8nFua1`p9OEkn$M}k~|7!9b&5D*Wej;LwmzWsi zB`U^viHk8_B4dn~*cjs_I>va3k1<{%WQ>;>8RI1m#`uVYF+O5pjE`s-<0In5c!+B5 z|4aoRG8OTisfg!HMf_$e;x$tdubGN?%~Zr^rXoHw74hg_j`jXzD&ot(4;%NVB7XcE zupTd&ig?LX#7m|kUNRN&l97%(-!m2Qlc|WGOhvq8D&i$m5igmFc*#`6i+>u{<0VrO zFPVyX$yCHkrXpT474ed(h?k6%+WDKQj2~&W^EV@PcD}|ReYN>*6o2(qdK`cB^+%=; z*$O@OPrf>TvlaTBtut6|Z~b$uuD4lDfqZ<- zR`4lX!KZ8mpRyHv%4#0u<72jhU)c(NW%u)I44)U`icIa3?B5tZFJ$&1+0yT%+u~21 z+q(NW?6&xG#BK5CsN3RCz1y1KG`KDPG`g+%O_SS-|C-c6HBbboY4!p!pr*wOM1h)C zFAxW6+Ppv{sA=~Cv7qKlULYFOJm&@CLCrBQ5D{vA&kMwan&0;VQK9B}FVNhi<^?a% z;8OFV7ie};bKDCwJ*he21-jp;`2#Ny!D>!=fkvR3Q(hp7)tvSMjY%~xd4Wh*^JOm( z>uO&10@1GKD_$Vp)qK?pM7)|m^a71s_c(WIN#@^A*#|hheO=SH8nc}Lvbxl+~rM+V6X_+GG zOESgNFUu57UzRDBenqB8`c;|Ys8F~jiazJ((vbIp+Zyst$@AAVenLgf3vvX$!$b{h6(H}*ZU5s>=l;i^{<-zxUHJ~*^V28&emRCG z`#etH^h1G8^wD?yD2dI7@Bg(Ef#T-Ff3d~Vv3I36|2F%+YDjQC9JJwo>|G82AAQg6_r~6p1pWI@ye|@= zkG?ysb4Z4M@prbsHukQB=ode$M|g;D z1(Kv+;ODYdAW{18yVEL=EPZ(6pH(1X`kx>q5BaY1%EL&n4`>~)3OH2o!srtgRmoHqd+WYTHUuWXf+n3Jwbie)TiJpsZpYOVO z@zrD9{TE+-v8VS^$HlHc?(9u;^d&AoQ&rVh0{m(%YTr?fj!FUA>)&GiO?lojh~q$?B(G@9F99>+kKj+*tc0E?@mj z)wk_zpE-Y}`)q$#PxqP5x4QcJ`)aDYN^u#=c*5eD|q!pyrXx2wVDICPFMFE zhY|hRUKKR&H$1Qt& zi{mO4m-tm%`?&L{slO@IAKT4e3!G;SoJ0)Lvt8Z&ACJ`22?Wo@PlZ>Fo&Dh6>1%L$ zm?0AWQ{elx`eUv9*7=T$px$HRa1Yr})2XVKp386dcD-?-KT&h`V4|)zdGrbV)0AlM zc)hE$J8`0?zq9*nS7&ck)rrpDOI>|^U|gcBFL9x>xAXP46L0i(boY0jJCr!z+u51u zIiEOtp`-VW&O?d*oK=k@-Mu5OSk(UCZdD^{Uq{{@_;ujhRKn;l?$ zNB22sMPJX^t`3|pajxg=l}nx7{T+ef2`EcvU!tb}LTBQ$r*y<;5Aq`CIy){_b#>!x z5FaJr$d3N44S9b!8VB`)=x z>pK57{&uokm#@5jv8(UGp~N|8PH)%iSNc)2FBG2b><&kvA5Zo4Ci*%rUaUe-yU=d- zb+2`-6Z`|;;QDe=UnqI=LeHiBJ%NkPLl|+%&U2w<=X&5WUh!+4XZyo3q2=d$E?(?; zGj#22Pxra5;IqD`tEx_8SI6r;-{@pt4CU^gel(TtCID=Np{iYd7dkFpOuXJHhXD+@ z7e#S*(7P+I_w@t#u8xa|%RRlkUS&6*#Py$ROPo61e){Fq$+pC?Q;8ENkAL}CYg=pL zv#C?CKYJ+g^0Cv;9e?R`0yR#iUOfHP#PRk->cy`ne*f5ut%nkAUpaBI?bNBn@sm}@ zUO4gmu{M+)d$HyDms*d#_@zWMj(hR=>BRHLUO09dCp~>U5iY1x9c#mJ?THuKPPROU zGpCx5J%8-`Tv`PCR$~`Bsc^ ziRL!6EYYI#0&?1e*#*3=8BFNM~fOdQ8qPF96#*1E*Y&$Wf3aJ>}%zvVQB z%FvCL<1d~*3G)zodGd68=*!1WwH->NP98fI9MXRB_zQ=sf;({p&W@AdxEI^(EWy=u zP>cww1c$wZPJ3Gtt!=62aW25~;y(C*X62aB-Epb2?{Wu%>7hx$3kZ)elw#oj_5M5G z)s4_Pdl8C|@RL=2vx@9H8-l#AKk-8InU>V4wlnQ;IEFvYE{?r;`pgTdui(0ud%L~? zB~jh-imz0?QvKh5Qhg{&E zQG&-vD8Zv~{ENvqN-#;sHs?%-%5qchqK_sua!9iqy_sE2O#N?weB~b zhls&-uT-Z_wH!MZ&h*mh_9q&vag6)V54qUU{l=A!H#$+hy1VlVW@HyJ7pts=L9??L z(6#9Tp|+hew1P63&>7?T*rnIc0MKuA_V%Cgc42aZSy+FMpC(MndV4OpiL-WJxp?s) zB7*ZuOei{fdpq8)*;nge;#=QJ>@P}u=6CJT#OFSjuu(LWAXqQ=^zE+-UmU_TEX*AD zofX%Hwx56cX*By>XFo)^qSfUM#iaS$iB1mIk2%X5o&Dac{nrnNR#lbxAinUvL-u@x z1L%$FW{{%(2v!WxvwclxU;1#K*E{+;>yM!Mh=NY|8i($yh$>|lj%VEa&s=upz4^P& zOdNG)P1mcRJN?4*Pe4D9orW~4b$jOsSh31OdqS%N%8xujxw9)zQNHg{Fjl|Z)qSq# z&Aum+bw}%XU%|^*xH3}qIVJYqt6d>n_oL;^dFrWSUwZNQNvz#rCTS4*%|dVI8)teu zFJFXUo;i!*^!1LjU$3ct>J>;&?GvxQ)!zQv=byr1uber9apKgeGiPwl3;mZa#^Dto z1y;vr|5Todo38WqclE!$hyJ0&tAOQ9%kdXZq%gpUuaAg{n8|=ty?V4S%7O=Xa1XoW zlZD_L9lc#0uVd0zQ+BQj6*!dm9I~(;K-IpW1P6M)j=1gbz0&zi71iL(n;jRw4ws+3 z(%aYd4Mbz8iHWLi8xu93JMfKzVX7CldcKac?}cN9?Aw9A@@nYHv-`Q5c-r)eWPH5;jM8T=bzSPb(AjaWvp29C&e;bScbDT|hY2M$ z?Leh|TL?e?dPem9*F(?4O+a5~7+Qi(2Up-Wq3?WWuWQ}jo{L@Go##6-$&BlQm6-U; z4voiD;*B>td(U+C_V)DFRKwp7U-n?QCwct}>YaNsapGbp6pql6tJYzO5^7@h0Ox>g zueP1R?C0be%o#D^`_d=%R^IK#oeAzL&`9vehN_?lmpU$^r@Ts-iSHkyf*(v%`oh@r z84Ou{P=O8t#FCmq^^GtlR-ZZ3-*bv{D-BK$JLi$DJecTw z>oQJ_)tHW6Ox!R`Cz9dz0rPyDtKsBNzKXG>|Lx0E?kB4gZ-6%4k93m!(%sL^&;M?y_D#%+bl@uox?W45w9HO? zK2e9+>;97DV`_y7R_F(v^N6webH7#d?9;WsQ}asg6HTvu{@}OX(ssua=TlE$ygqoa z=Go7D?%*@ehJkx;TpXc9r6pT$M7l+pH7T0X8-?LpFg!j{sZ)x0~QrJ5M~c`MMcHHVx^B6Rz7vJk7!s4tI?eu-M`sD zOTqfz1511Z8LzD)m zssp$puJ$UXPuS9JG*+CiTnv-fFsxU-c>xR89L_L|p%TSEv=kK%^iiO{5E|HsW;~1e z7zE$@@rw3;F6>7$!=QEr0*xcN`WhyVLCx^La0p}=?FwUIu)+WsD*8f2PcI%xg`<=UC*Y-x(uZ80|!aiy+`Pyq?N?JLX;1~@i2hJWpf8HkjVG#Lz*&q@sg;6dX zZlmBUxbEi@heN02u_w`Vk?`3>AJ&z+`_I=@f9|*5`t7eg6J{$q`$6~!rPWV*!IRa` zz|%Nwe^>XF&O}`pXuiGgtfAi=eqzQyc9^oZ{2F7G9mrwwQ!I0x#odX)ho=j8W)Kg6 zz$8c#Z)gAa1ajXpN&xRwU-HEw64uqj`c`mYV3o@NZpkiop6q-BcRO;XO@OKN#J#Kr z=~!mRl)==ywWGhIrkXoj0GFMznh1Z)obf=}*(%m@xCd)EeVu#jH@-HwuX8V*IDsbs zkLui&%XnJzm~L^O>5FQ`cM;YVRtQ~DCvdxpQypM2BEGyScBDJo*4NA1LkB?id_Aki z4u}`oF+21Xg}t(5*{YSJg@;y;?GX2|Ltd+*Lo9x{Lo6nR{VFc0=(nW@kO(ErMBi#g8qr4kA?R`OK=S31MyS+b8UN%R$@YU+1`c2 zKp(ECn=E@6@Q9OO+1(a3!*dW{26)8PfXu^3V0HA1U@rLEeV>ijyY`Owq!Hbpiq#wlTj;J2-}})HJIneZZ~4I{R(L7l z>;<%T&tK2mDZ!fl9;%vMh5QcVNTT{mcUSe(RkrO9<({spIQ@PQ!b$&E1K=Hf7y#c> z2|y@6_=(QP)61B1eKlq4+^;C?gDQR8b4SPj459%J#nT2a2`B3lBm{utWZC`zr#T)E zj2dGU?CH_d9H zk3h=eFm6fQZCvCrRHi4zL#y(r2cS4VrakfLFz0`?z<3MGdV|f-x=%pt;YY6hVFdB> z>4A1&AxZ%pUj9L!9$xC%cMA}X3T~-9ja*TFax|}erc}Pd7 ztL31ER&<}ebeYV=cy#R4@h2J^k2XDl7B)Sqd7LObG@d-3i3&K}L5GKF`+WI0wJ)z! z#UNZfo5lC9@WTC zl1&`LH(V}syp6k}v*BTPHy*lP42usvzHC!lra}Mfd;Q5xv`(twXyD!|sP$$4Gd3#= zZVh03%;a+a1H&_CUcf7O$6Le8=T9fvE?w?_J8`a)Z;E#ILR21pWb9-8UJy%OYAM7r z>{(d=?-PJ11L0Gk`blSloBqpT{iHTpHv>u@i=P3wxU0hZ*A|=sMc)^eEj0+2AM%JJ z%6ESu!Jip~qFOwO_LJcb(};}8jPpu?!)rQUV#0@dypgRr;QJ2I!`9GB@ccSujSgM$ zLqGnsI`~kcE_tNkNaNx9BMp`3$E@-I;tA)euC~_;to32q^_Y;adREVELx&#^O{_~* zPe1G;Z~y-0>?4A;CbSK2vPfdECL9Vr-K#8U-I>127rVmS5TGc3mqGgsB>Ij13+QvQ z?D{ZWrUS@fTkl)&;D=tiU@70S_{{z{FZOm+zJ8%re42MK_8R{yKiISD|LSjFBtuwN zI7lQaFa4DTu{yj(E|=p?c1{QSI^PH{=g0TcZ(x=X-YDt6(9xa1uYP(SDU=V*x*mNO zU%hwtUI$6AOYhw}?z>j4EoN5S<;1ZYxaI}|1mt~qt!7Vs_N|qa9rkfUJyoJNJWj5D zss;}tv;kptV9(7bd`At^&HV)ZWC-KDZCb%bz9eb7WyrcX~PgZ~X z;8R!naIkZZ(`i$kMYU4!a*a$&1%h!XvaYq+LODzIbQ|Pp!iH$zeXq*Jd9jj{rJtwnec{Q$60VTJga+zbn^a~w~6uveqGnyiGZ`8 zvBmdZ!6Ur=I*XexRw=*f^7Xx-$d4J00{)f45UPiF$v{>#k823k7kj$j&|LZ~-}JTD z@4VXixeF>|B9L_78vZpqm4Sfpl>xb)yzjoQrCh(e)uOf!Gs4w@Ru@a+?L0?mc zfIQ~mz2Uu}m{%eA%D^l`r94DGPK-1G{&nv=;`y}CaLSs=@svTK_C8xSD+$l7t=Pgp z;Aj1V1ajUb%U>Cb=UU|yI2fK8(<|1G$DDz)YAqR8wu)jBJ9KZ_v~R+w{ob;;`rZs3 zMPzyGcQ#EG@hqaek^9f)WFb-Zmi%JR`ZXRK2W0{9DWs(96YeDtLyw#N`xFjU9wT#J z!yo09KM&mB%)MK($DGP0rJsBvSAzI77y>?_i~CywmwycVFtx%7%V+L;cfoiqHeM+# zJ7+ly!`!vZTVZ&5T;s~U4jlLQ3yTJ)uDl*DSQF;MxLVl8pFNN7McyY5X1#cb_G|a4 zZ1DTfP!`>sz*UdIF$B3Us6HZ$T!}n@;Mej=KaSQFoLG;&P#LC1tbJqre>5arzX>L~ zg)0v=>BT3yp`196kIwSw`p*r`#}dSE3I9JJP>+dh1l*(71U_D&{sRQ+ahi#^4hq|H6UXnMNo6~oiF+CqG%lQg3gv84pzhV$tMYhZ2KzAAJKuvJ`{2h1(AaAH zKB}5;L4{%zyi!&D(48BmeqYqs_0U6ed{*N^fB)q(_!0Q!EB&lleHlL{4WGFv+rb|m z^4B5Z!hKfn|E%fne5?PdZ*^S8>cd(70R5@A!Y`*@{rp>(E*=c02=98I!SAYjFZSSf zB%vL(m8EC!GjUzGyC)IXMYT&ET^Bk3#>;YcMSE!IyX=QM@xwo>q965no!5KLz5Uo4 z_zXwq8y&hv+&WkKGyE;iMfS_ief<3S&?$cw%R&$e}!u~mI*@gEmwtpLc>kij7>2HdE+ZwIH;o8G(q2O@s5i3X?Y2gv6 z7M-HCA$2&^Xm3(Md#ar^+S`(L2sU`~_O`=bP;WKb{gC$hTAri5O*Puv>e-I)xf-^# zuD!jz)mq|3hgtL*e_T_|2c(A*VfEzz!03cZ`bb;Dg2WfTkVuC6ioKbO-*#vn%7pb} zV5ZhW!uVU7GR5^-LOIMK$jV-jFg1Vdv7dBl3+gji?K^joO9*)V7B|MaU+lk9{+q+_ z$}e|*Lez4`6mF%pVxs=wlROD8<>jBll@7O8oDwlx``DAV?K^3*?onqASHmmOJoP7E zG%RJ3Zr1Ad{TFC_%mtElA9ICI-GKeClI@qOk5`b`*;v-|MKo8C{qp3pp*0kjaQorV z<4M3b+sjuGDniEBbpJyet&^4osu3o{T9TSw_6WCZ?0VH&Yn3hJ@Yss5Ug^e{H1K=y zmfwML_*@J9!X)cR_zD3YqV(aXU>E|h{$VkV5U}5M*&ERESPfRp0zIBUV6}r8L1r3* z&R|C$erv`ZjoMKc?>M|~M?Duj`d&HWrGdM4RYdkDT(#|?lkdN(pFFm#6Qvdm2UU&CRphDuQ_e@pI|tqdpfQ8^FvXjUhLhyD-I{0xte)N|FYT}d~T;dELE(%W-VTVtl?TgQFGOrYVs~BGQe&ja< z5b6Fz#9vbmKYHO3v4xWf`X!dy(;6&Lhil^@iK%(m_xaDihEw^EJoe&66xcgFtYst6 zb<874P`JRa$^7a$kLCyQ1Hwny0(uD`%lU6 z-$ODyawln?Q7sJg`KISB*{f#K_hdRpPLnoC=H~{>pJw>S3AvvQ5mn1>2K3b-tPqC}?UV~no8H^rn<;w>_g1leXe=8ubR*_> zzkU{8K6K9qxP31)mL0GCtY1DM#7#nfPvuLke)rYbeC#xC%l6%qgcp~ZkG+^W`PGU& zfd+kYIkm-HdkrN5=6@24*DySz*K zZ0gc&?$g&&m-yY^C4D4yiQo8L(zj8UnA&SepF~~yJ07Pmp)UPBEqwrW=^tq6^UO;> zq@}MjFa0AeeVcjdpJ;v08ol%*T7MzyeOlLLP0-ROnU{V{OJ7!9;x})X^jX!VDelu3 zk(cm2EqG#BXW>_Am;O@LUGCFYkeBdPE!5N}keB#%+NHlyO?;0F`$lB(^T12`(D4#K zpSz^594|5d*OESQyu|EZOZvX?5_5ko>Ep&r%>1=1jF$OT-esY(jBkNeRWA#NfbWIyS)6j=Cfr+-STZ&jI-r&S&XdZzmvsSTK)%FjGkqE zM0uI)~@!VwszkF9!3H7o*j=TIX?2^8IyG%Hj^-a2W+mdLWgZzZn?vK2zMBFI(<*@_@r zA!I9pY=w}m2(lGIwj#(@2-%7tTOnjCf^3D5tq8IeLbf8vRtVXOAX_11D}ro=kgW)^ z6+*Tm$W{p1iXdAdWGjMfg^;ZXvK2zMBFI(<*@_@rA!I9pY=y*K5n?MuY(ZxUUlTRpI`Dc~BoQU!{Dn>Z{iu@T=&nih>Wwj@21m zfr7oNZ(OfZqF41z>krIliiy<>Jvgu9D7uw5gXYr
Yr=2sZ+M)BnYEj=G_b6V(h^hG$ka6< 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
L>4S)Q|TDIYjA5_aW{PE*y z*@i!I@R*IPJi#Bx!6V!7M-Cp@hCg!f$Ts|ugGaXEj~qO*4S(d|k!|=R2ajySA31nr z8~(__Biryt4j$Qte>TR~_=+Sv+ArJjLs}m8$MHj29@>r{((=%D{E(K1w&RDiJhUA@ zq~)RQ_#rJ1ZO0F3d1yO+NXtXp@k3f3+KwO6^3Zntkd}wGiXB<)Q8PAuUg^ zk%Y(kkF-3r9Y3Vyq3!q~Ee~zS4{3R5JAO#ZL)-B~S{~YtAJX#BcKnc*hqmK~v^=yO zKcwZM?f4-r4{hf!((=%D{vs_8ZO0dBd1yO-k(P(H^A~A(XgmH$%R}4oM_L}*jz7}! z(02ThmWQ_EkF-3r9eB9@?6H6p)sOw#N_B^3eAC2BhVo?eT!LJhc730cm+?dwd`*4{g71Kw2K!9xq7C zL)+)&_!uE=#~U9br0w(adBe-UfmoQgd9i;3u`qA=`8N;?^ENN`Zy*+smWSu__`t^q zY5Tkw9|NTAc;jP$w0&NTv^=yOf28H1?ek)!<)Q8IfV4cceO`>TJhVMNkd`OdNWx?I z+BX;r`1GS}!xu?-WE;KI;0{M#p%4(rB4j5-ULo4<0OSp#?cGP-Allvy zx(H4jf1lJ%fMNX=t5lJ(G54??mY+G12l)!B@1g=9Un z#i)?1hqf3MlJ(FQqe8MC+G12l)!B@fg=9Un#jKF5hqeMTBoOq~=k5(<`Lrk!^Z~)I73Hucn-zlcrZl&7=LMS5wZ< zNz*H&=J|ivd)M!}jx0ayJN}A84kUmhf+R@lG3XZ6AP7hpkpKdKt(FP(U6;TGaht%s zn0qfsu^($CY{zk4M4XQ8IO%cXB$LF61W$`}9=%55T5-JW<<-%A@%a1gkNl#4A-~^# zRMmOh2PkPK6E|mC7pG30s$Kh4yLRoWZSPfIAvKRZtG+^N9(z`Oh15LutorI#E}ws@ z`Ur%L~)PXDJ$|EEs>r%L~)PXDJ$|EEs>r%L~) zPXDJ$|EEs>r%HcZXKVedw!_u6_N?^BHMRDv^ndE~f2#C<>hyoA^ndE~f2#C<>hyoA z^ndE~f2#C<>hyoA^vBh()}PWJ7qQy2$`dYKwP%&)r%o@uMfJDOOndhqYkSWOzx~Im zy`H&k|5)39X87(uR_*o7w0Hlp^4l}RfB&&+uV<#c`;V3Xo*6y*k5wN$Gwt1fto-=Q z=+l3!`r(;r@BU-u&u2!j{$m~AGt=Jv$I7qIjDG#cI^Jidz59=qf1epW`;XPydS=?Y z|5*9?nbEiZSoURp`^>a=|FQD-GoyF^vFywI_L*t#{$u6$XGZ`2W7SvBOndhqEB`+; ze&|0|>+P9o@BU+zhiAqg{l{v(Ju~gyf2{KH%=o4MSgp5broH=*RbHMM|MVZL_4drP zcmJ`<&okqv{$sV?p1Jw{bCsuO#$WxPtM&5C*DgO-`Fdvj*8jO$FVD=n>i=Bj?V0JH z{?FBVd1m^j|8uogo|%5>|6HsKCTtS^WC|zf4<>A~XX%d#o9tQIXTm0X7I|jECVLin zX2K?W7I|jECVLinX2K?W7I|jECVLinX2K?W7I|jECVLinX2K?W7I|jECVLinX2K?W z7I|jECVLinX2K?W7I|jECVLk7W5OnT7I|aBCVLk7V!|eS7I|XACVLk7VZtVR7I|U9 zCVLk7c!7jX`ivA##5WQ)*|Xvs37hO$@r{H{_N@3u!X|rGd?R6#JuAMEu*sek-$>YG z&x&s(Y_ey?Hxf44v*H^Go9tQfjf741toTO4CVN(VBVm(0E53i}_(KXOj_;R_zh5f; zkg!SXBZZT^cl`ZQ@%Kx|A5u8Ud&l1|6@S0{C;q+Y6DDl3_C!B0VUscHm41Kh^g{|KdGGW?3MYAX`XPmrJUji6!bzT;en{aY&rUz2 zaFS=IA5u8Uv&#!oILWii3sN}Av&#!oILWiq8!4RR+3AfGPV(&ZMhYi+c6uX)lRP`U zk-|xyo!&^{B+pK7q;QgFTTQ({3MYBC)e}=V$+Oc7DVz9=1Wk%>q->IBrx#K-$+Oc7 zDVyZk>4lU{@@(_l3#4q4XU9KsG|98$A32)j+2*4c$OR*+pk?dLJ1(}iTS>*+pk?dLJ1(}iTS@Dm|NcxOiNrVTfj_g_HBc?jCXIU*X)sa2R z{LM^G`uq=NzJKwlt@is*bo`(GO5gXNh`sdUQ(Nu#pC~>)HT}?kBKFdYPi?i|f1>#L z)W+X`BKFdYPp$v{6UEo3hL8Rev6o(aYODSJ6UE=BhM)cuv6o(aYW&%MqWt`+;j8~d z?4=i<8o&0RD1U!y`0GCrd+Eiew%YGMQGWl_@Y#PN_R@|xeow&XDMCpBE{NI0~_ @rly^#N?s>MC_#(CvGo3QGPfveb#@X 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 Date: Fri, 25 Nov 2022 13:16:00 +0000 Subject: [PATCH 10/22] initial setup but class not found --- .../scratchpads/scratchpads_species/modules/iucn/iucn.info | 1 + .../scratchpads_species/modules/iucn/iucn.module | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/sites/all/modules/custom/scratchpads/scratchpads_species/modules/iucn/iucn.info b/sites/all/modules/custom/scratchpads/scratchpads_species/modules/iucn/iucn.info index a2462445f6..9004cb15f4 100644 --- a/sites/all/modules/custom/scratchpads/scratchpads_species/modules/iucn/iucn.info +++ b/sites/all/modules/custom/scratchpads/scratchpads_species/modules/iucn/iucn.info @@ -3,6 +3,7 @@ name = "IUCN Widget" description = "Displays an International Union for Conservation of Nature widget on a taxonomy term page." package = "Widgets" dependencies[] = scratchpads_species +dependencies[] = libraries tool=yes l10n server = localize.scratchpads.eu l10n url = http://localize.scratchpads.eu/files/l10n_packager/l10n_server.xml diff --git a/sites/all/modules/custom/scratchpads/scratchpads_species/modules/iucn/iucn.module b/sites/all/modules/custom/scratchpads/scratchpads_species/modules/iucn/iucn.module index dabe9d164e..1b8e772627 100644 --- a/sites/all/modules/custom/scratchpads/scratchpads_species/modules/iucn/iucn.module +++ b/sites/all/modules/custom/scratchpads/scratchpads_species/modules/iucn/iucn.module @@ -1,7 +1,5 @@ '); + $iucnClient = new \IucnApi\Client($iucnV3APIKeyToken); + + //$githubclient = new \Github\Client(); if(!function_exists('ajaxblocks_in_ajax_handler') || (function_exists('ajaxblocks_in_ajax_handler') && ajaxblocks_in_ajax_handler())){ $cache = cache_get($term->tid, 'cache_iucn'); From a9dc67b2a2d2266e61358b9958c47d035b74de7d Mon Sep 17 00:00:00 2001 From: Rob Davis Date: Fri, 25 Nov 2022 15:23:30 +0000 Subject: [PATCH 11/22] #6564 getting the iucn custom module to see the IUCNClient from https://github.com/jbroutier/iucn-api-client and also getting that project to run on php7.2 which is the version of php that's in production --- .gitignore | 16 ++++++++++++++++ .../libraries/vendor/composer/platform_check.php | 3 +++ .../scratchpads_species/modules/iucn/iucn.module | 2 ++ 3 files changed, 21 insertions(+) diff --git a/.gitignore b/.gitignore index 46424bf616..447f24c270 100644 --- a/.gitignore +++ b/.gitignore @@ -39,3 +39,19 @@ sites/default/files/css/ # files and assets sites/default/files + +# this library had to have code modified to be compatible with php 7.2 +# and therefore attempting to manage its git repp +# within the main scratchpads repo raised the question about where +# to store that modification. give that the modification is +# related to scratchpads' production server environment, +# the answer to that question is to store the change in +# scratchpads' repo. Moreover the original developer may not +# necessarily want to access a push up to their repo of this +# backwards compatible change to obsolete php version. +# Therefore with that decision to store in Scratchpads, that means +# disabling the project's own git (i.e. as a submodule), so the files +# will be removed and not tracked +sites/all/libraries/vendor/jbroutier/iucn-api-client/.git +sites/all/libraries/vendor/jbroutier/iucn-api-client/.github +sites/all/libraries/vendor/jbroutier/iucn-api-client/.gitignore \ No newline at end of file diff --git a/sites/all/libraries/vendor/composer/platform_check.php b/sites/all/libraries/vendor/composer/platform_check.php index 580fa96095..0204806b37 100644 --- a/sites/all/libraries/vendor/composer/platform_check.php +++ b/sites/all/libraries/vendor/composer/platform_check.php @@ -4,9 +4,12 @@ $issues = array(); +// credit: https://stackoverflow.com/a/68961924/227926 +/* if (!(PHP_VERSION_ID >= 70400)) { $issues[] = 'Your Composer dependencies require a PHP version ">= 7.4.0". You are running ' . PHP_VERSION . '.'; } +*/ if ($issues) { if (!headers_sent()) { diff --git a/sites/all/modules/custom/scratchpads/scratchpads_species/modules/iucn/iucn.module b/sites/all/modules/custom/scratchpads/scratchpads_species/modules/iucn/iucn.module index 1b8e772627..15c31b60ea 100644 --- a/sites/all/modules/custom/scratchpads/scratchpads_species/modules/iucn/iucn.module +++ b/sites/all/modules/custom/scratchpads/scratchpads_species/modules/iucn/iucn.module @@ -1,5 +1,7 @@ Date: Mon, 28 Nov 2022 02:56:38 +0000 Subject: [PATCH 12/22] #6564 initial working IUCN fetch, based on https://github.com/jbroutier/iucn-api-client --- .../vendor/jbroutier/iucn-api-client | 1 - .../jbroutier/iucn-api-client/.editorconfig | 9 + .../iucn-api-client/CHANGELOG-1.x.md | 3 + .../vendor/jbroutier/iucn-api-client/LICENSE | 21 + .../jbroutier/iucn-api-client/README.md | 68 + .../jbroutier/iucn-api-client/composer.json | 55 + .../jbroutier/iucn-api-client/phpstan.neon | 5 + .../iucn-api-client/phpunit.xml.dist | 20 + .../jbroutier/iucn-api-client/src/Client.php | 684 + .../iucn-api-client/src/ClientInterface.php | 412 + .../src/Exception/IucnApiException.php | 7 + .../iucn-api-client/src/Model/Assessment.php | 45 + .../iucn-api-client/src/Model/Citation.php | 24 + .../iucn-api-client/src/Model/CommonName.php | 38 + .../src/Model/ConservationMeasure.php | 31 + .../iucn-api-client/src/Model/Country.php | 31 + .../iucn-api-client/src/Model/Group.php | 24 + .../iucn-api-client/src/Model/GrowthForm.php | 24 + .../iucn-api-client/src/Model/Habitat.php | 52 + .../src/Model/NarrativeText.php | 81 + .../iucn-api-client/src/Model/Occurrence.php | 53 + .../iucn-api-client/src/Model/Region.php | 31 + .../iucn-api-client/src/Model/Species.php | 59 + .../src/Model/SpeciesDetails.php | 231 + .../iucn-api-client/src/Model/Synonym.php | 52 + .../iucn-api-client/src/Model/Threat.php | 66 + .../tests/Functional/ClientTest.php | 675 + .../tests/Functional/responses.json | 67769 ++++++++++++++++ .../tests/Unit/AssessmentTest.php | 24 + .../tests/Unit/CitationTest.php | 18 + .../tests/Unit/CommonNameTest.php | 22 + .../tests/Unit/ConservationMeasureTest.php | 20 + .../tests/Unit/CountryTest.php | 20 + .../iucn-api-client/tests/Unit/GroupTest.php | 18 + .../tests/Unit/GrowthFormTest.php | 18 + .../tests/Unit/HabitatTest.php | 26 + .../tests/Unit/NarrativeTextTest.php | 34 + .../tests/Unit/OccurrenceTest.php | 26 + .../iucn-api-client/tests/Unit/RegionTest.php | 20 + .../tests/Unit/SpeciesDetailsTest.php | 80 + .../tests/Unit/SpeciesTest.php | 28 + .../tests/Unit/SynonymTest.php | 26 + .../iucn-api-client/tests/Unit/ThreatTest.php | 30 + .../modules/iucn/iucn.module | 32 + 44 files changed, 71012 insertions(+), 1 deletion(-) delete mode 160000 sites/all/libraries/vendor/jbroutier/iucn-api-client create mode 100644 sites/all/libraries/vendor/jbroutier/iucn-api-client/.editorconfig create mode 100644 sites/all/libraries/vendor/jbroutier/iucn-api-client/CHANGELOG-1.x.md create mode 100644 sites/all/libraries/vendor/jbroutier/iucn-api-client/LICENSE create mode 100644 sites/all/libraries/vendor/jbroutier/iucn-api-client/README.md create mode 100644 sites/all/libraries/vendor/jbroutier/iucn-api-client/composer.json create mode 100644 sites/all/libraries/vendor/jbroutier/iucn-api-client/phpstan.neon create mode 100644 sites/all/libraries/vendor/jbroutier/iucn-api-client/phpunit.xml.dist create mode 100644 sites/all/libraries/vendor/jbroutier/iucn-api-client/src/Client.php create mode 100644 sites/all/libraries/vendor/jbroutier/iucn-api-client/src/ClientInterface.php create mode 100644 sites/all/libraries/vendor/jbroutier/iucn-api-client/src/Exception/IucnApiException.php create mode 100644 sites/all/libraries/vendor/jbroutier/iucn-api-client/src/Model/Assessment.php create mode 100644 sites/all/libraries/vendor/jbroutier/iucn-api-client/src/Model/Citation.php create mode 100644 sites/all/libraries/vendor/jbroutier/iucn-api-client/src/Model/CommonName.php create mode 100644 sites/all/libraries/vendor/jbroutier/iucn-api-client/src/Model/ConservationMeasure.php create mode 100644 sites/all/libraries/vendor/jbroutier/iucn-api-client/src/Model/Country.php create mode 100644 sites/all/libraries/vendor/jbroutier/iucn-api-client/src/Model/Group.php create mode 100644 sites/all/libraries/vendor/jbroutier/iucn-api-client/src/Model/GrowthForm.php create mode 100644 sites/all/libraries/vendor/jbroutier/iucn-api-client/src/Model/Habitat.php create mode 100644 sites/all/libraries/vendor/jbroutier/iucn-api-client/src/Model/NarrativeText.php create mode 100644 sites/all/libraries/vendor/jbroutier/iucn-api-client/src/Model/Occurrence.php create mode 100644 sites/all/libraries/vendor/jbroutier/iucn-api-client/src/Model/Region.php create mode 100644 sites/all/libraries/vendor/jbroutier/iucn-api-client/src/Model/Species.php create mode 100644 sites/all/libraries/vendor/jbroutier/iucn-api-client/src/Model/SpeciesDetails.php create mode 100644 sites/all/libraries/vendor/jbroutier/iucn-api-client/src/Model/Synonym.php create mode 100644 sites/all/libraries/vendor/jbroutier/iucn-api-client/src/Model/Threat.php create mode 100644 sites/all/libraries/vendor/jbroutier/iucn-api-client/tests/Functional/ClientTest.php create mode 100644 sites/all/libraries/vendor/jbroutier/iucn-api-client/tests/Functional/responses.json create mode 100644 sites/all/libraries/vendor/jbroutier/iucn-api-client/tests/Unit/AssessmentTest.php create mode 100644 sites/all/libraries/vendor/jbroutier/iucn-api-client/tests/Unit/CitationTest.php create mode 100644 sites/all/libraries/vendor/jbroutier/iucn-api-client/tests/Unit/CommonNameTest.php create mode 100644 sites/all/libraries/vendor/jbroutier/iucn-api-client/tests/Unit/ConservationMeasureTest.php create mode 100644 sites/all/libraries/vendor/jbroutier/iucn-api-client/tests/Unit/CountryTest.php create mode 100644 sites/all/libraries/vendor/jbroutier/iucn-api-client/tests/Unit/GroupTest.php create mode 100644 sites/all/libraries/vendor/jbroutier/iucn-api-client/tests/Unit/GrowthFormTest.php create mode 100644 sites/all/libraries/vendor/jbroutier/iucn-api-client/tests/Unit/HabitatTest.php create mode 100644 sites/all/libraries/vendor/jbroutier/iucn-api-client/tests/Unit/NarrativeTextTest.php create mode 100644 sites/all/libraries/vendor/jbroutier/iucn-api-client/tests/Unit/OccurrenceTest.php create mode 100644 sites/all/libraries/vendor/jbroutier/iucn-api-client/tests/Unit/RegionTest.php create mode 100644 sites/all/libraries/vendor/jbroutier/iucn-api-client/tests/Unit/SpeciesDetailsTest.php create mode 100644 sites/all/libraries/vendor/jbroutier/iucn-api-client/tests/Unit/SpeciesTest.php create mode 100644 sites/all/libraries/vendor/jbroutier/iucn-api-client/tests/Unit/SynonymTest.php create mode 100644 sites/all/libraries/vendor/jbroutier/iucn-api-client/tests/Unit/ThreatTest.php diff --git a/sites/all/libraries/vendor/jbroutier/iucn-api-client b/sites/all/libraries/vendor/jbroutier/iucn-api-client deleted file mode 160000 index d2b95768b4..0000000000 --- a/sites/all/libraries/vendor/jbroutier/iucn-api-client +++ /dev/null @@ -1 +0,0 @@ -Subproject commit d2b95768b40d3c4606030a626e7c99393deef1de diff --git a/sites/all/libraries/vendor/jbroutier/iucn-api-client/.editorconfig b/sites/all/libraries/vendor/jbroutier/iucn-api-client/.editorconfig new file mode 100644 index 0000000000..677e36e295 --- /dev/null +++ b/sites/all/libraries/vendor/jbroutier/iucn-api-client/.editorconfig @@ -0,0 +1,9 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +indent_size = 4 +indent_style = space +insert_final_newline = true +trim_trailing_whitespace = true diff --git a/sites/all/libraries/vendor/jbroutier/iucn-api-client/CHANGELOG-1.x.md b/sites/all/libraries/vendor/jbroutier/iucn-api-client/CHANGELOG-1.x.md new file mode 100644 index 0000000000..972cb5eba9 --- /dev/null +++ b/sites/all/libraries/vendor/jbroutier/iucn-api-client/CHANGELOG-1.x.md @@ -0,0 +1,3 @@ +**Version 1.0.0 (2022-09-09)** + +- Initial stable release. diff --git a/sites/all/libraries/vendor/jbroutier/iucn-api-client/LICENSE b/sites/all/libraries/vendor/jbroutier/iucn-api-client/LICENSE new file mode 100644 index 0000000000..39452a57fe --- /dev/null +++ b/sites/all/libraries/vendor/jbroutier/iucn-api-client/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 Jérémie BROUTIER + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/sites/all/libraries/vendor/jbroutier/iucn-api-client/README.md b/sites/all/libraries/vendor/jbroutier/iucn-api-client/README.md new file mode 100644 index 0000000000..a4d2abc6da --- /dev/null +++ b/sites/all/libraries/vendor/jbroutier/iucn-api-client/README.md @@ -0,0 +1,68 @@ +[![PHP](https://github.com/jbroutier/iucn-api-client/actions/workflows/php.yml/badge.svg)](https://github.com/jbroutier/iucn-api-client/actions/workflows/php.yml) +[![codecov](https://codecov.io/gh/jbroutier/iucn-api-client/branch/develop/graph/badge.svg?token=20HBOP8NB3)](https://codecov.io/gh/jbroutier/iucn-api-client) +[![PHP version](https://img.shields.io/badge/php-7.4+-787cb5?logo=php)](https://github.com/jbroutier/iucn-api-client) +[![License](https://img.shields.io/github/license/jbroutier/iucn-api-client)](https://github.com/jbroutier/iucn-api-client/blob/main/LICENSE) +[![Packagist](https://img.shields.io/packagist/v/jbroutier/iucn-api-client)](https://packagist.org/packages/jbroutier/iucn-api-client) + +# IUCN API Client + +A PHP client to retrieve data from [The IUCN Red List of Threatened Species™](https://www.iucnredlist.org/). +It currently supports the version 3 of the API as described in +the [API reference](https://apiv3.iucnredlist.org/api/v3/docs), with the IUCN database version 2022-1. + +## Disclaimer + +:warning: This project is not supported or endorsed in any manner by the IUCN. :warning: + +## Installation + +```bash +composer require jbroutier/iucn-api-client +``` + +## Requirements + +An API key (token) is required to authenticate yourself and be able to use the IUCN API. To obtain an API key, please +use the [application form](https://apiv3.iucnredlist.org/api/v3/token) and submit your request to the IUCN. + +## Getting started + +Start by creating an instance of the client. + +```php +$client = new IucnApi\Client(''); +``` + +You can pass an array of options to the underlying HTTP client, if you want to configure the timeout for example. See +the [Symfony HTTP client](https://symfony.com/doc/current/http_client.html) documentation for a list of available +options. + +```php +$client = new IucnApi\Client('', ['timeout' => 5000]); +``` + +You can then request the details of a species for example. + +```php +try { + $species = $client->getSpeciesByName('Ailurus fulgens'); +} catch (\IucnApi\Exception\IucnApiException $exception) { + // Something doesn't look good… +} + +echo $species->getKigdom(); // ANIMALIA +echo $species->getPhylum(); // CHORDATA +echo $species->getClass(); // MAMMALIA +echo $species->getOrder(); // CARNIVORA +echo $species->getFamily(); // AILURIDAE +echo $species->getGenus(); // Ailurus +echo $species->getMainCommonName(); // Red Panda +``` + +The full list of available methods is described in +the [ClientInterface](https://github.com/jbroutier/iucn-api-client/blob/main/src/ClientInterface.php) interface. + +## Documentation + +The official API documentation is available [here](https://apiv3.iucnredlist.org/api/v3/docs). The IUCN Red List +database changelog can be found [here](https://apiv3.iucnredlist.org/changelog). diff --git a/sites/all/libraries/vendor/jbroutier/iucn-api-client/composer.json b/sites/all/libraries/vendor/jbroutier/iucn-api-client/composer.json new file mode 100644 index 0000000000..4a799580d4 --- /dev/null +++ b/sites/all/libraries/vendor/jbroutier/iucn-api-client/composer.json @@ -0,0 +1,55 @@ +{ + "name": "jbroutier/iucn-api-client", + "description": "A PHP client to retrieve data from The IUCN Red List of Threatened Species™.", + "version": "1.0.0", + "type": "library", + "license": "MIT", + "authors": [ + { + "name": "Jérémie BROUTIER", + "email": "jeremie.broutier@posteo.net", + "homepage": "https://github.com/jbroutier", + "role": "developer" + } + ], + "repository": { + "type": "git", + "url": "https://github.com/jbroutier/iucn-api-client.git" + }, + "require": { + "php": "^7.4|^8.0", + "doctrine/collections": "^1.7.2", + "symfony/http-client": "^5.4|^6.0" + }, + "require-dev": { + "ext-json": "*", + "ekino/phpstan-banned-code": "^1.0.0", + "phpstan/extension-installer": "^1.1.0", + "phpstan/phpstan": "^1.8.4", + "phpstan/phpstan-deprecation-rules": "^1.0.0", + "phpstan/phpstan-phpunit": "^1.1.1", + "phpstan/phpstan-strict-rules": "^1.4.3", + "phpunit/phpunit": "^9.5.24" + }, + "autoload": { + "psr-4": { + "IucnApi\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "IucnApi\\Tests\\": "tests/" + } + }, + "config": { + "allow-plugins": { + "phpstan/extension-installer": true + } + }, + "scripts": { + "phpstan": "vendor/bin/phpstan analyse --memory-limit 1G", + "tests:all": "vendor/bin/phpunit --testdox --coverage-html coverage", + "tests:functional": "vendor/bin/phpunit --testsuite=functional --testdox --coverage-clover coverage-functional.xml", + "tests:unit": "vendor/bin/phpunit --testsuite=unit --testdox --coverage-clover coverage-unit.xml" + } +} diff --git a/sites/all/libraries/vendor/jbroutier/iucn-api-client/phpstan.neon b/sites/all/libraries/vendor/jbroutier/iucn-api-client/phpstan.neon new file mode 100644 index 0000000000..08566d07a4 --- /dev/null +++ b/sites/all/libraries/vendor/jbroutier/iucn-api-client/phpstan.neon @@ -0,0 +1,5 @@ +parameters: + level: 9 + paths: + - src + - tests diff --git a/sites/all/libraries/vendor/jbroutier/iucn-api-client/phpunit.xml.dist b/sites/all/libraries/vendor/jbroutier/iucn-api-client/phpunit.xml.dist new file mode 100644 index 0000000000..7684269da4 --- /dev/null +++ b/sites/all/libraries/vendor/jbroutier/iucn-api-client/phpunit.xml.dist @@ -0,0 +1,20 @@ + + + + + src + + + + + tests/Functional + + + tests/Unit + + + diff --git a/sites/all/libraries/vendor/jbroutier/iucn-api-client/src/Client.php b/sites/all/libraries/vendor/jbroutier/iucn-api-client/src/Client.php new file mode 100644 index 0000000000..a4c0b829b7 --- /dev/null +++ b/sites/all/libraries/vendor/jbroutier/iucn-api-client/src/Client.php @@ -0,0 +1,684 @@ += php 7.4: protected HttpClientInterface $httpClient; + protected $httpClient; + + /** + * Create a new instance of the client. + * + * @param string $token The API authentication token. + * @param array|null $options An optional array of options. These options will be passed directly to the underlying HTTP client. + */ + public function __construct(string $token, ?array $options = null) + { + $options = array_merge_recursive($options ?? [], [ + 'headers' => [ + 'accept' => 'application/json', + ], + 'query' => [ + 'token' => $token, + ], + ]); + + $this->httpClient = HttpClient::createForBaseUri('https://apiv3.iucnredlist.org/api/v3/', $options); + } + + public function getCountries(): Collection + { + try { + $response = $this->httpClient->request('GET', 'country/list'); + $data = $response->toArray(); + } catch (ExceptionInterface $exception) { + throw new IucnApiException($exception->getMessage(), $exception->getCode(), $exception); + } + + $collection = new ArrayCollection(); + + foreach ($data['results'] as $result) { + $country = Country::createFromArray($result); + $collection->add($country); + } + + return $collection; + } + + public function getComprehensiveGroups(): Collection + { + try { + $response = $this->httpClient->request('GET', 'comp-group/list'); + $data = $response->toArray(); + } catch (ExceptionInterface $exception) { + throw new IucnApiException($exception->getMessage(), $exception->getCode(), $exception); + } + + $collection = new ArrayCollection(); + + foreach ($data['result'] as $result) { + $group = Group::createFromArray($result); + $collection->add($group); + } + + return $collection; + } + + public function getPlantGrowthFormsById(int $id, string $region = null): Collection + { + $url = 'growth_forms/species/id/' . $id; + + if (!is_null($region)) { + $url .= '/region/' . $region; + } + + try { + $response = $this->httpClient->request('GET', $url); + $data = $response->toArray(); + } catch (ExceptionInterface $exception) { + throw new IucnApiException($exception->getMessage(), $exception->getCode(), $exception); + } + + $collection = new ArrayCollection(); + + foreach ($data['result'] as $result) { + $plantGrowthForm = GrowthForm::createFromArray($result); + $collection->add($plantGrowthForm); + } + + return $collection; + } + + public function getPlantGrowthFormsByName(string $name, string $region = null): Collection + { + $url = 'growth_forms/species/name/' . $name; + + if (!is_null($region)) { + $url .= '/region/' . $region; + } + + try { + $response = $this->httpClient->request('GET', $url); + $data = $response->toArray(); + } catch (ExceptionInterface $exception) { + throw new IucnApiException($exception->getMessage(), $exception->getCode(), $exception); + } + + $collection = new ArrayCollection(); + + foreach ($data['result'] as $result) { + $plantGrowthForm = GrowthForm::createFromArray($result); + $collection->add($plantGrowthForm); + } + + return $collection; + } + + public function getRegions(): Collection + { + try { + $response = $this->httpClient->request('GET', 'region/list'); + $data = $response->toArray(); + } catch (ExceptionInterface $exception) { + throw new IucnApiException($exception->getMessage(), $exception->getCode(), $exception); + } + + $collection = new ArrayCollection(); + + foreach ($data['results'] as $result) { + $region = Region::createFromArray($result); + $collection->add($region); + } + + return $collection; + } + + public function getSpeciesAssessmentsById(int $id, string $region = null): Collection + { + $url = 'species/history/id/' . $id; + + if (!is_null($region)) { + $url .= '/region/' . $region; + } + + try { + $response = $this->httpClient->request('GET', $url); + $data = $response->toArray(); + } catch (ExceptionInterface $exception) { + throw new IucnApiException($exception->getMessage(), $exception->getCode(), $exception); + } + + $collection = new ArrayCollection(); + + foreach ($data['result'] as $result) { + $assessment = Assessment::createFromArray($result); + $collection->add($assessment); + } + + return $collection; + } + + public function getSpeciesAssessmentsByName(string $name, string $region = null): Collection + { + $url = 'species/history/name/' . $name; + + if (!is_null($region)) { + $url .= '/region/' . $region; + } + + try { + $response = $this->httpClient->request('GET', $url); + $data = $response->toArray(); + } catch (ExceptionInterface $exception) { + throw new IucnApiException($exception->getMessage(), $exception->getCode(), $exception); + } + + $collection = new ArrayCollection(); + + foreach ($data['result'] as $result) { + $assessment = Assessment::createFromArray($result); + $collection->add($assessment); + } + + return $collection; + } + + public function getSpeciesByCategory(string $category): Collection + { + try { + $response = $this->httpClient->request('GET', 'species/category/' . $category); + $data = $response->toArray(); + } catch (ExceptionInterface $exception) { + throw new IucnApiException($exception->getMessage(), $exception->getCode(), $exception); + } + + $collection = new ArrayCollection(); + + foreach ($data['result'] as $result) { + $result['category'] = $data['category']; + $species = Species::createFromArray($result); + $collection->add($species); + } + + return $collection; + } + + public function getSpeciesByComprehensiveGroup(string $group): Collection + { + try { + $response = $this->httpClient->request('GET', 'comp-group/getspecies/' . $group); + $data = $response->toArray(); + } catch (ExceptionInterface $exception) { + throw new IucnApiException($exception->getMessage(), $exception->getCode(), $exception); + } + + $collection = new ArrayCollection(); + + foreach ($data['result'] as $result) { + $species = Species::createFromArray($result); + $collection->add($species); + } + + return $collection; + } + + public function getSpeciesByCountry(string $country): Collection + { + try { + $response = $this->httpClient->request('GET', 'country/getspecies/' . $country); + $data = $response->toArray(); + } catch (ExceptionInterface $exception) { + throw new IucnApiException($exception->getMessage(), $exception->getCode(), $exception); + } + + $collection = new ArrayCollection(); + + foreach ($data['result'] as $result) { + $species = Species::createFromArray($result); + $collection->add($species); + } + + return $collection; + } + + public function getSpeciesById(int $id, string $region = null): ?SpeciesDetails + { + $url = 'species/id/' . $id; + + if (!is_null($region)) { + $url .= '/region/' . $region; + } + + try { + $response = $this->httpClient->request('GET', $url); + $data = $response->toArray(); + } catch (ExceptionInterface $exception) { + throw new IucnApiException($exception->getMessage(), $exception->getCode(), $exception); + } + + if (count($data['result']) === 0) { + return null; + } + + return SpeciesDetails::createFromArray($data['result'][0]); + } + + // public function getSpeciesByName(string $name, string $region = null): ?SpeciesDetails + public function getSpeciesByName(string $name, string $region = null) { + $url = 'species/' . $name; + + if (!is_null($region)) { + $url .= '/region/' . $region; + } + + try { + $response = $this->httpClient->request('GET', $url, ['base_uri' => variable_get('iucn_api_v3_url', FALSE), 'query' => ['token' => variable_get('iucn_api_v3_api_key_token', FALSE)]]); + $data = $response->toArray(); + } catch (ExceptionInterface $exception) { + throw new IucnApiException($exception->getMessage(), $exception->getCode(), $exception); + } + + if (count($data['result']) === 0) { + return null; + } + + return SpeciesDetails::createFromArray($data['result'][0]); + } + + public function getSpeciesCitationById(int $id, string $region = null): ?Citation + { + $url = 'species/citation/id/' . $id; + + if (!is_null($region)) { + $url .= '/region/' . $region; + } + + try { + $response = $this->httpClient->request('GET', $url); + $data = $response->toArray(); + } catch (ExceptionInterface $exception) { + throw new IucnApiException($exception->getMessage(), $exception->getCode(), $exception); + } + + if (count($data['result']) === 0) { + return null; + } + + return Citation::createFromArray($data['result'][0]); + } + + public function getSpeciesCitationByName(string $name, string $region = null): ?Citation + { + $url = 'species/citation/' . $name; + + if (!is_null($region)) { + $url .= '/region/' . $region; + } + + try { + $response = $this->httpClient->request('GET', $url); + $data = $response->toArray(); + } catch (ExceptionInterface $exception) { + throw new IucnApiException($exception->getMessage(), $exception->getCode(), $exception); + } + + if (count($data['result']) === 0) { + return null; + } + + return Citation::createFromArray($data['result'][0]); + } + + public function getSpeciesCommonNames(string $name): Collection + { + try { + $response = $this->httpClient->request('GET', 'species/common_names/' . $name); + $data = $response->toArray(); + } catch (ExceptionInterface $exception) { + throw new IucnApiException($exception->getMessage(), $exception->getCode(), $exception); + } + + $collection = new ArrayCollection(); + + foreach ($data['result'] as $result) { + $commonName = CommonName::createFromArray($result); + $collection->add($commonName); + } + + return $collection; + } + + public function getSpeciesConservationMeasuresById(int $id, string $region = null): Collection + { + $url = 'measures/species/id/' . $id; + + if (!is_null($region)) { + $url .= '/region/' . $region; + } + + try { + $response = $this->httpClient->request('GET', $url); + $data = $response->toArray(); + } catch (ExceptionInterface $exception) { + throw new IucnApiException($exception->getMessage(), $exception->getCode(), $exception); + } + + $collection = new ArrayCollection(); + + foreach ($data['result'] as $result) { + $conservationMeasure = ConservationMeasure::createFromArray($result); + $collection->add($conservationMeasure); + } + + return $collection; + } + + public function getSpeciesConservationMeasuresByName(string $name, string $region = null): Collection + { + $url = 'measures/species/name/' . $name; + + if (!is_null($region)) { + $url .= '/region/' . $region; + } + + try { + $response = $this->httpClient->request('GET', $url); + $data = $response->toArray(); + } catch (ExceptionInterface $exception) { + throw new IucnApiException($exception->getMessage(), $exception->getCode(), $exception); + } + + $collection = new ArrayCollection(); + + foreach ($data['result'] as $result) { + $conservationMeasure = ConservationMeasure::createFromArray($result); + $collection->add($conservationMeasure); + } + + return $collection; + } + + public function getSpeciesCount(string $region = null, bool $includeSubspecies = true): int + { + $url = 'speciescount'; + + if (!is_null($region)) { + $url .= '/region/' . $region; + } + + try { + $response = $this->httpClient->request('GET', $url); + $data = $response->toArray(); + } catch (ExceptionInterface $exception) { + throw new IucnApiException($exception->getMessage(), $exception->getCode(), $exception); + } + + return intval($includeSubspecies ? $data['count'] : $data['speciescount']); + } + + public function getSpeciesHabitatsById(int $id, string $region = null): Collection + { + $url = 'habitats/species/id/' . $id; + + if (!is_null($region)) { + $url .= '/region/' . $region; + } + + try { + $response = $this->httpClient->request('GET', $url); + $data = $response->toArray(); + } catch (ExceptionInterface $exception) { + throw new IucnApiException($exception->getMessage(), $exception->getCode(), $exception); + } + + $collection = new ArrayCollection(); + + foreach ($data['result'] as $result) { + $habitat = Habitat::createFromArray($result); + $collection->add($habitat); + } + + return $collection; + } + + public function getSpeciesHabitatsByName(string $name, string $region = null): Collection + { + $url = 'habitats/species/name/' . $name; + + if (!is_null($region)) { + $url .= '/region/' . $region; + } + + try { + $response = $this->httpClient->request('GET', $url); + $data = $response->toArray(); + } catch (ExceptionInterface $exception) { + throw new IucnApiException($exception->getMessage(), $exception->getCode(), $exception); + } + + $collection = new ArrayCollection(); + + foreach ($data['result'] as $result) { + $habitat = Habitat::createFromArray($result); + $collection->add($habitat); + } + + return $collection; + } + + public function getSpeciesNarrativeTextById(int $id, string $region = null): ?NarrativeText + { + $url = 'species/narrative/id/' . $id; + + if (!is_null($region)) { + $url .= '/region/' . $region; + } + + try { + $response = $this->httpClient->request('GET', $url); + $data = $response->toArray(); + } catch (ExceptionInterface $exception) { + throw new IucnApiException($exception->getMessage(), $exception->getCode(), $exception); + } + + if (count($data['result']) === 0) { + return null; + } + + return NarrativeText::createFromArray($data['result'][0]); + } + + public function getSpeciesNarrativeTextByName(string $name, string $region = null): ?NarrativeText + { + $url = 'species/narrative/' . $name; + + if (!is_null($region)) { + $url .= '/region/' . $region; + } + + try { + $response = $this->httpClient->request('GET', $url); + $data = $response->toArray(); + } catch (ExceptionInterface $exception) { + throw new IucnApiException($exception->getMessage(), $exception->getCode(), $exception); + } + + if (count($data['result']) === 0) { + return null; + } + + return NarrativeText::createFromArray($data['result'][0]); + } + + public function getSpeciesOccurrencesById(int $id, string $region = null): Collection + { + $url = 'species/countries/id/' . $id; + + if (!is_null($region)) { + $url .= '/region/' . $region; + } + + try { + $response = $this->httpClient->request('GET', $url); + $data = $response->toArray(); + } catch (ExceptionInterface $exception) { + throw new IucnApiException($exception->getMessage(), $exception->getCode(), $exception); + } + + $collection = new ArrayCollection(); + + foreach ($data['result'] as $result) { + $occurrence = Occurrence::createFromArray($result); + $collection->add($occurrence); + } + + return $collection; + } + + public function getSpeciesOccurrencesByName(string $name, string $region = null): Collection + { + $url = 'species/countries/name/' . $name; + + if (!is_null($region)) { + $url .= '/region/' . $region; + } + + try { + $response = $this->httpClient->request('GET', $url); + $data = $response->toArray(); + } catch (ExceptionInterface $exception) { + throw new IucnApiException($exception->getMessage(), $exception->getCode(), $exception); + } + + $collection = new ArrayCollection(); + + foreach ($data['result'] as $result) { + $occurrence = Occurrence::createFromArray($result); + $collection->add($occurrence); + } + + return $collection; + } + + public function getSpeciesSynonyms(string $name): Collection + { + try { + $response = $this->httpClient->request('GET', 'species/synonym/' . $name); + $data = $response->toArray(); + } catch (ExceptionInterface $exception) { + throw new IucnApiException($exception->getMessage(), $exception->getCode(), $exception); + } + + $collection = new ArrayCollection(); + + foreach ($data['result'] as $result) { + $synonym = Synonym::createFromArray($result); + $collection->add($synonym); + } + + return $collection; + } + + public function getSpeciesThreatsById(int $id, string $region = null): Collection + { + $url = 'threats/species/id/' . $id; + + if (!is_null($region)) { + $url .= '/region/' . $region; + } + + try { + $response = $this->httpClient->request('GET', $url); + $data = $response->toArray(); + } catch (ExceptionInterface $exception) { + throw new IucnApiException($exception->getMessage(), $exception->getCode(), $exception); + } + + $collection = new ArrayCollection(); + + foreach ($data['result'] as $result) { + $threat = Threat::createFromArray($result); + $collection->add($threat); + } + + return $collection; + } + + public function getSpeciesThreatsByName(string $name, string $region = null): Collection + { + $url = 'threats/species/name/' . $name; + + if (!is_null($region)) { + $url .= '/region/' . $region; + } + + try { + $response = $this->httpClient->request('GET', $url); + $data = $response->toArray(); + } catch (ExceptionInterface $exception) { + throw new IucnApiException($exception->getMessage(), $exception->getCode(), $exception); + } + + $collection = new ArrayCollection(); + + foreach ($data['result'] as $result) { + $threat = Threat::createFromArray($result); + $collection->add($threat); + } + + return $collection; + } + + public function getVersion(): string + { + try { + $response = $this->httpClient->request('GET', 'version'); + $data = $response->toArray(); + } catch (ExceptionInterface $exception) { + throw new IucnApiException($exception->getMessage(), $exception->getCode(), $exception); + } + + return $data['version']; + } + + public function getWebsiteLink(string $name): ?string + { + try { + $response = $this->httpClient->request('GET', 'weblink/' . $name); + $data = $response->toArray(); + } catch (ExceptionInterface $exception) { + throw new IucnApiException($exception->getMessage(), $exception->getCode(), $exception); + } + + if (!array_key_exists('rlurl', $data)) { + return null; + } + + return $data['rlurl']; + } +} diff --git a/sites/all/libraries/vendor/jbroutier/iucn-api-client/src/ClientInterface.php b/sites/all/libraries/vendor/jbroutier/iucn-api-client/src/ClientInterface.php new file mode 100644 index 0000000000..275a2453a6 --- /dev/null +++ b/sites/all/libraries/vendor/jbroutier/iucn-api-client/src/ClientInterface.php @@ -0,0 +1,412 @@ + + * + * @throws IucnApiException If any error occurs. + */ + public function getCountries(): Collection; + + /** + * Get a list of comprehensive groups. + * + * @see https://apiv3.iucnredlist.org/api/v3/docs#comp-groups + * + * @return Collection + * + * @throws IucnApiException If any error occurs. + */ + public function getComprehensiveGroups(): Collection; + + /** + * Get a list of growth forms for a plant species, by species ID. + * + * @see https://apiv3.iucnredlist.org/api/v3/docs#growth-forms-id + * + * @param int $id The species ID. + * @param string|null $region An optional region identifier. + * + * @return Collection + * + * @throws IucnApiException If any error occurs. + */ + public function getPlantGrowthFormsById(int $id, string $region = null): Collection; + + /** + * Get a list of growth forms for a plant species, by species name. + * + * @see https://apiv3.iucnredlist.org/api/v3/docs#growth-forms-name + * + * @param string $name The species name. + * @param string|null $region An optional region identifier. + * + * @return Collection + * + * @throws IucnApiException If any error occurs. + */ + public function getPlantGrowthFormsByName(string $name, string $region = null): Collection; + + /** + * Get a list of regions. + * + * @see https://apiv3.iucnredlist.org/api/v3/docs#regions + * + * @return Collection + * + * @throws IucnApiException If any error occurs. + */ + public function getRegions(): Collection; + + /** + * Get a list of historical assessments for a species, by species ID. + * + * @see http://apiv3.iucnredlist.org/api/v3/docs#species-history-id + * + * @param int $id The species ID. + * @param string|null $region An optional region identifier. + * + * @return Collection + * + * @throws IucnApiException If any error occurs. + */ + public function getSpeciesAssessmentsById(int $id, string $region = null): Collection; + + /** + * Get a list of historical assessments for a species, by species name. + * + * @see http://apiv3.iucnredlist.org/api/v3/docs#species-history-name + * + * @param string $name The species name. + * @param string|null $region An optional region identifier. + * + * @return Collection + * + * @throws IucnApiException If any error occurs. + */ + public function getSpeciesAssessmentsByName(string $name, string $region = null): Collection; + + /** + * Get a list of species by category. + * + * @see http://apiv3.iucnredlist.org/api/v3/docs#species-category + * + * @param string $category The category code. + * + * @return Collection + * + * @throws IucnApiException If any error occurs. + */ + public function getSpeciesByCategory(string $category): Collection; + + /** + * Get a list of species by comprehensive group. + * + * @see https://apiv3.iucnredlist.org/api/v3/docs#comp-groups-species + * + * @param string $group The group code. + * + * @return Collection + * + * @throws IucnApiException If any error occurs. + */ + public function getSpeciesByComprehensiveGroup(string $group): Collection; + + /** + * Get a list of species by country. + * + * @see http://apiv3.iucnredlist.org/api/v3/docs#countries-species + * + * @param string $country The country, as an ISO 3166-1 alpha 2 country code. + * + * @return Collection + * + * @throws IucnApiException If any error occurs. + */ + public function getSpeciesByCountry(string $country): Collection; + + /** + * Get a species, by species ID. + * + * @see http://apiv3.iucnredlist.org/api/v3/docs#species-individual-id + * + * @param int $id The species ID. + * @param string|null $region An optional region identifier. + * + * @return SpeciesDetails|null + * + * @throws IucnApiException If any error occurs. + */ + public function getSpeciesById(int $id, string $region = null): ?SpeciesDetails; + + /** + * Get a species, by species name. + * + * @see http://apiv3.iucnredlist.org/api/v3/docs#species-individual-name + * + * @param string $name The species name. + * @param string|null $region An optional region identifier. + * + * @return SpeciesDetails|null + * + * @throws IucnApiException If any error occurs. + */ + public function getSpeciesByName(string $name, string $region = null); + + /** + * Get the citation for a species, by species ID. + * + * @see http://apiv3.iucnredlist.org/api/v3/docs#species-citation-id + * + * @param int $id The species ID. + * @param string|null $region An optional region identifier. + * + * @return Citation|null + * + * @throws IucnApiException If any error occurs. + */ + public function getSpeciesCitationById(int $id, string $region = null): ?Citation; + + /** + * Get the citation for a species, by species name. + * + * @see http://apiv3.iucnredlist.org/api/v3/docs#species-citation-name + * + * @param string $name The species name. + * @param string|null $region An optional region identifier. + * + * @return Citation|null + * + * @throws IucnApiException If any error occurs. + */ + public function getSpeciesCitationByName(string $name, string $region = null): ?Citation; + + /** + * Get a list of common names for a species. + * + * @see http://apiv3.iucnredlist.org/api/v3/docs#species-common + * + * @param string $name The species name. + * + * @return Collection + * + * @throws IucnApiException If any error occurs. + */ + public function getSpeciesCommonNames(string $name): Collection; + + /** + * Get a list of conservation measures for a species, by species ID. + * + * @see https://apiv3.iucnredlist.org/api/v3/docs#measures-id + * + * @param int $id The species ID. + * @param string|null $region An optional region identifier. + * + * @return Collection + * + * @throws IucnApiException If any error occurs. + */ + public function getSpeciesConservationMeasuresById(int $id, string $region = null): Collection; + + /** + * Get a list of conservation measures for a species, by species name. + * + * @see https://apiv3.iucnredlist.org/api/v3/docs#measures-name + * + * @param string $name The species name. + * @param string|null $region An optional region identifier. + * + * @return Collection + * + * @throws IucnApiException If any error occurs. + */ + public function getSpeciesConservationMeasuresByName(string $name, string $region = null): Collection; + + /** + * Get the total species count. + * + * @see http://apiv3.iucnredlist.org/api/v3/docs#species-count + * + * @param string|null $region An optional region identifier. + * @param bool $includeSubspecies Whether subspecies should be included or not. + * + * @return int + * + * @throws IucnApiException If any error occurs. + */ + public function getSpeciesCount(string $region = null, bool $includeSubspecies = true): int; + + /** + * Get a list of habitats for a species, by species ID. + * + * @see https://apiv3.iucnredlist.org/api/v3/docs#habitat-id + * + * @param int $id The species ID. + * @param string|null $region An optional region identifier. + * + * @return Collection + * + * @throws IucnApiException If any error occurs. + */ + public function getSpeciesHabitatsById(int $id, string $region = null): Collection; + + /** + * Get a list of habitats for a species, by species name. + * + * @see https://apiv3.iucnredlist.org/api/v3/docs#habitat-name + * + * @param string $name The species name. + * @param string|null $region An optional region identifier. + * + * @return Collection + * + * @throws IucnApiException If any error occurs. + */ + public function getSpeciesHabitatsByName(string $name, string $region = null): Collection; + + /** + * Get narrative information for a species, by species ID. + * + * @see http://apiv3.iucnredlist.org/api/v3/docs#species-narrative-id + * + * @param int $id The species ID. + * @param string|null $region An optional region identifier. + * + * @return NarrativeText|null + * + * @throws IucnApiException If any error occurs. + */ + public function getSpeciesNarrativeTextById(int $id, string $region = null): ?NarrativeText; + + /** + * Get narrative information for a species, by species name. + * + * @see http://apiv3.iucnredlist.org/api/v3/docs#species-narrative-name + * + * @param string $name The species name. + * @param string|null $region An optional region identifier. + * + * @return NarrativeText|null + * + * @throws IucnApiException If any error occurs. + */ + public function getSpeciesNarrativeTextByName(string $name, string $region = null): ?NarrativeText; + + /** + * Get a list of countries of occurrence for a species, by species ID. + * + * @see http://apiv3.iucnredlist.org/api/v3/docs#species-occurrence-id + * + * @param int $id The species ID. + * @param string|null $region An optional region identifier. + * + * @return Collection + * + * @throws IucnApiException If any error occurs. + */ + public function getSpeciesOccurrencesById(int $id, string $region = null): Collection; + + /** + * Get a list of countries of occurrence for a species, by species name. + * + * @see http://apiv3.iucnredlist.org/api/v3/docs#species-occurrence-name + * + * @param string $name The species name. + * @param string|null $region An optional region identifier. + * + * @return Collection + * + * @throws IucnApiException If any error occurs. + */ + public function getSpeciesOccurrencesByName(string $name, string $region = null): Collection; + + /** + * Get a list of synonyms for a species. + * + * @see http://apiv3.iucnredlist.org/api/v3/docs#species-synonym + * + * @param string $name The species name. + * + * @return Collection + * + * @throws IucnApiException If any error occurs. + */ + public function getSpeciesSynonyms(string $name): Collection; + + /** + * Get a list of threats to a species, by species ID. + * + * @see http://apiv3.iucnredlist.org/api/v3/docs#threat-id + * + * @param int $id The species ID. + * @param string|null $region An optional region identifier. + * + * @return Collection + * + * @throws IucnApiException If any error occurs. + */ + public function getSpeciesThreatsById(int $id, string $region = null): Collection; + + /** + * Get a list of threats to a species, by species name. + * + * @see http://apiv3.iucnredlist.org/api/v3/docs#threat-name + * + * @param string $name The species name. + * @param string|null $region An optional region identifier. + * + * @return Collection + * + * @throws IucnApiException If any error occurs. + */ + public function getSpeciesThreatsByName(string $name, string $region = null): Collection; + + /** + * Get the version of the IUCN Red List database. + * + * @see http://apiv3.iucnredlist.org/api/v3/docs#version + * + * @return string + * + * @throws IucnApiException If any error occurs. + */ + public function getVersion(): string; + + /** + * Get the IUCN website link for a species. + * + * @see https://apiv3.iucnredlist.org/api/v3/docs#weblink + * + * @param string $name The species name. + * + * @return string|null + * + * @throws IucnApiException If any error occurs. + */ + public function getWebsiteLink(string $name): ?string; +} diff --git a/sites/all/libraries/vendor/jbroutier/iucn-api-client/src/Exception/IucnApiException.php b/sites/all/libraries/vendor/jbroutier/iucn-api-client/src/Exception/IucnApiException.php new file mode 100644 index 0000000000..96f1f9c319 --- /dev/null +++ b/sites/all/libraries/vendor/jbroutier/iucn-api-client/src/Exception/IucnApiException.php @@ -0,0 +1,7 @@ +assessmentYear; + } + + public function getCategoryCode(): ?string + { + return $this->categoryCode; + } + + public function getCategoryName(): ?string + { + return $this->categoryName; + } + + public function getPublicationYear(): ?int + { + return $this->publicationYear; + } + + /** + * @param array $data + */ + public static function createFromArray(array $data): Assessment + { + $assessment = new Assessment(); + $assessment->assessmentYear = !is_null($data['assess_year']) ? intval($data['assess_year']) : null; + $assessment->categoryCode = !is_null($data['code']) ? strval($data['code']) : null; + $assessment->categoryName = !is_null($data['category']) ? strval($data['category']) : null; + $assessment->publicationYear = !is_null($data['year']) ? intval($data['year']) : null; + + return $assessment; + } +} diff --git a/sites/all/libraries/vendor/jbroutier/iucn-api-client/src/Model/Citation.php b/sites/all/libraries/vendor/jbroutier/iucn-api-client/src/Model/Citation.php new file mode 100644 index 0000000000..8f55a17ea0 --- /dev/null +++ b/sites/all/libraries/vendor/jbroutier/iucn-api-client/src/Model/Citation.php @@ -0,0 +1,24 @@ +citation; + } + + /** + * @param array $data + */ + public static function createFromArray(array $data): Citation + { + $citation = new Citation(); + $citation->citation = !is_null($data['citation']) ? strval($data['citation']) : null; + + return $citation; + } +} diff --git a/sites/all/libraries/vendor/jbroutier/iucn-api-client/src/Model/CommonName.php b/sites/all/libraries/vendor/jbroutier/iucn-api-client/src/Model/CommonName.php new file mode 100644 index 0000000000..d19741840c --- /dev/null +++ b/sites/all/libraries/vendor/jbroutier/iucn-api-client/src/Model/CommonName.php @@ -0,0 +1,38 @@ +language; + } + + public function getName(): ?string + { + return $this->name; + } + + public function isPrimary(): ?bool + { + return $this->primary; + } + + /** + * @param array $data + */ + public static function createFromArray(array $data): CommonName + { + $commonName = new CommonName(); + $commonName->language = !is_null($data['language']) ? strval($data['language']) : null; + $commonName->name = !is_null($data['taxonname']) ? strval($data['taxonname']) : null; + $commonName->primary = !is_null($data['primary']) ? boolval($data['primary']) : null; + + return $commonName; + } +} diff --git a/sites/all/libraries/vendor/jbroutier/iucn-api-client/src/Model/ConservationMeasure.php b/sites/all/libraries/vendor/jbroutier/iucn-api-client/src/Model/ConservationMeasure.php new file mode 100644 index 0000000000..3b0d461c29 --- /dev/null +++ b/sites/all/libraries/vendor/jbroutier/iucn-api-client/src/Model/ConservationMeasure.php @@ -0,0 +1,31 @@ +code; + } + + public function getTitle(): ?string + { + return $this->title; + } + + /** + * @param array $data + */ + public static function createFromArray(array $data): ConservationMeasure + { + $conservationMeasure = new ConservationMeasure(); + $conservationMeasure->code = !is_null($data['code']) ? strval($data['code']) : null; + $conservationMeasure->title = !is_null($data['title']) ? strval($data['title']) : null; + + return $conservationMeasure; + } +} diff --git a/sites/all/libraries/vendor/jbroutier/iucn-api-client/src/Model/Country.php b/sites/all/libraries/vendor/jbroutier/iucn-api-client/src/Model/Country.php new file mode 100644 index 0000000000..cbff78cb6c --- /dev/null +++ b/sites/all/libraries/vendor/jbroutier/iucn-api-client/src/Model/Country.php @@ -0,0 +1,31 @@ +code; + } + + public function getName(): ?string + { + return $this->name; + } + + /** + * @param array $data + */ + public static function createFromArray(array $data): Country + { + $country = new Country(); + $country->code = !is_null($data['isocode']) ? strval($data['isocode']) : null; + $country->name = !is_null($data['country']) ? strval($data['country']) : null; + + return $country; + } +} diff --git a/sites/all/libraries/vendor/jbroutier/iucn-api-client/src/Model/Group.php b/sites/all/libraries/vendor/jbroutier/iucn-api-client/src/Model/Group.php new file mode 100644 index 0000000000..d3482795af --- /dev/null +++ b/sites/all/libraries/vendor/jbroutier/iucn-api-client/src/Model/Group.php @@ -0,0 +1,24 @@ +code; + } + + /** + * @param array $data + */ + public static function createFromArray(array $data): Group + { + $group = new Group(); + $group->code = !is_null($data['group_name']) ? strval($data['group_name']) : null; + + return $group; + } +} diff --git a/sites/all/libraries/vendor/jbroutier/iucn-api-client/src/Model/GrowthForm.php b/sites/all/libraries/vendor/jbroutier/iucn-api-client/src/Model/GrowthForm.php new file mode 100644 index 0000000000..e81cfba03b --- /dev/null +++ b/sites/all/libraries/vendor/jbroutier/iucn-api-client/src/Model/GrowthForm.php @@ -0,0 +1,24 @@ +name; + } + + /** + * @param array $data + */ + public static function createFromArray(array $data): GrowthForm + { + $plantGrowthForm = new GrowthForm(); + $plantGrowthForm->name = !is_null($data['name']) ? strval($data['name']) : null; + + return $plantGrowthForm; + } +} diff --git a/sites/all/libraries/vendor/jbroutier/iucn-api-client/src/Model/Habitat.php b/sites/all/libraries/vendor/jbroutier/iucn-api-client/src/Model/Habitat.php new file mode 100644 index 0000000000..cb54fb5143 --- /dev/null +++ b/sites/all/libraries/vendor/jbroutier/iucn-api-client/src/Model/Habitat.php @@ -0,0 +1,52 @@ +code; + } + + public function getMajorImportance(): ?string + { + return $this->majorImportance; + } + + public function getName(): ?string + { + return $this->name; + } + + public function getSeason(): ?string + { + return $this->season; + } + + public function getSuitability(): ?string + { + return $this->suitability; + } + + /** + * @param array $data + */ + public static function createFromArray(array $data): Habitat + { + $habitat = new Habitat(); + $habitat->code = !is_null($data['code']) ? strval($data['code']) : null; + $habitat->majorImportance = !is_null($data['majorimportance']) ? strval($data['majorimportance']) : null; + $habitat->name = !is_null($data['habitat']) ? strval($data['habitat']) : null; + $habitat->season = !is_null($data['season']) ? strval($data['season']) : null; + $habitat->suitability = !is_null($data['suitability']) ? strval($data['suitability']) : null; + + return $habitat; + } +} diff --git a/sites/all/libraries/vendor/jbroutier/iucn-api-client/src/Model/NarrativeText.php b/sites/all/libraries/vendor/jbroutier/iucn-api-client/src/Model/NarrativeText.php new file mode 100644 index 0000000000..f2df4665dc --- /dev/null +++ b/sites/all/libraries/vendor/jbroutier/iucn-api-client/src/Model/NarrativeText.php @@ -0,0 +1,81 @@ +conservationMeasures; + } + + public function getGeographicRange(): ?string + { + return $this->geographicRange; + } + + public function getHabitat(): ?string + { + return $this->habitat; + } + + public function getPopulation(): ?string + { + return $this->population; + } + + public function getPopulationTrend(): ?string + { + return $this->populationTrend; + } + + public function getRationale(): ?string + { + return $this->rationale; + } + + public function getTaxonomicNotes(): ?string + { + return $this->taxonomicNotes; + } + + public function getThreats(): ?string + { + return $this->threats; + } + + public function getUseTrade(): ?string + { + return $this->useTrade; + } + + /** + * @param array $data + */ + public static function createFromArray(array $data): NarrativeText + { + $narrativeText = new NarrativeText(); + $narrativeText->conservationMeasures = !is_null($data['conservationmeasures']) + ? strval($data['conservationmeasures']) : null; + $narrativeText->geographicRange = !is_null($data['geographicrange']) ? strval($data['geographicrange']) : null; + $narrativeText->habitat = !is_null($data['habitat']) ? strval($data['habitat']) : null; + $narrativeText->population = !is_null($data['population']) ? strval($data['population']) : null; + $narrativeText->populationTrend = !is_null($data['populationtrend']) ? strval($data['populationtrend']) : null; + $narrativeText->rationale = !is_null($data['rationale']) ? strval($data['rationale']) : null; + $narrativeText->taxonomicNotes = !is_null($data['taxonomicnotes']) ? strval($data['taxonomicnotes']) : null; + $narrativeText->threats = !is_null($data['threats']) ? strval($data['threats']) : null; + $narrativeText->useTrade = !is_null($data['usetrade']) ? strval($data['usetrade']) : null; + + return $narrativeText; + } +} diff --git a/sites/all/libraries/vendor/jbroutier/iucn-api-client/src/Model/Occurrence.php b/sites/all/libraries/vendor/jbroutier/iucn-api-client/src/Model/Occurrence.php new file mode 100644 index 0000000000..16308d5498 --- /dev/null +++ b/sites/all/libraries/vendor/jbroutier/iucn-api-client/src/Model/Occurrence.php @@ -0,0 +1,53 @@ +countryCode; + } + + public function getCountryName(): ?string + { + return $this->countryName; + } + + public function getDistributionCode(): ?string + { + return $this->distributionCode; + } + + public function getOrigin(): ?string + { + return $this->origin; + } + + public function getPresence(): ?string + { + return $this->presence; + } + + /** + * @param array $data + */ + public static function createFromArray(array $data): Occurrence + { + $occurrence = new Occurrence(); + $occurrence->countryCode = !is_null($data['code']) ? strval($data['code']) : null; + $occurrence->countryName = !is_null($data['country']) ? strval($data['country']) : null; + $occurrence->distributionCode = !is_null($data['distribution_code']) + ? strval($data['distribution_code']) : null; + $occurrence->origin = !is_null($data['origin']) ? strval($data['origin']) : null; + $occurrence->presence = !is_null($data['presence']) ? strval($data['presence']) : null; + + return $occurrence; + } +} diff --git a/sites/all/libraries/vendor/jbroutier/iucn-api-client/src/Model/Region.php b/sites/all/libraries/vendor/jbroutier/iucn-api-client/src/Model/Region.php new file mode 100644 index 0000000000..f3acc7c558 --- /dev/null +++ b/sites/all/libraries/vendor/jbroutier/iucn-api-client/src/Model/Region.php @@ -0,0 +1,31 @@ +identifier; + } + + public function getName(): ?string + { + return $this->name; + } + + /** + * @param array $data + */ + public static function createFromArray(array $data): Region + { + $region = new Region(); + $region->identifier = !is_null($data['identifier']) ? strval($data['identifier']) : null; + $region->name = !is_null($data['name']) ? strval($data['name']) : null; + + return $region; + } +} diff --git a/sites/all/libraries/vendor/jbroutier/iucn-api-client/src/Model/Species.php b/sites/all/libraries/vendor/jbroutier/iucn-api-client/src/Model/Species.php new file mode 100644 index 0000000000..7c8be4e53f --- /dev/null +++ b/sites/all/libraries/vendor/jbroutier/iucn-api-client/src/Model/Species.php @@ -0,0 +1,59 @@ +category; + } + + public function getScientificName(): ?string + { + return $this->scientificName; + } + + public function getSubpopulation(): ?string + { + return $this->subpopulation; + } + + public function getSubspeciesName(): ?string + { + return $this->subspeciesName; + } + + public function getSubspeciesRank(): ?string + { + return $this->subspeciesRank; + } + + public function getTaxonId(): ?int + { + return $this->taxonId; + } + + /** + * @param array $data + */ + public static function createFromArray(array $data): Species + { + $species = new Species(); + $species->category = !is_null($data['category']) ? strval($data['category']) : null; + $species->scientificName = !is_null($data['scientific_name']) ? strval($data['scientific_name']) : null; + $species->subpopulation = !is_null($data['subpopulation']) ? strval($data['subpopulation']) : null; + $species->subspeciesName = !is_null($data['subspecies']) ? strval($data['subspecies']) : null; + $species->subspeciesRank = !is_null($data['rank']) ? strval($data['rank']) : null; + $species->taxonId = !is_null($data['taxonid']) ? intval($data['taxonid']) : null; + + return $species; + } +} diff --git a/sites/all/libraries/vendor/jbroutier/iucn-api-client/src/Model/SpeciesDetails.php b/sites/all/libraries/vendor/jbroutier/iucn-api-client/src/Model/SpeciesDetails.php new file mode 100644 index 0000000000..31776075b0 --- /dev/null +++ b/sites/all/libraries/vendor/jbroutier/iucn-api-client/src/Model/SpeciesDetails.php @@ -0,0 +1,231 @@ +amended; + } + + public function getAmendedReason(): ?string + { + return $this->amendedReason; + } + + public function getAOO(): ?int + { + return $this->aoo; + } + + public function getAssessmentDate(): ?string + { + return $this->assessmentDate; + } + + public function getAssessor(): ?string + { + return $this->assessor; + } + + public function getAuthority(): ?string + { + return $this->authority; + } + + public function getCategory(): ?string + { + return $this->category; + } + + public function getClass(): ?string + { + return $this->class; + } + + public function getCriteria(): ?string + { + return $this->criteria; + } + + public function getDepthLower(): ?int + { + return $this->depthLower; + } + + public function getDepthUpper(): ?int + { + return $this->depthUpper; + } + + public function getElevationLower(): ?int + { + return $this->elevationLower; + } + + public function getElevationUpper(): ?int + { + return $this->elevationUpper; + } + + public function getEOO(): ?int + { + return $this->eoo; + } + + public function isErrata(): ?bool + { + return $this->errata; + } + + public function getErrataReason(): ?string + { + return $this->errataReason; + } + + public function getFamily(): ?string + { + return $this->family; + } + + public function isFreshwaterSystem(): ?bool + { + return $this->freshwaterSystem; + } + + public function getGenus(): ?string + { + return $this->genus; + } + + public function getKingdom(): ?string + { + return $this->kingdom; + } + + public function getMainCommonName(): ?string + { + return $this->mainCommonName; + } + + public function isMarineSystem(): ?bool + { + return $this->marineSystem; + } + + public function getOrder(): ?string + { + return $this->order; + } + + public function getPhylum(): ?string + { + return $this->phylum; + } + + public function getPopulationTrend(): ?string + { + return $this->populationTrend; + } + + public function getPublicationYear(): ?int + { + return $this->publicationYear; + } + + public function getReviewer(): ?string + { + return $this->reviewer; + } + + public function getScientificName(): ?string + { + return $this->scientificName; + } + + public function getTaxonId(): ?int + { + return $this->taxonId; + } + + public function isTerrestrialSystem(): ?bool + { + return $this->terrestrialSystem; + } + + /** + * @param array $data + */ + public static function createFromArray(array $data): SpeciesDetails + { + $speciesDetails = new SpeciesDetails(); + $speciesDetails->amended = !is_null($data['amended_flag']) ? boolval($data['amended_flag']) : null; + $speciesDetails->amendedReason = !is_null($data['amended_reason']) ? strval($data['amended_reason']) : null; + $speciesDetails->aoo = !is_null($data['aoo_km2']) ? intval($data['aoo_km2']) : null; + $speciesDetails->assessmentDate = !is_null($data['assessment_date']) ? strval($data['assessment_date']) : null; + $speciesDetails->assessor = !is_null($data['assessor']) ? strval($data['assessor']) : null; + $speciesDetails->authority = !is_null($data['authority']) ? strval($data['authority']) : null; + $speciesDetails->category = !is_null($data['category']) ? strval($data['category']) : null; + $speciesDetails->class = !is_null($data['class']) ? strval($data['class']) : null; + $speciesDetails->criteria = !is_null($data['criteria']) ? strval($data['criteria']) : null; + $speciesDetails->depthLower = !is_null($data['depth_lower']) ? intval($data['depth_lower']) : null; + $speciesDetails->depthUpper = !is_null($data['depth_upper']) ? intval($data['depth_upper']) : null; + $speciesDetails->elevationLower = !is_null($data['elevation_lower']) ? intval($data['elevation_lower']) : null; + $speciesDetails->elevationUpper = !is_null($data['elevation_upper']) ? intval($data['elevation_upper']) : null; + $speciesDetails->eoo = !is_null($data['eoo_km2']) ? intval($data['eoo_km2']) : null; + $speciesDetails->errata = !is_null($data['errata_flag']) ? boolval($data['errata_flag']) : null; + $speciesDetails->errataReason = !is_null($data['errata_reason']) ? strval($data['errata_reason']) : null; + $speciesDetails->family = !is_null($data['family']) ? strval($data['family']) : null; + $speciesDetails->freshwaterSystem = !is_null($data['freshwater_system']) + ? boolval($data['freshwater_system']) : null; + $speciesDetails->genus = !is_null($data['genus']) ? strval($data['genus']) : null; + $speciesDetails->kingdom = !is_null($data['kingdom']) ? strval($data['kingdom']) : null; + $speciesDetails->mainCommonName = !is_null($data['main_common_name']) + ? strval($data['main_common_name']) : null; + $speciesDetails->marineSystem = !is_null($data['marine_system']) ? boolval($data['marine_system']) : null; + $speciesDetails->order = !is_null($data['order']) ? strval($data['order']) : null; + $speciesDetails->phylum = !is_null($data['phylum']) ? strval($data['phylum']) : null; + $speciesDetails->populationTrend = !is_null($data['population_trend']) + ? strval($data['population_trend']) : null; + $speciesDetails->publicationYear = !is_null($data['published_year']) ? intval($data['published_year']) : null; + $speciesDetails->reviewer = !is_null($data['reviewer']) ? strval($data['reviewer']) : null; + $speciesDetails->scientificName = !is_null($data['scientific_name']) ? strval($data['scientific_name']) : null; + $speciesDetails->taxonId = !is_null($data['taxonid']) ? intval($data['taxonid']) : null; + $speciesDetails->terrestrialSystem = !is_null($data['terrestrial_system']) + ? boolval($data['terrestrial_system']) : null; + + return $speciesDetails; + } +} diff --git a/sites/all/libraries/vendor/jbroutier/iucn-api-client/src/Model/Synonym.php b/sites/all/libraries/vendor/jbroutier/iucn-api-client/src/Model/Synonym.php new file mode 100644 index 0000000000..c41ee55c84 --- /dev/null +++ b/sites/all/libraries/vendor/jbroutier/iucn-api-client/src/Model/Synonym.php @@ -0,0 +1,52 @@ +acceptedId; + } + + public function getAcceptedName(): ?string + { + return $this->acceptedName; + } + + public function getAcceptedNameAuthority(): ?string + { + return $this->acceptedNameAuthority; + } + + public function getSynonym(): ?string + { + return $this->synonym; + } + + public function getSynonymAuthority(): ?string + { + return $this->synonymAuthority; + } + + /** + * @param array $data + */ + public static function createFromArray(array $data): Synonym + { + $synonym = new Synonym(); + $synonym->acceptedId = !is_null($data['accepted_id']) ? intval($data['accepted_id']) : null; + $synonym->acceptedName = !is_null($data['accepted_name']) ? strval($data['accepted_name']) : null; + $synonym->acceptedNameAuthority = !is_null($data['authority']) ? strval($data['authority']) : null; + $synonym->synonym = !is_null($data['synonym']) ? strval($data['synonym']) : null; + $synonym->synonymAuthority = !is_null($data['syn_authority']) ? strval($data['syn_authority']) : null; + + return $synonym; + } +} diff --git a/sites/all/libraries/vendor/jbroutier/iucn-api-client/src/Model/Threat.php b/sites/all/libraries/vendor/jbroutier/iucn-api-client/src/Model/Threat.php new file mode 100644 index 0000000000..098da2bb64 --- /dev/null +++ b/sites/all/libraries/vendor/jbroutier/iucn-api-client/src/Model/Threat.php @@ -0,0 +1,66 @@ +code; + } + + public function getInvasive(): ?string + { + return $this->invasive; + } + + public function getScope(): ?string + { + return $this->scope; + } + + public function getScore(): ?string + { + return $this->score; + } + + public function getSeverity(): ?string + { + return $this->severity; + } + + public function getTiming(): ?string + { + return $this->timing; + } + + public function getTitle(): ?string + { + return $this->title; + } + + /** + * @param array $data + */ + public static function createFromArray(array $data): Threat + { + $threat = new Threat(); + $threat->code = !is_null($data['code']) ? strval($data['code']) : null; + $threat->invasive = !is_null($data['invasive']) ? strval($data['invasive']) : null; + $threat->scope = !is_null($data['scope']) ? strval($data['scope']) : null; + $threat->score = !is_null($data['score']) ? strval($data['score']) : null; + $threat->severity = !is_null($data['severity']) ? strval($data['severity']) : null; + $threat->timing = !is_null($data['timing']) ? strval($data['timing']) : null; + $threat->title = !is_null($data['title']) ? strval($data['title']) : null; + + return $threat; + } +} diff --git a/sites/all/libraries/vendor/jbroutier/iucn-api-client/tests/Functional/ClientTest.php b/sites/all/libraries/vendor/jbroutier/iucn-api-client/tests/Functional/ClientTest.php new file mode 100644 index 0000000000..fa74266b98 --- /dev/null +++ b/sites/all/libraries/vendor/jbroutier/iucn-api-client/tests/Functional/ClientTest.php @@ -0,0 +1,675 @@ +httpClient = new MockHttpClient($responses, 'https://apiv3.iucnredlist.org/api/v3/'); + } +} + +final class ClientTest extends TestCase +{ + protected ClientInterface $client; + + public function setUp(): void + { + if (($json = file_get_contents(dirname(__FILE__) . '/responses.json')) === false) { + throw new \Exception('Could not read "responses.json" file.'); + } + + if (($responses = json_decode($json, true)) === false) { + throw new \Exception('Could not parse "responses.json" file.'); + } + + $responses = (array)$responses; + + $this->client = new MockClient(function ($method, $url) use ($responses) { + if (array_key_exists($url, $responses)) { + return new MockResponse((string)json_encode($responses[$url])); + } + + return new MockResponse(); + }); + } + + public function testGetCountries(): void + { + $countries = $this->client->getCountries(); + + self::assertCount(251, $countries); + + $country = $countries->first(); + + self::assertInstanceOf(Country::class, $country); + self::assertEquals('UZ', $country->getCode()); + self::assertEquals('Uzbekistan', $country->getName()); + } + + public function testGetComprehensiveGroups(): void + { + $groups = $this->client->getComprehensiveGroups(); + + self::assertCount(31, $groups); + + $group = $groups->first(); + + self::assertInstanceOf(Group::class, $group); + self::assertEquals('reef_building_corals', $group->getCode()); + } + + public function testGetPlantGrowthFormsById(): void + { + $growthForms = $this->client->getPlantGrowthFormsById(19891625); + + self::assertCount(4, $growthForms); + + $growthForm = $growthForms->first(); + + self::assertInstanceOf(GrowthForm::class, $growthForm); + self::assertEquals('Annual', $growthForm->getName()); + + $growthForms = $this->client->getPlantGrowthFormsById(63532, 'europe'); + + self::assertCount(1, $growthForms); + + $growthForm = $growthForms->first(); + + self::assertInstanceOf(GrowthForm::class, $growthForm); + self::assertEquals('Tree - large', $growthForm->getName()); + } + + public function testGetPlantGrowthFormsByName(): void + { + $growthForms = $this->client->getPlantGrowthFormsByName('Mucuna bracteata'); + + self::assertCount(4, $growthForms); + + $growthForm = $growthForms->first(); + + self::assertInstanceOf(GrowthForm::class, $growthForm); + self::assertEquals('Annual', $growthForm->getName()); + + $growthForms = $this->client->getPlantGrowthFormsByName('Quercus robur', 'europe'); + + self::assertCount(1, $growthForms); + + $growthForm = $growthForms->first(); + + self::assertInstanceOf(GrowthForm::class, $growthForm); + self::assertEquals('Tree - large', $growthForm->getName()); + } + + public function testGetRegions(): void + { + $regions = $this->client->getRegions(); + + self::assertCount(10, $regions); + + $region = $regions->first(); + + self::assertInstanceOf(Region::class, $region); + self::assertEquals('northeastern_africa', $region->getIdentifier()); + self::assertEquals('Northeastern Africa', $region->getName()); + } + + public function testGetSpeciesAssessmentsById(): void + { + $assessments = $this->client->getSpeciesAssessmentsById(181008073); + + self::assertCount(1, $assessments); + + $assessment = $assessments->first(); + + self::assertInstanceOf(Assessment::class, $assessment); + self::assertEquals('2020', $assessment->getAssessmentYear()); + self::assertEquals('EN', $assessment->getCategoryCode()); + self::assertEquals('Endangered', $assessment->getCategoryName()); + self::assertEquals('2021', $assessment->getPublicationYear()); + + $assessments = $this->client->getSpeciesAssessmentsById(22823, 'europe'); + + self::assertCount(1, $assessments); + + $assessment = $assessments->first(); + + self::assertInstanceOf(Assessment::class, $assessment); + self::assertEquals('2006', $assessment->getAssessmentYear()); + self::assertEquals('VU', $assessment->getCategoryCode()); + self::assertEquals('Vulnerable', $assessment->getCategoryName()); + self::assertEquals('2007', $assessment->getPublicationYear()); + } + + public function testGetSpeciesAssessmentsByName(): void + { + $assessments = $this->client->getSpeciesAssessmentsByName('Loxodonta africana'); + + self::assertCount(1, $assessments); + + $assessment = $assessments->first(); + + self::assertInstanceOf(Assessment::class, $assessment); + self::assertEquals('2020', $assessment->getAssessmentYear()); + self::assertEquals('EN', $assessment->getCategoryCode()); + self::assertEquals('Endangered', $assessment->getCategoryName()); + self::assertEquals('2021', $assessment->getPublicationYear()); + + $assessments = $this->client->getSpeciesAssessmentsByName('Ursus maritimus', 'europe'); + + self::assertCount(1, $assessments); + + $assessment = $assessments->first(); + + self::assertInstanceOf(Assessment::class, $assessment); + self::assertEquals('2006', $assessment->getAssessmentYear()); + self::assertEquals('VU', $assessment->getCategoryCode()); + self::assertEquals('Vulnerable', $assessment->getCategoryName()); + self::assertEquals('2007', $assessment->getPublicationYear()); + } + + public function testGetSpeciesByCategory(): void + { + $species = $this->client->getSpeciesByCategory('LRlc'); + + self::assertCount(512, $species); + + $species = $species->first(); + + self::assertInstanceOf(Species::class, $species); + self::assertEquals('LR/lc', $species->getCategory()); + self::assertEquals('Abarema commutata', $species->getScientificName()); + self::assertNull($species->getSubpopulation()); + self::assertNull($species->getSubspeciesName()); + self::assertNull($species->getSubspeciesRank()); + self::assertEquals(36549, $species->getTaxonId()); + } + + public function testGetSpeciesByComprehensiveGroup(): void + { + $species = $this->client->getSpeciesByComprehensiveGroup('mammals'); + + self::assertCount(6423, $species); + + $species = $species->first(); + + self::assertInstanceOf(Species::class, $species); + self::assertEquals('DD', $species->getCategory()); + self::assertEquals('Abditomys latidens', $species->getScientificName()); + self::assertNull($species->getSubpopulation()); + self::assertNull($species->getSubspeciesName()); + self::assertNull($species->getSubspeciesRank()); + self::assertEquals(42641, $species->getTaxonId()); + } + + public function testGetSpeciesByCountry(): void + { + $species = $this->client->getSpeciesByCountry('AZ'); + + self::assertCount(1162, $species); + + $species = $species->first(); + + self::assertInstanceOf(Species::class, $species); + self::assertEquals('LC', $species->getCategory()); + self::assertEquals('Abies nordmanniana', $species->getScientificName()); + self::assertNull($species->getSubpopulation()); + self::assertNull($species->getSubspeciesName()); + self::assertNull($species->getSubspeciesRank()); + self::assertEquals(42293, $species->getTaxonId()); + } + + public function testGetSpeciesById(): void + { + $species = $this->client->getSpeciesById(181008073); + + self::assertInstanceOf(SpeciesDetails::class, $species); + self::assertEquals('EN', $species->getCategory()); + self::assertEquals('MAMMALIA', $species->getClass()); + self::assertEquals('ELEPHANTIDAE', $species->getFamily()); + self::assertEquals('Loxodonta', $species->getGenus()); + self::assertEquals('ANIMALIA', $species->getKingdom()); + self::assertEquals('African Savanna Elephant', $species->getMainCommonName()); + self::assertEquals('PROBOSCIDEA', $species->getOrder()); + self::assertEquals('CHORDATA', $species->getPhylum()); + self::assertEquals('Loxodonta africana', $species->getScientificName()); + self::assertEquals(181008073, $species->getTaxonId()); + + $species = $this->client->getSpeciesById(22694927, 'europe'); + + self::assertInstanceOf(SpeciesDetails::class, $species); + self::assertEquals('EN', $species->getCategory()); + self::assertEquals('AVES', $species->getClass()); + self::assertEquals('ALCIDAE', $species->getFamily()); + self::assertEquals('Fratercula', $species->getGenus()); + self::assertEquals('ANIMALIA', $species->getKingdom()); + self::assertEquals('Atlantic Puffin', $species->getMainCommonName()); + self::assertEquals('CHARADRIIFORMES', $species->getOrder()); + self::assertEquals('CHORDATA', $species->getPhylum()); + self::assertEquals('Fratercula arctica', $species->getScientificName()); + self::assertEquals(22694927, $species->getTaxonId()); + } + + public function testGetSpeciesByName(): void + { + $species = $this->client->getSpeciesByName('Loxodonta africana'); + + self::assertInstanceOf(SpeciesDetails::class, $species); + self::assertEquals('EN', $species->getCategory()); + self::assertEquals('MAMMALIA', $species->getClass()); + self::assertEquals('ELEPHANTIDAE', $species->getFamily()); + self::assertEquals('Loxodonta', $species->getGenus()); + self::assertEquals('ANIMALIA', $species->getKingdom()); + self::assertEquals('African Savanna Elephant', $species->getMainCommonName()); + self::assertEquals('PROBOSCIDEA', $species->getOrder()); + self::assertEquals('CHORDATA', $species->getPhylum()); + self::assertEquals('Loxodonta africana', $species->getScientificName()); + self::assertEquals(181008073, $species->getTaxonId()); + + $species = $this->client->getSpeciesByName('Fratercula arctica', 'europe'); + + self::assertInstanceOf(SpeciesDetails::class, $species); + self::assertEquals('EN', $species->getCategory()); + self::assertEquals('AVES', $species->getClass()); + self::assertEquals('ALCIDAE', $species->getFamily()); + self::assertEquals('Fratercula', $species->getGenus()); + self::assertEquals('ANIMALIA', $species->getKingdom()); + self::assertEquals('Atlantic Puffin', $species->getMainCommonName()); + self::assertEquals('CHARADRIIFORMES', $species->getOrder()); + self::assertEquals('CHORDATA', $species->getPhylum()); + self::assertEquals('Fratercula arctica', $species->getScientificName()); + self::assertEquals(22694927, $species->getTaxonId()); + } + + public function testGetSpeciesCitationById(): void + { + $citation = $this->client->getSpeciesCitationById(181008073); + + self::assertInstanceOf(Citation::class, $citation); + self::assertStringStartsWith('Gobush, K.S., Edwards, C.T.T', (string)$citation->getCitation()); + + $citation = $this->client->getSpeciesCitationById(2467, 'europe'); + + self::assertInstanceOf(Citation::class, $citation); + self::assertStringStartsWith('Species account by IUCN', (string)$citation->getCitation()); + } + + public function testGetSpeciesCitationByName(): void + { + $citation = $this->client->getSpeciesCitationByName('Loxodonta africana'); + + self::assertInstanceOf(Citation::class, $citation); + self::assertStringStartsWith('Gobush, K.S., Edwards, C.T.T', (string)$citation->getCitation()); + + $citation = $this->client->getSpeciesCitationByName('Balaena mysticetus', 'europe'); + + self::assertInstanceOf(Citation::class, $citation); + self::assertStringStartsWith('Species account by IUCN', (string)$citation->getCitation()); + } + + public function testGetSpeciesCommonNames(): void + { + $commonNames = $this->client->getSpeciesCommonNames('Loxodonta africana'); + + self::assertCount(7, $commonNames); + + $commonName = $commonNames->first(); + + self::assertInstanceOf(CommonName::class, $commonName); + self::assertEquals('eng', $commonName->getLanguage()); + self::assertEquals('African Savanna Elephant', $commonName->getName()); + self::assertTrue($commonName->isPrimary()); + } + + public function testGetSpeciesConservationMeasuresById(): void + { + $conservationMeasures = $this->client->getSpeciesConservationMeasuresById(181008073); + + self::assertCount(20, $conservationMeasures); + + $conservationMeasure = $conservationMeasures->first(); + + self::assertInstanceOf(ConservationMeasure::class, $conservationMeasure); + self::assertEquals('1.1', $conservationMeasure->getCode()); + self::assertEquals('Site/area protection', $conservationMeasure->getTitle()); + + $conservationMeasures = $this->client->getSpeciesConservationMeasuresById(22823, 'europe'); + + self::assertCount(3, $conservationMeasures); + + $conservationMeasure = $conservationMeasures->first(); + + self::assertInstanceOf(ConservationMeasure::class, $conservationMeasure); + self::assertEquals('1.1', $conservationMeasure->getCode()); + self::assertEquals('Site/area protection', $conservationMeasure->getTitle()); + } + + public function testGetSpeciesConservationMeasuresByName(): void + { + $conservationMeasures = $this->client->getSpeciesConservationMeasuresByName('Loxodonta africana'); + + self::assertCount(20, $conservationMeasures); + + $conservationMeasure = $conservationMeasures->first(); + + self::assertInstanceOf(ConservationMeasure::class, $conservationMeasure); + self::assertEquals('1.1', $conservationMeasure->getCode()); + self::assertEquals('Site/area protection', $conservationMeasure->getTitle()); + + $conservationMeasures = $this->client->getSpeciesConservationMeasuresByName('Ursus maritimus', 'europe'); + + self::assertCount(3, $conservationMeasures); + + $conservationMeasure = $conservationMeasures->first(); + + self::assertInstanceOf(ConservationMeasure::class, $conservationMeasure); + self::assertEquals('1.1', $conservationMeasure->getCode()); + self::assertEquals('Site/area protection', $conservationMeasure->getTitle()); + } + + public function testGetSpeciesCount(): void + { + self::assertEquals(150754, $this->client->getSpeciesCount()); + self::assertEquals(147517, $this->client->getSpeciesCount(null, false)); + self::assertEquals(16232, $this->client->getSpeciesCount('europe')); + self::assertEquals(16132, $this->client->getSpeciesCount('europe', false)); + } + + public function testGetSpeciesHabitatsById(): void + { + $habitats = $this->client->getSpeciesHabitatsById(181008073); + + self::assertCount(17, $habitats); + + $habitat = $habitats->first(); + + self::assertInstanceOf(Habitat::class, $habitat); + self::assertEquals('1.5', $habitat->getCode()); + self::assertEquals('Yes', $habitat->getMajorImportance()); + self::assertEquals('Forest - Subtropical/Tropical Dry', $habitat->getName()); + self::assertNull($habitat->getSeason()); + self::assertEquals('Suitable', $habitat->getSuitability()); + + $habitats = $this->client->getSpeciesHabitatsById(22823, 'europe'); + + self::assertCount(6, $habitats); + + $habitat = $habitats->first(); + + self::assertInstanceOf(Habitat::class, $habitat); + self::assertEquals('12.1', $habitat->getCode()); + self::assertNull($habitat->getMajorImportance()); + self::assertEquals('Marine Intertidal - Rocky Shoreline', $habitat->getName()); + self::assertNull($habitat->getSeason()); + self::assertEquals('Suitable', $habitat->getSuitability()); + } + + public function testGetSpeciesHabitatsByName(): void + { + $habitats = $this->client->getSpeciesHabitatsByName('Loxodonta africana'); + + self::assertCount(17, $habitats); + + $habitat = $habitats->first(); + + self::assertInstanceOf(Habitat::class, $habitat); + self::assertEquals('1.5', $habitat->getCode()); + self::assertEquals('Yes', $habitat->getMajorImportance()); + self::assertEquals('Forest - Subtropical/Tropical Dry', $habitat->getName()); + self::assertNull($habitat->getSeason()); + self::assertEquals('Suitable', $habitat->getSuitability()); + + $habitats = $this->client->getSpeciesHabitatsByName('Ursus maritimus', 'europe'); + + self::assertCount(6, $habitats); + + $habitat = $habitats->first(); + + self::assertInstanceOf(Habitat::class, $habitat); + self::assertEquals('12.1', $habitat->getCode()); + self::assertNull($habitat->getMajorImportance()); + self::assertEquals('Marine Intertidal - Rocky Shoreline', $habitat->getName()); + self::assertNull($habitat->getSeason()); + self::assertEquals('Suitable', $habitat->getSuitability()); + } + + public function testGetSpeciesNarrativeTextById(): void + { + $narrativeText = $this->client->getSpeciesNarrativeTextById(181008073); + + self::assertInstanceOf(NarrativeText::class, $narrativeText); + self::assertStringStartsWith('

The African Savanna', (string)$narrativeText->getConservationMeasures()); + self::assertStringStartsWith('

African Savanna Elephants', (string)$narrativeText->getGeographicRange()); + self::assertStringStartsWith('

African Savanna Elephants', (string)$narrativeText->getHabitat()); + self::assertStringStartsWith('

Over the past century', (string)$narrativeText->getPopulation()); + self::assertEquals('decreasing', $narrativeText->getPopulationTrend()); + self::assertStringStartsWith('

The African Savanna Elephant', (string)$narrativeText->getRationale()); + self::assertStringStartsWith('Three elephant taxa remain', (string)$narrativeText->getTaxonomicNotes()); + self::assertStringStartsWith('Poaching of African Savanna Elephants', (string)$narrativeText->getThreats()); + self::assertStringStartsWith('Ivory:', (string)$narrativeText->getUseTrade()); + + $narrativeText = $this->client->getSpeciesNarrativeTextById(2467, 'europe'); + + self::assertInstanceOf(NarrativeText::class, $narrativeText); + self::assertStringStartsWith('The International', (string)$narrativeText->getConservationMeasures()); + self::assertStringStartsWith('Bowhead whales are found', (string)$narrativeText->getGeographicRange()); + self::assertStringStartsWith('The seasonal distribution', (string)$narrativeText->getHabitat()); + self::assertStringStartsWith('Current population size', (string)$narrativeText->getPopulation()); + self::assertEquals('unknown', $narrativeText->getPopulationTrend()); + self::assertStringStartsWith('This species is assessed', (string)$narrativeText->getRationale()); + self::assertStringStartsWith('The taxonomy is not in doubt', (string)$narrativeText->getTaxonomicNotes()); + self::assertStringStartsWith('Heavy commercial hunting', (string)$narrativeText->getThreats()); + self::assertNull($narrativeText->getUseTrade()); + } + + public function testGetSpeciesNarrativeTextByName(): void + { + $narrativeText = $this->client->getSpeciesNarrativeTextByName('Loxodonta africana'); + + self::assertInstanceOf(NarrativeText::class, $narrativeText); + self::assertStringStartsWith('

The African Savanna', (string)$narrativeText->getConservationMeasures()); + self::assertStringStartsWith('

African Savanna Elephants', (string)$narrativeText->getGeographicRange()); + self::assertStringStartsWith('

African Savanna Elephants', (string)$narrativeText->getHabitat()); + self::assertStringStartsWith('

Over the past century', (string)$narrativeText->getPopulation()); + self::assertEquals('decreasing', $narrativeText->getPopulationTrend()); + self::assertStringStartsWith('

The African Savanna Elephant', (string)$narrativeText->getRationale()); + self::assertStringStartsWith('Three elephant taxa remain', (string)$narrativeText->getTaxonomicNotes()); + self::assertStringStartsWith('Poaching of African Savanna Elephants', (string)$narrativeText->getThreats()); + self::assertStringStartsWith('Ivory:', (string)$narrativeText->getUseTrade()); + + $narrativeText = $this->client->getSpeciesNarrativeTextByName('Fratercula arctica', 'europe'); + + self::assertInstanceOf(NarrativeText::class, $narrativeText); + self::assertStringStartsWith('Conservation Actions', (string)$narrativeText->getConservationMeasures()); + self::assertStringStartsWith('The species can be found', (string)$narrativeText->getGeographicRange()); + self::assertStringStartsWith('The breeding range', (string)$narrativeText->getHabitat()); + self::assertStringStartsWith('The European breeding', (string)$narrativeText->getPopulation()); + self::assertEquals('decreasing', $narrativeText->getPopulationTrend()); + self::assertStringStartsWith('European regional assessment', (string)$narrativeText->getRationale()); + self::assertNull($narrativeText->getTaxonomicNotes()); + self::assertStringStartsWith('This species is highly', (string)$narrativeText->getThreats()); + self::assertNull($narrativeText->getUseTrade()); + } + + public function testGetSpeciesOccurrencesById(): void + { + $occurrences = $this->client->getSpeciesOccurrencesById(181008073); + + self::assertCount(26, $occurrences); + + $occurrence = $occurrences->first(); + + self::assertInstanceOf(Occurrence::class, $occurrence); + self::assertEquals('AO', $occurrence->getCountryCode()); + self::assertEquals('Angola', $occurrence->getCountryName()); + self::assertEquals('Native', $occurrence->getDistributionCode()); + self::assertEquals('Native', $occurrence->getOrigin()); + self::assertEquals('Extant', $occurrence->getPresence()); + + $occurrences = $this->client->getSpeciesOccurrencesById(22823, 'europe'); + + self::assertCount(3, $occurrences); + + $occurrence = $occurrences->first(); + + self::assertInstanceOf(Occurrence::class, $occurrence); + self::assertEquals('NO', $occurrence->getCountryCode()); + self::assertEquals('Norway', $occurrence->getCountryName()); + self::assertEquals('Native', $occurrence->getDistributionCode()); + self::assertEquals('Native', $occurrence->getOrigin()); + self::assertEquals('Extant', $occurrence->getPresence()); + } + + public function testGetSpeciesOccurrencesByName(): void + { + $occurrences = $this->client->getSpeciesOccurrencesByName('Loxodonta africana'); + + self::assertCount(26, $occurrences); + + $occurrence = $occurrences->first(); + + self::assertInstanceOf(Occurrence::class, $occurrence); + self::assertEquals('AO', $occurrence->getCountryCode()); + self::assertEquals('Angola', $occurrence->getCountryName()); + self::assertEquals('Native', $occurrence->getDistributionCode()); + self::assertEquals('Native', $occurrence->getOrigin()); + self::assertEquals('Extant', $occurrence->getPresence()); + + $occurrences = $this->client->getSpeciesOccurrencesByName('Ursus maritimus', 'europe'); + + self::assertCount(3, $occurrences); + + $occurrence = $occurrences->first(); + + self::assertInstanceOf(Occurrence::class, $occurrence); + self::assertEquals('NO', $occurrence->getCountryCode()); + self::assertEquals('Norway', $occurrence->getCountryName()); + self::assertEquals('Native', $occurrence->getDistributionCode()); + self::assertEquals('Native', $occurrence->getOrigin()); + self::assertEquals('Extant', $occurrence->getPresence()); + } + + public function testGetSpeciesSynonyms(): void + { + $synonyms = $this->client->getSpeciesSynonyms('Loxodonta africana'); + + self::assertCount(3, $synonyms); + + $synonym = $synonyms->first(); + + self::assertInstanceOf(Synonym::class, $synonym); + self::assertEquals(181007989, $synonym->getAcceptedId()); + self::assertEquals('Loxodonta cyclotis', $synonym->getAcceptedName()); + self::assertEquals('Matschie, 1900', $synonym->getAcceptedNameAuthority()); + self::assertEquals('Loxodonta africana', $synonym->getSynonym()); + self::assertNull($synonym->getSynonymAuthority()); + } + + public function testGetSpeciesThreatsById(): void + { + $threats = $this->client->getSpeciesThreatsById(181008073); + + self::assertCount(40, $threats); + + $threat = $threats->first(); + + self::assertInstanceOf(Threat::class, $threat); + self::assertEquals('1.1', $threat->getCode()); + self::assertNull($threat->getInvasive()); + self::assertEquals('Minority (<50%)', $threat->getScope()); + self::assertEquals('Unknown', $threat->getScore()); + self::assertEquals('Unknown', $threat->getSeverity()); + self::assertEquals('Ongoing', $threat->getTiming()); + self::assertEquals('Housing & urban areas', $threat->getTitle()); + + $threats = $this->client->getSpeciesThreatsById(2467, 'europe'); + + self::assertCount(2, $threats); + + $threat = $threats->first(); + + self::assertInstanceOf(Threat::class, $threat); + self::assertEquals('9.2', $threat->getCode()); + self::assertNull($threat->getInvasive()); + self::assertNull($threat->getScope()); + self::assertNull($threat->getScore()); + self::assertNull($threat->getSeverity()); + self::assertEquals('Ongoing', $threat->getTiming()); + self::assertEquals('Industrial & military effluents', $threat->getTitle()); + } + + public function testGetSpeciesThreatsByName(): void + { + $threats = $this->client->getSpeciesThreatsByName('Loxodonta africana'); + + self::assertCount(40, $threats); + + $threat = $threats->first(); + + self::assertInstanceOf(Threat::class, $threat); + self::assertEquals('1.1', $threat->getCode()); + self::assertNull($threat->getInvasive()); + self::assertEquals('Minority (<50%)', $threat->getScope()); + self::assertEquals('Unknown', $threat->getScore()); + self::assertEquals('Unknown', $threat->getSeverity()); + self::assertEquals('Ongoing', $threat->getTiming()); + self::assertEquals('Housing & urban areas', $threat->getTitle()); + + $threats = $this->client->getSpeciesThreatsByName('Fratercula arctica', 'europe'); + + self::assertCount(17, $threats); + + $threat = $threats->first(); + + self::assertInstanceOf(Threat::class, $threat); + self::assertEquals('11.1', $threat->getCode()); + self::assertNull($threat->getInvasive()); + self::assertEquals('Unknown', $threat->getScope()); + self::assertEquals('Unknown', $threat->getScore()); + self::assertEquals('Unknown', $threat->getSeverity()); + self::assertEquals('Ongoing', $threat->getTiming()); + self::assertEquals('Habitat shifting & alteration', $threat->getTitle()); + } + + public function testGetVersion(): void + { + self::assertEquals('2022-1', $this->client->getVersion()); + } + + public function testGetWebsiteLink(): void + { + self::assertEquals( + 'https://www.iucnredlist.org/species/181008073/204401095', + $this->client->getWebsiteLink('Loxodonta africana') + ); + } +} diff --git a/sites/all/libraries/vendor/jbroutier/iucn-api-client/tests/Functional/responses.json b/sites/all/libraries/vendor/jbroutier/iucn-api-client/tests/Functional/responses.json new file mode 100644 index 0000000000..23c04c6645 --- /dev/null +++ b/sites/all/libraries/vendor/jbroutier/iucn-api-client/tests/Functional/responses.json @@ -0,0 +1,67769 @@ +{ + "https://apiv3.iucnredlist.org/api/v3/comp-group/getspecies/mammals": { + "count": 6423, + "result": [ + { + "taxonid": 42641, + "scientific_name": "Abditomys latidens", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 17879, + "scientific_name": "Abeomelomys sevia", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 47761700, + "scientific_name": "Abrawayaomys chebezi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 47760825, + "scientific_name": "Abrawayaomys ruschii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 42656, + "scientific_name": "Abrocoma bennettii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 18, + "scientific_name": "Abrocoma boliviensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 136334, + "scientific_name": "Abrocoma budini", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 42657, + "scientific_name": "Abrocoma cinerea", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136367, + "scientific_name": "Abrocoma famatina", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 136525, + "scientific_name": "Abrocoma shistacea", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136302, + "scientific_name": "Abrocoma uspallata", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 136546, + "scientific_name": "Abrocoma vaccarum", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 4803, + "scientific_name": "Abrothrix andinus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 735, + "scientific_name": "Abrothrix illutea", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 4804, + "scientific_name": "Abrothrix jelskii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 740, + "scientific_name": "Abrothrix lanosus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 743, + "scientific_name": "Abrothrix longipilis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 114955961, + "scientific_name": "Abrothrix manni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 751, + "scientific_name": "Abrothrix olivaceus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 754, + "scientific_name": "Abrothrix sanborni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 137, + "scientific_name": "Acerodon celebensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 138, + "scientific_name": "Acerodon humilis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 139, + "scientific_name": "Acerodon jubatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 140, + "scientific_name": "Acerodon leucotis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 142, + "scientific_name": "Acerodon mackloti", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 219, + "scientific_name": "Acinonyx jubatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 44936, + "scientific_name": "Acomys airensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 263, + "scientific_name": "Acomys cahirinus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 264, + "scientific_name": "Acomys cilicicus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 265, + "scientific_name": "Acomys cineraceus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136471, + "scientific_name": "Acomys dimidiatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 266, + "scientific_name": "Acomys ignitus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 44938, + "scientific_name": "Acomys johannis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 267, + "scientific_name": "Acomys kempi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 268, + "scientific_name": "Acomys louisae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 269, + "scientific_name": "Acomys minous", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 270, + "scientific_name": "Acomys mullah", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 47806447, + "scientific_name": "Acomys muzei", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 271, + "scientific_name": "Acomys nesiotes", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 47806534, + "scientific_name": "Acomys ngurui", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 272, + "scientific_name": "Acomys percivali", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 273, + "scientific_name": "Acomys russatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 47806307, + "scientific_name": "Acomys selousi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 44939, + "scientific_name": "Acomys seurati", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 47805694, + "scientific_name": "Acomys spinosissimus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 275, + "scientific_name": "Acomys subspinosus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 276, + "scientific_name": "Acomys wilsoni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 278, + "scientific_name": "Aconaemys fuscus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136331, + "scientific_name": "Aconaemys porteri", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 279, + "scientific_name": "Aconaemys sagei", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 40584, + "scientific_name": "Acrobates pygmaeus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 512, + "scientific_name": "Addax nasomaculatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 15595, + "scientific_name": "Aegialomys galapagoensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 15616, + "scientific_name": "Aegialomys xanthaeolus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 548, + "scientific_name": "Aepeomys lugens", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136484, + "scientific_name": "Aepeomys reigi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 550, + "scientific_name": "Aepyceros melampus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136944, + "scientific_name": "Aepyceros melampus ssp. melampus", + "subspecies": "melampus", + "rank": "ssp.", + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 549, + "scientific_name": "Aepyceros melampus ssp. petersi", + "subspecies": "petersi", + "rank": "ssp.", + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 40558, + "scientific_name": "Aepyprymnus rufescens", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 555, + "scientific_name": "Aeretes melanopterus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 556, + "scientific_name": "Aeromys tephromelas", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 557, + "scientific_name": "Aeromys thomasi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136541, + "scientific_name": "Aethalops aequalis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 565, + "scientific_name": "Aethalops alecto", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 568, + "scientific_name": "Aethomys bocagei", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 569, + "scientific_name": "Aethomys chrysophilus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 571, + "scientific_name": "Aethomys hindei", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 44990, + "scientific_name": "Aethomys ineptus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 572, + "scientific_name": "Aethomys kaiseri", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 574, + "scientific_name": "Aethomys nyikae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 567, + "scientific_name": "Aethomys silindensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 575, + "scientific_name": "Aethomys stannarius", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 576, + "scientific_name": "Aethomys thomasi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 712, + "scientific_name": "Ailuropoda melanoleuca", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 136218, + "scientific_name": "Ailurops melanotis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 40637, + "scientific_name": "Ailurops ursinus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 714, + "scientific_name": "Ailurus fulgens", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 723, + "scientific_name": "Akodon aerosus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 724, + "scientific_name": "Akodon affinis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 725, + "scientific_name": "Akodon albiventer", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 726, + "scientific_name": "Akodon azarae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 728, + "scientific_name": "Akodon boliviensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 729, + "scientific_name": "Akodon budini", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 114956458, + "scientific_name": "Akodon caenosus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 730, + "scientific_name": "Akodon cursor", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 731, + "scientific_name": "Akodon dayi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 114957548, + "scientific_name": "Akodon dolores", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 733, + "scientific_name": "Akodon fumeus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 736, + "scientific_name": "Akodon iniscatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 737, + "scientific_name": "Akodon juninensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 739, + "scientific_name": "Akodon kofordi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 742, + "scientific_name": "Akodon lindberghi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 753, + "scientific_name": "Akodon lutescens", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 746, + "scientific_name": "Akodon mimus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 747, + "scientific_name": "Akodon molinae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 748, + "scientific_name": "Akodon mollis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136197, + "scientific_name": "Akodon montensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136260, + "scientific_name": "Akodon mystax", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 752, + "scientific_name": "Akodon orophilus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136483, + "scientific_name": "Akodon paranaensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136676, + "scientific_name": "Akodon pervalens", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 136753, + "scientific_name": "Akodon philipmyersi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 48300296, + "scientific_name": "Akodon polopi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136693, + "scientific_name": "Akodon reigi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 755, + "scientific_name": "Akodon sanctipaulensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 756, + "scientific_name": "Akodon serrensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 757, + "scientific_name": "Akodon siberiae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 758, + "scientific_name": "Akodon simulator", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 48302281, + "scientific_name": "Akodon spegazzinii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 760, + "scientific_name": "Akodon subfuscus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 761, + "scientific_name": "Akodon surdus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 762, + "scientific_name": "Akodon sylvanus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 763, + "scientific_name": "Akodon toba", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 764, + "scientific_name": "Akodon torques", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 766, + "scientific_name": "Akodon varius", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 811, + "scientific_name": "Alcelaphus buselaphus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 813, + "scientific_name": "Alcelaphus buselaphus ssp. buselaphus", + "subspecies": "buselaphus", + "rank": "ssp.", + "subpopulation": null, + "category": "EX" + }, + { + "taxonid": 814, + "scientific_name": "Alcelaphus buselaphus ssp. caama", + "subspecies": "caama", + "rank": "ssp.", + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 815, + "scientific_name": "Alcelaphus buselaphus ssp. cokii", + "subspecies": "cokii", + "rank": "ssp.", + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 816, + "scientific_name": "Alcelaphus buselaphus ssp. lelwel", + "subspecies": "lelwel", + "rank": "ssp.", + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 812, + "scientific_name": "Alcelaphus buselaphus ssp. lichtensteinii", + "subspecies": "lichtensteinii", + "rank": "ssp.", + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 817, + "scientific_name": "Alcelaphus buselaphus ssp. major", + "subspecies": "major", + "rank": "ssp.", + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 809, + "scientific_name": "Alcelaphus buselaphus ssp. swaynei", + "subspecies": "swaynei", + "rank": "ssp.", + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 810, + "scientific_name": "Alcelaphus buselaphus ssp. tora", + "subspecies": "tora", + "rank": "ssp.", + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 56003281, + "scientific_name": "Alces alces", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 843, + "scientific_name": "Alionycteris paucidentata", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 851, + "scientific_name": "Allactaga balikunica", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 852, + "scientific_name": "Allactaga bullata", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 855, + "scientific_name": "Allactaga firouzi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 856, + "scientific_name": "Allactaga hotsoni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 857, + "scientific_name": "Allactaga major", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 858, + "scientific_name": "Allactaga severtzovi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 859, + "scientific_name": "Allactaga sibirica", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 850, + "scientific_name": "Allactaga tetradactyla", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 861, + "scientific_name": "Allactodipus bobrinskii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 865, + "scientific_name": "Allenopithecus nigroviridis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 868, + "scientific_name": "Allocebus trichotis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 4220, + "scientific_name": "Allochrocebus lhoesti", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 4227, + "scientific_name": "Allochrocebus preussi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 4237, + "scientific_name": "Allochrocebus preussi ssp. insularis", + "subspecies": "insularis", + "rank": "ssp.", + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 4244, + "scientific_name": "Allochrocebus preussi ssp. preussi", + "subspecies": "preussi", + "rank": "ssp.", + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 4230, + "scientific_name": "Allochrocebus solatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 42640, + "scientific_name": "Allocricetulus curtatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 875, + "scientific_name": "Allocricetulus eversmanni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136486, + "scientific_name": "Alouatta arctoidea", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 39957, + "scientific_name": "Alouatta belzebul", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 41545, + "scientific_name": "Alouatta caraya", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 43912, + "scientific_name": "Alouatta discolor", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 39916, + "scientific_name": "Alouatta guariba", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 39918, + "scientific_name": "Alouatta guariba ssp. clamitans", + "subspecies": "clamitans", + "rank": "ssp.", + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 39917, + "scientific_name": "Alouatta guariba ssp. guariba", + "subspecies": "guariba", + "rank": "ssp.", + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 922, + "scientific_name": "Alouatta juara", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 198622924, + "scientific_name": "Alouatta macconnelli", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136332, + "scientific_name": "Alouatta nigerrima", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 39960, + "scientific_name": "Alouatta palliata", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 919, + "scientific_name": "Alouatta palliata ssp. aequatorialis", + "subspecies": "aequatorialis", + "rank": "ssp.", + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 43899, + "scientific_name": "Alouatta palliata ssp. coibensis", + "subspecies": "coibensis", + "rank": "ssp.", + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 925, + "scientific_name": "Alouatta palliata ssp. mexicana", + "subspecies": "mexicana", + "rank": "ssp.", + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 43928, + "scientific_name": "Alouatta palliata ssp. palliata", + "subspecies": "palliata", + "rank": "ssp.", + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 43900, + "scientific_name": "Alouatta palliata ssp. trabeata", + "subspecies": "trabeata", + "rank": "ssp.", + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 914, + "scientific_name": "Alouatta pigra", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 136787, + "scientific_name": "Alouatta puruensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 41546, + "scientific_name": "Alouatta sara", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 198676562, + "scientific_name": "Alouatta seniculus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 918, + "scientific_name": "Alouatta ululata", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 948, + "scientific_name": "Alticola albicaudus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 949, + "scientific_name": "Alticola argentatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 950, + "scientific_name": "Alticola barakshin", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 951, + "scientific_name": "Alticola lemminus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 952, + "scientific_name": "Alticola macrotis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 953, + "scientific_name": "Alticola montosa", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 136648, + "scientific_name": "Alticola olchonensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 954, + "scientific_name": "Alticola roylei", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 955, + "scientific_name": "Alticola semicanus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 956, + "scientific_name": "Alticola stoliczkanus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 958, + "scientific_name": "Alticola strelzowi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 959, + "scientific_name": "Alticola tuvinicus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 62006, + "scientific_name": "Amblysomus corriae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 41316, + "scientific_name": "Amblysomus hottentotus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 62007, + "scientific_name": "Amblysomus marleyi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 62008, + "scientific_name": "Amblysomus robustus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 62009, + "scientific_name": "Amblysomus septentrionalis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 1137, + "scientific_name": "Ametrida centurio", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 1140, + "scientific_name": "Ammodillus imbellis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 1141, + "scientific_name": "Ammodorcas clarkei", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 42399, + "scientific_name": "Ammospermophilus harrisii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 42451, + "scientific_name": "Ammospermophilus interpres", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 42452, + "scientific_name": "Ammospermophilus leucurus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 1149, + "scientific_name": "Ammospermophilus nelsoni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 1151, + "scientific_name": "Ammotragus lervia", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 1154, + "scientific_name": "Amorphochilus schnablii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 136723, + "scientific_name": "Amphinectomys savamis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 39593, + "scientific_name": "Anathana ellioti", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 1264, + "scientific_name": "Andalgalomys olrogi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 1265, + "scientific_name": "Andalgalomys pearsoni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 1271, + "scientific_name": "Andinomys edax", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 1307, + "scientific_name": "Anisomys imitator", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 44857, + "scientific_name": "Anomalurus beecrofti", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 1550, + "scientific_name": "Anomalurus derbianus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 1551, + "scientific_name": "Anomalurus pelii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 1553, + "scientific_name": "Anomalurus pusillus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 1559, + "scientific_name": "Anonymomys mindorensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 1564, + "scientific_name": "Anotomys leander", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 88109381, + "scientific_name": "Anoura aequatoris", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 88109476, + "scientific_name": "Anoura cadenai", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 88108473, + "scientific_name": "Anoura caudifer", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 1566, + "scientific_name": "Anoura cultrata", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136239, + "scientific_name": "Anoura fistulata", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 88109511, + "scientific_name": "Anoura geoffroyi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 1568, + "scientific_name": "Anoura latidens", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 1569, + "scientific_name": "Anoura luismanueli", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 88109497, + "scientific_name": "Anoura peruana", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136802, + "scientific_name": "Anourosorex assamensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136589, + "scientific_name": "Anourosorex schmidi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 41450, + "scientific_name": "Anourosorex squamipes", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136257, + "scientific_name": "Anourosorex yamashinai", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 1581, + "scientific_name": "Antechinomys laniger", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136555, + "scientific_name": "Antechinus adustus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 1590, + "scientific_name": "Antechinus agilis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 40523, + "scientific_name": "Antechinus bellus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 40524, + "scientific_name": "Antechinus flavipes", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 1583, + "scientific_name": "Antechinus godmani", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 1584, + "scientific_name": "Antechinus leo", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 40525, + "scientific_name": "Antechinus minimus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 40526, + "scientific_name": "Antechinus stuartii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136755, + "scientific_name": "Antechinus subtropicus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41508, + "scientific_name": "Antechinus swainsonii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 1620, + "scientific_name": "Anthops ornatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 1676, + "scientific_name": "Antidorcas marsupialis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 1677, + "scientific_name": "Antilocapra americana", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 1679, + "scientific_name": "Antilocapra americana ssp. peninsularis", + "subspecies": "peninsularis", + "rank": "ssp.", + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 1680, + "scientific_name": "Antilocapra americana ssp. sonoriensis", + "subspecies": "sonoriensis", + "rank": "ssp.", + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 1681, + "scientific_name": "Antilope cervicapra", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 1790, + "scientific_name": "Antrozous pallidus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 1793, + "scientific_name": "Aonyx capensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 44166, + "scientific_name": "Aonyx cinereus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 1794, + "scientific_name": "Aonyx congicus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 41539, + "scientific_name": "Aotus azarae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 43930, + "scientific_name": "Aotus azarae ssp. azarae", + "subspecies": "azarae", + "rank": "ssp.", + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 43931, + "scientific_name": "Aotus azarae ssp. boliviensis", + "subspecies": "boliviensis", + "rank": "ssp.", + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 43932, + "scientific_name": "Aotus azarae ssp. infulatus", + "subspecies": "infulatus", + "rank": "ssp.", + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 39915, + "scientific_name": "Aotus brumbacki", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 1807, + "scientific_name": "Aotus griseimembra", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 136211, + "scientific_name": "Aotus jorgehernandezi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 1808, + "scientific_name": "Aotus lemurinus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 1802, + "scientific_name": "Aotus miconax", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 41540, + "scientific_name": "Aotus nancymaae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 41542, + "scientific_name": "Aotus nigriceps", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41543, + "scientific_name": "Aotus trivirgatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41544, + "scientific_name": "Aotus vociferans", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 39953, + "scientific_name": "Aotus zonalis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 1869, + "scientific_name": "Aplodontia rufa", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 1888, + "scientific_name": "Apodemus agrarius", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 1907, + "scientific_name": "Apodemus alpicola", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 42642, + "scientific_name": "Apodemus argenteus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 45953906, + "scientific_name": "Apodemus avicennicus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 1890, + "scientific_name": "Apodemus chevrieri", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 1891, + "scientific_name": "Apodemus draco", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136816, + "scientific_name": "Apodemus epimelas", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 1892, + "scientific_name": "Apodemus flavicollis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 1894, + "scientific_name": "Apodemus gurkha", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 1896, + "scientific_name": "Apodemus hyrcanicus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 1897, + "scientific_name": "Apodemus latronum", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 1898, + "scientific_name": "Apodemus mystacinus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 1906, + "scientific_name": "Apodemus pallipes", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 1899, + "scientific_name": "Apodemus peninsulae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 1900, + "scientific_name": "Apodemus ponticus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 1901, + "scientific_name": "Apodemus rusiges", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 1902, + "scientific_name": "Apodemus semotus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 1903, + "scientific_name": "Apodemus speciosus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 1904, + "scientific_name": "Apodemus sylvaticus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 1905, + "scientific_name": "Apodemus uralensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 135724, + "scientific_name": "Apodemus witherbyi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 42643, + "scientific_name": "Apomys abrae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 45953968, + "scientific_name": "Apomys aurorae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 45953973, + "scientific_name": "Apomys banahao", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 45953976, + "scientific_name": "Apomys brownorum", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 136362, + "scientific_name": "Apomys camiguinensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 1910, + "scientific_name": "Apomys datae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 1917, + "scientific_name": "Apomys gracilirostris", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 1911, + "scientific_name": "Apomys hylocetes", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 1912, + "scientific_name": "Apomys insignis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 112139458, + "scientific_name": "Apomys iridensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 1913, + "scientific_name": "Apomys littoralis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 112139517, + "scientific_name": "Apomys lubangensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 45953979, + "scientific_name": "Apomys magnus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 1914, + "scientific_name": "Apomys microdon", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 45953991, + "scientific_name": "Apomys minganensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 1915, + "scientific_name": "Apomys musculus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 1916, + "scientific_name": "Apomys sacobianus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 45954010, + "scientific_name": "Apomys sierrae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 45954013, + "scientific_name": "Apomys zambalensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 1933, + "scientific_name": "Aproteles bulmerae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 9918, + "scientific_name": "Arabitragus jayakari", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 2017, + "scientific_name": "Arborimus albipes", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 42615, + "scientific_name": "Arborimus longicaudus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 2018, + "scientific_name": "Arborimus pomo", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 2045, + "scientific_name": "Archboldomys luzonensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 45954066, + "scientific_name": "Archboldomys maximus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 41690, + "scientific_name": "Arctictis binturong", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 2053, + "scientific_name": "Arctocebus aureus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 2054, + "scientific_name": "Arctocebus calabarensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 2055, + "scientific_name": "Arctocephalus australis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 72050476, + "scientific_name": "Arctocephalus australis Peruvian/Northern Chilean subpopulation", + "subspecies": null, + "rank": null, + "subpopulation": "Peruvian/Northern Chilean subpopulation", + "category": "VU" + }, + { + "taxonid": 2064, + "scientific_name": "Arctocephalus australis ssp. australis", + "subspecies": "australis", + "rank": "ssp.", + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41664, + "scientific_name": "Arctocephalus forsteri", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 2057, + "scientific_name": "Arctocephalus galapagoensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 2058, + "scientific_name": "Arctocephalus gazella", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 2059, + "scientific_name": "Arctocephalus philippii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 2060, + "scientific_name": "Arctocephalus pusillus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 2067, + "scientific_name": "Arctocephalus pusillus ssp. doriferus", + "subspecies": "doriferus", + "rank": "ssp.", + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 2066, + "scientific_name": "Arctocephalus pusillus ssp. pusillus", + "subspecies": "pusillus", + "rank": "ssp.", + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 2061, + "scientific_name": "Arctocephalus townsendi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 2062, + "scientific_name": "Arctocephalus tropicalis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41691, + "scientific_name": "Arctogalidia trivirgata", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 70206273, + "scientific_name": "Arctonyx albogularis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 70205537, + "scientific_name": "Arctonyx collaris", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 70205771, + "scientific_name": "Arctonyx hoevenii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 2089, + "scientific_name": "Ardops nichollsi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41534, + "scientific_name": "Arielulus circumdatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 40775, + "scientific_name": "Arielulus cuprosus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 40776, + "scientific_name": "Arielulus societatis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 40032, + "scientific_name": "Arielulus torquatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 2110, + "scientific_name": "Ariteus flavescens", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 88109970, + "scientific_name": "Artibeus aequatorialis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 2121, + "scientific_name": "Artibeus amplus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 2125, + "scientific_name": "Artibeus concolor", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 2126, + "scientific_name": "Artibeus fimbriatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 2127, + "scientific_name": "Artibeus fraterculus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 2131, + "scientific_name": "Artibeus hirsutus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 2132, + "scientific_name": "Artibeus inopinatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 88109731, + "scientific_name": "Artibeus jamaicensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 2136, + "scientific_name": "Artibeus lituratus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 2137, + "scientific_name": "Artibeus obscurus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 2139, + "scientific_name": "Artibeus planirostris", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 88109897, + "scientific_name": "Artibeus schwartzi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 2144, + "scientific_name": "Arvicanthis abyssinicus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 44991, + "scientific_name": "Arvicanthis ansorgei", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 2145, + "scientific_name": "Arvicanthis blicki", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 2146, + "scientific_name": "Arvicanthis nairobae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 2148, + "scientific_name": "Arvicanthis neumanni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 2147, + "scientific_name": "Arvicanthis niloticus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 44992, + "scientific_name": "Arvicanthis rufinus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 2149, + "scientific_name": "Arvicola amphibius", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 2150, + "scientific_name": "Arvicola sapidus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 136766, + "scientific_name": "Arvicola scherman", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 80222726, + "scientific_name": "Asellia arabica", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 80221456, + "scientific_name": "Asellia italosomalica", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 2153, + "scientific_name": "Asellia patrizii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 80221529, + "scientific_name": "Asellia tridens", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 2155, + "scientific_name": "Aselliscus stoliczkanus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 2156, + "scientific_name": "Aselliscus tricuspidatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 40602, + "scientific_name": "Atelerix albiventris", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 27926, + "scientific_name": "Atelerix algirus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 2274, + "scientific_name": "Atelerix frontalis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 2275, + "scientific_name": "Atelerix sclateri", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 2276, + "scientific_name": "Ateles belzebuth", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 41547, + "scientific_name": "Ateles chamek", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 135446, + "scientific_name": "Ateles fusciceps", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 39922, + "scientific_name": "Ateles fusciceps ssp. fusciceps", + "subspecies": "fusciceps", + "rank": "ssp.", + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 39921, + "scientific_name": "Ateles fusciceps ssp. rufiventris", + "subspecies": "rufiventris", + "rank": "ssp.", + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 2279, + "scientific_name": "Ateles geoffroyi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 2286, + "scientific_name": "Ateles geoffroyi ssp. azuerensis", + "subspecies": "azuerensis", + "rank": "ssp.", + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 2280, + "scientific_name": "Ateles geoffroyi ssp. frontatus", + "subspecies": "frontatus", + "rank": "ssp.", + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 43901, + "scientific_name": "Ateles geoffroyi ssp. geoffroyi", + "subspecies": "geoffroyi", + "rank": "ssp.", + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 2287, + "scientific_name": "Ateles geoffroyi ssp. grisescens", + "subspecies": "grisescens", + "rank": "ssp.", + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 2289, + "scientific_name": "Ateles geoffroyi ssp. ornatus", + "subspecies": "ornatus", + "rank": "ssp.", + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 160872795, + "scientific_name": "Ateles geoffroyi ssp. vellerosus", + "subspecies": "vellerosus", + "rank": "ssp.", + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 39961, + "scientific_name": "Ateles hybridus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 2282, + "scientific_name": "Ateles marginatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 2283, + "scientific_name": "Ateles paniscus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 6924, + "scientific_name": "Atelocynus microtis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 2353, + "scientific_name": "Atherurus africanus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 2354, + "scientific_name": "Atherurus macrourus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41590, + "scientific_name": "Atilax paludinosus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 2358, + "scientific_name": "Atlantoxerus getulus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 20320, + "scientific_name": "Atopogale cubana", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 2395, + "scientific_name": "Auliscomys boliviensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 2397, + "scientific_name": "Auliscomys pictus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 2398, + "scientific_name": "Auliscomys sublimis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 21313, + "scientific_name": "Austronomus australis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136201, + "scientific_name": "Austronomus kuboriensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136767, + "scientific_name": "Avahi betsileo", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 136335, + "scientific_name": "Avahi cleesei", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 2434, + "scientific_name": "Avahi laniger", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 136369, + "scientific_name": "Avahi meridionalis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 16971566, + "scientific_name": "Avahi mooreorum", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 2435, + "scientific_name": "Avahi occidentalis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 136285, + "scientific_name": "Avahi peyrierasi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 136434, + "scientific_name": "Avahi ramanantsoavanai", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 41579, + "scientific_name": "Avahi unicolor", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 41783, + "scientific_name": "Axis axis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 2446, + "scientific_name": "Axis calamianensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 2447, + "scientific_name": "Axis kuhlii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 41784, + "scientific_name": "Axis porcinus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 2461, + "scientific_name": "Babyrousa babyrussa", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 136446, + "scientific_name": "Babyrousa celebensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 136472, + "scientific_name": "Babyrousa togeanensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 19679, + "scientific_name": "Baeodon alleni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19681, + "scientific_name": "Baeodon gracilis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 2465, + "scientific_name": "Baiomys musculus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 2466, + "scientific_name": "Baiomys taylori", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 10311, + "scientific_name": "Baiyankamys habbema", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 10314, + "scientific_name": "Baiyankamys shawmayeri", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 2467, + "scientific_name": "Balaena mysticetus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 2468, + "scientific_name": "Balaena mysticetus Bering-Chukchi-Beaufort Sea subpopulation", + "subspecies": null, + "rank": null, + "subpopulation": "Bering-Chukchi-Beaufort Sea subpopulation", + "category": "LR/cd" + }, + { + "taxonid": 2472, + "scientific_name": "Balaena mysticetus East Greenland-Svalbard-Barents Sea subpopulation", + "subspecies": null, + "rank": null, + "subpopulation": "East Greenland-Svalbard-Barents Sea subpopulation", + "category": "EN" + }, + { + "taxonid": 2469, + "scientific_name": "Balaena mysticetus Okhotsk Sea subpopulation", + "subspecies": null, + "rank": null, + "subpopulation": "Okhotsk Sea subpopulation", + "category": "EN" + }, + { + "taxonid": 2474, + "scientific_name": "Balaenoptera acutorostrata", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 2480, + "scientific_name": "Balaenoptera bonaerensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 2475, + "scientific_name": "Balaenoptera borealis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 2476, + "scientific_name": "Balaenoptera edeni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 2477, + "scientific_name": "Balaenoptera musculus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 41713, + "scientific_name": "Balaenoptera musculus ssp. intermedia", + "subspecies": "intermedia", + "rank": "ssp.", + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 136623, + "scientific_name": "Balaenoptera omurai", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 2478, + "scientific_name": "Balaenoptera physalus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 16208224, + "scientific_name": "Balaenoptera physalus Mediterranean subpopulation", + "subspecies": null, + "rank": null, + "subpopulation": "Mediterranean subpopulation", + "category": "EN" + }, + { + "taxonid": 215823373, + "scientific_name": "Balaenoptera ricei", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 2531, + "scientific_name": "Balantiopteryx infusca", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 2532, + "scientific_name": "Balantiopteryx io", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 2533, + "scientific_name": "Balantiopteryx plicata", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 84454322, + "scientific_name": "Balionycteris maculata", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 84454980, + "scientific_name": "Balionycteris seimundi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 2540, + "scientific_name": "Bandicota bengalensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 2541, + "scientific_name": "Bandicota indica", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 2542, + "scientific_name": "Bandicota savilei", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 2553, + "scientific_name": "Barbastella barbastellus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 85180824, + "scientific_name": "Barbastella beijingensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 85197261, + "scientific_name": "Barbastella darjelingensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 85181182, + "scientific_name": "Barbastella leucomelas", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 48637566, + "scientific_name": "Bassaricyon alleni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 48637946, + "scientific_name": "Bassaricyon gabbii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 48637802, + "scientific_name": "Bassaricyon medius", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 48637280, + "scientific_name": "Bassaricyon neblina", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 41680, + "scientific_name": "Bassariscus astutus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 2613, + "scientific_name": "Bassariscus sumichrasti", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 2619, + "scientific_name": "Bathyergus janetta", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 2620, + "scientific_name": "Bathyergus suillus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 2642, + "scientific_name": "Batomys dentatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 111876319, + "scientific_name": "Batomys granti", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 45954108, + "scientific_name": "Batomys hamiguitan", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 136394, + "scientific_name": "Batomys russatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 2643, + "scientific_name": "Batomys salomonseni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 111876611, + "scientific_name": "Batomys uragon", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 1789, + "scientific_name": "Bauerus dubiaquercus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 41591, + "scientific_name": "Bdeogale crassicauda", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 2675, + "scientific_name": "Bdeogale jacksoni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 41592, + "scientific_name": "Bdeogale nigripes", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136686, + "scientific_name": "Bdeogale omnivora", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 2676, + "scientific_name": "Beamys hindei", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 6234, + "scientific_name": "Beatragus hunteri", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 2756, + "scientific_name": "Belomys pearsonii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 2762, + "scientific_name": "Berardius arnuxii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 2763, + "scientific_name": "Berardius bairdii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 178756893, + "scientific_name": "Berardius minimus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 2767, + "scientific_name": "Berylmys berdmorei", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 2768, + "scientific_name": "Berylmys bowersi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 2769, + "scientific_name": "Berylmys mackenziei", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 2770, + "scientific_name": "Berylmys manipulus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 71510353, + "scientific_name": "Bettongia anhydra", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EX" + }, + { + "taxonid": 2783, + "scientific_name": "Bettongia gaimardi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 2784, + "scientific_name": "Bettongia lesueur", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 2785, + "scientific_name": "Bettongia penicillata", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 136805, + "scientific_name": "Bettongia pusilla", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EX" + }, + { + "taxonid": 2787, + "scientific_name": "Bettongia tropica", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 2801, + "scientific_name": "Bibimys chacoensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 2802, + "scientific_name": "Bibimys labiosus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 2803, + "scientific_name": "Bibimys torresi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 2815, + "scientific_name": "Bison bison", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 2814, + "scientific_name": "Bison bonasus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 2816, + "scientific_name": "Biswamoyopterus biswasi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 88700294, + "scientific_name": "Biswamoyopterus laoensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 2823, + "scientific_name": "Blanfordimys afghanus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 2824, + "scientific_name": "Blanfordimys bucharensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41451, + "scientific_name": "Blarina brevicauda", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41452, + "scientific_name": "Blarina carolinensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41453, + "scientific_name": "Blarina hylophaga", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136365, + "scientific_name": "Blarinella griselda", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 40613, + "scientific_name": "Blarinella quadraticauda", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 2825, + "scientific_name": "Blarinella wardi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 2827, + "scientific_name": "Blarinomys breviceps", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 2828, + "scientific_name": "Blastocerus dichotomus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 19749, + "scientific_name": "Boneia bidens", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 2885, + "scientific_name": "Boromys offella", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EX" + }, + { + "taxonid": 2886, + "scientific_name": "Boromys torrei", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EX" + }, + { + "taxonid": 2893, + "scientific_name": "Boselaphus tragocamelus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 2891, + "scientific_name": "Bos gaurus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 2888, + "scientific_name": "Bos javanicus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 2892, + "scientific_name": "Bos mutus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 136721, + "scientific_name": "Bos primigenius", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EX" + }, + { + "taxonid": 2890, + "scientific_name": "Bos sauveli", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 2957, + "scientific_name": "Brachiones przewalskii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 2963, + "scientific_name": "Brachylagus idahoensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 2982, + "scientific_name": "Brachyphylla cavernarum", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 2983, + "scientific_name": "Brachyphylla nana", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 2991, + "scientific_name": "Brachytarsomys albicauda", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136399, + "scientific_name": "Brachytarsomys villosa", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 2993, + "scientific_name": "Brachyteles arachnoides", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 2994, + "scientific_name": "Brachyteles hypoxanthus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 2997, + "scientific_name": "Brachyuromys betsileoensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 2998, + "scientific_name": "Brachyuromys ramirohitra", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 61925, + "scientific_name": "Bradypus pygmaeus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 3036, + "scientific_name": "Bradypus torquatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 3037, + "scientific_name": "Bradypus tridactylus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 3038, + "scientific_name": "Bradypus variegatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 5042, + "scientific_name": "Brassomys albidens", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 3121, + "scientific_name": "Brotomys voratus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EX" + }, + { + "taxonid": 114980541, + "scientific_name": "Brucepattersonius griserufescens", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 136402, + "scientific_name": "Brucepattersonius guarani", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 136672, + "scientific_name": "Brucepattersonius igniventris", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 15787, + "scientific_name": "Brucepattersonius iheringi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136457, + "scientific_name": "Brucepattersonius misionensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 136198, + "scientific_name": "Brucepattersonius paradisus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 136442, + "scientific_name": "Brucepattersonius soricinus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 3129, + "scientific_name": "Bubalus arnee", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 3126, + "scientific_name": "Bubalus depressicornis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 3127, + "scientific_name": "Bubalus mindorensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 3128, + "scientific_name": "Bubalus quarlesi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 3160, + "scientific_name": "Budorcas taxicolor", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 3322, + "scientific_name": "Bullimus bagobus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136264, + "scientific_name": "Bullimus gamay", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 3323, + "scientific_name": "Bullimus luzonicus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 3326, + "scientific_name": "Bunolagus monticularis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 3327, + "scientific_name": "Bunomys andrewsi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 3328, + "scientific_name": "Bunomys chrysocomus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 3329, + "scientific_name": "Bunomys coelestis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 3330, + "scientific_name": "Bunomys fratrorum", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 92440861, + "scientific_name": "Bunomys karokophilus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 3332, + "scientific_name": "Bunomys penitus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 3333, + "scientific_name": "Bunomys prolatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 92440844, + "scientific_name": "Bunomys torajae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 3339, + "scientific_name": "Burramys parvus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 3412, + "scientific_name": "Cabassous centralis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 3413, + "scientific_name": "Cabassous chacoensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 3414, + "scientific_name": "Cabassous tatouay", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 3415, + "scientific_name": "Cabassous unicinctus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136419, + "scientific_name": "Cacajao ayresi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 3416, + "scientific_name": "Cacajao calvus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 3419, + "scientific_name": "Cacajao calvus ssp. calvus", + "subspecies": "calvus", + "rank": "ssp.", + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 3421, + "scientific_name": "Cacajao calvus ssp. novaesi", + "subspecies": "novaesi", + "rank": "ssp.", + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 3422, + "scientific_name": "Cacajao calvus ssp. rubicundus", + "subspecies": "rubicundus", + "rank": "ssp.", + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 3420, + "scientific_name": "Cacajao calvus ssp. ucayalii", + "subspecies": "ucayalii", + "rank": "ssp.", + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 136640, + "scientific_name": "Cacajao hosomi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 160875418, + "scientific_name": "Cacajao melanocephalus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 40521, + "scientific_name": "Caenolestes caniventer", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 136743, + "scientific_name": "Caenolestes condorensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 40522, + "scientific_name": "Caenolestes convelatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 41506, + "scientific_name": "Caenolestes fuliginosus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 51222063, + "scientific_name": "Caenolestes sangay", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 3519, + "scientific_name": "Calcochloris obtusirostris", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 4767, + "scientific_name": "Calcochloris tytonis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 41584, + "scientific_name": "Callibella humilis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 39929, + "scientific_name": "Callicebus barbarabrownae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 39954, + "scientific_name": "Callicebus coimbrai", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 39930, + "scientific_name": "Callicebus melanochir", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 39943, + "scientific_name": "Callicebus nigrifrons", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 3555, + "scientific_name": "Callicebus personatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 3564, + "scientific_name": "Callimico goeldii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 6985, + "scientific_name": "Callistomys pictus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 3570, + "scientific_name": "Callithrix aurita", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 3571, + "scientific_name": "Callithrix flaviceps", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 3572, + "scientific_name": "Callithrix geoffroyi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41518, + "scientific_name": "Callithrix jacchus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 3575, + "scientific_name": "Callithrix kuhlii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 41519, + "scientific_name": "Callithrix penicillata", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 3590, + "scientific_name": "Callorhinus ursinus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 3591, + "scientific_name": "Callosciurus adamsi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 3592, + "scientific_name": "Callosciurus albescens", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 3593, + "scientific_name": "Callosciurus baluensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 3594, + "scientific_name": "Callosciurus caniceps", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 3595, + "scientific_name": "Callosciurus erythraeus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 3596, + "scientific_name": "Callosciurus finlaysonii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 3597, + "scientific_name": "Callosciurus inornatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 3598, + "scientific_name": "Callosciurus melanogaster", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 3599, + "scientific_name": "Callosciurus nigrovittatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 3600, + "scientific_name": "Callosciurus notatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 3601, + "scientific_name": "Callosciurus orestes", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 3602, + "scientific_name": "Callosciurus phayrei", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 3603, + "scientific_name": "Callosciurus prevostii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 3604, + "scientific_name": "Callosciurus pygerythrus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 3605, + "scientific_name": "Callosciurus quinquestriatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 42468, + "scientific_name": "Callospermophilus lateralis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 20485, + "scientific_name": "Callospermophilus madrensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 42562, + "scientific_name": "Callospermophilus saturatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 3609, + "scientific_name": "Calomys boliviae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 3610, + "scientific_name": "Calomys callidus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 3611, + "scientific_name": "Calomys callosus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 3618, + "scientific_name": "Calomyscus bailwardi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 3619, + "scientific_name": "Calomyscus baluchi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136377, + "scientific_name": "Calomyscus elburzensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136252, + "scientific_name": "Calomyscus grandis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 3620, + "scientific_name": "Calomyscus hotsoni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 3621, + "scientific_name": "Calomyscus mystax", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 3622, + "scientific_name": "Calomyscus tsolovi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 3623, + "scientific_name": "Calomyscus urartensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136689, + "scientific_name": "Calomys expulsus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136737, + "scientific_name": "Calomys fecundus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 3612, + "scientific_name": "Calomys hummelincki", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 3613, + "scientific_name": "Calomys laucha", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 3614, + "scientific_name": "Calomys lepidus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 3615, + "scientific_name": "Calomys musculinus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 3616, + "scientific_name": "Calomys sorellus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 3617, + "scientific_name": "Calomys tener", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136679, + "scientific_name": "Calomys tocantinsi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136304, + "scientific_name": "Calomys venustus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 3626, + "scientific_name": "Caloprymnus campestris", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EX" + }, + { + "taxonid": 3650, + "scientific_name": "Caluromys derbianus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 3651, + "scientific_name": "Caluromysiops irrupta", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 3648, + "scientific_name": "Caluromys lanatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 3649, + "scientific_name": "Caluromys philander", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 4703, + "scientific_name": "Calyptophractus retusus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 63543, + "scientific_name": "Camelus ferus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 3753, + "scientific_name": "Canis adustus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 118264161, + "scientific_name": "Canis aureus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 3745, + "scientific_name": "Canis latrans", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 118264888, + "scientific_name": "Canis lupaster", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 3746, + "scientific_name": "Canis lupus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 3755, + "scientific_name": "Canis mesomelas", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 3747, + "scientific_name": "Canis rufus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 3748, + "scientific_name": "Canis simensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 3759, + "scientific_name": "Cannomys badius", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 3760, + "scientific_name": "Cansumys canus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 3778, + "scientific_name": "Caperea marginata", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 3786, + "scientific_name": "Capra aegagrus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 3794, + "scientific_name": "Capra caucasica", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 3795, + "scientific_name": "Capra cylindricornis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 3787, + "scientific_name": "Capra falconeri", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 42397, + "scientific_name": "Capra ibex", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 3796, + "scientific_name": "Capra nubiana", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 3798, + "scientific_name": "Capra pyrenaica", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 42398, + "scientific_name": "Capra sibirica", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 3797, + "scientific_name": "Capra walie", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 42395, + "scientific_name": "Capreolus capreolus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 42396, + "scientific_name": "Capreolus pygargus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 3811, + "scientific_name": "Capricornis crispus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 3815, + "scientific_name": "Capricornis rubidus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 162916735, + "scientific_name": "Capricornis sumatraensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 3810, + "scientific_name": "Capricornis swinhoei", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 3833, + "scientific_name": "Caprolagus hispidus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 3842, + "scientific_name": "Capromys pilorides", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 18306, + "scientific_name": "Caracal aurata", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 3847, + "scientific_name": "Caracal caracal", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 3858, + "scientific_name": "Cardiocranius paradoxus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 3859, + "scientific_name": "Cardioderma cor", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 21492, + "scientific_name": "Carlito syrichta", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 162369855, + "scientific_name": "Carlito syrichta ssp. carbonarius", + "subspecies": "carbonarius", + "rank": "ssp.", + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 162369880, + "scientific_name": "Carlito syrichta ssp. fraterculus", + "subspecies": "fraterculus", + "rank": "ssp.", + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 162369759, + "scientific_name": "Carlito syrichta ssp. syrichta", + "subspecies": "syrichta", + "rank": "ssp.", + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 88110352, + "scientific_name": "Carollia benkeithi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 3903, + "scientific_name": "Carollia brevicauda", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 88110411, + "scientific_name": "Carollia castanea", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136782, + "scientific_name": "Carollia manu", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 88110257, + "scientific_name": "Carollia monohernandezi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 3905, + "scientific_name": "Carollia perspicillata", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136268, + "scientific_name": "Carollia sowelli", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 3906, + "scientific_name": "Carollia subrufa", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 40596, + "scientific_name": "Carpitalpa arendsi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 3917, + "scientific_name": "Carpomys melanurus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 3916, + "scientific_name": "Carpomys phaeurus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 3921, + "scientific_name": "Carterodon sulcidens", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 7799, + "scientific_name": "Caryomys eva", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 7800, + "scientific_name": "Caryomys inez", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 3999, + "scientific_name": "Casinycteris argynnis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 84455300, + "scientific_name": "Casinycteris campomaanensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 4003, + "scientific_name": "Castor canadensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 4007, + "scientific_name": "Castor fiber", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 4015, + "scientific_name": "Catagonus wagneri", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 4037, + "scientific_name": "Catopuma badia", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 4038, + "scientific_name": "Catopuma temminckii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 86257782, + "scientific_name": "Cavia aperea", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 4065, + "scientific_name": "Cavia fulgida", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136520, + "scientific_name": "Cavia intermedia", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 4066, + "scientific_name": "Cavia magna", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 86263590, + "scientific_name": "Cavia patzelti", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 4068, + "scientific_name": "Cavia tschudii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136865, + "scientific_name": "Cebuella niveiventris", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 136926, + "scientific_name": "Cebuella pygmaea", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 4081, + "scientific_name": "Cebus aequatorialis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 39951, + "scientific_name": "Cebus albifrons", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 81237954, + "scientific_name": "Cebus brunneus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 81257277, + "scientific_name": "Cebus capucinus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 43934, + "scientific_name": "Cebus capucinus ssp. capucinus", + "subspecies": "capucinus", + "rank": "ssp.", + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 4087, + "scientific_name": "Cebus capucinus ssp. curtus", + "subspecies": "curtus", + "rank": "ssp.", + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 43941, + "scientific_name": "Cebus castaneus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 4082, + "scientific_name": "Cebus cesarae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 4075, + "scientific_name": "Cebus cuscinus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 81265980, + "scientific_name": "Cebus imitator", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 40019, + "scientific_name": "Cebus kaapori", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 70333164, + "scientific_name": "Cebus leucocephalus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 4084, + "scientific_name": "Cebus malitiosus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 81384371, + "scientific_name": "Cebus olivaceus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 4085, + "scientific_name": "Cebus trinitatis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 70333194, + "scientific_name": "Cebus unicolor", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 39952, + "scientific_name": "Cebus versicolor", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 4076, + "scientific_name": "Cebus yuracus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 136350, + "scientific_name": "Centronycteris centralis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 4112, + "scientific_name": "Centronycteris maximiliani", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 4133, + "scientific_name": "Centurio senex", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 21488, + "scientific_name": "Cephalopachus bancanus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 39762, + "scientific_name": "Cephalopachus bancanus ssp. bancanus", + "subspecies": "bancanus", + "rank": "ssp.", + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 39763, + "scientific_name": "Cephalopachus bancanus ssp. borneanus", + "subspecies": "borneanus", + "rank": "ssp.", + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 39764, + "scientific_name": "Cephalopachus bancanus ssp. natunensis", + "subspecies": "natunensis", + "rank": "ssp.", + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 39765, + "scientific_name": "Cephalopachus bancanus ssp. saltator", + "subspecies": "saltator", + "rank": "ssp.", + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 4137, + "scientific_name": "Cephalophus adersi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 4138, + "scientific_name": "Cephalophus callipygus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 4139, + "scientific_name": "Cephalophus dorsalis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 4154, + "scientific_name": "Cephalophus harveyi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 4140, + "scientific_name": "Cephalophus jentinki", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 4141, + "scientific_name": "Cephalophus leucogaster", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 4144, + "scientific_name": "Cephalophus natalensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 4145, + "scientific_name": "Cephalophus niger", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 4146, + "scientific_name": "Cephalophus nigrifrons", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 4147, + "scientific_name": "Cephalophus nigrifrons ssp. rubidus", + "subspecies": "rubidus", + "rank": "ssp.", + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 4148, + "scientific_name": "Cephalophus ogilbyi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136902, + "scientific_name": "Cephalophus ogilbyi ssp. brookei", + "subspecies": "brookei", + "rank": "ssp.", + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 4155, + "scientific_name": "Cephalophus ogilbyi ssp. crusalbum", + "subspecies": "crusalbum", + "rank": "ssp.", + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 136878, + "scientific_name": "Cephalophus ogilbyi ssp. ogilbyi", + "subspecies": "ogilbyi", + "rank": "ssp.", + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 4149, + "scientific_name": "Cephalophus rufilatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 4150, + "scientific_name": "Cephalophus silvicultor", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 4151, + "scientific_name": "Cephalophus spadix", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 4152, + "scientific_name": "Cephalophus weynsi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 4153, + "scientific_name": "Cephalophus zebra", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 4159, + "scientific_name": "Cephalorhynchus commersonii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 4160, + "scientific_name": "Cephalorhynchus eutropia", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 4161, + "scientific_name": "Cephalorhynchus heavisidii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 4162, + "scientific_name": "Cephalorhynchus hectori", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 39427, + "scientific_name": "Cephalorhynchus hectori ssp. maui", + "subspecies": "maui", + "rank": "ssp.", + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 4185, + "scientific_name": "Ceratotherium simum", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 4183, + "scientific_name": "Ceratotherium simum ssp. cottoni", + "subspecies": "cottoni", + "rank": "ssp.", + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 39317, + "scientific_name": "Ceratotherium simum ssp. simum", + "subspecies": "simum", + "rank": "ssp.", + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 4192, + "scientific_name": "Cercartetus caudatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 40576, + "scientific_name": "Cercartetus concinnus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 40577, + "scientific_name": "Cercartetus lepidus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 40578, + "scientific_name": "Cercartetus nanus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136615, + "scientific_name": "Cercocebus agilis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136933, + "scientific_name": "Cercocebus atys", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 4207, + "scientific_name": "Cercocebus chrysogaster", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 4200, + "scientific_name": "Cercocebus galeritus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 4206, + "scientific_name": "Cercocebus lunulatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 4203, + "scientific_name": "Cercocebus sanjei", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 4201, + "scientific_name": "Cercocebus torquatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 4212, + "scientific_name": "Cercopithecus ascanius", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136886, + "scientific_name": "Cercopithecus ascanius ssp. ascanius", + "subspecies": "ascanius", + "rank": "ssp.", + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136869, + "scientific_name": "Cercopithecus ascanius ssp. atrinasus", + "subspecies": "atrinasus", + "rank": "ssp.", + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 136868, + "scientific_name": "Cercopithecus ascanius ssp. katangae", + "subspecies": "katangae", + "rank": "ssp.", + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136917, + "scientific_name": "Cercopithecus ascanius ssp. schmidti", + "subspecies": "schmidti", + "rank": "ssp.", + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136947, + "scientific_name": "Cercopithecus ascanius ssp. whitesidei", + "subspecies": "whitesidei", + "rank": "ssp.", + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136930, + "scientific_name": "Cercopithecus campbelli", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 4214, + "scientific_name": "Cercopithecus cephus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136915, + "scientific_name": "Cercopithecus cephus ssp. cephodes", + "subspecies": "cephodes", + "rank": "ssp.", + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 136882, + "scientific_name": "Cercopithecus cephus ssp. cephus", + "subspecies": "cephus", + "rank": "ssp.", + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 40011, + "scientific_name": "Cercopithecus cephus ssp. ngottoensis", + "subspecies": "ngottoensis", + "rank": "ssp.", + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 136885, + "scientific_name": "Cercopithecus denti", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 4245, + "scientific_name": "Cercopithecus diana", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 4217, + "scientific_name": "Cercopithecus erythrogaster", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 40003, + "scientific_name": "Cercopithecus erythrogaster ssp. erythrogaster", + "subspecies": "erythrogaster", + "rank": "ssp.", + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 40004, + "scientific_name": "Cercopithecus erythrogaster ssp. pococki", + "subspecies": "pococki", + "rank": "ssp.", + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 4218, + "scientific_name": "Cercopithecus erythrotis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 136851, + "scientific_name": "Cercopithecus erythrotis ssp. camerunensis", + "subspecies": "camerunensis", + "rank": "ssp.", + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 4235, + "scientific_name": "Cercopithecus erythrotis ssp. erythrotis", + "subspecies": "erythrotis", + "rank": "ssp.", + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 4219, + "scientific_name": "Cercopithecus hamlyni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 92401376, + "scientific_name": "Cercopithecus lomamiensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 136931, + "scientific_name": "Cercopithecus lowei", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 4221, + "scientific_name": "Cercopithecus mitis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 40645, + "scientific_name": "Cercopithecus mitis ssp. albogularis", + "subspecies": "albogularis", + "rank": "ssp.", + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 39990, + "scientific_name": "Cercopithecus mitis ssp. albotorquatus", + "subspecies": "albotorquatus", + "rank": "ssp.", + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136901, + "scientific_name": "Cercopithecus mitis ssp. boutourlinii", + "subspecies": "boutourlinii", + "rank": "ssp.", + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 136861, + "scientific_name": "Cercopithecus mitis ssp. doggetti", + "subspecies": "doggetti", + "rank": "ssp.", + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136887, + "scientific_name": "Cercopithecus mitis ssp. erythrarchus", + "subspecies": "erythrarchus", + "rank": "ssp.", + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136877, + "scientific_name": "Cercopithecus mitis ssp. heymansi", + "subspecies": "heymansi", + "rank": "ssp.", + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 4236, + "scientific_name": "Cercopithecus mitis ssp. kandti", + "subspecies": "kandti", + "rank": "ssp.", + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 136927, + "scientific_name": "Cercopithecus mitis ssp. kolbi", + "subspecies": "kolbi", + "rank": "ssp.", + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41245, + "scientific_name": "Cercopithecus mitis ssp. labiatus", + "subspecies": "labiatus", + "rank": "ssp.", + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 95596261, + "scientific_name": "Cercopithecus mitis ssp. manyaraensis", + "subspecies": "manyaraensis", + "rank": "ssp.", + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 136943, + "scientific_name": "Cercopithecus mitis ssp. mitis", + "subspecies": "mitis", + "rank": "ssp.", + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 96189741, + "scientific_name": "Cercopithecus mitis ssp. moloneyi", + "subspecies": "moloneyi", + "rank": "ssp.", + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136942, + "scientific_name": "Cercopithecus mitis ssp. monoides", + "subspecies": "monoides", + "rank": "ssp.", + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136850, + "scientific_name": "Cercopithecus mitis ssp. opisthostictus", + "subspecies": "opisthostictus", + "rank": "ssp.", + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 96183584, + "scientific_name": "Cercopithecus mitis ssp. stuhlmanni", + "subspecies": "stuhlmanni", + "rank": "ssp.", + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136925, + "scientific_name": "Cercopithecus mitis ssp. zammaranoi", + "subspecies": "zammaranoi", + "rank": "ssp.", + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 4222, + "scientific_name": "Cercopithecus mona", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 4223, + "scientific_name": "Cercopithecus neglectus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 4224, + "scientific_name": "Cercopithecus nictitans", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 92488656, + "scientific_name": "Cercopithecus nictitans ssp. insolitus", + "subspecies": "insolitus", + "rank": "ssp.", + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 92488692, + "scientific_name": "Cercopithecus nictitans ssp. ludio", + "subspecies": "ludio", + "rank": "ssp.", + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 92489579, + "scientific_name": "Cercopithecus nictitans ssp. martini", + "subspecies": "martini", + "rank": "ssp.", + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 136922, + "scientific_name": "Cercopithecus nictitans ssp. nictitans", + "subspecies": "nictitans", + "rank": "ssp.", + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 41208, + "scientific_name": "Cercopithecus nictitans ssp. stampflii", + "subspecies": "stampflii", + "rank": "ssp.", + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 4225, + "scientific_name": "Cercopithecus petaurista", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 136910, + "scientific_name": "Cercopithecus petaurista ssp. buettikoferi", + "subspecies": "buettikoferi", + "rank": "ssp.", + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 136905, + "scientific_name": "Cercopithecus petaurista ssp. petaurista", + "subspecies": "petaurista", + "rank": "ssp.", + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 92411527, + "scientific_name": "Cercopithecus pogonias", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 136907, + "scientific_name": "Cercopithecus pogonias ssp. grayi", + "subspecies": "grayi", + "rank": "ssp.", + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 136940, + "scientific_name": "Cercopithecus pogonias ssp. nigripes", + "subspecies": "nigripes", + "rank": "ssp.", + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 4239, + "scientific_name": "Cercopithecus pogonias ssp. pogonias", + "subspecies": "pogonias", + "rank": "ssp.", + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 4232, + "scientific_name": "Cercopithecus roloway", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 4229, + "scientific_name": "Cercopithecus sclateri", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 92466239, + "scientific_name": "Cercopithecus wolfi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 136938, + "scientific_name": "Cercopithecus wolfi ssp. elegans", + "subspecies": "elegans", + "rank": "ssp.", + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 136921, + "scientific_name": "Cercopithecus wolfi ssp. pyrogaster", + "subspecies": "pyrogaster", + "rank": "ssp.", + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 40646, + "scientific_name": "Cercopithecus wolfi ssp. wolfi", + "subspecies": "wolfi", + "rank": "ssp.", + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 4248, + "scientific_name": "Cerdocyon thous", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136276, + "scientific_name": "Cerradomys maracajuensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136511, + "scientific_name": "Cerradomys marinhus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 114955348, + "scientific_name": "Cerradomys scotti", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 15614, + "scientific_name": "Cerradomys subflavus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 4256, + "scientific_name": "Cervus albirostris", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 55997823, + "scientific_name": "Cervus canadensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 55997072, + "scientific_name": "Cervus elaphus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 4261, + "scientific_name": "Cervus hanglu", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 113259123, + "scientific_name": "Cervus hanglu ssp. hanglu", + "subspecies": "hanglu", + "rank": "ssp.", + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 41788, + "scientific_name": "Cervus nippon", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136547, + "scientific_name": "Chacodelphys formosa", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 4305, + "scientific_name": "Chaerephon aloysiisabaudiae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 4306, + "scientific_name": "Chaerephon ansorgei", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 67360705, + "scientific_name": "Chaerephon atsinanana", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 4307, + "scientific_name": "Chaerephon bemmeleni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 4308, + "scientific_name": "Chaerephon bivittatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 4309, + "scientific_name": "Chaerephon bregullae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 4310, + "scientific_name": "Chaerephon chapini", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 4311, + "scientific_name": "Chaerephon gallagheri", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 4312, + "scientific_name": "Chaerephon jobensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136393, + "scientific_name": "Chaerephon jobimena", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 4313, + "scientific_name": "Chaerephon johorensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 4314, + "scientific_name": "Chaerephon major", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 4315, + "scientific_name": "Chaerephon nigeriae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 4316, + "scientific_name": "Chaerephon plicatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 67362271, + "scientific_name": "Chaerephon pumilus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 4319, + "scientific_name": "Chaerephon russatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 4320, + "scientific_name": "Chaerephon solomonis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 4321, + "scientific_name": "Chaerephon tomensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 4322, + "scientific_name": "Chaeropus ecaudatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EX" + }, + { + "taxonid": 6860, + "scientific_name": "Chaetocauda sichuanensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 96812094, + "scientific_name": "Chaetodipus ammophilus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 136421, + "scientific_name": "Chaetodipus ammophilus ssp. dalquesti", + "subspecies": "dalquesti", + "rank": "ssp.", + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 92459293, + "scientific_name": "Chaetodipus arenarius", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 4327, + "scientific_name": "Chaetodipus artus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 4328, + "scientific_name": "Chaetodipus baileyi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 4329, + "scientific_name": "Chaetodipus californicus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136606, + "scientific_name": "Chaetodipus eremicus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 4330, + "scientific_name": "Chaetodipus fallax", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 4331, + "scientific_name": "Chaetodipus formosus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 4332, + "scientific_name": "Chaetodipus goldmani", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 4333, + "scientific_name": "Chaetodipus hispidus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 4334, + "scientific_name": "Chaetodipus intermedius", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136361, + "scientific_name": "Chaetodipus lineatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 4335, + "scientific_name": "Chaetodipus nelsoni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 4336, + "scientific_name": "Chaetodipus penicillatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 4337, + "scientific_name": "Chaetodipus pernix", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136837, + "scientific_name": "Chaetodipus rudinoris", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 92461708, + "scientific_name": "Chaetodipus siccus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 4338, + "scientific_name": "Chaetodipus spinatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 4366, + "scientific_name": "Chaetomys subspinosus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 89604632, + "scientific_name": "Chaetophractus vellerosus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 4369, + "scientific_name": "Chaetophractus villosus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 4414, + "scientific_name": "Chalinolobus dwyeri", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 4417, + "scientific_name": "Chalinolobus gouldii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 4419, + "scientific_name": "Chalinolobus morio", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 4420, + "scientific_name": "Chalinolobus neocaledonicus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 4421, + "scientific_name": "Chalinolobus nigrogriseus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 4422, + "scientific_name": "Chalinolobus picatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 4425, + "scientific_name": "Chalinolobus tuberculatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 163021607, + "scientific_name": "Cheirogaleus andysabini", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 163021377, + "scientific_name": "Cheirogaleus crossleyi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 163021927, + "scientific_name": "Cheirogaleus grovesi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 163022131, + "scientific_name": "Cheirogaleus lavasoensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 54778911, + "scientific_name": "Cheirogaleus major", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 163023599, + "scientific_name": "Cheirogaleus medius", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 163020756, + "scientific_name": "Cheirogaleus shethi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 41576, + "scientific_name": "Cheirogaleus sibreei", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 163022885, + "scientific_name": "Cheirogaleus thomasi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 4600, + "scientific_name": "Cheiromeles parvidens", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 4601, + "scientific_name": "Cheiromeles torquatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 4603, + "scientific_name": "Chelemys macronyx", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 4604, + "scientific_name": "Chelemys megalonyx", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 41564, + "scientific_name": "Cheracebus lucifer", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41563, + "scientific_name": "Cheracebus lugens", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 39944, + "scientific_name": "Cheracebus medemi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 41566, + "scientific_name": "Cheracebus regulus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 210891841, + "scientific_name": "Cheracebus torquatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136775, + "scientific_name": "Chibchanomys orcesi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 4627, + "scientific_name": "Chibchanomys trichotis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 4634, + "scientific_name": "Chilomys instans", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 88088745, + "scientific_name": "Chilonatalus macer", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 88088852, + "scientific_name": "Chilonatalus micropus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 14361, + "scientific_name": "Chilonatalus tumidifrons", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 4647, + "scientific_name": "Chimarrogale hantu", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 40614, + "scientific_name": "Chimarrogale himalayica", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 4648, + "scientific_name": "Chimarrogale phaeura", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 40615, + "scientific_name": "Chimarrogale platycephalus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 40616, + "scientific_name": "Chimarrogale styani", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 4649, + "scientific_name": "Chimarrogale sumatrana", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 4651, + "scientific_name": "Chinchilla chinchilla", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 4652, + "scientific_name": "Chinchilla lanigera", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 4655, + "scientific_name": "Chinchillula sahamae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 4658, + "scientific_name": "Chionomys gud", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 4659, + "scientific_name": "Chionomys nivalis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 4660, + "scientific_name": "Chionomys roberti", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 4664, + "scientific_name": "Chiroderma doriae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 4665, + "scientific_name": "Chiroderma improvisum", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 4666, + "scientific_name": "Chiroderma salvini", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 4667, + "scientific_name": "Chiroderma trinitatum", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 4668, + "scientific_name": "Chiroderma villosum", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 88108434, + "scientific_name": "Chiroderma vizottoi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 4669, + "scientific_name": "Chiromyscus chiropus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 4670, + "scientific_name": "Chironax melanocephalus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 4671, + "scientific_name": "Chironectes minimus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 4679, + "scientific_name": "Chiropodomys calamianensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 4680, + "scientific_name": "Chiropodomys gliroides", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 4681, + "scientific_name": "Chiropodomys karlkoopmani", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 4682, + "scientific_name": "Chiropodomys major", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 4683, + "scientific_name": "Chiropodomys muroides", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 4684, + "scientific_name": "Chiropodomys pusillus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 4685, + "scientific_name": "Chiropotes albinasus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 43891, + "scientific_name": "Chiropotes chiropotes", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 70330167, + "scientific_name": "Chiropotes sagulatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 39956, + "scientific_name": "Chiropotes satanas", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 43892, + "scientific_name": "Chiropotes utahickae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 4691, + "scientific_name": "Chiruromys forbesi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 4692, + "scientific_name": "Chiruromys lamia", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 4693, + "scientific_name": "Chiruromys vates", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 4704, + "scientific_name": "Chlamyphorus truncatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 4233, + "scientific_name": "Chlorocebus aethiops", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 215123466, + "scientific_name": "Chlorocebus aethiops ssp. aethiops", + "subspecies": "aethiops", + "rank": "ssp.", + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 215119320, + "scientific_name": "Chlorocebus aethiops ssp. matschiei", + "subspecies": "matschiei", + "rank": "ssp.", + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 136291, + "scientific_name": "Chlorocebus cynosuros", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 4240, + "scientific_name": "Chlorocebus djamdjamensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 205910256, + "scientific_name": "Chlorocebus djamdjamensis ssp. djamdjamensis", + "subspecies": "djamdjamensis", + "rank": "ssp.", + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 205910110, + "scientific_name": "Chlorocebus djamdjamensis ssp. harennaensis", + "subspecies": "harennaensis", + "rank": "ssp.", + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 4216, + "scientific_name": "Chlorocebus dryas", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 136271, + "scientific_name": "Chlorocebus pygerythrus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 92252725, + "scientific_name": "Chlorocebus pygerythrus ssp. hilgerti", + "subspecies": "hilgerti", + "rank": "ssp.", + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 92252747, + "scientific_name": "Chlorocebus pygerythrus ssp. nesiotes", + "subspecies": "nesiotes", + "rank": "ssp.", + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 92252770, + "scientific_name": "Chlorocebus pygerythrus ssp. pygerythrus", + "subspecies": "pygerythrus", + "rank": "ssp.", + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 92252798, + "scientific_name": "Chlorocebus pygerythrus ssp. rufoviridis", + "subspecies": "rufoviridis", + "rank": "ssp.", + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 205895889, + "scientific_name": "Chlorocebus pygerythrus ssp. zavattarii", + "subspecies": "zavattarii", + "rank": "ssp.", + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 136265, + "scientific_name": "Chlorocebus sabaeus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136208, + "scientific_name": "Chlorocebus tantalus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 92252537, + "scientific_name": "Chlorocebus tantalus ssp. budgetti", + "subspecies": "budgetti", + "rank": "ssp.", + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 92252560, + "scientific_name": "Chlorocebus tantalus ssp. marrensis", + "subspecies": "marrensis", + "rank": "ssp.", + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 92252582, + "scientific_name": "Chlorocebus tantalus ssp. tantalus", + "subspecies": "tantalus", + "rank": "ssp.", + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 4768, + "scientific_name": "Chlorotalpa duthieae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 4766, + "scientific_name": "Chlorotalpa sclateri", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136259, + "scientific_name": "Chodsigoa caovansunga", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 41430, + "scientific_name": "Chodsigoa hypsibia", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41431, + "scientific_name": "Chodsigoa lamula", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41435, + "scientific_name": "Chodsigoa parca", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136298, + "scientific_name": "Chodsigoa parva", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 20416, + "scientific_name": "Chodsigoa salenskii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 41436, + "scientific_name": "Chodsigoa smithii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 136760, + "scientific_name": "Chodsigoa sodalis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 4772, + "scientific_name": "Choeroniscus godmani", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 4774, + "scientific_name": "Choeroniscus minor", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 4775, + "scientific_name": "Choeroniscus periosus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 4776, + "scientific_name": "Choeronycteris mexicana", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 10032, + "scientific_name": "Choeropsis liberiensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 4777, + "scientific_name": "Choloepus didactylus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 4778, + "scientific_name": "Choloepus hoffmanni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 4806, + "scientific_name": "Chrotogale owstoni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 4807, + "scientific_name": "Chrotomys gonzalesi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 4808, + "scientific_name": "Chrotomys mindorensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136388, + "scientific_name": "Chrotomys sibuyanensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 4092, + "scientific_name": "Chrotomys silaceus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 4810, + "scientific_name": "Chrotomys whiteheadi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 4811, + "scientific_name": "Chrotopterus auritus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 40600, + "scientific_name": "Chrysochloris asiatica", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 40601, + "scientific_name": "Chrysochloris stuhlmanni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 4812, + "scientific_name": "Chrysochloris visagiei", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 4819, + "scientific_name": "Chrysocyon brachyurus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 4828, + "scientific_name": "Chrysospalax trevelyani", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 4829, + "scientific_name": "Chrysospalax villosus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 44787, + "scientific_name": "Cistugo lesueuri", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 44788, + "scientific_name": "Cistugo seabrae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41695, + "scientific_name": "Civettictis civetta", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 4983, + "scientific_name": "Cloeotis percivali", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 4989, + "scientific_name": "Clyomys laticeps", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 112391705, + "scientific_name": "Coccymys kirrhos", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 112464492, + "scientific_name": "Coccymys ruemmleri", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 112393452, + "scientific_name": "Coccymys shawmayeri", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 5074, + "scientific_name": "Coelops frithii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 5076, + "scientific_name": "Coelops robinsoni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 5075, + "scientific_name": "Coelops robinsoni ssp. hirsutus", + "subspecies": "hirsutus", + "rank": "ssp.", + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 87411473, + "scientific_name": "Coendou baturitensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 5083, + "scientific_name": "Coendou bicolor", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136597, + "scientific_name": "Coendou ichillus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 20631, + "scientific_name": "Coendou insidiosus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136738, + "scientific_name": "Coendou melanurus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 20629, + "scientific_name": "Coendou mexicanus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 5084, + "scientific_name": "Coendou nycthemera", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 101228458, + "scientific_name": "Coendou prehensilis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136485, + "scientific_name": "Coendou pruinosus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136702, + "scientific_name": "Coendou quichua", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 136518, + "scientific_name": "Coendou roosmalenorum", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 7010, + "scientific_name": "Coendou rufescens", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 46205559, + "scientific_name": "Coendou speratus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 20630, + "scientific_name": "Coendou spinosus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 20633, + "scientific_name": "Coendou vestitus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 5113, + "scientific_name": "Coleura afra", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 80221085, + "scientific_name": "Coleura kibomalandy", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 5112, + "scientific_name": "Coleura seychellensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 5142, + "scientific_name": "Colobus angolensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 136929, + "scientific_name": "Colobus angolensis ssp. angolensis", + "subspecies": "angolensis", + "rank": "ssp.", + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 136935, + "scientific_name": "Colobus angolensis ssp. cordieri", + "subspecies": "cordieri", + "rank": "ssp.", + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 136923, + "scientific_name": "Colobus angolensis ssp. cottoni", + "subspecies": "cottoni", + "rank": "ssp.", + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 40041, + "scientific_name": "Colobus angolensis ssp. nov.", + "subspecies": "nov.", + "rank": "ssp.", + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 5148, + "scientific_name": "Colobus angolensis ssp. palliatus", + "subspecies": "palliatus", + "rank": "ssp.", + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 5149, + "scientific_name": "Colobus angolensis ssp. prigoginei", + "subspecies": "prigoginei", + "rank": "ssp.", + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 5147, + "scientific_name": "Colobus angolensis ssp. ruwenzorii", + "subspecies": "ruwenzorii", + "rank": "ssp.", + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 92576098, + "scientific_name": "Colobus angolensis ssp. sharpei", + "subspecies": "sharpei", + "rank": "ssp.", + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 136858, + "scientific_name": "Colobus caudatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 5143, + "scientific_name": "Colobus guereza", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136880, + "scientific_name": "Colobus guereza ssp. dodingae", + "subspecies": "dodingae", + "rank": "ssp.", + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 5150, + "scientific_name": "Colobus guereza ssp. gallarum", + "subspecies": "gallarum", + "rank": "ssp.", + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 136896, + "scientific_name": "Colobus guereza ssp. guereza", + "subspecies": "guereza", + "rank": "ssp.", + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136883, + "scientific_name": "Colobus guereza ssp. kikuyuensis", + "subspecies": "kikuyuensis", + "rank": "ssp.", + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136846, + "scientific_name": "Colobus guereza ssp. matschiei", + "subspecies": "matschiei", + "rank": "ssp.", + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136891, + "scientific_name": "Colobus guereza ssp. occidentalis", + "subspecies": "occidentalis", + "rank": "ssp.", + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 40007, + "scientific_name": "Colobus guereza ssp. percivali", + "subspecies": "percivali", + "rank": "ssp.", + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 5144, + "scientific_name": "Colobus polykomos", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 5145, + "scientific_name": "Colobus satanas", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 40012, + "scientific_name": "Colobus satanas ssp. anthracinus", + "subspecies": "anthracinus", + "rank": "ssp.", + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 40008, + "scientific_name": "Colobus satanas ssp. satanas", + "subspecies": "satanas", + "rank": "ssp.", + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 5146, + "scientific_name": "Colobus vellerosus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 5151, + "scientific_name": "Colomys goslingi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41458, + "scientific_name": "Condylura cristata", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41630, + "scientific_name": "Conepatus chinga", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41631, + "scientific_name": "Conepatus humboldtii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41632, + "scientific_name": "Conepatus leuconotus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41633, + "scientific_name": "Conepatus semistriatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136343, + "scientific_name": "Congosorex phillipsorum", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 5221, + "scientific_name": "Congosorex polli", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 44935, + "scientific_name": "Congosorex verheyeni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 5223, + "scientific_name": "Conilurus albipes", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EX" + }, + { + "taxonid": 75927841, + "scientific_name": "Conilurus capricornensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EX" + }, + { + "taxonid": 5224, + "scientific_name": "Conilurus penicillatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 5228, + "scientific_name": "Connochaetes gnou", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 5229, + "scientific_name": "Connochaetes taurinus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41527, + "scientific_name": "Cormura brevirostris", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 17599, + "scientific_name": "Corynorhinus mexicanus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 17600, + "scientific_name": "Corynorhinus rafinesquii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 17598, + "scientific_name": "Corynorhinus townsendii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 5414, + "scientific_name": "Coryphomys buehleri", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EX" + }, + { + "taxonid": 5481, + "scientific_name": "Craseonycteris thonglongyai", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 5499, + "scientific_name": "Crateromys australis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 5502, + "scientific_name": "Crateromys heaneyi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 5501, + "scientific_name": "Crateromys paulus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 5500, + "scientific_name": "Crateromys schadenbergi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 16025, + "scientific_name": "Cratogeomys castanops", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136215, + "scientific_name": "Cratogeomys fulvescens", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 16026, + "scientific_name": "Cratogeomys fumosus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136647, + "scientific_name": "Cratogeomys goldmani", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 16028, + "scientific_name": "Cratogeomys merriami", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136396, + "scientific_name": "Cratogeomys perotensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136249, + "scientific_name": "Cratogeomys planiceps", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 5513, + "scientific_name": "Cremnomys cutchicus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 5514, + "scientific_name": "Cremnomys elvira", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 112256552, + "scientific_name": "Cricetomys ansorgei", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 5521, + "scientific_name": "Cricetomys emini", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 112169507, + "scientific_name": "Cricetomys gambianus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 5523, + "scientific_name": "Cricetulus alticola", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 5524, + "scientific_name": "Cricetulus barabensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 5525, + "scientific_name": "Cricetulus kamensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136746, + "scientific_name": "Cricetulus lama", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 5526, + "scientific_name": "Cricetulus longicaudatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 5528, + "scientific_name": "Cricetulus migratorius", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 5527, + "scientific_name": "Cricetulus sokolovi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136527, + "scientific_name": "Cricetulus tibetanus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 5529, + "scientific_name": "Cricetus cricetus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 111747853, + "scientific_name": "Crocidura absconditus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 112517405, + "scientific_name": "Crocidura afeworkbekelei", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 40617, + "scientific_name": "Crocidura aleksandrisi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 5620, + "scientific_name": "Crocidura allex", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 5595, + "scientific_name": "Crocidura andamanensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 48296764, + "scientific_name": "Crocidura annamitensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 5558, + "scientific_name": "Crocidura ansellorum", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 40618, + "scientific_name": "Crocidura arabica", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136270, + "scientific_name": "Crocidura arispa", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 5596, + "scientific_name": "Crocidura armenica", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 48296412, + "scientific_name": "Crocidura attenuata", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 5621, + "scientific_name": "Crocidura attila", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 5559, + "scientific_name": "Crocidura baileyi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136726, + "scientific_name": "Crocidura baluensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 48297939, + "scientific_name": "Crocidura batakorum", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 40620, + "scientific_name": "Crocidura batesi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 5597, + "scientific_name": "Crocidura beatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 5598, + "scientific_name": "Crocidura beccarii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 40621, + "scientific_name": "Crocidura bottegi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 5622, + "scientific_name": "Crocidura bottegoides", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 136742, + "scientific_name": "Crocidura brunnea", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 40622, + "scientific_name": "Crocidura buettikoferi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 5623, + "scientific_name": "Crocidura caliginea", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 5560, + "scientific_name": "Crocidura canariensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 136444, + "scientific_name": "Crocidura caspica", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 41317, + "scientific_name": "Crocidura cinderella", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 5561, + "scientific_name": "Crocidura congobelgica", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 48296479, + "scientific_name": "Crocidura cranbrooki", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 5562, + "scientific_name": "Crocidura crenata", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 40624, + "scientific_name": "Crocidura crossei", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 40625, + "scientific_name": "Crocidura cyanea", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 40626, + "scientific_name": "Crocidura denti", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 5624, + "scientific_name": "Crocidura desperata", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 5599, + "scientific_name": "Crocidura dhofarensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 40628, + "scientific_name": "Crocidura dolichura", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 40629, + "scientific_name": "Crocidura douceti", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 40627, + "scientific_name": "Crocidura dsinezumi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 111764928, + "scientific_name": "Crocidura eburnea", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 5565, + "scientific_name": "Crocidura eisentrauti", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 5625, + "scientific_name": "Crocidura elgonius", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 40630, + "scientific_name": "Crocidura elongata", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 5626, + "scientific_name": "Crocidura erica", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 111739377, + "scientific_name": "Crocidura fingui", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 5627, + "scientific_name": "Crocidura fischeri", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 5628, + "scientific_name": "Crocidura flavescens", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 5600, + "scientific_name": "Crocidura floweri", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 136578, + "scientific_name": "Crocidura foetida", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 5629, + "scientific_name": "Crocidura foxi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 40631, + "scientific_name": "Crocidura fuliginosa", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 40632, + "scientific_name": "Crocidura fulvastra", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 5630, + "scientific_name": "Crocidura fumosa", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 40633, + "scientific_name": "Crocidura fuscomurina", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 111765432, + "scientific_name": "Crocidura gathornei", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 5566, + "scientific_name": "Crocidura glassi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 41319, + "scientific_name": "Crocidura gmelini", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 112518189, + "scientific_name": "Crocidura goliath", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 5631, + "scientific_name": "Crocidura gracilipes", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 41321, + "scientific_name": "Crocidura grandiceps", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 5601, + "scientific_name": "Crocidura grandis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 5567, + "scientific_name": "Crocidura grassei", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 5602, + "scientific_name": "Crocidura grayi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 5632, + "scientific_name": "Crocidura greenwoodi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 48296877, + "scientific_name": "Crocidura guy", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 5633, + "scientific_name": "Crocidura harenna", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 136596, + "scientific_name": "Crocidura hikmiya", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 41322, + "scientific_name": "Crocidura hildegardeae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136226, + "scientific_name": "Crocidura hilliana", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 41323, + "scientific_name": "Crocidura hirta", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 5603, + "scientific_name": "Crocidura hispida", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 41324, + "scientific_name": "Crocidura horsfieldii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 136781, + "scientific_name": "Crocidura hutanis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136733, + "scientific_name": "Crocidura indochinensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41325, + "scientific_name": "Crocidura jacksoni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 5604, + "scientific_name": "Crocidura jenkinsi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 136327, + "scientific_name": "Crocidura jouvenetae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136634, + "scientific_name": "Crocidura katinka", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 5568, + "scientific_name": "Crocidura kivuana", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 41326, + "scientific_name": "Crocidura lamottei", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 5569, + "scientific_name": "Crocidura lanosa", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 41327, + "scientific_name": "Crocidura lasiura", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 5570, + "scientific_name": "Crocidura latona", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41328, + "scientific_name": "Crocidura lea", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136792, + "scientific_name": "Crocidura lepidura", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 29651, + "scientific_name": "Crocidura leucodon", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41329, + "scientific_name": "Crocidura levicula", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41330, + "scientific_name": "Crocidura littoralis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 5571, + "scientific_name": "Crocidura longipes", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 5572, + "scientific_name": "Crocidura lucina", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 5589, + "scientific_name": "Crocidura ludia", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41331, + "scientific_name": "Crocidura luna", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41332, + "scientific_name": "Crocidura lusitania", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 112139630, + "scientific_name": "Crocidura lwiroensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 41333, + "scientific_name": "Crocidura macarthuri", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 5634, + "scientific_name": "Crocidura macmillani", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 5635, + "scientific_name": "Crocidura macowi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 5605, + "scientific_name": "Crocidura malayana", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 5575, + "scientific_name": "Crocidura manengubae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 5576, + "scientific_name": "Crocidura maquassiensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41334, + "scientific_name": "Crocidura mariquensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41335, + "scientific_name": "Crocidura maurisca", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41336, + "scientific_name": "Crocidura maxi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 112503346, + "scientific_name": "Crocidura mdumai", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 48269124, + "scientific_name": "Crocidura mindorus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 5608, + "scientific_name": "Crocidura miya", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 111767309, + "scientific_name": "Crocidura monax", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 41337, + "scientific_name": "Crocidura monticola", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41338, + "scientific_name": "Crocidura montis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 112503202, + "scientific_name": "Crocidura munissii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41339, + "scientific_name": "Crocidura muricauda", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136711, + "scientific_name": "Crocidura musseri", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 41340, + "scientific_name": "Crocidura mutesae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 41341, + "scientific_name": "Crocidura nana", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 41342, + "scientific_name": "Crocidura nanilla", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136786, + "scientific_name": "Crocidura negligens", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 5609, + "scientific_name": "Crocidura negrina", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 112503255, + "scientific_name": "Crocidura newmarki", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 5610, + "scientific_name": "Crocidura nicobarica", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 41344, + "scientific_name": "Crocidura nigeriae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41345, + "scientific_name": "Crocidura nigricans", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41447, + "scientific_name": "Crocidura nigripes", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41346, + "scientific_name": "Crocidura nigrofusca", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 5578, + "scientific_name": "Crocidura nimbae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 112519468, + "scientific_name": "Crocidura nimbasilvanus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 48269338, + "scientific_name": "Crocidura ninoyi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 41347, + "scientific_name": "Crocidura niobe", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 111754705, + "scientific_name": "Crocidura obscurior", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41348, + "scientific_name": "Crocidura olivieri", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 5611, + "scientific_name": "Crocidura orientalis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 5590, + "scientific_name": "Crocidura orii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 40623, + "scientific_name": "Crocidura pachyura", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 5613, + "scientific_name": "Crocidura palawanensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 48297786, + "scientific_name": "Crocidura panayensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 5614, + "scientific_name": "Crocidura paradoxura", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41349, + "scientific_name": "Crocidura parvipes", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41350, + "scientific_name": "Crocidura pasha", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 5615, + "scientific_name": "Crocidura pergrisea", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 5580, + "scientific_name": "Crocidura phaeura", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 45954204, + "scientific_name": "Crocidura phanluongi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 45954289, + "scientific_name": "Crocidura phuquocensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 5636, + "scientific_name": "Crocidura picea", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 5637, + "scientific_name": "Crocidura pitmani", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 41351, + "scientific_name": "Crocidura planiceps", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 41352, + "scientific_name": "Crocidura poensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 5591, + "scientific_name": "Crocidura polia", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 41353, + "scientific_name": "Crocidura pullata", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 5592, + "scientific_name": "Crocidura raineyi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 136722, + "scientific_name": "Crocidura ramona", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136783, + "scientific_name": "Crocidura rapax", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 5616, + "scientific_name": "Crocidura religiosa", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 41354, + "scientific_name": "Crocidura rhoditis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41355, + "scientific_name": "Crocidura roosevelti", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 29652, + "scientific_name": "Crocidura russula", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 112465460, + "scientific_name": "Crocidura sapaensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 5593, + "scientific_name": "Crocidura selina", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 29654, + "scientific_name": "Crocidura serezkyensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 5617, + "scientific_name": "Crocidura shantungensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41356, + "scientific_name": "Crocidura sibirica", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 29655, + "scientific_name": "Crocidura sicula", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41357, + "scientific_name": "Crocidura silacea", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41358, + "scientific_name": "Crocidura smithii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 45954331, + "scientific_name": "Crocidura sokolovi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 41359, + "scientific_name": "Crocidura somalica", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 5582, + "scientific_name": "Crocidura stenocephala", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 29656, + "scientific_name": "Crocidura suaveolens", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 5618, + "scientific_name": "Crocidura susiana", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 136238, + "scientific_name": "Crocidura tanakae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 112513345, + "scientific_name": "Crocidura tansaniana", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 41360, + "scientific_name": "Crocidura tarella", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 41361, + "scientific_name": "Crocidura tarfayensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 5584, + "scientific_name": "Crocidura telfordi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 5619, + "scientific_name": "Crocidura tenuis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 5585, + "scientific_name": "Crocidura thalia", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41362, + "scientific_name": "Crocidura theresae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 5594, + "scientific_name": "Crocidura thomensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 136379, + "scientific_name": "Crocidura trichura", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 41363, + "scientific_name": "Crocidura turba", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 5638, + "scientific_name": "Crocidura ultima", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 112465479, + "scientific_name": "Crocidura umbra", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 112515709, + "scientific_name": "Crocidura usambarae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 41364, + "scientific_name": "Crocidura viaria", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136602, + "scientific_name": "Crocidura virgata", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41365, + "scientific_name": "Crocidura voi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136454, + "scientific_name": "Crocidura vorax", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136447, + "scientific_name": "Crocidura vosmaeri", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 136341, + "scientific_name": "Crocidura watasei", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41366, + "scientific_name": "Crocidura whitakeri", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 5587, + "scientific_name": "Crocidura wimmeri", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 136382, + "scientific_name": "Crocidura wuchihensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 5639, + "scientific_name": "Crocidura xantippe", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 112517512, + "scientific_name": "Crocidura yaldeni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 41367, + "scientific_name": "Crocidura yankariensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 45954341, + "scientific_name": "Crocidura zaitsevi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 41368, + "scientific_name": "Crocidura zaphiri", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 41369, + "scientific_name": "Crocidura zarudnyi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 5640, + "scientific_name": "Crocidura zimmeri", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 5588, + "scientific_name": "Crocidura zimmermanni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 5674, + "scientific_name": "Crocuta crocuta", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41593, + "scientific_name": "Crossarchus alexandri", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41594, + "scientific_name": "Crossarchus ansorgei", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41595, + "scientific_name": "Crossarchus obscurus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41596, + "scientific_name": "Crossarchus platycephalus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 5679, + "scientific_name": "Crossomys moncktoni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 5704, + "scientific_name": "Crunomys celebensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 5705, + "scientific_name": "Crunomys fallax", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 5706, + "scientific_name": "Crunomys melanius", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 29458, + "scientific_name": "Crunomys suncoides", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 5748, + "scientific_name": "Cryptochloris wintoni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 5749, + "scientific_name": "Cryptochloris zyli", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 111932681, + "scientific_name": "Cryptomys hottentotus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 111933779, + "scientific_name": "Cryptomys mahali", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 111935513, + "scientific_name": "Cryptomys natalensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 111935780, + "scientific_name": "Cryptomys pretoriae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136545, + "scientific_name": "Cryptonanus agricolai", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 136845, + "scientific_name": "Cryptonanus chacoensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136705, + "scientific_name": "Cryptonanus guahybae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 41320, + "scientific_name": "Cryptonanus ignitus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EX" + }, + { + "taxonid": 136749, + "scientific_name": "Cryptonanus unduaviensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 5760, + "scientific_name": "Cryptoprocta ferox", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 136456, + "scientific_name": "Cryptoprocta spelea", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EX" + }, + { + "taxonid": 136789, + "scientific_name": "Cryptotis alticola", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 45954361, + "scientific_name": "Cryptotis aroensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 136736, + "scientific_name": "Cryptotis brachyonyx", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 96829126, + "scientific_name": "Cryptotis cavatorculus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 96828906, + "scientific_name": "Cryptotis celaque", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 136795, + "scientific_name": "Cryptotis colombiana", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 114956336, + "scientific_name": "Cryptotis dinirensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 5763, + "scientific_name": "Cryptotis endersi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 136438, + "scientific_name": "Cryptotis equatoris", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41371, + "scientific_name": "Cryptotis goldmani", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 48269679, + "scientific_name": "Cryptotis goodwini", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 5764, + "scientific_name": "Cryptotis gracilis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 48269619, + "scientific_name": "Cryptotis griseoventris", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 5765, + "scientific_name": "Cryptotis hondurensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 48269646, + "scientific_name": "Cryptotis lacertosus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 5766, + "scientific_name": "Cryptotis magna", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 48269568, + "scientific_name": "Cryptotis mam", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 136488, + "scientific_name": "Cryptotis mayensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 96829093, + "scientific_name": "Cryptotis mccarthyi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 136267, + "scientific_name": "Cryptotis medellinia", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 48268560, + "scientific_name": "Cryptotis meridensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 136398, + "scientific_name": "Cryptotis merriami", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136299, + "scientific_name": "Cryptotis merus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 41374, + "scientific_name": "Cryptotis mexicana", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41375, + "scientific_name": "Cryptotis montivaga", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136389, + "scientific_name": "Cryptotis nelsoni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 96829156, + "scientific_name": "Cryptotis niausa", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 41376, + "scientific_name": "Cryptotis nigrescens", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136462, + "scientific_name": "Cryptotis obscura", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 45954370, + "scientific_name": "Cryptotis oreoryctes", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 136838, + "scientific_name": "Cryptotis orophila", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 41377, + "scientific_name": "Cryptotis parva", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136550, + "scientific_name": "Cryptotis peregrina", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 91356351, + "scientific_name": "Cryptotis perijensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 136734, + "scientific_name": "Cryptotis peruviensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 136639, + "scientific_name": "Cryptotis phillipsii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 41378, + "scientific_name": "Cryptotis squamipes", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136780, + "scientific_name": "Cryptotis tamensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41379, + "scientific_name": "Cryptotis thomasi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136757, + "scientific_name": "Cryptotis tropicalis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 48267978, + "scientific_name": "Cryptotis venezuelensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 5792, + "scientific_name": "Ctenodactylus gundi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 5793, + "scientific_name": "Ctenodactylus vali", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 5795, + "scientific_name": "Ctenomys argentinus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 5796, + "scientific_name": "Ctenomys australis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 5797, + "scientific_name": "Ctenomys azarae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 136687, + "scientific_name": "Ctenomys bergi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 5798, + "scientific_name": "Ctenomys boliviensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 5799, + "scientific_name": "Ctenomys bonettoi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 5800, + "scientific_name": "Ctenomys brasiliensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 5801, + "scientific_name": "Ctenomys colburni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 136308, + "scientific_name": "Ctenomys coludo", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 5802, + "scientific_name": "Ctenomys conoveri", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136295, + "scientific_name": "Ctenomys coyhaiquensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 136234, + "scientific_name": "Ctenomys dorbignyi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 5803, + "scientific_name": "Ctenomys dorsalis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 5804, + "scientific_name": "Ctenomys emilianus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136588, + "scientific_name": "Ctenomys famosus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 136464, + "scientific_name": "Ctenomys flamarioni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 136499, + "scientific_name": "Ctenomys fochi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 136601, + "scientific_name": "Ctenomys fodax", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 115553730, + "scientific_name": "Ctenomys frater", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 5806, + "scientific_name": "Ctenomys fulvus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 136607, + "scientific_name": "Ctenomys goodfellowi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 5807, + "scientific_name": "Ctenomys haigi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 45958852, + "scientific_name": "Ctenomys ibicuiensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 136281, + "scientific_name": "Ctenomys johannis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 136556, + "scientific_name": "Ctenomys juris", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 5808, + "scientific_name": "Ctenomys knighti", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 136567, + "scientific_name": "Ctenomys lami", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 5809, + "scientific_name": "Ctenomys latro", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 5810, + "scientific_name": "Ctenomys leucodon", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 5811, + "scientific_name": "Ctenomys lewisi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 5812, + "scientific_name": "Ctenomys magellanicus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 5813, + "scientific_name": "Ctenomys maulinus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 5814, + "scientific_name": "Ctenomys mendocinus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 5815, + "scientific_name": "Ctenomys minutus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 5817, + "scientific_name": "Ctenomys occultus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 5818, + "scientific_name": "Ctenomys opimus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136390, + "scientific_name": "Ctenomys osvaldoreigi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 5819, + "scientific_name": "Ctenomys pearsoni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 5820, + "scientific_name": "Ctenomys perrensi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 5821, + "scientific_name": "Ctenomys peruanus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136289, + "scientific_name": "Ctenomys pilarensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 5822, + "scientific_name": "Ctenomys pontifex", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 5823, + "scientific_name": "Ctenomys porteousi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 136370, + "scientific_name": "Ctenomys pundti", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 136635, + "scientific_name": "Ctenomys rionegrensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 136633, + "scientific_name": "Ctenomys roigi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 5824, + "scientific_name": "Ctenomys saltarius", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 136568, + "scientific_name": "Ctenomys scagliai", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 5825, + "scientific_name": "Ctenomys sericeus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 5826, + "scientific_name": "Ctenomys sociabilis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 5827, + "scientific_name": "Ctenomys steinbachi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 5828, + "scientific_name": "Ctenomys talarum", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 5829, + "scientific_name": "Ctenomys torquatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 5830, + "scientific_name": "Ctenomys tuconax", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 5831, + "scientific_name": "Ctenomys tucumanus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 136826, + "scientific_name": "Ctenomys tulduco", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 5832, + "scientific_name": "Ctenomys validus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 136758, + "scientific_name": "Ctenomys viperinus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 136681, + "scientific_name": "Ctenomys yolandae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 699, + "scientific_name": "Cuniculus paca", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 700, + "scientific_name": "Cuniculus taczanowskii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 5953, + "scientific_name": "Cuon alpinus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 136466, + "scientific_name": "Cuscomys ashaninka", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 136658, + "scientific_name": "Cuscomys oblativa", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 6019, + "scientific_name": "Cyclopes didactylus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 173393, + "scientific_name": "Cyclopes didactylus Northeastern Brazil subpopulation", + "subspecies": null, + "rank": null, + "subpopulation": "Northeastern Brazil subpopulation", + "category": "DD" + }, + { + "taxonid": 41597, + "scientific_name": "Cynictis penicillata", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 6081, + "scientific_name": "Cynocephalus volans", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 6082, + "scientific_name": "Cynogale bennettii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 13637, + "scientific_name": "Cynomops abrasus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 13639, + "scientific_name": "Cynomops greenhalli", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136611, + "scientific_name": "Cynomops mexicanus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 87993512, + "scientific_name": "Cynomops milleri", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 87993365, + "scientific_name": "Cynomops paranus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 13642, + "scientific_name": "Cynomops planirostris", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 42453, + "scientific_name": "Cynomys gunnisoni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 42454, + "scientific_name": "Cynomys leucurus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 6091, + "scientific_name": "Cynomys ludovicianus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 6089, + "scientific_name": "Cynomys mexicanus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 6090, + "scientific_name": "Cynomys parvidens", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 6103, + "scientific_name": "Cynopterus brachyotis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 6104, + "scientific_name": "Cynopterus horsfieldii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136798, + "scientific_name": "Cynopterus luzoniensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136423, + "scientific_name": "Cynopterus minutus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 6105, + "scientific_name": "Cynopterus nusatenggara", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 6106, + "scientific_name": "Cynopterus sphinx", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 6107, + "scientific_name": "Cynopterus titthaecheilus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 6204, + "scientific_name": "Cystophora cristata", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 6206, + "scientific_name": "Cyttarops alecto", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 6219, + "scientific_name": "Dacnomys millardi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 6220, + "scientific_name": "Dactylomys boliviensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 6221, + "scientific_name": "Dactylomys dactylinus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 6222, + "scientific_name": "Dactylomys peruanus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 6225, + "scientific_name": "Dactylonax palpator", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 6223, + "scientific_name": "Dactylopsila megalura", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 6224, + "scientific_name": "Dactylopsila tatei", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 6226, + "scientific_name": "Dactylopsila trivirgata", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 42188, + "scientific_name": "Dama dama", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 6235, + "scientific_name": "Damaliscus lunatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 6241, + "scientific_name": "Damaliscus lunatus ssp. jimela", + "subspecies": "jimela", + "rank": "ssp.", + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 6238, + "scientific_name": "Damaliscus lunatus ssp. korrigum", + "subspecies": "korrigum", + "rank": "ssp.", + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 6240, + "scientific_name": "Damaliscus lunatus ssp. lunatus", + "subspecies": "lunatus", + "rank": "ssp.", + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136860, + "scientific_name": "Damaliscus lunatus ssp. superstes", + "subspecies": "superstes", + "rank": "ssp.", + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 6242, + "scientific_name": "Damaliscus lunatus ssp. tiang", + "subspecies": "tiang", + "rank": "ssp.", + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 6243, + "scientific_name": "Damaliscus lunatus ssp. topi", + "subspecies": "topi", + "rank": "ssp.", + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 30208, + "scientific_name": "Damaliscus pygargus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 30209, + "scientific_name": "Damaliscus pygargus ssp. phillipsi", + "subspecies": "phillipsi", + "rank": "ssp.", + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 6236, + "scientific_name": "Damaliscus pygargus ssp. pygargus", + "subspecies": "pygargus", + "rank": "ssp.", + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 6232, + "scientific_name": "Dama mesopotamica", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 6267, + "scientific_name": "Dasycercus blythi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 6266, + "scientific_name": "Dasycercus cristicauda", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 40527, + "scientific_name": "Dasykaluta rosamondae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 6268, + "scientific_name": "Dasymys foxi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 6269, + "scientific_name": "Dasymys incomtus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 6270, + "scientific_name": "Dasymys montanus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 6271, + "scientific_name": "Dasymys nudipes", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 6272, + "scientific_name": "Dasymys rufulus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 6278, + "scientific_name": "Dasyprocta azarae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 6279, + "scientific_name": "Dasyprocta coibae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 89497187, + "scientific_name": "Dasyprocta croconota", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 6281, + "scientific_name": "Dasyprocta fuliginosa", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 6282, + "scientific_name": "Dasyprocta guamara", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 89531729, + "scientific_name": "Dasyprocta iacki", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 6283, + "scientific_name": "Dasyprocta kalinowskii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 89497102, + "scientific_name": "Dasyprocta leporina", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 6285, + "scientific_name": "Dasyprocta mexicana", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 6286, + "scientific_name": "Dasyprocta prymnolopha", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 89497686, + "scientific_name": "Dasyprocta punctata", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 6287, + "scientific_name": "Dasyprocta ruatanica", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 89497716, + "scientific_name": "Dasyprocta variegata", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 6288, + "scientific_name": "Dasypus hybridus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 6289, + "scientific_name": "Dasypus kappleri", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 6290, + "scientific_name": "Dasypus novemcinctus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 6291, + "scientific_name": "Dasypus pilosus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 6292, + "scientific_name": "Dasypus sabanicola", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 6293, + "scientific_name": "Dasypus septemcinctus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 61924, + "scientific_name": "Dasypus yepesi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 6265, + "scientific_name": "Dasyuroides byrnei", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 6299, + "scientific_name": "Dasyurus albopunctatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 6294, + "scientific_name": "Dasyurus geoffroii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 6295, + "scientific_name": "Dasyurus hallucatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 6300, + "scientific_name": "Dasyurus maculatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 6301, + "scientific_name": "Dasyurus spartacus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 6296, + "scientific_name": "Dasyurus viverrinus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 6302, + "scientific_name": "Daubentonia madagascariensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 6313, + "scientific_name": "Delanymys brooksi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 136338, + "scientific_name": "Delomys collinus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 6329, + "scientific_name": "Delomys dorsalis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 6330, + "scientific_name": "Delomys sublineatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 6335, + "scientific_name": "Delphinapterus leucas", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 61442, + "scientific_name": "Delphinapterus leucas Cook Inlet subpopulation", + "subspecies": null, + "rank": null, + "subpopulation": "Cook Inlet subpopulation", + "category": "CR" + }, + { + "taxonid": 134817215, + "scientific_name": "Delphinus delphis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 156206333, + "scientific_name": "Delphinus delphis Gulf of Corinth subpopulation", + "subspecies": null, + "rank": null, + "subpopulation": "Gulf of Corinth subpopulation", + "category": "CR" + }, + { + "taxonid": 189865869, + "scientific_name": "Delphinus delphis Inner Mediterranean subpopulation", + "subspecies": null, + "rank": null, + "subpopulation": "Inner Mediterranean subpopulation", + "category": "EN" + }, + { + "taxonid": 41762, + "scientific_name": "Delphinus delphis Mediterranean subpopulation", + "subspecies": null, + "rank": null, + "subpopulation": "Mediterranean subpopulation", + "category": "EN" + }, + { + "taxonid": 133729, + "scientific_name": "Delphinus delphis ssp. ponticus", + "subspecies": "ponticus", + "rank": "ssp.", + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 738, + "scientific_name": "Deltamys kempi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 6405, + "scientific_name": "Dendrogale melanura", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 41490, + "scientific_name": "Dendrogale murina", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 6409, + "scientific_name": "Dendrohyrax arboreus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 6410, + "scientific_name": "Dendrohyrax dorsalis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136599, + "scientific_name": "Dendrohyrax validus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 6426, + "scientific_name": "Dendrolagus bennettianus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 6427, + "scientific_name": "Dendrolagus dorianus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 6429, + "scientific_name": "Dendrolagus goodfellowi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 6431, + "scientific_name": "Dendrolagus inustus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 6432, + "scientific_name": "Dendrolagus lumholtzi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 6433, + "scientific_name": "Dendrolagus matschiei", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 136668, + "scientific_name": "Dendrolagus mayri", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 6437, + "scientific_name": "Dendrolagus mbaiso", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 136732, + "scientific_name": "Dendrolagus notatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 136696, + "scientific_name": "Dendrolagus pulcherrimus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 6435, + "scientific_name": "Dendrolagus scottae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 6436, + "scientific_name": "Dendrolagus spadix", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 136812, + "scientific_name": "Dendrolagus stellarum", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 6434, + "scientific_name": "Dendrolagus ursinus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 6440, + "scientific_name": "Dendromus insignis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 6439, + "scientific_name": "Dendromus kahuziensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 6441, + "scientific_name": "Dendromus kivu", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 45958985, + "scientific_name": "Dendromus lachaisei", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 6442, + "scientific_name": "Dendromus lovati", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 6443, + "scientific_name": "Dendromus melanotis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 6444, + "scientific_name": "Dendromus mesomelas", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 6445, + "scientific_name": "Dendromus messorius", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 6446, + "scientific_name": "Dendromus mystacalis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 6447, + "scientific_name": "Dendromus nyikae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 6448, + "scientific_name": "Dendromus oreas", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 45959000, + "scientific_name": "Dendromus ruppi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 6449, + "scientific_name": "Dendromus vernayi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 6483, + "scientific_name": "Dendroprionomys rousseloti", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 6490, + "scientific_name": "Deomys ferrugineus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 6491, + "scientific_name": "Dephomys defua", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 2122, + "scientific_name": "Dermanura anderseni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 2123, + "scientific_name": "Dermanura azteca", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 83683094, + "scientific_name": "Dermanura bogotensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 2124, + "scientific_name": "Dermanura cinerea", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 83683065, + "scientific_name": "Dermanura glauca", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 2129, + "scientific_name": "Dermanura gnoma", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 83683287, + "scientific_name": "Dermanura phaeotis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 83683265, + "scientific_name": "Dermanura rava", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136505, + "scientific_name": "Dermanura rosenbergi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 2140, + "scientific_name": "Dermanura tolteca", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 99586593, + "scientific_name": "Dermanura watsoni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 18731, + "scientific_name": "Desmalopex leucopterus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 84457227, + "scientific_name": "Desmalopex microleucopterus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 6506, + "scientific_name": "Desmana moschata", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 6508, + "scientific_name": "Desmodilliscus braueri", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 6509, + "scientific_name": "Desmodillus auricularis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 6510, + "scientific_name": "Desmodus rotundus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 6511, + "scientific_name": "Desmomys harringtoni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 45052, + "scientific_name": "Desmomys yaldeni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 6520, + "scientific_name": "Diaemus youngi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 6553, + "scientific_name": "Dicerorhinus sumatrensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 6557, + "scientific_name": "Diceros bicornis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 39318, + "scientific_name": "Diceros bicornis ssp. bicornis", + "subspecies": "bicornis", + "rank": "ssp.", + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 39319, + "scientific_name": "Diceros bicornis ssp. longipes", + "subspecies": "longipes", + "rank": "ssp.", + "subpopulation": null, + "category": "EX" + }, + { + "taxonid": 39320, + "scientific_name": "Diceros bicornis ssp. michaeli", + "subspecies": "michaeli", + "rank": "ssp.", + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 39321, + "scientific_name": "Diceros bicornis ssp. minor", + "subspecies": "minor", + "rank": "ssp.", + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 6561, + "scientific_name": "Diclidurus albus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 6562, + "scientific_name": "Diclidurus ingens", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 6563, + "scientific_name": "Diclidurus isabella", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 6564, + "scientific_name": "Diclidurus scutatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 42618, + "scientific_name": "Dicrostonyx groenlandicus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 42619, + "scientific_name": "Dicrostonyx hudsonius", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 42620, + "scientific_name": "Dicrostonyx nelsoni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 6567, + "scientific_name": "Dicrostonyx nunatakensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 42622, + "scientific_name": "Dicrostonyx richardsoni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 6568, + "scientific_name": "Dicrostonyx torquatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 39974, + "scientific_name": "Dicrostonyx unalascensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 6569, + "scientific_name": "Dicrostonyx vinogradovi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 40489, + "scientific_name": "Didelphis albiventris", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 40500, + "scientific_name": "Didelphis aurita", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136592, + "scientific_name": "Didelphis imperfecta", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 40501, + "scientific_name": "Didelphis marsupialis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136395, + "scientific_name": "Didelphis pernigra", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 40502, + "scientific_name": "Didelphis virginiana", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 6607, + "scientific_name": "Dinaromys bogdanovi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 6608, + "scientific_name": "Dinomys branickii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 6622, + "scientific_name": "Diomys crumpi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 6628, + "scientific_name": "Diphylla ecaudata", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 6635, + "scientific_name": "Diplogale hosei", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 41448, + "scientific_name": "Diplomesodon pulchellum", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 6662, + "scientific_name": "Diplomys caniceps", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 6663, + "scientific_name": "Diplomys labilis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 6671, + "scientific_name": "Diplothrix legata", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 6684, + "scientific_name": "Dipodomys agilis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 42599, + "scientific_name": "Dipodomys californicus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 6685, + "scientific_name": "Dipodomys compactus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 6686, + "scientific_name": "Dipodomys deserti", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 6675, + "scientific_name": "Dipodomys elator", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 6676, + "scientific_name": "Dipodomys gravipes", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 42600, + "scientific_name": "Dipodomys heermanni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 6678, + "scientific_name": "Dipodomys ingens", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 92465716, + "scientific_name": "Dipodomys merriami", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 42603, + "scientific_name": "Dipodomys microps", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 6690, + "scientific_name": "Dipodomys nelsoni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 6683, + "scientific_name": "Dipodomys nitratoides", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 6691, + "scientific_name": "Dipodomys ordii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 92464382, + "scientific_name": "Dipodomys ornatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 42604, + "scientific_name": "Dipodomys panamintinus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 92463503, + "scientific_name": "Dipodomys phillipsii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136630, + "scientific_name": "Dipodomys simulans", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 6693, + "scientific_name": "Dipodomys spectabilis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 6682, + "scientific_name": "Dipodomys stephensi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 42605, + "scientific_name": "Dipodomys venustus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 6705, + "scientific_name": "Dipus sagitta", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 6765, + "scientific_name": "Distoechurus pennatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136374, + "scientific_name": "Dobsonia anderseni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 6772, + "scientific_name": "Dobsonia beauforti", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 6773, + "scientific_name": "Dobsonia chapmani", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 136571, + "scientific_name": "Dobsonia crenulata", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 6774, + "scientific_name": "Dobsonia emersa", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 6775, + "scientific_name": "Dobsonia exoleta", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 6778, + "scientific_name": "Dobsonia inermis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 84882338, + "scientific_name": "Dobsonia magna", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 6770, + "scientific_name": "Dobsonia minor", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 84882605, + "scientific_name": "Dobsonia moluccensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 6776, + "scientific_name": "Dobsonia pannietensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 6771, + "scientific_name": "Dobsonia peronii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 6777, + "scientific_name": "Dobsonia praedatrix", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 6780, + "scientific_name": "Dobsonia viridis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 6785, + "scientific_name": "Dolichotis patagonum", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 6786, + "scientific_name": "Dolichotis salinicola", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41598, + "scientific_name": "Dologale dybowskii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 6793, + "scientific_name": "Dorcatragus megalotis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 6794, + "scientific_name": "Dorcopsis atrata", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 6795, + "scientific_name": "Dorcopsis hageni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 6799, + "scientific_name": "Dorcopsis luctuosa", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 6800, + "scientific_name": "Dorcopsis muelleri", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 6801, + "scientific_name": "Dorcopsulus macleayi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 6802, + "scientific_name": "Dorcopsulus vanheurni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 6820, + "scientific_name": "Dremomys everetti", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136313, + "scientific_name": "Dremomys gularis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 6821, + "scientific_name": "Dremomys lokriah", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 6822, + "scientific_name": "Dremomys pernyi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 6823, + "scientific_name": "Dremomys pyrrhomerus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 6824, + "scientific_name": "Dremomys rufigenis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 6834, + "scientific_name": "Dromiciops gliroides", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 88120233, + "scientific_name": "Dryadonycteris capixaba", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 46205572, + "scientific_name": "Drymoreomys albimaculatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 6859, + "scientific_name": "Dryomys laniger", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 40767, + "scientific_name": "Dryomys niethammeri", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 6858, + "scientific_name": "Dryomys nitedula", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 6909, + "scientific_name": "Dugong dugon", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 157011948, + "scientific_name": "Dugong dugon Nansei subpopulation", + "subspecies": null, + "rank": null, + "subpopulation": "Nansei subpopulation", + "category": "CR" + }, + { + "taxonid": 6923, + "scientific_name": "Dusicyon australis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EX" + }, + { + "taxonid": 82337482, + "scientific_name": "Dusicyon avus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EX" + }, + { + "taxonid": 136356, + "scientific_name": "Dyacopterus brooksi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 84457541, + "scientific_name": "Dyacopterus rickarti", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 6931, + "scientific_name": "Dyacopterus spadiceus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 41488, + "scientific_name": "Dymecodon pilirostris", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 6979, + "scientific_name": "Echimys chrysurus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 6987, + "scientific_name": "Echimys saturnus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 136652, + "scientific_name": "Echimys vieirai", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 40592, + "scientific_name": "Echinops telfairi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 40603, + "scientific_name": "Echinosorex gymnura", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136230, + "scientific_name": "Echiothrix centrosa", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 7014, + "scientific_name": "Echiothrix leucura", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 7015, + "scientific_name": "Echymipera clara", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 7017, + "scientific_name": "Echymipera davidi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 7016, + "scientific_name": "Echymipera echinista", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 7018, + "scientific_name": "Echymipera kalubu", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 7019, + "scientific_name": "Echymipera rufescens", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 7030, + "scientific_name": "Ectophylla alba", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 7083, + "scientific_name": "Eidolon dupreanum", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 7084, + "scientific_name": "Eidolon helvum", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 41644, + "scientific_name": "Eira barbara", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 7112, + "scientific_name": "Elaphodus cephalophus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 7121, + "scientific_name": "Elaphurus davidianus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EW" + }, + { + "taxonid": 42658, + "scientific_name": "Elephantulus brachyrhynchus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 7136, + "scientific_name": "Elephantulus edwardii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 42659, + "scientific_name": "Elephantulus fuscipes", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 42660, + "scientific_name": "Elephantulus fuscus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 42661, + "scientific_name": "Elephantulus intufi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 42662, + "scientific_name": "Elephantulus myurus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 165924, + "scientific_name": "Elephantulus pilicaudus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 7137, + "scientific_name": "Elephantulus revoilii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 42663, + "scientific_name": "Elephantulus rozeti", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 42664, + "scientific_name": "Elephantulus rufescens", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 7138, + "scientific_name": "Elephantulus rupestris", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 7140, + "scientific_name": "Elephas maximus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 199856, + "scientific_name": "Elephas maximus ssp. sumatranus", + "subspecies": "sumatranus", + "rank": "ssp.", + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 7577, + "scientific_name": "Eligmodontia moreni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 7578, + "scientific_name": "Eligmodontia morgani", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 7579, + "scientific_name": "Eligmodontia puerulus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 48303074, + "scientific_name": "Eligmodontia typus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 7619, + "scientific_name": "Eliomys melanurus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136469, + "scientific_name": "Eliomys munbyanus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 7618, + "scientific_name": "Eliomys quercinus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 48267436, + "scientific_name": "Eliurus antsingy", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 48266951, + "scientific_name": "Eliurus carletoni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136251, + "scientific_name": "Eliurus danieli", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136325, + "scientific_name": "Eliurus ellermani", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 29460, + "scientific_name": "Eliurus grandidieri", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 7622, + "scientific_name": "Eliurus majori", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 7621, + "scientific_name": "Eliurus minor", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 7623, + "scientific_name": "Eliurus myoxinus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 7624, + "scientific_name": "Eliurus penicillatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 136316, + "scientific_name": "Eliurus petteri", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 7625, + "scientific_name": "Eliurus tanala", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 7626, + "scientific_name": "Eliurus webbi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 7653, + "scientific_name": "Ellobius alaicus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 7654, + "scientific_name": "Ellobius fuscocapillus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 7655, + "scientific_name": "Ellobius lutescens", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 7656, + "scientific_name": "Ellobius talpinus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 7657, + "scientific_name": "Ellobius tancrei", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 7670, + "scientific_name": "Emballonura alecto", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 7672, + "scientific_name": "Emballonura beccarii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 7673, + "scientific_name": "Emballonura dianae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 7667, + "scientific_name": "Emballonura furax", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 7674, + "scientific_name": "Emballonura monticola", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 7668, + "scientific_name": "Emballonura raffrayana", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 7669, + "scientific_name": "Emballonura semicaudata", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 41528, + "scientific_name": "Emballonura serii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 2130, + "scientific_name": "Enchisthenes hartii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 7750, + "scientific_name": "Enhydra lutris", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 7781, + "scientific_name": "Eoglaucomys fimbriatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 7782, + "scientific_name": "Eolagurus luteus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 7783, + "scientific_name": "Eolagurus przewalskii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 7786, + "scientific_name": "Eonycteris major", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 136768, + "scientific_name": "Eonycteris robusta", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 7787, + "scientific_name": "Eonycteris spelaea", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 14118, + "scientific_name": "Eospalax fontanierii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 14121, + "scientific_name": "Eospalax rothschildi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 14122, + "scientific_name": "Eospalax smithii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136507, + "scientific_name": "Eothenomys cachinus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 7797, + "scientific_name": "Eothenomys chinensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 7798, + "scientific_name": "Eothenomys custos", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 7801, + "scientific_name": "Eothenomys melanogaster", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136275, + "scientific_name": "Eothenomys miletus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 7802, + "scientific_name": "Eothenomys olitor", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 7803, + "scientific_name": "Eothenomys proditor", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 136196, + "scientific_name": "Eothenomys wardi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 7806, + "scientific_name": "Eozapus setchuanus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41428, + "scientific_name": "Episoriculus caudatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41429, + "scientific_name": "Episoriculus fumidus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41432, + "scientific_name": "Episoriculus leucops", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41433, + "scientific_name": "Episoriculus macrurus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 7899, + "scientific_name": "Epixerus ebii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 7901, + "scientific_name": "Epomophorus angolensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 136351, + "scientific_name": "Epomophorus anselli", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 44697, + "scientific_name": "Epomophorus crypturus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 7903, + "scientific_name": "Epomophorus gambianus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 7902, + "scientific_name": "Epomophorus grandis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 84457881, + "scientific_name": "Epomophorus labiatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 7905, + "scientific_name": "Epomophorus minimus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 84458822, + "scientific_name": "Epomophorus minor", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 7906, + "scientific_name": "Epomophorus wahlbergi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 7907, + "scientific_name": "Epomops buettikoferi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 7908, + "scientific_name": "Epomops dobsonii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 7909, + "scientific_name": "Epomops franqueti", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 85198368, + "scientific_name": "Eptesicus anatolicus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 7912, + "scientific_name": "Eptesicus andinus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 7914, + "scientific_name": "Eptesicus bobrinskoi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 85197425, + "scientific_name": "Eptesicus bottae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 7916, + "scientific_name": "Eptesicus brasiliensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136524, + "scientific_name": "Eptesicus chiriquinus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 7922, + "scientific_name": "Eptesicus diminutus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 7921, + "scientific_name": "Eptesicus dimissus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 7926, + "scientific_name": "Eptesicus floweri", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 7927, + "scientific_name": "Eptesicus furinalis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 7928, + "scientific_name": "Eptesicus fuscus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41531, + "scientific_name": "Eptesicus gobiensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 7929, + "scientific_name": "Eptesicus guadeloupensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 7931, + "scientific_name": "Eptesicus hottentotus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 7932, + "scientific_name": "Eptesicus innoxius", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 85200107, + "scientific_name": "Eptesicus isabellinus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136823, + "scientific_name": "Eptesicus japonensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 7933, + "scientific_name": "Eptesicus kobayashii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 7910, + "scientific_name": "Eptesicus nilssonii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 85198662, + "scientific_name": "Eptesicus ognevi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 85200202, + "scientific_name": "Eptesicus pachyomus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 7936, + "scientific_name": "Eptesicus pachyotis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 7937, + "scientific_name": "Eptesicus platyops", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 85199559, + "scientific_name": "Eptesicus serotinus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 88151044, + "scientific_name": "Eptesicus taddeii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 7942, + "scientific_name": "Eptesicus tatei", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 7949, + "scientific_name": "Equus africanus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 41763, + "scientific_name": "Equus ferus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 7961, + "scientific_name": "Equus ferus ssp. przewalskii", + "subspecies": "przewalskii", + "rank": "ssp.", + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 7950, + "scientific_name": "Equus grevyi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 7951, + "scientific_name": "Equus hemionus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 7952, + "scientific_name": "Equus hemionus ssp. hemionus", + "subspecies": "hemionus", + "rank": "ssp.", + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 7962, + "scientific_name": "Equus hemionus ssp. hemippus", + "subspecies": "hemippus", + "rank": "ssp.", + "subpopulation": null, + "category": "EX" + }, + { + "taxonid": 7963, + "scientific_name": "Equus hemionus ssp. khur", + "subspecies": "khur", + "rank": "ssp.", + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 7964, + "scientific_name": "Equus hemionus ssp. kulan", + "subspecies": "kulan", + "rank": "ssp.", + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 7966, + "scientific_name": "Equus hemionus ssp. onager", + "subspecies": "onager", + "rank": "ssp.", + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 7953, + "scientific_name": "Equus kiang", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41013, + "scientific_name": "Equus quagga", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 7957, + "scientific_name": "Equus quagga ssp. quagga", + "subspecies": "quagga", + "rank": "ssp.", + "subpopulation": null, + "category": "EX" + }, + { + "taxonid": 7960, + "scientific_name": "Equus zebra", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 7958, + "scientific_name": "Equus zebra ssp. hartmannae", + "subspecies": "hartmannae", + "rank": "ssp.", + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 7959, + "scientific_name": "Equus zebra ssp. zebra", + "subspecies": "zebra", + "rank": "ssp.", + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 7994, + "scientific_name": "Eremitalpa granti", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 7995, + "scientific_name": "Eremodipus lichtensteini", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 15609, + "scientific_name": "Eremoryzomys polius", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 8004, + "scientific_name": "Erethizon dorsatum", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 8010, + "scientific_name": "Erignathus barbatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 8012, + "scientific_name": "Erignathus barbatus ssp. barbatus", + "subspecies": "barbatus", + "rank": "ssp.", + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 8013, + "scientific_name": "Erignathus barbatus ssp. nauticus", + "subspecies": "nauticus", + "rank": "ssp.", + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 40604, + "scientific_name": "Erinaceus amurensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 40605, + "scientific_name": "Erinaceus concolor", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 29650, + "scientific_name": "Erinaceus europaeus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136344, + "scientific_name": "Erinaceus roumanicus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 8032, + "scientific_name": "Eropeplus canus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 136247, + "scientific_name": "Erophylla bombifrons", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 8033, + "scientific_name": "Erophylla sezekorni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 92252436, + "scientific_name": "Erythrocebus baumstarki", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 174391079, + "scientific_name": "Erythrocebus patas", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 92252458, + "scientific_name": "Erythrocebus patas ssp. patas", + "subspecies": "patas", + "rank": "ssp.", + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 92252480, + "scientific_name": "Erythrocebus patas ssp. pyrrhonotus", + "subspecies": "pyrrhonotus", + "rank": "ssp.", + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 205893280, + "scientific_name": "Erythrocebus patas ssp. villiersi", + "subspecies": "villiersi", + "rank": "ssp.", + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 164377509, + "scientific_name": "Erythrocebus poliophaeus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 8097, + "scientific_name": "Eschrichtius robustus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 8099, + "scientific_name": "Eschrichtius robustus western subpopulation", + "subspecies": null, + "rank": null, + "subpopulation": "western subpopulation", + "category": "EN" + }, + { + "taxonid": 8153, + "scientific_name": "Eubalaena australis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 133704, + "scientific_name": "Eubalaena australis Chile-Peru subpopulation", + "subspecies": null, + "rank": null, + "subpopulation": "Chile-Peru subpopulation", + "category": "CR" + }, + { + "taxonid": 41712, + "scientific_name": "Eubalaena glacialis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 41711, + "scientific_name": "Eubalaena japonica", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 133706, + "scientific_name": "Eubalaena japonica Northeast Pacific subpopulation", + "subspecies": null, + "rank": null, + "subpopulation": "Northeast Pacific subpopulation", + "category": "CR" + }, + { + "taxonid": 8162, + "scientific_name": "Euchoreutes naso", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 8166, + "scientific_name": "Euderma maculatum", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 80263386, + "scientific_name": "Eudiscoderma thongareeae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 8168, + "scientific_name": "Eudiscopus denticulus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 8992, + "scientific_name": "Eudorcas albonotata", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 8973, + "scientific_name": "Eudorcas rufifrons", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 8982, + "scientific_name": "Eudorcas thomsonii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 8991, + "scientific_name": "Eudorcas tilonura", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 8204, + "scientific_name": "Eulemur albifrons", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 8205, + "scientific_name": "Eulemur cinereiceps", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 8206, + "scientific_name": "Eulemur collaris", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 8199, + "scientific_name": "Eulemur coronatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 8211, + "scientific_name": "Eulemur flavifrons", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 8207, + "scientific_name": "Eulemur fulvus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 8212, + "scientific_name": "Eulemur macaco", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 8202, + "scientific_name": "Eulemur mongoz", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 8203, + "scientific_name": "Eulemur rubriventer", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 136269, + "scientific_name": "Eulemur rufifrons", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 8209, + "scientific_name": "Eulemur rufus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 8210, + "scientific_name": "Eulemur sanfordi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 8239, + "scientific_name": "Eumetopias jubatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 17367725, + "scientific_name": "Eumetopias jubatus ssp. jubatus", + "subspecies": "jubatus", + "rank": "ssp.", + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 17345844, + "scientific_name": "Eumetopias jubatus ssp. monteriensis", + "subspecies": "monteriensis", + "rank": "ssp.", + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 8241, + "scientific_name": "Eumops auripendulus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 87993837, + "scientific_name": "Eumops bonariensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 8243, + "scientific_name": "Eumops dabbenei", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 87993965, + "scientific_name": "Eumops delticus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 87994072, + "scientific_name": "Eumops ferox", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136433, + "scientific_name": "Eumops floridanus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 87994083, + "scientific_name": "Eumops glaucinus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 8245, + "scientific_name": "Eumops hansae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 8246, + "scientific_name": "Eumops maurus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 87994060, + "scientific_name": "Eumops nanus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136825, + "scientific_name": "Eumops patagonicus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 8247, + "scientific_name": "Eumops perotis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136809, + "scientific_name": "Eumops trumbulli", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 8248, + "scientific_name": "Eumops underwoodi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 87993523, + "scientific_name": "Eumops wilsoni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 8256, + "scientific_name": "Euneomys chinchilloides", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 136261, + "scientific_name": "Euneomys fossor", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 8257, + "scientific_name": "Euneomys mordax", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 8258, + "scientific_name": "Euneomys petersoni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 8265, + "scientific_name": "Euoticus elegantulus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 8266, + "scientific_name": "Euoticus pallidus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 40002, + "scientific_name": "Euoticus pallidus ssp. pallidus", + "subspecies": "pallidus", + "rank": "ssp.", + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 136892, + "scientific_name": "Euoticus pallidus ssp. talboti", + "subspecies": "talboti", + "rank": "ssp.", + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 8269, + "scientific_name": "Eupetaurus cinereus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 8306, + "scientific_name": "Euphractus sexcinctus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 68336601, + "scientific_name": "Eupleres goudotii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 39547, + "scientific_name": "Eupleres major", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 41459, + "scientific_name": "Euroscaptor grandis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41460, + "scientific_name": "Euroscaptor klossi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41461, + "scientific_name": "Euroscaptor longirostris", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41462, + "scientific_name": "Euroscaptor micrura", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 8385, + "scientific_name": "Euroscaptor mizura", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 8386, + "scientific_name": "Euroscaptor parvidens", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 45955241, + "scientific_name": "Euroscaptor subanura", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 29402, + "scientific_name": "Euryoryzomys emmonsae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 15602, + "scientific_name": "Euryoryzomys lamia", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 15603, + "scientific_name": "Euryoryzomys legatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 15605, + "scientific_name": "Euryoryzomys macconnelli", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 15607, + "scientific_name": "Euryoryzomys nitidus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 29405, + "scientific_name": "Euryoryzomys russatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 8418, + "scientific_name": "Euryzygomatomys spinosus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 21360, + "scientific_name": "Eutamias sibiricus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 8436, + "scientific_name": "Exilisciurus concinnus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 8437, + "scientific_name": "Exilisciurus exilis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 8438, + "scientific_name": "Exilisciurus whiteheadi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 17348, + "scientific_name": "Falsistrellus mackenziei", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 17351, + "scientific_name": "Falsistrellus mordax", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 17359, + "scientific_name": "Falsistrellus petersi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 17367, + "scientific_name": "Falsistrellus tasmaniensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 8539, + "scientific_name": "Felis bieti", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 8540, + "scientific_name": "Felis chaus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 131299383, + "scientific_name": "Felis lybica", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 8541, + "scientific_name": "Felis margarita", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 8542, + "scientific_name": "Felis nigripes", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 181049859, + "scientific_name": "Felis silvestris", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 8548, + "scientific_name": "Felovia vae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 8551, + "scientific_name": "Feresa attenuata", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 8553, + "scientific_name": "Feroculus feroculus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 8668, + "scientific_name": "Fossa fossana", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 44858, + "scientific_name": "Fukomys anselli", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 5752, + "scientific_name": "Fukomys bocagei", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 5753, + "scientific_name": "Fukomys damarensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 44860, + "scientific_name": "Fukomys darlingi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 5754, + "scientific_name": "Fukomys foxi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 167462770, + "scientific_name": "Fukomys hanangensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 48295791, + "scientific_name": "Fukomys ilariae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 44861, + "scientific_name": "Fukomys kafuensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 167463928, + "scientific_name": "Fukomys livingstoni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 5756, + "scientific_name": "Fukomys mechowii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 5757, + "scientific_name": "Fukomys ochraceocinereus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 48294834, + "scientific_name": "Fukomys vandewoestijneae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 5751, + "scientific_name": "Fukomys zechi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 8700, + "scientific_name": "Funambulus layardi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 88814157, + "scientific_name": "Funambulus obscurus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 8701, + "scientific_name": "Funambulus palmarum", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 8702, + "scientific_name": "Funambulus pennantii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 88813572, + "scientific_name": "Funambulus sublineatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 8704, + "scientific_name": "Funambulus tristriatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 8756, + "scientific_name": "Funisciurus anerythrus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 8757, + "scientific_name": "Funisciurus bayonii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 8755, + "scientific_name": "Funisciurus carruthersi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 8758, + "scientific_name": "Funisciurus congicus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136461, + "scientific_name": "Funisciurus duchaillui", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 8759, + "scientific_name": "Funisciurus isabella", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 8760, + "scientific_name": "Funisciurus lemniscatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 8761, + "scientific_name": "Funisciurus leucogenys", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 8762, + "scientific_name": "Funisciurus pyrropus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 8763, + "scientific_name": "Funisciurus substriatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 8771, + "scientific_name": "Furipterus horrens", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 8786, + "scientific_name": "Galago gallarum", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 40649, + "scientific_name": "Galagoides demidoff", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 91966225, + "scientific_name": "Galagoides demidoff ssp. demidoff", + "subspecies": "demidoff", + "rank": "ssp.", + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 91967801, + "scientific_name": "Galagoides demidoff ssp. poensis", + "subspecies": "poensis", + "rank": "ssp.", + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 164378198, + "scientific_name": "Galagoides kumbirensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 40653, + "scientific_name": "Galagoides thomasi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 8787, + "scientific_name": "Galago matschiei", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 8788, + "scientific_name": "Galago moholi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 8789, + "scientific_name": "Galago senegalensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136863, + "scientific_name": "Galago senegalensis ssp. braccatus", + "subspecies": "braccatus", + "rank": "ssp.", + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136872, + "scientific_name": "Galago senegalensis ssp. dunni", + "subspecies": "dunni", + "rank": "ssp.", + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136897, + "scientific_name": "Galago senegalensis ssp. senegalensis", + "subspecies": "senegalensis", + "rank": "ssp.", + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136904, + "scientific_name": "Galago senegalensis ssp. sotikae", + "subspecies": "sotikae", + "rank": "ssp.", + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 86235821, + "scientific_name": "Galea comes", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 8823, + "scientific_name": "Galea flavidens", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 86236150, + "scientific_name": "Galea leucoblephara", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 86226097, + "scientific_name": "Galea musteloides", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 8825, + "scientific_name": "Galea spixii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 8826, + "scientific_name": "Galemys pyrenaicus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 8827, + "scientific_name": "Galenomys garleppi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 41502, + "scientific_name": "Galeopterus variegatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41639, + "scientific_name": "Galictis cuja", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41640, + "scientific_name": "Galictis vittata", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 39426, + "scientific_name": "Galidia elegans", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 8833, + "scientific_name": "Galidictis fasciata", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 8834, + "scientific_name": "Galidictis grandidieri", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 13560, + "scientific_name": "Gardnerycteris crenulatum", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136266, + "scientific_name": "Gardnerycteris koepckeae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 117582065, + "scientific_name": "Gazella arabica", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 8978, + "scientific_name": "Gazella bennettii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 8987, + "scientific_name": "Gazella bilkis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EX" + }, + { + "taxonid": 8967, + "scientific_name": "Gazella cuvieri", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 8969, + "scientific_name": "Gazella dorcas", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 8989, + "scientific_name": "Gazella gazella", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 8972, + "scientific_name": "Gazella leptoceros", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 8977, + "scientific_name": "Gazella marica", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 8980, + "scientific_name": "Gazella saudiya", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EX" + }, + { + "taxonid": 8975, + "scientific_name": "Gazella spekei", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 8976, + "scientific_name": "Gazella subgutturosa", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 8994, + "scientific_name": "Genetta abyssinica", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 41696, + "scientific_name": "Genetta angolensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136223, + "scientific_name": "Genetta bourloni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 8998, + "scientific_name": "Genetta cristata", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 41698, + "scientific_name": "Genetta genetta", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 8997, + "scientific_name": "Genetta johnstoni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 41699, + "scientific_name": "Genetta maculata", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136437, + "scientific_name": "Genetta pardina", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 15628, + "scientific_name": "Genetta piscivora", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 136435, + "scientific_name": "Genetta poensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 41700, + "scientific_name": "Genetta servalina", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41701, + "scientific_name": "Genetta thierryi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41702, + "scientific_name": "Genetta tigrina", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41703, + "scientific_name": "Genetta victoriae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 9001, + "scientific_name": "Geocapromys brownii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 9004, + "scientific_name": "Geocapromys columbianus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EX" + }, + { + "taxonid": 9002, + "scientific_name": "Geocapromys ingrahami", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 9003, + "scientific_name": "Geocapromys thoracatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EX" + }, + { + "taxonid": 9048, + "scientific_name": "Geogale aurita", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 9054, + "scientific_name": "Geomys arenarius", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 136380, + "scientific_name": "Geomys attwateri", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136840, + "scientific_name": "Geomys breviceps", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 42588, + "scientific_name": "Geomys bursarius", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136258, + "scientific_name": "Geomys knoxjonesi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 9055, + "scientific_name": "Geomys personatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 42589, + "scientific_name": "Geomys pinetis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 9062, + "scientific_name": "Geomys texensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 9056, + "scientific_name": "Geomys tropicalis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 9077, + "scientific_name": "Georychus capensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 40768, + "scientific_name": "Geoxus annectens", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 9089, + "scientific_name": "Geoxus valdivianus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 21509, + "scientific_name": "Gerbilliscus afra", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 21510, + "scientific_name": "Gerbilliscus boehmi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 21511, + "scientific_name": "Gerbilliscus brantsii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 45076, + "scientific_name": "Gerbilliscus gambiana", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 21512, + "scientific_name": "Gerbilliscus guineae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 21513, + "scientific_name": "Gerbilliscus inclusus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 21515, + "scientific_name": "Gerbilliscus kempi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 21516, + "scientific_name": "Gerbilliscus leucogaster", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 21517, + "scientific_name": "Gerbilliscus nigricaudus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 21518, + "scientific_name": "Gerbilliscus phillipsi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 21519, + "scientific_name": "Gerbilliscus robustus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 21520, + "scientific_name": "Gerbilliscus validus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 9092, + "scientific_name": "Gerbillurus paeba", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 9093, + "scientific_name": "Gerbillurus setzeri", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 9094, + "scientific_name": "Gerbillurus tytonis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 9095, + "scientific_name": "Gerbillurus vallinus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 9096, + "scientific_name": "Gerbillus acticola", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 9102, + "scientific_name": "Gerbillus agag", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 9104, + "scientific_name": "Gerbillus amoenus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 9105, + "scientific_name": "Gerbillus andersoni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 9106, + "scientific_name": "Gerbillus aquilus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 9109, + "scientific_name": "Gerbillus bottai", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 9110, + "scientific_name": "Gerbillus brockmani", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 9111, + "scientific_name": "Gerbillus burtoni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 45088, + "scientific_name": "Gerbillus campestris", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 9113, + "scientific_name": "Gerbillus cheesmani", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 9114, + "scientific_name": "Gerbillus cosensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 9116, + "scientific_name": "Gerbillus dasyurus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 9119, + "scientific_name": "Gerbillus dunni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 9120, + "scientific_name": "Gerbillus famulus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 9121, + "scientific_name": "Gerbillus floweri", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 9123, + "scientific_name": "Gerbillus gerbillus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 9124, + "scientific_name": "Gerbillus gleadowi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 9125, + "scientific_name": "Gerbillus grobbeni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 9126, + "scientific_name": "Gerbillus harwoodi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 9127, + "scientific_name": "Gerbillus henleyi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 9128, + "scientific_name": "Gerbillus hesperinus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 9129, + "scientific_name": "Gerbillus hoogstraali", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 9130, + "scientific_name": "Gerbillus jamesi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 9131, + "scientific_name": "Gerbillus juliani", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 9132, + "scientific_name": "Gerbillus latastei", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 9133, + "scientific_name": "Gerbillus lowei", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 9134, + "scientific_name": "Gerbillus mackilligini", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 9097, + "scientific_name": "Gerbillus maghrebi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 9135, + "scientific_name": "Gerbillus mesopotamiae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 9099, + "scientific_name": "Gerbillus muriculus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 9100, + "scientific_name": "Gerbillus nancillus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 9136, + "scientific_name": "Gerbillus nanus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 9137, + "scientific_name": "Gerbillus nigeriae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 9138, + "scientific_name": "Gerbillus occiduus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 136703, + "scientific_name": "Gerbillus percivali", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 9140, + "scientific_name": "Gerbillus perpallidus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 9141, + "scientific_name": "Gerbillus poecilops", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 9142, + "scientific_name": "Gerbillus principulus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 9143, + "scientific_name": "Gerbillus pulvinatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 9144, + "scientific_name": "Gerbillus pusillus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 9145, + "scientific_name": "Gerbillus pyramidum", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 9101, + "scientific_name": "Gerbillus rosalinda", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 45053, + "scientific_name": "Gerbillus rupicola", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 9149, + "scientific_name": "Gerbillus simoni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 9150, + "scientific_name": "Gerbillus somalicus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 9151, + "scientific_name": "Gerbillus stigmonyx", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 9153, + "scientific_name": "Gerbillus tarabuli", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 9155, + "scientific_name": "Gerbillus watersi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 9194, + "scientific_name": "Giraffa camelopardalis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 88420726, + "scientific_name": "Giraffa camelopardalis ssp. angolensis", + "subspecies": "angolensis", + "rank": "ssp.", + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 88420742, + "scientific_name": "Giraffa camelopardalis ssp. antiquorum", + "subspecies": "antiquorum", + "rank": "ssp.", + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 88420707, + "scientific_name": "Giraffa camelopardalis ssp. camelopardalis", + "subspecies": "camelopardalis", + "rank": "ssp.", + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 136913, + "scientific_name": "Giraffa camelopardalis ssp. peralta", + "subspecies": "peralta", + "rank": "ssp.", + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 88420717, + "scientific_name": "Giraffa camelopardalis ssp. reticulata", + "subspecies": "reticulata", + "rank": "ssp.", + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 174469, + "scientific_name": "Giraffa camelopardalis ssp. rothschildi", + "subspecies": "rothschildi", + "rank": "ssp.", + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 88421020, + "scientific_name": "Giraffa camelopardalis ssp. thornicrofti", + "subspecies": "thornicrofti", + "rank": "ssp.", + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 88421036, + "scientific_name": "Giraffa camelopardalis ssp. tippelskirchi", + "subspecies": "tippelskirchi", + "rank": "ssp.", + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 39553, + "scientific_name": "Glaucomys sabrinus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 9240, + "scientific_name": "Glaucomys volans", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 44789, + "scientific_name": "Glauconycteris alboguttata", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 44790, + "scientific_name": "Glauconycteris argentata", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 44791, + "scientific_name": "Glauconycteris beatrix", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 44792, + "scientific_name": "Glauconycteris curryae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 44793, + "scientific_name": "Glauconycteris egeria", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 44794, + "scientific_name": "Glauconycteris gleni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 44795, + "scientific_name": "Glauconycteris humeralis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 44796, + "scientific_name": "Glauconycteris kenyacola", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 44797, + "scientific_name": "Glauconycteris machadoi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 44798, + "scientific_name": "Glauconycteris poensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 44799, + "scientific_name": "Glauconycteris superba", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 44800, + "scientific_name": "Glauconycteris variegata", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 9245, + "scientific_name": "Glironia venusta", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 9246, + "scientific_name": "Glirulus japonicus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 81189973, + "scientific_name": "Glischropus bucephalus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 9247, + "scientific_name": "Glischropus javanus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 81187867, + "scientific_name": "Glischropus tylopus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 39316, + "scientific_name": "Glis glis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 9249, + "scientific_name": "Globicephala macrorhynchus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 9250, + "scientific_name": "Globicephala melas", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 198785664, + "scientific_name": "Globicephala melas Inner Mediterranean subpopulation", + "subspecies": null, + "rank": null, + "subpopulation": "Inner Mediterranean subpopulation", + "category": "EN" + }, + { + "taxonid": 198787290, + "scientific_name": "Globicephala melas Strait of Gibraltar subpopulation", + "subspecies": null, + "rank": null, + "subpopulation": "Strait of Gibraltar subpopulation", + "category": "CR" + }, + { + "taxonid": 9273, + "scientific_name": "Glossophaga commissarisi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 9274, + "scientific_name": "Glossophaga leachii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 9275, + "scientific_name": "Glossophaga longirostris", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 9276, + "scientific_name": "Glossophaga morenoi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 9277, + "scientific_name": "Glossophaga soricina", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13375, + "scientific_name": "Glyphonycteris behnii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 13377, + "scientific_name": "Glyphonycteris daviesi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13384, + "scientific_name": "Glyphonycteris sylvestris", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 9283, + "scientific_name": "Glyphotes simus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 9303, + "scientific_name": "Golunda ellioti", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 39994, + "scientific_name": "Gorilla beringei", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 39999, + "scientific_name": "Gorilla beringei ssp. beringei", + "subspecies": "beringei", + "rank": "ssp.", + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 39995, + "scientific_name": "Gorilla beringei ssp. graueri", + "subspecies": "graueri", + "rank": "ssp.", + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 9404, + "scientific_name": "Gorilla gorilla", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 39998, + "scientific_name": "Gorilla gorilla ssp. diehli", + "subspecies": "diehli", + "rank": "ssp.", + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 9406, + "scientific_name": "Gorilla gorilla ssp. gorilla", + "subspecies": "gorilla", + "rank": "ssp.", + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 92363381, + "scientific_name": "Gracilimus radix", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 9416, + "scientific_name": "Gracilinanus aceramarcae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 9417, + "scientific_name": "Gracilinanus agilis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 9418, + "scientific_name": "Gracilinanus dryas", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 9419, + "scientific_name": "Gracilinanus emiliae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 9420, + "scientific_name": "Gracilinanus marica", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 9421, + "scientific_name": "Gracilinanus microtarsus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 9450, + "scientific_name": "Grammomys aridulus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 47967714, + "scientific_name": "Grammomys brevirostris", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 9451, + "scientific_name": "Grammomys buntingi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 9452, + "scientific_name": "Grammomys caniceps", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 9453, + "scientific_name": "Grammomys cometes", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 9454, + "scientific_name": "Grammomys dolichurus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 9455, + "scientific_name": "Grammomys dryas", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 9456, + "scientific_name": "Grammomys gigas", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 9457, + "scientific_name": "Grammomys ibeanus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 9460, + "scientific_name": "Grammomys kuru", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 9458, + "scientific_name": "Grammomys macmillani", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 9459, + "scientific_name": "Grammomys minnae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 45955965, + "scientific_name": "Grammomys selousi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 9461, + "scientific_name": "Grampus griseus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 16378423, + "scientific_name": "Grampus griseus Mediterranean subpopulation", + "subspecies": null, + "rank": null, + "subpopulation": "Mediterranean subpopulation", + "category": "EN" + }, + { + "taxonid": 114981285, + "scientific_name": "Graomys chacoensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 9465, + "scientific_name": "Graomys domorum", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 9466, + "scientific_name": "Graomys edithae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 9467, + "scientific_name": "Graomys griseoflavus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 44915, + "scientific_name": "Graphiurus angolensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 9480, + "scientific_name": "Graphiurus christyi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 9481, + "scientific_name": "Graphiurus crassicaudatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 44928, + "scientific_name": "Graphiurus johnstoni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 9483, + "scientific_name": "Graphiurus kelleni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 9484, + "scientific_name": "Graphiurus lorraineus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 9485, + "scientific_name": "Graphiurus microtis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 9486, + "scientific_name": "Graphiurus monardi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 9487, + "scientific_name": "Graphiurus murinus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 44916, + "scientific_name": "Graphiurus nagtglasii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 9488, + "scientific_name": "Graphiurus ocularis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 9491, + "scientific_name": "Graphiurus platyops", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 9492, + "scientific_name": "Graphiurus rupicola", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 9493, + "scientific_name": "Graphiurus surdus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 112387339, + "scientific_name": "Graphiurus walterverheyeni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 9561, + "scientific_name": "Gulo gulo", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 11062, + "scientific_name": "Gyldenstolpia fronto", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 9564, + "scientific_name": "Gymnobelideus leadbeateri", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 9581, + "scientific_name": "Gymnuromys roberti", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 9608, + "scientific_name": "Habromys chinanteco", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 136683, + "scientific_name": "Habromys delicatulus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 136582, + "scientific_name": "Habromys ixtlani", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 9609, + "scientific_name": "Habromys lepturus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 9610, + "scientific_name": "Habromys lophurus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 136616, + "scientific_name": "Habromys schmidlyi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 9611, + "scientific_name": "Habromys simulatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 9618, + "scientific_name": "Hadromys humei", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 136417, + "scientific_name": "Hadromys yunnanensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 9630, + "scientific_name": "Haeromys margarettae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 9631, + "scientific_name": "Haeromys minahassae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 9632, + "scientific_name": "Haeromys pusillus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 9660, + "scientific_name": "Halichoerus grypus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 74491261, + "scientific_name": "Halichoerus grypus Baltic Sea subpopulation", + "subspecies": null, + "rank": null, + "subpopulation": "Baltic Sea subpopulation", + "category": "LC" + }, + { + "taxonid": 61382004, + "scientific_name": "Halichoerus grypus ssp. grypus", + "subspecies": "grypus", + "rank": "ssp.", + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 61382025, + "scientific_name": "Halichoerus grypus ssp. macrorhynchus", + "subspecies": "macrorhynchus", + "rank": "ssp.", + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 112386684, + "scientific_name": "Halmaheramys bokimekot", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 15585, + "scientific_name": "Handleyomys alfaroi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 15591, + "scientific_name": "Handleyomys chapmani", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 547, + "scientific_name": "Handleyomys fuscatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 15598, + "scientific_name": "Handleyomys intectus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 15606, + "scientific_name": "Handleyomys melanotis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 15611, + "scientific_name": "Handleyomys rhabdops", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 15612, + "scientific_name": "Handleyomys rostratus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 15613, + "scientific_name": "Handleyomys saturatior", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 9676, + "scientific_name": "Hapalemur alaotrensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 9672, + "scientific_name": "Hapalemur aureus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 9673, + "scientific_name": "Hapalemur griseus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 136894, + "scientific_name": "Hapalemur griseus ssp. gilberti", + "subspecies": "gilberti", + "rank": "ssp.", + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 9677, + "scientific_name": "Hapalemur griseus ssp. griseus", + "subspecies": "griseus", + "rank": "ssp.", + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 16971595, + "scientific_name": "Hapalemur griseus ssp. ranomafanensis", + "subspecies": "ranomafanensis", + "rank": "ssp.", + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 136384, + "scientific_name": "Hapalemur meridionalis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 9678, + "scientific_name": "Hapalemur occidentalis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 9679, + "scientific_name": "Hapalomys delacouri", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 9680, + "scientific_name": "Hapalomys longicaudatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 9690, + "scientific_name": "Haplonycteris fischeri", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 99711843, + "scientific_name": "Harpiocephalus harpia", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13941, + "scientific_name": "Harpiola grisea", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 136445, + "scientific_name": "Harpiola isodon", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136776, + "scientific_name": "Harpyionycteris celebensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 9740, + "scientific_name": "Harpyionycteris whiteheadi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 9759, + "scientific_name": "Heimyscus fumosus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 9760, + "scientific_name": "Helarctos malayanus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 9828, + "scientific_name": "Heliophobius argenteocinereus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 9830, + "scientific_name": "Heliosciurus gambianus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 9831, + "scientific_name": "Heliosciurus mutabilis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 9832, + "scientific_name": "Heliosciurus punctatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 9833, + "scientific_name": "Heliosciurus rufobrachium", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 9834, + "scientific_name": "Heliosciurus ruwenzorii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 9835, + "scientific_name": "Heliosciurus undulatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 41608, + "scientific_name": "Helogale hirtula", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41609, + "scientific_name": "Helogale parvula", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 9869, + "scientific_name": "Hemibelideus lemuroides", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 62011, + "scientific_name": "Hemicentetes nigriceps", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 40593, + "scientific_name": "Hemicentetes semispinosus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 40607, + "scientific_name": "Hemiechinus auritus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 40608, + "scientific_name": "Hemiechinus collaris", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41689, + "scientific_name": "Hemigalus derbyanus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 9919, + "scientific_name": "Hemitragus jemlahicus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 9948, + "scientific_name": "Herpailurus yagouaroundi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 70204120, + "scientific_name": "Herpestes auropunctatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41610, + "scientific_name": "Herpestes brachyurus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 41611, + "scientific_name": "Herpestes edwardsii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41599, + "scientific_name": "Herpestes flavescens", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41612, + "scientific_name": "Herpestes fuscus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41613, + "scientific_name": "Herpestes ichneumon", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 70203940, + "scientific_name": "Herpestes javanicus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41615, + "scientific_name": "Herpestes naso", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41605, + "scientific_name": "Herpestes ochraceus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41600, + "scientific_name": "Herpestes pulverulentus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41606, + "scientific_name": "Herpestes sanguineus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41616, + "scientific_name": "Herpestes semitorquatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 41617, + "scientific_name": "Herpestes smithii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41618, + "scientific_name": "Herpestes urva", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41619, + "scientific_name": "Herpestes vitticollis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 9975, + "scientific_name": "Hesperoptenus blanfordi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 9976, + "scientific_name": "Hesperoptenus doriae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 9977, + "scientific_name": "Hesperoptenus gaskelli", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 9978, + "scientific_name": "Hesperoptenus tickelli", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 9979, + "scientific_name": "Hesperoptenus tomesi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 9987, + "scientific_name": "Heterocephalus glaber", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 96802567, + "scientific_name": "Heterogeomys cherriei", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 42592, + "scientific_name": "Heterogeomys cherriei ssp. matagalpae", + "subspecies": "matagalpae", + "rank": "ssp.", + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 96802987, + "scientific_name": "Heterogeomys dariensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 15550, + "scientific_name": "Heterogeomys dariensis ssp. thaeleri", + "subspecies": "thaeleri", + "rank": "ssp.", + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 42591, + "scientific_name": "Heterogeomys lanius", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 9997, + "scientific_name": "Heterohyrax brucei", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 12073, + "scientific_name": "Heteromys adspersus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 47802541, + "scientific_name": "Heteromys anomalus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 10005, + "scientific_name": "Heteromys australis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 47802999, + "scientific_name": "Heteromys catopterius", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 47804700, + "scientific_name": "Heteromys desmarestianus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 10007, + "scientific_name": "Heteromys gaumeri", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 12074, + "scientific_name": "Heteromys irroratus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 10009, + "scientific_name": "Heteromys nelsoni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 136638, + "scientific_name": "Heteromys oasicus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 10010, + "scientific_name": "Heteromys oresterus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 12075, + "scientific_name": "Heteromys pictus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 12076, + "scientific_name": "Heteromys salvini", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 12077, + "scientific_name": "Heteromys spectabilis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 136333, + "scientific_name": "Heteromys teleus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 10025, + "scientific_name": "Heteropsomys insulans", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EX" + }, + { + "taxonid": 10034, + "scientific_name": "Hexolobodon phenax", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EX" + }, + { + "taxonid": 10053, + "scientific_name": "Hippocamelus antisensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 10054, + "scientific_name": "Hippocamelus bisulcus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 10103, + "scientific_name": "Hippopotamus amphibius", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 40782, + "scientific_name": "Hippopotamus lemerlei", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EX" + }, + { + "taxonid": 40783, + "scientific_name": "Hippopotamus madagascariensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EX" + }, + { + "taxonid": 10109, + "scientific_name": "Hipposideros abae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 80224880, + "scientific_name": "Hipposideros alongensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 10110, + "scientific_name": "Hipposideros armiger", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 80457009, + "scientific_name": "Hipposideros ater", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 80259774, + "scientific_name": "Hipposideros atrox", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 10112, + "scientific_name": "Hipposideros beatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 80258800, + "scientific_name": "Hipposideros bicolor", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136566, + "scientific_name": "Hipposideros boeadii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 10114, + "scientific_name": "Hipposideros breviceps", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 80459007, + "scientific_name": "Hipposideros caffer", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 10116, + "scientific_name": "Hipposideros calcaratus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 10117, + "scientific_name": "Hipposideros camerunensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 10118, + "scientific_name": "Hipposideros cervinus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 10119, + "scientific_name": "Hipposideros cineraceus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 10121, + "scientific_name": "Hipposideros coronatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 10122, + "scientific_name": "Hipposideros corynophyllus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 10123, + "scientific_name": "Hipposideros coxi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 10124, + "scientific_name": "Hipposideros crumeniferus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 10125, + "scientific_name": "Hipposideros curtus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 10126, + "scientific_name": "Hipposideros cyclops", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 10127, + "scientific_name": "Hipposideros demissus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 10128, + "scientific_name": "Hipposideros diadema", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 10129, + "scientific_name": "Hipposideros dinops", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 10130, + "scientific_name": "Hipposideros doriae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 10131, + "scientific_name": "Hipposideros durgadasi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 10132, + "scientific_name": "Hipposideros dyacorum", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 10133, + "scientific_name": "Hipposideros edwardshilli", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 80222798, + "scientific_name": "Hipposideros einnaythu", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 10134, + "scientific_name": "Hipposideros fuliginosus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 10135, + "scientific_name": "Hipposideros fulvus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 10136, + "scientific_name": "Hipposideros galeritus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 180991219, + "scientific_name": "Hipposideros gentilis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136478, + "scientific_name": "Hipposideros grandis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 80222915, + "scientific_name": "Hipposideros griffini", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 10137, + "scientific_name": "Hipposideros halophyllus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 10138, + "scientific_name": "Hipposideros hypophyllus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 10139, + "scientific_name": "Hipposideros inexpectatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 136739, + "scientific_name": "Hipposideros inornatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 10140, + "scientific_name": "Hipposideros jonesi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 136819, + "scientific_name": "Hipposideros khaokhouayensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 85646546, + "scientific_name": "Hipposideros khasiana", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 10141, + "scientific_name": "Hipposideros lamottei", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 10142, + "scientific_name": "Hipposideros lankadiva", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 85646564, + "scientific_name": "Hipposideros larvatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 10144, + "scientific_name": "Hipposideros lekaguli", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 10145, + "scientific_name": "Hipposideros lylei", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 10146, + "scientific_name": "Hipposideros macrobullatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 10147, + "scientific_name": "Hipposideros madurae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 10148, + "scientific_name": "Hipposideros maggietaylorae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 10149, + "scientific_name": "Hipposideros marisae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 10150, + "scientific_name": "Hipposideros megalotis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 10151, + "scientific_name": "Hipposideros muscinus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 10152, + "scientific_name": "Hipposideros nequam", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 80458824, + "scientific_name": "Hipposideros nicobarulae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 10153, + "scientific_name": "Hipposideros obscurus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136192, + "scientific_name": "Hipposideros orbiculus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 10107, + "scientific_name": "Hipposideros papua", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136600, + "scientific_name": "Hipposideros pelingensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 80224655, + "scientific_name": "Hipposideros pendleburyi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 180990825, + "scientific_name": "Hipposideros pomona", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 10155, + "scientific_name": "Hipposideros pratti", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 10156, + "scientific_name": "Hipposideros pygmaeus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 10108, + "scientific_name": "Hipposideros ridleyi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 136477, + "scientific_name": "Hipposideros rotalis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 10157, + "scientific_name": "Hipposideros ruber", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136586, + "scientific_name": "Hipposideros scutinares", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 10160, + "scientific_name": "Hipposideros semoni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 10161, + "scientific_name": "Hipposideros sorenseni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 10162, + "scientific_name": "Hipposideros speoris", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 10163, + "scientific_name": "Hipposideros stenotis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 10164, + "scientific_name": "Hipposideros sumbae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 85646524, + "scientific_name": "Hipposideros tephrus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 80224148, + "scientific_name": "Hipposideros turpis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 10166, + "scientific_name": "Hipposideros wollastoni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 10167, + "scientific_name": "Hippotragus equinus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 10168, + "scientific_name": "Hippotragus leucophaeus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EX" + }, + { + "taxonid": 10170, + "scientific_name": "Hippotragus niger", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 10169, + "scientific_name": "Hippotragus niger ssp. variani", + "subspecies": "variani", + "rank": "ssp.", + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 10200, + "scientific_name": "Histiotus alienus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 29606, + "scientific_name": "Histiotus humboldti", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 136502, + "scientific_name": "Histiotus laephotis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 10201, + "scientific_name": "Histiotus macrotus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136292, + "scientific_name": "Histiotus magellanicus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 10202, + "scientific_name": "Histiotus montanus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 10203, + "scientific_name": "Histiotus velatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 41670, + "scientific_name": "Histriophoca fasciata", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 10211, + "scientific_name": "Hodomys alleni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 10217, + "scientific_name": "Holochilus brasiliensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 10218, + "scientific_name": "Holochilus chacarius", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 10220, + "scientific_name": "Holochilus sciureus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 39876, + "scientific_name": "Hoolock hoolock", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 167541870, + "scientific_name": "Hoolock hoolock ssp. hoolock", + "subspecies": "hoolock", + "rank": "ssp.", + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 167541945, + "scientific_name": "Hoolock hoolock ssp. mishmiensis", + "subspecies": "mishmiensis", + "rank": "ssp.", + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 118355453, + "scientific_name": "Hoolock leuconedys", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 118355648, + "scientific_name": "Hoolock tianxing", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 10259, + "scientific_name": "Hoplomys gymnurus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 40597, + "scientific_name": "Huetia leucorhina", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 10274, + "scientific_name": "Hyaena hyaena", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 116989865, + "scientific_name": "Hybomys badius", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 10278, + "scientific_name": "Hybomys basilii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 116990322, + "scientific_name": "Hybomys eisentrauti", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 10280, + "scientific_name": "Hybomys lunaris", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 10281, + "scientific_name": "Hybomys planifrons", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 10282, + "scientific_name": "Hybomys trivirgatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 10283, + "scientific_name": "Hybomys univittatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 12420, + "scientific_name": "Hydrictis maculicollis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 10300, + "scientific_name": "Hydrochoerus hydrochaeris", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136277, + "scientific_name": "Hydrochoerus isthmius", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 10303, + "scientific_name": "Hydrodamalis gigas", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EX" + }, + { + "taxonid": 10310, + "scientific_name": "Hydromys chrysogaster", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 10312, + "scientific_name": "Hydromys hussoni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 10313, + "scientific_name": "Hydromys neobritannicus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 136476, + "scientific_name": "Hydromys ziegleri", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 10329, + "scientific_name": "Hydropotes inermis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 10340, + "scientific_name": "Hydrurga leptonyx", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 10341, + "scientific_name": "Hyemoschus aquaticus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 9422, + "scientific_name": "Hyladelphys kalinowskii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136283, + "scientific_name": "Hylaeamys acritus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 29404, + "scientific_name": "Hylaeamys laticeps", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 29403, + "scientific_name": "Hylaeamys megacephalus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 15608, + "scientific_name": "Hylaeamys oniscus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 136529, + "scientific_name": "Hylaeamys perenensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 29401, + "scientific_name": "Hylaeamys tatei", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 15617, + "scientific_name": "Hylaeamys yunganus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 39889, + "scientific_name": "Hylobates abbotti", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 10543, + "scientific_name": "Hylobates agilis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 39879, + "scientific_name": "Hylobates albibarbis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 39890, + "scientific_name": "Hylobates funereus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 10547, + "scientific_name": "Hylobates klossii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 10548, + "scientific_name": "Hylobates lar", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 39882, + "scientific_name": "Hylobates lar ssp. carpenteri", + "subspecies": "carpenteri", + "rank": "ssp.", + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 39883, + "scientific_name": "Hylobates lar ssp. entelloides", + "subspecies": "entelloides", + "rank": "ssp.", + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 39881, + "scientific_name": "Hylobates lar ssp. lar", + "subspecies": "lar", + "rank": "ssp.", + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 39884, + "scientific_name": "Hylobates lar ssp. vestitus", + "subspecies": "vestitus", + "rank": "ssp.", + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 39885, + "scientific_name": "Hylobates lar ssp. yunnanensis", + "subspecies": "yunnanensis", + "rank": "ssp.", + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 10550, + "scientific_name": "Hylobates moloch", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 39888, + "scientific_name": "Hylobates muelleri", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 10552, + "scientific_name": "Hylobates pileatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 41769, + "scientific_name": "Hylochoerus meinertzhageni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 10593, + "scientific_name": "Hylomyscus aeta", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 10594, + "scientific_name": "Hylomyscus alleni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 47968796, + "scientific_name": "Hylomyscus anselli", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 47968299, + "scientific_name": "Hylomyscus arcimontensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 10591, + "scientific_name": "Hylomyscus baeri", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 10595, + "scientific_name": "Hylomyscus carillus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 47968266, + "scientific_name": "Hylomyscus denniae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 111679859, + "scientific_name": "Hylomyscus endorobae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 45054, + "scientific_name": "Hylomyscus grandis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 111653543, + "scientific_name": "Hylomyscus heinrichorum", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 111679568, + "scientific_name": "Hylomyscus kerbispeterhansi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 45955968, + "scientific_name": "Hylomyscus pamfi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 10592, + "scientific_name": "Hylomyscus parvus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 47979117, + "scientific_name": "Hylomyscus stella", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 111679876, + "scientific_name": "Hylomyscus vulcanorum", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 47979811, + "scientific_name": "Hylomyscus walterverheyeni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136193, + "scientific_name": "Hylomys megalotis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 10589, + "scientific_name": "Hylomys parvus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 40611, + "scientific_name": "Hylomys suillus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 10598, + "scientific_name": "Hylonycteris underwoodi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 10600, + "scientific_name": "Hylopetes alboniger", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 10602, + "scientific_name": "Hylopetes bartelsi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 10604, + "scientific_name": "Hylopetes nigripes", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 10605, + "scientific_name": "Hylopetes phayrei", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136262, + "scientific_name": "Hylopetes platyurus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 112296031, + "scientific_name": "Hylopetes sagitta", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 10606, + "scientific_name": "Hylopetes sipora", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 10607, + "scientific_name": "Hylopetes spadiceus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 10608, + "scientific_name": "Hylopetes winstoni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 10632, + "scientific_name": "Hyomys dammermani", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 10633, + "scientific_name": "Hyomys goliath", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 92441853, + "scientific_name": "Hyorhinomys stuempkei", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 10635, + "scientific_name": "Hyosciurus heinrichi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 10636, + "scientific_name": "Hyosciurus ileile", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 10642, + "scientific_name": "Hyperacrius fertilis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 10643, + "scientific_name": "Hyperacrius wynnei", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 10707, + "scientific_name": "Hyperoodon ampullatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 10708, + "scientific_name": "Hyperoodon planifrons", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 10714, + "scientific_name": "Hypogeomys antimena", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 10734, + "scientific_name": "Hypsignathus monstrosus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 40559, + "scientific_name": "Hypsiprymnodon moschatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 17324, + "scientific_name": "Hypsugo affinis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136560, + "scientific_name": "Hypsugo alaschanicus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 17327, + "scientific_name": "Hypsugo anthonyi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 17328, + "scientific_name": "Hypsugo arabicus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 171619155, + "scientific_name": "Hypsugo ariel", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 85200870, + "scientific_name": "Hypsugo bemainty", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 17331, + "scientific_name": "Hypsugo cadornae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 85201719, + "scientific_name": "Hypsugo dolichodon", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 44854, + "scientific_name": "Hypsugo eisentrauti", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 17345, + "scientific_name": "Hypsugo joffrei", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 17346, + "scientific_name": "Hypsugo kitcheneri", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 85202881, + "scientific_name": "Hypsugo lanzai", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 17347, + "scientific_name": "Hypsugo lophurus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 17349, + "scientific_name": "Hypsugo macrotis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 44855, + "scientific_name": "Hypsugo musciculus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 17360, + "scientific_name": "Hypsugo pulveratus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 44856, + "scientific_name": "Hypsugo savii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 44195, + "scientific_name": "Hypsugo vordermanni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 10748, + "scientific_name": "Hystrix africaeaustralis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 10749, + "scientific_name": "Hystrix brachyura", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 10750, + "scientific_name": "Hystrix crassispinis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 10746, + "scientific_name": "Hystrix cristata", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 10751, + "scientific_name": "Hystrix indica", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 10752, + "scientific_name": "Hystrix javanica", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 10753, + "scientific_name": "Hystrix pumila", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 10754, + "scientific_name": "Hystrix sumatrae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 10755, + "scientific_name": "Ia io", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 41620, + "scientific_name": "Ichneumia albicauda", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 10761, + "scientific_name": "Ichthyomys hydrobates", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 10762, + "scientific_name": "Ichthyomys pittieri", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 10763, + "scientific_name": "Ichthyomys stolzmanni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 10764, + "scientific_name": "Ichthyomys tweedii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 20487, + "scientific_name": "Ictidomys mexicanus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 42564, + "scientific_name": "Ictidomys tridecemlineatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41645, + "scientific_name": "Ictonyx libycus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41646, + "scientific_name": "Ictonyx striatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 10790, + "scientific_name": "Idionycteris phyllotis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 10794, + "scientific_name": "Idiurus macrotis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 10796, + "scientific_name": "Idiurus zenkeri", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 40635, + "scientific_name": "Indopacetus pacificus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 10826, + "scientific_name": "Indri indri", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 10831, + "scientific_name": "Inia geoffrensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 10845, + "scientific_name": "Iomys horsfieldii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 10846, + "scientific_name": "Iomys sipora", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 10851, + "scientific_name": "Irenomys tarsalis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 10859, + "scientific_name": "Isolobodon montanus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EX" + }, + { + "taxonid": 10860, + "scientific_name": "Isolobodon portoricensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EX" + }, + { + "taxonid": 10863, + "scientific_name": "Isoodon auratus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 40552, + "scientific_name": "Isoodon macrourus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 40553, + "scientific_name": "Isoodon obesulus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 14914835, + "scientific_name": "Isothrix barbarabrownae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 90386297, + "scientific_name": "Isothrix bistriata", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136751, + "scientific_name": "Isothrix negrensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 90386307, + "scientific_name": "Isothrix orinoci", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 10879, + "scientific_name": "Isothrix pagurus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41786, + "scientific_name": "Isothrix sinnamariensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 10886, + "scientific_name": "Isthmomys flavidus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 10887, + "scientific_name": "Isthmomys pirrensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 10911, + "scientific_name": "Jaculus blanfordi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 10912, + "scientific_name": "Jaculus jaculus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 10913, + "scientific_name": "Jaculus orientalis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 45958882, + "scientific_name": "Jaculus thaleri", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 23078, + "scientific_name": "Juliomys pictipes", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136563, + "scientific_name": "Juliomys rimofrons", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 10946, + "scientific_name": "Juscelinomys candango", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EX" + }, + { + "taxonid": 115555116, + "scientific_name": "Juscelinomys huanchacae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 10954, + "scientific_name": "Kadarsanomys sodyi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 10957, + "scientific_name": "Kannabateomys amblyonyx", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 10966, + "scientific_name": "Kerivoula africana", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 10968, + "scientific_name": "Kerivoula agnella", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 10969, + "scientific_name": "Kerivoula argentata", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 154196297, + "scientific_name": "Kerivoula crypta", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 10971, + "scientific_name": "Kerivoula cuprosa", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 154195907, + "scientific_name": "Kerivoula depressa", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 154195951, + "scientific_name": "Kerivoula dongduongana", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 10972, + "scientific_name": "Kerivoula eriophora", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 10973, + "scientific_name": "Kerivoula flora", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 154196065, + "scientific_name": "Kerivoula furva", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 154195594, + "scientific_name": "Kerivoula hardwickii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 10975, + "scientific_name": "Kerivoula intermedia", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 136240, + "scientific_name": "Kerivoula kachinensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136572, + "scientific_name": "Kerivoula krauensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 10977, + "scientific_name": "Kerivoula lanosa", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136428, + "scientific_name": "Kerivoula lenis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 10978, + "scientific_name": "Kerivoula minuta", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 10979, + "scientific_name": "Kerivoula muscina", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 10980, + "scientific_name": "Kerivoula myrella", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 10981, + "scientific_name": "Kerivoula papillosa", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 10983, + "scientific_name": "Kerivoula pellucida", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 10984, + "scientific_name": "Kerivoula phalaena", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 10985, + "scientific_name": "Kerivoula picta", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 10986, + "scientific_name": "Kerivoula smithii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136817, + "scientific_name": "Kerivoula titania", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 10987, + "scientific_name": "Kerivoula whiteheadi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136222, + "scientific_name": "Kerodon acrobata", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 10988, + "scientific_name": "Kerodon rupestris", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 11035, + "scientific_name": "Kobus ellipsiprymnus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 11040, + "scientific_name": "Kobus ellipsiprymnus ssp. defassa", + "subspecies": "defassa", + "rank": "ssp.", + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 11039, + "scientific_name": "Kobus ellipsiprymnus ssp. ellipsiprymnus", + "subspecies": "ellipsiprymnus", + "rank": "ssp.", + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 11036, + "scientific_name": "Kobus kob", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 11041, + "scientific_name": "Kobus kob ssp. kob", + "subspecies": "kob", + "rank": "ssp.", + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 11042, + "scientific_name": "Kobus kob ssp. leucotis", + "subspecies": "leucotis", + "rank": "ssp.", + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 11043, + "scientific_name": "Kobus kob ssp. thomasi", + "subspecies": "thomasi", + "rank": "ssp.", + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 11033, + "scientific_name": "Kobus leche", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 136937, + "scientific_name": "Kobus leche ssp. anselli", + "subspecies": "anselli", + "rank": "ssp.", + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 11045, + "scientific_name": "Kobus leche ssp. kafuensis", + "subspecies": "kafuensis", + "rank": "ssp.", + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 11044, + "scientific_name": "Kobus leche ssp. leche", + "subspecies": "leche", + "rank": "ssp.", + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 11038, + "scientific_name": "Kobus leche ssp. robertsi", + "subspecies": "robertsi", + "rank": "ssp.", + "subpopulation": null, + "category": "EX" + }, + { + "taxonid": 11046, + "scientific_name": "Kobus leche ssp. smithemani", + "subspecies": "smithemani", + "rank": "ssp.", + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 11034, + "scientific_name": "Kobus megaceros", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 11037, + "scientific_name": "Kobus vardonii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 11047, + "scientific_name": "Kogia breviceps", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 11048, + "scientific_name": "Kogia sima", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 11050, + "scientific_name": "Komodomys rintjanus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 11061, + "scientific_name": "Kunsia tomentosus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 11135, + "scientific_name": "Laephotis angolensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 11136, + "scientific_name": "Laephotis botswanae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 11137, + "scientific_name": "Laephotis namibensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 11138, + "scientific_name": "Laephotis wintoni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 11140, + "scientific_name": "Lagenodelphis hosei", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 11141, + "scientific_name": "Lagenorhynchus acutus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 11142, + "scientific_name": "Lagenorhynchus albirostris", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 11143, + "scientific_name": "Lagenorhynchus australis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 11144, + "scientific_name": "Lagenorhynchus cruciger", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 11145, + "scientific_name": "Lagenorhynchus obliquidens", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 11146, + "scientific_name": "Lagenorhynchus obscurus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 134820643, + "scientific_name": "Lagenorhynchus obscurus ssp. posidonia", + "subspecies": "posidonia", + "rank": "ssp.", + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 48295808, + "scientific_name": "Lagidium ahuacaense", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 11148, + "scientific_name": "Lagidium viscacia", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 11149, + "scientific_name": "Lagidium wolffsohni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 11160, + "scientific_name": "Lagorchestes asomatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EX" + }, + { + "taxonid": 11161, + "scientific_name": "Lagorchestes conspicillatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 11162, + "scientific_name": "Lagorchestes hirsutus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 11163, + "scientific_name": "Lagorchestes leporides", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EX" + }, + { + "taxonid": 136452, + "scientific_name": "Lagostomus crassus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EX" + }, + { + "taxonid": 11170, + "scientific_name": "Lagostomus maximus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 11171, + "scientific_name": "Lagostrophus fasciatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 39924, + "scientific_name": "Lagothrix flavicauda", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 160881218, + "scientific_name": "Lagothrix lagothricha", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 39962, + "scientific_name": "Lagothrix lagothricha ssp. cana", + "subspecies": "cana", + "rank": "ssp.", + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 11175, + "scientific_name": "Lagothrix lagothricha ssp. lagothricha", + "subspecies": "lagothricha", + "rank": "ssp.", + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 39926, + "scientific_name": "Lagothrix lagothricha ssp. lugens", + "subspecies": "lugens", + "rank": "ssp.", + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 39927, + "scientific_name": "Lagothrix lagothricha ssp. poeppigii", + "subspecies": "poeppigii", + "rank": "ssp.", + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 39963, + "scientific_name": "Lagothrix lagothricha ssp. tschudii", + "subspecies": "tschudii", + "rank": "ssp.", + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 11179, + "scientific_name": "Lagurus lagurus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 11186, + "scientific_name": "Lama guanicoe", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 11202, + "scientific_name": "Lamottemys okuensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 13376, + "scientific_name": "Lampronycteris brachyotis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136474, + "scientific_name": "Laonastes aenigmamus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 11304, + "scientific_name": "Lariscus hosei", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 11305, + "scientific_name": "Lariscus insignis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 11306, + "scientific_name": "Lariscus niobe", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 11307, + "scientific_name": "Lariscus obscurus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 11339, + "scientific_name": "Lasionycteris noctivagans", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 11340, + "scientific_name": "Lasiopodomys brandtii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 11341, + "scientific_name": "Lasiopodomys fuscus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 11342, + "scientific_name": "Lasiopodomys mandarinus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 11343, + "scientific_name": "Lasiorhinus krefftii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 40555, + "scientific_name": "Lasiorhinus latifrons", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 29607, + "scientific_name": "Lasiurus atratus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 88151055, + "scientific_name": "Lasiurus blossevillii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 11347, + "scientific_name": "Lasiurus borealis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 11348, + "scientific_name": "Lasiurus castaneus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 11345, + "scientific_name": "Lasiurus cinereus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136306, + "scientific_name": "Lasiurus degelidus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 11349, + "scientific_name": "Lasiurus ebenus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 11350, + "scientific_name": "Lasiurus ega", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 11351, + "scientific_name": "Lasiurus egregius", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 136754, + "scientific_name": "Lasiurus insularis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 11352, + "scientific_name": "Lasiurus intermedius", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136627, + "scientific_name": "Lasiurus minor", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 136217, + "scientific_name": "Lasiurus pfeifferi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 88151061, + "scientific_name": "Lasiurus salinae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 11353, + "scientific_name": "Lasiurus seminolus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136690, + "scientific_name": "Lasiurus varius", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41532, + "scientific_name": "Lasiurus xanthinus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 11374, + "scientific_name": "Latidens salimalii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 11378, + "scientific_name": "Lavia frons", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 11385, + "scientific_name": "Leggadina forresti", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 11384, + "scientific_name": "Leggadina lakedownensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 11387, + "scientific_name": "Leimacomys buettneri", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 42624, + "scientific_name": "Lemmiscus curtatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 11480, + "scientific_name": "Lemmus amurensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 11481, + "scientific_name": "Lemmus lemmus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136303, + "scientific_name": "Lemmus portenkoi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 11482, + "scientific_name": "Lemmus sibiricus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136712, + "scientific_name": "Lemmus trimucronatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 11487, + "scientific_name": "Lemniscomys barbarus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 11488, + "scientific_name": "Lemniscomys bellieri", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 11489, + "scientific_name": "Lemniscomys griselda", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 11490, + "scientific_name": "Lemniscomys hoogstraali", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 11491, + "scientific_name": "Lemniscomys linulus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 11492, + "scientific_name": "Lemniscomys macculus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 11486, + "scientific_name": "Lemniscomys mittendorfi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 11493, + "scientific_name": "Lemniscomys rosalia", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 11494, + "scientific_name": "Lemniscomys roseveari", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 11495, + "scientific_name": "Lemniscomys striatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41212, + "scientific_name": "Lemniscomys zebra", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 11496, + "scientific_name": "Lemur catta", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 11498, + "scientific_name": "Lenomys meyeri", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 11499, + "scientific_name": "Lenothrix canus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 11500, + "scientific_name": "Lenoxus apicalis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19829, + "scientific_name": "Leontocebus cruzlimai", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 160885500, + "scientific_name": "Leontocebus fuscicollis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 43949, + "scientific_name": "Leontocebus fuscicollis ssp. avilapiresi", + "subspecies": "avilapiresi", + "rank": "ssp.", + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 43948, + "scientific_name": "Leontocebus fuscicollis ssp. fuscicollis", + "subspecies": "fuscicollis", + "rank": "ssp.", + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 172188235, + "scientific_name": "Leontocebus fuscicollis ssp. mura", + "subspecies": "mura", + "rank": "ssp.", + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 43951, + "scientific_name": "Leontocebus fuscicollis ssp. primitivus", + "subspecies": "primitivus", + "rank": "ssp.", + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 42693, + "scientific_name": "Leontocebus fuscus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 43952, + "scientific_name": "Leontocebus illigeri", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 43950, + "scientific_name": "Leontocebus lagonotus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19826, + "scientific_name": "Leontocebus leucogenys", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 39945, + "scientific_name": "Leontocebus nigricollis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 43947, + "scientific_name": "Leontocebus nigricollis ssp. graellsi", + "subspecies": "graellsi", + "rank": "ssp.", + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 19828, + "scientific_name": "Leontocebus nigricollis ssp. hernandezi", + "subspecies": "hernandezi", + "rank": "ssp.", + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 39946, + "scientific_name": "Leontocebus nigricollis ssp. nigricollis", + "subspecies": "nigricollis", + "rank": "ssp.", + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 43953, + "scientific_name": "Leontocebus nigrifrons", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19824, + "scientific_name": "Leontocebus tripartitus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 160939221, + "scientific_name": "Leontocebus weddelli", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19830, + "scientific_name": "Leontocebus weddelli ssp. crandalli", + "subspecies": "crandalli", + "rank": "ssp.", + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 43955, + "scientific_name": "Leontocebus weddelli ssp. melanoleucus", + "subspecies": "melanoleucus", + "rank": "ssp.", + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 43954, + "scientific_name": "Leontocebus weddelli ssp. weddelli", + "subspecies": "weddelli", + "rank": "ssp.", + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 11503, + "scientific_name": "Leontopithecus caissara", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 40643, + "scientific_name": "Leontopithecus chrysomelas", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 11505, + "scientific_name": "Leontopithecus chrysopygus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 11506, + "scientific_name": "Leontopithecus rosalia", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 15309, + "scientific_name": "Leopardus colocolo", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 15310, + "scientific_name": "Leopardus geoffroyi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 15311, + "scientific_name": "Leopardus guigna", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 54010476, + "scientific_name": "Leopardus guttulus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 15452, + "scientific_name": "Leopardus jacobita", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 11509, + "scientific_name": "Leopardus pardalis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 54012637, + "scientific_name": "Leopardus tigrinus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 11511, + "scientific_name": "Leopardus wiedii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 136691, + "scientific_name": "Leopoldamys ciliatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 45955979, + "scientific_name": "Leopoldamys diwangkarai", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 11518, + "scientific_name": "Leopoldamys edwardsi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136418, + "scientific_name": "Leopoldamys milleti", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 11519, + "scientific_name": "Leopoldamys neilli", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 11520, + "scientific_name": "Leopoldamys sabanus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 11521, + "scientific_name": "Leopoldamys siporanus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 136376, + "scientific_name": "Lepilemur aeeclis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 136843, + "scientific_name": "Lepilemur ahmansoni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 136731, + "scientific_name": "Lepilemur ankaranensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 136662, + "scientific_name": "Lepilemur betsileo", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 210368667, + "scientific_name": "Lepilemur dorsalis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 11617, + "scientific_name": "Lepilemur edwardsi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 136263, + "scientific_name": "Lepilemur fleuretae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 136771, + "scientific_name": "Lepilemur grewcockorum", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 16971489, + "scientific_name": "Lepilemur hollandorum", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 136761, + "scientific_name": "Lepilemur hubbardorum", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 136491, + "scientific_name": "Lepilemur jamesorum", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 11618, + "scientific_name": "Lepilemur leucopus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 11619, + "scientific_name": "Lepilemur microdon", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 136609, + "scientific_name": "Lepilemur milanoii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 11620, + "scientific_name": "Lepilemur mustelinus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 136796, + "scientific_name": "Lepilemur otto", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 136677, + "scientific_name": "Lepilemur petteri", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 136254, + "scientific_name": "Lepilemur randrianasoloi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 11621, + "scientific_name": "Lepilemur ruficaudatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 136645, + "scientific_name": "Lepilemur sahamalaza", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 16971518, + "scientific_name": "Lepilemur scottorum", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 136707, + "scientific_name": "Lepilemur seali", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 11622, + "scientific_name": "Lepilemur septentrionalis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 136709, + "scientific_name": "Lepilemur tymerlachsoni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 136311, + "scientific_name": "Lepilemur wrightae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 11633, + "scientific_name": "Leporillus apicalis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EX" + }, + { + "taxonid": 11634, + "scientific_name": "Leporillus conditor", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 11638, + "scientific_name": "Leptailurus serval", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 47983297, + "scientific_name": "Leptomys arfakensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 11691, + "scientific_name": "Leptomys elegans", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 11692, + "scientific_name": "Leptomys ernstmayri", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 47989221, + "scientific_name": "Leptomys paulus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 11693, + "scientific_name": "Leptomys signatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 11696, + "scientific_name": "Leptonychotes weddellii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 11699, + "scientific_name": "Leptonycteris curasoae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 11697, + "scientific_name": "Leptonycteris nivalis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 136659, + "scientific_name": "Leptonycteris yerbabuenae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 41272, + "scientific_name": "Lepus alleni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41273, + "scientific_name": "Lepus americanus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41274, + "scientific_name": "Lepus arcticus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41275, + "scientific_name": "Lepus brachyurus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41276, + "scientific_name": "Lepus californicus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 11792, + "scientific_name": "Lepus callotis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 41277, + "scientific_name": "Lepus capensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 11797, + "scientific_name": "Lepus castroviejoi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 41278, + "scientific_name": "Lepus comus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41279, + "scientific_name": "Lepus coreanus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41305, + "scientific_name": "Lepus corsicanus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 41280, + "scientific_name": "Lepus europaeus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 11798, + "scientific_name": "Lepus fagani", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 11790, + "scientific_name": "Lepus flavigularis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 41306, + "scientific_name": "Lepus granatensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41289, + "scientific_name": "Lepus habessinicus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 11793, + "scientific_name": "Lepus hainanus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 11794, + "scientific_name": "Lepus insularis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 41281, + "scientific_name": "Lepus mandshuricus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41282, + "scientific_name": "Lepus nigricollis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41283, + "scientific_name": "Lepus oiostolus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 11795, + "scientific_name": "Lepus othus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41284, + "scientific_name": "Lepus peguensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41285, + "scientific_name": "Lepus saxatilis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41286, + "scientific_name": "Lepus sinensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41287, + "scientific_name": "Lepus starcki", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41307, + "scientific_name": "Lepus tibetanus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 11791, + "scientific_name": "Lepus timidus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41308, + "scientific_name": "Lepus tolai", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41288, + "scientific_name": "Lepus townsendii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41879, + "scientific_name": "Lepus victoriae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 11796, + "scientific_name": "Lepus yarkandensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 11856, + "scientific_name": "Lestodelphys halli", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41507, + "scientific_name": "Lestoros inca", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 11933, + "scientific_name": "Liberiictis kuhni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 88120307, + "scientific_name": "Lichonycteris degener", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 88120245, + "scientific_name": "Lichonycteris obscura", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 11979, + "scientific_name": "Limnogale mergulus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 136286, + "scientific_name": "Limnomys bryophilus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 11980, + "scientific_name": "Limnomys sibuanus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 12078, + "scientific_name": "Lionycteris spurrelli", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 12119, + "scientific_name": "Lipotes vexillifer", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 12125, + "scientific_name": "Lissodelphis borealis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 12126, + "scientific_name": "Lissodelphis peronii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 44698, + "scientific_name": "Lissonycteris angolensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 12142, + "scientific_name": "Litocranius walleri", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 12246, + "scientific_name": "Lobodon carcinophaga", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 12263, + "scientific_name": "Lonchophylla bokermanni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 88149262, + "scientific_name": "Lonchophylla cadenai", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 136348, + "scientific_name": "Lonchophylla chocoana", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 136706, + "scientific_name": "Lonchophylla concava", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 12264, + "scientific_name": "Lonchophylla dekeyseri", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 88150313, + "scientific_name": "Lonchophylla fornicata", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 12265, + "scientific_name": "Lonchophylla handleyi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 12266, + "scientific_name": "Lonchophylla hesperia", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 12267, + "scientific_name": "Lonchophylla mordax", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 136735, + "scientific_name": "Lonchophylla orcesi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 88150966, + "scientific_name": "Lonchophylla orienticollina", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 88149229, + "scientific_name": "Lonchophylla pattoni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 88150984, + "scientific_name": "Lonchophylla peracchii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 12268, + "scientific_name": "Lonchophylla robusta", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 12269, + "scientific_name": "Lonchophylla thomasi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 12270, + "scientific_name": "Lonchorhina aurita", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 12271, + "scientific_name": "Lonchorhina fernandezi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 40027, + "scientific_name": "Lonchorhina inusitata", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 12272, + "scientific_name": "Lonchorhina marinkellei", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 12273, + "scientific_name": "Lonchorhina orinocensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 12274, + "scientific_name": "Lonchothrix emiliae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 12302, + "scientific_name": "Lontra canadensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 12303, + "scientific_name": "Lontra felina", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 12304, + "scientific_name": "Lontra longicaudis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 12305, + "scientific_name": "Lontra provocax", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 12308, + "scientific_name": "Lophiomys imhausi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 12309, + "scientific_name": "Lophocebus albigena", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 95587156, + "scientific_name": "Lophocebus albigena ssp. albigena", + "subspecies": "albigena", + "rank": "ssp.", + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 92248915, + "scientific_name": "Lophocebus albigena ssp. johnstoni", + "subspecies": "johnstoni", + "rank": "ssp.", + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 92248950, + "scientific_name": "Lophocebus albigena ssp. osmani", + "subspecies": "osmani", + "rank": "ssp.", + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 92248984, + "scientific_name": "Lophocebus albigena ssp. ugandae", + "subspecies": "ugandae", + "rank": "ssp.", + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 12310, + "scientific_name": "Lophocebus aterrimus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 136916, + "scientific_name": "Lophocebus aterrimus ssp. aterrimus", + "subspecies": "aterrimus", + "rank": "ssp.", + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 12311, + "scientific_name": "Lophocebus aterrimus ssp. opdenboschi", + "subspecies": "opdenboschi", + "rank": "ssp.", + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 21984, + "scientific_name": "Lophostoma brasiliense", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 99783878, + "scientific_name": "Lophostoma carrikeri", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 21986, + "scientific_name": "Lophostoma evotis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 88149216, + "scientific_name": "Lophostoma kalkoae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 88149174, + "scientific_name": "Lophostoma occidentalis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 21987, + "scientific_name": "Lophostoma schulzi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 88149202, + "scientific_name": "Lophostoma silvicolum", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 45058, + "scientific_name": "Lophuromys brevicaudus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 47992974, + "scientific_name": "Lophuromys chercherensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 45057, + "scientific_name": "Lophuromys chrysopus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 12349, + "scientific_name": "Lophuromys cinereus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 45056, + "scientific_name": "Lophuromys dieterleni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 136619, + "scientific_name": "Lophuromys eisentrauti", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 47990318, + "scientific_name": "Lophuromys flavopunctatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 45059, + "scientific_name": "Lophuromys huttereri", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 47998388, + "scientific_name": "Lophuromys kilonzoi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 12354, + "scientific_name": "Lophuromys luteogaster", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 47997166, + "scientific_name": "Lophuromys machangui", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 47997538, + "scientific_name": "Lophuromys makundii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 12350, + "scientific_name": "Lophuromys medicaudatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 12351, + "scientific_name": "Lophuromys melanonyx", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 47992929, + "scientific_name": "Lophuromys menageshae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 12355, + "scientific_name": "Lophuromys nudicaudus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 47992992, + "scientific_name": "Lophuromys pseudosikapusi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 12352, + "scientific_name": "Lophuromys rahmi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 45055, + "scientific_name": "Lophuromys roseveari", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 47997414, + "scientific_name": "Lophuromys sabunii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 12356, + "scientific_name": "Lophuromys sikapusi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 47992103, + "scientific_name": "Lophuromys simensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 47996434, + "scientific_name": "Lophuromys stanleyi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 12357, + "scientific_name": "Lophuromys woosnami", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 12361, + "scientific_name": "Lorentzimys nouhuysi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 44722, + "scientific_name": "Loris lydekkerianus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 44718, + "scientific_name": "Loris lydekkerianus ssp. grandis", + "subspecies": "grandis", + "rank": "ssp.", + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 44719, + "scientific_name": "Loris lydekkerianus ssp. lydekkerianus", + "subspecies": "lydekkerianus", + "rank": "ssp.", + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 44720, + "scientific_name": "Loris lydekkerianus ssp. malabaricus", + "subspecies": "malabaricus", + "rank": "ssp.", + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 44721, + "scientific_name": "Loris lydekkerianus ssp. nordicus", + "subspecies": "nordicus", + "rank": "ssp.", + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 163020804, + "scientific_name": "Loris lydekkerianus ssp. uva", + "subspecies": "uva", + "rank": "ssp.", + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 12375, + "scientific_name": "Loris tardigradus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 39756, + "scientific_name": "Loris tardigradus ssp. nycticeboides", + "subspecies": "nycticeboides", + "rank": "ssp.", + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 163021284, + "scientific_name": "Loris tardigradus ssp. parvus", + "subspecies": "parvus", + "rank": "ssp.", + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 39757, + "scientific_name": "Loris tardigradus ssp. tardigradus", + "subspecies": "tardigradus", + "rank": "ssp.", + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 181008073, + "scientific_name": "Loxodonta africana", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 181007989, + "scientific_name": "Loxodonta cyclotis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 2396, + "scientific_name": "Loxodontomys micropus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 29434, + "scientific_name": "Loxodontomys pikumche", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 10219, + "scientific_name": "Lundomys molitor", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 12419, + "scientific_name": "Lutra lutra", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 12421, + "scientific_name": "Lutra sumatrana", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 40503, + "scientific_name": "Lutreolina crassicaudata", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 95740145, + "scientific_name": "Lutreolina massoia", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 12427, + "scientific_name": "Lutrogale perspicillata", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 6929, + "scientific_name": "Lycalopex culpaeus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41586, + "scientific_name": "Lycalopex fulvipes", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 6927, + "scientific_name": "Lycalopex griseus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 6928, + "scientific_name": "Lycalopex gymnocercus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 6925, + "scientific_name": "Lycalopex sechurae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 6926, + "scientific_name": "Lycalopex vetulus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 12436, + "scientific_name": "Lycaon pictus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 16991111, + "scientific_name": "Lycaon pictus North Africa subpopulation", + "subspecies": null, + "rank": null, + "subpopulation": "North Africa subpopulation", + "category": "CR" + }, + { + "taxonid": 16991108, + "scientific_name": "Lycaon pictus West Africa subpopulation", + "subspecies": null, + "rank": null, + "subpopulation": "West Africa subpopulation", + "category": "CR" + }, + { + "taxonid": 41647, + "scientific_name": "Lyncodon patagonicus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 12518, + "scientific_name": "Lynx canadensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 12519, + "scientific_name": "Lynx lynx", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 68986842, + "scientific_name": "Lynx lynx ssp. balcanicus", + "subspecies": "balcanicus", + "rank": "ssp.", + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 12520, + "scientific_name": "Lynx pardinus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 12521, + "scientific_name": "Lynx rufus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 12938, + "scientific_name": "Lyroderma lyra", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 12548, + "scientific_name": "Macaca arctoides", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 12549, + "scientific_name": "Macaca assamensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 39766, + "scientific_name": "Macaca assamensis ssp. assamensis", + "subspecies": "assamensis", + "rank": "ssp.", + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 39767, + "scientific_name": "Macaca assamensis ssp. pelops", + "subspecies": "pelops", + "rank": "ssp.", + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 12569, + "scientific_name": "Macaca brunnescens", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 12550, + "scientific_name": "Macaca cyclopis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 12551, + "scientific_name": "Macaca fascicularis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 39769, + "scientific_name": "Macaca fascicularis ssp. atriceps", + "subspecies": "atriceps", + "rank": "ssp.", + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 39770, + "scientific_name": "Macaca fascicularis ssp. aurea", + "subspecies": "aurea", + "rank": "ssp.", + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 39785, + "scientific_name": "Macaca fascicularis ssp. condorensis", + "subspecies": "condorensis", + "rank": "ssp.", + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 195351957, + "scientific_name": "Macaca fascicularis ssp. fascicularis", + "subspecies": "fascicularis", + "rank": "ssp.", + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 39786, + "scientific_name": "Macaca fascicularis ssp. fusca", + "subspecies": "fusca", + "rank": "ssp.", + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 39787, + "scientific_name": "Macaca fascicularis ssp. karimondjawae", + "subspecies": "karimondjawae", + "rank": "ssp.", + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 39788, + "scientific_name": "Macaca fascicularis ssp. lasiae", + "subspecies": "lasiae", + "rank": "ssp.", + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 39790, + "scientific_name": "Macaca fascicularis ssp. tua", + "subspecies": "tua", + "rank": "ssp.", + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 39791, + "scientific_name": "Macaca fascicularis ssp. umbrosa", + "subspecies": "umbrosa", + "rank": "ssp.", + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 12552, + "scientific_name": "Macaca fuscata", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 39909, + "scientific_name": "Macaca fuscata ssp. fuscata", + "subspecies": "fuscata", + "rank": "ssp.", + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 12565, + "scientific_name": "Macaca fuscata ssp. yakui", + "subspecies": "yakui", + "rank": "ssp.", + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 12570, + "scientific_name": "Macaca hecki", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 39792, + "scientific_name": "Macaca leonina", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 205889816, + "scientific_name": "Macaca leucogenys", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 12553, + "scientific_name": "Macaca maura", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 12554, + "scientific_name": "Macaca mulatta", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136569, + "scientific_name": "Macaca munzala", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 12555, + "scientific_name": "Macaca nemestrina", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 12556, + "scientific_name": "Macaca nigra", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 12568, + "scientific_name": "Macaca nigrescens", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 39793, + "scientific_name": "Macaca ochreata", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 39794, + "scientific_name": "Macaca pagensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 12558, + "scientific_name": "Macaca radiata", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 39797, + "scientific_name": "Macaca radiata ssp. diluta", + "subspecies": "diluta", + "rank": "ssp.", + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 39796, + "scientific_name": "Macaca radiata ssp. radiata", + "subspecies": "radiata", + "rank": "ssp.", + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 39795, + "scientific_name": "Macaca siberu", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 12559, + "scientific_name": "Macaca silenus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 12560, + "scientific_name": "Macaca sinica", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 39799, + "scientific_name": "Macaca sinica ssp. aurifrons", + "subspecies": "aurifrons", + "rank": "ssp.", + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 39800, + "scientific_name": "Macaca sinica ssp. opisthomelas", + "subspecies": "opisthomelas", + "rank": "ssp.", + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 39798, + "scientific_name": "Macaca sinica ssp. sinica", + "subspecies": "sinica", + "rank": "ssp.", + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 12561, + "scientific_name": "Macaca sylvanus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 12562, + "scientific_name": "Macaca thibetana", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 12563, + "scientific_name": "Macaca tonkeana", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 12590, + "scientific_name": "Macroderma gigas", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 12592, + "scientific_name": "Macrogalidia musschenbroekii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 12594, + "scientific_name": "Macroglossus minimus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 12595, + "scientific_name": "Macroglossus sobrinus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 10120, + "scientific_name": "Macronycteris commersoni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 44687, + "scientific_name": "Macronycteris gigas", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 44689, + "scientific_name": "Macronycteris thomensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 135485, + "scientific_name": "Macronycteris vittatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 12615, + "scientific_name": "Macrophyllum macrophyllum", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 40560, + "scientific_name": "Macropus agilis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 40561, + "scientific_name": "Macropus antilopinus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 12620, + "scientific_name": "Macropus bernardus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 40562, + "scientific_name": "Macropus dorsalis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41512, + "scientific_name": "Macropus eugenii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 40563, + "scientific_name": "Macropus fuliginosus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41513, + "scientific_name": "Macropus giganteus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 12625, + "scientific_name": "Macropus greyi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EX" + }, + { + "taxonid": 12626, + "scientific_name": "Macropus irma", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 12627, + "scientific_name": "Macropus parma", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 40564, + "scientific_name": "Macropus parryi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 40565, + "scientific_name": "Macropus robustus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 40566, + "scientific_name": "Macropus rufogriseus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 40567, + "scientific_name": "Macropus rufus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 45369877, + "scientific_name": "Macroscelides flavicaudatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 45434566, + "scientific_name": "Macroscelides micus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 45369602, + "scientific_name": "Macroscelides proboscideus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 12647, + "scientific_name": "Macrotarsomys bastardi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 12646, + "scientific_name": "Macrotarsomys ingens", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 136510, + "scientific_name": "Macrotarsomys petteri", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 12650, + "scientific_name": "Macrotis lagotis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 12651, + "scientific_name": "Macrotis leucura", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EX" + }, + { + "taxonid": 12652, + "scientific_name": "Macrotus californicus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 12653, + "scientific_name": "Macrotus waterhousii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 12655, + "scientific_name": "Macruromys elegans", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 12656, + "scientific_name": "Macruromys major", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 12669, + "scientific_name": "Madoqua guentheri", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 12670, + "scientific_name": "Madoqua kirkii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 12667, + "scientific_name": "Madoqua piacentinii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 12668, + "scientific_name": "Madoqua saltiana", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 5512, + "scientific_name": "Madromys blanfordi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13233, + "scientific_name": "Makalata didelphoides", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 6983, + "scientific_name": "Makalata macrura", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13236, + "scientific_name": "Makalata obscura", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 12702, + "scientific_name": "Malacomys cansdalei", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 12703, + "scientific_name": "Malacomys edwardsi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 12704, + "scientific_name": "Malacomys longipes", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 12712, + "scientific_name": "Malacothrix typica", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 12721, + "scientific_name": "Mallomys aroaensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 12722, + "scientific_name": "Mallomys gunung", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 12723, + "scientific_name": "Mallomys istapantap", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 12724, + "scientific_name": "Mallomys rothschildi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13122, + "scientific_name": "Mammelomys lanosus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13130, + "scientific_name": "Mammelomys rattoides", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 12753, + "scientific_name": "Mandrillus leucophaeus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 12756, + "scientific_name": "Mandrillus leucophaeus ssp. leucophaeus", + "subspecies": "leucophaeus", + "rank": "ssp.", + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 12755, + "scientific_name": "Mandrillus leucophaeus ssp. poensis", + "subspecies": "poensis", + "rank": "ssp.", + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 12754, + "scientific_name": "Mandrillus sphinx", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 12761, + "scientific_name": "Manis crassicaudata", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 136497, + "scientific_name": "Manis culionensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 12763, + "scientific_name": "Manis javanica", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 12764, + "scientific_name": "Manis pentadactyla", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 12795, + "scientific_name": "Margaretamys beccarii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 45957342, + "scientific_name": "Margaretamys christinae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 12796, + "scientific_name": "Margaretamys elegans", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 12797, + "scientific_name": "Margaretamys parvus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 13296, + "scientific_name": "Marmosa alstoni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 12812, + "scientific_name": "Marmosa andersoni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 13297, + "scientific_name": "Marmosa constantiae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 40510, + "scientific_name": "Marmosa demerarae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 12814, + "scientific_name": "Marmosa lepida", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 40504, + "scientific_name": "Marmosa mexicana", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 40505, + "scientific_name": "Marmosa murina", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136844, + "scientific_name": "Marmosa paraguayana", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136244, + "scientific_name": "Marmosa phaea", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 136508, + "scientific_name": "Marmosa quichua", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 40511, + "scientific_name": "Marmosa regina", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 40506, + "scientific_name": "Marmosa robinsoni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 40507, + "scientific_name": "Marmosa rubra", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 12816, + "scientific_name": "Marmosa tyleriana", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 12815, + "scientific_name": "Marmosa xerophila", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 136296, + "scientific_name": "Marmosops bishopi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 89333777, + "scientific_name": "Marmosops caucae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 12817, + "scientific_name": "Marmosops cracens", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 136803, + "scientific_name": "Marmosops creightoni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 12819, + "scientific_name": "Marmosops fuscatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 12820, + "scientific_name": "Marmosops handleyi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 89333331, + "scientific_name": "Marmosops impavidus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 12822, + "scientific_name": "Marmosops incanus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 12823, + "scientific_name": "Marmosops invictus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136364, + "scientific_name": "Marmosops juninensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 136830, + "scientific_name": "Marmosops neblina", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 40508, + "scientific_name": "Marmosops noctivagus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136842, + "scientific_name": "Marmosops ocellatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 51221900, + "scientific_name": "Marmosops pakaraimae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 12824, + "scientific_name": "Marmosops parvidens", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136278, + "scientific_name": "Marmosops paulensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136573, + "scientific_name": "Marmosops pinheiroi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 12829, + "scientific_name": "Marmota baibacina", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 12830, + "scientific_name": "Marmota bobak", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 42455, + "scientific_name": "Marmota broweri", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 42456, + "scientific_name": "Marmota caligata", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 12831, + "scientific_name": "Marmota camtschatica", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 12825, + "scientific_name": "Marmota caudata", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 42457, + "scientific_name": "Marmota flaviventris", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 12826, + "scientific_name": "Marmota himalayana", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 12835, + "scientific_name": "Marmota marmota", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 12827, + "scientific_name": "Marmota menzbieri", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 42458, + "scientific_name": "Marmota monax", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 42459, + "scientific_name": "Marmota olympus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 12832, + "scientific_name": "Marmota sibirica", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 12828, + "scientific_name": "Marmota vancouverensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 41648, + "scientific_name": "Martes americana", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41649, + "scientific_name": "Martes flavigula", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 29672, + "scientific_name": "Martes foina", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 12847, + "scientific_name": "Martes gwatkinsii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 12848, + "scientific_name": "Martes martes", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41650, + "scientific_name": "Martes melampus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41651, + "scientific_name": "Martes pennanti", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41652, + "scientific_name": "Martes zibellina", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 12855, + "scientific_name": "Massoutiera mzabi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 18563, + "scientific_name": "Mastacomys fuscus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 45060, + "scientific_name": "Mastomys awashensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 12865, + "scientific_name": "Mastomys coucha", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 12866, + "scientific_name": "Mastomys erythroleucus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 45061, + "scientific_name": "Mastomys huberti", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 45092, + "scientific_name": "Mastomys kollmannspergeri", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 12868, + "scientific_name": "Mastomys natalensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 12863, + "scientific_name": "Mastomys pernanus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 12869, + "scientific_name": "Mastomys shortridgei", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 12891, + "scientific_name": "Maxomys alticola", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 12892, + "scientific_name": "Maxomys baeodon", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 12893, + "scientific_name": "Maxomys bartelsii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 12894, + "scientific_name": "Maxomys dollmani", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 12895, + "scientific_name": "Maxomys hellwaldii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 12896, + "scientific_name": "Maxomys hylomyoides", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 12897, + "scientific_name": "Maxomys inas", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 12898, + "scientific_name": "Maxomys inflatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 12899, + "scientific_name": "Maxomys moi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 12900, + "scientific_name": "Maxomys musschenbroekii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 12901, + "scientific_name": "Maxomys ochraceiventer", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 12902, + "scientific_name": "Maxomys pagensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 12903, + "scientific_name": "Maxomys panglima", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 12904, + "scientific_name": "Maxomys rajah", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 12905, + "scientific_name": "Maxomys surifer", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 45958508, + "scientific_name": "Maxomys tajuddinii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 12906, + "scientific_name": "Maxomys wattsi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 12907, + "scientific_name": "Maxomys whiteheadi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 29619, + "scientific_name": "Mazama americana", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 41023, + "scientific_name": "Mazama bororo", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 136301, + "scientific_name": "Mazama bricenii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 12913, + "scientific_name": "Mazama chunyi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 29620, + "scientific_name": "Mazama gouazoubira", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 29621, + "scientific_name": "Mazama nana", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 136708, + "scientific_name": "Mazama nemorivaga", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 29622, + "scientific_name": "Mazama pandora", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 12914, + "scientific_name": "Mazama rufina", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 136290, + "scientific_name": "Mazama temama", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 12937, + "scientific_name": "Megadendromus nikolausi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 12939, + "scientific_name": "Megaderma spasma", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 12940, + "scientific_name": "Megadontomys cryophilus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 12941, + "scientific_name": "Megadontomys nelsoni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 12942, + "scientific_name": "Megadontomys thomasi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 12946, + "scientific_name": "Megaerops ecaudatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 12945, + "scientific_name": "Megaerops kusnotoi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 12947, + "scientific_name": "Megaerops niphanae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 12948, + "scientific_name": "Megaerops wetmorei", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 84459322, + "scientific_name": "Megaloglossus azagnyi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 84462869, + "scientific_name": "Megaloglossus woermanni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 12980, + "scientific_name": "Megalomys desmarestii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EX" + }, + { + "taxonid": 12981, + "scientific_name": "Megalomys luciae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EX" + }, + { + "taxonid": 136657, + "scientific_name": "Megaoryzomys curioi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EX" + }, + { + "taxonid": 13006, + "scientific_name": "Megaptera novaeangliae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 132835, + "scientific_name": "Megaptera novaeangliae Arabian Sea subpopulation", + "subspecies": null, + "rank": null, + "subpopulation": "Arabian Sea subpopulation", + "category": "EN" + }, + { + "taxonid": 132832, + "scientific_name": "Megaptera novaeangliae Oceania subpopulation", + "subspecies": null, + "rank": null, + "subpopulation": "Oceania subpopulation", + "category": "EN" + }, + { + "taxonid": 41454, + "scientific_name": "Megasorex gigas", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13046, + "scientific_name": "Melanomys caliginosus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13047, + "scientific_name": "Melanomys robustulus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13048, + "scientific_name": "Melanomys zunigae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 13079, + "scientific_name": "Melasmothrix naso", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 136242, + "scientific_name": "Meles anakuma", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136385, + "scientific_name": "Meles leucurus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 29673, + "scientific_name": "Meles meles", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41629, + "scientific_name": "Mellivora capensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 68369199, + "scientific_name": "Melogale cucphuongensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 13110, + "scientific_name": "Melogale everetti", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 41626, + "scientific_name": "Melogale moschata", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41697, + "scientific_name": "Melogale orientalis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41627, + "scientific_name": "Melogale personata", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13114, + "scientific_name": "Melomys aerosus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 136431, + "scientific_name": "Melomys arcium", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 136604, + "scientific_name": "Melomys bannisteri", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 13115, + "scientific_name": "Melomys bougainville", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 13116, + "scientific_name": "Melomys burtoni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13117, + "scientific_name": "Melomys capensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136715, + "scientific_name": "Melomys caurinus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 13118, + "scientific_name": "Melomys cervinipes", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136467, + "scientific_name": "Melomys cooperae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 136818, + "scientific_name": "Melomys dollmani", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13120, + "scientific_name": "Melomys fraterculus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 136719, + "scientific_name": "Melomys frigicola", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136673, + "scientific_name": "Melomys fulgens", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 136482, + "scientific_name": "Melomys howi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 13123, + "scientific_name": "Melomys leucogaster", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136764, + "scientific_name": "Melomys lutillus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136522, + "scientific_name": "Melomys matambuai", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 13128, + "scientific_name": "Melomys obiensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136359, + "scientific_name": "Melomys paveli", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 13132, + "scientific_name": "Melomys rubicola", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EX" + }, + { + "taxonid": 13133, + "scientific_name": "Melomys rufescens", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136320, + "scientific_name": "Melomys talaudium", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 13141, + "scientific_name": "Melonycteris fardoulisi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 13139, + "scientific_name": "Melonycteris melanops", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13140, + "scientific_name": "Melonycteris woodfordi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13143, + "scientific_name": "Melursus ursinus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 13144, + "scientific_name": "Menetes berdmorei", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41634, + "scientific_name": "Mephitis macroura", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41635, + "scientific_name": "Mephitis mephitis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13159, + "scientific_name": "Meriones arimalius", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13160, + "scientific_name": "Meriones chengi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13161, + "scientific_name": "Meriones crassus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13162, + "scientific_name": "Meriones dahli", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 136460, + "scientific_name": "Meriones grandis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13163, + "scientific_name": "Meriones hurrianae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13164, + "scientific_name": "Meriones libycus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13165, + "scientific_name": "Meriones meridianus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13166, + "scientific_name": "Meriones persicus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13167, + "scientific_name": "Meriones rex", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13168, + "scientific_name": "Meriones sacramenti", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 42666, + "scientific_name": "Meriones shawi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13169, + "scientific_name": "Meriones tamariscinus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13170, + "scientific_name": "Meriones tristrami", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13171, + "scientific_name": "Meriones unguiculatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13172, + "scientific_name": "Meriones vinogradovi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13173, + "scientific_name": "Meriones zarudnyi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 40612, + "scientific_name": "Mesechinus dauuricus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13209, + "scientific_name": "Mesechinus hughi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13211, + "scientific_name": "Mesembriomys gouldii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 13212, + "scientific_name": "Mesembriomys macrurus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 13215, + "scientific_name": "Mesocapromys angelcabrerai", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 13216, + "scientific_name": "Mesocapromys auritus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 13217, + "scientific_name": "Mesocapromys nanus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 13218, + "scientific_name": "Mesocapromys sanfelipensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 13219, + "scientific_name": "Mesocricetus auratus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 13220, + "scientific_name": "Mesocricetus brandti", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 13221, + "scientific_name": "Mesocricetus newtoni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 13222, + "scientific_name": "Mesocricetus raddei", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13234, + "scientific_name": "Mesomys hispidus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13235, + "scientific_name": "Mesomys leniceps", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 136284, + "scientific_name": "Mesomys occultus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13237, + "scientific_name": "Mesomys stimulax", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13240, + "scientific_name": "Mesophylla macconnelli", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13241, + "scientific_name": "Mesoplodon bidens", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13242, + "scientific_name": "Mesoplodon bowdoini", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 13243, + "scientific_name": "Mesoplodon carlhubbsi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 13244, + "scientific_name": "Mesoplodon densirostris", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13245, + "scientific_name": "Mesoplodon europaeus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 127827012, + "scientific_name": "Mesoplodon ginkgodens", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 13247, + "scientific_name": "Mesoplodon grayi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13248, + "scientific_name": "Mesoplodon hectori", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 127826787, + "scientific_name": "Mesoplodon hotaula", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 13249, + "scientific_name": "Mesoplodon layardii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13250, + "scientific_name": "Mesoplodon mirus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41759, + "scientific_name": "Mesoplodon perrini", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 13251, + "scientific_name": "Mesoplodon peruvianus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13252, + "scientific_name": "Mesoplodon stejnegeri", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 41760, + "scientific_name": "Mesoplodon traversii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 40509, + "scientific_name": "Metachirus nudicaudatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 570, + "scientific_name": "Micaelamys granti", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 573, + "scientific_name": "Micaelamys namaquensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41580, + "scientific_name": "Mico acariensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41520, + "scientific_name": "Mico argentatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 39910, + "scientific_name": "Mico chrysoleucos", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 42691, + "scientific_name": "Mico emiliae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41521, + "scientific_name": "Mico humeralifer", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 39911, + "scientific_name": "Mico intermedius", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 39912, + "scientific_name": "Mico leucippe", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 39914, + "scientific_name": "Mico marcai", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 41583, + "scientific_name": "Mico mauesi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136294, + "scientific_name": "Mico melanurus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 172269376, + "scientific_name": "Mico munduruku", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 39913, + "scientific_name": "Mico nigriceps", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 136804, + "scientific_name": "Mico rondoni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 42692, + "scientific_name": "Mico saterei", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 210363264, + "scientific_name": "Mico schneideri", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 136468, + "scientific_name": "Microakodontomys transitorius", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 13319, + "scientific_name": "Microcavia australis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13320, + "scientific_name": "Microcavia niata", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13321, + "scientific_name": "Microcavia shiptoni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 16971390, + "scientific_name": "Microcebus arnholdi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 41573, + "scientific_name": "Microcebus berthae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 136537, + "scientific_name": "Microcebus bongolavensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 163314140, + "scientific_name": "Microcebus boraha", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 136347, + "scientific_name": "Microcebus danfossi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 163313085, + "scientific_name": "Microcebus ganzhorni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 16971461, + "scientific_name": "Microcebus gerpi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 136637, + "scientific_name": "Microcebus griseorufus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136458, + "scientific_name": "Microcebus jollyae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 196429436, + "scientific_name": "Microcebus jonahi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 210364856, + "scientific_name": "Microcebus lehilahytsara", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 16971425, + "scientific_name": "Microcebus macarthurii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 136206, + "scientific_name": "Microcebus mamiratra", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 163313848, + "scientific_name": "Microcebus manitatra", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 16971364, + "scientific_name": "Microcebus margotmarshae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 46251646, + "scientific_name": "Microcebus marohita", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 163314248, + "scientific_name": "Microcebus murinus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13325, + "scientific_name": "Microcebus myoxinus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 39751, + "scientific_name": "Microcebus ravelobensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 59544947, + "scientific_name": "Microcebus rufus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 41572, + "scientific_name": "Microcebus sambiranensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 163314538, + "scientific_name": "Microcebus simmonsi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 163024481, + "scientific_name": "Microcebus tanosi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 41571, + "scientific_name": "Microcebus tavaratra", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 13332, + "scientific_name": "Microdillus peeli", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 42606, + "scientific_name": "Microdipodops megacephalus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 42607, + "scientific_name": "Microdipodops pallidus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 54007828, + "scientific_name": "Microgale brevicaudata", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 40586, + "scientific_name": "Microgale cowani", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 40587, + "scientific_name": "Microgale dobsoni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 62012, + "scientific_name": "Microgale drouhardi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13356, + "scientific_name": "Microgale dryas", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 62013, + "scientific_name": "Microgale fotsifotsy", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13343, + "scientific_name": "Microgale gracilis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 54008309, + "scientific_name": "Microgale grandidieri", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 62014, + "scientific_name": "Microgale gymnorhyncha", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 62015, + "scientific_name": "Microgale jenkinsae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 136628, + "scientific_name": "Microgale jobihely", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 13344, + "scientific_name": "Microgale longicaudata", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 62016, + "scientific_name": "Microgale majori", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 29462, + "scientific_name": "Microgale monticola", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 62017, + "scientific_name": "Microgale nasoloi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 13349, + "scientific_name": "Microgale parvula", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13350, + "scientific_name": "Microgale principula", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41314, + "scientific_name": "Microgale pusilla", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 62018, + "scientific_name": "Microgale soricoides", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 62019, + "scientific_name": "Microgale taiva", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41315, + "scientific_name": "Microgale talazaci", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13355, + "scientific_name": "Microgale thomasi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 48004865, + "scientific_name": "Microhydromys argenteus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 48002425, + "scientific_name": "Microhydromys richardsoni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 13373, + "scientific_name": "Micromys minutus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 76776686, + "scientific_name": "Micronomus norfolkensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 40028, + "scientific_name": "Micronycteris brosseti", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 88120333, + "scientific_name": "Micronycteris buriri", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 88120398, + "scientific_name": "Micronycteris giovanniae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 13378, + "scientific_name": "Micronycteris hirsuta", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136207, + "scientific_name": "Micronycteris matses", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 13379, + "scientific_name": "Micronycteris megalotis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136424, + "scientific_name": "Micronycteris microtis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13380, + "scientific_name": "Micronycteris minuta", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 40029, + "scientific_name": "Micronycteris sanborni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13383, + "scientific_name": "Micronycteris schmidtorum", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 88132568, + "scientific_name": "Micronycteris yatesi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 136538, + "scientific_name": "Microperoryctes aplini", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 84783217, + "scientific_name": "Microperoryctes longicauda", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13389, + "scientific_name": "Microperoryctes murina", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 13390, + "scientific_name": "Microperoryctes papuensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13393, + "scientific_name": "Micropotamogale lamottei", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 13394, + "scientific_name": "Micropotamogale ruwenzorii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13401, + "scientific_name": "Micropteropus intermedius", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 13402, + "scientific_name": "Micropteropus pusillus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13407, + "scientific_name": "Microryzomys altissimus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13408, + "scientific_name": "Microryzomys minutus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13409, + "scientific_name": "Microsciurus alfari", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13410, + "scientific_name": "Microsciurus flaviventer", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13411, + "scientific_name": "Microsciurus mimulus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13412, + "scientific_name": "Microsciurus santanderensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 13425, + "scientific_name": "Microtus abbreviatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13426, + "scientific_name": "Microtus agrestis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136237, + "scientific_name": "Microtus anatolicus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 13488, + "scientific_name": "Microtus arvalis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13461, + "scientific_name": "Microtus bavaricus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 136536, + "scientific_name": "Microtus brachycercus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13417, + "scientific_name": "Microtus breweri", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 13418, + "scientific_name": "Microtus cabrerae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 13427, + "scientific_name": "Microtus californicus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 42625, + "scientific_name": "Microtus canicaudus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 42626, + "scientific_name": "Microtus chrotorrhinus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 23041, + "scientific_name": "Microtus clarkei", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13428, + "scientific_name": "Microtus daghestanicus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136535, + "scientific_name": "Microtus dogramacii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13493, + "scientific_name": "Microtus duodecimcostatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 112465222, + "scientific_name": "Microtus elbeyli", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 13429, + "scientific_name": "Microtus evoronensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 13462, + "scientific_name": "Microtus felteni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13430, + "scientific_name": "Microtus fortis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 39315, + "scientific_name": "Microtus gerbei", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13431, + "scientific_name": "Microtus gregalis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13432, + "scientific_name": "Microtus guatemalensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 13463, + "scientific_name": "Microtus guentheri", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13433, + "scientific_name": "Microtus hyperboreus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13438, + "scientific_name": "Microtus ilaeus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 112465090, + "scientific_name": "Microtus irani", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 13437, + "scientific_name": "Microtus kermanensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 23042, + "scientific_name": "Microtus kikuchii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13454, + "scientific_name": "Microtus levis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136498, + "scientific_name": "Microtus liechtensteini", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13440, + "scientific_name": "Microtus limnophilus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 42627, + "scientific_name": "Microtus longicaudus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13494, + "scientific_name": "Microtus lusitanicus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136354, + "scientific_name": "Microtus majori", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13442, + "scientific_name": "Microtus maximowiczii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13443, + "scientific_name": "Microtus mexicanus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13444, + "scientific_name": "Microtus middendorffii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 42629, + "scientific_name": "Microtus miurus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13445, + "scientific_name": "Microtus mongolicus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 42630, + "scientific_name": "Microtus montanus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13446, + "scientific_name": "Microtus montebelli", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13447, + "scientific_name": "Microtus mujanensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 13490, + "scientific_name": "Microtus multiplex", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13448, + "scientific_name": "Microtus nasarovi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 13449, + "scientific_name": "Microtus oaxacensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 42631, + "scientific_name": "Microtus ochrogaster", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13451, + "scientific_name": "Microtus oeconomus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 42632, + "scientific_name": "Microtus oregoni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136216, + "scientific_name": "Microtus paradoxus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13452, + "scientific_name": "Microtus pennsylvanicus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 42633, + "scientific_name": "Microtus pinetorum", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136565, + "scientific_name": "Microtus qazvinensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13453, + "scientific_name": "Microtus quasiater", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 42634, + "scientific_name": "Microtus richardsoni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13455, + "scientific_name": "Microtus sachalinensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13491, + "scientific_name": "Microtus savii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13456, + "scientific_name": "Microtus schelkovnikovi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136391, + "scientific_name": "Microtus schidlovskii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13458, + "scientific_name": "Microtus socialis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13489, + "scientific_name": "Microtus subterraneus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13464, + "scientific_name": "Microtus tatricus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13486, + "scientific_name": "Microtus thomasi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13487, + "scientific_name": "Microtus townsendii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13459, + "scientific_name": "Microtus transcaspicus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 42635, + "scientific_name": "Microtus umbrosus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 42628, + "scientific_name": "Microtus xanthognathus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13522, + "scientific_name": "Millardia gleadowi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13523, + "scientific_name": "Millardia kathleenae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13524, + "scientific_name": "Millardia kondana", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 13525, + "scientific_name": "Millardia meltada", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13556, + "scientific_name": "Mimetillus moloneyi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13559, + "scientific_name": "Mimon bennettii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136561, + "scientific_name": "Mimon cozumelae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 15597, + "scientific_name": "Mindomys hammondi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 81629770, + "scientific_name": "Miniopterus aelleni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 44859, + "scientific_name": "Miniopterus africanus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 81633128, + "scientific_name": "Miniopterus ambohitrensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13562, + "scientific_name": "Miniopterus australis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 81629758, + "scientific_name": "Miniopterus brachytragos", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 81633146, + "scientific_name": "Miniopterus egeri", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13563, + "scientific_name": "Miniopterus fraterculus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13564, + "scientific_name": "Miniopterus fuscus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 81633094, + "scientific_name": "Miniopterus gleni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 81633105, + "scientific_name": "Miniopterus griffithsi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 136752, + "scientific_name": "Miniopterus griveaudi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 13565, + "scientific_name": "Miniopterus inflatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136579, + "scientific_name": "Miniopterus macrocneme", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 81633156, + "scientific_name": "Miniopterus maghrebensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 13566, + "scientific_name": "Miniopterus magnater", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 81629764, + "scientific_name": "Miniopterus mahafaliensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 40039, + "scientific_name": "Miniopterus majori", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 81629742, + "scientific_name": "Miniopterus manavi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13567, + "scientific_name": "Miniopterus medius", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13568, + "scientific_name": "Miniopterus minor", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 44862, + "scientific_name": "Miniopterus natalensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136310, + "scientific_name": "Miniopterus newtoni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 81633088, + "scientific_name": "Miniopterus pallidus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 136233, + "scientific_name": "Miniopterus paululus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 81633135, + "scientific_name": "Miniopterus petersoni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 13569, + "scientific_name": "Miniopterus pusillus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13570, + "scientific_name": "Miniopterus robustior", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 81633057, + "scientific_name": "Miniopterus schreibersii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 136827, + "scientific_name": "Miniopterus shortridgei", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 136401, + "scientific_name": "Miniopterus sororculus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13571, + "scientific_name": "Miniopterus tristis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41570, + "scientific_name": "Miopithecus ogouensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 13572, + "scientific_name": "Miopithecus talapoin", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 18655, + "scientific_name": "Mirimiri acrodonta", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 13581, + "scientific_name": "Mirounga angustirostris", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13583, + "scientific_name": "Mirounga leonina", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13584, + "scientific_name": "Mirza coquereli", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 48266217, + "scientific_name": "Mirzamys louiseae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 48265836, + "scientific_name": "Mirzamys norahae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 136684, + "scientific_name": "Mirza zaza", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 13604, + "scientific_name": "Mogera etigo", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 41465, + "scientific_name": "Mogera imaizumii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41463, + "scientific_name": "Mogera insularis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41466, + "scientific_name": "Mogera robusta", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13603, + "scientific_name": "Mogera tokudae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 14563, + "scientific_name": "Mogera uchidai", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 41467, + "scientific_name": "Mogera wogura", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13638, + "scientific_name": "Molossops aequatorianus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 13640, + "scientific_name": "Molossops mattogrossensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13641, + "scientific_name": "Molossops neglectus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 13643, + "scientific_name": "Molossops temminckii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 88087329, + "scientific_name": "Molossus alvarezi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 13645, + "scientific_name": "Molossus aztecus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 88087507, + "scientific_name": "Molossus bondae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 102208365, + "scientific_name": "Molossus coibensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 88087340, + "scientific_name": "Molossus currentium", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13648, + "scientific_name": "Molossus molossus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13649, + "scientific_name": "Molossus pretiosus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13644, + "scientific_name": "Molossus rufus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13650, + "scientific_name": "Molossus sinaloae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13653, + "scientific_name": "Monachus monachus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 120868935, + "scientific_name": "Monachus monachus Eastern Mediterranean subpopulation", + "subspecies": null, + "rank": null, + "subpopulation": "Eastern Mediterranean subpopulation", + "category": "EN" + }, + { + "taxonid": 51343071, + "scientific_name": "Monodelphis adusta", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 96866849, + "scientific_name": "Monodelphis americana", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 40513, + "scientific_name": "Monodelphis brevicaudata", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13693, + "scientific_name": "Monodelphis dimidiata", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 40514, + "scientific_name": "Monodelphis domestica", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13694, + "scientific_name": "Monodelphis emiliae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 97249272, + "scientific_name": "Monodelphis glirina", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 199833, + "scientific_name": "Monodelphis handleyi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 13695, + "scientific_name": "Monodelphis iheringi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 13696, + "scientific_name": "Monodelphis kunsi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13698, + "scientific_name": "Monodelphis osgoodi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136516, + "scientific_name": "Monodelphis palliolata", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 51342998, + "scientific_name": "Monodelphis peruviana", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136392, + "scientific_name": "Monodelphis reigi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 136404, + "scientific_name": "Monodelphis ronaldi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 97249078, + "scientific_name": "Monodelphis scalops", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13703, + "scientific_name": "Monodelphis unistriata", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 13704, + "scientific_name": "Monodon monoceros", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13719, + "scientific_name": "Monophyllus plethodon", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13720, + "scientific_name": "Monophyllus redmani", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136539, + "scientific_name": "Monticolomys koopmani", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 67363010, + "scientific_name": "Mops bakarii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 13837, + "scientific_name": "Mops brachypterus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13838, + "scientific_name": "Mops condylurus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13839, + "scientific_name": "Mops congicus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13840, + "scientific_name": "Mops demonstrator", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 40038, + "scientific_name": "Mops leucogaster", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 40024, + "scientific_name": "Mops leucostigma", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13841, + "scientific_name": "Mops midas", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13842, + "scientific_name": "Mops mops", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 13843, + "scientific_name": "Mops nanulus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13844, + "scientific_name": "Mops niangarae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 13845, + "scientific_name": "Mops niveiventer", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13846, + "scientific_name": "Mops petersoni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 4318, + "scientific_name": "Mops pusillus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 13847, + "scientific_name": "Mops sarasinorum", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 13848, + "scientific_name": "Mops spurrelli", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13849, + "scientific_name": "Mops thersites", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13850, + "scientific_name": "Mops trevori", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 13877, + "scientific_name": "Mormoops blainvillei", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13878, + "scientific_name": "Mormoops megalophylla", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 71733227, + "scientific_name": "Mormopterus acetabulosus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 13881, + "scientific_name": "Mormopterus doriae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 71727235, + "scientific_name": "Mormopterus francoismoutoui", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13882, + "scientific_name": "Mormopterus jugularis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13883, + "scientific_name": "Mormopterus kalinowskii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13884, + "scientific_name": "Mormopterus minutus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 13887, + "scientific_name": "Mormopterus phrudus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 136585, + "scientific_name": "Moschiola indica", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136799, + "scientific_name": "Moschiola kathygre", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41779, + "scientific_name": "Moschiola meminna", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136643, + "scientific_name": "Moschus anhuiensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 13894, + "scientific_name": "Moschus berezovskii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 13895, + "scientific_name": "Moschus chrysogaster", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 136750, + "scientific_name": "Moschus cupreus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 13896, + "scientific_name": "Moschus fuscus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 13901, + "scientific_name": "Moschus leucogaster", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 13897, + "scientific_name": "Moschus moschiferus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 13904, + "scientific_name": "Mosia nigrescens", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13922, + "scientific_name": "Mungos gambianus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41621, + "scientific_name": "Mungos mungo", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13923, + "scientific_name": "Mungotictis decemlineata", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 42189, + "scientific_name": "Muntiacus atherodes", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 13924, + "scientific_name": "Muntiacus crinifrons", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 13927, + "scientific_name": "Muntiacus feae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 13926, + "scientific_name": "Muntiacus gongshanensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 136831, + "scientific_name": "Muntiacus montanus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 42190, + "scientific_name": "Muntiacus muntjak", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136293, + "scientific_name": "Muntiacus puhoatensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 136479, + "scientific_name": "Muntiacus putaoensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 42191, + "scientific_name": "Muntiacus reevesi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13928, + "scientific_name": "Muntiacus rooseveltorum", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 44704, + "scientific_name": "Muntiacus truongsonensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 136551, + "scientific_name": "Muntiacus vaginalis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 44703, + "scientific_name": "Muntiacus vuquangensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 1589, + "scientific_name": "Murexia habbema", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13930, + "scientific_name": "Murexia longicaudata", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 1591, + "scientific_name": "Murexia melanurus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 1587, + "scientific_name": "Murexia naso", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13931, + "scientific_name": "Murexia rothschildi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 13935, + "scientific_name": "Muriculus imberbis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13936, + "scientific_name": "Murina aenea", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 84487907, + "scientific_name": "Murina annamitica", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13937, + "scientific_name": "Murina aurata", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 84487939, + "scientific_name": "Murina balaensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 84488085, + "scientific_name": "Murina beelzebub", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 84488443, + "scientific_name": "Murina bicolor", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 84500863, + "scientific_name": "Murina chrysochaetes", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 154196798, + "scientific_name": "Murina cyclotis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 84557696, + "scientific_name": "Murina eleryi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 84561002, + "scientific_name": "Murina feae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 84500852, + "scientific_name": "Murina fionae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13939, + "scientific_name": "Murina florium", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13940, + "scientific_name": "Murina fusca", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 84500832, + "scientific_name": "Murina gracilis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 84562293, + "scientific_name": "Murina harpioloides", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 99712630, + "scientific_name": "Murina harrisoni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136409, + "scientific_name": "Murina hilgendorfi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13942, + "scientific_name": "Murina huttoni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 84547975, + "scientific_name": "Murina jaintiana", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 13943, + "scientific_name": "Murina leucogaster", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 84500876, + "scientific_name": "Murina lorelieae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 84548064, + "scientific_name": "Murina pluvialis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 13944, + "scientific_name": "Murina puta", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 84500842, + "scientific_name": "Murina recondita", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13945, + "scientific_name": "Murina rozendaali", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 29485, + "scientific_name": "Murina ryukyuana", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 84501698, + "scientific_name": "Murina shuipuensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 13947, + "scientific_name": "Murina suilla", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13948, + "scientific_name": "Murina tenebrosa", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 84560827, + "scientific_name": "Murina tubinaris", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 84562332, + "scientific_name": "Murina ussuriensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 84562267, + "scientific_name": "Murina walstoni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 13952, + "scientific_name": "Mus baoulei", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13953, + "scientific_name": "Mus booduga", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13954, + "scientific_name": "Mus bufo", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13955, + "scientific_name": "Mus callewaerti", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 13992, + "scientific_name": "Muscardinus avellanarius", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13956, + "scientific_name": "Mus caroli", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13957, + "scientific_name": "Mus cervicolor", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13958, + "scientific_name": "Mus cookii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13959, + "scientific_name": "Mus crociduroides", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 136641, + "scientific_name": "Mus cypriacus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13960, + "scientific_name": "Mus famulus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 13961, + "scientific_name": "Mus fernandoni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 136280, + "scientific_name": "Mus fragilicauda", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13962, + "scientific_name": "Mus goundae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 13963, + "scientific_name": "Mus haussa", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13964, + "scientific_name": "Mus indutus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13966, + "scientific_name": "Mus macedonicus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13967, + "scientific_name": "Mus mahomet", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13968, + "scientific_name": "Mus mattheyi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13969, + "scientific_name": "Mus mayori", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 13970, + "scientific_name": "Mus minutoides", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 135725, + "scientific_name": "Mus musculoides", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13972, + "scientific_name": "Mus musculus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13973, + "scientific_name": "Mus neavei", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 14003, + "scientific_name": "Musonycteris harrisoni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 13975, + "scientific_name": "Mus oubanguii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 13976, + "scientific_name": "Mus pahari", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13977, + "scientific_name": "Mus phillipsi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13978, + "scientific_name": "Mus platythrix", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13979, + "scientific_name": "Mus saxicola", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 112041975, + "scientific_name": "Musseromys anacuao", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 112041990, + "scientific_name": "Musseromys beneficus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 48298233, + "scientific_name": "Musseromys gulantang", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 112042008, + "scientific_name": "Musseromys inopinatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 13980, + "scientific_name": "Mus setulosus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13981, + "scientific_name": "Mus setzeri", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13982, + "scientific_name": "Mus shortridgei", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13983, + "scientific_name": "Mus sorella", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13984, + "scientific_name": "Mus spicilegus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13985, + "scientific_name": "Mus spretus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 14025, + "scientific_name": "Mustela africana", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41653, + "scientific_name": "Mustela altaica", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 29674, + "scientific_name": "Mustela erminea", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 29679, + "scientific_name": "Mustela eversmanii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 14026, + "scientific_name": "Mustela felipei", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 41654, + "scientific_name": "Mustela frenata", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41656, + "scientific_name": "Mustela itatsi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 41655, + "scientific_name": "Mustela kathiah", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 14018, + "scientific_name": "Mustela lutreola", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 14019, + "scientific_name": "Mustela lutreolina", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 14020, + "scientific_name": "Mustela nigripes", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 70207409, + "scientific_name": "Mustela nivalis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41657, + "scientific_name": "Mustela nudipes", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41658, + "scientific_name": "Mustela putorius", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 70207817, + "scientific_name": "Mustela russelliana", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 41659, + "scientific_name": "Mustela sibirica", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 14027, + "scientific_name": "Mustela strigidorsa", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41660, + "scientific_name": "Mustela subpalmata", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 70207949, + "scientific_name": "Mustela tonkinensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 13986, + "scientific_name": "Mus tenellus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13987, + "scientific_name": "Mus terricolor", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13988, + "scientific_name": "Mus triton", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13989, + "scientific_name": "Mus vulcani", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41628, + "scientific_name": "Mydaus javanensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 14055, + "scientific_name": "Mydaus marchei", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 42670, + "scientific_name": "Mylomys dybowskii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 45199, + "scientific_name": "Mylomys rex", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 14085, + "scientific_name": "Myocastor coypus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 16899, + "scientific_name": "Myodes andersoni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 42616, + "scientific_name": "Myodes californicus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 4972, + "scientific_name": "Myodes centralis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 42617, + "scientific_name": "Myodes gapperi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 4973, + "scientific_name": "Myodes glareolus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 7804, + "scientific_name": "Myodes regulus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 39591, + "scientific_name": "Myodes rex", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 4974, + "scientific_name": "Myodes rufocanus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 4975, + "scientific_name": "Myodes rutilus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 7805, + "scientific_name": "Myodes shanseius", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 16900, + "scientific_name": "Myodes smithii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136449, + "scientific_name": "Myoictis leucura", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 14086, + "scientific_name": "Myoictis melas", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136839, + "scientific_name": "Myoictis wallacei", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136829, + "scientific_name": "Myoictis wavicus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 14088, + "scientific_name": "Myomimus personatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 14087, + "scientific_name": "Myomimus roachi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 14089, + "scientific_name": "Myomimus setzeri", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 45091, + "scientific_name": "Myomyscus angolensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 45200, + "scientific_name": "Myomyscus brockmani", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 45097, + "scientific_name": "Myomyscus verreauxii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 14096, + "scientific_name": "Myomyscus yemeni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 14097, + "scientific_name": "Myonycteris brachycephala", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 84463728, + "scientific_name": "Myonycteris leptodon", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 14098, + "scientific_name": "Myonycteris relicta", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 84463104, + "scientific_name": "Myonycteris torquata", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 14100, + "scientific_name": "Myoprocta acouchy", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136663, + "scientific_name": "Myoprocta pratti", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 14102, + "scientific_name": "Myopterus daubentonii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 14103, + "scientific_name": "Myopterus whitleyi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 14104, + "scientific_name": "Myopus schisticolor", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 14105, + "scientific_name": "Myosciurus pumilio", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41380, + "scientific_name": "Myosorex babaulti", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 14111, + "scientific_name": "Myosorex blarina", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 45954374, + "scientific_name": "Myosorex bururiensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 110660763, + "scientific_name": "Myosorex cafer", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 14106, + "scientific_name": "Myosorex eisentrauti", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 14107, + "scientific_name": "Myosorex geata", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 45954382, + "scientific_name": "Myosorex gnoskei", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 45954378, + "scientific_name": "Myosorex jejei", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 112042073, + "scientific_name": "Myosorex kabogoensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 45047, + "scientific_name": "Myosorex kihaulei", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 14108, + "scientific_name": "Myosorex longicaudatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 110661822, + "scientific_name": "Myosorex meesteri", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 14112, + "scientific_name": "Myosorex okuensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 14113, + "scientific_name": "Myosorex rumpii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 14110, + "scientific_name": "Myosorex schalleri", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 14114, + "scientific_name": "Myosorex sclateri", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 110662121, + "scientific_name": "Myosorex tenuis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 41382, + "scientific_name": "Myosorex varius", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 45048, + "scientific_name": "Myosorex zinki", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 14116, + "scientific_name": "Myospalax aspalax", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 14119, + "scientific_name": "Myospalax myospalax", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 14120, + "scientific_name": "Myospalax psilurus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 85735326, + "scientific_name": "Myotis adversus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 14139, + "scientific_name": "Myotis aelleni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 14140, + "scientific_name": "Myotis albescens", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136680, + "scientific_name": "Myotis alcathoe", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 14141, + "scientific_name": "Myotis altarium", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 44863, + "scientific_name": "Myotis anjouanensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 136279, + "scientific_name": "Myotis annamiticus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 85342605, + "scientific_name": "Myotis annatessae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 14142, + "scientific_name": "Myotis annectans", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 14143, + "scientific_name": "Myotis atacamensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 14144, + "scientific_name": "Myotis ater", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136553, + "scientific_name": "Myotis aurascens", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 14145, + "scientific_name": "Myotis auriculus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 14146, + "scientific_name": "Myotis australis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 14147, + "scientific_name": "Myotis austroriparius", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 85342628, + "scientific_name": "Myotis badius", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 14123, + "scientific_name": "Myotis bechsteinii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 14124, + "scientific_name": "Myotis blythii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 14148, + "scientific_name": "Myotis bocagii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 14149, + "scientific_name": "Myotis bombinus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 85568289, + "scientific_name": "Myotis borneoensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 85566997, + "scientific_name": "Myotis brandtii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136219, + "scientific_name": "Myotis bucharensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 14150, + "scientific_name": "Myotis californicus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 14126, + "scientific_name": "Myotis capaccinii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 14151, + "scientific_name": "Myotis chiloensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 14152, + "scientific_name": "Myotis chinensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 14153, + "scientific_name": "Myotis ciliolabrum", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 14154, + "scientific_name": "Myotis cobanensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 29420, + "scientific_name": "Myotis csorbai", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 14127, + "scientific_name": "Myotis dasycneme", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 85342710, + "scientific_name": "Myotis daubentonii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136250, + "scientific_name": "Myotis davidii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136678, + "scientific_name": "Myotis dieteri", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 88151417, + "scientific_name": "Myotis diminutus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 136204, + "scientific_name": "Myotis dinellii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 14155, + "scientific_name": "Myotis dominicensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 14156, + "scientific_name": "Myotis elegans", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 14129, + "scientific_name": "Myotis emarginatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 85733126, + "scientific_name": "Myotis escalerai", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 14157, + "scientific_name": "Myotis evotis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 85568302, + "scientific_name": "Myotis federatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 85735587, + "scientific_name": "Myotis fimbriatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 14159, + "scientific_name": "Myotis findleyi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 85736120, + "scientific_name": "Myotis formosus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 14161, + "scientific_name": "Myotis fortidens", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 85566806, + "scientific_name": "Myotis frater", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 40035, + "scientific_name": "Myotis gomantongensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 14163, + "scientific_name": "Myotis goudoti", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 14132, + "scientific_name": "Myotis grisescens", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 14164, + "scientific_name": "Myotis hasseltii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 14165, + "scientific_name": "Myotis hermani", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 14166, + "scientific_name": "Myotis horsfieldii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 14168, + "scientific_name": "Myotis ikonnikovi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 85342688, + "scientific_name": "Myotis indochinensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 14169, + "scientific_name": "Myotis insularum", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 88151563, + "scientific_name": "Myotis izecksohni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 14170, + "scientific_name": "Myotis keaysi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 14171, + "scientific_name": "Myotis keenii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136429, + "scientific_name": "Myotis laniger", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 88151601, + "scientific_name": "Myotis lavali", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 14172, + "scientific_name": "Myotis leibii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 14174, + "scientific_name": "Myotis levis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 85566977, + "scientific_name": "Myotis longicaudatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 14175, + "scientific_name": "Myotis longipes", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 14176, + "scientific_name": "Myotis lucifugus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 14177, + "scientific_name": "Myotis macrodactylus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136697, + "scientific_name": "Myotis macropus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 14178, + "scientific_name": "Myotis macrotarsus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 76435251, + "scientific_name": "Myotis martiniquensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 136784, + "scientific_name": "Myotis melanorhinus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136770, + "scientific_name": "Myotis moluccarum", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 85567622, + "scientific_name": "Myotis montivagus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 14182, + "scientific_name": "Myotis morrisi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 85537578, + "scientific_name": "Myotis muricola", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 14133, + "scientific_name": "Myotis myotis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 14134, + "scientific_name": "Myotis mystacinus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 85733032, + "scientific_name": "Myotis nattereri", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 14184, + "scientific_name": "Myotis nesopolus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 14185, + "scientific_name": "Myotis nigricans", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136495, + "scientific_name": "Myotis nipalensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 76435059, + "scientific_name": "Myotis nyctor", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 136650, + "scientific_name": "Myotis occultus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 14186, + "scientific_name": "Myotis oreias", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 14187, + "scientific_name": "Myotis oxyotus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 14189, + "scientific_name": "Myotis peninsularis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 14190, + "scientific_name": "Myotis pequinius", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 85342726, + "scientific_name": "Myotis petax", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 85568321, + "scientific_name": "Myotis peytoni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 14193, + "scientific_name": "Myotis pilosus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 14191, + "scientific_name": "Myotis planiceps", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 14192, + "scientific_name": "Myotis pruinosus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 44864, + "scientific_name": "Myotis punicus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 14194, + "scientific_name": "Myotis ridleyi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 14195, + "scientific_name": "Myotis riparius", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 14196, + "scientific_name": "Myotis rosseti", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 14197, + "scientific_name": "Myotis ruber", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 85735909, + "scientific_name": "Myotis rufoniger", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136411, + "scientific_name": "Myotis rufopictus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 14198, + "scientific_name": "Myotis schaubi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 14199, + "scientific_name": "Myotis scotti", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 85342651, + "scientific_name": "Myotis secundus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 14201, + "scientific_name": "Myotis septentrionalis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 85567062, + "scientific_name": "Myotis sibiricus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 14202, + "scientific_name": "Myotis sicarius", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 14203, + "scientific_name": "Myotis siligorensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 14204, + "scientific_name": "Myotis simus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 14136, + "scientific_name": "Myotis sodalis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 85342662, + "scientific_name": "Myotis soror", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 14205, + "scientific_name": "Myotis stalkeri", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 14206, + "scientific_name": "Myotis thysanodes", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 14207, + "scientific_name": "Myotis tricolor", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 14208, + "scientific_name": "Myotis velifer", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 14209, + "scientific_name": "Myotis vivesi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 14210, + "scientific_name": "Myotis volans", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 85736011, + "scientific_name": "Myotis weberi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 14211, + "scientific_name": "Myotis welwitschii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 29484, + "scientific_name": "Myotis yanbarensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 14213, + "scientific_name": "Myotis yumanensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 14222, + "scientific_name": "Myrmecobius fasciatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 14224, + "scientific_name": "Myrmecophaga tridactyla", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 14256, + "scientific_name": "Mysateles melanurus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 14258, + "scientific_name": "Mysateles prehensilis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 14260, + "scientific_name": "Mystacina robusta", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 14261, + "scientific_name": "Mystacina tuberculata", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 14262, + "scientific_name": "Mystromys albicaudatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 14288, + "scientific_name": "Myzopoda aurita", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136465, + "scientific_name": "Myzopoda schliemanni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 14294, + "scientific_name": "Naemorhedus baileyi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 14295, + "scientific_name": "Naemorhedus caudatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 14296, + "scientific_name": "Naemorhedus goral", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 41589, + "scientific_name": "Nandinia binotata", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 8968, + "scientific_name": "Nanger dama", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 8971, + "scientific_name": "Nanger granti", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 51187222, + "scientific_name": "Nanger granti ssp. granti", + "subspecies": "granti", + "rank": "ssp.", + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 51187228, + "scientific_name": "Nanger granti ssp. notata", + "subspecies": "notata", + "rank": "ssp.", + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 51187225, + "scientific_name": "Nanger granti ssp. petersii", + "subspecies": "petersii", + "rank": "ssp.", + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 63541, + "scientific_name": "Nanger soemmerringii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 14325, + "scientific_name": "Nannosciurus melanotis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 14326, + "scientific_name": "Nannospalax ehrenbergi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 14328, + "scientific_name": "Nannospalax leucodon", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 14327, + "scientific_name": "Nannospalax xanthodon", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 14333, + "scientific_name": "Nanonycteris veldkampii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 42612, + "scientific_name": "Napaeozapus insignis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 14352, + "scientific_name": "Nasalis larvatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 41683, + "scientific_name": "Nasua narica", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41684, + "scientific_name": "Nasua nasua", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 72261777, + "scientific_name": "Nasuella meridensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 72261737, + "scientific_name": "Nasuella olivacea", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 136448, + "scientific_name": "Natalus espiritosantensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 136824, + "scientific_name": "Natalus jamaicensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 136548, + "scientific_name": "Natalus major", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 123984355, + "scientific_name": "Natalus mexicanus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136777, + "scientific_name": "Natalus primus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 14360, + "scientific_name": "Natalus stramineus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 14362, + "scientific_name": "Natalus tumidirostris", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136727, + "scientific_name": "Neacomys dubosti", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 14386, + "scientific_name": "Neacomys guianae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136570, + "scientific_name": "Neacomys minutus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136655, + "scientific_name": "Neacomys musseri", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136352, + "scientific_name": "Neacomys paracou", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 14387, + "scientific_name": "Neacomys pictus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 14388, + "scientific_name": "Neacomys spinosus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 42649, + "scientific_name": "Neacomys tenuipes", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 1087, + "scientific_name": "Neamblysomus gunningi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 1089, + "scientific_name": "Neamblysomus julianae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 62010, + "scientific_name": "Neamblysomus julianae Bronberg Ridge subpopulation", + "subspecies": null, + "rank": null, + "subpopulation": "Bronberg Ridge subpopulation", + "category": "CR" + }, + { + "taxonid": 2857, + "scientific_name": "Necromys amoenus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136665, + "scientific_name": "Necromys benefactus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 2858, + "scientific_name": "Necromys lactens", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 2859, + "scientific_name": "Necromys lasiurus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136481, + "scientific_name": "Necromys lenguarum", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 2860, + "scientific_name": "Necromys obscurus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 2861, + "scientific_name": "Necromys punctulatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 2862, + "scientific_name": "Necromys temchuki", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 765, + "scientific_name": "Necromys urichi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41455, + "scientific_name": "Nectogale elegans", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136756, + "scientific_name": "Nectomys apicalis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 115555721, + "scientific_name": "Nectomys grandis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 14473, + "scientific_name": "Nectomys palmipes", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 14474, + "scientific_name": "Nectomys rattus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 14475, + "scientific_name": "Nectomys squamipes", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 14486, + "scientific_name": "Nelsonia goldmani", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 14487, + "scientific_name": "Nelsonia neotomodon", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136554, + "scientific_name": "Neodon forresti", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 13435, + "scientific_name": "Neodon irene", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13436, + "scientific_name": "Neodon juldaschi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 45959213, + "scientific_name": "Neodon linzhiensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 13457, + "scientific_name": "Neodon sikimensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136603, + "scientific_name": "Neofelis diardi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 136945, + "scientific_name": "Neofelis diardi ssp. borneensis", + "subspecies": "borneensis", + "rank": "ssp.", + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 136866, + "scientific_name": "Neofelis diardi ssp. diardi", + "subspecies": "diardi", + "rank": "ssp.", + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 14519, + "scientific_name": "Neofelis nebulosa", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 14520, + "scientific_name": "Neofiber alleni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 10588, + "scientific_name": "Neohylomys hainanensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 727, + "scientific_name": "Neomicroxus bogotensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 741, + "scientific_name": "Neomicroxus latebricola", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 13654, + "scientific_name": "Neomonachus schauinslandi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 13655, + "scientific_name": "Neomonachus tropicalis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EX" + }, + { + "taxonid": 29657, + "scientific_name": "Neomys anomalus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 29658, + "scientific_name": "Neomys fodiens", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 29659, + "scientific_name": "Neomys teres", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13382, + "scientific_name": "Neonycteris pusilla", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 14541, + "scientific_name": "Neophascogale lorentzii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 14549, + "scientific_name": "Neophoca cinerea", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 41754, + "scientific_name": "Neophocaena asiaeorientalis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 43205774, + "scientific_name": "Neophocaena asiaeorientalis ssp. asiaeorientalis", + "subspecies": "asiaeorientalis", + "rank": "ssp.", + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 198920, + "scientific_name": "Neophocaena phocaenoides", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 14560, + "scientific_name": "Neopteryx frosti", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 44917, + "scientific_name": "Neoromicia brunnea", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 44918, + "scientific_name": "Neoromicia capensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 44919, + "scientific_name": "Neoromicia flavescens", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 44920, + "scientific_name": "Neoromicia guineensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 44921, + "scientific_name": "Neoromicia helios", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 95558146, + "scientific_name": "Neoromicia isabella", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 136820, + "scientific_name": "Neoromicia malagasyensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 40033, + "scientific_name": "Neoromicia matroka", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 44922, + "scientific_name": "Neoromicia melckorum", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 44923, + "scientific_name": "Neoromicia nana", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 44924, + "scientific_name": "Neoromicia rendalli", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 67359364, + "scientific_name": "Neoromicia robertsi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 67359375, + "scientific_name": "Neoromicia roseveari", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 44925, + "scientific_name": "Neoromicia somalica", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 44926, + "scientific_name": "Neoromicia tenuipinnis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 44927, + "scientific_name": "Neoromicia zuluensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 42568, + "scientific_name": "Neotamias alpinus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 42569, + "scientific_name": "Neotamias amoenus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 21356, + "scientific_name": "Neotamias bulleri", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 21364, + "scientific_name": "Neotamias canipes", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 42570, + "scientific_name": "Neotamias cinereicollis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 42571, + "scientific_name": "Neotamias dorsalis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 21357, + "scientific_name": "Neotamias durangae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 21358, + "scientific_name": "Neotamias merriami", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 42572, + "scientific_name": "Neotamias minimus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 21359, + "scientific_name": "Neotamias obscurus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 42573, + "scientific_name": "Neotamias ochrogenys", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 21355, + "scientific_name": "Neotamias palmeri", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 42574, + "scientific_name": "Neotamias panamintinus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 42575, + "scientific_name": "Neotamias quadrimaculatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 42576, + "scientific_name": "Neotamias quadrivittatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 42577, + "scientific_name": "Neotamias ruficaudus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 42578, + "scientific_name": "Neotamias rufus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 42579, + "scientific_name": "Neotamias senex", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 42580, + "scientific_name": "Neotamias siskiyou", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 42581, + "scientific_name": "Neotamias sonomae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 42582, + "scientific_name": "Neotamias speciosus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 42584, + "scientific_name": "Neotamias townsendii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 42585, + "scientific_name": "Neotamias umbrinus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 10590, + "scientific_name": "Neotetracus sinensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 14582, + "scientific_name": "Neotoma albigula", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 14583, + "scientific_name": "Neotoma angustapalata", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 117189944, + "scientific_name": "Neotoma bryanti", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 14576, + "scientific_name": "Neotoma bryanti ssp. anthonyi", + "subspecies": "anthonyi", + "rank": "ssp.", + "subpopulation": null, + "category": "EX" + }, + { + "taxonid": 14577, + "scientific_name": "Neotoma bryanti ssp. bunkeri", + "subspecies": "bunkeri", + "rank": "ssp.", + "subpopulation": null, + "category": "EX" + }, + { + "taxonid": 14580, + "scientific_name": "Neotoma bryanti ssp. martinensis", + "subspecies": "martinensis", + "rank": "ssp.", + "subpopulation": null, + "category": "EX" + }, + { + "taxonid": 14585, + "scientific_name": "Neotoma chrysomelas", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 42673, + "scientific_name": "Neotoma cinerea", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 14586, + "scientific_name": "Neotoma devia", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 42650, + "scientific_name": "Neotoma floridana", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 14587, + "scientific_name": "Neotoma fuscipes", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 14588, + "scientific_name": "Neotoma goldmani", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 116989038, + "scientific_name": "Neotoma insularis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 116988741, + "scientific_name": "Neotoma lepida", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136793, + "scientific_name": "Neotoma leucodon", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 14597, + "scientific_name": "Neotoma macrotis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 14581, + "scientific_name": "Neotoma magister", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 14590, + "scientific_name": "Neotoma mexicana", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 14591, + "scientific_name": "Neotoma micropus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 14592, + "scientific_name": "Neotoma nelsoni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 14593, + "scientific_name": "Neotoma palatina", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 14594, + "scientific_name": "Neotoma phenax", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 42651, + "scientific_name": "Neotoma stephensi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 14600, + "scientific_name": "Neotomodon alstoni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 14601, + "scientific_name": "Neotomys ebriosus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 14603, + "scientific_name": "Neotragus batesi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 14602, + "scientific_name": "Neotragus pygmaeus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 40784, + "scientific_name": "Neovison macrodon", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EX" + }, + { + "taxonid": 41661, + "scientific_name": "Neovison vison", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 15584, + "scientific_name": "Nephelomys albigularis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 15586, + "scientific_name": "Nephelomys auriventer", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136833, + "scientific_name": "Nephelomys caracolus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 15593, + "scientific_name": "Nephelomys devius", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 15600, + "scientific_name": "Nephelomys keaysi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 15604, + "scientific_name": "Nephelomys levipes", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136328, + "scientific_name": "Nephelomys meridensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 14660, + "scientific_name": "Nesokia bunnii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 14661, + "scientific_name": "Nesokia indica", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 14662, + "scientific_name": "Nesolagus netscheri", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 41209, + "scientific_name": "Nesolagus timminsi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 136475, + "scientific_name": "Nesomys audeberti", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136330, + "scientific_name": "Nesomys lambertoni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 14671, + "scientific_name": "Nesomys rufus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41313, + "scientific_name": "Nesophontes edithae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EX" + }, + { + "taxonid": 14672, + "scientific_name": "Nesophontes hypomicrus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EX" + }, + { + "taxonid": 136381, + "scientific_name": "Nesophontes major", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EX" + }, + { + "taxonid": 14673, + "scientific_name": "Nesophontes micrus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EX" + }, + { + "taxonid": 14674, + "scientific_name": "Nesophontes paramicrus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EX" + }, + { + "taxonid": 14676, + "scientific_name": "Nesophontes zamicrus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EX" + }, + { + "taxonid": 20757, + "scientific_name": "Nesoromys ceramicus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 14706, + "scientific_name": "Nesoryzomys darwini", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EX" + }, + { + "taxonid": 14707, + "scientific_name": "Nesoryzomys fernandinae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 14708, + "scientific_name": "Nesoryzomys indefessus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EX" + }, + { + "taxonid": 40765, + "scientific_name": "Nesoryzomys narboroughi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 14709, + "scientific_name": "Nesoryzomys swarthi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 14604, + "scientific_name": "Nesotragus moschatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41468, + "scientific_name": "Neurotrichus gibbsii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136236, + "scientific_name": "Neusticomys ferreirai", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 14740, + "scientific_name": "Neusticomys monticolus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 14741, + "scientific_name": "Neusticomys mussoi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 14742, + "scientific_name": "Neusticomys oyapocki", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 14743, + "scientific_name": "Neusticomys peruviensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 14744, + "scientific_name": "Neusticomys venezuelae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 9917, + "scientific_name": "Nilgiritragus hylocrius", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 40766, + "scientific_name": "Nilopegamys plumbeus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 40529, + "scientific_name": "Ningaui ridei", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 40530, + "scientific_name": "Ningaui timealeyi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 40531, + "scientific_name": "Ningaui yvonneae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 14812, + "scientific_name": "Niviventer andersoni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 14813, + "scientific_name": "Niviventer brahma", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136512, + "scientific_name": "Niviventer cameroni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 14814, + "scientific_name": "Niviventer confucianus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 14815, + "scientific_name": "Niviventer coninga", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 14816, + "scientific_name": "Niviventer cremoriventer", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 14817, + "scientific_name": "Niviventer culturatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 14818, + "scientific_name": "Niviventer eha", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 14819, + "scientific_name": "Niviventer excelsior", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136747, + "scientific_name": "Niviventer fraternus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 14820, + "scientific_name": "Niviventer fulvescens", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 14821, + "scientific_name": "Niviventer hinpoon", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 14822, + "scientific_name": "Niviventer langbianis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 14823, + "scientific_name": "Niviventer lepturus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 14824, + "scientific_name": "Niviventer niviventer", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 14825, + "scientific_name": "Niviventer rapit", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 14826, + "scientific_name": "Niviventer tenaster", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 14829, + "scientific_name": "Noctilio albiventris", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 14830, + "scientific_name": "Noctilio leporinus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 120659170, + "scientific_name": "Nomascus annamensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 39775, + "scientific_name": "Nomascus concolor", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 160304839, + "scientific_name": "Nomascus concolor ssp. concolor", + "subspecies": "concolor", + "rank": "ssp.", + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 39894, + "scientific_name": "Nomascus concolor ssp. lu", + "subspecies": "lu", + "rank": "ssp.", + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 128073282, + "scientific_name": "Nomascus gabriellae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 41643, + "scientific_name": "Nomascus hainanus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 39895, + "scientific_name": "Nomascus leucogenys", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 41642, + "scientific_name": "Nomascus nasutus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 39896, + "scientific_name": "Nomascus siki", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 136692, + "scientific_name": "Noronhomys vespuccii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EX" + }, + { + "taxonid": 14854, + "scientific_name": "Notiomys edwardsii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136666, + "scientific_name": "Notiosorex cockrumi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41456, + "scientific_name": "Notiosorex crawfordi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136273, + "scientific_name": "Notiosorex evotis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136688, + "scientific_name": "Notiosorex villai", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 20477, + "scientific_name": "Notocitellus adocetus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 20479, + "scientific_name": "Notocitellus annulatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 14867, + "scientific_name": "Notomys alexis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 14861, + "scientific_name": "Notomys amplus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EX" + }, + { + "taxonid": 14862, + "scientific_name": "Notomys aquilo", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 14868, + "scientific_name": "Notomys cervinus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 14863, + "scientific_name": "Notomys fuscus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 14864, + "scientific_name": "Notomys longicaudatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EX" + }, + { + "taxonid": 14865, + "scientific_name": "Notomys macrotis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EX" + }, + { + "taxonid": 14869, + "scientific_name": "Notomys mitchellii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 14866, + "scientific_name": "Notomys mordax", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EX" + }, + { + "taxonid": 45958541, + "scientific_name": "Notomys robustus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EX" + }, + { + "taxonid": 14876, + "scientific_name": "Notopteris macdonaldi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 136519, + "scientific_name": "Notopteris neocaledonica", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 14878, + "scientific_name": "Notoryctes caurinus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 14879, + "scientific_name": "Notoryctes typhlops", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 14921, + "scientific_name": "Nyctalus aviator", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 14922, + "scientific_name": "Nyctalus azoreum", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 136765, + "scientific_name": "Nyctalus furvus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 14918, + "scientific_name": "Nyctalus lasiopterus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 14919, + "scientific_name": "Nyctalus leisleri", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 14923, + "scientific_name": "Nyctalus montanus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 14920, + "scientific_name": "Nyctalus noctula", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136828, + "scientific_name": "Nyctalus plancyi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 14925, + "scientific_name": "Nyctereutes procyonoides", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 14926, + "scientific_name": "Nycteris arge", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 14927, + "scientific_name": "Nycteris aurita", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 14928, + "scientific_name": "Nycteris gambiensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 14929, + "scientific_name": "Nycteris grandis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 14930, + "scientific_name": "Nycteris hispida", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 14931, + "scientific_name": "Nycteris intermedia", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 14932, + "scientific_name": "Nycteris javanica", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 14933, + "scientific_name": "Nycteris macrotis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 40022, + "scientific_name": "Nycteris madagascariensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 14934, + "scientific_name": "Nycteris major", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 14935, + "scientific_name": "Nycteris nana", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 44695, + "scientific_name": "Nycteris parisii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 14936, + "scientific_name": "Nycteris thebaica", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 14937, + "scientific_name": "Nycteris tragata", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 44696, + "scientific_name": "Nycteris vinsoni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 14939, + "scientific_name": "Nycteris woodi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 163015864, + "scientific_name": "Nycticebus bancanus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 39758, + "scientific_name": "Nycticebus bengalensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 163015906, + "scientific_name": "Nycticebus borneanus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 163017685, + "scientific_name": "Nycticebus coucang", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 163029192, + "scientific_name": "Nycticebus coucang ssp. insularis", + "subspecies": "insularis", + "rank": "ssp.", + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 163019804, + "scientific_name": "Nycticebus hilleri", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 39761, + "scientific_name": "Nycticebus javanicus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 163015583, + "scientific_name": "Nycticebus kayan", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 163013860, + "scientific_name": "Nycticebus menagensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 205915373, + "scientific_name": "Nycticebus menagensis ssp. insularis", + "subspecies": "insularis", + "rank": "ssp.", + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 205915351, + "scientific_name": "Nycticebus menagensis ssp. natunae", + "subspecies": "natunae", + "rank": "ssp.", + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 14941, + "scientific_name": "Nycticebus pygmaeus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 41533, + "scientific_name": "Nycticeinops schlieffeni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136562, + "scientific_name": "Nycticeius aenobarbus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 136386, + "scientific_name": "Nycticeius cubanus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 14944, + "scientific_name": "Nycticeius humeralis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 14358, + "scientific_name": "Nyctiellus lepidus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 14954, + "scientific_name": "Nyctimene aello", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 14962, + "scientific_name": "Nyctimene albiventer", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 14963, + "scientific_name": "Nyctimene cephalotes", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 14964, + "scientific_name": "Nyctimene certans", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 14955, + "scientific_name": "Nyctimene cyclotis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 14956, + "scientific_name": "Nyctimene draconilla", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 136441, + "scientific_name": "Nyctimene keasti", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 14965, + "scientific_name": "Nyctimene major", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 14958, + "scientific_name": "Nyctimene malaitensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 14959, + "scientific_name": "Nyctimene masalai", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 14960, + "scientific_name": "Nyctimene minutus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 14953, + "scientific_name": "Nyctimene rabori", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 14966, + "scientific_name": "Nyctimene robinsoni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 14961, + "scientific_name": "Nyctimene sanctacrucis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 14967, + "scientific_name": "Nyctimene vizcaccia", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 14993, + "scientific_name": "Nyctinomops aurispinosus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 14994, + "scientific_name": "Nyctinomops femorosaccus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 14995, + "scientific_name": "Nyctinomops laticaudatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 14996, + "scientific_name": "Nyctinomops macrotis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 14999, + "scientific_name": "Nyctomys sumichrasti", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 15000, + "scientific_name": "Nyctophilus arnhemensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 85289369, + "scientific_name": "Nyctophilus bifax", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 85289516, + "scientific_name": "Nyctophilus corbeni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 85289826, + "scientific_name": "Nyctophilus daedalus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 15003, + "scientific_name": "Nyctophilus geoffroyi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 15004, + "scientific_name": "Nyctophilus gouldi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 15005, + "scientific_name": "Nyctophilus heran", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 15006, + "scientific_name": "Nyctophilus howensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EX" + }, + { + "taxonid": 85289614, + "scientific_name": "Nyctophilus major", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 15007, + "scientific_name": "Nyctophilus microdon", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 15008, + "scientific_name": "Nyctophilus microtis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136807, + "scientific_name": "Nyctophilus nebulosus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 15009, + "scientific_name": "Nyctophilus sherrini", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 85289876, + "scientific_name": "Nyctophilus shirleyae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 15011, + "scientific_name": "Nyctophilus walkeri", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41255, + "scientific_name": "Ochotona alpina", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41986, + "scientific_name": "Ochotona argentata", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 41256, + "scientific_name": "Ochotona cansus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41257, + "scientific_name": "Ochotona collaris", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 87948071, + "scientific_name": "Ochotona coreana", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 41258, + "scientific_name": "Ochotona curzoniae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41259, + "scientific_name": "Ochotona dauurica", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41260, + "scientific_name": "Ochotona erythrotis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 15048, + "scientific_name": "Ochotona forresti", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41261, + "scientific_name": "Ochotona gloveri", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 40800, + "scientific_name": "Ochotona hoffmanni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 87948061, + "scientific_name": "Ochotona hyperborea", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 15050, + "scientific_name": "Ochotona iliensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 15046, + "scientific_name": "Ochotona koslowi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 41264, + "scientific_name": "Ochotona ladacensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41265, + "scientific_name": "Ochotona macrotis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 87948094, + "scientific_name": "Ochotona mantchurica", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 15051, + "scientific_name": "Ochotona nubrica", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 99892252, + "scientific_name": "Ochotona opaca", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 99890206, + "scientific_name": "Ochotona pallasii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41267, + "scientific_name": "Ochotona princeps", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 15052, + "scientific_name": "Ochotona pusilla", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41268, + "scientific_name": "Ochotona roylei", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41269, + "scientific_name": "Ochotona rufescens", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41270, + "scientific_name": "Ochotona rutila", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 87948175, + "scientific_name": "Ochotona syrinx", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41271, + "scientific_name": "Ochotona thibetana", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 15053, + "scientific_name": "Ochotona thomasi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41503, + "scientific_name": "Ochotona turuchanensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 42674, + "scientific_name": "Ochrotomys nuttalli", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 15087, + "scientific_name": "Octodon bridgesi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 15088, + "scientific_name": "Octodon degus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 15089, + "scientific_name": "Octodon lunatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 15090, + "scientific_name": "Octodon pacificus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 15091, + "scientific_name": "Octodontomys gliroides", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 15093, + "scientific_name": "Octomys mimax", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 15106, + "scientific_name": "Odobenus rosmarus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 61963499, + "scientific_name": "Odobenus rosmarus ssp. divergens", + "subspecies": "divergens", + "rank": "ssp.", + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 15108, + "scientific_name": "Odobenus rosmarus ssp. rosmarus", + "subspecies": "rosmarus", + "rank": "ssp.", + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 42393, + "scientific_name": "Odocoileus hemionus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 42394, + "scientific_name": "Odocoileus virginianus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136624, + "scientific_name": "Oecomys auyantepui", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 15131, + "scientific_name": "Oecomys bicolor", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136358, + "scientific_name": "Oecomys catherinae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 15132, + "scientific_name": "Oecomys cleberi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 47766841, + "scientific_name": "Oecomys concolor", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 15134, + "scientific_name": "Oecomys flavicans", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 15135, + "scientific_name": "Oecomys mamorae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 15136, + "scientific_name": "Oecomys paricola", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 15137, + "scientific_name": "Oecomys phaeotis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 15138, + "scientific_name": "Oecomys rex", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 15139, + "scientific_name": "Oecomys roberti", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 15140, + "scientific_name": "Oecomys rutilus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 15141, + "scientific_name": "Oecomys speciosus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 15142, + "scientific_name": "Oecomys superans", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 15143, + "scientific_name": "Oecomys trinitatis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 15164, + "scientific_name": "Oenomys hypoxanthus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 15165, + "scientific_name": "Oenomys ornatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 15188, + "scientific_name": "Okapia johnstoni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 15189, + "scientific_name": "Olallamys albicauda", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 15190, + "scientific_name": "Olallamys edax", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 15241, + "scientific_name": "Oligoryzomys andinus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 15242, + "scientific_name": "Oligoryzomys arenalis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136530, + "scientific_name": "Oligoryzomys brendae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 15243, + "scientific_name": "Oligoryzomys chacoensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 15244, + "scientific_name": "Oligoryzomys delticola", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 15245, + "scientific_name": "Oligoryzomys destructor", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 15246, + "scientific_name": "Oligoryzomys eliurus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 15247, + "scientific_name": "Oligoryzomys flavescens", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136728, + "scientific_name": "Oligoryzomys fornesi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 15248, + "scientific_name": "Oligoryzomys fulvescens", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 15249, + "scientific_name": "Oligoryzomys griseolus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 15250, + "scientific_name": "Oligoryzomys longicaudatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 15251, + "scientific_name": "Oligoryzomys magellanicus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 15252, + "scientific_name": "Oligoryzomys microtis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136336, + "scientific_name": "Oligoryzomys moojeni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 15253, + "scientific_name": "Oligoryzomys nigripes", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136425, + "scientific_name": "Oligoryzomys rupestris", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 29418, + "scientific_name": "Oligoryzomys stramineus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 15254, + "scientific_name": "Oligoryzomys vegetus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 15255, + "scientific_name": "Oligoryzomys victus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EX" + }, + { + "taxonid": 15269, + "scientific_name": "Ommatophoca rossii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 15324, + "scientific_name": "Ondatra zibethicus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 15330, + "scientific_name": "Onychogalea fraenata", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 15331, + "scientific_name": "Onychogalea lunata", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EX" + }, + { + "taxonid": 40568, + "scientific_name": "Onychogalea unguifera", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 15337, + "scientific_name": "Onychomys arenicola", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 15338, + "scientific_name": "Onychomys leucogaster", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 15339, + "scientific_name": "Onychomys torridus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 15419, + "scientific_name": "Orcaella brevirostris", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 44556, + "scientific_name": "Orcaella brevirostris Ayeyarwady River subpopulation", + "subspecies": null, + "rank": null, + "subpopulation": "Ayeyarwady River subpopulation", + "category": "CR" + }, + { + "taxonid": 123095978, + "scientific_name": "Orcaella brevirostris Iloilo-Guimaras Subpopulation", + "subspecies": null, + "rank": null, + "subpopulation": "Iloilo-Guimaras Subpopulation", + "category": "CR" + }, + { + "taxonid": 39428, + "scientific_name": "Orcaella brevirostris Mahakam River subpopulation", + "subspecies": null, + "rank": null, + "subpopulation": "Mahakam River subpopulation", + "category": "CR" + }, + { + "taxonid": 44187, + "scientific_name": "Orcaella brevirostris Malampaya Sound subpopulation", + "subspecies": null, + "rank": null, + "subpopulation": "Malampaya Sound subpopulation", + "category": "CR" + }, + { + "taxonid": 44555, + "scientific_name": "Orcaella brevirostris Mekong River subpopulation", + "subspecies": null, + "rank": null, + "subpopulation": "Mekong River subpopulation", + "category": "CR" + }, + { + "taxonid": 44557, + "scientific_name": "Orcaella brevirostris Songkhla Lake subpopulation", + "subspecies": null, + "rank": null, + "subpopulation": "Songkhla Lake subpopulation", + "category": "CR" + }, + { + "taxonid": 136315, + "scientific_name": "Orcaella heinsohni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 15421, + "scientific_name": "Orcinus orca", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 132948040, + "scientific_name": "Orcinus orca Strait of Gibraltar subpopulation", + "subspecies": null, + "rank": null, + "subpopulation": "Strait of Gibraltar subpopulation", + "category": "CR" + }, + { + "taxonid": 42680, + "scientific_name": "Oreamnos americanus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 15587, + "scientific_name": "Oreoryzomys balneator", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 15485, + "scientific_name": "Oreotragus oreotragus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 15484, + "scientific_name": "Oreotragus oreotragus ssp. porteousi", + "subspecies": "porteousi", + "rank": "ssp.", + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 40488, + "scientific_name": "Ornithorhynchus anatinus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 15544, + "scientific_name": "Orthogeomys cavator", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 15546, + "scientific_name": "Orthogeomys cuniculus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 42590, + "scientific_name": "Orthogeomys grandis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 15548, + "scientific_name": "Orthogeomys heterodus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 15549, + "scientific_name": "Orthogeomys hispidus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 42593, + "scientific_name": "Orthogeomys underwoodi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41504, + "scientific_name": "Orycteropus afer", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41291, + "scientific_name": "Oryctolagus cuniculus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 15571, + "scientific_name": "Oryx beisa", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 136871, + "scientific_name": "Oryx beisa ssp. beisa", + "subspecies": "beisa", + "rank": "ssp.", + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 15572, + "scientific_name": "Oryx beisa ssp. callotis", + "subspecies": "callotis", + "rank": "ssp.", + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 15568, + "scientific_name": "Oryx dammah", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EW" + }, + { + "taxonid": 15573, + "scientific_name": "Oryx gazella", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 15569, + "scientific_name": "Oryx leucoryx", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 136540, + "scientific_name": "Oryzomys antillarum", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EX" + }, + { + "taxonid": 15592, + "scientific_name": "Oryzomys couesi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 15594, + "scientific_name": "Oryzomys dimidiatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 115554360, + "scientific_name": "Oryzomys gorgasi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 15583, + "scientific_name": "Oryzomys nelsoni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EX" + }, + { + "taxonid": 42675, + "scientific_name": "Oryzomys palustris", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 40589, + "scientific_name": "Oryzorictes hova", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 40591, + "scientific_name": "Oryzorictes tetradactylus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 15629, + "scientific_name": "Osgoodomys banderanus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41665, + "scientific_name": "Otaria byronia", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 15640, + "scientific_name": "Otocolobus manul", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 15642, + "scientific_name": "Otocyon megalotis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 15643, + "scientific_name": "Otolemur crassicaudatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 91980457, + "scientific_name": "Otolemur crassicaudatus ssp. argentatus", + "subspecies": "argentatus", + "rank": "ssp.", + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136881, + "scientific_name": "Otolemur crassicaudatus ssp. crassicaudatus", + "subspecies": "crassicaudatus", + "rank": "ssp.", + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136874, + "scientific_name": "Otolemur crassicaudatus ssp. kirkii", + "subspecies": "kirkii", + "rank": "ssp.", + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 91991178, + "scientific_name": "Otolemur crassicaudatus ssp. monteiri", + "subspecies": "monteiri", + "rank": "ssp.", + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 15644, + "scientific_name": "Otolemur garnettii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136855, + "scientific_name": "Otolemur garnettii ssp. garnettii", + "subspecies": "garnettii", + "rank": "ssp.", + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 136911, + "scientific_name": "Otolemur garnettii ssp. kikuyuensis", + "subspecies": "kikuyuensis", + "rank": "ssp.", + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136900, + "scientific_name": "Otolemur garnettii ssp. lasiotis", + "subspecies": "lasiotis", + "rank": "ssp.", + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136859, + "scientific_name": "Otolemur garnettii ssp. panganiensis", + "subspecies": "panganiensis", + "rank": "ssp.", + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 15645, + "scientific_name": "Otomops formosus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 95558305, + "scientific_name": "Otomops harrisoni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 15647, + "scientific_name": "Otomops johnstonei", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 136564, + "scientific_name": "Otomops madagascariensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 15648, + "scientific_name": "Otomops martiensseni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 15649, + "scientific_name": "Otomops papuensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 15650, + "scientific_name": "Otomops secundus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 15646, + "scientific_name": "Otomops wroughtoni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 15651, + "scientific_name": "Otomys anchietae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 15652, + "scientific_name": "Otomys angoniensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 110662638, + "scientific_name": "Otomys auratus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 45069, + "scientific_name": "Otomys barbouri", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 45071, + "scientific_name": "Otomys burtoni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 48008221, + "scientific_name": "Otomys cheesmani", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 45065, + "scientific_name": "Otomys cuanzensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 45063, + "scientific_name": "Otomys dartmouthi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 15653, + "scientific_name": "Otomys denti", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 48009551, + "scientific_name": "Otomys fortior", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 48011421, + "scientific_name": "Otomys helleri", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 110662669, + "scientific_name": "Otomys irroratus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 45064, + "scientific_name": "Otomys jacksoni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 111949037, + "scientific_name": "Otomys karoensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 45067, + "scientific_name": "Otomys lacustris", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 15655, + "scientific_name": "Otomys laminatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 15657, + "scientific_name": "Otomys occidentalis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 48014975, + "scientific_name": "Otomys simiensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 15659, + "scientific_name": "Otomys sloggetti", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 48016232, + "scientific_name": "Otomys thomasi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 15660, + "scientific_name": "Otomys tropicalis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 48006730, + "scientific_name": "Otomys typus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 15662, + "scientific_name": "Otomys unisulcatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 45068, + "scientific_name": "Otomys uzungwensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 48018096, + "scientific_name": "Otomys yaldeni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 48018185, + "scientific_name": "Otomys zinki", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 85294528, + "scientific_name": "Otonycteris hemprichii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 85295233, + "scientific_name": "Otonycteris leucophaea", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 15664, + "scientific_name": "Otonyctomys hatti", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 15665, + "scientific_name": "Otopteropus cartilagonodus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 20481, + "scientific_name": "Otospermophilus beecheyi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 20480, + "scientific_name": "Otospermophilus beecheyi ssp. atricapillus", + "subspecies": "atricapillus", + "rank": "ssp.", + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 20495, + "scientific_name": "Otospermophilus variegatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 15666, + "scientific_name": "Ototylomys phyllotis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 15730, + "scientific_name": "Ourebia ourebi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 15731, + "scientific_name": "Ourebia ourebi ssp. haggardi", + "subspecies": "haggardi", + "rank": "ssp.", + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 15732, + "scientific_name": "Ourebia ourebi ssp. kenyae", + "subspecies": "kenyae", + "rank": "ssp.", + "subpopulation": null, + "category": "EX" + }, + { + "taxonid": 29684, + "scientific_name": "Ovibos moschatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 15733, + "scientific_name": "Ovis ammon", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 15735, + "scientific_name": "Ovis canadensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 39250, + "scientific_name": "Ovis dalli", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 54940218, + "scientific_name": "Ovis gmelini", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 15740, + "scientific_name": "Ovis nivicola", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 54940655, + "scientific_name": "Ovis vignei", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 136205, + "scientific_name": "Oxymycterus amazonicus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 15783, + "scientific_name": "Oxymycterus angularis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136725, + "scientific_name": "Oxymycterus caparoae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136813, + "scientific_name": "Oxymycterus dasytrichus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 15784, + "scientific_name": "Oxymycterus delator", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 15793, + "scientific_name": "Oxymycterus hiska", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 15785, + "scientific_name": "Oxymycterus hispidus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 15786, + "scientific_name": "Oxymycterus hucucha", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 15788, + "scientific_name": "Oxymycterus inca", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136453, + "scientific_name": "Oxymycterus josei", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 15789, + "scientific_name": "Oxymycterus nasutus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 115590985, + "scientific_name": "Oxymycterus paramensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136613, + "scientific_name": "Oxymycterus quaestor", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 15791, + "scientific_name": "Oxymycterus roberti", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 15792, + "scientific_name": "Oxymycterus rufus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 45959327, + "scientific_name": "Oxymycterus wayku", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 13880, + "scientific_name": "Ozimops beccarii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 71536513, + "scientific_name": "Ozimops cobourgianus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 71532803, + "scientific_name": "Ozimops halli", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 71532724, + "scientific_name": "Ozimops kitcheneri", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 82345325, + "scientific_name": "Ozimops loriae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 71531227, + "scientific_name": "Ozimops lumsdenae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 71534469, + "scientific_name": "Ozimops petersi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 71732146, + "scientific_name": "Ozimops planiceps", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 71533043, + "scientific_name": "Ozimops ridei", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 15803, + "scientific_name": "Ozotoceros bezoarticus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 15865, + "scientific_name": "Pachyuromys duprasi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41671, + "scientific_name": "Pagophilus groenlandicus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41692, + "scientific_name": "Paguma larvata", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136532, + "scientific_name": "Palaeopropithecus ingens", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EX" + }, + { + "taxonid": 15917, + "scientific_name": "Palawanomys furvus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 15932, + "scientific_name": "Pan paniscus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 15951, + "scientific_name": "Panthera leo", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 15952, + "scientific_name": "Panthera leo ssp. persica", + "subspecies": "persica", + "rank": "ssp.", + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 68933833, + "scientific_name": "Panthera leo West Africa subpopulation", + "subspecies": null, + "rank": null, + "subpopulation": "West Africa subpopulation", + "category": "CR" + }, + { + "taxonid": 15953, + "scientific_name": "Panthera onca", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 15954, + "scientific_name": "Panthera pardus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 124159083, + "scientific_name": "Panthera pardus ssp. delacouri", + "subspecies": "delacouri", + "rank": "ssp.", + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 15959, + "scientific_name": "Panthera pardus ssp. kotiya", + "subspecies": "kotiya", + "rank": "ssp.", + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 15962, + "scientific_name": "Panthera pardus ssp. melas", + "subspecies": "melas", + "rank": "ssp.", + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 15955, + "scientific_name": "Panthera tigris", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 22732, + "scientific_name": "Panthera uncia", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 15967, + "scientific_name": "Pantholops hodgsonii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 15933, + "scientific_name": "Pan troglodytes", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 40014, + "scientific_name": "Pan troglodytes ssp. ellioti", + "subspecies": "ellioti", + "rank": "ssp.", + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 15937, + "scientific_name": "Pan troglodytes ssp. schweinfurthii", + "subspecies": "schweinfurthii", + "rank": "ssp.", + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 15936, + "scientific_name": "Pan troglodytes ssp. troglodytes", + "subspecies": "troglodytes", + "rank": "ssp.", + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 15935, + "scientific_name": "Pan troglodytes ssp. verus", + "subspecies": "verus", + "rank": "ssp.", + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 15975, + "scientific_name": "Papagomys armandvillei", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 40647, + "scientific_name": "Papio anubis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 92250442, + "scientific_name": "Papio cynocephalus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136898, + "scientific_name": "Papio cynocephalus ssp. cynocephalus", + "subspecies": "cynocephalus", + "rank": "ssp.", + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136862, + "scientific_name": "Papio cynocephalus ssp. ibeanus", + "subspecies": "ibeanus", + "rank": "ssp.", + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 16019, + "scientific_name": "Papio hamadryas", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136848, + "scientific_name": "Papio kindae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 16018, + "scientific_name": "Papio papio", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 16022, + "scientific_name": "Papio ursinus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136870, + "scientific_name": "Papio ursinus ssp. griseipes", + "subspecies": "griseipes", + "rank": "ssp.", + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 92250358, + "scientific_name": "Papio ursinus ssp. ruacana", + "subspecies": "ruacana", + "rank": "ssp.", + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136856, + "scientific_name": "Papio ursinus ssp. ursinus", + "subspecies": "ursinus", + "rank": "ssp.", + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 92474664, + "scientific_name": "Pappogeomys bulleri", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 16023, + "scientific_name": "Pappogeomys bulleri ssp. alcorni", + "subspecies": "alcorni", + "rank": "ssp.", + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 16042, + "scientific_name": "Paracrocidura graueri", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 16043, + "scientific_name": "Paracrocidura maxima", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41383, + "scientific_name": "Paracrocidura schoutedeni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41622, + "scientific_name": "Paracynictis selousi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 16074, + "scientific_name": "Paradipus ctenodactylus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41693, + "scientific_name": "Paradoxurus hermaphroditus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 16104, + "scientific_name": "Paradoxurus jerdoni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41694, + "scientific_name": "Paradoxurus zeylonensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 40606, + "scientific_name": "Paraechinus aethiopicus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 40610, + "scientific_name": "Paraechinus hypomelas", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 40609, + "scientific_name": "Paraechinus micropus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 39594, + "scientific_name": "Paraechinus nudiventris", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136212, + "scientific_name": "Paragalago cocos", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 91970347, + "scientific_name": "Paragalago granti", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 40651, + "scientific_name": "Paragalago orinus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 40652, + "scientific_name": "Paragalago rondoensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 8790, + "scientific_name": "Paragalago zanzibaricus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 136903, + "scientific_name": "Paragalago zanzibaricus ssp. udzungwensis", + "subspecies": "udzungwensis", + "rank": "ssp.", + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 136924, + "scientific_name": "Paragalago zanzibaricus ssp. zanzibaricus", + "subspecies": "zanzibaricus", + "rank": "ssp.", + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 10276, + "scientific_name": "Parahyaena brunnea", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 16114, + "scientific_name": "Parahydromys asper", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 16121, + "scientific_name": "Paraleptomys rufilatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 16122, + "scientific_name": "Paraleptomys wilhelmina", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 136701, + "scientific_name": "Paramelomys gressitti", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 13124, + "scientific_name": "Paramelomys levipes", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13125, + "scientific_name": "Paramelomys lorentzii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13126, + "scientific_name": "Paramelomys mollis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13127, + "scientific_name": "Paramelomys moncktoni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136324, + "scientific_name": "Paramelomys naso", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13129, + "scientific_name": "Paramelomys platyops", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13131, + "scientific_name": "Paramelomys rubex", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136718, + "scientific_name": "Paramelomys steini", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 16138, + "scientific_name": "Parantechinus apicalis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 16174, + "scientific_name": "Paranyctimene raptor", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136836, + "scientific_name": "Paranyctimene tenax", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41469, + "scientific_name": "Parascalops breweri", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41470, + "scientific_name": "Parascaptor leucura", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 17341, + "scientific_name": "Parastrellus hesperus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 40025, + "scientific_name": "Paratriaenops auritus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 81060220, + "scientific_name": "Paratriaenops furculus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 81068840, + "scientific_name": "Paratriaenops pauliani", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 16203, + "scientific_name": "Paraxerus alexandri", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 16204, + "scientific_name": "Paraxerus boehmi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 16205, + "scientific_name": "Paraxerus cepapi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 16206, + "scientific_name": "Paraxerus cooperi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 16207, + "scientific_name": "Paraxerus flavovittis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 16208, + "scientific_name": "Paraxerus lucifer", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 16209, + "scientific_name": "Paraxerus ochraceus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 16210, + "scientific_name": "Paraxerus palliatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 16211, + "scientific_name": "Paraxerus poensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 16202, + "scientific_name": "Paraxerus vexillarius", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 16212, + "scientific_name": "Paraxerus vincenti", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 16218, + "scientific_name": "Pardofelis marmorata", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 7671, + "scientific_name": "Paremballonura atrata", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136835, + "scientific_name": "Paremballonura tiavato", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 16270, + "scientific_name": "Parotomys brantsii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 16271, + "scientific_name": "Parotomys littledalei", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 16375, + "scientific_name": "Paruromys dominator", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 90386442, + "scientific_name": "Pattonomys carrikeri", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 90386462, + "scientific_name": "Pattonomys flavidus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 39314, + "scientific_name": "Pattonomys occasius", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 90386776, + "scientific_name": "Pattonomys punctatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 90386452, + "scientific_name": "Pattonomys semivillosus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 46205590, + "scientific_name": "Paucidentomys vermidax", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 16426, + "scientific_name": "Paulamys naso", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 41777, + "scientific_name": "Pecari tajacu", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 16458, + "scientific_name": "Pectinator spekei", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 16467, + "scientific_name": "Pedetes capensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136621, + "scientific_name": "Pedetes surdaster", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 16484, + "scientific_name": "Pelea capreolus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 16508, + "scientific_name": "Pelomys campanae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 16509, + "scientific_name": "Pelomys fallax", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 16506, + "scientific_name": "Pelomys hopkinsi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 16507, + "scientific_name": "Pelomys isseli", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 16510, + "scientific_name": "Pelomys minor", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 199838, + "scientific_name": "Pennatomys nivalis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EX" + }, + { + "taxonid": 16559, + "scientific_name": "Pentalagus furnessi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 16563, + "scientific_name": "Penthetor lucasi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 16564, + "scientific_name": "Peponocephala electra", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 16569, + "scientific_name": "Perameles bougainville", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 16570, + "scientific_name": "Perameles eremiana", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EX" + }, + { + "taxonid": 16572, + "scientific_name": "Perameles gunnii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 40554, + "scientific_name": "Perameles nasuta", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 17366, + "scientific_name": "Perimyotis subflavus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 136852, + "scientific_name": "Perodicticus edwardsi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136875, + "scientific_name": "Perodicticus ibeanus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 95706286, + "scientific_name": "Perodicticus ibeanus ssp. ibeanus", + "subspecies": "ibeanus", + "rank": "ssp.", + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136932, + "scientific_name": "Perodicticus ibeanus ssp. stockleyi", + "subspecies": "stockleyi", + "rank": "ssp.", + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 91995408, + "scientific_name": "Perodicticus potto", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 91995465, + "scientific_name": "Perodicticus potto ssp. juju", + "subspecies": "juju", + "rank": "ssp.", + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 136847, + "scientific_name": "Perodicticus potto ssp. potto", + "subspecies": "potto", + "rank": "ssp.", + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 16631, + "scientific_name": "Perognathus alticola", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 16633, + "scientific_name": "Perognathus amplus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 42608, + "scientific_name": "Perognathus fasciatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 16634, + "scientific_name": "Perognathus flavescens", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 16635, + "scientific_name": "Perognathus flavus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 42609, + "scientific_name": "Perognathus inornatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 16636, + "scientific_name": "Perognathus longimembris", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 16637, + "scientific_name": "Perognathus merriami", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 42610, + "scientific_name": "Perognathus parvus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 42652, + "scientific_name": "Peromyscus attwateri", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 16651, + "scientific_name": "Peromyscus aztecus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136323, + "scientific_name": "Peromyscus beatae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 16652, + "scientific_name": "Peromyscus boylii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 16653, + "scientific_name": "Peromyscus bullatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 16654, + "scientific_name": "Peromyscus californicus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 16655, + "scientific_name": "Peromyscus caniceps", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 16656, + "scientific_name": "Peromyscus crinitus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 16657, + "scientific_name": "Peromyscus dickeyi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 16658, + "scientific_name": "Peromyscus difficilis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 16659, + "scientific_name": "Peromyscus eremicus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 16660, + "scientific_name": "Peromyscus eva", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136412, + "scientific_name": "Peromyscus fraterculus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 16661, + "scientific_name": "Peromyscus furvus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 42653, + "scientific_name": "Peromyscus gossypinus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 16662, + "scientific_name": "Peromyscus grandis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 16663, + "scientific_name": "Peromyscus gratus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 16664, + "scientific_name": "Peromyscus guardia", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 16665, + "scientific_name": "Peromyscus guatemalensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 16666, + "scientific_name": "Peromyscus gymnotis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 16667, + "scientific_name": "Peromyscus hooperi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136416, + "scientific_name": "Peromyscus hylocetes", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 16668, + "scientific_name": "Peromyscus interparietalis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 135164, + "scientific_name": "Peromyscus keeni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 16669, + "scientific_name": "Peromyscus leucopus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 16670, + "scientific_name": "Peromyscus levipes", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 16671, + "scientific_name": "Peromyscus madrensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 16672, + "scientific_name": "Peromyscus maniculatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 16673, + "scientific_name": "Peromyscus mayensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 16674, + "scientific_name": "Peromyscus megalops", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 16675, + "scientific_name": "Peromyscus mekisturus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 16676, + "scientific_name": "Peromyscus melanocarpus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 16677, + "scientific_name": "Peromyscus melanophrys", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 16678, + "scientific_name": "Peromyscus melanotis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 16679, + "scientific_name": "Peromyscus melanurus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 16680, + "scientific_name": "Peromyscus merriami", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 16681, + "scientific_name": "Peromyscus mexicanus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 16682, + "scientific_name": "Peromyscus nasutus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 16683, + "scientific_name": "Peromyscus ochraventer", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 16684, + "scientific_name": "Peromyscus pectoralis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 16645, + "scientific_name": "Peromyscus pembertoni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EX" + }, + { + "taxonid": 16685, + "scientific_name": "Peromyscus perfulvus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 42654, + "scientific_name": "Peromyscus polionotus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 16686, + "scientific_name": "Peromyscus polius", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 16687, + "scientific_name": "Peromyscus pseudocrinitus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 136710, + "scientific_name": "Peromyscus sagax", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 136576, + "scientific_name": "Peromyscus schmidlyi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 16688, + "scientific_name": "Peromyscus sejugis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 16689, + "scientific_name": "Peromyscus simulus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 16690, + "scientific_name": "Peromyscus slevini", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 16691, + "scientific_name": "Peromyscus spicilegus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 16692, + "scientific_name": "Peromyscus stephani", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 16693, + "scientific_name": "Peromyscus stirtoni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 16694, + "scientific_name": "Peromyscus truei", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 16695, + "scientific_name": "Peromyscus winkelmanni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 16696, + "scientific_name": "Peromyscus yucatanicus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 16697, + "scientific_name": "Peromyscus zarhynchus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 16707, + "scientific_name": "Peropteryx kappleri", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 16708, + "scientific_name": "Peropteryx leucoptera", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 16709, + "scientific_name": "Peropteryx macrotis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 85822291, + "scientific_name": "Peropteryx pallidoptera", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 136790, + "scientific_name": "Peropteryx trinitatis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 16710, + "scientific_name": "Peroryctes broadbenti", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 16711, + "scientific_name": "Peroryctes raffrayana", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 16714, + "scientific_name": "Petaurillus emiliae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 16715, + "scientific_name": "Petaurillus hosei", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 16716, + "scientific_name": "Petaurillus kinlochii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 16718, + "scientific_name": "Petaurista alborufus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 16719, + "scientific_name": "Petaurista elegans", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 16720, + "scientific_name": "Petaurista leucogenys", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 16721, + "scientific_name": "Petaurista magnificus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 45959013, + "scientific_name": "Petaurista mechukaensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 45959040, + "scientific_name": "Petaurista mishmiensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 16722, + "scientific_name": "Petaurista nobilis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 16723, + "scientific_name": "Petaurista petaurista", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 16724, + "scientific_name": "Petaurista philippensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 16725, + "scientific_name": "Petaurista xanthotis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 40579, + "scientific_name": "Petauroides volans", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 16726, + "scientific_name": "Petaurus abidi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 16730, + "scientific_name": "Petaurus australis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 16732, + "scientific_name": "Petaurus biacensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 16731, + "scientific_name": "Petaurus breviceps", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 16727, + "scientific_name": "Petaurus gracilis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 16728, + "scientific_name": "Petaurus norfolcensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 16733, + "scientific_name": "Petinomys crinitus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 16734, + "scientific_name": "Petinomys fuscocapillus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 16735, + "scientific_name": "Petinomys genibarbis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 16736, + "scientific_name": "Petinomys hageni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 16737, + "scientific_name": "Petinomys lugens", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 136439, + "scientific_name": "Petinomys mindanensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 16739, + "scientific_name": "Petinomys setosus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 16740, + "scientific_name": "Petinomys vordermanni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 42679, + "scientific_name": "Petrodromus tetradactylus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 40569, + "scientific_name": "Petrogale assimilis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 40570, + "scientific_name": "Petrogale brachyotis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 16744, + "scientific_name": "Petrogale burbidgei", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 16752, + "scientific_name": "Petrogale coenensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 16761, + "scientific_name": "Petrogale concinna", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 41514, + "scientific_name": "Petrogale godmani", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 41515, + "scientific_name": "Petrogale herberti", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41516, + "scientific_name": "Petrogale inornata", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 16751, + "scientific_name": "Petrogale lateralis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 136509, + "scientific_name": "Petrogale mareeba", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 16746, + "scientific_name": "Petrogale penicillata", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 16747, + "scientific_name": "Petrogale persephone", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 136463, + "scientific_name": "Petrogale purpureicollis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 41517, + "scientific_name": "Petrogale rothschildi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 16753, + "scientific_name": "Petrogale sharmani", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 16750, + "scientific_name": "Petrogale xanthopus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 16776, + "scientific_name": "Petromus typicus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 16777, + "scientific_name": "Petromyscus barbouri", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 16778, + "scientific_name": "Petromyscus collinus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 16779, + "scientific_name": "Petromyscus monticularis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 16780, + "scientific_name": "Petromyscus shortridgei", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 40580, + "scientific_name": "Petropseudes dahli", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41767, + "scientific_name": "Phacochoerus aethiopicus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41768, + "scientific_name": "Phacochoerus africanus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 16799, + "scientific_name": "Phaenomys ferrugineus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 13439, + "scientific_name": "Phaiomys leucurus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 16858, + "scientific_name": "Phalanger alexandrae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 16853, + "scientific_name": "Phalanger carmelitae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 16856, + "scientific_name": "Phalanger gymnotis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 16857, + "scientific_name": "Phalanger intercastellanus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 16846, + "scientific_name": "Phalanger lullulae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 136200, + "scientific_name": "Phalanger matabiru", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 16851, + "scientific_name": "Phalanger matanim", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 136450, + "scientific_name": "Phalanger mimicus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 16847, + "scientific_name": "Phalanger orientalis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 16854, + "scientific_name": "Phalanger ornatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 16852, + "scientific_name": "Phalanger rothschildi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 16855, + "scientific_name": "Phalanger sericeus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 16850, + "scientific_name": "Phalanger vestitus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 16875, + "scientific_name": "Phaner electromontis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 16872, + "scientific_name": "Phaner furcifer", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 16874, + "scientific_name": "Phaner pallescens", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 16873, + "scientific_name": "Phaner parienti", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 16887, + "scientific_name": "Pharotis imogene", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 16888, + "scientific_name": "Phascogale calura", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 16889, + "scientific_name": "Phascogale pirata", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 16890, + "scientific_name": "Phascogale tapoatafa", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 16892, + "scientific_name": "Phascolarctos cinereus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 16893, + "scientific_name": "Phascolosorex doriae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 16894, + "scientific_name": "Phascolosorex dorsalis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 12766, + "scientific_name": "Phataginus tetradactyla", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 12767, + "scientific_name": "Phataginus tricuspis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 42636, + "scientific_name": "Phenacomys intermedius", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 42637, + "scientific_name": "Phenacomys ungava", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 40515, + "scientific_name": "Philander andersoni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136227, + "scientific_name": "Philander deltae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136375, + "scientific_name": "Philander frenatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136501, + "scientific_name": "Philander mcilhennyi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136202, + "scientific_name": "Philander mondolfii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 199832, + "scientific_name": "Philander olrogi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 40516, + "scientific_name": "Philander opossum", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 4142, + "scientific_name": "Philantomba maxwellii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 4143, + "scientific_name": "Philantomba monticola", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 88418111, + "scientific_name": "Philantomba walteri", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 16981, + "scientific_name": "Philetor brachypterus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 17003, + "scientific_name": "Phloeomys cumingi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 17004, + "scientific_name": "Phloeomys pallidus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 17023, + "scientific_name": "Phoca largha", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 17026, + "scientific_name": "Phocarctos hookeri", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 17013, + "scientific_name": "Phoca vitulina", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 17021, + "scientific_name": "Phoca vitulina ssp. concolor", + "subspecies": "concolor", + "rank": "ssp.", + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 17018, + "scientific_name": "Phoca vitulina ssp. mellonae", + "subspecies": "mellonae", + "rank": "ssp.", + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 17022, + "scientific_name": "Phoca vitulina ssp. richardii", + "subspecies": "richardii", + "rank": "ssp.", + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 17014, + "scientific_name": "Phoca vitulina ssp. stejnegeri", + "subspecies": "stejnegeri", + "rank": "ssp.", + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 17020, + "scientific_name": "Phoca vitulina ssp. vitulina", + "subspecies": "vitulina", + "rank": "ssp.", + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41715, + "scientific_name": "Phocoena dioptrica", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 17027, + "scientific_name": "Phocoena phocoena", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 17031, + "scientific_name": "Phocoena phocoena Baltic Sea subpopulation", + "subspecies": null, + "rank": null, + "subpopulation": "Baltic Sea subpopulation", + "category": "CR" + }, + { + "taxonid": 17030, + "scientific_name": "Phocoena phocoena ssp. relicta", + "subspecies": "relicta", + "rank": "ssp.", + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 17028, + "scientific_name": "Phocoena sinus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 17029, + "scientific_name": "Phocoena spinipinnis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 17032, + "scientific_name": "Phocoenoides dalli", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 17035, + "scientific_name": "Phodopus campbelli", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 17036, + "scientific_name": "Phodopus roborovskii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 17037, + "scientific_name": "Phodopus sungorus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 10967, + "scientific_name": "Phoniscus aerosus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 10970, + "scientific_name": "Phoniscus atrox", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 10976, + "scientific_name": "Phoniscus jagorii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 10982, + "scientific_name": "Phoniscus papuensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 17168, + "scientific_name": "Phylloderma stenops", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 6977, + "scientific_name": "Phyllomys blainvillii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 6978, + "scientific_name": "Phyllomys brasiliensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 6980, + "scientific_name": "Phyllomys dasythrix", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136682, + "scientific_name": "Phyllomys kerri", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 6982, + "scientific_name": "Phyllomys lamarum", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 136400, + "scientific_name": "Phyllomys lundi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 136274, + "scientific_name": "Phyllomys mantiqueirensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 136704, + "scientific_name": "Phyllomys medius", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 6984, + "scientific_name": "Phyllomys nigrispinus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136801, + "scientific_name": "Phyllomys pattoni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 47792718, + "scientific_name": "Phyllomys sulinus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 6989, + "scientific_name": "Phyllomys thomasi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 6990, + "scientific_name": "Phyllomys unicolor", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 17173, + "scientific_name": "Phyllonycteris aphylla", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 17175, + "scientific_name": "Phyllonycteris poeyi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 17176, + "scientific_name": "Phyllops falcatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 17216, + "scientific_name": "Phyllostomus discolor", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 17217, + "scientific_name": "Phyllostomus elongatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 17218, + "scientific_name": "Phyllostomus hastatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 17219, + "scientific_name": "Phyllostomus latifolius", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 45959348, + "scientific_name": "Phyllotis alisosiensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 17220, + "scientific_name": "Phyllotis amicus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 17221, + "scientific_name": "Phyllotis andium", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136674, + "scientific_name": "Phyllotis anitae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 17222, + "scientific_name": "Phyllotis bonariensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 17223, + "scientific_name": "Phyllotis caprinus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 17224, + "scientific_name": "Phyllotis darwini", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 17225, + "scientific_name": "Phyllotis definitus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 17226, + "scientific_name": "Phyllotis gerbillus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 17227, + "scientific_name": "Phyllotis haggardi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136629, + "scientific_name": "Phyllotis limatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 17228, + "scientific_name": "Phyllotis magister", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 17229, + "scientific_name": "Phyllotis osgoodi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 17230, + "scientific_name": "Phyllotis osilae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 17231, + "scientific_name": "Phyllotis wolffsohni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 17232, + "scientific_name": "Phyllotis xanthopygus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41755, + "scientific_name": "Physeter macrocephalus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 16370739, + "scientific_name": "Physeter macrocephalus Mediterranean subpopulation", + "subspecies": null, + "rank": null, + "subpopulation": "Mediterranean subpopulation", + "category": "EN" + }, + { + "taxonid": 161247840, + "scientific_name": "Piliocolobus badius", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 40009, + "scientific_name": "Piliocolobus badius ssp. badius", + "subspecies": "badius", + "rank": "ssp.", + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 18247, + "scientific_name": "Piliocolobus badius ssp. temminckii", + "subspecies": "temminckii", + "rank": "ssp.", + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 18250, + "scientific_name": "Piliocolobus bouvieri", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 41024, + "scientific_name": "Piliocolobus epieni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 18252, + "scientific_name": "Piliocolobus foai", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 40015, + "scientific_name": "Piliocolobus gordonorum", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 39992, + "scientific_name": "Piliocolobus kirkii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 18261, + "scientific_name": "Piliocolobus langi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 18262, + "scientific_name": "Piliocolobus lulindicus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 18255, + "scientific_name": "Piliocolobus oustaleti", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 40648, + "scientific_name": "Piliocolobus parmentieri", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 41025, + "scientific_name": "Piliocolobus pennantii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 41026, + "scientific_name": "Piliocolobus preussi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 136939, + "scientific_name": "Piliocolobus rufomitratus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 92657343, + "scientific_name": "Piliocolobus semlikiensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 18256, + "scientific_name": "Piliocolobus tephrosceles", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 18257, + "scientific_name": "Piliocolobus tholloni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 18248, + "scientific_name": "Piliocolobus waldroni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 17320, + "scientific_name": "Pipistrellus abramus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 17321, + "scientific_name": "Pipistrellus adamsi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 17323, + "scientific_name": "Pipistrellus aero", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 85333758, + "scientific_name": "Pipistrellus aladdin", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 44851, + "scientific_name": "Pipistrellus anchietae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 17326, + "scientific_name": "Pipistrellus angulatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 17332, + "scientific_name": "Pipistrellus ceylonicus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 17334, + "scientific_name": "Pipistrellus collinus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 17335, + "scientific_name": "Pipistrellus coromandra", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 44853, + "scientific_name": "Pipistrellus crassulus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 17340, + "scientific_name": "Pipistrellus endoi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 85736277, + "scientific_name": "Pipistrellus grandidieri", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 136209, + "scientific_name": "Pipistrellus hanaki", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 136741, + "scientific_name": "Pipistrellus hesperidus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 17342, + "scientific_name": "Pipistrellus imbricatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 17343, + "scientific_name": "Pipistrellus inexspectatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 17344, + "scientific_name": "Pipistrellus javanicus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 17314, + "scientific_name": "Pipistrellus kuhlii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 17315, + "scientific_name": "Pipistrellus maderensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 17350, + "scientific_name": "Pipistrellus minahassae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 136769, + "scientific_name": "Pipistrellus murrayi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EX" + }, + { + "taxonid": 17353, + "scientific_name": "Pipistrellus nanulus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 17316, + "scientific_name": "Pipistrellus nathusii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 17355, + "scientific_name": "Pipistrellus papuanus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 17356, + "scientific_name": "Pipistrellus paterculus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 17358, + "scientific_name": "Pipistrellus permixtus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 85333513, + "scientific_name": "Pipistrellus pipistrellus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136649, + "scientific_name": "Pipistrellus pygmaeus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136646, + "scientific_name": "Pipistrellus raceyi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 17361, + "scientific_name": "Pipistrellus rueppellii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 17362, + "scientific_name": "Pipistrellus rusticus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 17364, + "scientific_name": "Pipistrellus stenopterus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 17365, + "scientific_name": "Pipistrellus sturdeei", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EX" + }, + { + "taxonid": 186170680, + "scientific_name": "Pipistrellus tenuis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 17369, + "scientific_name": "Pipistrellus wattsi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 17370, + "scientific_name": "Pipistrellus westralis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 17400, + "scientific_name": "Pithecheir melanurus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 136834, + "scientific_name": "Pithecheirops otion", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 17401, + "scientific_name": "Pithecheir parvus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 17402, + "scientific_name": "Pithecia aequatorialis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41567, + "scientific_name": "Pithecia albicans", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 70609874, + "scientific_name": "Pithecia cazuzai", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 43943, + "scientific_name": "Pithecia chrysocephala", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 70606542, + "scientific_name": "Pithecia hirsuta", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 70609849, + "scientific_name": "Pithecia inusta", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 70610758, + "scientific_name": "Pithecia irrorata", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 70609893, + "scientific_name": "Pithecia isabela", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 17407, + "scientific_name": "Pithecia milleri", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 70610693, + "scientific_name": "Pithecia mittermeieri", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 70609726, + "scientific_name": "Pithecia monachus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 39955, + "scientific_name": "Pithecia napensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 70610729, + "scientific_name": "Pithecia pissinattii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 43942, + "scientific_name": "Pithecia pithecia", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 70610711, + "scientific_name": "Pithecia rylandsi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 43946, + "scientific_name": "Pithecia vanzolinii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 17460, + "scientific_name": "Plagiodontia aedium", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 17462, + "scientific_name": "Plagiodontia ipnaeum", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EX" + }, + { + "taxonid": 40533, + "scientific_name": "Planigale gilesi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 40534, + "scientific_name": "Planigale ingrami", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 40535, + "scientific_name": "Planigale maculata", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 17474, + "scientific_name": "Planigale novaeguineae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 40536, + "scientific_name": "Planigale tenuirostris", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 17481, + "scientific_name": "Platacanthomys lasiurus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 17487, + "scientific_name": "Platalina genovensium", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 41756, + "scientific_name": "Platanista gangetica", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 41757, + "scientific_name": "Platanista minor", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 44692, + "scientific_name": "Platymops setiger", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136203, + "scientific_name": "Platyrrhinus albericoi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 88160255, + "scientific_name": "Platyrrhinus angustirostris", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 88160364, + "scientific_name": "Platyrrhinus aquilus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 17566, + "scientific_name": "Platyrrhinus aurarius", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 17567, + "scientific_name": "Platyrrhinus brachycephalus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 17568, + "scientific_name": "Platyrrhinus chocoensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 88160389, + "scientific_name": "Platyrrhinus dorsalis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 88160339, + "scientific_name": "Platyrrhinus fusciventris", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 88159886, + "scientific_name": "Platyrrhinus helleri", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 88160214, + "scientific_name": "Platyrrhinus incarum", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 17571, + "scientific_name": "Platyrrhinus infuscus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136232, + "scientific_name": "Platyrrhinus ismaeli", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 17565, + "scientific_name": "Platyrrhinus lineatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136577, + "scientific_name": "Platyrrhinus masu", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136378, + "scientific_name": "Platyrrhinus matapalensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 136317, + "scientific_name": "Platyrrhinus nigellus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 88160517, + "scientific_name": "Platyrrhinus nitelinea", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 17572, + "scientific_name": "Platyrrhinus recifinus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 95908089, + "scientific_name": "Platyrrhinus umbratus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 17574, + "scientific_name": "Platyrrhinus vittatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 85534953, + "scientific_name": "Plecotus ariel", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 85535522, + "scientific_name": "Plecotus auritus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 85533333, + "scientific_name": "Plecotus austriacus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 44930, + "scientific_name": "Plecotus balensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 44931, + "scientific_name": "Plecotus christii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 85537505, + "scientific_name": "Plecotus homochrous", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 136473, + "scientific_name": "Plecotus kolombatovici", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 85535146, + "scientific_name": "Plecotus kozlovi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136229, + "scientific_name": "Plecotus macrobullaris", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136598, + "scientific_name": "Plecotus ognevi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136664, + "scientific_name": "Plecotus sacrimontis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136503, + "scientific_name": "Plecotus sardus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 85535363, + "scientific_name": "Plecotus strelkovi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 17601, + "scientific_name": "Plecotus taivanus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 215482954, + "scientific_name": "Plecotus teneriffae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 85535176, + "scientific_name": "Plecotus turkmenicus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 85535265, + "scientific_name": "Plecotus wardi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136815, + "scientific_name": "Plecturocebus aureipalatii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41560, + "scientific_name": "Plecturocebus baptista", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41561, + "scientific_name": "Plecturocebus bernhardi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41558, + "scientific_name": "Plecturocebus brunneus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 41552, + "scientific_name": "Plecturocebus caligatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 14699281, + "scientific_name": "Plecturocebus caquetensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 41557, + "scientific_name": "Plecturocebus cinerascens", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 127530593, + "scientific_name": "Plecturocebus cupreus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41553, + "scientific_name": "Plecturocebus discolor", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41548, + "scientific_name": "Plecturocebus donacophilus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 3549, + "scientific_name": "Plecturocebus dubius", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 172272064, + "scientific_name": "Plecturocebus grovesi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 41559, + "scientific_name": "Plecturocebus hoffmannsi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 127530569, + "scientific_name": "Plecturocebus miltoni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 41550, + "scientific_name": "Plecturocebus modestus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 41556, + "scientific_name": "Plecturocebus moloch", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 3553, + "scientific_name": "Plecturocebus oenanthe", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 3554, + "scientific_name": "Plecturocebus olallae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 39928, + "scientific_name": "Plecturocebus ornatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 41549, + "scientific_name": "Plecturocebus pallescens", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 172274032, + "scientific_name": "Plecturocebus parecis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 41555, + "scientific_name": "Plecturocebus stephennashi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 127530624, + "scientific_name": "Plecturocebus toppini", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 127530581, + "scientific_name": "Plecturocebus urubambensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 70330181, + "scientific_name": "Plecturocebus vieirai", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 17618, + "scientific_name": "Plerotes anchietae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 17829, + "scientific_name": "Podogymnura aureospinula", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 17828, + "scientific_name": "Podogymnura truei", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 17830, + "scientific_name": "Podomys floridanus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 17831, + "scientific_name": "Podoxymys roraimae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 41662, + "scientific_name": "Poecilogale albinucha", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41292, + "scientific_name": "Poelagus marjorita", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136660, + "scientific_name": "Pogonomelomys brassi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 17877, + "scientific_name": "Pogonomelomys bruijnii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 17878, + "scientific_name": "Pogonomelomys mayeri", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 17882, + "scientific_name": "Pogonomys championi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 136763, + "scientific_name": "Pogonomys fergussoniensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 17883, + "scientific_name": "Pogonomys loriae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 17884, + "scientific_name": "Pogonomys macrourus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 17885, + "scientific_name": "Pogonomys sylvestris", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 44165, + "scientific_name": "Poiana leightoni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 41704, + "scientific_name": "Poiana richardsonii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41787, + "scientific_name": "Poliocitellus franklinii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 121097935, + "scientific_name": "Pongo abelii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 17975, + "scientific_name": "Pongo pygmaeus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 63544, + "scientific_name": "Pongo pygmaeus ssp. morio", + "subspecies": "morio", + "rank": "ssp.", + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 39781, + "scientific_name": "Pongo pygmaeus ssp. pygmaeus", + "subspecies": "pygmaeus", + "rank": "ssp.", + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 39782, + "scientific_name": "Pongo pygmaeus ssp. wurmbii", + "subspecies": "wurmbii", + "rank": "ssp.", + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 120588639, + "scientific_name": "Pongo tapanuliensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 17978, + "scientific_name": "Pontoporia blainvillei", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 41761, + "scientific_name": "Pontoporia blainvillei Rio Grande do Sul/Uruguay subpopulation", + "subspecies": null, + "rank": null, + "subpopulation": "Rio Grande do Sul/Uruguay subpopulation", + "category": "VU" + }, + { + "taxonid": 21172, + "scientific_name": "Porcula salvania", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 41770, + "scientific_name": "Potamochoerus larvatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41771, + "scientific_name": "Potamochoerus porcus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 18095, + "scientific_name": "Potamogale velox", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 18107, + "scientific_name": "Potorous gilbertii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 18102, + "scientific_name": "Potorous longipes", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 18103, + "scientific_name": "Potorous platyops", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EX" + }, + { + "taxonid": 41511, + "scientific_name": "Potorous tridactylus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 41679, + "scientific_name": "Potos flavus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 48018659, + "scientific_name": "Praomys coetzeei", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 45094, + "scientific_name": "Praomys daltoni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41210, + "scientific_name": "Praomys degraaffi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 18114, + "scientific_name": "Praomys delectorum", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 45095, + "scientific_name": "Praomys derooi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 18113, + "scientific_name": "Praomys hartwigi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 18115, + "scientific_name": "Praomys jacksoni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 45089, + "scientific_name": "Praomys lukolelae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 18116, + "scientific_name": "Praomys minor", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 18117, + "scientific_name": "Praomys misonnei", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 18118, + "scientific_name": "Praomys morio", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 18119, + "scientific_name": "Praomys mutoni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 41211, + "scientific_name": "Praomys obscurus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 45072, + "scientific_name": "Praomys petteri", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 18120, + "scientific_name": "Praomys rostratus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 48018502, + "scientific_name": "Praomys tullbergi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 45090, + "scientific_name": "Praomys verschureni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 39812, + "scientific_name": "Presbytis bicolor", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 39808, + "scientific_name": "Presbytis canicrus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 39803, + "scientific_name": "Presbytis chrysomelas", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 136857, + "scientific_name": "Presbytis chrysomelas ssp. chrysomelas", + "subspecies": "chrysomelas", + "rank": "ssp.", + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 39804, + "scientific_name": "Presbytis chrysomelas ssp. cruciger", + "subspecies": "cruciger", + "rank": "ssp.", + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 210369485, + "scientific_name": "Presbytis comata", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 39801, + "scientific_name": "Presbytis femoralis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 18133, + "scientific_name": "Presbytis fredericae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 18127, + "scientific_name": "Presbytis frontata", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 175648870, + "scientific_name": "Presbytis hosei", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 39811, + "scientific_name": "Presbytis melalophos", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 39813, + "scientific_name": "Presbytis mitrata", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 136500, + "scientific_name": "Presbytis natunae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 39805, + "scientific_name": "Presbytis percura", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 39815, + "scientific_name": "Presbytis potenziani", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 39806, + "scientific_name": "Presbytis robinsoni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 18131, + "scientific_name": "Presbytis rubicunda", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 39818, + "scientific_name": "Presbytis rubicunda ssp. carimatae", + "subspecies": "carimatae", + "rank": "ssp.", + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 39819, + "scientific_name": "Presbytis rubicunda ssp. chrysea", + "subspecies": "chrysea", + "rank": "ssp.", + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 39820, + "scientific_name": "Presbytis rubicunda ssp. ignita", + "subspecies": "ignita", + "rank": "ssp.", + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 39817, + "scientific_name": "Presbytis rubicunda ssp. rubicunda", + "subspecies": "rubicunda", + "rank": "ssp.", + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 136919, + "scientific_name": "Presbytis rubicunda ssp. rubida", + "subspecies": "rubida", + "rank": "ssp.", + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 39810, + "scientific_name": "Presbytis sabana", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 18134, + "scientific_name": "Presbytis siamensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 39822, + "scientific_name": "Presbytis siamensis ssp. cana", + "subspecies": "cana", + "rank": "ssp.", + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 39824, + "scientific_name": "Presbytis siamensis ssp. paenulata", + "subspecies": "paenulata", + "rank": "ssp.", + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 39825, + "scientific_name": "Presbytis siamensis ssp. rhionis", + "subspecies": "rhionis", + "rank": "ssp.", + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 39821, + "scientific_name": "Presbytis siamensis ssp. siamensis", + "subspecies": "siamensis", + "rank": "ssp.", + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 39816, + "scientific_name": "Presbytis siberu", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 136912, + "scientific_name": "Presbytis sumatranus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 18132, + "scientific_name": "Presbytis thomasi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 18144, + "scientific_name": "Priodontes maximus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 18146, + "scientific_name": "Prionailurus bengalensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 18151, + "scientific_name": "Prionailurus bengalensis ssp. iriomotensis", + "subspecies": "iriomotensis", + "rank": "ssp.", + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 136889, + "scientific_name": "Prionailurus bengalensis ssp. rabori", + "subspecies": "rabori", + "rank": "ssp.", + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 18148, + "scientific_name": "Prionailurus planiceps", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 18149, + "scientific_name": "Prionailurus rubiginosus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 18150, + "scientific_name": "Prionailurus viverrinus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 41705, + "scientific_name": "Prionodon linsang", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41706, + "scientific_name": "Prionodon pardicolor", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 18164, + "scientific_name": "Prionomys batesi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 18232, + "scientific_name": "Procapra gutturosa", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 18231, + "scientific_name": "Procapra picticaudata", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 18230, + "scientific_name": "Procapra przewalskii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 41766, + "scientific_name": "Procavia capensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 18245, + "scientific_name": "Procolobus verus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 41685, + "scientific_name": "Procyon cancrivorus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41686, + "scientific_name": "Procyon lotor", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 18267, + "scientific_name": "Procyon pygmaeus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 18275, + "scientific_name": "Proechimys brevicauda", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 18276, + "scientific_name": "Proechimys canicollis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 18278, + "scientific_name": "Proechimys chrysaeolus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 18279, + "scientific_name": "Proechimys cuvieri", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 18280, + "scientific_name": "Proechimys decumanus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 29463, + "scientific_name": "Proechimys echinothrix", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 29466, + "scientific_name": "Proechimys gardneri", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 18282, + "scientific_name": "Proechimys goeldii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 18284, + "scientific_name": "Proechimys guairae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 18277, + "scientific_name": "Proechimys guyannensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 18287, + "scientific_name": "Proechimys hoplomyoides", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 29464, + "scientific_name": "Proechimys kulinae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 18289, + "scientific_name": "Proechimys longicaudatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 18291, + "scientific_name": "Proechimys mincae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 18293, + "scientific_name": "Proechimys oconnelli", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 29465, + "scientific_name": "Proechimys pattoni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 18296, + "scientific_name": "Proechimys quadruplicatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 18294, + "scientific_name": "Proechimys roberti", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 18297, + "scientific_name": "Proechimys semispinosus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 18299, + "scientific_name": "Proechimys simonsi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 18300, + "scientific_name": "Proechimys steerei", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 18301, + "scientific_name": "Proechimys trinitatis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 18305, + "scientific_name": "Proedromys bedfordi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 136459, + "scientific_name": "Proedromys liangshanensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 18338, + "scientific_name": "Prolagus sardus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EX" + }, + { + "taxonid": 9674, + "scientific_name": "Prolemur simus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 18339, + "scientific_name": "Prometheomys schaposchnikowi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 88087651, + "scientific_name": "Promops centralis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 88087551, + "scientific_name": "Promops davisoni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 18341, + "scientific_name": "Promops nasutus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41293, + "scientific_name": "Pronolagus crassicaudatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41294, + "scientific_name": "Pronolagus randensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41295, + "scientific_name": "Pronolagus rupestris", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136713, + "scientific_name": "Pronolagus saundersiae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 18360, + "scientific_name": "Propithecus candidus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 18355, + "scientific_name": "Propithecus coquereli", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 18356, + "scientific_name": "Propithecus coronatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 18357, + "scientific_name": "Propithecus deckenii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 18358, + "scientific_name": "Propithecus diadema", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 18359, + "scientific_name": "Propithecus edwardsi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 18361, + "scientific_name": "Propithecus perrieri", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 18352, + "scientific_name": "Propithecus tattersalli", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 18354, + "scientific_name": "Propithecus verreauxi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 18362, + "scientific_name": "Prosciurillus abstrusus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 112300360, + "scientific_name": "Prosciurillus alstoni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 112297404, + "scientific_name": "Prosciurillus leucomus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 18364, + "scientific_name": "Prosciurillus murinus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136574, + "scientific_name": "Prosciurillus rosenbergii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 112298450, + "scientific_name": "Prosciurillus topapuensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 18365, + "scientific_name": "Prosciurillus weberi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 18372, + "scientific_name": "Proteles cristata", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13119, + "scientific_name": "Protochromys fellowsi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 18385, + "scientific_name": "Protoxerus aubinnii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 18386, + "scientific_name": "Protoxerus stangeri", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 18418, + "scientific_name": "Psammomys obesus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 18419, + "scientific_name": "Psammomys vexillaris", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 40636, + "scientific_name": "Pseudantechinus bilarni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 40537, + "scientific_name": "Pseudantechinus macdonnellensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 18447, + "scientific_name": "Pseudantechinus mimulus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 40538, + "scientific_name": "Pseudantechinus ningbing", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136620, + "scientific_name": "Pseudantechinus roryi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 40539, + "scientific_name": "Pseudantechinus woolleyae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 46205597, + "scientific_name": "Pseudoberylmys muongbangensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 18492, + "scientific_name": "Pseudocheirus occidentalis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 40581, + "scientific_name": "Pseudocheirus peregrinus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 18503, + "scientific_name": "Pseudochirops albertisii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 18502, + "scientific_name": "Pseudochirops archeri", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 18504, + "scientific_name": "Pseudochirops corinnae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 40582, + "scientific_name": "Pseudochirops coronatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 18505, + "scientific_name": "Pseudochirops cupreus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 40638, + "scientific_name": "Pseudochirulus canescens", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 18507, + "scientific_name": "Pseudochirulus caroli", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 18508, + "scientific_name": "Pseudochirulus cinereus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 40639, + "scientific_name": "Pseudochirulus forbesi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 18509, + "scientific_name": "Pseudochirulus herbertensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136699, + "scientific_name": "Pseudochirulus larvatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 40640, + "scientific_name": "Pseudochirulus mayeri", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 40641, + "scientific_name": "Pseudochirulus schlegeli", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 45958680, + "scientific_name": "Pseudohydromys berniceae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 48020754, + "scientific_name": "Pseudohydromys carlae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 48265607, + "scientific_name": "Pseudohydromys eleanorae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 48019838, + "scientific_name": "Pseudohydromys ellermani", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 14528, + "scientific_name": "Pseudohydromys fuscus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136523, + "scientific_name": "Pseudohydromys germani", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 48265359, + "scientific_name": "Pseudohydromys murinus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 42646, + "scientific_name": "Pseudohydromys musseri", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 48266448, + "scientific_name": "Pseudohydromys occidentalis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 45958683, + "scientific_name": "Pseudohydromys patriciae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 48020705, + "scientific_name": "Pseudohydromys pumehanae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 45958686, + "scientific_name": "Pseudohydromys sandrae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 61513537, + "scientific_name": "Pseudois nayaur", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 18535, + "scientific_name": "Pseudois nayaur ssp. schaeferi", + "subspecies": "schaeferi", + "rank": "ssp.", + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 18558, + "scientific_name": "Pseudomys albocinereus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 18559, + "scientific_name": "Pseudomys apodemoides", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 75927882, + "scientific_name": "Pseudomys auritus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EX" + }, + { + "taxonid": 75927871, + "scientific_name": "Pseudomys australis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 42647, + "scientific_name": "Pseudomys bolami", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136808, + "scientific_name": "Pseudomys calabyi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 42648, + "scientific_name": "Pseudomys chapmani", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 18561, + "scientific_name": "Pseudomys delicatulus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 18562, + "scientific_name": "Pseudomys desertor", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 18549, + "scientific_name": "Pseudomys fieldi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 18550, + "scientific_name": "Pseudomys fumeus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 18564, + "scientific_name": "Pseudomys glaucus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EX" + }, + { + "taxonid": 18551, + "scientific_name": "Pseudomys gouldii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EX" + }, + { + "taxonid": 18565, + "scientific_name": "Pseudomys gracilicaudatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 18566, + "scientific_name": "Pseudomys hermannsburgensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 18567, + "scientific_name": "Pseudomys higginsi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 18568, + "scientific_name": "Pseudomys johnsoni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 18570, + "scientific_name": "Pseudomys nanus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 18552, + "scientific_name": "Pseudomys novaehollandiae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 18553, + "scientific_name": "Pseudomys occidentalis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 18554, + "scientific_name": "Pseudomys oralis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 18571, + "scientific_name": "Pseudomys patrius", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 18555, + "scientific_name": "Pseudomys pilligaensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 18557, + "scientific_name": "Pseudomys shortridgei", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 18596, + "scientific_name": "Pseudorca crassidens", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 18597, + "scientific_name": "Pseudoryx nghetinhensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 18598, + "scientific_name": "Pseudoryzomys simplex", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 18653, + "scientific_name": "Ptenochirus jagori", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 18654, + "scientific_name": "Ptenochirus minor", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 18656, + "scientific_name": "Pteralopex anceps", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 18657, + "scientific_name": "Pteralopex atrata", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 136587, + "scientific_name": "Pteralopex flanneryi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 18658, + "scientific_name": "Pteralopex pulchra", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 29473, + "scientific_name": "Pteralopex taki", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 18703, + "scientific_name": "Pteromyscus pulverulentus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 18701, + "scientific_name": "Pteromys momonga", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 18702, + "scientific_name": "Pteromys volans", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 18705, + "scientific_name": "Pteronotus davyi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 18706, + "scientific_name": "Pteronotus gymnonotus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 18707, + "scientific_name": "Pteronotus macleayii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 88018392, + "scientific_name": "Pteronotus mesoamericanus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136610, + "scientific_name": "Pteronotus paraguanensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 88017638, + "scientific_name": "Pteronotus parnellii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 18709, + "scientific_name": "Pteronotus personatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 18710, + "scientific_name": "Pteronotus quadridens", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 88018592, + "scientific_name": "Pteronotus rubiginosus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 18711, + "scientific_name": "Pteronura brasiliensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 18713, + "scientific_name": "Pteropus admiralitatum", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 18714, + "scientific_name": "Pteropus aldabrensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 18715, + "scientific_name": "Pteropus alecto", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 84882966, + "scientific_name": "Pteropus allenorum", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EX" + }, + { + "taxonid": 18716, + "scientific_name": "Pteropus anetianus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 136504, + "scientific_name": "Pteropus aruensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 18718, + "scientific_name": "Pteropus brunneus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EX" + }, + { + "taxonid": 18719, + "scientific_name": "Pteropus caniceps", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 84891540, + "scientific_name": "Pteropus capistratus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 99688187, + "scientific_name": "Pteropus chrysoproctus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 136397, + "scientific_name": "Pteropus cognatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 18721, + "scientific_name": "Pteropus conspicillatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 84931267, + "scientific_name": "Pteropus coxi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EX" + }, + { + "taxonid": 18722, + "scientific_name": "Pteropus dasymallus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 84883915, + "scientific_name": "Pteropus ennisae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 18723, + "scientific_name": "Pteropus faunulus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 18724, + "scientific_name": "Pteropus fundatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 18725, + "scientific_name": "Pteropus giganteus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 18726, + "scientific_name": "Pteropus gilliardorum", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 18727, + "scientific_name": "Pteropus griseus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 18728, + "scientific_name": "Pteropus howensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 18729, + "scientific_name": "Pteropus hypomelanus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 136841, + "scientific_name": "Pteropus intermedius", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 136528, + "scientific_name": "Pteropus keyensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 18732, + "scientific_name": "Pteropus livingstonii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 18733, + "scientific_name": "Pteropus lombocensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 18773, + "scientific_name": "Pteropus loochoensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 18734, + "scientific_name": "Pteropus lylei", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 18735, + "scientific_name": "Pteropus macrotis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 18736, + "scientific_name": "Pteropus mahaganus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 188566753, + "scientific_name": "Pteropus mariannus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 18739, + "scientific_name": "Pteropus melanopogon", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 18740, + "scientific_name": "Pteropus melanotus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 18741, + "scientific_name": "Pteropus molossinus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 18742, + "scientific_name": "Pteropus neohibernicus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 18743, + "scientific_name": "Pteropus niger", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 18744, + "scientific_name": "Pteropus nitendiensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 18745, + "scientific_name": "Pteropus ocularis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 18746, + "scientific_name": "Pteropus ornatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 85043053, + "scientific_name": "Pteropus pelagicus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 118093652, + "scientific_name": "Pteropus pelewensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 136387, + "scientific_name": "Pteropus pelewensis ssp. pelewensis", + "subspecies": "pelewensis", + "rank": "ssp.", + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 136246, + "scientific_name": "Pteropus pelewensis ssp. yapensis", + "subspecies": "yapensis", + "rank": "ssp.", + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 18747, + "scientific_name": "Pteropus personatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 18749, + "scientific_name": "Pteropus pilosus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EX" + }, + { + "taxonid": 18750, + "scientific_name": "Pteropus pohlei", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 18751, + "scientific_name": "Pteropus poliocephalus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 18752, + "scientific_name": "Pteropus pselaphon", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 18753, + "scientific_name": "Pteropus pumilus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 18754, + "scientific_name": "Pteropus rayneri", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 136685, + "scientific_name": "Pteropus rennelli", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 18755, + "scientific_name": "Pteropus rodricensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 18756, + "scientific_name": "Pteropus rufus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 18757, + "scientific_name": "Pteropus samoensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 18758, + "scientific_name": "Pteropus scapulatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 18759, + "scientific_name": "Pteropus seychellensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 18760, + "scientific_name": "Pteropus speciosus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 18761, + "scientific_name": "Pteropus subniger", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EX" + }, + { + "taxonid": 18762, + "scientific_name": "Pteropus temminckii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 18763, + "scientific_name": "Pteropus tokudae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EX" + }, + { + "taxonid": 18764, + "scientific_name": "Pteropus tonganus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 18765, + "scientific_name": "Pteropus tuberculatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 136531, + "scientific_name": "Pteropus ualanus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 18766, + "scientific_name": "Pteropus vampyrus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 18767, + "scientific_name": "Pteropus vetulus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 18768, + "scientific_name": "Pteropus voeltzkowi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 18769, + "scientific_name": "Pteropus woodfordi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41491, + "scientific_name": "Ptilocercus lowii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 18847, + "scientific_name": "Pudu mephistophiles", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 18848, + "scientific_name": "Pudu puda", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 18868, + "scientific_name": "Puma concolor", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136287, + "scientific_name": "Punomys kofordi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 18880, + "scientific_name": "Punomys lemminus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 41669, + "scientific_name": "Pusa caspica", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 41672, + "scientific_name": "Pusa hispida", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41673, + "scientific_name": "Pusa hispida ssp. botnica", + "subspecies": "botnica", + "rank": "ssp.", + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 61382318, + "scientific_name": "Pusa hispida ssp. hispida", + "subspecies": "hispida", + "rank": "ssp.", + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41674, + "scientific_name": "Pusa hispida ssp. ladogensis", + "subspecies": "ladogensis", + "rank": "ssp.", + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 41677, + "scientific_name": "Pusa hispida ssp. ochotensis", + "subspecies": "ochotensis", + "rank": "ssp.", + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41675, + "scientific_name": "Pusa hispida ssp. saimensis", + "subspecies": "saimensis", + "rank": "ssp.", + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 41676, + "scientific_name": "Pusa sibirica", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 39827, + "scientific_name": "Pygathrix cinerea", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 39826, + "scientific_name": "Pygathrix nemaeus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 39828, + "scientific_name": "Pygathrix nigripes", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 18942, + "scientific_name": "Pygeretmus platyurus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 18943, + "scientific_name": "Pygeretmus pumilio", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 18944, + "scientific_name": "Pygeretmus zhitkovi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 18945, + "scientific_name": "Pygoderma bilabiatum", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 29742, + "scientific_name": "Rangifer tarandus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 19308, + "scientific_name": "Raphicerus campestris", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19306, + "scientific_name": "Raphicerus melanotis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19307, + "scientific_name": "Raphicerus sharpei", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19320, + "scientific_name": "Rattus adustus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 19361, + "scientific_name": "Rattus andamanensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19321, + "scientific_name": "Rattus annandalei", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136526, + "scientific_name": "Rattus arfakiensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 19322, + "scientific_name": "Rattus argentiventer", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136730, + "scientific_name": "Rattus arrogans", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19323, + "scientific_name": "Rattus baluensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136580, + "scientific_name": "Rattus blangorum", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 19324, + "scientific_name": "Rattus bontanus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 19325, + "scientific_name": "Rattus burrus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 19326, + "scientific_name": "Rattus colletti", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 112139816, + "scientific_name": "Rattus detentus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 19327, + "scientific_name": "Rattus elaphinus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 19328, + "scientific_name": "Rattus enganus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 19329, + "scientific_name": "Rattus everetti", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19330, + "scientific_name": "Rattus exulans", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19331, + "scientific_name": "Rattus feliceus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 19333, + "scientific_name": "Rattus fuscipes", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19318, + "scientific_name": "Rattus giluwensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19334, + "scientific_name": "Rattus hainaldi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 19335, + "scientific_name": "Rattus hoffmanni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19336, + "scientific_name": "Rattus hoogerwerfi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 19337, + "scientific_name": "Rattus jobiensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19338, + "scientific_name": "Rattus koopmani", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 19339, + "scientific_name": "Rattus korinchi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 19340, + "scientific_name": "Rattus leucopus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19341, + "scientific_name": "Rattus losea", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19342, + "scientific_name": "Rattus lugens", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 19343, + "scientific_name": "Rattus lutreolus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19344, + "scientific_name": "Rattus macleari", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EX" + }, + { + "taxonid": 19345, + "scientific_name": "Rattus marmosurus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19346, + "scientific_name": "Rattus mindorensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19347, + "scientific_name": "Rattus mollicomulus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19348, + "scientific_name": "Rattus montanus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 19349, + "scientific_name": "Rattus mordax", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19350, + "scientific_name": "Rattus morotaiensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19351, + "scientific_name": "Rattus nativitatis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EX" + }, + { + "taxonid": 45958849, + "scientific_name": "Rattus nikenii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 20758, + "scientific_name": "Rattus niobe", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19352, + "scientific_name": "Rattus nitidus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19353, + "scientific_name": "Rattus norvegicus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19354, + "scientific_name": "Rattus novaeguineae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136785, + "scientific_name": "Rattus omichlodes", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 19355, + "scientific_name": "Rattus osgoodi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19356, + "scientific_name": "Rattus palmarum", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 19357, + "scientific_name": "Rattus pelurus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 136632, + "scientific_name": "Rattus pococki", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19358, + "scientific_name": "Rattus praetor", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19370, + "scientific_name": "Rattus pyctoris", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19359, + "scientific_name": "Rattus ranjiniae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 19360, + "scientific_name": "Rattus rattus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 20759, + "scientific_name": "Rattus richardsoni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 136700, + "scientific_name": "Rattus salocco", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 136517, + "scientific_name": "Rattus satarae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 19362, + "scientific_name": "Rattus simalurensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 19363, + "scientific_name": "Rattus sordidus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19364, + "scientific_name": "Rattus steini", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19365, + "scientific_name": "Rattus stoicus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 19366, + "scientific_name": "Rattus tanezumi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19319, + "scientific_name": "Rattus tawitawiensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 19367, + "scientific_name": "Rattus timorensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 19368, + "scientific_name": "Rattus tiomanicus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19369, + "scientific_name": "Rattus tunneyi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 20760, + "scientific_name": "Rattus vandeuseni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 20761, + "scientific_name": "Rattus verecundus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19371, + "scientific_name": "Rattus villosissimus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19372, + "scientific_name": "Rattus xanthurus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 19376, + "scientific_name": "Ratufa affinis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 19377, + "scientific_name": "Ratufa bicolor", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 19378, + "scientific_name": "Ratufa indica", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19381, + "scientific_name": "Ratufa macroura", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 19390, + "scientific_name": "Redunca arundinum", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19391, + "scientific_name": "Redunca fulvorufula", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 19389, + "scientific_name": "Redunca fulvorufula ssp. adamauae", + "subspecies": "adamauae", + "rank": "ssp.", + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 19393, + "scientific_name": "Redunca fulvorufula ssp. chanleri", + "subspecies": "chanleri", + "rank": "ssp.", + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 19394, + "scientific_name": "Redunca fulvorufula ssp. fulvorufula", + "subspecies": "fulvorufula", + "rank": "ssp.", + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 19392, + "scientific_name": "Redunca redunca", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19399, + "scientific_name": "Reithrodon auritus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136821, + "scientific_name": "Reithrodontomys bakeri", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 19402, + "scientific_name": "Reithrodontomys brevirostris", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19403, + "scientific_name": "Reithrodontomys burti", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 19404, + "scientific_name": "Reithrodontomys chrysopsis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19405, + "scientific_name": "Reithrodontomys creper", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19406, + "scientific_name": "Reithrodontomys darienensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19407, + "scientific_name": "Reithrodontomys fulvescens", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19408, + "scientific_name": "Reithrodontomys gracilis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19409, + "scientific_name": "Reithrodontomys hirsutus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 42678, + "scientific_name": "Reithrodontomys humulis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19410, + "scientific_name": "Reithrodontomys megalotis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19411, + "scientific_name": "Reithrodontomys mexicanus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19412, + "scientific_name": "Reithrodontomys microdon", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19413, + "scientific_name": "Reithrodontomys montanus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 45959367, + "scientific_name": "Reithrodontomys musseri", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 19414, + "scientific_name": "Reithrodontomys paradoxus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 19401, + "scientific_name": "Reithrodontomys raviventris", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 19415, + "scientific_name": "Reithrodontomys rodriguezi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19416, + "scientific_name": "Reithrodontomys spectabilis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 19417, + "scientific_name": "Reithrodontomys sumichrasti", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19418, + "scientific_name": "Reithrodontomys tenuirostris", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 19419, + "scientific_name": "Reithrodontomys zacatecae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136583, + "scientific_name": "Reithrodon typicus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 112166182, + "scientific_name": "Rhabdomys bechuanae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 112168645, + "scientific_name": "Rhabdomys dilectus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 112168732, + "scientific_name": "Rhabdomys intermedius", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 112168517, + "scientific_name": "Rhabdomys pumilio", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136521, + "scientific_name": "Rhagomys longilingua", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19454, + "scientific_name": "Rhagomys rufescens", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 19474, + "scientific_name": "Rheithrosciurus macrotis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 19484, + "scientific_name": "Rheomys mexicanus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 19485, + "scientific_name": "Rheomys raptor", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19486, + "scientific_name": "Rheomys thomasi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 19487, + "scientific_name": "Rheomys underwoodi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19495, + "scientific_name": "Rhinoceros sondaicus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 19496, + "scientific_name": "Rhinoceros unicornis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 19520, + "scientific_name": "Rhinolophus acuminatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19521, + "scientific_name": "Rhinolophus adami", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 19522, + "scientific_name": "Rhinolophus affinis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19523, + "scientific_name": "Rhinolophus alcyone", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 84372137, + "scientific_name": "Rhinolophus arcuatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 40023, + "scientific_name": "Rhinolophus beddomei", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 84372084, + "scientific_name": "Rhinolophus belligerator", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 19515, + "scientific_name": "Rhinolophus blasii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19526, + "scientific_name": "Rhinolophus bocharicus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19527, + "scientific_name": "Rhinolophus borneensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19528, + "scientific_name": "Rhinolophus canuti", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 19529, + "scientific_name": "Rhinolophus capensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19530, + "scientific_name": "Rhinolophus celebensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 84372474, + "scientific_name": "Rhinolophus chiewkweeae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 19531, + "scientific_name": "Rhinolophus clivosus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19532, + "scientific_name": "Rhinolophus coelophyllus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19533, + "scientific_name": "Rhinolophus cognatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 64587154, + "scientific_name": "Rhinolophus cohenae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 40037, + "scientific_name": "Rhinolophus convexus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 19535, + "scientific_name": "Rhinolophus creaghi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 67369846, + "scientific_name": "Rhinolophus damarensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 67369483, + "scientific_name": "Rhinolophus darlingi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19537, + "scientific_name": "Rhinolophus deckenii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 19538, + "scientific_name": "Rhinolophus denti", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19539, + "scientific_name": "Rhinolophus eloquens", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19516, + "scientific_name": "Rhinolophus euryale", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 84372418, + "scientific_name": "Rhinolophus euryotis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19517, + "scientific_name": "Rhinolophus ferrumequinum", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136644, + "scientific_name": "Rhinolophus formosae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19541, + "scientific_name": "Rhinolophus fumigatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19542, + "scientific_name": "Rhinolophus guineensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 64586080, + "scientific_name": "Rhinolophus hildebrandtii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 44781, + "scientific_name": "Rhinolophus hilli", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 44782, + "scientific_name": "Rhinolophus hillorum", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 19518, + "scientific_name": "Rhinolophus hipposideros", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 84372666, + "scientific_name": "Rhinolophus huananus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 84376286, + "scientific_name": "Rhinolophus indorouxii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 19545, + "scientific_name": "Rhinolophus inops", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 82347204, + "scientific_name": "Rhinolophus kahuzi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 19577, + "scientific_name": "Rhinolophus keyensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 19546, + "scientific_name": "Rhinolophus landeri", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19547, + "scientific_name": "Rhinolophus lepidus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19548, + "scientific_name": "Rhinolophus luctus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 64588047, + "scientific_name": "Rhinolophus mabuensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 19549, + "scientific_name": "Rhinolophus maclaudi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 19550, + "scientific_name": "Rhinolophus macrotis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136410, + "scientific_name": "Rhinolophus madurensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 44783, + "scientific_name": "Rhinolophus maendeleo", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 19551, + "scientific_name": "Rhinolophus malayanus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19552, + "scientific_name": "Rhinolophus marshalli", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 84372245, + "scientific_name": "Rhinolophus mcintyrei", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 19553, + "scientific_name": "Rhinolophus megaphyllus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19519, + "scientific_name": "Rhinolophus mehelyi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 84384558, + "scientific_name": "Rhinolophus microglobosus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19554, + "scientific_name": "Rhinolophus mitratus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 136248, + "scientific_name": "Rhinolophus montanus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 64589126, + "scientific_name": "Rhinolophus mossambicus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19556, + "scientific_name": "Rhinolophus nereis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 19557, + "scientific_name": "Rhinolophus osgoodi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19558, + "scientific_name": "Rhinolophus paradoxolophus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19559, + "scientific_name": "Rhinolophus pearsonii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 85707170, + "scientific_name": "Rhinolophus perditus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 19560, + "scientific_name": "Rhinolophus philippinensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 84372306, + "scientific_name": "Rhinolophus proconsulis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 85707059, + "scientific_name": "Rhinolophus pusillus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19562, + "scientific_name": "Rhinolophus rex", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 136496, + "scientific_name": "Rhinolophus robinsoni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 84379218, + "scientific_name": "Rhinolophus rouxii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19564, + "scientific_name": "Rhinolophus rufus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 44784, + "scientific_name": "Rhinolophus ruwenzorii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 44785, + "scientific_name": "Rhinolophus sakejiensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 82347791, + "scientific_name": "Rhinolophus schnitzleri", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 19565, + "scientific_name": "Rhinolophus sedulus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 19566, + "scientific_name": "Rhinolophus shameli", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136631, + "scientific_name": "Rhinolophus shortridgei", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 136651, + "scientific_name": "Rhinolophus siamensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19567, + "scientific_name": "Rhinolophus silvestris", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 19568, + "scientific_name": "Rhinolophus simulator", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41529, + "scientific_name": "Rhinolophus sinicus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 64588371, + "scientific_name": "Rhinolophus smithersi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 84383122, + "scientific_name": "Rhinolophus stheno", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19570, + "scientific_name": "Rhinolophus subbadius", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19571, + "scientific_name": "Rhinolophus subrufus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 19572, + "scientific_name": "Rhinolophus swinnyi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 84372447, + "scientific_name": "Rhinolophus tatar", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 82348077, + "scientific_name": "Rhinolophus thailandensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19573, + "scientific_name": "Rhinolophus thomasi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19574, + "scientific_name": "Rhinolophus trifoliatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 19575, + "scientific_name": "Rhinolophus virgo", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 82346260, + "scientific_name": "Rhinolophus willardi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 82348701, + "scientific_name": "Rhinolophus xinanzhongguoensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 19576, + "scientific_name": "Rhinolophus yunanensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 44786, + "scientific_name": "Rhinolophus ziama", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 19589, + "scientific_name": "Rhinonicteris aurantia", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19591, + "scientific_name": "Rhinophylla alethina", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 19592, + "scientific_name": "Rhinophylla fischerae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19593, + "scientific_name": "Rhinophylla pumilio", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19594, + "scientific_name": "Rhinopithecus avunculus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 19597, + "scientific_name": "Rhinopithecus bieti", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 19595, + "scientific_name": "Rhinopithecus brelichi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 19596, + "scientific_name": "Rhinopithecus roxellana", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 39830, + "scientific_name": "Rhinopithecus roxellana ssp. hubeiensis", + "subspecies": "hubeiensis", + "rank": "ssp.", + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 39831, + "scientific_name": "Rhinopithecus roxellana ssp. qinlingensis", + "subspecies": "qinlingensis", + "rank": "ssp.", + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 39829, + "scientific_name": "Rhinopithecus roxellana ssp. roxellana", + "subspecies": "roxellana", + "rank": "ssp.", + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 13508501, + "scientific_name": "Rhinopithecus strykeri", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 82345555, + "scientific_name": "Rhinopoma cystops", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 82345696, + "scientific_name": "Rhinopoma hadramauticum", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 82345477, + "scientific_name": "Rhinopoma hardwickii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19601, + "scientific_name": "Rhinopoma macinnesi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 19600, + "scientific_name": "Rhinopoma microphyllum", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19602, + "scientific_name": "Rhinopoma muscatellum", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 42460, + "scientific_name": "Rhinosciurus laticaudatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 113476528, + "scientific_name": "Rhipidomys albujai", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 19605, + "scientific_name": "Rhipidomys austrinus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136357, + "scientific_name": "Rhipidomys cariri", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 19606, + "scientific_name": "Rhipidomys caucensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 19607, + "scientific_name": "Rhipidomys couesi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136195, + "scientific_name": "Rhipidomys emiliae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19608, + "scientific_name": "Rhipidomys fulviventer", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136759, + "scientific_name": "Rhipidomys gardneri", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 47778330, + "scientific_name": "Rhipidomys ipukensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 48297964, + "scientific_name": "Rhipidomys itoan", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19609, + "scientific_name": "Rhipidomys latimanus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19610, + "scientific_name": "Rhipidomys leucodactylus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19611, + "scientific_name": "Rhipidomys macconnelli", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136422, + "scientific_name": "Rhipidomys macrurus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19612, + "scientific_name": "Rhipidomys mastacalis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136210, + "scientific_name": "Rhipidomys modicus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 47778059, + "scientific_name": "Rhipidomys nitela", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19614, + "scientific_name": "Rhipidomys ochrogaster", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 48297988, + "scientific_name": "Rhipidomys tribei", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 19616, + "scientific_name": "Rhipidomys venezuelae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19617, + "scientific_name": "Rhipidomys venustus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19618, + "scientific_name": "Rhipidomys wetzeli", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19645, + "scientific_name": "Rhizomys pruinosus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19646, + "scientific_name": "Rhizomys sinensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19647, + "scientific_name": "Rhizomys sumatrensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136810, + "scientific_name": "Rhogeessa aeneus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 88151726, + "scientific_name": "Rhogeessa bickhami", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19680, + "scientific_name": "Rhogeessa genowaysi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 136220, + "scientific_name": "Rhogeessa hussoni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 88151760, + "scientific_name": "Rhogeessa io", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 88151749, + "scientific_name": "Rhogeessa menchuae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 19682, + "scientific_name": "Rhogeessa minutilla", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 19683, + "scientific_name": "Rhogeessa mira", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 19684, + "scientific_name": "Rhogeessa parvula", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19685, + "scientific_name": "Rhogeessa tumida", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 88151777, + "scientific_name": "Rhogeessa velilla", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 19686, + "scientific_name": "Rhombomys opimus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19705, + "scientific_name": "Rhynchocyon chrysopygus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 19709, + "scientific_name": "Rhynchocyon cirnei", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19708, + "scientific_name": "Rhynchocyon petersi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136309, + "scientific_name": "Rhynchocyon udzungwensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 41623, + "scientific_name": "Rhynchogale melleri", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19710, + "scientific_name": "Rhyncholestes raphanurus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 19711, + "scientific_name": "Rhynchomeles prattorum", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 136724, + "scientific_name": "Rhynchomys banahao", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19712, + "scientific_name": "Rhynchomys isarogensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 19713, + "scientific_name": "Rhynchomys soricoides", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 136489, + "scientific_name": "Rhynchomys tapulao", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 19714, + "scientific_name": "Rhynchonycteris naso", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 7935, + "scientific_name": "Rhyneptesicus nasutus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19742, + "scientific_name": "Romerolagus diazi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 29730, + "scientific_name": "Rousettus aegyptiacus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19754, + "scientific_name": "Rousettus amplexicaudatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19755, + "scientific_name": "Rousettus celebensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19758, + "scientific_name": "Rousettus lanosus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19756, + "scientific_name": "Rousettus leschenaultii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 136593, + "scientific_name": "Rousettus linduensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 19750, + "scientific_name": "Rousettus madagascariensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 19757, + "scientific_name": "Rousettus obliviosus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 19751, + "scientific_name": "Rousettus spinalatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 19762, + "scientific_name": "Rubrisciurus rubriventer", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 4257, + "scientific_name": "Rucervus duvaucelii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 4265, + "scientific_name": "Rucervus eldii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 4288, + "scientific_name": "Rucervus schomburgki", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EX" + }, + { + "taxonid": 136791, + "scientific_name": "Rungwecebus kipunji", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 19771, + "scientific_name": "Rupicapra pyrenaica", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 39255, + "scientific_name": "Rupicapra rupicapra", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 4273, + "scientific_name": "Rusa alfredi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 4274, + "scientific_name": "Rusa marianna", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 41789, + "scientific_name": "Rusa timorensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 41790, + "scientific_name": "Rusa unicolor", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 19790, + "scientific_name": "Ruwenzorisorex suncoides", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 19799, + "scientific_name": "Saccolaimus flaviventris", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19800, + "scientific_name": "Saccolaimus mixtus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 19801, + "scientific_name": "Saccolaimus peli", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19802, + "scientific_name": "Saccolaimus saccolaimus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136420, + "scientific_name": "Saccopteryx antioquensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 19804, + "scientific_name": "Saccopteryx bilineata", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19805, + "scientific_name": "Saccopteryx canescens", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19806, + "scientific_name": "Saccopteryx gymnura", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 19807, + "scientific_name": "Saccopteryx leptura", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19808, + "scientific_name": "Saccostomus campestris", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19809, + "scientific_name": "Saccostomus mearnsi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 40644, + "scientific_name": "Saguinus bicolor", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 41522, + "scientific_name": "Saguinus geoffroyi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 39948, + "scientific_name": "Saguinus imperator", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19827, + "scientific_name": "Saguinus imperator ssp. imperator", + "subspecies": "imperator", + "rank": "ssp.", + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 43962, + "scientific_name": "Saguinus imperator ssp. subgrisescens", + "subspecies": "subgrisescens", + "rank": "ssp.", + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41523, + "scientific_name": "Saguinus inustus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41524, + "scientific_name": "Saguinus labiatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 43959, + "scientific_name": "Saguinus labiatus ssp. labiatus", + "subspecies": "labiatus", + "rank": "ssp.", + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 43961, + "scientific_name": "Saguinus labiatus ssp. rufiventer", + "subspecies": "rufiventer", + "rank": "ssp.", + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 43960, + "scientific_name": "Saguinus labiatus ssp. thomasi", + "subspecies": "thomasi", + "rank": "ssp.", + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19819, + "scientific_name": "Saguinus leucopus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 42695, + "scientific_name": "Saguinus martinsi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 136888, + "scientific_name": "Saguinus martinsi ssp. martinsi", + "subspecies": "martinsi", + "rank": "ssp.", + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 43963, + "scientific_name": "Saguinus martinsi ssp. ochraceus", + "subspecies": "ochraceus", + "rank": "ssp.", + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 135429, + "scientific_name": "Saguinus melanoleucus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41525, + "scientific_name": "Saguinus midas", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41526, + "scientific_name": "Saguinus mystax", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 43956, + "scientific_name": "Saguinus mystax ssp. mystax", + "subspecies": "mystax", + "rank": "ssp.", + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 43957, + "scientific_name": "Saguinus mystax ssp. pileatus", + "subspecies": "pileatus", + "rank": "ssp.", + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 43958, + "scientific_name": "Saguinus mystax ssp. pluto", + "subspecies": "pluto", + "rank": "ssp.", + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 160901052, + "scientific_name": "Saguinus niger", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 19823, + "scientific_name": "Saguinus oedipus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 70610874, + "scientific_name": "Saguinus ursulus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 19832, + "scientific_name": "Saiga tatarica", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 19833, + "scientific_name": "Saiga tatarica ssp. mongolica", + "subspecies": "mongolica", + "rank": "ssp.", + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 19834, + "scientific_name": "Saiga tatarica ssp. tatarica", + "subspecies": "tatarica", + "rank": "ssp.", + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 41536, + "scientific_name": "Saimiri boliviensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 43964, + "scientific_name": "Saimiri boliviensis ssp. boliviensis", + "subspecies": "boliviensis", + "rank": "ssp.", + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 43965, + "scientific_name": "Saimiri boliviensis ssp. peruviensis", + "subspecies": "peruviensis", + "rank": "ssp.", + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 160940148, + "scientific_name": "Saimiri cassiquiarensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 43969, + "scientific_name": "Saimiri cassiquiarensis ssp. albigena", + "subspecies": "albigena", + "rank": "ssp.", + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 43970, + "scientific_name": "Saimiri cassiquiarensis ssp. cassiquiarensis", + "subspecies": "cassiquiarensis", + "rank": "ssp.", + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 43971, + "scientific_name": "Saimiri cassiquiarensis ssp. macrodon", + "subspecies": "macrodon", + "rank": "ssp.", + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 70610928, + "scientific_name": "Saimiri collinsi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19836, + "scientific_name": "Saimiri oerstedii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 19841, + "scientific_name": "Saimiri oerstedii ssp. citrinellus", + "subspecies": "citrinellus", + "rank": "ssp.", + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 19840, + "scientific_name": "Saimiri oerstedii ssp. oerstedii", + "subspecies": "oerstedii", + "rank": "ssp.", + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 43968, + "scientific_name": "Saimiri sciureus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41538, + "scientific_name": "Saimiri ustus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 19839, + "scientific_name": "Saimiri vanzolinii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 19852, + "scientific_name": "Salanoia concolor", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 136228, + "scientific_name": "Salinomys delicatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 19866, + "scientific_name": "Salpingotulus michaelis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 19863, + "scientific_name": "Salpingotus crassicauda", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19864, + "scientific_name": "Salpingotus heptneri", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 19865, + "scientific_name": "Salpingotus kozlovi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19867, + "scientific_name": "Salpingotus pallidus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 6664, + "scientific_name": "Santamartamys rufodorsalis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 172351505, + "scientific_name": "Sapajus apella", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 43933, + "scientific_name": "Sapajus apella ssp. apella", + "subspecies": "apella", + "rank": "ssp.", + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 4079, + "scientific_name": "Sapajus apella ssp. margaritae", + "subspecies": "margaritae", + "rank": "ssp.", + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 136366, + "scientific_name": "Sapajus cay", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 136253, + "scientific_name": "Sapajus flavius", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 136346, + "scientific_name": "Sapajus libidinosus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 136717, + "scientific_name": "Sapajus nigritus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 160945956, + "scientific_name": "Sapajus nigritus ssp. cucullatus", + "subspecies": "cucullatus", + "rank": "ssp.", + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 160946025, + "scientific_name": "Sapajus nigritus ssp. nigritus", + "subspecies": "nigritus", + "rank": "ssp.", + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 42697, + "scientific_name": "Sapajus robustus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 4074, + "scientific_name": "Sapajus xanthosternos", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 40540, + "scientific_name": "Sarcophilus harrisii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 44693, + "scientific_name": "Sauromys petrophilus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136806, + "scientific_name": "Saxatilomys paulinae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 41471, + "scientific_name": "Scalopus aquaticus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41472, + "scientific_name": "Scapanulus oweni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41473, + "scientific_name": "Scapanus latimanus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41474, + "scientific_name": "Scapanus orarius", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41475, + "scientific_name": "Scapanus townsendii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136549, + "scientific_name": "Scapteromys aquaticus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19945, + "scientific_name": "Scapteromys tumidus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41476, + "scientific_name": "Scaptochirus moschatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41477, + "scientific_name": "Scaptonyx fusicaudus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 47783095, + "scientific_name": "Scarturus elater", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 854, + "scientific_name": "Scarturus euphratica", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 47781395, + "scientific_name": "Scarturus toussi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 860, + "scientific_name": "Scarturus vinogradovi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136326, + "scientific_name": "Scarturus williamsi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19997, + "scientific_name": "Sciurillus pusillus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 8785, + "scientific_name": "Sciurocheirus alleni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 40001, + "scientific_name": "Sciurocheirus alleni ssp. alleni", + "subspecies": "alleni", + "rank": "ssp.", + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 136854, + "scientific_name": "Sciurocheirus alleni ssp. cameronensis", + "subspecies": "cameronensis", + "rank": "ssp.", + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 136214, + "scientific_name": "Sciurocheirus gabonensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 91979463, + "scientific_name": "Sciurocheirus makandensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 19998, + "scientific_name": "Sciurotamias davidianus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19999, + "scientific_name": "Sciurotamias forresti", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 42461, + "scientific_name": "Sciurus aberti", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 20003, + "scientific_name": "Sciurus aestuans", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 20004, + "scientific_name": "Sciurus alleni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 20000, + "scientific_name": "Sciurus anomalus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 20005, + "scientific_name": "Sciurus arizonensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 20006, + "scientific_name": "Sciurus aureogaster", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 42462, + "scientific_name": "Sciurus carolinensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 20007, + "scientific_name": "Sciurus colliaei", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 20001, + "scientific_name": "Sciurus deppei", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 20008, + "scientific_name": "Sciurus flammifer", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 20009, + "scientific_name": "Sciurus gilvigularis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 20010, + "scientific_name": "Sciurus granatensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 20011, + "scientific_name": "Sciurus griseus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 20012, + "scientific_name": "Sciurus ignitus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 20013, + "scientific_name": "Sciurus igniventris", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 20014, + "scientific_name": "Sciurus lis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 20015, + "scientific_name": "Sciurus nayaritensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 20016, + "scientific_name": "Sciurus niger", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 20017, + "scientific_name": "Sciurus oculatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 20018, + "scientific_name": "Sciurus pucheranii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 20019, + "scientific_name": "Sciurus pyrrhinus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 20020, + "scientific_name": "Sciurus richmondi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 20021, + "scientific_name": "Sciurus sanborni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 20022, + "scientific_name": "Sciurus spadiceus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 20023, + "scientific_name": "Sciurus stramineus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 20024, + "scientific_name": "Sciurus variegatoides", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 20025, + "scientific_name": "Sciurus vulgaris", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 20026, + "scientific_name": "Sciurus yucatanensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 20033, + "scientific_name": "Scleronycteris ega", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 20036, + "scientific_name": "Scolomys melanops", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 20037, + "scientific_name": "Scolomys ucayalensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 14946, + "scientific_name": "Scoteanax rueppellii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 20052, + "scientific_name": "Scotinomys teguina", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 20053, + "scientific_name": "Scotinomys xerampelinus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 20054, + "scientific_name": "Scotoecus albofuscus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 20055, + "scientific_name": "Scotoecus hirundo", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 20056, + "scientific_name": "Scotoecus pallidus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 20058, + "scientific_name": "Scotomanes ornatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 84466436, + "scientific_name": "Scotonycteris bergmansi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 84466273, + "scientific_name": "Scotonycteris occidentalis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 84464403, + "scientific_name": "Scotonycteris zenkeri", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 84466713, + "scientific_name": "Scotophilus andrewreborii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 20064, + "scientific_name": "Scotophilus borbonicus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 20065, + "scientific_name": "Scotophilus celebensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 136612, + "scientific_name": "Scotophilus collinus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 20066, + "scientific_name": "Scotophilus dinganii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 84466810, + "scientific_name": "Scotophilus ejetai", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 20067, + "scientific_name": "Scotophilus heathii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 20068, + "scientific_name": "Scotophilus kuhlii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 20069, + "scientific_name": "Scotophilus leucogaster", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 84466826, + "scientific_name": "Scotophilus livingstonii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136774, + "scientific_name": "Scotophilus marovaza", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 20070, + "scientific_name": "Scotophilus nigrita", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 44934, + "scientific_name": "Scotophilus nucella", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 20071, + "scientific_name": "Scotophilus nux", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 20072, + "scientific_name": "Scotophilus robustus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136675, + "scientific_name": "Scotophilus tandrefana", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 84466859, + "scientific_name": "Scotophilus trujilloi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 20073, + "scientific_name": "Scotophilus viridis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 14942, + "scientific_name": "Scotorepens balstoni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 14943, + "scientific_name": "Scotorepens greyii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 14945, + "scientific_name": "Scotorepens orion", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 14947, + "scientific_name": "Scotorepens sanborni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 17338, + "scientific_name": "Scotozous dormeri", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41449, + "scientific_name": "Scutisorex somereni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 112390882, + "scientific_name": "Scutisorex thori", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 20089, + "scientific_name": "Sekeetamys calurus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 20102, + "scientific_name": "Selevinia betpakdalaensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 39833, + "scientific_name": "Semnopithecus ajax", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 39832, + "scientific_name": "Semnopithecus entellus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 39837, + "scientific_name": "Semnopithecus hector", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 167543916, + "scientific_name": "Semnopithecus hypoleucos", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 44723, + "scientific_name": "Semnopithecus hypoleucos ssp. achates", + "subspecies": "achates", + "rank": "ssp.", + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 167541339, + "scientific_name": "Semnopithecus hypoleucos ssp. hypoleucos", + "subspecies": "hypoleucos", + "rank": "ssp.", + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 167541667, + "scientific_name": "Semnopithecus hypoleucos ssp. iulus", + "subspecies": "iulus", + "rank": "ssp.", + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 44694, + "scientific_name": "Semnopithecus johnii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 167546892, + "scientific_name": "Semnopithecus priam", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 39834, + "scientific_name": "Semnopithecus priam ssp. anchises", + "subspecies": "anchises", + "rank": "ssp.", + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 39839, + "scientific_name": "Semnopithecus priam ssp. priam", + "subspecies": "priam", + "rank": "ssp.", + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 39841, + "scientific_name": "Semnopithecus priam ssp. thersites", + "subspecies": "thersites", + "rank": "ssp.", + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 39840, + "scientific_name": "Semnopithecus schistaceus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22042, + "scientific_name": "Semnopithecus vetulus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 39843, + "scientific_name": "Semnopithecus vetulus ssp. monticola", + "subspecies": "monticola", + "rank": "ssp.", + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 39844, + "scientific_name": "Semnopithecus vetulus ssp. nestor", + "subspecies": "nestor", + "rank": "ssp.", + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 39845, + "scientific_name": "Semnopithecus vetulus ssp. philbricki", + "subspecies": "philbricki", + "rank": "ssp.", + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 39842, + "scientific_name": "Semnopithecus vetulus ssp. vetulus", + "subspecies": "vetulus", + "rank": "ssp.", + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 40594, + "scientific_name": "Setifer setosus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 71529901, + "scientific_name": "Setirostris eleryi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 20165, + "scientific_name": "Setonix brachyurus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 20185, + "scientific_name": "Sicista armenica", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 20184, + "scientific_name": "Sicista betulina", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 20186, + "scientific_name": "Sicista caucasica", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 20187, + "scientific_name": "Sicista caudata", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 20188, + "scientific_name": "Sicista concolor", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 20189, + "scientific_name": "Sicista kazbegica", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 20196, + "scientific_name": "Sicista kluchorica", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 110500058, + "scientific_name": "Sicista loriger", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 20190, + "scientific_name": "Sicista napaea", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 20191, + "scientific_name": "Sicista pseudonapaea", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 20192, + "scientific_name": "Sicista severtzovi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 20193, + "scientific_name": "Sicista strandi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 91934441, + "scientific_name": "Sicista subtilis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 20195, + "scientific_name": "Sicista tianshanica", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 92332716, + "scientific_name": "Sicista trizona", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 115591937, + "scientific_name": "Sigmodon alleni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 20210, + "scientific_name": "Sigmodon alstoni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 20211, + "scientific_name": "Sigmodon arizonae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 20212, + "scientific_name": "Sigmodon fulviventer", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136426, + "scientific_name": "Sigmodon hirsutus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 20213, + "scientific_name": "Sigmodon hispidus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 20214, + "scientific_name": "Sigmodon inopinatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 20215, + "scientific_name": "Sigmodon leucotis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 20216, + "scientific_name": "Sigmodon mascotensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 20217, + "scientific_name": "Sigmodon ochrognathus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 20218, + "scientific_name": "Sigmodon peruanus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136559, + "scientific_name": "Sigmodon toltecus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 20221, + "scientific_name": "Sigmodontomys alfari", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 20229, + "scientific_name": "Simias concolor", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 39846, + "scientific_name": "Simias concolor ssp. concolor", + "subspecies": "concolor", + "rank": "ssp.", + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 39847, + "scientific_name": "Simias concolor ssp. siberu", + "subspecies": "siberu", + "rank": "ssp.", + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 20296, + "scientific_name": "Sminthopsis archeri", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 40551, + "scientific_name": "Sminthopsis bindi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 20295, + "scientific_name": "Sminthopsis butleri", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 40541, + "scientific_name": "Sminthopsis crassicaudata", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 40542, + "scientific_name": "Sminthopsis dolichura", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 20290, + "scientific_name": "Sminthopsis douglasi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 20294, + "scientific_name": "Sminthopsis fuliginosus ssp. aitkeni", + "subspecies": "aitkeni", + "rank": "ssp.", + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 40543, + "scientific_name": "Sminthopsis gilberti", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41509, + "scientific_name": "Sminthopsis granulipes", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41510, + "scientific_name": "Sminthopsis griseoventer", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 40544, + "scientific_name": "Sminthopsis hirtipes", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 20297, + "scientific_name": "Sminthopsis leucopus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 40545, + "scientific_name": "Sminthopsis longicaudata", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 40546, + "scientific_name": "Sminthopsis macroura", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 40547, + "scientific_name": "Sminthopsis murina", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 40548, + "scientific_name": "Sminthopsis ooldea", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 20293, + "scientific_name": "Sminthopsis psammophila", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 40549, + "scientific_name": "Sminthopsis virginiae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 40550, + "scientific_name": "Sminthopsis youngsoni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 12762, + "scientific_name": "Smutsia gigantea", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 12765, + "scientific_name": "Smutsia temminckii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 20322, + "scientific_name": "Solenodon marcanoi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EX" + }, + { + "taxonid": 20321, + "scientific_name": "Solenodon paradoxus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 20332, + "scientific_name": "Solisorex pearsoni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 20333, + "scientific_name": "Solomys ponceleti", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 20334, + "scientific_name": "Solomys salamonis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 20335, + "scientific_name": "Solomys salebrosus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 20336, + "scientific_name": "Solomys sapientis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 136642, + "scientific_name": "Sommeromys macrorhinos", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 135161, + "scientific_name": "Sooretamys angouya", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41384, + "scientific_name": "Sorex alaskanus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 29660, + "scientific_name": "Sorex alpinus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 136814, + "scientific_name": "Sorex antinorii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 29661, + "scientific_name": "Sorex araneus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41385, + "scientific_name": "Sorex arcticus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 20396, + "scientific_name": "Sorex arizonae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41386, + "scientific_name": "Sorex asper", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41387, + "scientific_name": "Sorex bairdi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41388, + "scientific_name": "Sorex bedfordiae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41389, + "scientific_name": "Sorex bendirii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41390, + "scientific_name": "Sorex buchariensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 29662, + "scientific_name": "Sorex caecutiens", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41391, + "scientific_name": "Sorex camtschatica", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 20383, + "scientific_name": "Sorex cansulus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 41392, + "scientific_name": "Sorex cinereus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 29663, + "scientific_name": "Sorex coronatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 20384, + "scientific_name": "Sorex cylindricauda", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41393, + "scientific_name": "Sorex daphaenodon", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41394, + "scientific_name": "Sorex dispar", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41395, + "scientific_name": "Sorex emarginatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 20385, + "scientific_name": "Sorex excelsus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41396, + "scientific_name": "Sorex fumeus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41398, + "scientific_name": "Sorex gracillimus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 29664, + "scientific_name": "Sorex granarius", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41399, + "scientific_name": "Sorex haydeni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 20386, + "scientific_name": "Sorex hosonoi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41400, + "scientific_name": "Sorex hoyi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 29665, + "scientific_name": "Sorex isodon", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136339, + "scientific_name": "Sorex ixtlanensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 20390, + "scientific_name": "Sorex jacksoni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 20387, + "scientific_name": "Sorex kozlovi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 20388, + "scientific_name": "Sorex leucogaster", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 41401, + "scientific_name": "Sorex longirostris", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41402, + "scientific_name": "Sorex lyelli", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 20392, + "scientific_name": "Sorex macrodon", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 136779, + "scientific_name": "Sorex maritimensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136656, + "scientific_name": "Sorex mediopua", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41403, + "scientific_name": "Sorex merriami", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 20397, + "scientific_name": "Sorex milleri", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 29666, + "scientific_name": "Sorex minutissimus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 29667, + "scientific_name": "Sorex minutus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41404, + "scientific_name": "Sorex mirabilis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 41405, + "scientific_name": "Sorex monticola", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41406, + "scientific_name": "Sorex nanus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136608, + "scientific_name": "Sorex neomexicanus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 20393, + "scientific_name": "Sorex oreopolus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136698, + "scientific_name": "Sorex orizabae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41408, + "scientific_name": "Sorex ornatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41409, + "scientific_name": "Sorex pacificus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41410, + "scientific_name": "Sorex palustris", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41411, + "scientific_name": "Sorex planiceps", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41412, + "scientific_name": "Sorex portenkoi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 41413, + "scientific_name": "Sorex preblei", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 20391, + "scientific_name": "Sorex pribilofensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 29668, + "scientific_name": "Sorex raddei", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41414, + "scientific_name": "Sorex roboratus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136282, + "scientific_name": "Sorex rohweri", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 20411, + "scientific_name": "Sorex samniticus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41415, + "scientific_name": "Sorex satunini", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41416, + "scientific_name": "Sorex saussurei", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 20394, + "scientific_name": "Sorex sclateri", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 41417, + "scientific_name": "Sorex shinto", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 20389, + "scientific_name": "Sorex sinalis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 41418, + "scientific_name": "Sorex sonomae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 20395, + "scientific_name": "Sorex stizodon", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 41419, + "scientific_name": "Sorex tenellus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41420, + "scientific_name": "Sorex thibetanus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 41421, + "scientific_name": "Sorex trowbridgii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41422, + "scientific_name": "Sorex tundrensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41423, + "scientific_name": "Sorex ugyunak", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41424, + "scientific_name": "Sorex unguiculatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41425, + "scientific_name": "Sorex vagrans", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41426, + "scientific_name": "Sorex ventralis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136811, + "scientific_name": "Sorex veraecrucis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41427, + "scientific_name": "Sorex veraepacis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 29670, + "scientific_name": "Sorex volnuchini", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136542, + "scientific_name": "Sorex yukonicus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136618, + "scientific_name": "Soricomys kalinga", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 46205609, + "scientific_name": "Soricomys leonardocoi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 47808962, + "scientific_name": "Soricomys montanus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 29459, + "scientific_name": "Soricomys musseri", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41434, + "scientific_name": "Soriculus nigrescens", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 190871, + "scientific_name": "Sotalia fluviatilis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 181359, + "scientific_name": "Sotalia guianensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 82031425, + "scientific_name": "Sousa chinensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 133710, + "scientific_name": "Sousa chinensis ssp. taiwanensis", + "subspecies": "taiwanensis", + "rank": "ssp.", + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 82031633, + "scientific_name": "Sousa plumbea", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 82031667, + "scientific_name": "Sousa sahulensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 20425, + "scientific_name": "Sousa teuszii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 20427, + "scientific_name": "Spalacopus cyanus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 97250195, + "scientific_name": "Spalax antiquus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 20428, + "scientific_name": "Spalax arenarius", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 20429, + "scientific_name": "Spalax giganteus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 97249856, + "scientific_name": "Spalax graecus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 97250154, + "scientific_name": "Spalax istricus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 20430, + "scientific_name": "Spalax microphthalmus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136581, + "scientific_name": "Spalax uralensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 42655, + "scientific_name": "Spalax zemni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 20468, + "scientific_name": "Speothos venaticus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 20471, + "scientific_name": "Spermophilopsis leptodactylus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 20478, + "scientific_name": "Spermophilus alashanicus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136490, + "scientific_name": "Spermophilus brevicauda", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 20472, + "scientific_name": "Spermophilus citellus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 20482, + "scientific_name": "Spermophilus dauricus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 20483, + "scientific_name": "Spermophilus erythrogenys", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 20484, + "scientific_name": "Spermophilus fulvus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 20486, + "scientific_name": "Spermophilus major", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 42470, + "scientific_name": "Spermophilus musicus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 136231, + "scientific_name": "Spermophilus pallidicauda", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 20490, + "scientific_name": "Spermophilus pygmaeus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136455, + "scientific_name": "Spermophilus ralli", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 20491, + "scientific_name": "Spermophilus relictus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 20492, + "scientific_name": "Spermophilus suslicus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 136590, + "scientific_name": "Spermophilus taurensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 20496, + "scientific_name": "Spermophilus xanthoprymnus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 20521, + "scientific_name": "Sphaerias blanfordi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 20599, + "scientific_name": "Sphaeronycteris toxophyllum", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 20637, + "scientific_name": "Spilocuscus kraemeri", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 20636, + "scientific_name": "Spilocuscus maculatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 20638, + "scientific_name": "Spilocuscus papuensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 20639, + "scientific_name": "Spilocuscus rufoniger", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 136443, + "scientific_name": "Spilocuscus wilsoni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 136636, + "scientific_name": "Spilogale angustifrons", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136797, + "scientific_name": "Spilogale gracilis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41636, + "scientific_name": "Spilogale putorius", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 41637, + "scientific_name": "Spilogale pygmaea", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 20688, + "scientific_name": "Srilankamys ohiensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 136625, + "scientific_name": "Steatomys bocagei", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 20718, + "scientific_name": "Steatomys caurinus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 20719, + "scientific_name": "Steatomys cuppedius", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 20717, + "scientific_name": "Steatomys jacksoni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 20720, + "scientific_name": "Steatomys krebsii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136513, + "scientific_name": "Steatomys opimus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 20721, + "scientific_name": "Steatomys parvus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 20722, + "scientific_name": "Steatomys pratensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 20729, + "scientific_name": "Stenella attenuata", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 20730, + "scientific_name": "Stenella clymene", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 20731, + "scientific_name": "Stenella coeruleoalba", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 210188066, + "scientific_name": "Stenella coeruleoalba Gulf of Corinth subpopulation", + "subspecies": null, + "rank": null, + "subpopulation": "Gulf of Corinth subpopulation", + "category": "EN" + }, + { + "taxonid": 16674437, + "scientific_name": "Stenella coeruleoalba Mediterranean subpopulation", + "subspecies": null, + "rank": null, + "subpopulation": "Mediterranean subpopulation", + "category": "LC" + }, + { + "taxonid": 20732, + "scientific_name": "Stenella frontalis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 20733, + "scientific_name": "Stenella longirostris", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 133712, + "scientific_name": "Stenella longirostris ssp. orientalis", + "subspecies": "orientalis", + "rank": "ssp.", + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 20738, + "scientific_name": "Steno bredanensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 160158217, + "scientific_name": "Steno bredanensis Mediterranean subpopulation", + "subspecies": null, + "rank": null, + "subpopulation": "Mediterranean subpopulation", + "category": "NT" + }, + { + "taxonid": 45093, + "scientific_name": "Stenocephalemys albipes", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 20740, + "scientific_name": "Stenocephalemys albocaudata", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 20741, + "scientific_name": "Stenocephalemys griseicauda", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 45096, + "scientific_name": "Stenocephalemys ruppi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 20743, + "scientific_name": "Stenoderma rufum", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 20863, + "scientific_name": "Stochomys longicaudatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 20890, + "scientific_name": "Strigocuscus celebensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 20892, + "scientific_name": "Strigocuscus pelengensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 88154322, + "scientific_name": "Sturnira angeli", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 20949, + "scientific_name": "Sturnira aratathomasi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 88152001, + "scientific_name": "Sturnira bakeri", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 20950, + "scientific_name": "Sturnira bidens", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 20951, + "scientific_name": "Sturnira bogotensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 88152206, + "scientific_name": "Sturnira burtonlimi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 20952, + "scientific_name": "Sturnira erythromos", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 88154577, + "scientific_name": "Sturnira hondurensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 88159599, + "scientific_name": "Sturnira koopmanhilli", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 88159688, + "scientific_name": "Sturnira lilium", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 88159722, + "scientific_name": "Sturnira ludovici", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 20955, + "scientific_name": "Sturnira luisi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 20956, + "scientific_name": "Sturnira magna", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136591, + "scientific_name": "Sturnira mistratensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 20957, + "scientific_name": "Sturnira mordax", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 20958, + "scientific_name": "Sturnira nana", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 136494, + "scientific_name": "Sturnira oporaphilum", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 88154376, + "scientific_name": "Sturnira parvidens", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 88154558, + "scientific_name": "Sturnira paulsoni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 88159664, + "scientific_name": "Sturnira perla", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 136778, + "scientific_name": "Sturnira sorianoi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 20960, + "scientific_name": "Sturnira tildae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136534, + "scientific_name": "Styloctenium mindorensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 21100, + "scientific_name": "Styloctenium wallacei", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 21101, + "scientific_name": "Stylodipus andrewsi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 21102, + "scientific_name": "Stylodipus sungorus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 21103, + "scientific_name": "Stylodipus telum", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 85537971, + "scientific_name": "Submyotodon latirostris", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136224, + "scientific_name": "Suncus aequatorius", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 21141, + "scientific_name": "Suncus ater", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 21142, + "scientific_name": "Suncus dayi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 90389138, + "scientific_name": "Suncus etruscus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 21143, + "scientific_name": "Suncus fellowesgordoni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 21144, + "scientific_name": "Suncus hosei", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 45954392, + "scientific_name": "Suncus hututsi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 41437, + "scientific_name": "Suncus infinitesimus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41438, + "scientific_name": "Suncus lixus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 21145, + "scientific_name": "Suncus malayanus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41446, + "scientific_name": "Suncus megalura", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 21146, + "scientific_name": "Suncus mertensi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 21147, + "scientific_name": "Suncus montanus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 41440, + "scientific_name": "Suncus murinus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 21140, + "scientific_name": "Suncus remyi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41441, + "scientific_name": "Suncus stoliczkanus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41442, + "scientific_name": "Suncus varilla", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 21148, + "scientific_name": "Suncus zeylanicus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 21150, + "scientific_name": "Sundamys infraluteus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 21151, + "scientific_name": "Sundamys maxi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 21152, + "scientific_name": "Sundamys muelleri", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 21153, + "scientific_name": "Sundasciurus brookei", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 42565, + "scientific_name": "Sundasciurus davensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 21154, + "scientific_name": "Sundasciurus fraterculus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 21155, + "scientific_name": "Sundasciurus hippurus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 42566, + "scientific_name": "Sundasciurus hoogstraali", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 21156, + "scientific_name": "Sundasciurus jentinki", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 21157, + "scientific_name": "Sundasciurus juvencus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 21158, + "scientific_name": "Sundasciurus lowii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 42567, + "scientific_name": "Sundasciurus mindanensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 21159, + "scientific_name": "Sundasciurus moellendorffi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 21160, + "scientific_name": "Sundasciurus philippinensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 21161, + "scientific_name": "Sundasciurus rabori", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 21162, + "scientific_name": "Sundasciurus samarensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 21163, + "scientific_name": "Sundasciurus steerii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 21164, + "scientific_name": "Sundasciurus tenuis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 21134, + "scientific_name": "Surdisorex norae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 21135, + "scientific_name": "Surdisorex polulus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 45954401, + "scientific_name": "Surdisorex schlitteri", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 41624, + "scientific_name": "Suricata suricatta", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 21177, + "scientific_name": "Sus ahoenobarbus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 41772, + "scientific_name": "Sus barbatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 21171, + "scientific_name": "Sus barbatus ssp. oi", + "subspecies": "oi", + "rank": "ssp.", + "subpopulation": null, + "category": "LR/nt" + }, + { + "taxonid": 21178, + "scientific_name": "Sus bucculentus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EX" + }, + { + "taxonid": 21175, + "scientific_name": "Sus cebifrons", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 41773, + "scientific_name": "Sus celebensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 136340, + "scientific_name": "Sus oliveri", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 21176, + "scientific_name": "Sus philippensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 41775, + "scientific_name": "Sus scrofa", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 21174, + "scientific_name": "Sus verrucosus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 84697083, + "scientific_name": "Sus verrucosus ssp. blouchi", + "subspecies": "blouchi", + "rank": "ssp.", + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 21185, + "scientific_name": "Syconycteris australis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 21184, + "scientific_name": "Syconycteris carolinae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 21183, + "scientific_name": "Syconycteris hobbit", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 21203, + "scientific_name": "Sylvicapra grimmia", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 142541491, + "scientific_name": "Sylvilagus andinus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 41296, + "scientific_name": "Sylvilagus aquaticus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41297, + "scientific_name": "Sylvilagus audubonii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41302, + "scientific_name": "Sylvilagus bachmani", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 87491102, + "scientific_name": "Sylvilagus brasiliensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 41309, + "scientific_name": "Sylvilagus cognatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 21211, + "scientific_name": "Sylvilagus cunicularius", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 21209, + "scientific_name": "Sylvilagus dicei", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 41299, + "scientific_name": "Sylvilagus floridanus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 87491157, + "scientific_name": "Sylvilagus gabbi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 21206, + "scientific_name": "Sylvilagus graysoni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 21207, + "scientific_name": "Sylvilagus insonus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 21210, + "scientific_name": "Sylvilagus mansuetus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 41300, + "scientific_name": "Sylvilagus nuttallii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41301, + "scientific_name": "Sylvilagus obscurus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 41303, + "scientific_name": "Sylvilagus palustris", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41310, + "scientific_name": "Sylvilagus robustus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 142642715, + "scientific_name": "Sylvilagus sanctaemartae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 142542759, + "scientific_name": "Sylvilagus tapetillus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 21212, + "scientific_name": "Sylvilagus transitionalis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 41311, + "scientific_name": "Sylvilagus varynaensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 45954406, + "scientific_name": "Sylvisorex akaibei", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 45051, + "scientific_name": "Sylvisorex camerunensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 48294480, + "scientific_name": "Sylvisorex corbeti", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 41443, + "scientific_name": "Sylvisorex granti", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 21216, + "scientific_name": "Sylvisorex howelli", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 21220, + "scientific_name": "Sylvisorex isabellae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 41444, + "scientific_name": "Sylvisorex johnstoni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 45050, + "scientific_name": "Sylvisorex konganensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 41445, + "scientific_name": "Sylvisorex lunaris", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 21221, + "scientific_name": "Sylvisorex morio", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 48294467, + "scientific_name": "Sylvisorex ollula", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 21222, + "scientific_name": "Sylvisorex oriundus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 45049, + "scientific_name": "Sylvisorex pluvialis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 45954439, + "scientific_name": "Sylvisorex silvanorum", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 21219, + "scientific_name": "Sylvisorex vulcanorum", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 39779, + "scientific_name": "Symphalangus syndactylus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 42638, + "scientific_name": "Synaptomys borealis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 42639, + "scientific_name": "Synaptomys cooperi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 21251, + "scientific_name": "Syncerus caffer", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 21260, + "scientific_name": "Syntheosciurus brochus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 41312, + "scientific_name": "Tachyglossus aculeatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 21293, + "scientific_name": "Tachyoryctes macrocephalus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 21299, + "scientific_name": "Tachyoryctes splendens", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 21312, + "scientific_name": "Tadarida aegyptiaca", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 21314, + "scientific_name": "Tadarida brasiliensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 21316, + "scientific_name": "Tadarida fulminans", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136716, + "scientific_name": "Tadarida insignis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 40036, + "scientific_name": "Tadarida latouchei", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 21317, + "scientific_name": "Tadarida lobata", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 21311, + "scientific_name": "Tadarida teniotis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 21318, + "scientific_name": "Tadarida ventralis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 21328, + "scientific_name": "Taeromys arcuatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 21329, + "scientific_name": "Taeromys callitrichus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 21330, + "scientific_name": "Taeromys celebensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 21331, + "scientific_name": "Taeromys hamatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 136654, + "scientific_name": "Taeromys microbullatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 21332, + "scientific_name": "Taeromys punicans", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 21333, + "scientific_name": "Taeromys taerae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 41478, + "scientific_name": "Talpa altaica", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41479, + "scientific_name": "Talpa caeca", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41480, + "scientific_name": "Talpa caucasica", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 135458, + "scientific_name": "Talpa davidiana", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 41481, + "scientific_name": "Talpa europaea", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41482, + "scientific_name": "Talpa levantis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41483, + "scientific_name": "Talpa occidentalis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41484, + "scientific_name": "Talpa romana", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41485, + "scientific_name": "Talpa stankovici", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 21349, + "scientific_name": "Tamandua mexicana", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 21350, + "scientific_name": "Tamandua tetradactyla", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 42586, + "scientific_name": "Tamiasciurus douglasii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 42587, + "scientific_name": "Tamiasciurus hudsonicus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 21378, + "scientific_name": "Tamiasciurus mearnsi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 42583, + "scientific_name": "Tamias striatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 21380, + "scientific_name": "Tamiops maritimus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 21379, + "scientific_name": "Tamiops mcclellandii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 21381, + "scientific_name": "Tamiops rodolphii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 21382, + "scientific_name": "Tamiops swinhoei", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 20222, + "scientific_name": "Tanyuromys aphrastus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 136772, + "scientific_name": "Tapecomys primus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 21453, + "scientific_name": "Taphozous achates", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 21452, + "scientific_name": "Taphozous australis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 21454, + "scientific_name": "Taphozous georgianus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 21455, + "scientific_name": "Taphozous hamiltoni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 21456, + "scientific_name": "Taphozous hildegardeae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 21457, + "scientific_name": "Taphozous hilli", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 21458, + "scientific_name": "Taphozous kapalgensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 21459, + "scientific_name": "Taphozous longimanus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 21460, + "scientific_name": "Taphozous mauritianus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 21461, + "scientific_name": "Taphozous melanopogon", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 21462, + "scientific_name": "Taphozous nudiventris", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 21463, + "scientific_name": "Taphozous perforatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 21465, + "scientific_name": "Taphozous theobaldi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 21466, + "scientific_name": "Taphozous troughtoni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 21471, + "scientific_name": "Tapirus bairdii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 21472, + "scientific_name": "Tapirus indicus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 21473, + "scientific_name": "Tapirus pinchaque", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 21474, + "scientific_name": "Tapirus terrestris", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 40583, + "scientific_name": "Tarsipes rostratus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 21489, + "scientific_name": "Tarsius dentatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 162369593, + "scientific_name": "Tarsius fuscus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 136319, + "scientific_name": "Tarsius lariang", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 162337005, + "scientific_name": "Tarsius niemitzi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 21494, + "scientific_name": "Tarsius pelengensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 21490, + "scientific_name": "Tarsius pumilus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 21493, + "scientific_name": "Tarsius sangirensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 162336422, + "scientific_name": "Tarsius spectrumgurskyae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 162336881, + "scientific_name": "Tarsius supriatnai", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 162369551, + "scientific_name": "Tarsius tarsier", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 179234, + "scientific_name": "Tarsius tumpara", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 195277, + "scientific_name": "Tarsius wallacei", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 21495, + "scientific_name": "Tarsomys apoensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 21496, + "scientific_name": "Tarsomys echinatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 21500, + "scientific_name": "Tasmacetus shepherdi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 21507, + "scientific_name": "Tateomys macrocercus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 21508, + "scientific_name": "Tateomys rhinogradoides", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 21514, + "scientific_name": "Tatera indica", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 21521, + "scientific_name": "Taterillus arenarius", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 21522, + "scientific_name": "Taterillus congicus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 21523, + "scientific_name": "Taterillus emini", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 21524, + "scientific_name": "Taterillus gracilis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 21526, + "scientific_name": "Taterillus lacustris", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 21527, + "scientific_name": "Taterillus petteri", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 21528, + "scientific_name": "Taterillus pygargus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 45077, + "scientific_name": "Taterillus tranieri", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41663, + "scientific_name": "Taxidea taxus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41778, + "scientific_name": "Tayassu pecari", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 40595, + "scientific_name": "Tenrec ecaudatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 21661, + "scientific_name": "Tetracerus quadricornis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 40031, + "scientific_name": "Thainycteris aureocollaris", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 21690, + "scientific_name": "Thallomys loringi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 21691, + "scientific_name": "Thallomys nigricauda", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 21692, + "scientific_name": "Thallomys paedulcus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 21693, + "scientific_name": "Thallomys shortridgei", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 21694, + "scientific_name": "Thalpomys cerradensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 21695, + "scientific_name": "Thalpomys lasiotis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 21703, + "scientific_name": "Thamnomys kempi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 45078, + "scientific_name": "Thamnomys schoutedeni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 21702, + "scientific_name": "Thamnomys venustus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 750, + "scientific_name": "Thaptomys nigrita", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 21744, + "scientific_name": "Theropithecus gelada", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136849, + "scientific_name": "Theropithecus gelada ssp. gelada", + "subspecies": "gelada", + "rank": "ssp.", + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 40010, + "scientific_name": "Theropithecus gelada ssp. obscurus", + "subspecies": "obscurus", + "rank": "ssp.", + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 46675319, + "scientific_name": "Thomasomys andersoni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 136667, + "scientific_name": "Thomasomys apeco", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 96801180, + "scientific_name": "Thomasomys aureus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 96790651, + "scientific_name": "Thomasomys auricularis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 96791089, + "scientific_name": "Thomasomys australis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 21770, + "scientific_name": "Thomasomys baeops", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 21771, + "scientific_name": "Thomasomys bombycinus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 136413, + "scientific_name": "Thomasomys caudivarius", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 96791166, + "scientific_name": "Thomasomys cinereiventer", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 21773, + "scientific_name": "Thomasomys cinereus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136822, + "scientific_name": "Thomasomys cinnameus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 96792191, + "scientific_name": "Thomasomys contradictus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 96790818, + "scientific_name": "Thomasomys daphne", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 96792239, + "scientific_name": "Thomasomys dispar", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 21775, + "scientific_name": "Thomasomys eleusis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 96799858, + "scientific_name": "Thomasomys emeritus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 136345, + "scientific_name": "Thomasomys erro", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 96800350, + "scientific_name": "Thomasomys fumeus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 21776, + "scientific_name": "Thomasomys gracilis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 136762, + "scientific_name": "Thomasomys hudsoni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 21777, + "scientific_name": "Thomasomys hylophilus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 21778, + "scientific_name": "Thomasomys incanus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 21779, + "scientific_name": "Thomasomys ischyrus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 21780, + "scientific_name": "Thomasomys kalinowskii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 21781, + "scientific_name": "Thomasomys ladewi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 96799835, + "scientific_name": "Thomasomys laniger", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136480, + "scientific_name": "Thomasomys macrotis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 21783, + "scientific_name": "Thomasomys monochromos", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 96801038, + "scientific_name": "Thomasomys nicefori", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 21784, + "scientific_name": "Thomasomys niveipes", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 21785, + "scientific_name": "Thomasomys notatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136575, + "scientific_name": "Thomasomys onkiro", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 21786, + "scientific_name": "Thomasomys oreas", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 21787, + "scientific_name": "Thomasomys paramorum", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 96800866, + "scientific_name": "Thomasomys popayanus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 136312, + "scientific_name": "Thomasomys praetor", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 96801288, + "scientific_name": "Thomasomys princeps", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 96789732, + "scientific_name": "Thomasomys pyrrhonotus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 21790, + "scientific_name": "Thomasomys rosalinda", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 21791, + "scientific_name": "Thomasomys silvestris", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 21792, + "scientific_name": "Thomasomys taczanowskii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136487, + "scientific_name": "Thomasomys ucucha", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 21793, + "scientific_name": "Thomasomys vestitus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 99693990, + "scientific_name": "Thomasomys vulcani", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 21799, + "scientific_name": "Thomomys bottae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 42594, + "scientific_name": "Thomomys bulbivorus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 42595, + "scientific_name": "Thomomys clusius", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 21809, + "scientific_name": "Thomomys idahoensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 21810, + "scientific_name": "Thomomys mazama", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 42596, + "scientific_name": "Thomomys monticola", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 42597, + "scientific_name": "Thomomys talpoides", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 42598, + "scientific_name": "Thomomys townsendii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 21800, + "scientific_name": "Thomomys umbrinus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 21815, + "scientific_name": "Thoopterus nigrescens", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 84463939, + "scientific_name": "Thoopterus suhaniahae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 21839, + "scientific_name": "Thrichomys apereoides", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136355, + "scientific_name": "Thrichomys inermis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 90386381, + "scientific_name": "Thrichomys laurentius", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 136245, + "scientific_name": "Thrichomys pachyurus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 21846, + "scientific_name": "Thryonomys gregorianus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 21847, + "scientific_name": "Thryonomys swinderianus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 21866, + "scientific_name": "Thylacinus cynocephalus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EX" + }, + { + "taxonid": 51343307, + "scientific_name": "Thylamys cinderella", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 199835, + "scientific_name": "Thylamys citellus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 40517, + "scientific_name": "Thylamys elegans", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 199836, + "scientific_name": "Thylamys fenestrae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 136653, + "scientific_name": "Thylamys karimii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 21867, + "scientific_name": "Thylamys macrurus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 14888655, + "scientific_name": "Thylamys pallidior", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 199834, + "scientific_name": "Thylamys pulchellus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 201936, + "scientific_name": "Thylamys pusillus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136243, + "scientific_name": "Thylamys tatei", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 40520, + "scientific_name": "Thylamys velutinus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 136626, + "scientific_name": "Thylamys venustus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 40571, + "scientific_name": "Thylogale billardierii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 21874, + "scientific_name": "Thylogale browni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 21870, + "scientific_name": "Thylogale brunii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 21873, + "scientific_name": "Thylogale calabyi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 136255, + "scientific_name": "Thylogale lanatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 40574, + "scientific_name": "Thylogale stigmatica", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 40573, + "scientific_name": "Thylogale thetis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136594, + "scientific_name": "Thyroptera devivoi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 21877, + "scientific_name": "Thyroptera discifera", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 21878, + "scientific_name": "Thyroptera lavali", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 21879, + "scientific_name": "Thyroptera tricolor", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 88151033, + "scientific_name": "Thyroptera wynneae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 12813, + "scientific_name": "Tlacuatzin canescens", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 21972, + "scientific_name": "Tokudaia muenninki", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 21973, + "scientific_name": "Tokudaia osimensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 136695, + "scientific_name": "Tokudaia tokunoshimensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 21974, + "scientific_name": "Tolypeutes matacus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 21975, + "scientific_name": "Tolypeutes tricinctus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 21982, + "scientific_name": "Tomopeas ravus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 21983, + "scientific_name": "Tonatia bidens", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 41530, + "scientific_name": "Tonatia saurophila", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136342, + "scientific_name": "Tonkinomys daovantieni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 6981, + "scientific_name": "Toromys grandis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 90386329, + "scientific_name": "Toromys rhipidurus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 22029, + "scientific_name": "Trachops cirrhosus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 39848, + "scientific_name": "Trachypithecus auratus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 41554, + "scientific_name": "Trachypithecus barbei", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 136920, + "scientific_name": "Trachypithecus crepusculus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 22035, + "scientific_name": "Trachypithecus cristatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 136914, + "scientific_name": "Trachypithecus cristatus ssp. cristatus", + "subspecies": "cristatus", + "rank": "ssp.", + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 136918, + "scientific_name": "Trachypithecus cristatus ssp. vigilans", + "subspecies": "vigilans", + "rank": "ssp.", + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 22043, + "scientific_name": "Trachypithecus delacouri", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 39853, + "scientific_name": "Trachypithecus francoisi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 22037, + "scientific_name": "Trachypithecus geei", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 39874, + "scientific_name": "Trachypithecus germaini", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 40789, + "scientific_name": "Trachypithecus hatinhensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 22044, + "scientific_name": "Trachypithecus laotum", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 39872, + "scientific_name": "Trachypithecus leucocephalus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 39875, + "scientific_name": "Trachypithecus margarita", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 39849, + "scientific_name": "Trachypithecus mauritius", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 22039, + "scientific_name": "Trachypithecus obscurus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 39857, + "scientific_name": "Trachypithecus obscurus ssp. carbo", + "subspecies": "carbo", + "rank": "ssp.", + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 39859, + "scientific_name": "Trachypithecus obscurus ssp. flavicauda", + "subspecies": "flavicauda", + "rank": "ssp.", + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 39860, + "scientific_name": "Trachypithecus obscurus ssp. halonifer", + "subspecies": "halonifer", + "rank": "ssp.", + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 39856, + "scientific_name": "Trachypithecus obscurus ssp. obscurus", + "subspecies": "obscurus", + "rank": "ssp.", + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 39861, + "scientific_name": "Trachypithecus obscurus ssp. sanctorum", + "subspecies": "sanctorum", + "rank": "ssp.", + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 39862, + "scientific_name": "Trachypithecus obscurus ssp. seimundi", + "subspecies": "seimundi", + "rank": "ssp.", + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 39865, + "scientific_name": "Trachypithecus obscurus ssp. styx", + "subspecies": "styx", + "rank": "ssp.", + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 175862145, + "scientific_name": "Trachypithecus phayrei", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 136928, + "scientific_name": "Trachypithecus phayrei ssp. phayrei", + "subspecies": "phayrei", + "rank": "ssp.", + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 39863, + "scientific_name": "Trachypithecus phayrei ssp. shanicus", + "subspecies": "shanicus", + "rank": "ssp.", + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 22041, + "scientific_name": "Trachypithecus pileatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 39867, + "scientific_name": "Trachypithecus pileatus ssp. brahma", + "subspecies": "brahma", + "rank": "ssp.", + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 195360310, + "scientific_name": "Trachypithecus pileatus ssp. pileatus", + "subspecies": "pileatus", + "rank": "ssp.", + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 39870, + "scientific_name": "Trachypithecus pileatus ssp. tenebricus", + "subspecies": "tenebricus", + "rank": "ssp.", + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 39871, + "scientific_name": "Trachypithecus poliocephalus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 196344474, + "scientific_name": "Trachypithecus popa", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 205911038, + "scientific_name": "Trachypithecus selangorensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 39869, + "scientific_name": "Trachypithecus shortridgei", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 22052, + "scientific_name": "Tragelaphus angasii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22046, + "scientific_name": "Tragelaphus buxtoni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 44172, + "scientific_name": "Tragelaphus derbianus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 22056, + "scientific_name": "Tragelaphus derbianus ssp. derbianus", + "subspecies": "derbianus", + "rank": "ssp.", + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 22059, + "scientific_name": "Tragelaphus derbianus ssp. gigas", + "subspecies": "gigas", + "rank": "ssp.", + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 22047, + "scientific_name": "Tragelaphus eurycerus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 22058, + "scientific_name": "Tragelaphus eurycerus ssp. eurycerus", + "subspecies": "eurycerus", + "rank": "ssp.", + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 22057, + "scientific_name": "Tragelaphus eurycerus ssp. isaaci", + "subspecies": "isaaci", + "rank": "ssp.", + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 22053, + "scientific_name": "Tragelaphus imberbis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 22055, + "scientific_name": "Tragelaphus oryx", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22051, + "scientific_name": "Tragelaphus scriptus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22050, + "scientific_name": "Tragelaphus spekii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22054, + "scientific_name": "Tragelaphus strepsiceros", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41780, + "scientific_name": "Tragulus javanicus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 136297, + "scientific_name": "Tragulus kanchil", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41781, + "scientific_name": "Tragulus napu", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22065, + "scientific_name": "Tragulus nigricans", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 136360, + "scientific_name": "Tragulus versicolor", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 136533, + "scientific_name": "Tragulus williamsoni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 15588, + "scientific_name": "Transandinomys bolivaris", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 15615, + "scientific_name": "Transandinomys talamancae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22066, + "scientific_name": "Tremarctos ornatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 81081036, + "scientific_name": "Triaenops afer", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 81082829, + "scientific_name": "Triaenops parvus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 81069403, + "scientific_name": "Triaenops persicus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 40026, + "scientific_name": "Triaenops rufus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22102, + "scientific_name": "Trichechus inunguis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 22103, + "scientific_name": "Trichechus manatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 22106, + "scientific_name": "Trichechus manatus ssp. latirostris", + "subspecies": "latirostris", + "rank": "ssp.", + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 22105, + "scientific_name": "Trichechus manatus ssp. manatus", + "subspecies": "manatus", + "rank": "ssp.", + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 22104, + "scientific_name": "Trichechus senegalensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 40557, + "scientific_name": "Trichosurus caninus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136256, + "scientific_name": "Trichosurus cunninghami", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 40585, + "scientific_name": "Trichosurus vulpecula", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22132, + "scientific_name": "Trichys fasciculata", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 18272, + "scientific_name": "Trinomys albispinus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 18281, + "scientific_name": "Trinomys dimidiatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136407, + "scientific_name": "Trinomys eliasi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 136221, + "scientific_name": "Trinomys gratiosus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 18288, + "scientific_name": "Trinomys iheringi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136305, + "scientific_name": "Trinomys mirapitanga", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 136543, + "scientific_name": "Trinomys moojeni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 136492, + "scientific_name": "Trinomys paratus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 18298, + "scientific_name": "Trinomys setosus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136414, + "scientific_name": "Trinomys yonenagae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 13381, + "scientific_name": "Trinycteris nicefori", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22297, + "scientific_name": "Trogopterus xanthipes", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 22431, + "scientific_name": "Tryphomys adustus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 22432, + "scientific_name": "Tscherskia triton", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41492, + "scientific_name": "Tupaia belangeri", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22446, + "scientific_name": "Tupaia chrysogaster", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 111873499, + "scientific_name": "Tupaia discolor", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 41493, + "scientific_name": "Tupaia dorsalis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 22784, + "scientific_name": "Tupaia everetti", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 111873543, + "scientific_name": "Tupaia ferruginea", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 111872341, + "scientific_name": "Tupaia glis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41495, + "scientific_name": "Tupaia gracilis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 111873049, + "scientific_name": "Tupaia hypochrysa", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 41496, + "scientific_name": "Tupaia javanica", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 111871214, + "scientific_name": "Tupaia longipes", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41497, + "scientific_name": "Tupaia minor", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41498, + "scientific_name": "Tupaia montana", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22454, + "scientific_name": "Tupaia nicobarica", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 110678346, + "scientific_name": "Tupaia palawanensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41499, + "scientific_name": "Tupaia picta", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 111871663, + "scientific_name": "Tupaia salatana", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41500, + "scientific_name": "Tupaia splendidula", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41501, + "scientific_name": "Tupaia tana", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41714, + "scientific_name": "Tursiops aduncus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 22563, + "scientific_name": "Tursiops truncatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 194300, + "scientific_name": "Tursiops truncatus Fiordland subpopulation", + "subspecies": null, + "rank": null, + "subpopulation": "Fiordland subpopulation", + "category": "CR" + }, + { + "taxonid": 181208820, + "scientific_name": "Tursiops truncatus Gulf of Ambracia subpopulation", + "subspecies": null, + "rank": null, + "subpopulation": "Gulf of Ambracia subpopulation", + "category": "CR" + }, + { + "taxonid": 16369383, + "scientific_name": "Tursiops truncatus Mediterranean subpopulation", + "subspecies": null, + "rank": null, + "subpopulation": "Mediterranean subpopulation", + "category": "LC" + }, + { + "taxonid": 134822416, + "scientific_name": "Tursiops truncatus ssp. gephyreus", + "subspecies": "gephyreus", + "rank": "ssp.", + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 133714, + "scientific_name": "Tursiops truncatus ssp. ponticus", + "subspecies": "ponticus", + "rank": "ssp.", + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 22570, + "scientific_name": "Tylomys bullaris", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 22571, + "scientific_name": "Tylomys fulviventer", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 22572, + "scientific_name": "Tylomys mirae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22573, + "scientific_name": "Tylomys nudicaudus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22574, + "scientific_name": "Tylomys panamensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 22575, + "scientific_name": "Tylomys tumbalensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 22576, + "scientific_name": "Tylomys watsoni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22577, + "scientific_name": "Tylonycteris pachypus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 85342580, + "scientific_name": "Tylonycteris pygmaeus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 22578, + "scientific_name": "Tylonycteris robustula", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136557, + "scientific_name": "Tympanoctomys aureus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 22586, + "scientific_name": "Tympanoctomys barrerae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 86051353, + "scientific_name": "Tympanoctomys kirchnerorum", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 136714, + "scientific_name": "Tympanoctomys loschalchalerosorum", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 22605, + "scientific_name": "Typhlomys cinereus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22771, + "scientific_name": "Uranomys ruddi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 42463, + "scientific_name": "Urocitellus armatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 42464, + "scientific_name": "Urocitellus beldingi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 20497, + "scientific_name": "Urocitellus brunneus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 42465, + "scientific_name": "Urocitellus canus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 42466, + "scientific_name": "Urocitellus columbianus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 42467, + "scientific_name": "Urocitellus elegans", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 20498, + "scientific_name": "Urocitellus endemicus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 116989381, + "scientific_name": "Urocitellus mollis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 116989724, + "scientific_name": "Urocitellus nancyae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 20488, + "scientific_name": "Urocitellus parryii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 42561, + "scientific_name": "Urocitellus richardsonii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 20476, + "scientific_name": "Urocitellus townsendii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 20494, + "scientific_name": "Urocitellus undulatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 20475, + "scientific_name": "Urocitellus washingtoni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 22780, + "scientific_name": "Urocyon cinereoargenteus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22781, + "scientific_name": "Urocyon littoralis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 22782, + "scientific_name": "Uroderma bilobatum", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22783, + "scientific_name": "Uroderma magnirostrum", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22800, + "scientific_name": "Uromys anak", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136773, + "scientific_name": "Uromys boeadii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 22801, + "scientific_name": "Uromys caudimaculatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136470, + "scientific_name": "Uromys emmae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 22802, + "scientific_name": "Uromys hadrourus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 22803, + "scientific_name": "Uromys imperator", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 22804, + "scientific_name": "Uromys neobritannicus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 22805, + "scientific_name": "Uromys porculus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 22806, + "scientific_name": "Uromys rex", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 136493, + "scientific_name": "Uromys siebersi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 120569706, + "scientific_name": "Uromys vika", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 41486, + "scientific_name": "Uropsilus andersoni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 41487, + "scientific_name": "Uropsilus gracilis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22809, + "scientific_name": "Uropsilus investigator", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 22810, + "scientific_name": "Uropsilus soricipes", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41489, + "scientific_name": "Urotrichus talpoides", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41687, + "scientific_name": "Ursus americanus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41688, + "scientific_name": "Ursus arctos", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22823, + "scientific_name": "Ursus maritimus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 22824, + "scientific_name": "Ursus thibetanus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 22839, + "scientific_name": "Vampyressa melissa", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 22841, + "scientific_name": "Vampyressa pusilla", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 136671, + "scientific_name": "Vampyressa thyone", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22837, + "scientific_name": "Vampyriscus bidens", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22838, + "scientific_name": "Vampyriscus brocki", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22840, + "scientific_name": "Vampyriscus nymphaea", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 88151904, + "scientific_name": "Vampyrodes caraccioli", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 88151984, + "scientific_name": "Vampyrodes major", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22843, + "scientific_name": "Vampyrum spectrum", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 136372, + "scientific_name": "Vandeleuria nilagirica", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 22844, + "scientific_name": "Vandeleuria nolthenii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 22845, + "scientific_name": "Vandeleuria oleracea", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22920, + "scientific_name": "Varecia rubra", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 22918, + "scientific_name": "Varecia variegata", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 136895, + "scientific_name": "Varecia variegata ssp. editorum", + "subspecies": "editorum", + "rank": "ssp.", + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 136934, + "scientific_name": "Varecia variegata ssp. subcincta", + "subspecies": "subcincta", + "rank": "ssp.", + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 22919, + "scientific_name": "Varecia variegata ssp. variegata", + "subspecies": "variegata", + "rank": "ssp.", + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 22933, + "scientific_name": "Vernaya fulva", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 7913, + "scientific_name": "Vespadelus baverstocki", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 7919, + "scientific_name": "Vespadelus caurinus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 7920, + "scientific_name": "Vespadelus darlingtoni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 7923, + "scientific_name": "Vespadelus douglasorum", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 7924, + "scientific_name": "Vespadelus finlaysoni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 7938, + "scientific_name": "Vespadelus pumilus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 7939, + "scientific_name": "Vespadelus regulus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 7944, + "scientific_name": "Vespadelus troughtoni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 7945, + "scientific_name": "Vespadelus vulturnus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22947, + "scientific_name": "Vespertilio murinus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22949, + "scientific_name": "Vespertilio sinensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22956, + "scientific_name": "Vicugna vicugna", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 23036, + "scientific_name": "Viverra civettina", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 41707, + "scientific_name": "Viverra megaspila", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 41708, + "scientific_name": "Viverra tangalunga", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41709, + "scientific_name": "Viverra zibetha", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41710, + "scientific_name": "Viverricula indica", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 165925, + "scientific_name": "Voalavo antsahabensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 29461, + "scientific_name": "Voalavo gymnocaudus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 23043, + "scientific_name": "Volemys millicens", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 23044, + "scientific_name": "Volemys musseri", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 40556, + "scientific_name": "Vombatus ursinus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 29680, + "scientific_name": "Vormela peregusna", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 23049, + "scientific_name": "Vulpes bengalensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 23050, + "scientific_name": "Vulpes cana", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 23060, + "scientific_name": "Vulpes chama", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 23051, + "scientific_name": "Vulpes corsac", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 23061, + "scientific_name": "Vulpes ferrilata", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 899, + "scientific_name": "Vulpes lagopus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41587, + "scientific_name": "Vulpes macrotis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 23052, + "scientific_name": "Vulpes pallida", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 23053, + "scientific_name": "Vulpes rueppellii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 23059, + "scientific_name": "Vulpes velox", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 23062, + "scientific_name": "Vulpes vulpes", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41588, + "scientific_name": "Vulpes zerda", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 92441666, + "scientific_name": "Waiomys mamasae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 40575, + "scientific_name": "Wallabia bicolor", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136745, + "scientific_name": "Wiedomys cerradensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 23076, + "scientific_name": "Wiedomys pyrrhorhinos", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 23077, + "scientific_name": "Wilfredomys oenax", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 23091, + "scientific_name": "Wyulda squamicaudata", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 23115, + "scientific_name": "Xenomys nelsoni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 136515, + "scientific_name": "Xenothrix mcgregori", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EX" + }, + { + "taxonid": 23137, + "scientific_name": "Xenuromys barbatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 23141, + "scientific_name": "Xeromys myoides", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 136321, + "scientific_name": "Xeronycteris vieirai", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 20474, + "scientific_name": "Xerospermophilus mohavensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 20489, + "scientific_name": "Xerospermophilus perotensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 42563, + "scientific_name": "Xerospermophilus spilosoma", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 20493, + "scientific_name": "Xerospermophilus tereticaudus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 23144, + "scientific_name": "Xerus erythropus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 23145, + "scientific_name": "Xerus inauris", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 23146, + "scientific_name": "Xerus princeps", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 23147, + "scientific_name": "Xerus rutilus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 23178, + "scientific_name": "Zaedyus pichiy", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 136322, + "scientific_name": "Zaglossus attenboroughi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 136552, + "scientific_name": "Zaglossus bartoni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 23179, + "scientific_name": "Zaglossus bruijnii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 41666, + "scientific_name": "Zalophus californianus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41667, + "scientific_name": "Zalophus japonicus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EX" + }, + { + "taxonid": 41668, + "scientific_name": "Zalophus wollebaeki", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 42613, + "scientific_name": "Zapus hudsonius", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 42614, + "scientific_name": "Zapus princeps", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 23192, + "scientific_name": "Zapus trinotatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 23197, + "scientific_name": "Zelotomys hildegardeae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 23196, + "scientific_name": "Zelotomys woosnami", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 23204, + "scientific_name": "Zenkerella insignis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 23211, + "scientific_name": "Ziphius cavirostris", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 16381144, + "scientific_name": "Ziphius cavirostris Mediterranean subpopulation", + "subspecies": null, + "rank": null, + "subpopulation": "Mediterranean subpopulation", + "category": "VU" + }, + { + "taxonid": 23321, + "scientific_name": "Zygodontomys brevicauda", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 23322, + "scientific_name": "Zygodontomys brunneus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 23323, + "scientific_name": "Zygogeomys trichopus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 23325, + "scientific_name": "Zyzomys argurus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 23326, + "scientific_name": "Zyzomys maini", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 23327, + "scientific_name": "Zyzomys palatalis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 23324, + "scientific_name": "Zyzomys pedunculatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 23328, + "scientific_name": "Zyzomys woodwardi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + } + ] + }, + "https://apiv3.iucnredlist.org/api/v3/comp-group/list": { + "count": 31, + "result": [ + { + "group_name": "reef_building_corals" + }, + { + "group_name": "chameleons" + }, + { + "group_name": "mammals" + }, + { + "group_name": "mangrove_plants" + }, + { + "group_name": "seagrasses" + }, + { + "group_name": "cycads" + }, + { + "group_name": "blennies" + }, + { + "group_name": "cone_snails" + }, + { + "group_name": "magnolias" + }, + { + "group_name": "seasnakes" + }, + { + "group_name": "fw_caridean_shrimps" + }, + { + "group_name": "fw_crayfish" + }, + { + "group_name": "tunas_and_billfishes" + }, + { + "group_name": "butterfly_fishes" + }, + { + "group_name": "groupers" + }, + { + "group_name": "pufferfishes" + }, + { + "group_name": "conifers" + }, + { + "group_name": "surgeonfishes" + }, + { + "group_name": "birds" + }, + { + "group_name": "crocodiles_and_alligators" + }, + { + "group_name": "sharks_and_rays" + }, + { + "group_name": "fw_crabs" + }, + { + "group_name": "cacti" + }, + { + "group_name": "tarpons_and_ladyfishes" + }, + { + "group_name": "sturgeons" + }, + { + "group_name": "angelfishes" + }, + { + "group_name": "lobsters" + }, + { + "group_name": "amphibians" + }, + { + "group_name": "seabreams_porgies_picarels" + }, + { + "group_name": "hagfishes" + }, + { + "group_name": "wrasses_and_parrotfishes" + } + ] + }, + "https://apiv3.iucnredlist.org/api/v3/country/getspecies/AZ": { + "count": 1162, + "country": "AZ", + "result": [ + { + "taxonid": 42293, + "scientific_name": "Abies nordmanniana", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 196098, + "scientific_name": "Abies nordmanniana subsp. nordmanniana", + "subspecies": "nordmanniana", + "rank": "subsp.", + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 48839409, + "scientific_name": "Ablepharus bivittatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 135696, + "scientific_name": "Abramis brama", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22725044, + "scientific_name": "Acanthis flammea", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19017703, + "scientific_name": "Acanthobrama microlepis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22695490, + "scientific_name": "Accipiter badius", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22695499, + "scientific_name": "Accipiter brevipes", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22695683, + "scientific_name": "Accipiter gentilis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22695624, + "scientific_name": "Accipiter nisus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 193523, + "scientific_name": "Acer campestre", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 193525, + "scientific_name": "Acer cappadocicum", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 193616, + "scientific_name": "Acer hyrcanum", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 193835, + "scientific_name": "Acer monspessulanum", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 193853, + "scientific_name": "Acer platanoides", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 193856, + "scientific_name": "Acer pseudoplatanus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 193888, + "scientific_name": "Acer velutinum", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 199866, + "scientific_name": "Achnatherum roshevitzii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 232, + "scientific_name": "Acipenser gueldenstaedtii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 246, + "scientific_name": "Acipenser gueldenstaedtii Caspian Sea subpopulation", + "subspecies": null, + "rank": null, + "subpopulation": "Caspian Sea subpopulation", + "category": "CR" + }, + { + "taxonid": 225, + "scientific_name": "Acipenser nudiventris", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 250, + "scientific_name": "Acipenser nudiventris Caspian Sea subpopulation", + "subspecies": null, + "rank": null, + "subpopulation": "Caspian Sea subpopulation", + "category": "CR" + }, + { + "taxonid": 235, + "scientific_name": "Acipenser persicus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 227, + "scientific_name": "Acipenser ruthenus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 182582605, + "scientific_name": "Acipenser ruthenus Caspian Sea subpopulation", + "subspecies": null, + "rank": null, + "subpopulation": "Caspian Sea subpopulation", + "category": "VU" + }, + { + "taxonid": 229, + "scientific_name": "Acipenser stellatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 254, + "scientific_name": "Acipenser stellatus Caspian Sea subpopulation", + "subspecies": null, + "rank": null, + "subpopulation": "Caspian Sea subpopulation", + "category": "CR" + }, + { + "taxonid": 104317670, + "scientific_name": "Acrocephalus arundinaceus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22714693, + "scientific_name": "Acrocephalus melanopogon", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22714741, + "scientific_name": "Acrocephalus palustris", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22714700, + "scientific_name": "Acrocephalus schoenobaenus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22714722, + "scientific_name": "Acrocephalus scirpaceus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22693264, + "scientific_name": "Actitis hypoleucos", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 172268, + "scientific_name": "Aegilops biuncialis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 172247, + "scientific_name": "Aegilops columnaris", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19208893, + "scientific_name": "Aegilops crassa", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 172218, + "scientific_name": "Aegilops cylindrica", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 172214, + "scientific_name": "Aegilops geniculata", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19208905, + "scientific_name": "Aegilops juvenalis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 172178, + "scientific_name": "Aegilops kotschyi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 172223, + "scientific_name": "Aegilops neglecta", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 172145, + "scientific_name": "Aegilops peregrina", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 110433063, + "scientific_name": "Aegilops peregrina var. peregrina", + "subspecies": "peregrina", + "rank": "var.", + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 172086, + "scientific_name": "Aegilops tauschii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 172127, + "scientific_name": "Aegilops triuncialis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 110433393, + "scientific_name": "Aegilops triuncialis var. triuncialis", + "subspecies": "triuncialis", + "rank": "var.", + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 172255, + "scientific_name": "Aegilops umbellulata", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 172098, + "scientific_name": "Aegilops ventricosa", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 103871923, + "scientific_name": "Aegithalos caudatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22689362, + "scientific_name": "Aegolius funereus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22695231, + "scientific_name": "Aegypius monachus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 158694, + "scientific_name": "Aeshna affinis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 165524, + "scientific_name": "Aeshna cyanea", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 158692, + "scientific_name": "Aeshna isoceles", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 165518, + "scientific_name": "Aeshna juncea", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 126819440, + "scientific_name": "Aeshna vercanica", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 102998555, + "scientific_name": "Alauda arvensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 104007058, + "scientific_name": "Alaudala rufescens", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22717298, + "scientific_name": "Alauda leucoptera", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 184454, + "scientific_name": "Alburnoides eichwaldii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 135499, + "scientific_name": "Alburnus chalcoides", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19018496, + "scientific_name": "Alburnus filippii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 135532, + "scientific_name": "Alburnus hohenackeri", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22683027, + "scientific_name": "Alcedo atthis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 199873, + "scientific_name": "Alchemilla jaroschenkoi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 22678691, + "scientific_name": "Alectoris chukar", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 163974, + "scientific_name": "Alisma lanceolatum", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 172192, + "scientific_name": "Allium ampeloprasum", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 172241, + "scientific_name": "Allium atroviolaceum", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 20666191, + "scientific_name": "Allium scabriscapum", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 172256, + "scientific_name": "Allium schoenoprasum", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 194472, + "scientific_name": "Alnus incana", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 194249, + "scientific_name": "Alnus subcordata", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 164156, + "scientific_name": "Alopecurus aequalis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 98468449, + "scientific_name": "Alosa braschnikowi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 135548, + "scientific_name": "Alosa caspia", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 98468550, + "scientific_name": "Alosa curensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 135537, + "scientific_name": "Alosa kessleri", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 98470302, + "scientific_name": "Alosa saposchnikowii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 98470350, + "scientific_name": "Alosa sphaerocephala", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 135559, + "scientific_name": "Alosa volgensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 22678652, + "scientific_name": "Ammoperdix griseogularis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 199895, + "scientific_name": "Anabasis eugeniae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 199896, + "scientific_name": "Anacyclus ciliatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22680301, + "scientific_name": "Anas acuta", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22680321, + "scientific_name": "Anas crecca", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22680186, + "scientific_name": "Anas platyrhynchos", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 156754995, + "scientific_name": "Anatirostrum profundorum", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 59812, + "scientific_name": "Anax imperator", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 165488, + "scientific_name": "Anax parthenope", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 156181, + "scientific_name": "Ancylus fluviatilis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 189408, + "scientific_name": "Anisus kolesnikovi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 155814, + "scientific_name": "Anisus leucostoma", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 188871, + "scientific_name": "Anodonta cyrea", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 22679881, + "scientific_name": "Anser albifrons", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22679889, + "scientific_name": "Anser anser", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22679886, + "scientific_name": "Anser erythropus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 22692081, + "scientific_name": "Anthropoides virgo", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22718501, + "scientific_name": "Anthus campestris", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22718560, + "scientific_name": "Anthus cervinus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22718556, + "scientific_name": "Anthus pratensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22718571, + "scientific_name": "Anthus spinoletta", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22718546, + "scientific_name": "Anthus trivialis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 164203, + "scientific_name": "Apium graveolens", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 1888, + "scientific_name": "Apodemus agrarius", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 1896, + "scientific_name": "Apodemus hyrcanicus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 1900, + "scientific_name": "Apodemus ponticus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 1905, + "scientific_name": "Apodemus uralensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 135724, + "scientific_name": "Apodemus witherbyi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22686856, + "scientific_name": "Apus affinis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22686800, + "scientific_name": "Apus apus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22696060, + "scientific_name": "Aquila chrysaetos", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22696076, + "scientific_name": "Aquila fasciata", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22696048, + "scientific_name": "Aquila heliaca", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 22696038, + "scientific_name": "Aquila nipalensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 200622, + "scientific_name": "Arabis armena", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 19181048, + "scientific_name": "Arbutus andrachne", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22697043, + "scientific_name": "Ardea alba", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22696993, + "scientific_name": "Ardea cinerea", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22697031, + "scientific_name": "Ardea purpurea", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22697123, + "scientific_name": "Ardeola ralloides", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22693336, + "scientific_name": "Arenaria interpres", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 64321666, + "scientific_name": "Argentina anserina", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 164340, + "scientific_name": "Arundo donax", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 2149, + "scientific_name": "Arvicola amphibius", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22689531, + "scientific_name": "Asio flammeus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22689507, + "scientific_name": "Asio otus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 176377, + "scientific_name": "Asparagus officinalis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 176392, + "scientific_name": "Asparagus verticillatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 199916, + "scientific_name": "Astragalus albanicus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 184928963, + "scientific_name": "Astragalus austriacus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19892262, + "scientific_name": "Astragalus cephalotes", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19891983, + "scientific_name": "Astragalus commixtus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19378897, + "scientific_name": "Astragalus crenatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 199928, + "scientific_name": "Astragalus cuscutae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 199947, + "scientific_name": "Astragalus igniarius", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 199950, + "scientific_name": "Astragalus kabristanicus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 199935, + "scientific_name": "Astragalus kubensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 199915, + "scientific_name": "Astragalus maraziensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 199924, + "scientific_name": "Astragalus schachbuzensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 199942, + "scientific_name": "Astragalus stevenianus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19378961, + "scientific_name": "Astragalus tribuloides", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22689328, + "scientific_name": "Athene noctua", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 2352, + "scientific_name": "Atherina boyeri", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 172049, + "scientific_name": "Avena fatua", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 172187, + "scientific_name": "Avena hybrida", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 172204, + "scientific_name": "Avena sterilis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22680358, + "scientific_name": "Aythya ferina", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 22680391, + "scientific_name": "Aythya fuligula", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22680398, + "scientific_name": "Aythya marila", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22680373, + "scientific_name": "Aythya nyroca", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 188118, + "scientific_name": "Babka gymnotrachelus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 199960, + "scientific_name": "Barbarea grandiflora", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 43100527, + "scientific_name": "Barbarea plantaginea", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 2553, + "scientific_name": "Barbastella barbastellus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 85181182, + "scientific_name": "Barbastella leucomelas", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 135607, + "scientific_name": "Barbus ciscaucasicus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 168748, + "scientific_name": "Beckmannia syzigachne", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 199964, + "scientific_name": "Bellis hyrcanica", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 2761, + "scientific_name": "Benthophiloides brauneri", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 156757703, + "scientific_name": "Benthophilus abdurahmanovi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 156755278, + "scientific_name": "Benthophilus baeri", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 156755702, + "scientific_name": "Benthophilus ctenolepidus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 135682, + "scientific_name": "Benthophilus granulosus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 156756575, + "scientific_name": "Benthophilus grimmi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 156756692, + "scientific_name": "Benthophilus kessleri", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 135680, + "scientific_name": "Benthophilus leobergius", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 156756871, + "scientific_name": "Benthophilus leptocephalus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 156757038, + "scientific_name": "Benthophilus leptorhynchus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 135656, + "scientific_name": "Benthophilus macrocephalus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 156755866, + "scientific_name": "Benthophilus pinchuki", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 159639612, + "scientific_name": "Benthophilus ragimovi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 156758702, + "scientific_name": "Benthophilus spinosus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 133732509, + "scientific_name": "Berberis integerrima", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 194521, + "scientific_name": "Betula pubescens", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 194832, + "scientific_name": "Betula pubescens var. litwinowii", + "subspecies": "litwinowii", + "rank": "var.", + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 30748, + "scientific_name": "Betula raddeana", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 168750, + "scientific_name": "Bidens tripartita", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 199965, + "scientific_name": "Bilacunaria caspia", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 39270, + "scientific_name": "Blicca bjoerkna", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 164449, + "scientific_name": "Blysmus compressus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 44792563, + "scientific_name": "Bolivaria brachyptera", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 22708146, + "scientific_name": "Bombycilla garrulus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22697346, + "scientific_name": "Botaurus stellaris", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22679954, + "scientific_name": "Branta ruficollis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 170103, + "scientific_name": "Brassica elongata", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 121972235, + "scientific_name": "Brassica elongata subsp. integrifolia", + "subspecies": "integrifolia", + "rank": "subsp.", + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22688927, + "scientific_name": "Bubo bubo", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22697109, + "scientific_name": "Bubulcus ibis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22720513, + "scientific_name": "Bucanetes githagineus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22720520, + "scientific_name": "Bucanetes mongolicus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22680455, + "scientific_name": "Bucephala clangula", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 190987, + "scientific_name": "Bufo eichwaldi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 153571, + "scientific_name": "Bufotes variabilis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 39421, + "scientific_name": "Bufo verrucosissimus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 70401726, + "scientific_name": "Buglossoporus quercinus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 158275, + "scientific_name": "Bulbostylis hispidula", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 199968, + "scientific_name": "Bupleurum wittmannii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 45111439, + "scientific_name": "Burhinus oedicnemus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 61695117, + "scientific_name": "Buteo buteo", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22695973, + "scientific_name": "Buteo lagopus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22736562, + "scientific_name": "Buteo rufinus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 32177, + "scientific_name": "Buxus colchica", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LR/nt" + }, + { + "taxonid": 202944, + "scientific_name": "Buxus sempervirens", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 103766207, + "scientific_name": "Calandrella brachydactyla", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 165467, + "scientific_name": "Caliaeschna microstigma", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22693369, + "scientific_name": "Calidris alba", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22693427, + "scientific_name": "Calidris alpina", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22693363, + "scientific_name": "Calidris canutus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 22693464, + "scientific_name": "Calidris falcinellus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22693431, + "scientific_name": "Calidris ferruginea", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 22693379, + "scientific_name": "Calidris minuta", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22693468, + "scientific_name": "Calidris pugnax", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22693388, + "scientific_name": "Calidris temminckii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 3621, + "scientific_name": "Calomyscus mystax", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 3623, + "scientific_name": "Calomyscus urartensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 158717, + "scientific_name": "Calopteryx orientalis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 158701, + "scientific_name": "Calopteryx splendens", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 118264161, + "scientific_name": "Canis aureus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 3746, + "scientific_name": "Canis lupus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19025492, + "scientific_name": "Capoeta capoeta", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 137745831, + "scientific_name": "Capparis spinosa", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 3786, + "scientific_name": "Capra aegagrus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 3795, + "scientific_name": "Capra cylindricornis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 42395, + "scientific_name": "Capreolus capreolus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22689887, + "scientific_name": "Caprimulgus europaeus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 3849, + "scientific_name": "Carassius carassius", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 43100547, + "scientific_name": "Cardamine uliginosa", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 103764950, + "scientific_name": "Carduelis carduelis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 175266, + "scientific_name": "Carex atherodes", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19617663, + "scientific_name": "Carex diluta", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 164014, + "scientific_name": "Carex divisa", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19617705, + "scientific_name": "Carex otrubae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19617758, + "scientific_name": "Carex pseudofoetida", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 164262, + "scientific_name": "Carex riparia", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 167845, + "scientific_name": "Carex rostrata", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 194274, + "scientific_name": "Carpinus betulus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 194499, + "scientific_name": "Carpinus orientalis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 194500, + "scientific_name": "Carpinus orientalis subsp. macrocarpa", + "subspecies": "macrocarpa", + "rank": "subsp.", + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 194501, + "scientific_name": "Carpinus orientalis subsp. orientalis", + "subspecies": "orientalis", + "rank": "subsp.", + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 200026, + "scientific_name": "Carpoceras tatianae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 22720556, + "scientific_name": "Carpodacus erythrinus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22720610, + "scientific_name": "Carpodacus rubicilla", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22718310, + "scientific_name": "Carpospiza brachydactyla", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 200029, + "scientific_name": "Carum komarovii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 189439, + "scientific_name": "Caspiohydrobia gemmata", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 135706, + "scientific_name": "Caspiomyzon wagneri", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 135622, + "scientific_name": "Caspiosoma caspium", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 202948, + "scientific_name": "Castanea sativa", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19218728, + "scientific_name": "Celtis australis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 79913693, + "scientific_name": "Celtis planchoniana", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 79913713, + "scientific_name": "Celtis tournefortii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 18963206, + "scientific_name": "Cenchrus orientalis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 200567, + "scientific_name": "Centaurea daralagoezica", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 202950, + "scientific_name": "Centaurium erythraea", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 4166, + "scientific_name": "Cerambyx cerdo", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 200052, + "scientific_name": "Cerastium szowitsii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 163995, + "scientific_name": "Ceratophyllum muricatum", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22709936, + "scientific_name": "Cercotrichas galactotes", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22711249, + "scientific_name": "Certhia brachydactyla", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22735060, + "scientific_name": "Certhia familiaris", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 55997072, + "scientific_name": "Cervus elaphus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41788, + "scientific_name": "Cervus nippon", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22714445, + "scientific_name": "Cettia cetti", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22727487, + "scientific_name": "Charadrius alexandrinus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22693868, + "scientific_name": "Charadrius asiaticus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22693770, + "scientific_name": "Charadrius dubius", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22693759, + "scientific_name": "Charadrius hiaticula", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22693862, + "scientific_name": "Charadrius leschenaultii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 4658, + "scientific_name": "Chionomys gud", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 4659, + "scientific_name": "Chionomys nivalis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 4660, + "scientific_name": "Chionomys roberti", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22694764, + "scientific_name": "Chlidonias hybrida", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22694782, + "scientific_name": "Chlidonias leucopterus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22694787, + "scientific_name": "Chlidonias niger", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22720330, + "scientific_name": "Chloris chloris", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 135536, + "scientific_name": "Chondrostoma oxyrhynchum", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 16084483, + "scientific_name": "Chorthippus brunneus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22697691, + "scientific_name": "Ciconia ciconia", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22697669, + "scientific_name": "Ciconia nigra", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22708156, + "scientific_name": "Cinclus cinclus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22734216, + "scientific_name": "Circaetus gallicus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22695344, + "scientific_name": "Circus aeruginosus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22727733, + "scientific_name": "Circus cyaneus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22695396, + "scientific_name": "Circus macrourus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 22695405, + "scientific_name": "Circus pygargus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 200626, + "scientific_name": "Cirsium pugnax", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22696027, + "scientific_name": "Clanga clanga", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 22696022, + "scientific_name": "Clanga pomarina", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22680427, + "scientific_name": "Clangula hyemalis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 155608, + "scientific_name": "Clessiniola variabilis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 135707, + "scientific_name": "Clupeonella caspia", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 98471289, + "scientific_name": "Clupeonella engrauliformis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 98471433, + "scientific_name": "Clupeonella grimmi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 42622224, + "scientific_name": "Cobitis amphilekta", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 22720681, + "scientific_name": "Coccothraustes coccothraustes", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 5047, + "scientific_name": "Cochlicopa nitens", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LR/lc" + }, + { + "taxonid": 59702, + "scientific_name": "Coenagrion australocaspicum", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 165511, + "scientific_name": "Coenagrion lunulatum", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 165520, + "scientific_name": "Coenagrion ornatum", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 60288, + "scientific_name": "Coenagrion ponticum", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 158707, + "scientific_name": "Coenagrion puella", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 165484, + "scientific_name": "Coenagrion pulchellum", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 165502, + "scientific_name": "Coenagrion scitulum", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22690066, + "scientific_name": "Columba livia", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22690088, + "scientific_name": "Columba oenas", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22690103, + "scientific_name": "Columba palumbus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22682860, + "scientific_name": "Coracias garrulus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 98201936, + "scientific_name": "Corbicula fluminalis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 165507, + "scientific_name": "Cordulegaster insignis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 165526, + "scientific_name": "Cordulegaster picta", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 158702, + "scientific_name": "Cordulia aenea", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 130048490, + "scientific_name": "Cornus iberica", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 79914024, + "scientific_name": "Cornus mas", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 157284, + "scientific_name": "Coronella austriaca", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22706068, + "scientific_name": "Corvus corax", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22706016, + "scientific_name": "Corvus corone", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22705983, + "scientific_name": "Corvus frugilegus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22705929, + "scientific_name": "Corvus monedula", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 63521, + "scientific_name": "Corylus avellana", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 194668, + "scientific_name": "Corylus colurna", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 202959, + "scientific_name": "Cotinus coggygria", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22678944, + "scientific_name": "Coturnix coturnix", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 200085, + "scientific_name": "Cousinia araxena", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 200090, + "scientific_name": "Cousinia daralaghezica", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 200084, + "scientific_name": "Cousinia gabrieljaniae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 200087, + "scientific_name": "Cousinia iljinii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 200093, + "scientific_name": "Cousinia lomakinii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 200088, + "scientific_name": "Cousinia macrocephala", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 200097, + "scientific_name": "Crambe armena", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 102860169, + "scientific_name": "Crataegus meyeri", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 203428, + "scientific_name": "Crataegus pentagyna", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 109993237, + "scientific_name": "Crataegus rhipidophylla", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22692543, + "scientific_name": "Crex crex", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 5528, + "scientific_name": "Cricetulus migratorius", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 5596, + "scientific_name": "Crocidura armenica", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 136444, + "scientific_name": "Crocidura caspica", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 29651, + "scientific_name": "Crocidura leucodon", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 29654, + "scientific_name": "Crocidura serezkyensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 29656, + "scientific_name": "Crocidura suaveolens", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 59859, + "scientific_name": "Crocothemis erythraea", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 164139, + "scientific_name": "Crypsis alopecuroides", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 164424, + "scientific_name": "Crypsis schoenoides", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 61295, + "scientific_name": "Ctenopharyngodon idella", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22683873, + "scientific_name": "Cuculus canorus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 103874024, + "scientific_name": "Curruca cantillans", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22716910, + "scientific_name": "Curruca communis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22734793, + "scientific_name": "Curruca crassirostris", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22734992, + "scientific_name": "Curruca curruca", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22716959, + "scientific_name": "Curruca melanocephala", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22716971, + "scientific_name": "Curruca mystacea", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 103872996, + "scientific_name": "Curruca nana", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22716937, + "scientific_name": "Curruca nisoria", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22709707, + "scientific_name": "Cyanecula svecica", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 103761667, + "scientific_name": "Cyanistes caeruleus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 61611928, + "scientific_name": "Cydonia oblonga", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22679862, + "scientific_name": "Cygnus columbianus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22679856, + "scientific_name": "Cygnus cygnus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22679839, + "scientific_name": "Cygnus olor", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 164294, + "scientific_name": "Cyperus difformis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 157979, + "scientific_name": "Cyperus flavescens", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 175292, + "scientific_name": "Cyperus glaber", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 18906981, + "scientific_name": "Cyperus odoratus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 158183, + "scientific_name": "Cyperus rotundus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 6181, + "scientific_name": "Cyprinus carpio", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 200114, + "scientific_name": "Dactylorhiza euxina", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 22486113, + "scientific_name": "Dactylorhiza urvilleana", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 164706, + "scientific_name": "Darevskia armeniaca", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 164713, + "scientific_name": "Darevskia caucasica", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 164702, + "scientific_name": "Darevskia chlorogaster", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 164587, + "scientific_name": "Darevskia daghestanica", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 164576, + "scientific_name": "Darevskia derjugini", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 164654, + "scientific_name": "Darevskia portschinskii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 157245, + "scientific_name": "Darevskia praticola", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 164753, + "scientific_name": "Darevskia raddei", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 164563, + "scientific_name": "Darevskia rostombekowi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 164633, + "scientific_name": "Darevskia rudis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 164707, + "scientific_name": "Darevskia valentini", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 172210, + "scientific_name": "Daucus carota", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 103811886, + "scientific_name": "Delichon urbicum", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22727124, + "scientific_name": "Dendrocopos leucotos", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22681124, + "scientific_name": "Dendrocopos major", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22681127, + "scientific_name": "Dendrocopos syriacus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 200128, + "scientific_name": "Dianthus bicolor", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 200132, + "scientific_name": "Dianthus raddeanus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 200124, + "scientific_name": "Dianthus schemachensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 164595, + "scientific_name": "Dolichophis schmidti", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 188911, + "scientific_name": "Dreissena bugensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 188971, + "scientific_name": "Dreissena caspia", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 155495, + "scientific_name": "Dreissena polymorpha", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 189369, + "scientific_name": "Dreissena rostriformis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22681076, + "scientific_name": "Dryobates minor", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22681382, + "scientific_name": "Dryocopus martius", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 6858, + "scientific_name": "Dryomys nitedula", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 200139, + "scientific_name": "Dryopteris raddeana", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 62774969, + "scientific_name": "Egretta garzetta", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 164639, + "scientific_name": "Eirenis collaris", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 157292, + "scientific_name": "Eirenis modestus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 164628, + "scientific_name": "Eirenis punctatolineatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 62002626, + "scientific_name": "Elaeagnus angustifolia", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 157275, + "scientific_name": "Elaphe dione", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 157265, + "scientific_name": "Elaphe sauromates", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 43100762, + "scientific_name": "Eleocharis argyrolepis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13579845, + "scientific_name": "Eleocharis mitracarpa", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 164353, + "scientific_name": "Eleocharis quinqueflora", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 7655, + "scientific_name": "Ellobius lutescens", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 21343347, + "scientific_name": "Elymus elongatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 21343956, + "scientific_name": "Elymus hispidus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22720909, + "scientific_name": "Emberiza buchanani", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22721020, + "scientific_name": "Emberiza calandra", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22720894, + "scientific_name": "Emberiza cia", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22720878, + "scientific_name": "Emberiza citrinella", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22720916, + "scientific_name": "Emberiza hortulana", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22720990, + "scientific_name": "Emberiza melanocephala", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22721012, + "scientific_name": "Emberiza schoeniclus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 7717, + "scientific_name": "Emys orbicularis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LR/nt" + }, + { + "taxonid": 165515, + "scientific_name": "Epallage fatime", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 164347, + "scientific_name": "Epilobium hirsutum", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19619856, + "scientific_name": "Epilobium minutiflorum", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 164450, + "scientific_name": "Epilobium parviflorum", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22486454, + "scientific_name": "Epipactis rechingeri", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 7910, + "scientific_name": "Eptesicus nilssonii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 85198662, + "scientific_name": "Eptesicus ognevi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 85199559, + "scientific_name": "Eptesicus serotinus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 167859, + "scientific_name": "Equisetum palustre", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 203004, + "scientific_name": "Equisetum telmateia", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 7951, + "scientific_name": "Equus hemionus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 157260, + "scientific_name": "Eremias arguta", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 164583, + "scientific_name": "Eremias pleskei", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 164641, + "scientific_name": "Eremias strauchi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 157286, + "scientific_name": "Eremias velox", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22717434, + "scientific_name": "Eremophila alpestris", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 40605, + "scientific_name": "Erinaceus concolor", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22709675, + "scientific_name": "Erithacus rubecula", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 200162, + "scientific_name": "Erysimum brachycarpum", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 200158, + "scientific_name": "Erysimum caspicum", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 200160, + "scientific_name": "Erysimum wagifii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 158696, + "scientific_name": "Erythromma lindenii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 165478, + "scientific_name": "Erythromma viridulum", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 157244, + "scientific_name": "Eryx jaculus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 47944419, + "scientific_name": "Esymus fumigatulus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 157289, + "scientific_name": "Eumeces schneiderii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 68378608, + "scientific_name": "Eumodicogryllus bordigalensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 79913822, + "scientific_name": "Euonymus europaeus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 62391, + "scientific_name": "Euonymus latifolius", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 200167, + "scientific_name": "Euphorbia grossheimii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 79914188, + "scientific_name": "Fagus orientalis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22696487, + "scientific_name": "Falco biarmicus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22696495, + "scientific_name": "Falco cherrug", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 22696453, + "scientific_name": "Falco columbarius", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22696357, + "scientific_name": "Falco naumanni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 45354964, + "scientific_name": "Falco peregrinus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22696460, + "scientific_name": "Falco subbuteo", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22696362, + "scientific_name": "Falco tinnunculus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22696432, + "scientific_name": "Falco vespertinus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 8540, + "scientific_name": "Felis chaus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 131299383, + "scientific_name": "Felis lybica", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 181049859, + "scientific_name": "Felis silvestris", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 200170, + "scientific_name": "Ferula caucasica", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 22486248, + "scientific_name": "Festuca varia", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22735909, + "scientific_name": "Ficedula parva", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22709319, + "scientific_name": "Ficedula semitorquata", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 169008, + "scientific_name": "Fimbristylis dichotoma", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 180096947, + "scientific_name": "Flavoparmelia caperata", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22678719, + "scientific_name": "Francolinus francolinus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 164056, + "scientific_name": "Frangula alnus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 203367, + "scientific_name": "Fraxinus excelsior", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 174361, + "scientific_name": "Freyeria trochylus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22720030, + "scientific_name": "Fringilla coelebs", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22720041, + "scientific_name": "Fringilla montifringilla", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 200174, + "scientific_name": "Fritillaria grandiflora", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 22692913, + "scientific_name": "Fulica atra", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 189631, + "scientific_name": "Galba schirazensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22717383, + "scientific_name": "Galerida cristata", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22693097, + "scientific_name": "Gallinago gallinago", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22693093, + "scientific_name": "Gallinago media", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 62120190, + "scientific_name": "Gallinula chloropus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 103723684, + "scientific_name": "Garrulus glandarius", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22697834, + "scientific_name": "Gavia arctica", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22697829, + "scientific_name": "Gavia stellata", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 8976, + "scientific_name": "Gazella subgutturosa", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 62026481, + "scientific_name": "Gelochelidon nilotica", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22694136, + "scientific_name": "Glareola nordmanni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 22694127, + "scientific_name": "Glareola pratincola", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 39316, + "scientific_name": "Glis glis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 157282, + "scientific_name": "Gloydius halys", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19618896, + "scientific_name": "Glyceria arundinacea", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 203353, + "scientific_name": "Glycyrrhiza glabra", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 135646, + "scientific_name": "Gobio holurus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 173329988, + "scientific_name": "Gomphus schneiderii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 164174, + "scientific_name": "Groenlandia densa", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22692146, + "scientific_name": "Grus grus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 9568, + "scientific_name": "Gymnocephalus cernua", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22695174, + "scientific_name": "Gypaetus barbatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 22695219, + "scientific_name": "Gyps fulvus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 200198, + "scientific_name": "Gypsophila capitata", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 200196, + "scientific_name": "Gypsophila robusta", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 200197, + "scientific_name": "Gypsophila szovitsii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 155828, + "scientific_name": "Gyraulus laevis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22693613, + "scientific_name": "Haematopus ostralegus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 22725846, + "scientific_name": "Halcyon smyrnensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22695137, + "scientific_name": "Haliaeetus albicilla", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 79919017, + "scientific_name": "Halostachys belangeriana", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 172636580, + "scientific_name": "Hedysarum formosum", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 164067, + "scientific_name": "Hemarthria altissima", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 157285, + "scientific_name": "Hemorrhois ravergieri", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 200217, + "scientific_name": "Heracleum schelkovnikovii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 200212, + "scientific_name": "Heracleum trachyloma", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 164634, + "scientific_name": "Heremites septemtaeniatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22696092, + "scientific_name": "Hieraaetus pennatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 118892125, + "scientific_name": "Hierodula tenuidentata", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22727969, + "scientific_name": "Himantopus himantopus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 155940, + "scientific_name": "Hippeutis complanatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22714916, + "scientific_name": "Hippolais icterina", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22714904, + "scientific_name": "Hippolais languida", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 55686342, + "scientific_name": "Hippophae rhamnoides", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22712252, + "scientific_name": "Hirundo rustica", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 172229, + "scientific_name": "Hordeum brevisubulatum", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 172092, + "scientific_name": "Hordeum bulbosum", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 172273, + "scientific_name": "Hordeum marinum", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 172094, + "scientific_name": "Hordeum murinum", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 10269, + "scientific_name": "Huso huso", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 10272, + "scientific_name": "Huso huso Caspian Sea subpopulation", + "subspecies": null, + "rank": null, + "subpopulation": "Caspian Sea subpopulation", + "category": "CR" + }, + { + "taxonid": 10274, + "scientific_name": "Hyaena hyaena", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 22694469, + "scientific_name": "Hydrocoloeus minutus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13581832, + "scientific_name": "Hydrocotyle ranunculoides", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22694524, + "scientific_name": "Hydroprogne caspia", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 82494309, + "scientific_name": "Hyla orientalis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 55647, + "scientific_name": "Hyla savignyi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 10542, + "scientific_name": "Hyles hippophaes", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 200236, + "scientific_name": "Hypericum theodori", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 44856, + "scientific_name": "Hypsugo savii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 10751, + "scientific_name": "Hystrix indica", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22714891, + "scientific_name": "Iduna caligata", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22734747, + "scientific_name": "Iduna pallida", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22709758, + "scientific_name": "Irania gutturalis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 164697, + "scientific_name": "Iranolacerta brandtii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 200241, + "scientific_name": "Iris camillae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 44791269, + "scientific_name": "Iris polystictica", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 163999, + "scientific_name": "Iris pseudacorus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 200246, + "scientific_name": "Isatis karjaginii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 200247, + "scientific_name": "Isatis ornithorhynchus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 165479, + "scientific_name": "Ischnura elegans", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 158572, + "scientific_name": "Ischnura fountaineae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 165525, + "scientific_name": "Ischnura pumilio", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22735766, + "scientific_name": "Ixobrychus minutus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 63495, + "scientific_name": "Juglans regia", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 42229, + "scientific_name": "Juniperus communis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 42232, + "scientific_name": "Juniperus excelsa", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 16336618, + "scientific_name": "Juniperus excelsa subsp. excelsa", + "subspecies": "excelsa", + "rank": "subsp.", + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 16336635, + "scientific_name": "Juniperus excelsa subsp. polycarpos", + "subspecies": "polycarpos", + "rank": "subsp.", + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 42234, + "scientific_name": "Juniperus foetidissima", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 42243, + "scientific_name": "Juniperus oxycedrus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 16347367, + "scientific_name": "Juniperus oxycedrus subsp. oxycedrus", + "subspecies": "oxycedrus", + "rank": "subsp.", + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 42249, + "scientific_name": "Juniperus sabina", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 191611, + "scientific_name": "Juniperus sabina var. sabina", + "subspecies": "sabina", + "rank": "var.", + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22680683, + "scientific_name": "Jynx torquilla", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 200494, + "scientific_name": "Klasea caucasica", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 135608, + "scientific_name": "Knipowitschia bergi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 11030, + "scientific_name": "Knipowitschia caucasica", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 123435142, + "scientific_name": "Knipowitschia iljini", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 135509, + "scientific_name": "Knipowitschia longecaudata", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 161916, + "scientific_name": "Kosteletzkya pentacarpos", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 157288, + "scientific_name": "Lacerta agilis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 164749, + "scientific_name": "Lacerta media", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 157287, + "scientific_name": "Lacerta strigata", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 189493, + "scientific_name": "Laevicaspia caspia", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 189262, + "scientific_name": "Laevicaspia conus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 174427, + "scientific_name": "Lampides boeticus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22705001, + "scientific_name": "Lanius collurio", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 103718932, + "scientific_name": "Lanius excubitor", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22705038, + "scientific_name": "Lanius minor", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22705095, + "scientific_name": "Lanius senator", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 22735929, + "scientific_name": "Larus cachinnans", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22694308, + "scientific_name": "Larus canus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22694428, + "scientific_name": "Larus genei", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22694379, + "scientific_name": "Larus ichthyaetus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22694443, + "scientific_name": "Larus melanocephalus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22694420, + "scientific_name": "Larus ridibundus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 176510, + "scientific_name": "Lathyrus annuus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19379015, + "scientific_name": "Lathyrus aphaca", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 120073091, + "scientific_name": "Lathyrus aureus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19379018, + "scientific_name": "Lathyrus blepharicarpus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 120073416, + "scientific_name": "Lathyrus chloranthus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 174708, + "scientific_name": "Lathyrus cicera", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 120073522, + "scientific_name": "Lathyrus cyaneus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 120073759, + "scientific_name": "Lathyrus digitatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 176501, + "scientific_name": "Lathyrus hirsutus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19379024, + "scientific_name": "Lathyrus inconspicuus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 120074322, + "scientific_name": "Lathyrus incurvus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 120074472, + "scientific_name": "Lathyrus laxiflorus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19379030, + "scientific_name": "Lathyrus nissolia", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19891853, + "scientific_name": "Lathyrus pallescens", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 120075014, + "scientific_name": "Lathyrus pratensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19379043, + "scientific_name": "Lathyrus roseus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19379055, + "scientific_name": "Lathyrus setifolius", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19379058, + "scientific_name": "Lathyrus sphaericus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 176568, + "scientific_name": "Lathyrus sylvestris", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 176505, + "scientific_name": "Lathyrus tuberosus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22681114, + "scientific_name": "Leiopicus medius", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 164057, + "scientific_name": "Lemna minor", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 165516, + "scientific_name": "Lestes dryas", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 165480, + "scientific_name": "Lestes macrostigma", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 165492, + "scientific_name": "Lestes virens", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 11873, + "scientific_name": "Leucaspius delineatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 2178, + "scientific_name": "Leuciscus aspius", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22692053, + "scientific_name": "Leucogeranus leucogeranus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 165531, + "scientific_name": "Libellula depressa", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22693158, + "scientific_name": "Limosa lapponica", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 22693150, + "scientific_name": "Limosa limosa", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 22720441, + "scientific_name": "Linaria cannabina", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22720438, + "scientific_name": "Linaria flavirostris", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 200274, + "scientific_name": "Linaria zangezura", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 165460, + "scientific_name": "Lindenia tetraphylla", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 59481, + "scientific_name": "Lissotriton vulgaris", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22714679, + "scientific_name": "Locustella fluviatilis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22714684, + "scientific_name": "Locustella luscinioides", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22714657, + "scientific_name": "Locustella naevia", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22711810, + "scientific_name": "Lophophanes cristatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 172602755, + "scientific_name": "Lotus herbaceus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22720646, + "scientific_name": "Loxia curvirostra", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 135684, + "scientific_name": "Luciobarbus brachycephalus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 135687, + "scientific_name": "Luciobarbus capito", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 156750169, + "scientific_name": "Luciobarbus caspius", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19383456, + "scientific_name": "Luciobarbus mursa", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22717411, + "scientific_name": "Lullula arborea", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22709691, + "scientific_name": "Luscinia luscinia", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22709696, + "scientific_name": "Luscinia megarhynchos", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 12419, + "scientific_name": "Lutra lutra", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 12433, + "scientific_name": "Lycaena dispar", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LR/nt" + }, + { + "taxonid": 122090665, + "scientific_name": "Lycoperdon perlatum", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 163972, + "scientific_name": "Lycopus europaeus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 189802, + "scientific_name": "Lymnaea bakowskyana", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 22693133, + "scientific_name": "Lymnocryptes minimus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 12519, + "scientific_name": "Lynx lynx", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22679483, + "scientific_name": "Lyrurus mlokosiewiczi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 64317602, + "scientific_name": "Lysimachia maritima", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 164091, + "scientific_name": "Lythrum hyssopifolia", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 164323, + "scientific_name": "Lythrum salicaria", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 157295, + "scientific_name": "Macrovipera lebetina", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 157253, + "scientific_name": "Malpolon insignitus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 50049710, + "scientific_name": "Malus orientalis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 44793247, + "scientific_name": "Mantis religiosa", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22680157, + "scientific_name": "Mareca penelope", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22680149, + "scientific_name": "Mareca strepera", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22680339, + "scientific_name": "Marmaronetta angustirostris", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 29672, + "scientific_name": "Martes foina", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19379282, + "scientific_name": "Medicago astroites", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 126657419, + "scientific_name": "Medicago biflora", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 126657791, + "scientific_name": "Medicago brachycarpa", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 176599, + "scientific_name": "Medicago littoralis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 71716111, + "scientific_name": "Medicago papillosa", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 174725, + "scientific_name": "Medicago sativa", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 176489, + "scientific_name": "Medicago truncatula", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22724836, + "scientific_name": "Melanitta fusca", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 22724879, + "scientific_name": "Melanitta nigra", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22717288, + "scientific_name": "Melanocorypha bimaculata", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22717285, + "scientific_name": "Melanocorypha calandra", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22717301, + "scientific_name": "Melanocorypha yeltoniensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 29673, + "scientific_name": "Meles meles", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 64317970, + "scientific_name": "Mentha arvensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 164306, + "scientific_name": "Mentha longifolia", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22680465, + "scientific_name": "Mergellus albellus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22680492, + "scientific_name": "Mergus merganser", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22680485, + "scientific_name": "Mergus serrator", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13164, + "scientific_name": "Meriones libycus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13166, + "scientific_name": "Meriones persicus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13170, + "scientific_name": "Meriones tristrami", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13172, + "scientific_name": "Meriones vinogradovi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22683756, + "scientific_name": "Merops apiaster", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22683740, + "scientific_name": "Merops persicus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13220, + "scientific_name": "Mesocricetus brandti", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 123435177, + "scientific_name": "Mesogobius nonultimus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 79920045, + "scientific_name": "Mespilus germanica", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22696734, + "scientific_name": "Microcarbo pygmaeus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13373, + "scientific_name": "Micromys minutus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13488, + "scientific_name": "Microtus arvalis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13428, + "scientific_name": "Microtus daghestanicus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13454, + "scientific_name": "Microtus levis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136354, + "scientific_name": "Microtus majori", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13448, + "scientific_name": "Microtus nasarovi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 13456, + "scientific_name": "Microtus schelkovnikovi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13458, + "scientific_name": "Microtus socialis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 181568721, + "scientific_name": "Milvus migrans", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22695072, + "scientific_name": "Milvus milvus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 81633088, + "scientific_name": "Miniopterus pallidus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 22708257, + "scientific_name": "Monticola saxatilis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22708286, + "scientific_name": "Monticola solitarius", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 103819582, + "scientific_name": "Montifringilla nivalis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22993, + "scientific_name": "Montivipera raddei", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 22718348, + "scientific_name": "Motacilla alba", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22718392, + "scientific_name": "Motacilla cinerea", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22718379, + "scientific_name": "Motacilla citreola", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 103822349, + "scientific_name": "Motacilla flava", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22709192, + "scientific_name": "Muscicapa striata", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13966, + "scientific_name": "Mus macedonicus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13972, + "scientific_name": "Mus musculus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 29674, + "scientific_name": "Mustela erminea", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 70207409, + "scientific_name": "Mustela nivalis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136553, + "scientific_name": "Myotis aurascens", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 14123, + "scientific_name": "Myotis bechsteinii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 14124, + "scientific_name": "Myotis blythii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 85566997, + "scientific_name": "Myotis brandtii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 14129, + "scientific_name": "Myotis emarginatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 14134, + "scientific_name": "Myotis mystacinus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136495, + "scientific_name": "Myotis nipalensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 164335, + "scientific_name": "Myriophyllum verticillatum", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 164322, + "scientific_name": "Najas marina", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19557120, + "scientific_name": "Narthecium balansae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 164311, + "scientific_name": "Nasturtium officinale", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 165594334, + "scientific_name": "Natrix natrix", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 157256, + "scientific_name": "Natrix tessellata", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 156761995, + "scientific_name": "Neogobius caspius", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 14524, + "scientific_name": "Neogobius melanostomus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 135596, + "scientific_name": "Neogobius pallasi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 29659, + "scientific_name": "Neomys teres", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22695180, + "scientific_name": "Neophron percnopterus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 22680348, + "scientific_name": "Netta rufina", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 200295, + "scientific_name": "Nonea daghestanica", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 22693190, + "scientific_name": "Numenius arquata", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 22693178, + "scientific_name": "Numenius phaeopus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22693185, + "scientific_name": "Numenius tenuirostris", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 14918, + "scientific_name": "Nyctalus lasiopterus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 14919, + "scientific_name": "Nyctalus leisleri", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 14920, + "scientific_name": "Nyctalus noctula", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22697211, + "scientific_name": "Nycticorax nycticorax", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22734803, + "scientific_name": "Oenanthe chrysopygia", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22710325, + "scientific_name": "Oenanthe deserti", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22710292, + "scientific_name": "Oenanthe finschii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22710302, + "scientific_name": "Oenanthe hispanica", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22710333, + "scientific_name": "Oenanthe isabellina", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 103773898, + "scientific_name": "Oenanthe oenanthe", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22710308, + "scientific_name": "Oenanthe pleschanka", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 164155, + "scientific_name": "Oenanthe silaifolia", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 164293, + "scientific_name": "Oldenlandia capensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 203369, + "scientific_name": "Onoclea struthiopteris", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 185666, + "scientific_name": "Onychogomphus flexuosus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 165489, + "scientific_name": "Onychogomphus forcipatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 157279, + "scientific_name": "Ophisops elegans", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 99011545, + "scientific_name": "Oplismenus compositus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 103692938, + "scientific_name": "Oriolus oriolus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 200318, + "scientific_name": "Ornithogalum hyrcanum", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 158689, + "scientific_name": "Orthetrum albistylum", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 158698, + "scientific_name": "Orthetrum brunneum", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 165521, + "scientific_name": "Orthetrum cancellatum", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 158682, + "scientific_name": "Orthetrum coerulescens", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 13186368, + "scientific_name": "Orthetrum coerulescens ssp. anceps", + "subspecies": "anceps", + "rank": "ssp.", + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 165470, + "scientific_name": "Orthetrum sabina", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 194280, + "scientific_name": "Ostrya carpinifolia", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22691900, + "scientific_name": "Otis tarda", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 15640, + "scientific_name": "Otocolobus manul", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 155019854, + "scientific_name": "Otus scops", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 54940218, + "scientific_name": "Ovis gmelini", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 19385622, + "scientific_name": "Oxynoemacheilus bergianus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19387030, + "scientific_name": "Oxynoemacheilus brandtii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19428862, + "scientific_name": "Oxynoemacheilus lenkoranensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 135495, + "scientific_name": "Oxynoemacheilus merga", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22679814, + "scientific_name": "Oxyura leucocephala", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 188978, + "scientific_name": "Paladilhiopsis schakuranica", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 22694938, + "scientific_name": "Pandion haliaetus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 64319146, + "scientific_name": "Panicum acuminatum", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 59971, + "scientific_name": "Pantala flavescens", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 15954, + "scientific_name": "Panthera pardus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 15955, + "scientific_name": "Panthera tigris", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 22716776, + "scientific_name": "Panurus biarmicus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 200325, + "scientific_name": "Papaver talyshense", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 164611, + "scientific_name": "Paralaudakia caucasia", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 194660642, + "scientific_name": "Parmelia squarrosa", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 16249, + "scientific_name": "Parnassius apollo", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 174210, + "scientific_name": "Parnassius mnemosyne", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 122552788, + "scientific_name": "Parnassius nordmanni", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22735990, + "scientific_name": "Parus major", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 169030, + "scientific_name": "Paspalum distichum", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 103818789, + "scientific_name": "Passer domesticus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22718179, + "scientific_name": "Passer hispaniolensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22718270, + "scientific_name": "Passer montanus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22710881, + "scientific_name": "Pastor roseus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22697599, + "scientific_name": "Pelecanus crispus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 22697590, + "scientific_name": "Pelecanus onocrotalus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 16494, + "scientific_name": "Pelecus cultratus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 172313717, + "scientific_name": "Pelobates syriacus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 39422, + "scientific_name": "Pelodytes caucasicus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 58705, + "scientific_name": "Pelophylax ridibundus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 16580, + "scientific_name": "Perca fluviatilis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22678911, + "scientific_name": "Perdix perdix", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22735965, + "scientific_name": "Periparus ater", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22694989, + "scientific_name": "Pernis apivorus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 184425, + "scientific_name": "Persicaria amphibia", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 164411, + "scientific_name": "Persicaria lapathifolia", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 175559, + "scientific_name": "Persicaria maculosa", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22718307, + "scientific_name": "Petronia petronia", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22696792, + "scientific_name": "Phalacrocorax carbo", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22693490, + "scientific_name": "Phalaropus lobatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 45100023, + "scientific_name": "Phasianus colchicus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 12659, + "scientific_name": "Phengaris arion", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LR/nt" + }, + { + "taxonid": 12662, + "scientific_name": "Phengaris nausithous", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LR/nt" + }, + { + "taxonid": 22697360, + "scientific_name": "Phoenicopterus roseus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22710072, + "scientific_name": "Phoenicurus erythrogastrus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22710051, + "scientific_name": "Phoenicurus ochruros", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22710055, + "scientific_name": "Phoenicurus phoenicurus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 164494, + "scientific_name": "Phragmites australis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 164759, + "scientific_name": "Phrynocephalus horvathi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 164647, + "scientific_name": "Phrynocephalus persicus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 103843725, + "scientific_name": "Phylloscopus collybita", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22731553, + "scientific_name": "Phylloscopus nitidus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22715260, + "scientific_name": "Phylloscopus sibilatrix", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22728939, + "scientific_name": "Phylloscopus sindianus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 103845399, + "scientific_name": "Phylloscopus trochiloides", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22715240, + "scientific_name": "Phylloscopus trochilus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 103727048, + "scientific_name": "Pica pica", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22725022, + "scientific_name": "Picus viridis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 42347, + "scientific_name": "Pinus brutia", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 34183, + "scientific_name": "Pinus brutia var. eldarica", + "subspecies": "eldarica", + "rank": "var.", + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 42418, + "scientific_name": "Pinus sylvestris", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 17314, + "scientific_name": "Pipistrellus kuhlii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 17316, + "scientific_name": "Pipistrellus nathusii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 85333513, + "scientific_name": "Pipistrellus pipistrellus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136649, + "scientific_name": "Pipistrellus pygmaeus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19365844, + "scientific_name": "Pistacia atlantica", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 156083, + "scientific_name": "Planorbarius corneus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 64320667, + "scientific_name": "Plantago maritima", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22697555, + "scientific_name": "Platalea leucorodia", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 33951, + "scientific_name": "Platanus orientalis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 157277, + "scientific_name": "Platyceps najadum", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 158693, + "scientific_name": "Platycnemis dealbata", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 158709, + "scientific_name": "Platycnemis pennipes", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 85535522, + "scientific_name": "Plecotus auritus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136229, + "scientific_name": "Plecotus macrobullaris", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22697422, + "scientific_name": "Plegadis falcinellus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22693727, + "scientific_name": "Pluvialis apricaria", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22693749, + "scientific_name": "Pluvialis squatarola", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 176440, + "scientific_name": "Poa pratensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22696606, + "scientific_name": "Podiceps auritus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 22696602, + "scientific_name": "Podiceps cristatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22696599, + "scientific_name": "Podiceps grisegena", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22696610, + "scientific_name": "Podiceps nigricollis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 200339, + "scientific_name": "Podospermum grossheimii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 103761508, + "scientific_name": "Poecile hyrcanus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 103761494, + "scientific_name": "Poecile lugubris", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136637664, + "scientific_name": "Polycarpon prostratum", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 200344, + "scientific_name": "Polygonum caspicum", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 164232, + "scientific_name": "Polypogon monspeliensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 200346, + "scientific_name": "Polystichum kadyrovii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 153745, + "scientific_name": "Pontastacus leptodactylus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 153702, + "scientific_name": "Pontastacus pachypus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 160251, + "scientific_name": "Pontia daplidice", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 156761759, + "scientific_name": "Ponticola bathybius", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19513722, + "scientific_name": "Ponticola cyrius", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 156763140, + "scientific_name": "Ponticola goebelii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 188114, + "scientific_name": "Ponticola gorlap", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 188116, + "scientific_name": "Ponticola syrman", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 203464, + "scientific_name": "Populus alba", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19178509, + "scientific_name": "Populus euphratica", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 164052144, + "scientific_name": "Populus hyrcana", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 63530, + "scientific_name": "Populus nigra", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 61959941, + "scientific_name": "Populus tremula", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 58517228, + "scientific_name": "Poronia punctata", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22692792, + "scientific_name": "Porphyrio porphyrio", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 164001, + "scientific_name": "Portulaca oleracea", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22692676, + "scientific_name": "Porzana porzana", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 167896, + "scientific_name": "Potamogeton alpinus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 164038, + "scientific_name": "Potamogeton lucens", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 164189, + "scientific_name": "Potamogeton perfoliatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 164357, + "scientific_name": "Potamogeton trichoides", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 134681, + "scientific_name": "Potamon ibericum", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 164521, + "scientific_name": "Potentilla supina", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41686, + "scientific_name": "Procyon lotor", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 18366, + "scientific_name": "Proserpinus proserpina", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 135486, + "scientific_name": "Proterorhinus nasalis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22718617, + "scientific_name": "Prunella collaris", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22718651, + "scientific_name": "Prunella modularis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 105986083, + "scientific_name": "Prunella ocularis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 203256, + "scientific_name": "Prunella vulgaris", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 172064, + "scientific_name": "Prunus avium", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 172162, + "scientific_name": "Prunus cerasifera", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 50135950, + "scientific_name": "Prunus domestica", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 50393773, + "scientific_name": "Prunus fenzliana", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 172277, + "scientific_name": "Prunus laurocerasus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 172121, + "scientific_name": "Prunus mahaleb", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 172194, + "scientific_name": "Prunus spinosa", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 164615, + "scientific_name": "Psammophis lineolatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 200387, + "scientific_name": "Psephellus erivanensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 200374, + "scientific_name": "Psephellus karabaghensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 189051, + "scientific_name": "Pseudamnicola brusiniana", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 109224220, + "scientific_name": "Pseudoceles persa", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 157263, + "scientific_name": "Pseudopus apodus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 166136, + "scientific_name": "Pseudorasbora parva", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 66815986, + "scientific_name": "Pterocarya fraxinifolia", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 22692983, + "scientific_name": "Pterocles alchata", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22693002, + "scientific_name": "Pterocles orientalis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22712216, + "scientific_name": "Ptyonoprogne rupestris", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 18877, + "scientific_name": "Pungitius platygaster", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41669, + "scientific_name": "Pusa caspica", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 189305, + "scientific_name": "Pyrgula abichi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 189386, + "scientific_name": "Pyrgula behningi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 189385, + "scientific_name": "Pyrgula cincta", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 189454, + "scientific_name": "Pyrgula ebersini", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 189070, + "scientific_name": "Pyrgula isseli", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 189244, + "scientific_name": "Pyrgula kolesnikoviana", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 189508, + "scientific_name": "Pyrgula nossovi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 189458, + "scientific_name": "Pyrgula pulla", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 188922, + "scientific_name": "Pyrgula rudis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 189266, + "scientific_name": "Pyrgula sowinskyi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 22705921, + "scientific_name": "Pyrrhocorax graculus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22705916, + "scientific_name": "Pyrrhocorax pyrrhocorax", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22720671, + "scientific_name": "Pyrrhula pyrrhula", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 64133731, + "scientific_name": "Pyrus acutiserrata", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 173010, + "scientific_name": "Pyrus communis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 173012, + "scientific_name": "Pyrus elaeagrifolia", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 64134271, + "scientific_name": "Pyrus megrica", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 200403, + "scientific_name": "Pyrus nutans", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 200401, + "scientific_name": "Pyrus vsevolodovii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 200408, + "scientific_name": "Pyrus zangezura", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 78809372, + "scientific_name": "Quercus castaneifolia", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 194176, + "scientific_name": "Quercus infectoria", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 78968730, + "scientific_name": "Quercus macranthera", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 62539, + "scientific_name": "Quercus petraea", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 63532, + "scientific_name": "Quercus robur", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22725141, + "scientific_name": "Rallus aquaticus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 58651, + "scientific_name": "Rana macrocnemis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 164175, + "scientific_name": "Ranunculus sceleratus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19353, + "scientific_name": "Rattus norvegicus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22693712, + "scientific_name": "Recurvirostra avosetta", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22735002, + "scientific_name": "Regulus ignicapilla", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22734997, + "scientific_name": "Regulus regulus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 155249960, + "scientific_name": "Remiz pendulinus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 184930799, + "scientific_name": "Reseda microcarpa", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 61957125, + "scientific_name": "Rhamnus cathartica", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19515, + "scientific_name": "Rhinolophus blasii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19516, + "scientific_name": "Rhinolophus euryale", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 19517, + "scientific_name": "Rhinolophus ferrumequinum", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19518, + "scientific_name": "Rhinolophus hipposideros", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19519, + "scientific_name": "Rhinolophus mehelyi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 22725105, + "scientific_name": "Rhodopechys sanguineus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 63485, + "scientific_name": "Rhus coriaria", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 164637, + "scientific_name": "Rhynchocalamus melanocephalus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 60037, + "scientific_name": "Rhyothemis semihyalina", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 103815961, + "scientific_name": "Riparia riparia", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19892296, + "scientific_name": "Robinia hispida", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19891648, + "scientific_name": "Robinia pseudoacacia", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 135555, + "scientific_name": "Romanogobio ciscaucasicus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19449302, + "scientific_name": "Romanogobio macropterus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 176580, + "scientific_name": "Rorippa austriaca", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 200430, + "scientific_name": "Rosa abutalybovii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 200429, + "scientific_name": "Rosa isaevii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 200438, + "scientific_name": "Rosa jaroshenkoi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 200427, + "scientific_name": "Rosa komarovii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19743, + "scientific_name": "Rosalia alpina", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 133733320, + "scientific_name": "Rosa rapinii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 200428, + "scientific_name": "Rosa zakatalensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 39255, + "scientific_name": "Rupicapra rupicapra", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19449330, + "scientific_name": "Rutilus atropatenus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 135601, + "scientific_name": "Rutilus caspicus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19782, + "scientific_name": "Rutilus frisii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19787, + "scientific_name": "Rutilus rutilus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19449338, + "scientific_name": "Rutilus sojuchbulagi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 19793, + "scientific_name": "Sabanejewia aurata", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 135553, + "scientific_name": "Sabanejewia caucasica", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19811, + "scientific_name": "Saga pedo", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 13584819, + "scientific_name": "Salix acmophylla", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 203465, + "scientific_name": "Salix alba", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19620273, + "scientific_name": "Salix caprea", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19620468, + "scientific_name": "Salix cinerea", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19620474, + "scientific_name": "Salix excelsa", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 79927560, + "scientific_name": "Salix pentandra", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 61960615, + "scientific_name": "Salix triandra", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 200448, + "scientific_name": "Salvia andreji", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 163996, + "scientific_name": "Salvinia natans", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 200449, + "scientific_name": "Sameraria glastifolia", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 163967, + "scientific_name": "Samolus valerandi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 20860, + "scientific_name": "Sander lucioperca", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 20861, + "scientific_name": "Sander marinus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22710156, + "scientific_name": "Saxicola rubetra", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22710184, + "scientific_name": "Saxicola torquatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19946, + "scientific_name": "Scardinius erythrophthalmus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 47783095, + "scientific_name": "Scarturus elater", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 136326, + "scientific_name": "Scarturus williamsi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 175246, + "scientific_name": "Schoenoplectus tabernaemontani", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 20000, + "scientific_name": "Sciurus anomalus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 20025, + "scientific_name": "Sciurus vulgaris", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 44393706, + "scientific_name": "Sclerochloa woronowii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22693052, + "scientific_name": "Scolopax rusticola", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 200630, + "scientific_name": "Scorzonera czerepanovii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 19210380, + "scientific_name": "Scutellaria galericulata", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 200468, + "scientific_name": "Scutellaria rhomboidalis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 165471, + "scientific_name": "Selysiothemis nigra", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22720045, + "scientific_name": "Serinus pusillus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 200501, + "scientific_name": "Seseli cuneifolium", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 20184, + "scientific_name": "Sicista betulina", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19108751, + "scientific_name": "Silene vulgaris", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 40713, + "scientific_name": "Silurus glanis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 103879804, + "scientific_name": "Sitta europaea", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22711205, + "scientific_name": "Sitta neumayer", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22711211, + "scientific_name": "Sitta tephronota", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 175289, + "scientific_name": "Sium sisarum", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19617543, + "scientific_name": "Sonchus palustris", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 61957558, + "scientific_name": "Sorbus aucuparia", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 79921482, + "scientific_name": "Sorbus graeca", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 97153711, + "scientific_name": "Sorbus roopiana", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 97151952, + "scientific_name": "Sorbus stankovii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 61957590, + "scientific_name": "Sorbus torminalis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 79925699, + "scientific_name": "Sorbus umbellata", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 29667, + "scientific_name": "Sorex minutus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 29668, + "scientific_name": "Sorex raddei", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41415, + "scientific_name": "Sorex satunini", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 29670, + "scientific_name": "Sorex volnuchini", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 167926, + "scientific_name": "Sparganium emersum", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22680247, + "scientific_name": "Spatula clypeata", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22680313, + "scientific_name": "Spatula querquedula", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 164054, + "scientific_name": "Spergularia media", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19891638, + "scientific_name": "Sphaerophysa salsula", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 157033, + "scientific_name": "Sphyradium doliolum", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22690445, + "scientific_name": "Spilopelia senegalensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22720354, + "scientific_name": "Spinus spinus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 61205, + "scientific_name": "Squalius cephalus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19452497, + "scientific_name": "Squalius turcicus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 200523, + "scientific_name": "Stachys fominii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 200524, + "scientific_name": "Stachys talyschensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 20745, + "scientific_name": "Stenodus leucichthys", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EW" + }, + { + "taxonid": 22694245, + "scientific_name": "Stercorarius parasiticus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22694240, + "scientific_name": "Stercorarius pomarinus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 200529, + "scientific_name": "Sterigmostemum acanthocarpum", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 22694623, + "scientific_name": "Sterna hirundo", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22694656, + "scientific_name": "Sternula albifrons", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 200531, + "scientific_name": "Stipa karjaginii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 22727811, + "scientific_name": "Streptopelia decaocto", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22690419, + "scientific_name": "Streptopelia turtur", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 22725469, + "scientific_name": "Strix aluco", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22710886, + "scientific_name": "Sturnus vulgaris", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 165491, + "scientific_name": "Stylurus ubadschii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 90389138, + "scientific_name": "Suncus etruscus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41775, + "scientific_name": "Sus scrofa", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19618320, + "scientific_name": "Swertia iberica", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22716901, + "scientific_name": "Sylvia atricapilla", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22716906, + "scientific_name": "Sylvia borin", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 158690, + "scientific_name": "Sympecma fusca", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 165459, + "scientific_name": "Sympecma paedisca", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 158718, + "scientific_name": "Sympetrum arenicolor", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 165512, + "scientific_name": "Sympetrum depressiusculum", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 165501, + "scientific_name": "Sympetrum flaveolum", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 60038, + "scientific_name": "Sympetrum fonscolombii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 165510, + "scientific_name": "Sympetrum meridionale", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 165464, + "scientific_name": "Sympetrum pedemontanum", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 158691, + "scientific_name": "Sympetrum sanguineum", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 158685, + "scientific_name": "Sympetrum striolatum", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 21257, + "scientific_name": "Syngnathus abaster", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22696545, + "scientific_name": "Tachybaptus ruficollis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22686774, + "scientific_name": "Tachymarptis melba", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 21311, + "scientific_name": "Tadarida teniotis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22680003, + "scientific_name": "Tadorna ferruginea", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22680024, + "scientific_name": "Tadorna tadorna", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41482, + "scientific_name": "Talpa levantis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 46102934, + "scientific_name": "Tamarix kotschyi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19620778, + "scientific_name": "Tamarix octandra", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 21251979, + "scientific_name": "Tamarix ramosissima", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 79928441, + "scientific_name": "Tamarix smyrnensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 118130328, + "scientific_name": "Tamarix tetragyna", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 200544, + "scientific_name": "Tanacetum zangezuricum", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 200556, + "scientific_name": "Taraxacum desertorum", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 42546, + "scientific_name": "Taxus baccata", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 157258, + "scientific_name": "Telescopus fallax", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 157255, + "scientific_name": "Tenuidactylus caspius", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 21646, + "scientific_name": "Testudo graeca", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 21651, + "scientific_name": "Testudo horsfieldii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 22678664, + "scientific_name": "Tetraogallus caspius", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22678661, + "scientific_name": "Tetraogallus caucasicus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22691896, + "scientific_name": "Tetrax tetrax", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 22694591, + "scientific_name": "Thalasseus sandvicensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 200558, + "scientific_name": "Thesium maritimum", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 22697510, + "scientific_name": "Threskiornis aethiopicus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 200564, + "scientific_name": "Thymus karamarianicus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 22711234, + "scientific_name": "Tichodroma muraria", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 203360, + "scientific_name": "Tilia cordata", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 79738009, + "scientific_name": "Tilia dasystyla", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 21912, + "scientific_name": "Tinca tinca", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 200577, + "scientific_name": "Tragopogon sosnowskyi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 199893, + "scientific_name": "Trifolium bobrovii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 19891967, + "scientific_name": "Trifolium canescens", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19892578, + "scientific_name": "Trifolium caucasicum", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 174713, + "scientific_name": "Trifolium pratense", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19379644, + "scientific_name": "Trifolium scabrum", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 176372, + "scientific_name": "Trifolium subterraneum", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 164152, + "scientific_name": "Triglochin palustris", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22693207, + "scientific_name": "Tringa erythropus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22693247, + "scientific_name": "Tringa glareola", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22693220, + "scientific_name": "Tringa nebularia", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22693243, + "scientific_name": "Tringa ochropus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22693216, + "scientific_name": "Tringa stagnatilis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22693211, + "scientific_name": "Tringa totanus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 172116, + "scientific_name": "Triticum monococcum", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 39420, + "scientific_name": "Triturus karelinii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 103883277, + "scientific_name": "Troglodytes troglodytes", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22736108, + "scientific_name": "Turdus atrogularis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22708819, + "scientific_name": "Turdus iliacus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 103888106, + "scientific_name": "Turdus merula", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22708822, + "scientific_name": "Turdus philomelos", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22708816, + "scientific_name": "Turdus pilaris", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 103892167, + "scientific_name": "Turdus ruficollis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22708768, + "scientific_name": "Turdus torquatus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22708829, + "scientific_name": "Turdus viscivorus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 189097, + "scientific_name": "Turricaspia dagestanica", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 189467, + "scientific_name": "Turricaspia pullula", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 189280, + "scientific_name": "Turricaspia sajenkovae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 189404, + "scientific_name": "Turricaspia spasskii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 155871, + "scientific_name": "Turricaspia triton", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 189519, + "scientific_name": "Turricaspia trivialis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 22486464, + "scientific_name": "Typha grossheimii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 61966807, + "scientific_name": "Ulmus glabra", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 19218731, + "scientific_name": "Ulmus minor", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 22736, + "scientific_name": "Unio crassus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "EN" + }, + { + "taxonid": 22682655, + "scientific_name": "Upupa epops", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 41688, + "scientific_name": "Ursus arctos", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 156186, + "scientific_name": "Valvata piscinalis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22694053, + "scientific_name": "Vanellus gregarius", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "CR" + }, + { + "taxonid": 22694064, + "scientific_name": "Vanellus leucurus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22693949, + "scientific_name": "Vanellus vanellus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 174225, + "scientific_name": "Vanessa atalanta", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 164036, + "scientific_name": "Veronica anagallis-aquatica", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 164530, + "scientific_name": "Veronica anagalloides", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 167923, + "scientific_name": "Veronica beccabunga", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 167924, + "scientific_name": "Veronica scutellata", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19620739, + "scientific_name": "Veronica serpyllifolia", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22947, + "scientific_name": "Vespertilio murinus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 176124, + "scientific_name": "Vicia abbreviata", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 176100, + "scientific_name": "Vicia balansae", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 176111, + "scientific_name": "Vicia bithynica", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 176099, + "scientific_name": "Vicia ciliatula", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 180099, + "scientific_name": "Vicia ervilia", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 176120, + "scientific_name": "Vicia grandiflora", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 176122, + "scientific_name": "Vicia hybrida", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 176125, + "scientific_name": "Vicia hyrcanica", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 176098, + "scientific_name": "Vicia johannis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 176113, + "scientific_name": "Vicia lathyroides", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 176104, + "scientific_name": "Vicia lutea", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 176119, + "scientific_name": "Vicia michauxii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 176095, + "scientific_name": "Vicia narbonensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 176110, + "scientific_name": "Vicia pannonica", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 176126, + "scientific_name": "Vicia peregrina", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 176097, + "scientific_name": "Vicia sativa", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 135133262, + "scientific_name": "Vicia sativa subsp. amphicarpa", + "subspecies": "amphicarpa", + "rank": "subsp.", + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 135133337, + "scientific_name": "Vicia sativa subsp. cordata", + "subspecies": "cordata", + "rank": "subsp.", + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 135133707, + "scientific_name": "Vicia sativa subsp. nigra", + "subspecies": "nigra", + "rank": "subsp.", + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 176091, + "scientific_name": "Vicia sepium", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 19379811, + "scientific_name": "Vicia tenuifolia", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22979, + "scientific_name": "Vimba vimba", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 23001, + "scientific_name": "Vipera dinniki", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 164679, + "scientific_name": "Vipera eriwanensis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 157268, + "scientific_name": "Vipera renardi", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 164708, + "scientific_name": "Vipera transcaucasiana", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "NT" + }, + { + "taxonid": 203350, + "scientific_name": "Vitex agnus-castus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 155517, + "scientific_name": "Viviparus viviparus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 29680, + "scientific_name": "Vormela peregusna", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 23062, + "scientific_name": "Vulpes vulpes", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22693251, + "scientific_name": "Xenus cinereus", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 157274, + "scientific_name": "Xerotyphlops vermicularis", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 157251, + "scientific_name": "Zamenis hohenackeri", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 164577, + "scientific_name": "Zamenis persica", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "DD" + }, + { + "taxonid": 22692663, + "scientific_name": "Zapornia parva", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 22692667, + "scientific_name": "Zapornia pusilla", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + }, + { + "taxonid": 31303, + "scientific_name": "Zelkova carpinifolia", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 39482, + "scientific_name": "Zerynthia caucasica", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "VU" + }, + { + "taxonid": 173361, + "scientific_name": "Zostera noltii", + "subspecies": null, + "rank": null, + "subpopulation": null, + "category": "LC" + } + ] + }, + "https://apiv3.iucnredlist.org/api/v3/country/list": { + "count": 251, + "results": [ + { + "isocode": "UZ", + "country": "Uzbekistan" + }, + { + "isocode": "QA", + "country": "Qatar" + }, + { + "isocode": "SA", + "country": "Saudi Arabia" + }, + { + "isocode": "AF", + "country": "Afghanistan" + }, + { + "isocode": "LB", + "country": "Lebanon" + }, + { + "isocode": "BH", + "country": "Bahrain" + }, + { + "isocode": "CY", + "country": "Cyprus" + }, + { + "isocode": "SY", + "country": "Syrian Arab Republic" + }, + { + "isocode": "TJ", + "country": "Tajikistan" + }, + { + "isocode": "TM", + "country": "Turkmenistan" + }, + { + "isocode": "AE", + "country": "United Arab Emirates" + }, + { + "isocode": "IR", + "country": "Iran, Islamic Republic of" + }, + { + "isocode": "IQ", + "country": "Iraq" + }, + { + "isocode": "OM", + "country": "Oman" + }, + { + "isocode": "PK", + "country": "Pakistan" + }, + { + "isocode": "IL", + "country": "Israel" + }, + { + "isocode": "JO", + "country": "Jordan" + }, + { + "isocode": "KZ", + "country": "Kazakhstan" + }, + { + "isocode": "KW", + "country": "Kuwait" + }, + { + "isocode": "KG", + "country": "Kyrgyzstan" + }, + { + "isocode": "VN", + "country": "Viet Nam" + }, + { + "isocode": "MV", + "country": "Maldives" + }, + { + "isocode": "PH", + "country": "Philippines" + }, + { + "isocode": "BD", + "country": "Bangladesh" + }, + { + "isocode": "BT", + "country": "Bhutan" + }, + { + "isocode": "BN", + "country": "Brunei Darussalam" + }, + { + "isocode": "KH", + "country": "Cambodia" + }, + { + "isocode": "TL", + "country": "Timor-Leste" + }, + { + "isocode": "SG", + "country": "Singapore" + }, + { + "isocode": "LK", + "country": "Sri Lanka" + }, + { + "isocode": "TH", + "country": "Thailand" + }, + { + "isocode": "NP", + "country": "Nepal" + }, + { + "isocode": "LA", + "country": "Lao People's Democratic Republic" + }, + { + "isocode": "YE", + "country": "Yemen" + }, + { + "isocode": "GE", + "country": "Georgia" + }, + { + "isocode": "TR", + "country": "Turkey" + }, + { + "isocode": "AM", + "country": "Armenia" + }, + { + "isocode": "AZ", + "country": "Azerbaijan" + }, + { + "isocode": "DT", + "country": "Disputed Territory" + }, + { + "isocode": "MY", + "country": "Malaysia" + }, + { + "isocode": "IO", + "country": "British Indian Ocean Territory" + }, + { + "isocode": "IN", + "country": "India" + }, + { + "isocode": "ID", + "country": "Indonesia" + }, + { + "isocode": "MM", + "country": "Myanmar" + }, + { + "isocode": "VU", + "country": "Vanuatu" + }, + { + "isocode": "WF", + "country": "Wallis and Futuna" + }, + { + "isocode": "GU", + "country": "Guam" + }, + { + "isocode": "MH", + "country": "Marshall Islands" + }, + { + "isocode": "PN", + "country": "Pitcairn" + }, + { + "isocode": "WS", + "country": "Samoa" + }, + { + "isocode": "CX", + "country": "Christmas Island" + }, + { + "isocode": "CC", + "country": "Cocos (Keeling) Islands" + }, + { + "isocode": "FJ", + "country": "Fiji" + }, + { + "isocode": "TK", + "country": "Tokelau" + }, + { + "isocode": "TO", + "country": "Tonga" + }, + { + "isocode": "TV", + "country": "Tuvalu" + }, + { + "isocode": "NR", + "country": "Nauru" + }, + { + "isocode": "NC", + "country": "New Caledonia" + }, + { + "isocode": "NU", + "country": "Niue" + }, + { + "isocode": "NF", + "country": "Norfolk Island" + }, + { + "isocode": "MP", + "country": "Northern Mariana Islands" + }, + { + "isocode": "PW", + "country": "Palau" + }, + { + "isocode": "VA", + "country": "Holy See (Vatican City State)" + }, + { + "isocode": "MT", + "country": "Malta" + }, + { + "isocode": "MC", + "country": "Monaco" + }, + { + "isocode": "PL", + "country": "Poland" + }, + { + "isocode": "RO", + "country": "Romania" + }, + { + "isocode": "SM", + "country": "San Marino" + }, + { + "isocode": "AL", + "country": "Albania" + }, + { + "isocode": "LV", + "country": "Latvia" + }, + { + "isocode": "LI", + "country": "Liechtenstein" + }, + { + "isocode": "LT", + "country": "Lithuania" + }, + { + "isocode": "LU", + "country": "Luxembourg" + }, + { + "isocode": "BE", + "country": "Belgium" + }, + { + "isocode": "BA", + "country": "Bosnia and Herzegovina" + }, + { + "isocode": "BG", + "country": "Bulgaria" + }, + { + "isocode": "HR", + "country": "Croatia" + }, + { + "isocode": "DK", + "country": "Denmark" + }, + { + "isocode": "EE", + "country": "Estonia" + }, + { + "isocode": "FO", + "country": "Faroe Islands" + }, + { + "isocode": "FI", + "country": "Finland" + }, + { + "isocode": "GG", + "country": "Guernsey" + }, + { + "isocode": "IM", + "country": "Isle of Man" + }, + { + "isocode": "JE", + "country": "Jersey" + }, + { + "isocode": "DE", + "country": "Germany" + }, + { + "isocode": "GI", + "country": "Gibraltar" + }, + { + "isocode": "GL", + "country": "Greenland" + }, + { + "isocode": "SK", + "country": "Slovakia" + }, + { + "isocode": "SI", + "country": "Slovenia" + }, + { + "isocode": "SJ", + "country": "Svalbard and Jan Mayen" + }, + { + "isocode": "SE", + "country": "Sweden" + }, + { + "isocode": "CH", + "country": "Switzerland" + }, + { + "isocode": "AD", + "country": "Andorra" + }, + { + "isocode": "AT", + "country": "Austria" + }, + { + "isocode": "HU", + "country": "Hungary" + }, + { + "isocode": "IS", + "country": "Iceland" + }, + { + "isocode": "NL", + "country": "Netherlands" + }, + { + "isocode": "NO", + "country": "Norway" + }, + { + "isocode": "IE", + "country": "Ireland" + }, + { + "isocode": "AX", + "country": "Åland Islands" + }, + { + "isocode": "ME", + "country": "Montenegro" + }, + { + "isocode": "GY", + "country": "Guyana" + }, + { + "isocode": "FK", + "country": "Falkland Islands (Malvinas)" + }, + { + "isocode": "GF", + "country": "French Guiana" + }, + { + "isocode": "SR", + "country": "Suriname" + }, + { + "isocode": "UY", + "country": "Uruguay" + }, + { + "isocode": "PY", + "country": "Paraguay" + }, + { + "isocode": "PE", + "country": "Peru" + }, + { + "isocode": "VG", + "country": "Virgin Islands, British" + }, + { + "isocode": "VI", + "country": "Virgin Islands, U.S." + }, + { + "isocode": "GP", + "country": "Guadeloupe" + }, + { + "isocode": "HT", + "country": "Haiti" + }, + { + "isocode": "MQ", + "country": "Martinique" + }, + { + "isocode": "MS", + "country": "Montserrat" + }, + { + "isocode": "KN", + "country": "Saint Kitts and Nevis" + }, + { + "isocode": "LC", + "country": "Saint Lucia" + }, + { + "isocode": "VC", + "country": "Saint Vincent and the Grenadines" + }, + { + "isocode": "BB", + "country": "Barbados" + }, + { + "isocode": "BM", + "country": "Bermuda" + }, + { + "isocode": "KY", + "country": "Cayman Islands" + }, + { + "isocode": "CU", + "country": "Cuba" + }, + { + "isocode": "DM", + "country": "Dominica" + }, + { + "isocode": "DO", + "country": "Dominican Republic" + }, + { + "isocode": "GD", + "country": "Grenada" + }, + { + "isocode": "TT", + "country": "Trinidad and Tobago" + }, + { + "isocode": "TC", + "country": "Turks and Caicos Islands" + }, + { + "isocode": "AI", + "country": "Anguilla" + }, + { + "isocode": "AG", + "country": "Antigua and Barbuda" + }, + { + "isocode": "AW", + "country": "Aruba" + }, + { + "isocode": "BS", + "country": "Bahamas" + }, + { + "isocode": "JM", + "country": "Jamaica" + }, + { + "isocode": "EH", + "country": "Western Sahara" + }, + { + "isocode": "DZ", + "country": "Algeria" + }, + { + "isocode": "TN", + "country": "Tunisia" + }, + { + "isocode": "MA", + "country": "Morocco" + }, + { + "isocode": "ZM", + "country": "Zambia" + }, + { + "isocode": "ZW", + "country": "Zimbabwe" + }, + { + "isocode": "GN", + "country": "Guinea" + }, + { + "isocode": "LY", + "country": "Libya" + }, + { + "isocode": "GW", + "country": "Guinea-Bissau" + }, + { + "isocode": "ML", + "country": "Mali" + }, + { + "isocode": "MR", + "country": "Mauritania" + }, + { + "isocode": "YT", + "country": "Mayotte" + }, + { + "isocode": "RW", + "country": "Rwanda" + }, + { + "isocode": "RE", + "country": "Réunion" + }, + { + "isocode": "SN", + "country": "Senegal" + }, + { + "isocode": "SL", + "country": "Sierra Leone" + }, + { + "isocode": "LS", + "country": "Lesotho" + }, + { + "isocode": "LR", + "country": "Liberia" + }, + { + "isocode": "MG", + "country": "Madagascar" + }, + { + "isocode": "MW", + "country": "Malawi" + }, + { + "isocode": "BJ", + "country": "Benin" + }, + { + "isocode": "BW", + "country": "Botswana" + }, + { + "isocode": "BF", + "country": "Burkina Faso" + }, + { + "isocode": "BI", + "country": "Burundi" + }, + { + "isocode": "CM", + "country": "Cameroon" + }, + { + "isocode": "CF", + "country": "Central African Republic" + }, + { + "isocode": "TD", + "country": "Chad" + }, + { + "isocode": "KM", + "country": "Comoros" + }, + { + "isocode": "CG", + "country": "Congo" + }, + { + "isocode": "CD", + "country": "Congo, The Democratic Republic of the" + }, + { + "isocode": "CI", + "country": "Côte d'Ivoire" + }, + { + "isocode": "DJ", + "country": "Djibouti" + }, + { + "isocode": "ER", + "country": "Eritrea" + }, + { + "isocode": "ET", + "country": "Ethiopia" + }, + { + "isocode": "GA", + "country": "Gabon" + }, + { + "isocode": "GM", + "country": "Gambia" + }, + { + "isocode": "GH", + "country": "Ghana" + }, + { + "isocode": "SO", + "country": "Somalia" + }, + { + "isocode": "SD", + "country": "Sudan" + }, + { + "isocode": "TZ", + "country": "Tanzania, United Republic of" + }, + { + "isocode": "TG", + "country": "Togo" + }, + { + "isocode": "UG", + "country": "Uganda" + }, + { + "isocode": "MZ", + "country": "Mozambique" + }, + { + "isocode": "NE", + "country": "Niger" + }, + { + "isocode": "NG", + "country": "Nigeria" + }, + { + "isocode": "KE", + "country": "Kenya" + }, + { + "isocode": "GT", + "country": "Guatemala" + }, + { + "isocode": "BZ", + "country": "Belize" + }, + { + "isocode": "SV", + "country": "El Salvador" + }, + { + "isocode": "PA", + "country": "Panama" + }, + { + "isocode": "HM", + "country": "Heard Island and McDonald Islands" + }, + { + "isocode": "BV", + "country": "Bouvet Island" + }, + { + "isocode": "AQ", + "country": "Antarctica" + }, + { + "isocode": "BY", + "country": "Belarus" + }, + { + "isocode": "MN", + "country": "Mongolia" + }, + { + "isocode": "MO", + "country": "Macao" + }, + { + "isocode": "HK", + "country": "Hong Kong" + }, + { + "isocode": "KP", + "country": "Korea, Democratic People's Republic of" + }, + { + "isocode": "KR", + "country": "Korea, Republic of" + }, + { + "isocode": "PM", + "country": "Saint Pierre and Miquelon" + }, + { + "isocode": "MF", + "country": "Saint Martin (French part)" + }, + { + "isocode": "BL", + "country": "Saint Barthélemy" + }, + { + "isocode": "AS", + "country": "American Samoa" + }, + { + "isocode": "CK", + "country": "Cook Islands" + }, + { + "isocode": "PF", + "country": "French Polynesia" + }, + { + "isocode": "SB", + "country": "Solomon Islands" + }, + { + "isocode": "UM", + "country": "United States Minor Outlying Islands" + }, + { + "isocode": "AU", + "country": "Australia" + }, + { + "isocode": "NZ", + "country": "New Zealand" + }, + { + "isocode": "PG", + "country": "Papua New Guinea" + }, + { + "isocode": "KI", + "country": "Kiribati" + }, + { + "isocode": "PT", + "country": "Portugal" + }, + { + "isocode": "FR", + "country": "France" + }, + { + "isocode": "GR", + "country": "Greece" + }, + { + "isocode": "ES", + "country": "Spain" + }, + { + "isocode": "IT", + "country": "Italy" + }, + { + "isocode": "RS", + "country": "Serbia" + }, + { + "isocode": "BR", + "country": "Brazil" + }, + { + "isocode": "CL", + "country": "Chile" + }, + { + "isocode": "CO", + "country": "Colombia" + }, + { + "isocode": "EC", + "country": "Ecuador" + }, + { + "isocode": "AR", + "country": "Argentina" + }, + { + "isocode": "PR", + "country": "Puerto Rico" + }, + { + "isocode": "AN", + "country": "Netherlands Antilles" + }, + { + "isocode": "EG", + "country": "Egypt" + }, + { + "isocode": "MU", + "country": "Mauritius" + }, + { + "isocode": "SC", + "country": "Seychelles" + }, + { + "isocode": "GQ", + "country": "Equatorial Guinea" + }, + { + "isocode": "ZA", + "country": "South Africa" + }, + { + "isocode": "AO", + "country": "Angola" + }, + { + "isocode": "NA", + "country": "Namibia" + }, + { + "isocode": "MX", + "country": "Mexico" + }, + { + "isocode": "CR", + "country": "Costa Rica" + }, + { + "isocode": "HN", + "country": "Honduras" + }, + { + "isocode": "NI", + "country": "Nicaragua" + }, + { + "isocode": "GS", + "country": "South Georgia and the South Sandwich Islands" + }, + { + "isocode": "RU", + "country": "Russian Federation" + }, + { + "isocode": "UA", + "country": "Ukraine" + }, + { + "isocode": "CN", + "country": "China" + }, + { + "isocode": "TW", + "country": "Taiwan, Province of China" + }, + { + "isocode": "JP", + "country": "Japan" + }, + { + "isocode": "CA", + "country": "Canada" + }, + { + "isocode": "SH", + "country": "Saint Helena, Ascension and Tristan da Cunha" + }, + { + "isocode": "CW", + "country": "Curaçao" + }, + { + "isocode": "SX", + "country": "Sint Maarten (Dutch part)" + }, + { + "isocode": "BQ", + "country": "Bonaire, Sint Eustatius and Saba" + }, + { + "isocode": "SS", + "country": "South Sudan" + }, + { + "isocode": "TF", + "country": "French Southern Territories" + }, + { + "isocode": "FM", + "country": "Micronesia, Federated States of " + }, + { + "isocode": "VE", + "country": "Venezuela, Bolivarian Republic of" + }, + { + "isocode": "PS", + "country": "Palestine, State of" + }, + { + "isocode": "CZ", + "country": "Czechia" + }, + { + "isocode": "CV", + "country": "Cabo Verde" + }, + { + "isocode": "SZ", + "country": "Eswatini" + }, + { + "isocode": "ST", + "country": "Sao Tome and Principe" + }, + { + "isocode": "MK", + "country": "North Macedonia" + }, + { + "isocode": "BO", + "country": "Bolivia, Plurinational State of" + }, + { + "isocode": "MD", + "country": "Moldova, Republic of" + }, + { + "isocode": "GB", + "country": "United Kingdom of Great Britain and Northern Ireland" + }, + { + "isocode": "US", + "country": "United States of America" + } + ] + }, + "https://apiv3.iucnredlist.org/api/v3/growth_forms/species/id/19891625": { + "id": "19891625", + "result": [ + { + "name": "Annual" + }, + { + "name": "Forb or Herb" + }, + { + "name": "Shrub - size unknown" + }, + { + "name": "Vines" + } + ] + }, + "https://apiv3.iucnredlist.org/api/v3/growth_forms/species/id/63532/region/europe": { + "id": "63532", + "region_identifier": "europe", + "result": [ + { + "name": "Tree - large" + } + ] + }, + "https://apiv3.iucnredlist.org/api/v3/growth_forms/species/name/Mucuna%20bracteata": { + "name": "Mucuna bracteata", + "result": [ + { + "name": "Annual" + }, + { + "name": "Forb or Herb" + }, + { + "name": "Shrub - size unknown" + }, + { + "name": "Vines" + } + ] + }, + "https://apiv3.iucnredlist.org/api/v3/growth_forms/species/name/Quercus%20robur/region/europe": { + "name": "Quercus robur", + "region_identifier": "europe", + "result": [ + { + "name": "Tree - large" + } + ] + }, + "https://apiv3.iucnredlist.org/api/v3/habitats/species/id/181008073": { + "id": "181008073", + "result": [ + { + "code": "1.5", + "habitat": "Forest - Subtropical/Tropical Dry", + "suitability": "Suitable", + "season": null, + "majorimportance": "Yes" + }, + { + "code": "1.6", + "habitat": "Forest - Subtropical/Tropical Moist Lowland", + "suitability": "Suitable", + "season": null, + "majorimportance": "No" + }, + { + "code": "1.7", + "habitat": "Forest - Subtropical/Tropical Mangrove Vegetation Above High Tide Level", + "suitability": "Suitable", + "season": null, + "majorimportance": "No" + }, + { + "code": "1.8", + "habitat": "Forest - Subtropical/Tropical Swamp", + "suitability": "Suitable", + "season": null, + "majorimportance": "No" + }, + { + "code": "1.9", + "habitat": "Forest - Subtropical/Tropical Moist Montane", + "suitability": "Suitable", + "season": null, + "majorimportance": "No" + }, + { + "code": "2.1", + "habitat": "Savanna - Dry", + "suitability": "Suitable", + "season": null, + "majorimportance": "Yes" + }, + { + "code": "2.2", + "habitat": "Savanna - Moist", + "suitability": "Suitable", + "season": null, + "majorimportance": "Yes" + }, + { + "code": "3.5", + "habitat": "Shrubland - Subtropical/Tropical Dry", + "suitability": "Suitable", + "season": null, + "majorimportance": "Yes" + }, + { + "code": "3.6", + "habitat": "Shrubland - Subtropical/Tropical Moist", + "suitability": "Suitable", + "season": null, + "majorimportance": "Yes" + }, + { + "code": "4.5", + "habitat": "Grassland - Subtropical/Tropical Dry", + "suitability": "Suitable", + "season": null, + "majorimportance": "Yes" + }, + { + "code": "4.6", + "habitat": "Grassland - Subtropical/Tropical Seasonally Wet/Flooded", + "suitability": "Suitable", + "season": null, + "majorimportance": "Yes" + }, + { + "code": "5.1", + "habitat": "Wetlands (inland) - Permanent Rivers/Streams/Creeks (includes waterfalls)", + "suitability": "Suitable", + "season": null, + "majorimportance": "Yes" + }, + { + "code": "5.13", + "habitat": "Wetlands (inland) - Permanent Inland Deltas", + "suitability": "Suitable", + "season": null, + "majorimportance": "No" + }, + { + "code": "5.2", + "habitat": "Wetlands (inland) - Seasonal/Intermittent/Irregular Rivers/Streams/Creeks", + "suitability": "Suitable", + "season": null, + "majorimportance": "Yes" + }, + { + "code": "5.3", + "habitat": "Wetlands (inland) - Shrub Dominated Wetlands", + "suitability": "Marginal", + "season": null, + "majorimportance": null + }, + { + "code": "8.1", + "habitat": "Desert - Hot", + "suitability": "Suitable", + "season": null, + "majorimportance": "Yes" + }, + { + "code": "8.3", + "habitat": "Desert - Cold", + "suitability": "Suitable", + "season": null, + "majorimportance": "Yes" + } + ] + }, + "https://apiv3.iucnredlist.org/api/v3/habitats/species/id/22823/region/europe": { + "id": "22823", + "region_identifier": "europe", + "result": [ + { + "code": "12.1", + "habitat": "Marine Intertidal - Rocky Shoreline", + "suitability": "Suitable", + "season": null, + "majorimportance": null + }, + { + "code": "12.2", + "habitat": "Marine Intertidal - Sandy Shoreline and/or Beaches, Sand Bars, Spits, Etc", + "suitability": "Suitable", + "season": null, + "majorimportance": null + }, + { + "code": "12.3", + "habitat": "Marine Intertidal - Shingle and/or Pebble Shoreline and/or Beaches", + "suitability": "Suitable", + "season": null, + "majorimportance": null + }, + { + "code": "3.1", + "habitat": "Shrubland - Subarctic", + "suitability": "Marginal", + "season": null, + "majorimportance": null + }, + { + "code": "9.1", + "habitat": "Marine Neritic - Pelagic", + "suitability": "Suitable", + "season": null, + "majorimportance": null + }, + { + "code": "9.10", + "habitat": "Marine Neritic - Estuaries", + "suitability": "Suitable", + "season": null, + "majorimportance": null + } + ] + }, + "https://apiv3.iucnredlist.org/api/v3/habitats/species/name/Loxodonta%20africana": { + "name": "Loxodonta africana", + "result": [ + { + "code": "1.5", + "habitat": "Forest - Subtropical/Tropical Dry", + "suitability": "Suitable", + "season": null, + "majorimportance": "Yes" + }, + { + "code": "1.6", + "habitat": "Forest - Subtropical/Tropical Moist Lowland", + "suitability": "Suitable", + "season": null, + "majorimportance": "No" + }, + { + "code": "1.7", + "habitat": "Forest - Subtropical/Tropical Mangrove Vegetation Above High Tide Level", + "suitability": "Suitable", + "season": null, + "majorimportance": "No" + }, + { + "code": "1.8", + "habitat": "Forest - Subtropical/Tropical Swamp", + "suitability": "Suitable", + "season": null, + "majorimportance": "No" + }, + { + "code": "1.9", + "habitat": "Forest - Subtropical/Tropical Moist Montane", + "suitability": "Suitable", + "season": null, + "majorimportance": "No" + }, + { + "code": "2.1", + "habitat": "Savanna - Dry", + "suitability": "Suitable", + "season": null, + "majorimportance": "Yes" + }, + { + "code": "2.2", + "habitat": "Savanna - Moist", + "suitability": "Suitable", + "season": null, + "majorimportance": "Yes" + }, + { + "code": "3.5", + "habitat": "Shrubland - Subtropical/Tropical Dry", + "suitability": "Suitable", + "season": null, + "majorimportance": "Yes" + }, + { + "code": "3.6", + "habitat": "Shrubland - Subtropical/Tropical Moist", + "suitability": "Suitable", + "season": null, + "majorimportance": "Yes" + }, + { + "code": "4.5", + "habitat": "Grassland - Subtropical/Tropical Dry", + "suitability": "Suitable", + "season": null, + "majorimportance": "Yes" + }, + { + "code": "4.6", + "habitat": "Grassland - Subtropical/Tropical Seasonally Wet/Flooded", + "suitability": "Suitable", + "season": null, + "majorimportance": "Yes" + }, + { + "code": "5.1", + "habitat": "Wetlands (inland) - Permanent Rivers/Streams/Creeks (includes waterfalls)", + "suitability": "Suitable", + "season": null, + "majorimportance": "Yes" + }, + { + "code": "5.13", + "habitat": "Wetlands (inland) - Permanent Inland Deltas", + "suitability": "Suitable", + "season": null, + "majorimportance": "No" + }, + { + "code": "5.2", + "habitat": "Wetlands (inland) - Seasonal/Intermittent/Irregular Rivers/Streams/Creeks", + "suitability": "Suitable", + "season": null, + "majorimportance": "Yes" + }, + { + "code": "5.3", + "habitat": "Wetlands (inland) - Shrub Dominated Wetlands", + "suitability": "Marginal", + "season": null, + "majorimportance": null + }, + { + "code": "8.1", + "habitat": "Desert - Hot", + "suitability": "Suitable", + "season": null, + "majorimportance": "Yes" + }, + { + "code": "8.3", + "habitat": "Desert - Cold", + "suitability": "Suitable", + "season": null, + "majorimportance": "Yes" + } + ] + }, + "https://apiv3.iucnredlist.org/api/v3/habitats/species/name/Ursus%20maritimus/region/europe": { + "name": "Ursus maritimus", + "region_identifier": "europe", + "result": [ + { + "code": "12.1", + "habitat": "Marine Intertidal - Rocky Shoreline", + "suitability": "Suitable", + "season": null, + "majorimportance": null + }, + { + "code": "12.2", + "habitat": "Marine Intertidal - Sandy Shoreline and/or Beaches, Sand Bars, Spits, Etc", + "suitability": "Suitable", + "season": null, + "majorimportance": null + }, + { + "code": "12.3", + "habitat": "Marine Intertidal - Shingle and/or Pebble Shoreline and/or Beaches", + "suitability": "Suitable", + "season": null, + "majorimportance": null + }, + { + "code": "3.1", + "habitat": "Shrubland - Subarctic", + "suitability": "Marginal", + "season": null, + "majorimportance": null + }, + { + "code": "9.1", + "habitat": "Marine Neritic - Pelagic", + "suitability": "Suitable", + "season": null, + "majorimportance": null + }, + { + "code": "9.10", + "habitat": "Marine Neritic - Estuaries", + "suitability": "Suitable", + "season": null, + "majorimportance": null + } + ] + }, + "https://apiv3.iucnredlist.org/api/v3/measures/species/id/181008073": { + "id": "181008073", + "result": [ + { + "code": "1.1", + "title": "Site/area protection" + }, + { + "code": "1.2", + "title": "Resource & habitat protection" + }, + { + "code": "2.1", + "title": "Site/area management" + }, + { + "code": "3.1", + "title": "Species management" + }, + { + "code": "3.1.1", + "title": "Harvest management" + }, + { + "code": "3.1.2", + "title": "Trade management" + }, + { + "code": "3.1.3", + "title": "Limiting population growth" + }, + { + "code": "3.2", + "title": "Species recovery" + }, + { + "code": "4.1", + "title": "Formal education" + }, + { + "code": "4.2", + "title": "Training" + }, + { + "code": "4.3", + "title": "Awareness & communications" + }, + { + "code": "5.1", + "title": "Legislation" + }, + { + "code": "5.1.1", + "title": "International level" + }, + { + "code": "5.1.2", + "title": "National level" + }, + { + "code": "5.1.3", + "title": "Sub-national level" + }, + { + "code": "5.4", + "title": "Compliance and enforcement" + }, + { + "code": "5.4.1", + "title": "International level" + }, + { + "code": "5.4.2", + "title": "National level" + }, + { + "code": "5.4.3", + "title": "Sub-national level" + }, + { + "code": "6.1", + "title": "Linked enterprises & livelihood alternatives" + } + ] + }, + "https://apiv3.iucnredlist.org/api/v3/measures/species/id/22823/region/europe": { + "id": "22823", + "region_identifier": "europe", + "result": [ + { + "code": "1.1", + "title": "Site/area protection" + }, + { + "code": "1.2", + "title": "Resource & habitat protection" + }, + { + "code": "2.1", + "title": "Site/area management" + } + ] + }, + "https://apiv3.iucnredlist.org/api/v3/measures/species/name/Loxodonta%20africana": { + "name": "Loxodonta africana", + "result": [ + { + "code": "1.1", + "title": "Site/area protection" + }, + { + "code": "1.2", + "title": "Resource & habitat protection" + }, + { + "code": "2.1", + "title": "Site/area management" + }, + { + "code": "3.1", + "title": "Species management" + }, + { + "code": "3.1.1", + "title": "Harvest management" + }, + { + "code": "3.1.2", + "title": "Trade management" + }, + { + "code": "3.1.3", + "title": "Limiting population growth" + }, + { + "code": "3.2", + "title": "Species recovery" + }, + { + "code": "4.1", + "title": "Formal education" + }, + { + "code": "4.2", + "title": "Training" + }, + { + "code": "4.3", + "title": "Awareness & communications" + }, + { + "code": "5.1", + "title": "Legislation" + }, + { + "code": "5.1.1", + "title": "International level" + }, + { + "code": "5.1.2", + "title": "National level" + }, + { + "code": "5.1.3", + "title": "Sub-national level" + }, + { + "code": "5.4", + "title": "Compliance and enforcement" + }, + { + "code": "5.4.1", + "title": "International level" + }, + { + "code": "5.4.2", + "title": "National level" + }, + { + "code": "5.4.3", + "title": "Sub-national level" + }, + { + "code": "6.1", + "title": "Linked enterprises & livelihood alternatives" + } + ] + }, + "https://apiv3.iucnredlist.org/api/v3/measures/species/name/Ursus%20maritimus/region/europe": { + "name": "Ursus maritimus", + "region_identifier": "europe", + "result": [ + { + "code": "1.1", + "title": "Site/area protection" + }, + { + "code": "1.2", + "title": "Resource & habitat protection" + }, + { + "code": "2.1", + "title": "Site/area management" + } + ] + }, + "https://apiv3.iucnredlist.org/api/v3/region/list": { + "count": 10, + "results": [ + { + "name": "Northeastern Africa", + "identifier": "northeastern_africa" + }, + { + "name": "Eastern Africa", + "identifier": "eastern_africa" + }, + { + "name": "Global", + "identifier": "global" + }, + { + "name": "Western Africa", + "identifier": "western_africa" + }, + { + "name": "Northern Africa", + "identifier": "northern_africa" + }, + { + "name": "Central Africa", + "identifier": "central_africa" + }, + { + "name": "Pan-Africa", + "identifier": "pan-africa" + }, + { + "name": "Southern Africa", + "identifier": "southern_africa" + }, + { + "name": "Mediterranean", + "identifier": "mediterranean" + }, + { + "name": "Europe", + "identifier": "europe" + } + ] + }, + "https://apiv3.iucnredlist.org/api/v3/species/Loxodonta%20africana": { + "name": "loxodonta africana", + "result": [ + { + "taxonid": 181008073, + "scientific_name": "Loxodonta africana", + "kingdom": "ANIMALIA", + "phylum": "CHORDATA", + "class": "MAMMALIA", + "order": "PROBOSCIDEA", + "family": "ELEPHANTIDAE", + "genus": "Loxodonta", + "main_common_name": "African Savanna Elephant", + "authority": "(Blumenbach, 1797)", + "published_year": 2021, + "assessment_date": "2020-11-13", + "category": "EN", + "criteria": "A2abd", + "population_trend": "Decreasing", + "marine_system": false, + "freshwater_system": false, + "terrestrial_system": true, + "assessor": "Gobush, K.S., Edwards, C.T.T, Balfour, D., Wittemyer, G., Maisels, F. & Taylor, R.D.", + "reviewer": "Selier, J. & Sibanda, N.", + "aoo_km2": null, + "eoo_km2": null, + "elevation_upper": 2500, + "elevation_lower": 0, + "depth_upper": null, + "depth_lower": null, + "errata_flag": null, + "errata_reason": null, + "amended_flag": true, + "amended_reason": "This amended version has been created because Burkina Faso has been added to the list of Countries of Occurrence and the supporting reference for the occurrence added to the Bibliography. Also a reference cited in the Trade and Use section of the assessment has been deleted and removed from the Bibliography. In addition an errata version of the Supplementary Material has been attached; an error in Figure 1 has been corrected and some additional text about the data in Tables 2 and 3 has been added." + } + ] + }, + "https://apiv3.iucnredlist.org/api/v3/species/Fratercula%20arctica/region/europe": { + "name": "Fratercula arctica", + "region_identifier": "europe", + "result": [ + { + "taxonid": 22694927, + "scientific_name": "Fratercula arctica", + "kingdom": "ANIMALIA", + "phylum": "CHORDATA", + "class": "AVES", + "order": "CHARADRIIFORMES", + "family": "ALCIDAE", + "genus": "Fratercula", + "main_common_name": "Atlantic Puffin", + "authority": "(Linnaeus, 1758)", + "published_year": 2021, + "assessment_date": "2020-12-18", + "category": "EN", + "criteria": "A2abcde+4abcde", + "population_trend": "Decreasing", + "marine_system": true, + "freshwater_system": false, + "terrestrial_system": true, + "assessor": "BirdLife International", + "reviewer": "Burfield, I. & Martin, R.", + "aoo_km2": null, + "eoo_km2": null, + "elevation_upper": null, + "elevation_lower": null, + "depth_upper": null, + "depth_lower": null, + "errata_flag": null, + "errata_reason": null, + "amended_flag": null, + "amended_reason": null + } + ] + }, + "https://apiv3.iucnredlist.org/api/v3/species/id/181008073": { + "name": "181008073", + "result": [ + { + "taxonid": 181008073, + "scientific_name": "Loxodonta africana", + "kingdom": "ANIMALIA", + "phylum": "CHORDATA", + "class": "MAMMALIA", + "order": "PROBOSCIDEA", + "family": "ELEPHANTIDAE", + "genus": "Loxodonta", + "main_common_name": "African Savanna Elephant", + "authority": "(Blumenbach, 1797)", + "published_year": 2021, + "assessment_date": "2020-11-13", + "category": "EN", + "criteria": "A2abd", + "population_trend": "Decreasing", + "marine_system": false, + "freshwater_system": false, + "terrestrial_system": true, + "assessor": "Gobush, K.S., Edwards, C.T.T, Balfour, D., Wittemyer, G., Maisels, F. & Taylor, R.D.", + "reviewer": "Selier, J. & Sibanda, N.", + "aoo_km2": null, + "eoo_km2": null, + "elevation_upper": 2500, + "elevation_lower": 0, + "depth_upper": null, + "depth_lower": null, + "errata_flag": null, + "errata_reason": null, + "amended_flag": true, + "amended_reason": "This amended version has been created because Burkina Faso has been added to the list of Countries of Occurrence and the supporting reference for the occurrence added to the Bibliography. Also a reference cited in the Trade and Use section of the assessment has been deleted and removed from the Bibliography. In addition an errata version of the Supplementary Material has been attached; an error in Figure 1 has been corrected and some additional text about the data in Tables 2 and 3 has been added." + } + ] + }, + "https://apiv3.iucnredlist.org/api/v3/species/id/22694927/region/europe": { + "name": "22694927", + "region_identifier": "europe", + "result": [ + { + "taxonid": 22694927, + "scientific_name": "Fratercula arctica", + "kingdom": "ANIMALIA", + "phylum": "CHORDATA", + "class": "AVES", + "order": "CHARADRIIFORMES", + "family": "ALCIDAE", + "genus": "Fratercula", + "main_common_name": "Atlantic Puffin", + "authority": "(Linnaeus, 1758)", + "published_year": 2021, + "assessment_date": "2020-12-18", + "category": "EN", + "criteria": "A2abcde+4abcde", + "population_trend": "Decreasing", + "marine_system": true, + "freshwater_system": false, + "terrestrial_system": true, + "assessor": "BirdLife International", + "reviewer": "Burfield, I. & Martin, R.", + "aoo_km2": null, + "eoo_km2": null, + "elevation_upper": null, + "elevation_lower": null, + "depth_upper": null, + "depth_lower": null, + "errata_flag": null, + "errata_reason": null, + "amended_flag": null, + "amended_reason": null + } + ] + }, + "https://apiv3.iucnredlist.org/api/v3/species/category/LRlc": { + "count": 512, + "category": "LR/lc", + "result": [ + { + "taxonid": 36549, + "scientific_name": "Abarema commutata", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 34283, + "scientific_name": "Actinodaphne malaccensis", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 36797, + "scientific_name": "Aegiphila cordifolia", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 36799, + "scientific_name": "Aegiphila sordida", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 37432, + "scientific_name": "Agarista mexicana var. pinetorum", + "subspecies": "pinetorum", + "rank": "var.", + "subpopulation": null + }, + { + "taxonid": 36801, + "scientific_name": "Ageratina macbridei", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 30537, + "scientific_name": "Aglaia argentea", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 33711, + "scientific_name": "Aglaia elaeagnoidea", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 33726, + "scientific_name": "Aglaia elliptica", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 34362, + "scientific_name": "Aglaia lawii", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 33712, + "scientific_name": "Aglaia odoratissima", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 34919, + "scientific_name": "Aglaia sapindina", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 34765, + "scientific_name": "Aglaia tomentosa", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 33285, + "scientific_name": "Ailanthus integrifolia", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 35204, + "scientific_name": "Aiouea acarodomatifera", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 38183, + "scientific_name": "Aiphanes ulei", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 34502, + "scientific_name": "Alangium griffithi", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 33707, + "scientific_name": "Alangium javanicum", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 34503, + "scientific_name": "Alangium nobile", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 36592, + "scientific_name": "Albizia decandra", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 36586, + "scientific_name": "Albizia niopoides var. colombiana", + "subspecies": "colombiana", + "rank": "var.", + "subpopulation": null + }, + { + "taxonid": 36589, + "scientific_name": "Albizia pistaciifolia", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 34299, + "scientific_name": "Alectryon excelsus var. grandis", + "subspecies": "grandis", + "rank": "var.", + "subpopulation": null + }, + { + "taxonid": 37785, + "scientific_name": "Allantospermum borneense var. borneense", + "subspecies": "borneense", + "rank": "var.", + "subpopulation": null + }, + { + "taxonid": 34277, + "scientific_name": "Allophylus rhomboidalis", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 35888, + "scientific_name": "Alniphyllum eberhardtii", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 34891, + "scientific_name": "Alnus glutinosa subsp. betuloides", + "subspecies": "betuloides", + "rank": "subsp.", + "subpopulation": null + }, + { + "taxonid": 30473, + "scientific_name": "Alphitonia marquesensis", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 36337, + "scientific_name": "Alseodaphne ridleyi", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 33175, + "scientific_name": "Alstonia angustifolia", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 33194, + "scientific_name": "Alstonia macrophylla", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 33231, + "scientific_name": "Alstonia pneumatophora", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 31985, + "scientific_name": "Alstonia rupestris", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 33900, + "scientific_name": "Anadenanthera colubrina var. cebil", + "subspecies": "cebil", + "rank": "var.", + "subpopulation": null + }, + { + "taxonid": 33225, + "scientific_name": "Angelesia splendens", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 32937, + "scientific_name": "Anisophyllea beccariana", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 31772, + "scientific_name": "Anisophyllea corneri", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 32938, + "scientific_name": "Anisophyllea disticha", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 31279, + "scientific_name": "Aralia leschenaultii", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 35230, + "scientific_name": "Ardisia pulverulenta", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 38422, + "scientific_name": "Ardisia standleyana", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 2218, + "scientific_name": "Astraea heliotropium", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 38755, + "scientific_name": "Astrocaryum aculeatissimum", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 35103, + "scientific_name": "Astronidium fraternum", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 35104, + "scientific_name": "Astronidium glabrum", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 35105, + "scientific_name": "Astronidium ligulatum", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 34378, + "scientific_name": "Astronidium robustum", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 35107, + "scientific_name": "Astronidium saccatum", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 34374, + "scientific_name": "Astronidium victoriae", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 38757, + "scientific_name": "Attalea oleifera", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 33702, + "scientific_name": "Azima sarmentosa", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 39000, + "scientific_name": "Bactris constanciae", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 31537, + "scientific_name": "Beilschmiedia pahangensis", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 33933, + "scientific_name": "Bhesa paniculata", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 33934, + "scientific_name": "Bhesa robusta", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 34641, + "scientific_name": "Biancaea sappan", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 33590, + "scientific_name": "Brackenridgea hookeri", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 3041, + "scientific_name": "Branchinecta gigas", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 37814, + "scientific_name": "Brosimum alicastrum subsp. bolivarense", + "subspecies": "bolivarense", + "rank": "subsp.", + "subpopulation": null + }, + { + "taxonid": 37648, + "scientific_name": "Calophyllum neoebudicum", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 37667, + "scientific_name": "Calophyllum pisiferum", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 33180, + "scientific_name": "Calophyllum soulattri", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 32285, + "scientific_name": "Calophyllum tetrapterum", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 33233, + "scientific_name": "Canarium asperum", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 33182, + "scientific_name": "Canarium littorale", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 32128, + "scientific_name": "Canarium patentinervium", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 33223, + "scientific_name": "Canarium pilosum subsp. pilosum", + "subspecies": "pilosum", + "rank": "subsp.", + "subpopulation": null + }, + { + "taxonid": 38466, + "scientific_name": "Caryota no", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 32149, + "scientific_name": "Cassia afrofistula var. patentipila", + "subspecies": "patentipila", + "rank": "var.", + "subpopulation": null + }, + { + "taxonid": 33501, + "scientific_name": "Cassine viburnifolia", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 34617, + "scientific_name": "Catha edulis", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 34749, + "scientific_name": "Cecropia obtusifolia", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 44066, + "scientific_name": "Ceropegia decidua", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 35047, + "scientific_name": "Cheirodendron bastardianum", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 46580, + "scientific_name": "Chelodina novaeguineae", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 36532, + "scientific_name": "Chionanthus pubescens", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 34287, + "scientific_name": "Chisocheton tomentosus", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 35369, + "scientific_name": "Chromolucuma rubriflora", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 35068, + "scientific_name": "Claoxylon collenettei", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 38482, + "scientific_name": "Coccothrinax gundlachii", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 5047, + "scientific_name": "Cochlicopa nitens", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 39239, + "scientific_name": "Cololejeunea azorica", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 34413, + "scientific_name": "Comarostaphylis discolor var. discolor", + "subspecies": "discolor", + "rank": "var.", + "subpopulation": null + }, + { + "taxonid": 38486, + "scientific_name": "Copernicia baileyana", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 38490, + "scientific_name": "Copernicia rigida", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 38955, + "scientific_name": "Coprosma nephelephila", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 38956, + "scientific_name": "Coprosma orohenensis", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 38957, + "scientific_name": "Coprosma reticulata", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 33042, + "scientific_name": "Cordia millenii", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 5362, + "scientific_name": "Coregonus artedi", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 34895, + "scientific_name": "Cotoneaster morulus", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 34896, + "scientific_name": "Cotoneaster transcaucasicus", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 33969, + "scientific_name": "Couroupita guianensis", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 32817, + "scientific_name": "Craibiodendron stellatum", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 33200, + "scientific_name": "Cratoxylum arborescens", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 33936, + "scientific_name": "Cratoxylum cochinchinense", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 33354, + "scientific_name": "Cratoxylum formosum", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 33937, + "scientific_name": "Cratoxylum maingayi", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 5836, + "scientific_name": "Ctenophila salaziensis", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 33350, + "scientific_name": "Cubilia cubili", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 31757, + "scientific_name": "Cyathocalyx sumatranus", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 35943, + "scientific_name": "Cyathostemma vietnamense", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 6066, + "scientific_name": "Cylindrovertilla kingi", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 34751, + "scientific_name": "Cynometra insularis", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 35010, + "scientific_name": "Cynometra minor", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 35011, + "scientific_name": "Cynometra vitiensis", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 31098, + "scientific_name": "Cyrtandra anthropophagorum", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 32846, + "scientific_name": "Dacryodes costata", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 32844, + "scientific_name": "Dacryodes laxa", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 33222, + "scientific_name": "Dacryodes rostrata", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 39714, + "scientific_name": "Darlingtonia californica", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 34985, + "scientific_name": "Decaspermum vitiense", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 31447, + "scientific_name": "Diospyros adenophora", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 34519, + "scientific_name": "Diospyros apiculata", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 36308, + "scientific_name": "Diospyros areolata", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 31448, + "scientific_name": "Diospyros argentea", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 31449, + "scientific_name": "Diospyros bibracteata", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 31455, + "scientific_name": "Diospyros foxworthyi", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 31457, + "scientific_name": "Diospyros ismailii", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 34520, + "scientific_name": "Diospyros latisepala", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 31827, + "scientific_name": "Diospyros nutans", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 31460, + "scientific_name": "Diospyros penangiana", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 37847, + "scientific_name": "Diospyros ridleyi", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 31461, + "scientific_name": "Diospyros rufa", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 37845, + "scientific_name": "Diospyros scortechinii", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 37841, + "scientific_name": "Diospyros singaporensis", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 36310, + "scientific_name": "Diospyros transitoria", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 31463, + "scientific_name": "Diospyros trengganuensis", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 31464, + "scientific_name": "Diospyros tristis", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 32338, + "scientific_name": "Dipentodon sinicus", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 33078, + "scientific_name": "Dipterocarpus oblongifolius", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 32823, + "scientific_name": "Disepalum petelotii", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 39010, + "scientific_name": "Dombeya rotundifolia", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 35949, + "scientific_name": "Drepananthus filiformis", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 31418, + "scientific_name": "Drepananthus pahangensis", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 31419, + "scientific_name": "Drepananthus pruniferus", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 33212, + "scientific_name": "Dyera costulata", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 35500, + "scientific_name": "Ecclinusa bullata", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 32158, + "scientific_name": "Elaeodendron fruticosum", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 46581, + "scientific_name": "Elseya novaeguineae", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 46582, + "scientific_name": "Emydura subglobosa ssp. subglobosa", + "subspecies": "subglobosa", + "rank": "ssp.", + "subpopulation": null + }, + { + "taxonid": 37156, + "scientific_name": "Endocomia macrocoma subsp. macrocoma", + "subspecies": "macrocoma", + "rank": "subsp.", + "subpopulation": null + }, + { + "taxonid": 33345, + "scientific_name": "Engelhardtia rigida", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 33710, + "scientific_name": "Engelhardtia serrata", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 32652, + "scientific_name": "Engelhardtia spicata", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 35950, + "scientific_name": "Enicosanthellum plagioneurum", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 33909, + "scientific_name": "Entandrophragma excelsum", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 30678, + "scientific_name": "Eschweilera pittieri", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 31563, + "scientific_name": "Eugenia anisosepala", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 31848, + "scientific_name": "Eugenia duthieana", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 31847, + "scientific_name": "Eugenia flosculifera", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 31846, + "scientific_name": "Eugenia glauca", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 31572, + "scientific_name": "Eugenia goodenovii", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 34291, + "scientific_name": "Eugenia gramae-andersonii", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 31573, + "scientific_name": "Eugenia inasensis", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 31574, + "scientific_name": "Eugenia jasminifolia", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 31576, + "scientific_name": "Eugenia kemamensis", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 31577, + "scientific_name": "Eugenia kiahii", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 31579, + "scientific_name": "Eugenia koordersiana", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 31580, + "scientific_name": "Eugenia laevicaulis", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 31844, + "scientific_name": "Eugenia nemestrina", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 31981, + "scientific_name": "Eugenia oblongifolia", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 31583, + "scientific_name": "Eugenia oreophila", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 31980, + "scientific_name": "Eugenia pauper", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 31979, + "scientific_name": "Eugenia pendens", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 31588, + "scientific_name": "Eugenia perakensis", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 31589, + "scientific_name": "Eugenia pergamentacea", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 31590, + "scientific_name": "Eugenia polita", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 31592, + "scientific_name": "Eugenia prainiana", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 31595, + "scientific_name": "Eugenia quadribracteata", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 34550, + "scientific_name": "Eugenia ridleyi", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 34551, + "scientific_name": "Eugenia scortechinii", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 31601, + "scientific_name": "Eugenia symingtoniana", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 31607, + "scientific_name": "Eugenia variolosa", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 31609, + "scientific_name": "Eugenia wrayi", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 33618, + "scientific_name": "Euonymus cochinchinensis", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 31269, + "scientific_name": "Euonymus grandiflorus", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 33706, + "scientific_name": "Euonymus javanicus", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 33040, + "scientific_name": "Euonymus latifolius subsp. cauconis", + "subspecies": "cauconis", + "rank": "subsp.", + "subpopulation": null + }, + { + "taxonid": 8331, + "scientific_name": "Euploea configurata", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 8333, + "scientific_name": "Euploea dentiplaga", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 8334, + "scientific_name": "Euploea doretta", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 8335, + "scientific_name": "Euploea eboraci", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 8336, + "scientific_name": "Euploea eupator", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 8340, + "scientific_name": "Euploea latifasciata", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 32403, + "scientific_name": "Euptelea pleiosperma", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 37942, + "scientific_name": "Eurya rapensis", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 34463, + "scientific_name": "Ficus albert-smithii", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 34464, + "scientific_name": "Ficus amazonica", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 34467, + "scientific_name": "Ficus broadwayi", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 34469, + "scientific_name": "Ficus castellviana", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 34470, + "scientific_name": "Ficus catappifolia", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 34474, + "scientific_name": "Ficus greiffiana", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 34475, + "scientific_name": "Ficus hebetifolia", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 34477, + "scientific_name": "Ficus krukovii", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 34479, + "scientific_name": "Ficus lauretana", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 34480, + "scientific_name": "Ficus malacocarpa", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 34482, + "scientific_name": "Ficus matiziana", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 34484, + "scientific_name": "Ficus monckii", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 34486, + "scientific_name": "Ficus pallida", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 34487, + "scientific_name": "Ficus panurensis", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 34491, + "scientific_name": "Ficus schippii", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 34492, + "scientific_name": "Ficus schultesii", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 34493, + "scientific_name": "Ficus schumacheri", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 34494, + "scientific_name": "Ficus sphenophylla", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 34495, + "scientific_name": "Ficus trigonata", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 34499, + "scientific_name": "Ficus velutina", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 37927, + "scientific_name": "Fitchia rapensis", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 1298, + "scientific_name": "Fluvidona simsoniana", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 31515, + "scientific_name": "Garcinia burkillii", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 31516, + "scientific_name": "Garcinia cantleyana", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 31520, + "scientific_name": "Garcinia hendersoniana", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 37830, + "scientific_name": "Garcinia maingayi", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 36326, + "scientific_name": "Garcinia minutiflora", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 31522, + "scientific_name": "Garcinia monantha", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 31524, + "scientific_name": "Garcinia murtonii", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 31525, + "scientific_name": "Garcinia opaca", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 36327, + "scientific_name": "Garcinia prainiana", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 31526, + "scientific_name": "Garcinia pyrifera", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 34529, + "scientific_name": "Garcinia scortechinii", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 31527, + "scientific_name": "Garcinia uniflora", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 34622, + "scientific_name": "Gardenia gummifera", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 31044, + "scientific_name": "Gardenia hutchinsoniana", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 34986, + "scientific_name": "Geissois ternata var. glabrior", + "subspecies": "glabrior", + "rank": "var.", + "subpopulation": null + }, + { + "taxonid": 31029, + "scientific_name": "Geissois ternata var. ternata", + "subspecies": "ternata", + "rank": "var.", + "subpopulation": null + }, + { + "taxonid": 31345, + "scientific_name": "Geniostoma confertiflorum", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 35100, + "scientific_name": "Geniostoma quadrangulare", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 35101, + "scientific_name": "Geniostoma rapense", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 31341, + "scientific_name": "Geniostoma uninervium", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 35537, + "scientific_name": "Gesneria exserta", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 35071, + "scientific_name": "Glochidion longfieldiae", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 30470, + "scientific_name": "Glochidion marchionicum", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 35073, + "scientific_name": "Glochidion moorei", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 35074, + "scientific_name": "Glochidion myrtifolium", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 35076, + "scientific_name": "Glochidion taitense", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 35077, + "scientific_name": "Glochidion temehaniense", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 31411, + "scientific_name": "Gluta capituliflora", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 38264, + "scientific_name": "Gluta curtisii", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 31412, + "scientific_name": "Gluta lanceolata", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 35954, + "scientific_name": "Goniothalamus chinensis", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 9389, + "scientific_name": "Gonospira funicula", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 9401, + "scientific_name": "Gopherus berlandieri", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 38734, + "scientific_name": "Guapira myrtiflora", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 9532, + "scientific_name": "Gulella plantii", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 35565, + "scientific_name": "Heisteria maguirei", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 9786, + "scientific_name": "Helicarion australis", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 33902, + "scientific_name": "Helicostylis tomentosa", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 29624, + "scientific_name": "Hemicycla glyceia ssp. glyceia", + "subspecies": "glyceia", + "rank": "ssp.", + "subpopulation": null + }, + { + "taxonid": 34506, + "scientific_name": "Heptapleurum hullettii", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 31256, + "scientific_name": "Heptapleurum taiwanianum", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 30472, + "scientific_name": "Hernandia nukuhivensis", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 38333, + "scientific_name": "Homalium longifolium", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 35087, + "scientific_name": "Homalium moto", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 35088, + "scientific_name": "Homalium mouo", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 37162, + "scientific_name": "Horsfieldia moluccana var. petiolaris", + "subspecies": "petiolaris", + "rank": "var.", + "subpopulation": null + }, + { + "taxonid": 34695, + "scientific_name": "Horsfieldia parviflora", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 33947, + "scientific_name": "Horsfieldia wallichii", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 10786, + "scientific_name": "Ideopsis klassika", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 38354, + "scientific_name": "Ilex diospyroides", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 38959, + "scientific_name": "Ixora moorensis", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 38964, + "scientific_name": "Ixora setchellii", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 37933, + "scientific_name": "Ixora stokesii", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 31837, + "scientific_name": "Kayea elegans", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 33589, + "scientific_name": "Kibara coriacea", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 22000, + "scientific_name": "Kimberleytrachia crawfordi", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 35013, + "scientific_name": "Kingiodendron platycarpum", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 62994, + "scientific_name": "Kirkia burgeri", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 35506, + "scientific_name": "Kirkia burgeri subsp. burgeri", + "subspecies": "burgeri", + "rank": "subsp.", + "subpopulation": null + }, + { + "taxonid": 37122, + "scientific_name": "Knema attenuata", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 33948, + "scientific_name": "Knema conferta", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 31944, + "scientific_name": "Knema elmeri", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 34544, + "scientific_name": "Knema furfuracea", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 33357, + "scientific_name": "Knema glomerata", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 37129, + "scientific_name": "Knema kunstleri subsp. kunstleri", + "subspecies": "kunstleri", + "rank": "subsp.", + "subpopulation": null + }, + { + "taxonid": 37105, + "scientific_name": "Knema latericia subsp. albifolia", + "subspecies": "albifolia", + "rank": "subsp.", + "subpopulation": null + }, + { + "taxonid": 33725, + "scientific_name": "Knema latericia subsp. ridleyi", + "subspecies": "ridleyi", + "rank": "subsp.", + "subpopulation": null + }, + { + "taxonid": 34606, + "scientific_name": "Knema latifolia", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 37098, + "scientific_name": "Knema laurina var. heteropilis", + "subspecies": "heteropilis", + "rank": "var.", + "subpopulation": null + }, + { + "taxonid": 37097, + "scientific_name": "Knema laurina var. laurina", + "subspecies": "laurina", + "rank": "var.", + "subpopulation": null + }, + { + "taxonid": 31947, + "scientific_name": "Knema linguiformis", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 34548, + "scientific_name": "Knema malayana", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 31952, + "scientific_name": "Knema oblongata subsp. oblongata", + "subspecies": "oblongata", + "rank": "subsp.", + "subpopulation": null + }, + { + "taxonid": 34549, + "scientific_name": "Knema rubens", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 32944, + "scientific_name": "Knema scortechinii", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 37137, + "scientific_name": "Knema stenophylla subsp. longipedicellata", + "subspecies": "longipedicellata", + "rank": "subsp.", + "subpopulation": null + }, + { + "taxonid": 37136, + "scientific_name": "Knema stenophylla subsp. stenophylla", + "subspecies": "stenophylla", + "rank": "subsp.", + "subpopulation": null + }, + { + "taxonid": 35581, + "scientific_name": "Lacistema robustum", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 35582, + "scientific_name": "Lafoensia pacari", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 35587, + "scientific_name": "Lecythis ollaria", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 39205, + "scientific_name": "Lepidozia azorica", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 39194, + "scientific_name": "Leptoscyphus azoricus", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 41985, + "scientific_name": "Leptothorax recedens", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 36360, + "scientific_name": "Lindera reticulosa", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 33210, + "scientific_name": "Lophopetalum javanicum", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 33211, + "scientific_name": "Lophopetalum wightianum", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 35078, + "scientific_name": "Macaranga attenuata", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 35081, + "scientific_name": "Macaranga venosa", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 32294, + "scientific_name": "Maclura brasiliensis", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 36579, + "scientific_name": "Macrosamanea consanguinea", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 35971, + "scientific_name": "Magnolia fordiana var. fordiana", + "subspecies": "fordiana", + "rank": "var.", + "subpopulation": null + }, + { + "taxonid": 31391, + "scientific_name": "Mangifera gracilipes", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 33940, + "scientific_name": "Mangifera magnifica", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 31395, + "scientific_name": "Mangifera parvifolia", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 31402, + "scientific_name": "Mangifera quadrifida var. quadrifida", + "subspecies": "quadrifida", + "rank": "var.", + "subpopulation": null + }, + { + "taxonid": 31405, + "scientific_name": "Mangifera sylvatica", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 33935, + "scientific_name": "Maranthes corymbosa", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 38018, + "scientific_name": "Mastixia arborea", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 33502, + "scientific_name": "Mastixia trichotoma var. maingayi", + "subspecies": "maingayi", + "rank": "var.", + "subpopulation": null + }, + { + "taxonid": 34539, + "scientific_name": "Matthaea sancta", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 31413, + "scientific_name": "Melanochyla nitida", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 38979, + "scientific_name": "Melicope revoluta", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 31529, + "scientific_name": "Mesua daphnifolia", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 31531, + "scientific_name": "Mesua kunstleri", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 31532, + "scientific_name": "Mesua nivenii", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 31533, + "scientific_name": "Mesua nuda", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 31536, + "scientific_name": "Mesua wrayi", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 38611, + "scientific_name": "Metroxylon warburgii", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 36716, + "scientific_name": "Miconia tomentosa", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 35630, + "scientific_name": "Micropholis cayennensis", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 35646, + "scientific_name": "Micropholis sanctae-rosae", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 31718, + "scientific_name": "Monoon hypogaeum", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 36217, + "scientific_name": "Monoon membranifolium", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 6069, + "scientific_name": "Monoplex parthenopaeum", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 36238, + "scientific_name": "Mundulea sericea subsp. madagascariensis", + "subspecies": "madagascariensis", + "rank": "subsp.", + "subpopulation": null + }, + { + "taxonid": 35110, + "scientific_name": "Myoporum rapense", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 37240, + "scientific_name": "Myristica bialata var. brevipila", + "subspecies": "brevipila", + "rank": "var.", + "subpopulation": null + }, + { + "taxonid": 33278, + "scientific_name": "Myristica cinnamomea", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 33236, + "scientific_name": "Myristica elliptica", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 37213, + "scientific_name": "Myristica fatua subsp. fatua", + "subspecies": "fatua", + "rank": "subsp.", + "subpopulation": null + }, + { + "taxonid": 37251, + "scientific_name": "Myristica fissiflora subsp. kostermansii", + "subspecies": "kostermansii", + "rank": "subsp.", + "subpopulation": null + }, + { + "taxonid": 34990, + "scientific_name": "Myristica gillespieana", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 34988, + "scientific_name": "Myristica grandifolia", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 37877, + "scientific_name": "Myristica guatteriifolia", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 37235, + "scientific_name": "Myristica guillauminiana", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 33237, + "scientific_name": "Myristica iners", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 37259, + "scientific_name": "Myristica inutilis subsp. papuana", + "subspecies": "papuana", + "rank": "subsp.", + "subpopulation": null + }, + { + "taxonid": 37227, + "scientific_name": "Myristica inutilis subsp. platyphylla", + "subspecies": "platyphylla", + "rank": "subsp.", + "subpopulation": null + }, + { + "taxonid": 37215, + "scientific_name": "Myristica lancifolia subsp. lancifolia", + "subspecies": "lancifolia", + "rank": "subsp.", + "subpopulation": null + }, + { + "taxonid": 37216, + "scientific_name": "Myristica lancifolia subsp. montana", + "subspecies": "montana", + "rank": "subsp.", + "subpopulation": null + }, + { + "taxonid": 34989, + "scientific_name": "Myristica macrantha", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 33229, + "scientific_name": "Myristica maxima", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 37221, + "scientific_name": "Myristica sangowoensis", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 37325, + "scientific_name": "Myristica tristis subsp. moluccana", + "subspecies": "moluccana", + "rank": "subsp.", + "subpopulation": null + }, + { + "taxonid": 37222, + "scientific_name": "Myristica tristis subsp. tristis", + "subspecies": "tristis", + "rank": "subsp.", + "subpopulation": null + }, + { + "taxonid": 35116, + "scientific_name": "Myrsine collina", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 38947, + "scientific_name": "Myrsine falcata", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 38948, + "scientific_name": "Myrsine fasciculata", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 35117, + "scientific_name": "Myrsine fusca", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 38949, + "scientific_name": "Myrsine grantii var. grantii", + "subspecies": "grantii", + "rank": "var.", + "subpopulation": null + }, + { + "taxonid": 38950, + "scientific_name": "Myrsine niauensis", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 38951, + "scientific_name": "Myrsine nukuhivensis", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 38952, + "scientific_name": "Myrsine ovalis var. wilderi", + "subspecies": "wilderi", + "rank": "var.", + "subpopulation": null + }, + { + "taxonid": 37931, + "scientific_name": "Myrsine rapensis", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 39664, + "scientific_name": "Nepenthes gymnamphora", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 39686, + "scientific_name": "Nepenthes pectinata", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 34369, + "scientific_name": "Neuburgia collina", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 32489, + "scientific_name": "Ocotea puberula", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 37928, + "scientific_name": "Oparanthus coriaceus", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 37929, + "scientific_name": "Oparanthus rapensis", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 15455, + "scientific_name": "Oreisplanus munionga ssp. larana", + "subspecies": "larana", + "rank": "ssp.", + "subpopulation": null + }, + { + "taxonid": 32152, + "scientific_name": "Ozoroa reticulata var. mossambicensis", + "subspecies": "mossambicensis", + "rank": "var.", + "subpopulation": null + }, + { + "taxonid": 36198, + "scientific_name": "Pakaraimaea dipterocarpacea subsp. nitida", + "subspecies": "nitida", + "rank": "subsp.", + "subpopulation": null + }, + { + "taxonid": 38990, + "scientific_name": "Pandanus temehaniensis", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 15978, + "scientific_name": "Papilio acheron", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 16142, + "scientific_name": "Parantica crowleyi", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 16146, + "scientific_name": "Parantica kirbyi", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 16149, + "scientific_name": "Parantica menadensis", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 16155, + "scientific_name": "Parantica pumila", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 16156, + "scientific_name": "Parantica rotundata", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 16163, + "scientific_name": "Parantica weiskei", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 34360, + "scientific_name": "Parasenegalia visco", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 33232, + "scientific_name": "Parinari costata", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 31797, + "scientific_name": "Payena maingayi", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 31774, + "scientific_name": "Pellacalyx saccardianus", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 37393, + "scientific_name": "Peltophorum dasyrachis var. tonkinensis", + "subspecies": "tonkinensis", + "rank": "var.", + "subpopulation": null + }, + { + "taxonid": 41601, + "scientific_name": "Pelusios castanoides", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 16530, + "scientific_name": "Pelusios rhodesianus", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 41602, + "scientific_name": "Pelusios subniger", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 34415, + "scientific_name": "Persea pyrifolia", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 38736, + "scientific_name": "Piper laevigatum", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 38988, + "scientific_name": "Pipturus polynesicus var. polynesicus", + "subspecies": "polynesicus", + "rank": "var.", + "subpopulation": null + }, + { + "taxonid": 31369, + "scientific_name": "Pistacia malayana", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 35008, + "scientific_name": "Pittosporum pickeringii", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 37932, + "scientific_name": "Pittosporum rapense", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 35006, + "scientific_name": "Pittosporum rhytidocarpum", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 38954, + "scientific_name": "Pittosporum taitense", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 31024, + "scientific_name": "Plerandra seemanniana", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 31825, + "scientific_name": "Polyalthia chrysotricha", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 38249, + "scientific_name": "Polyalthia lateritia", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 30469, + "scientific_name": "Polyscias marchionensis", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 35056, + "scientific_name": "Polyscias verrucosa", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 31830, + "scientific_name": "Popowia fusca", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 35823, + "scientific_name": "Pouteria cayennensis", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 35842, + "scientific_name": "Pouteria franciscana", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 35860, + "scientific_name": "Pouteria melanopoda", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 35889, + "scientific_name": "Pouteria reticulata subsp. surinamensis", + "subspecies": "surinamensis", + "rank": "subsp.", + "subpopulation": null + }, + { + "taxonid": 35898, + "scientific_name": "Pouteria sagotiana", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 35899, + "scientific_name": "Pouteria scrobiculata", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 35906, + "scientific_name": "Pouteria tenuisepala", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 35907, + "scientific_name": "Pouteria trigonosperma", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 35014, + "scientific_name": "Premna protrusa", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 37944, + "scientific_name": "Premna tahitensis", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 38639, + "scientific_name": "Prestoea pubens var. pubens", + "subspecies": "pubens", + "rank": "var.", + "subpopulation": null + }, + { + "taxonid": 33727, + "scientific_name": "Prunus arborea", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 31340, + "scientific_name": "Prunus grisea", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 31285, + "scientific_name": "Prunus henryi", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 33592, + "scientific_name": "Prunus javanica", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 31777, + "scientific_name": "Prunus malayana", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 33614, + "scientific_name": "Prunus marsupialis", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 33722, + "scientific_name": "Prunus polystachya", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 18391, + "scientific_name": "Prymnbriareus nimberlinus", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 31610, + "scientific_name": "Pseudoeugenia perakiana", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 37840, + "scientific_name": "Pseudoeugenia singaporensis", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 38973, + "scientific_name": "Psychotria raivavaensis", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 18911, + "scientific_name": "Pupilla pupula", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 18919, + "scientific_name": "Pupisoma orcula", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 18973, + "scientific_name": "Pyrgulopsis micrococcus", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 34508, + "scientific_name": "Radermachera pinnata subsp. acuminata", + "subspecies": "acuminata", + "rank": "subsp.", + "subpopulation": null + }, + { + "taxonid": 35968, + "scientific_name": "Rapanea allenii", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 32224, + "scientific_name": "Rhaptopetalum beguei", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 31465, + "scientific_name": "Rhododendron wrayi", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 36017, + "scientific_name": "Rhodoleia championii", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 30654, + "scientific_name": "Rinorea dasyadena", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 19855, + "scientific_name": "Salmo salar", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 32130, + "scientific_name": "Santiria apiculata", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 32843, + "scientific_name": "Santiria griffithii", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 32842, + "scientific_name": "Santiria laevigata", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 32841, + "scientific_name": "Santiria tomentosa", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 37004, + "scientific_name": "Sarcaulus brasiliensis subsp. gracilis", + "subspecies": "gracilis", + "rank": "subsp.", + "subpopulation": null + }, + { + "taxonid": 31700, + "scientific_name": "Sarcotheca glomerula", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 39715, + "scientific_name": "Sarracenia flava", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 39717, + "scientific_name": "Sarracenia minor", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 39719, + "scientific_name": "Sarracenia psittacina", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 39720, + "scientific_name": "Sarracenia purpurea subsp. purpurea", + "subspecies": "purpurea", + "rank": "subsp.", + "subpopulation": null + }, + { + "taxonid": 39722, + "scientific_name": "Sarracenia purpurea subsp. venosa", + "subspecies": "venosa", + "rank": "subsp.", + "subpopulation": null + }, + { + "taxonid": 30765, + "scientific_name": "Saurauia pustulata", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 31321, + "scientific_name": "Saurauia scabrida", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 39222, + "scientific_name": "Sauteria spongiosa", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 37810, + "scientific_name": "Scaevola floribunda", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 38791, + "scientific_name": "Scalesia affinis", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 33255, + "scientific_name": "Scaphium macropodum", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 31025, + "scientific_name": "Schefflera vitiensis", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 32017, + "scientific_name": "Schinopsis balansae", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 32022, + "scientific_name": "Schinopsis quebracho-colorado", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 37009, + "scientific_name": "Sciodaphyllum sprucei", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 34557, + "scientific_name": "Scleropyrum wallichianum", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 33257, + "scientific_name": "Scutinanthe brunnea", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 35507, + "scientific_name": "Searsia glutinosa subsp. glutinosa", + "subspecies": "glutinosa", + "rank": "subsp.", + "subpopulation": null + }, + { + "taxonid": 20111, + "scientific_name": "Semperdon heptaptychius", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 34864, + "scientific_name": "Serianthes melanesica var. melanesica", + "subspecies": "melanesica", + "rank": "var.", + "subpopulation": null + }, + { + "taxonid": 20162, + "scientific_name": "Setobaudinia collingii", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 32097, + "scientific_name": "Shorea robusta", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 32307, + "scientific_name": "Shorea siamensis", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 33704, + "scientific_name": "Siphonodon celastrineus", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 31283, + "scientific_name": "Sloanea assamica", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 31275, + "scientific_name": "Sloanea tomentosa", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 35468, + "scientific_name": "Solanum endopogon subsp. guianensis", + "subspecies": "guianensis", + "rank": "subsp.", + "subpopulation": null + }, + { + "taxonid": 34729, + "scientific_name": "Sorbus aria subsp. lanifera", + "subspecies": "lanifera", + "rank": "subsp.", + "subpopulation": null + }, + { + "taxonid": 31273, + "scientific_name": "Sorbus wallichii", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 31032, + "scientific_name": "Spiraeanthemum katakata", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 39197, + "scientific_name": "Stenorrhipis rhizomatica", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 33949, + "scientific_name": "Sterculia parviflora", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 31414, + "scientific_name": "Swintonia robinsonii", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 38279, + "scientific_name": "Swintonia spicifera", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 38701, + "scientific_name": "Syagrus picrophylla", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 38702, + "scientific_name": "Syagrus pseudococos", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 38703, + "scientific_name": "Syagrus ruschiana", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 38704, + "scientific_name": "Syagrus smithii", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 35000, + "scientific_name": "Syzygium diffusum", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 36385, + "scientific_name": "Syzygium dyerianum", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 34994, + "scientific_name": "Syzygium fijiense", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 35001, + "scientific_name": "Syzygium purpureum", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 35003, + "scientific_name": "Syzygium seemannianum", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 36388, + "scientific_name": "Syzygium stapfianum", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 32024, + "scientific_name": "Tabebuia impetiginosa", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 34504, + "scientific_name": "Tabernaemontana corymbosa", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 35351, + "scientific_name": "Tabernaemontana thurstonii", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 32376, + "scientific_name": "Tetrameles nudiflora", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 21740, + "scientific_name": "Thermosphaeroma subequalum", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 32161, + "scientific_name": "Thespesiopsis mossambicensis", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 31287, + "scientific_name": "Tilia paucicostata var. yunnanensis", + "subspecies": "yunnanensis", + "rank": "var.", + "subpopulation": null + }, + { + "taxonid": 21917, + "scientific_name": "Tirumala gautama", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 22018, + "scientific_name": "Toxotes oligolepis", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 38984, + "scientific_name": "Trema discolor", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 36110, + "scientific_name": "Trichilia stellato-tomentosa", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 22135, + "scientific_name": "Tridacna crocea", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 37943, + "scientific_name": "Trimenia weinmanniifolia subsp. marquesensis", + "subspecies": "marquesensis", + "rank": "subsp.", + "subpopulation": null + }, + { + "taxonid": 32174, + "scientific_name": "Triplochiton scleroxylon", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 33367, + "scientific_name": "Unonopsis velutina", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 30756, + "scientific_name": "Vachellia chiapensis", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 30368, + "scientific_name": "Vachellia hebeclada subsp. chobiensis", + "subspecies": "chobiensis", + "rank": "subsp.", + "subpopulation": null + }, + { + "taxonid": 33165, + "scientific_name": "Vatica odorata subsp. odorata", + "subspecies": "odorata", + "rank": "subsp.", + "subpopulation": null + }, + { + "taxonid": 33156, + "scientific_name": "Vatica umbonata subsp. acrocarpa", + "subspecies": "acrocarpa", + "rank": "subsp.", + "subpopulation": null + }, + { + "taxonid": 38714, + "scientific_name": "Veitchia macdanielsii", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 38724, + "scientific_name": "Wallichia triandra", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 37915, + "scientific_name": "Weinmannia affinis", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 35061, + "scientific_name": "Weinmannia parviflora var. marquesana", + "subspecies": "marquesana", + "rank": "var.", + "subpopulation": null + }, + { + "taxonid": 35062, + "scientific_name": "Weinmannia parviflora var. parviflora", + "subspecies": "parviflora", + "rank": "var.", + "subpopulation": null + }, + { + "taxonid": 35064, + "scientific_name": "Weinmannia rapensis", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 31035, + "scientific_name": "Weinmannia richii", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 38726, + "scientific_name": "Wettinia anomala", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 38728, + "scientific_name": "Wettinia drudei", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 38729, + "scientific_name": "Wettinia fascicularis", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 38731, + "scientific_name": "Wettinia kalbreyeri", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 38982, + "scientific_name": "Wikstroemia coriacea", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 39058, + "scientific_name": "Woodfordia fruticosa", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 32119, + "scientific_name": "Wrightia pubescens subsp. lanitii", + "subspecies": "lanitii", + "rank": "subsp.", + "subpopulation": null + }, + { + "taxonid": 31726, + "scientific_name": "Xylopia elliptica", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 31727, + "scientific_name": "Xylopia magna", + "subspecies": null, + "rank": null, + "subpopulation": null + }, + { + "taxonid": 35089, + "scientific_name": "Xylosma suaveolens subsp. gracile", + "subspecies": "gracile", + "rank": "subsp.", + "subpopulation": null + }, + { + "taxonid": 35090, + "scientific_name": "Xylosma suaveolens subsp. pubigerum", + "subspecies": "pubigerum", + "rank": "subsp.", + "subpopulation": null + }, + { + "taxonid": 35091, + "scientific_name": "Xylosma suaveolens subsp. suaveolens", + "subspecies": "suaveolens", + "rank": "subsp.", + "subpopulation": null + } + ] + }, + "https://apiv3.iucnredlist.org/api/v3/species/citation/Loxodonta%20africana": { + "name": "loxodonta africana", + "result": [ + { + "citation": "Gobush, K.S., Edwards, C.T.T, Balfour, D., Wittemyer, G., Maisels, F. & Taylor, R.D. 2021. Loxodonta africana. The IUCN Red List of Threatened Species 2021: e.T181008073A204401095. https://dx.doi.org/10.2305/IUCN.UK.2021-2.RLTS.T181008073A204401095.en .Accessed on 9 September 2022" + } + ] + }, + "https://apiv3.iucnredlist.org/api/v3/species/citation/Balaena%20mysticetus/region/europe": { + "name": "Balaena mysticetus", + "region_identifier": "europe", + "result": [ + { + "citation": "Species account by IUCN SSC Cetacean Specialist Group; regional assessment by European Mammal Assessment team 2007. Balaena mysticetus. The IUCN Red List of Threatened Species 2007: e.T2467A9442304. .Accessed on 9 September 2022" + } + ] + }, + "https://apiv3.iucnredlist.org/api/v3/species/citation/id/181008073": { + "name": "181008073", + "result": [ + { + "citation": "Gobush, K.S., Edwards, C.T.T, Balfour, D., Wittemyer, G., Maisels, F. & Taylor, R.D. 2021. Loxodonta africana. The IUCN Red List of Threatened Species 2021: e.T181008073A204401095. https://dx.doi.org/10.2305/IUCN.UK.2021-2.RLTS.T181008073A204401095.en .Accessed on 9 September 2022" + } + ] + }, + "https://apiv3.iucnredlist.org/api/v3/species/citation/id/2467/region/europe": { + "name": "2467", + "region_identifier": "europe", + "result": [ + { + "citation": "Species account by IUCN SSC Cetacean Specialist Group; regional assessment by European Mammal Assessment team 2007. Balaena mysticetus. The IUCN Red List of Threatened Species 2007: e.T2467A9442304. .Accessed on 9 September 2022" + } + ] + }, + "https://apiv3.iucnredlist.org/api/v3/species/common_names/Loxodonta%20africana": { + "name": "loxodonta africana", + "result": [ + { + "taxonname": "African Savanna Elephant", + "primary": true, + "language": "eng" + }, + { + "taxonname": "Savanna Elephant", + "primary": false, + "language": "eng" + }, + { + "taxonname": "African Bush Elephant", + "primary": false, + "language": "eng" + }, + { + "taxonname": "African Savannah Elephant", + "primary": false, + "language": "eng" + }, + { + "taxonname": "Savannah Elephant", + "primary": false, + "language": "eng" + }, + { + "taxonname": "Éléphant de savane", + "primary": false, + "language": "fre" + }, + { + "taxonname": "Elefante de Sabana", + "primary": false, + "language": "spa" + } + ] + }, + "https://apiv3.iucnredlist.org/api/v3/species/countries/id/181008073": { + "name": "181008073", + "count": 26, + "result": [ + { + "code": "AO", + "country": "Angola", + "presence": "Extant", + "origin": "Native", + "distribution_code": "Native" + }, + { + "code": "BF", + "country": "Burkina Faso", + "presence": "Extant", + "origin": "Native", + "distribution_code": "Native" + }, + { + "code": "BI", + "country": "Burundi", + "presence": "Extinct Post-1500", + "origin": "Native", + "distribution_code": "Regionally Extinct" + }, + { + "code": "BW", + "country": "Botswana", + "presence": "Extant", + "origin": "Native", + "distribution_code": "Native" + }, + { + "code": "CD", + "country": "Congo, The Democratic Republic of the", + "presence": "Extant", + "origin": "Native", + "distribution_code": "Native" + }, + { + "code": "CF", + "country": "Central African Republic", + "presence": "Extant", + "origin": "Native", + "distribution_code": "Native" + }, + { + "code": "CM", + "country": "Cameroon", + "presence": "Extant", + "origin": "Native", + "distribution_code": "Native" + }, + { + "code": "ER", + "country": "Eritrea", + "presence": "Extant", + "origin": "Native", + "distribution_code": "Native" + }, + { + "code": "ET", + "country": "Ethiopia", + "presence": "Extant", + "origin": "Native", + "distribution_code": "Native" + }, + { + "code": "KE", + "country": "Kenya", + "presence": "Extant", + "origin": "Native", + "distribution_code": "Native" + }, + { + "code": "ML", + "country": "Mali", + "presence": "Extant", + "origin": "Native", + "distribution_code": "Native" + }, + { + "code": "MR", + "country": "Mauritania", + "presence": "Extinct Post-1500", + "origin": "Native", + "distribution_code": "Regionally Extinct" + }, + { + "code": "MW", + "country": "Malawi", + "presence": "Extant", + "origin": "Native", + "distribution_code": "Native" + }, + { + "code": "MZ", + "country": "Mozambique", + "presence": "Extant", + "origin": "Native", + "distribution_code": "Native" + }, + { + "code": "NA", + "country": "Namibia", + "presence": "Extant", + "origin": "Native", + "distribution_code": "Native" + }, + { + "code": "NG", + "country": "Nigeria", + "presence": "Extant", + "origin": "Native", + "distribution_code": "Native" + }, + { + "code": "RW", + "country": "Rwanda", + "presence": "Extant", + "origin": "Native", + "distribution_code": "Native" + }, + { + "code": "SO", + "country": "Somalia", + "presence": "Extant", + "origin": "Native", + "distribution_code": "Native" + }, + { + "code": "SS", + "country": "South Sudan", + "presence": "Extant", + "origin": "Native", + "distribution_code": "Native" + }, + { + "code": "SZ", + "country": "Eswatini", + "presence": "Extant", + "origin": "Reintroduced", + "distribution_code": "Reintroduced" + }, + { + "code": "TD", + "country": "Chad", + "presence": "Extant", + "origin": "Native", + "distribution_code": "Native" + }, + { + "code": "TZ", + "country": "Tanzania, United Republic of", + "presence": "Extant", + "origin": "Native", + "distribution_code": "Native" + }, + { + "code": "UG", + "country": "Uganda", + "presence": "Extant", + "origin": "Native", + "distribution_code": "Native" + }, + { + "code": "ZA", + "country": "South Africa", + "presence": "Extant", + "origin": "Native", + "distribution_code": "Native" + }, + { + "code": "ZM", + "country": "Zambia", + "presence": "Extant", + "origin": "Native", + "distribution_code": "Native" + }, + { + "code": "ZW", + "country": "Zimbabwe", + "presence": "Extant", + "origin": "Native", + "distribution_code": "Native" + } + ] + }, + "https://apiv3.iucnredlist.org/api/v3/species/countries/id/22823/region/europe": { + "name": "22823", + "count": 3, + "region_identifier": "europe", + "result": [ + { + "code": "NO", + "country": "Norway", + "presence": "Extant", + "origin": "Native", + "distribution_code": "Native" + }, + { + "code": "RU", + "country": "Russian Federation", + "presence": "Extant", + "origin": "Native", + "distribution_code": "Native" + }, + { + "code": "SJ", + "country": "Svalbard and Jan Mayen", + "presence": "Extant", + "origin": "Native", + "distribution_code": "Native" + } + ] + }, + "https://apiv3.iucnredlist.org/api/v3/species/countries/name/Loxodonta%20africana": { + "name": "Loxodonta africana", + "count": 26, + "result": [ + { + "code": "AO", + "country": "Angola", + "presence": "Extant", + "origin": "Native", + "distribution_code": "Native" + }, + { + "code": "BF", + "country": "Burkina Faso", + "presence": "Extant", + "origin": "Native", + "distribution_code": "Native" + }, + { + "code": "BI", + "country": "Burundi", + "presence": "Extinct Post-1500", + "origin": "Native", + "distribution_code": "Regionally Extinct" + }, + { + "code": "BW", + "country": "Botswana", + "presence": "Extant", + "origin": "Native", + "distribution_code": "Native" + }, + { + "code": "CD", + "country": "Congo, The Democratic Republic of the", + "presence": "Extant", + "origin": "Native", + "distribution_code": "Native" + }, + { + "code": "CF", + "country": "Central African Republic", + "presence": "Extant", + "origin": "Native", + "distribution_code": "Native" + }, + { + "code": "CM", + "country": "Cameroon", + "presence": "Extant", + "origin": "Native", + "distribution_code": "Native" + }, + { + "code": "ER", + "country": "Eritrea", + "presence": "Extant", + "origin": "Native", + "distribution_code": "Native" + }, + { + "code": "ET", + "country": "Ethiopia", + "presence": "Extant", + "origin": "Native", + "distribution_code": "Native" + }, + { + "code": "KE", + "country": "Kenya", + "presence": "Extant", + "origin": "Native", + "distribution_code": "Native" + }, + { + "code": "ML", + "country": "Mali", + "presence": "Extant", + "origin": "Native", + "distribution_code": "Native" + }, + { + "code": "MR", + "country": "Mauritania", + "presence": "Extinct Post-1500", + "origin": "Native", + "distribution_code": "Regionally Extinct" + }, + { + "code": "MW", + "country": "Malawi", + "presence": "Extant", + "origin": "Native", + "distribution_code": "Native" + }, + { + "code": "MZ", + "country": "Mozambique", + "presence": "Extant", + "origin": "Native", + "distribution_code": "Native" + }, + { + "code": "NA", + "country": "Namibia", + "presence": "Extant", + "origin": "Native", + "distribution_code": "Native" + }, + { + "code": "NG", + "country": "Nigeria", + "presence": "Extant", + "origin": "Native", + "distribution_code": "Native" + }, + { + "code": "RW", + "country": "Rwanda", + "presence": "Extant", + "origin": "Native", + "distribution_code": "Native" + }, + { + "code": "SO", + "country": "Somalia", + "presence": "Extant", + "origin": "Native", + "distribution_code": "Native" + }, + { + "code": "SS", + "country": "South Sudan", + "presence": "Extant", + "origin": "Native", + "distribution_code": "Native" + }, + { + "code": "SZ", + "country": "Eswatini", + "presence": "Extant", + "origin": "Reintroduced", + "distribution_code": "Reintroduced" + }, + { + "code": "TD", + "country": "Chad", + "presence": "Extant", + "origin": "Native", + "distribution_code": "Native" + }, + { + "code": "TZ", + "country": "Tanzania, United Republic of", + "presence": "Extant", + "origin": "Native", + "distribution_code": "Native" + }, + { + "code": "UG", + "country": "Uganda", + "presence": "Extant", + "origin": "Native", + "distribution_code": "Native" + }, + { + "code": "ZA", + "country": "South Africa", + "presence": "Extant", + "origin": "Native", + "distribution_code": "Native" + }, + { + "code": "ZM", + "country": "Zambia", + "presence": "Extant", + "origin": "Native", + "distribution_code": "Native" + }, + { + "code": "ZW", + "country": "Zimbabwe", + "presence": "Extant", + "origin": "Native", + "distribution_code": "Native" + } + ] + }, + "https://apiv3.iucnredlist.org/api/v3/species/countries/name/Ursus%20maritimus/region/europe": { + "name": "Ursus maritimus", + "count": 3, + "region_identifier": "europe", + "result": [ + { + "code": "NO", + "country": "Norway", + "presence": "Extant", + "origin": "Native", + "distribution_code": "Native" + }, + { + "code": "RU", + "country": "Russian Federation", + "presence": "Extant", + "origin": "Native", + "distribution_code": "Native" + }, + { + "code": "SJ", + "country": "Svalbard and Jan Mayen", + "presence": "Extant", + "origin": "Native", + "distribution_code": "Native" + } + ] + }, + "https://apiv3.iucnredlist.org/api/v3/species/history/id/181008073": { + "name": "181008073", + "result": [ + { + "year": "2021", + "assess_year": "2020", + "code": "EN", + "category": "Endangered" + } + ] + }, + "https://apiv3.iucnredlist.org/api/v3/species/history/id/22823/region/europe": { + "name": "22823", + "region_identifier": "europe", + "result": [ + { + "year": "2007", + "assess_year": "2006", + "code": "VU", + "category": "Vulnerable" + } + ] + }, + "https://apiv3.iucnredlist.org/api/v3/species/history/name/Loxodonta%20africana": { + "name": "Loxodonta africana", + "result": [ + { + "year": "2021", + "assess_year": "2020", + "code": "EN", + "category": "Endangered" + } + ] + }, + "https://apiv3.iucnredlist.org/api/v3/species/history/name/Ursus%20maritimus/region/europe": { + "name": "Ursus maritimus", + "region_identifier": "europe", + "result": [ + { + "year": "2007", + "assess_year": "2006", + "code": "VU", + "category": "Vulnerable" + } + ] + }, + "https://apiv3.iucnredlist.org/api/v3/species/narrative/Loxodonta%20africana": { + "name": "loxodonta africana", + "result": [ + { + "species_id": 181008073, + "taxonomicnotes": "Three elephant taxa remain from the sixteen elephant-like species that are known from across the planet in the Pleistocene: Asian Elephant (Elephas maximus), African Savanna Elephant (Loxodonta africana), and African Forest Elephant (Loxodonta cyclotis) (Faurby and Svenning 2015, Malhi et al. 2016). The Asian and African ancestral lineages diverged approximately seven million years ago, and the African Savanna and African Forest ancestral lineages began diverging approximately one million years later (Rohland et al. 2010, Brandt et al. 2014, Roca et al. 2015, Meyer et al. 2017, Palkopoulou et al. 2018). The Third Edition of ‘Mammal Species of the World’ (Wilson and Reeder 2005) was the first to formally designate the African elephant as these two separate species. Recent genetic findings also support this designation (Roca et al. 2007, Ishida et al. 2011, Mondol et al. 2015, Palkopoulou et al. 2018, Kim and Wasser 2019). Hybridization between the two species appears restricted and evident at only 14 of the more than 100 localities recently examined across the vast forest-savanna ecotone. In nine of these 14 localities, hybrid individuals occurred alongside non-hybrid individuals of either one species or the other and not both (i.e., three localities had hybrids and African Forest Elephants only and assigned as this species; six localities had hybrids and African Savanna Elephants only and assigned as this species). For the IUCN Red List assessments, a distribution map published in Mondol et al. (2015) and recent data by Kim and Wasser (2019) are used to assign localities as range of either L. africana or L. cyclotis.", + "rationale": "

The African Savanna Elephant (Loxodonta africana) is assessed as Endangered A2abd. Analysis of estimates from 334 localities across their global range indicates a reduction of more than 50% of the continental population in the past three generations (75 years) that is understood to be continuing and likely irreversible. The continental trend is not, however, spatially uniform; some subpopulations are increasing or stable while others are declining significantly faster than the continental rate. Many local subpopulations have been extirpated.

A generation length (GL) of 25 years is used; calculated in the standard format as the average age of mothers in the population (IUCN SPC 2019, p. 29). This figure is based on analysis of the life table of culled Savanna Elephant family groups in South Africa (Whyte 2001) and a 14-year study of Savanna Elephants in Kenya (Wittemyer et al. 2013) which generated a range of 24.1–25 years.

Subcriterion A2 is applied because some of the major causes for population reduction, such as habitat loss due to human population expansion, have not ceased and are projected to increase in coming decades as well as unlikely to be reversible. The population reduction assessment for subcriterion A2 (considering three generations back) is inferred from published survey data using a modelling approach described in the Supplementary Information document. Density and distribution estimates for the African Savanna Elephant vary in methodology, completeness, regularity, date of first survey and confidence limits. Few credible estimates exist prior to the 1970s, and there is no credible estimate for the continental population more than two generations back. For this Red List assessment, an attempt was made to model the data three generations back to 1940 (see the attached Supplementary Information for description of data that is current as of and up to the end of 2015); however, given the sparseness of information available to inform the model, such modelling was of limited value. Therefore, rather than projecting declines well beyond the available data, we made the assumption that the continental African Savanna Elephant population of three generations back (1940) was equal to that of two generations back (i.e., 1965). Additional assumptions, necessary to fill gaps in the dataset, are detailed in the attached Supplementary Information document.

Subcriterion A3 has not been applied, because although the major threats to the species are known, projecting the level of such threats 25 or more years into the future (i.e., three generations, up to a maximum of 100 years) would likely introduce high levels of uncertainty.

An assessment of population reduction according to subcriterion A4 considering two generations back and one forward is in progress by this team of Assessors (Edwards et al. in prep.). Analysis of poaching and human influence in the recent past and anticipated in the future based upon available data of two representative indices (i.e., proportion of illegal killed elephants (PIKE) and the human footprint index) are being included as covariates in the projection.

Criteria B, C and D are not relevant to the threatened status as the species currently occupies more than 20,000 km2 and there are more than 10,000 mature individuals. No quantitative analysis of the probability of extinction in the wild was conducted, and therefore criterion E does not apply.


Previous Assessments of African Elephant:
This is the first assessment of the African Savanna Elephant (Loxodonta africana) as a species separate from the African Forest Elephant (L. cyclotis).

The African Elephant, as a single species, was listed as Vulnerable (VU A2a) in the 2004 and 2008 updates of the IUCN Red List of Threatened Species, under the same IUCN Categories and Criteria used in this assessment (Version 3.1; IUCN 2001).

Previously the African Elephant, as a single species, was listed as Endangered (EN A1b) under the IUCN Categories and Criteria Version 2.3 (IUCN 1994), in an assessment conducted in 1996 by the IUCN SSC African Elephant Specialist Group.

", + "geographicrange": "

African Savanna Elephants once occurred across all of Africa (Sikes 1971) and currently are found in 24 countries (see range map). Although knowledge of African Savanna Elephant distribution varies spatially and temporally, it is evident that the species’ distribution is retracting and becoming increasingly fragmented across their range in the continent. African Savanna Elephants occupy an estimated 15% of their historic pre- agricultural range (Chase et al. 2016). African Savanna Elephants are considered nationally extirpated in Burundi and Mauritania. In Eswatini, the once extirpated population has been re-established through reintroductions that began in the 1980s. Recent range expansion is evident in Kenya and Botswana (Douglas-Hamilton 1979; Said et al. 1995; Barnes et al. 1998; Blanc et al. 2003, 2007; Thouless et al. 2016).

The species' range map associated with this assessment is complete with the exception that some small private reserves in South Africa with less than 50 elephants each are absent as they were excluded from the African Elephant Database due to concerns about the census techniques.

", + "population": "

Over the past century, African Savanna Elephant subpopulations have declined across most of their range. The African Elephant Status Report 2016 estimated a continental population of 415,428 (+/- 95% C.I. 20,111) for both African Savanna and African Forest Elephants combined and reported a continental decline of approximately 111,000 elephants since 2006 (Thouless et al. 2016). For a similar time period, following a survey of approximately 90% of their range, a decline of 30% of African Savanna Elephants was reported (Chase et al. 2016).


For further information about this species, see the attached Supplementary Information document.

", + "populationtrend": "decreasing", + "habitat": "

African Savanna Elephants are found over a wide latitudinal range between the northern tropics in Mali (16° North) to the southern temperate zone (34° South) in South Africa. They occupy a variety of habitats ranging from montane forest, miombo and mopane woodland, thicket, savanna and grasslands to arid deserts and a wide altitudinal range from mountain slopes to oceanic beaches.

See also the list of habitats.

African Savanna Elephants are capable of moving long distances and naturally do so in arid ecosystems and in response to climatic conditions (e.g., seasonality and drought). Depending on productivity, and water availability African Savanna Elephants demonstrate range residence, migratory, semi-migratory and near nomadic movement patterns in different regions of the continent. Home range sizes vary by several orders of magnitude primarily in relation to plant productivity and human activity in different ecosystems (Loarie et al. 2009, Wall et al. 2013). Thirty African Savanna Elephant subpopulations (eight of which number more than 1,000 individuals) span international boundaries, including the more than 200,000 elephants in the five-country Kavango-Zambezi Transfrontier Conservation Area (Lindsay et al. 2017, KAZA Secretariat 2018).

Ecosystem Services:
Ecosystem services provided by African Savanna Elephants vary and depend to a large extent on the ecosystem (forest, savanna, grassland or desert), specific conditions on the ground and the geographical context under consideration. In general, they play an important ecological role as bulk processors of plant material (Owen-Smith 1989). In savannas, African Savanna Elephants are manipulators of woody vegetation structure which in turn influences the tree to grass ratio with knock-on consequences for the fire regime and potentially species composition in the area (Dublin et al. 1990, Augustine and McNaughton 2004, Palmer et al. 2008, Holdo et al. 2009, Goheen et al. 2010, Pringle et al. 2016). Where they are associated with wetlands African Savanna Elephants can be important in maintaining channels and open water ways. Soil fertility can be maintained by the transport of nutrients by large herbivores from nutrient rich sources (e.g., eutrophic soils in flood plains and termite mounds) to dystrophic soils (e.g., upland miombo woodlands) (Cumming et al. 1997, Doughty et al. 2016, Malhi et al. 2016).


The charismatic nature of African Savanna Elephants plays an important role in attracting tourism into national parks. They also hold important symbolic significance within the cultures of many African communities. African Savanna Elephants are one of the few iconic species that occur in the majority of African countries, and innumerable stories, songs, and cultural traditions revolve around them.

", + "threats": "Poaching of African Savanna Elephants for ivory is a major cause of individual death and population decline (Wittemyer et al. 2014, Thouless et al. 2016). After a sustained period of intense poaching between the late 1970s and 1989, many African Savanna Elephant populations (e.g., in Kenya, Tanzania, Zambia, Uganda) experienced two to three decades of recovery. Some northern African Savanna Elephant populations, however, experienced persistent poaching pressure through the last three decades (Bouche et al. 2011, 2012). Data collected as a part of the CITES Monitoring the Illegal Killing of Elephants programme (MIKE), indicate that poaching significantly intensified across the continent starting in 2008 and peaking in 2011 – an unsustainably high level of poaching has continued into current times in some areas of the continent (CITES 2018, 2019), and may be increasing in some of the historically less-affected southern African populations (CITES 2018, 2019). Rapid land use change by humans is driving the direct loss and fragmentation of habitat for African Savanna Elephants and is an increasing threat to populations across their range (Thouless et al. 2016, Mpakairi et al. 2019). Land conversion is a product of the ongoing expansion of the human population and associated agriculture and infrastructure development, which in turn are driven by economic and technological advances. A manifestation of this trend is the reported increase in human-elephant conflict (e.g., Pozo et al. 2018). Human population growth projections suggest land conversion will accelerate rapidly in the coming decades across Africa (see
https://population.un.org/wpp/Publications/) which will likely increase this threat.", + "conservationmeasures": "

The African Savanna Elephant was listed in CITES Appendix I in 1989 when all African elephants were considered a single species. Subsequently, the populations of Botswana (1997), Namibia (1997), South Africa (2000) and Zimbabwe (1997) were transferred to Appendix II, each with specific annotations. These annotations have been recently replaced by a single annotation for all four countries, with specific sub-annotations for the populations of Namibia and Zimbabwe. A separate CITES listing for each species has not occurred yet because a formal designation as two separate species (i.e., Loxodonta africana and L. cyclotis) is still in progress.

The African Elephant Action Plan (AEAP; developed and adopted by African elephant range countries) was adopted by CITES in March 2010 and is a statement by all range countries regarding the most important and immediate activities which require implementation and funding if Africa’s elephants are to be conserved. The African Elephant Fund was established to support the implementation of the African Elephant Action Plan. It should be noted that the AEAP does not distinguish different taxa of African elephant. The African Savanna Elephants which occur in transboundary populations introduce complications (e.g., matters of sovereignty) when considering national populations for CITES purposes (Lindsay et al. 2017).

A number of CITES-initiated instruments were created to monitor and combat illegal trade in ivory. The CITES MIKE programme, which was established in 2002, has 66 designated sites across African elephant range and provides the most detailed and reliable data available on continental poaching pressure (CITES 2018, 2019); this includes 39 sites in 18 range countries of the African Savanna Elephant (https://cites.org/eng/prog/mike, Accessed 16 August 2020). Eswatini, South Sudan, Central African Republic, the Democratic Republic of the Congo and Somalia do not have MIKE sites for their African Savanna Elephants. The Elephant Trade Information System (ETIS), established in 1996, is managed by TRAFFIC as a comprehensive information system for tracking illegal trade in ivory and other elephant products (CITES 2016, 2019). At a national level National Ivory Action Plans (NIAPs) are a tool designed to track significant and timely action to combat the illegal trade in ivory. Currently 24 African, Middle Eastern, and Asian countries, as identified by ETIS analyses, are required to produce and implement a NIAP (https://cites.org/eng/niaps, Accessed 16 August 2020); 11 of these are range countries of the African Savanna Elephant.

At a national level the African Savanna Elephant is subject to varying degrees of legal protection in the 23 range states, with most countries granting the species the highest protection status. Providing this protection is complicated by the fact that over half of the species’ range may extend beyond the boundaries of protected areas (Taylor 2009). This point is emphasized by the evidence that the degree of protection is an important predictor of African Savanna Elephant’s presence and density (de Boer et al. 2013).

Conservation measures usually include habitat management and protection through policy, legislation and law enforcement. Successful anti-poaching and management at a site level has contributed to the re-establishment or recovery of a number of populations of African Savanna Elephants. In instances where protection efforts have failed, the African Savanna Elephant population was reduced by 70% or more in a matter of a decade (Chase et al. 2016).

In the context where management culling was undertaken (mainly southern Africa) up until the late 1980s or early 1990s, culling has largely ceased and population density management is being attempted through actions such as translocation, contraception and range manipulation such as closure of artificial water point (Scholes and Mennell 2008). Fire management is being attempted in some transboundary situations to mitigate further habitat and/or woodland modification as a consequence of African Savanna Elephant activity (Starfield et al. 1993, Mapaure 2013, Eastment 2020). 

", + "usetrade": "Ivory: Use of African Savanna Elephant ivory is entrenched in numerous cultures across the globe, primarily for ornamental and decorative items. Historically, demand for African Savanna Elephant ivory has been high in Europe, the USA and Asia. For example, beginning in the 1920s, Japanese carvers turned to African elephant ivory as Asian supplies diminished through the 1970s when Japan accounted for about 40% of the global ivory market (Martin 1986; Nishihara 2003, 2012).

In 1989, the Convention on International Trade of Endangered Species of Wild Flora and Fauna (CITES) banned the international commercial trade of ivory in response to a steep decline in African elephants across a substantial portion of their range. Thereafter, two CITES-sanctioned sales of national ivory stockpiles occurred in 2002 and 2008 with Botswana, Zimbabwe, Namibia and South Africa selling ivory to China and Japan. At the same time, a nine-year moratorium (ending in 2017) on any new ivory sale proposals from the four African countries followed (www.cites.org). In the 2000s, Chinese demand for ivory greatly surpassed that of Japan where demand for ivory appeared to substantially decline (CITES 2014). As a consequence, prices rose steeply in China and in Africa (Wittemyer et al. 2011, 2014). Debates about the benefits and consequences of the sale of national ivory stockpiles are highly polarized with limited consensus (Stiles 2004, 't Sas-Rolfes et al. 2014, Bennett 2015, Biggs et al. 2017).

Analysis of ivory seizure data indicates that the volume of illegally trafficked ivory has increased substantially since 2006 (Underwood et al. 2013; Milliken 2016; CITES 2018, 2019). Undercover investigations and DNA forensics point to the laundering of illegal ivory in legal domestic ivory markets. Seizure analyses indicate that the majority of illegally trafficked ivory is destined for Asia, especially China, Viet Nam and Thailand (CITES 2016, 2018 and 2019; Lui 2015; Krishnasamy 2016). In response to this and other concerns, China closed its legal domestic ivory market in 2017; Hong Kong SAR took steps to do the same by 2021 (https://www.info.gov.hk/gia/general/201706/02/P2017060100655.htm), and Thailand tightened its domestic Asian Elephant ivory trade regulations in 2015 (http://www.mfa.go.th/main/en/media-center/14/52929-Thailand-Submits-First-Progress-Report-on-Implemen.html). A substantial drop in ivory prices in mainland China has been associated with this action (Vigne and Martin 2017, Meijer et al. 2018). Significant illegal ivory markets remain in several Southeast Asian countries, such as Lao PDR and Viet Nam (www.cites.org; Vigne and Martin 2017).

Non-consumptive Tourism: 
African Savanna Elephants have significant tourism draw for wildlife watching and photographic tourism throughout their range substantially contributing to local economies (Naidoo et al. 2016). Tourism operations occur in national and local government protected areas, as well as on land under private or communal tenure.

Trophy Hunting: In some range states sport hunting of African Savanna Elephants is legally permitted and forms an integral element of those countries’ wildlife management and community support programs (e.g., Naidoo et al. 2016). Botswana, Mozambique, Namibia, South Africa, Tanzania and Zimbabwe have trophy hunting industries and in 2020 applied to CITES for a combined quota of 2,404 tusks (https://cites.org/eng/resources/quotas/index.php Accessed 16 August 2020). Depending on the governance and management structures, government bodies, private operations and local communities derive revenue from trophy hunting.

Other Trade: A legal domestic trade in African Savanna Elephant hides is present in South Africa. There have been limited reports of African Savanna Elephant poaching for other body parts beyond ivory, such as meat, bone, skin and hair (Blignaut et al. 2008). International live trade in African Savanna Elephants occurs between South Africa and other countries, mainly in an effort to re-establish or fortify dwindling populations in some protected areas. Live trade in young African Savanna Elephants from Zimbabwe (Russo and Cruise 2016) and Eswatini (Madowo 2019) to Asia and elsewhere for zoological collections also occurs. Controversy surrounds these transactions as the conservation benefit to the species has been questioned. Domestic trade in African Savanna Elephants exists in South Africa at a low level for reintroduction purposes between reserves (Pretorius et al. 2018)." + } + ] + }, + "https://apiv3.iucnredlist.org/api/v3/species/narrative/Fratercula%20arctica/region/europe": { + "name": "Fratercula arctica", + "region_identifier": "europe", + "result": [ + { + "species_id": 22694927, + "taxonomicnotes": null, + "rationale": "European regional assessment: Endangered (EN)
EU28 regional assessment: Least Concern (LC)


In Europe, this species has an extremely large range (its extent of occurrence (EOO) is much larger than 20,000 km² and its area of occupancy (AOO) is much larger than 2,000 km²), and hence it does not approach the thresholds for Vulnerable under the range size criteria (criteria B and D2). The population size is extremely large, (much larger than 10,000 mature individuals), and hence it does not approach the thresholds for Vulnerable under the population size criteria (criteria C and D). The probability of extinction has not been calculated for this species, therefore criterion E cannot be applied. The population trend appears to be decreasing at a very rapid rate which meets the thresholds for Endangered (EN) under the population size reduction criterion (criterion A). The species is therefore assessed as such in Europe. There is not considered to be significant potential for rescue from outside the region, and therefore the final category remains unchanged. 

In the EU28, this species has a very large range (its extent of occurrence (EOO) is much larger than 20,000 km² and its area of occupancy (AOO) is much larger than 2,000 km²), and hence it does not approach the thresholds for Vulnerable under the range size criteria (criteria B and D2). The population size is extremely large, (much larger than 10,000 mature individuals), and hence it does not approach the thresholds for Vulnerable under the population size criteria (criteria C and D). The population trend appears to be increasing, and hence the species does not approach the thresholds for Vulnerable under the population size reduction criterion (criterion A). The probability of extinction has not been calculated for this species, therefore criterion E cannot be applied. For these reasons the species is evaluated as Least Concern in the EU28.", + "geographicrange": "The species can be found throughout the North Atlantic Ocean. In Europe, it occurs in north-west Greenland (to Denmark), and from north Norway down to the Canary Islands, Spain in the east (Nettleship et al. 2014), and breeds primarily in Norway and Iceland, and also notably in the United Kingdom and the Faroe Islands.", + "population": "The European breeding population is estimated at 3,700,000-4,120,000 pairs, which equates to 7,400,000-8,240,000 mature individuals. The breeding population in the EU28 is estimated at 601,000-602,000 pairs, which equates to 1,200,000-1,210,000 mature individuals. For details of national estimates, see the Supplementary Information.", + "populationtrend": "decreasing", + "habitat": "The breeding range is restricted to colder parts of the Atlantic and Arctic Oceans, with its southernmost colonies in Brittany (France).  Breeding colonies are located in Iceland, Norway, Faroe Islands, the U.K., Ireland and France on islands or high cliffs.

The species nests on grassy maritime slopes, sea cliffs and rocky slopes (Nettleship et al. 2014). During the winter the species is highly pelagic and is dispersed widely across the sea from the Azores to the western Mediterranean and Canary Islands. When feeding chicks, birds generally forage within 10 km of their colony, but may range as far as 50 to 100 km or more (Harris 1984, Rodway and Montevecchi 1996). Birds of this species are pursuit-divers that catch most of their prey within 30 m of the water surface (Piatt and Nettleship 1985). They prey on 'forage' species, including juvenile pelagic fishes, such as herring (Clupea harengus), juvenile and adult capelin (Mallotus villosus), and sand eel (Ammodytes spp.) (Barrett et al. 1987). At times, they also prey on juvenile demersal fishes, such as gadids (Harris and Hislop 1978, Martin 1989, Rodway and Montevecchi 1996). Sand eels usually form the majority of the prey fed to chicks (Corkhill 1973, Hislop and Harris 1985, Harris and Wanless 1986, Harris and Riddiford 1989, Martin 1989), and many chicks starve during periods of low sand eel abundance (Martin 1989).

Although the generation length for both EU and Europe regional assessments were calculated using the same methodology, new information arriving after the EU assessments were undertaken gave rise to an update in the generation lengths. This new information was then used for the Europe level assessments giving rise to a difference between the generation lengths used for the EU and Europe regions.", + "threats": "This species is highly susceptible to the impacts of climate change, such as sea temperature rise and shifts in prey distribution and abundance (Durant et al. 2003, Sandvik et al. 2005). This is a particularly important threat when prey species are exploited unsustainably, leading to prey reductions and subsequent unsuccessful breeding. The species is vulnerable to oil spills and other marine pollution. The species is also vulnerable to extreme weather events and storms, with large wrecks recorded following severe winter storms at sea. At the breeding colonies the species is vulnerable to invasive predators, such as rats, cats, and American Mink (Neovison vison). The species is susceptible to being caught in gillnets, although other fishing gears may also catch significant numbers. Increasing numbers of offshore wind farms may result in displacement from habitat, although the risk of collision is considered very low (Bradbury et al. 2014). The species is hunted for human consumption in Iceland, and in the Faroe Islands (Thorup et al. 2014).", + "conservationmeasures": "Conservation Actions Underway
The species is listed under the African Eurasian Waterbird Agreement. The species is included in the Action plan for seabirds in Western-Nordic areas (2010). There are 76 marine Important Bird Areas identified across the European region. Within the EU there are 40 Special Protection Areas which list this species as occurring within its boundaries.

Conservation Actions Proposed
Further identification of important sites for this species, particularly in offshore regions and designation as marine protected areas; Identify the risks of different activities on seabirds, and locations sensitive to seabirds. Continue eradication of invasive predators from breeding colonies. Management of fisheries to ensure long term sustainability of key stocks (e.g. sand eels). Establish observer schemes for bycatch and prepare National/European Community plans of action on seabird bycatch. Continue AMAP monitoring of seabird contaminants; include new contaminants and secure communication between seabird and contaminants research. Increase the level of understanding among the public of introducing hunting restrictions. Develop codes-of-conduct for more organised activities (e.g. tourism). Ensure that appropriate protection (national laws and international agreements) applies to new areas and times in cases of changes in seabird migration routes and times.", + "usetrade": null + } + ] + }, + "https://apiv3.iucnredlist.org/api/v3/species/narrative/id/181008073": { + "name": "181008073", + "result": [ + { + "species_id": 181008073, + "taxonomicnotes": "Three elephant taxa remain from the sixteen elephant-like species that are known from across the planet in the Pleistocene: Asian Elephant (Elephas maximus), African Savanna Elephant (Loxodonta africana), and African Forest Elephant (Loxodonta cyclotis) (Faurby and Svenning 2015, Malhi et al. 2016). The Asian and African ancestral lineages diverged approximately seven million years ago, and the African Savanna and African Forest ancestral lineages began diverging approximately one million years later (Rohland et al. 2010, Brandt et al. 2014, Roca et al. 2015, Meyer et al. 2017, Palkopoulou et al. 2018). The Third Edition of ‘Mammal Species of the World’ (Wilson and Reeder 2005) was the first to formally designate the African elephant as these two separate species. Recent genetic findings also support this designation (Roca et al. 2007, Ishida et al. 2011, Mondol et al. 2015, Palkopoulou et al. 2018, Kim and Wasser 2019). Hybridization between the two species appears restricted and evident at only 14 of the more than 100 localities recently examined across the vast forest-savanna ecotone. In nine of these 14 localities, hybrid individuals occurred alongside non-hybrid individuals of either one species or the other and not both (i.e., three localities had hybrids and African Forest Elephants only and assigned as this species; six localities had hybrids and African Savanna Elephants only and assigned as this species). For the IUCN Red List assessments, a distribution map published in Mondol et al. (2015) and recent data by Kim and Wasser (2019) are used to assign localities as range of either L. africana or L. cyclotis.", + "rationale": "

The African Savanna Elephant (Loxodonta africana) is assessed as Endangered A2abd. Analysis of estimates from 334 localities across their global range indicates a reduction of more than 50% of the continental population in the past three generations (75 years) that is understood to be continuing and likely irreversible. The continental trend is not, however, spatially uniform; some subpopulations are increasing or stable while others are declining significantly faster than the continental rate. Many local subpopulations have been extirpated.

A generation length (GL) of 25 years is used; calculated in the standard format as the average age of mothers in the population (IUCN SPC 2019, p. 29). This figure is based on analysis of the life table of culled Savanna Elephant family groups in South Africa (Whyte 2001) and a 14-year study of Savanna Elephants in Kenya (Wittemyer et al. 2013) which generated a range of 24.1–25 years.

Subcriterion A2 is applied because some of the major causes for population reduction, such as habitat loss due to human population expansion, have not ceased and are projected to increase in coming decades as well as unlikely to be reversible. The population reduction assessment for subcriterion A2 (considering three generations back) is inferred from published survey data using a modelling approach described in the Supplementary Information document. Density and distribution estimates for the African Savanna Elephant vary in methodology, completeness, regularity, date of first survey and confidence limits. Few credible estimates exist prior to the 1970s, and there is no credible estimate for the continental population more than two generations back. For this Red List assessment, an attempt was made to model the data three generations back to 1940 (see the attached Supplementary Information for description of data that is current as of and up to the end of 2015); however, given the sparseness of information available to inform the model, such modelling was of limited value. Therefore, rather than projecting declines well beyond the available data, we made the assumption that the continental African Savanna Elephant population of three generations back (1940) was equal to that of two generations back (i.e., 1965). Additional assumptions, necessary to fill gaps in the dataset, are detailed in the attached Supplementary Information document.

Subcriterion A3 has not been applied, because although the major threats to the species are known, projecting the level of such threats 25 or more years into the future (i.e., three generations, up to a maximum of 100 years) would likely introduce high levels of uncertainty.

An assessment of population reduction according to subcriterion A4 considering two generations back and one forward is in progress by this team of Assessors (Edwards et al. in prep.). Analysis of poaching and human influence in the recent past and anticipated in the future based upon available data of two representative indices (i.e., proportion of illegal killed elephants (PIKE) and the human footprint index) are being included as covariates in the projection.

Criteria B, C and D are not relevant to the threatened status as the species currently occupies more than 20,000 km2 and there are more than 10,000 mature individuals. No quantitative analysis of the probability of extinction in the wild was conducted, and therefore criterion E does not apply.


Previous Assessments of African Elephant:
This is the first assessment of the African Savanna Elephant (Loxodonta africana) as a species separate from the African Forest Elephant (L. cyclotis).

The African Elephant, as a single species, was listed as Vulnerable (VU A2a) in the 2004 and 2008 updates of the IUCN Red List of Threatened Species, under the same IUCN Categories and Criteria used in this assessment (Version 3.1; IUCN 2001).

Previously the African Elephant, as a single species, was listed as Endangered (EN A1b) under the IUCN Categories and Criteria Version 2.3 (IUCN 1994), in an assessment conducted in 1996 by the IUCN SSC African Elephant Specialist Group.

", + "geographicrange": "

African Savanna Elephants once occurred across all of Africa (Sikes 1971) and currently are found in 24 countries (see range map). Although knowledge of African Savanna Elephant distribution varies spatially and temporally, it is evident that the species’ distribution is retracting and becoming increasingly fragmented across their range in the continent. African Savanna Elephants occupy an estimated 15% of their historic pre- agricultural range (Chase et al. 2016). African Savanna Elephants are considered nationally extirpated in Burundi and Mauritania. In Eswatini, the once extirpated population has been re-established through reintroductions that began in the 1980s. Recent range expansion is evident in Kenya and Botswana (Douglas-Hamilton 1979; Said et al. 1995; Barnes et al. 1998; Blanc et al. 2003, 2007; Thouless et al. 2016).

The species' range map associated with this assessment is complete with the exception that some small private reserves in South Africa with less than 50 elephants each are absent as they were excluded from the African Elephant Database due to concerns about the census techniques.

", + "population": "

Over the past century, African Savanna Elephant subpopulations have declined across most of their range. The African Elephant Status Report 2016 estimated a continental population of 415,428 (+/- 95% C.I. 20,111) for both African Savanna and African Forest Elephants combined and reported a continental decline of approximately 111,000 elephants since 2006 (Thouless et al. 2016). For a similar time period, following a survey of approximately 90% of their range, a decline of 30% of African Savanna Elephants was reported (Chase et al. 2016).


For further information about this species, see the attached Supplementary Information document.

", + "populationtrend": "decreasing", + "habitat": "

African Savanna Elephants are found over a wide latitudinal range between the northern tropics in Mali (16° North) to the southern temperate zone (34° South) in South Africa. They occupy a variety of habitats ranging from montane forest, miombo and mopane woodland, thicket, savanna and grasslands to arid deserts and a wide altitudinal range from mountain slopes to oceanic beaches.

See also the list of habitats.

African Savanna Elephants are capable of moving long distances and naturally do so in arid ecosystems and in response to climatic conditions (e.g., seasonality and drought). Depending on productivity, and water availability African Savanna Elephants demonstrate range residence, migratory, semi-migratory and near nomadic movement patterns in different regions of the continent. Home range sizes vary by several orders of magnitude primarily in relation to plant productivity and human activity in different ecosystems (Loarie et al. 2009, Wall et al. 2013). Thirty African Savanna Elephant subpopulations (eight of which number more than 1,000 individuals) span international boundaries, including the more than 200,000 elephants in the five-country Kavango-Zambezi Transfrontier Conservation Area (Lindsay et al. 2017, KAZA Secretariat 2018).

Ecosystem Services:
Ecosystem services provided by African Savanna Elephants vary and depend to a large extent on the ecosystem (forest, savanna, grassland or desert), specific conditions on the ground and the geographical context under consideration. In general, they play an important ecological role as bulk processors of plant material (Owen-Smith 1989). In savannas, African Savanna Elephants are manipulators of woody vegetation structure which in turn influences the tree to grass ratio with knock-on consequences for the fire regime and potentially species composition in the area (Dublin et al. 1990, Augustine and McNaughton 2004, Palmer et al. 2008, Holdo et al. 2009, Goheen et al. 2010, Pringle et al. 2016). Where they are associated with wetlands African Savanna Elephants can be important in maintaining channels and open water ways. Soil fertility can be maintained by the transport of nutrients by large herbivores from nutrient rich sources (e.g., eutrophic soils in flood plains and termite mounds) to dystrophic soils (e.g., upland miombo woodlands) (Cumming et al. 1997, Doughty et al. 2016, Malhi et al. 2016).


The charismatic nature of African Savanna Elephants plays an important role in attracting tourism into national parks. They also hold important symbolic significance within the cultures of many African communities. African Savanna Elephants are one of the few iconic species that occur in the majority of African countries, and innumerable stories, songs, and cultural traditions revolve around them.

", + "threats": "Poaching of African Savanna Elephants for ivory is a major cause of individual death and population decline (Wittemyer et al. 2014, Thouless et al. 2016). After a sustained period of intense poaching between the late 1970s and 1989, many African Savanna Elephant populations (e.g., in Kenya, Tanzania, Zambia, Uganda) experienced two to three decades of recovery. Some northern African Savanna Elephant populations, however, experienced persistent poaching pressure through the last three decades (Bouche et al. 2011, 2012). Data collected as a part of the CITES Monitoring the Illegal Killing of Elephants programme (MIKE), indicate that poaching significantly intensified across the continent starting in 2008 and peaking in 2011 – an unsustainably high level of poaching has continued into current times in some areas of the continent (CITES 2018, 2019), and may be increasing in some of the historically less-affected southern African populations (CITES 2018, 2019). Rapid land use change by humans is driving the direct loss and fragmentation of habitat for African Savanna Elephants and is an increasing threat to populations across their range (Thouless et al. 2016, Mpakairi et al. 2019). Land conversion is a product of the ongoing expansion of the human population and associated agriculture and infrastructure development, which in turn are driven by economic and technological advances. A manifestation of this trend is the reported increase in human-elephant conflict (e.g., Pozo et al. 2018). Human population growth projections suggest land conversion will accelerate rapidly in the coming decades across Africa (see https://population.un.org/wpp/Publications/) which will likely increase this threat.", + "conservationmeasures": "

The African Savanna Elephant was listed in CITES Appendix I in 1989 when all African elephants were considered a single species. Subsequently, the populations of Botswana (1997), Namibia (1997), South Africa (2000) and Zimbabwe (1997) were transferred to Appendix II, each with specific annotations. These annotations have been recently replaced by a single annotation for all four countries, with specific sub-annotations for the populations of Namibia and Zimbabwe. A separate CITES listing for each species has not occurred yet because a formal designation as two separate species (i.e., Loxodonta africana and L. cyclotis) is still in progress.

The African Elephant Action Plan (AEAP; developed and adopted by African elephant range countries) was adopted by CITES in March 2010 and is a statement by all range countries regarding the most important and immediate activities which require implementation and funding if Africa’s elephants are to be conserved. The African Elephant Fund was established to support the implementation of the African Elephant Action Plan. It should be noted that the AEAP does not distinguish different taxa of African elephant. The African Savanna Elephants which occur in transboundary populations introduce complications (e.g., matters of sovereignty) when considering national populations for CITES purposes (Lindsay et al. 2017).

A number of CITES-initiated instruments were created to monitor and combat illegal trade in ivory. The CITES MIKE programme, which was established in 2002, has 66 designated sites across African elephant range and provides the most detailed and reliable data available on continental poaching pressure (CITES 2018, 2019); this includes 39 sites in 18 range countries of the African Savanna Elephant (https://cites.org/eng/prog/mike, Accessed 16 August 2020). Eswatini, South Sudan, Central African Republic, the Democratic Republic of the Congo and Somalia do not have MIKE sites for their African Savanna Elephants. The Elephant Trade Information System (ETIS), established in 1996, is managed by TRAFFIC as a comprehensive information system for tracking illegal trade in ivory and other elephant products (CITES 2016, 2019). At a national level National Ivory Action Plans (NIAPs) are a tool designed to track significant and timely action to combat the illegal trade in ivory. Currently 24 African, Middle Eastern, and Asian countries, as identified by ETIS analyses, are required to produce and implement a NIAP (https://cites.org/eng/niaps, Accessed 16 August 2020); 11 of these are range countries of the African Savanna Elephant.

At a national level the African Savanna Elephant is subject to varying degrees of legal protection in the 23 range states, with most countries granting the species the highest protection status. Providing this protection is complicated by the fact that over half of the species’ range may extend beyond the boundaries of protected areas (Taylor 2009). This point is emphasized by the evidence that the degree of protection is an important predictor of African Savanna Elephant’s presence and density (de Boer et al. 2013).

Conservation measures usually include habitat management and protection through policy, legislation and law enforcement. Successful anti-poaching and management at a site level has contributed to the re-establishment or recovery of a number of populations of African Savanna Elephants. In instances where protection efforts have failed, the African Savanna Elephant population was reduced by 70% or more in a matter of a decade (Chase et al. 2016).

In the context where management culling was undertaken (mainly southern Africa) up until the late 1980s or early 1990s, culling has largely ceased and population density management is being attempted through actions such as translocation, contraception and range manipulation such as closure of artificial water point (Scholes and Mennell 2008). Fire management is being attempted in some transboundary situations to mitigate further habitat and/or woodland modification as a consequence of African Savanna Elephant activity (Starfield et al. 1993, Mapaure 2013, Eastment 2020). 

", + "usetrade": "Ivory: Use of African Savanna Elephant ivory is entrenched in numerous cultures across the globe, primarily for ornamental and decorative items. Historically, demand for African Savanna Elephant ivory has been high in Europe, the USA and Asia. For example, beginning in the 1920s, Japanese carvers turned to African elephant ivory as Asian supplies diminished through the 1970s when Japan accounted for about 40% of the global ivory market (Martin 1986; Nishihara 2003, 2012).

In 1989, the Convention on International Trade of Endangered Species of Wild Flora and Fauna (CITES) banned the international commercial trade of ivory in response to a steep decline in African elephants across a substantial portion of their range. Thereafter, two CITES-sanctioned sales of national ivory stockpiles occurred in 2002 and 2008 with Botswana, Zimbabwe, Namibia and South Africa selling ivory to China and Japan. At the same time, a nine-year moratorium (ending in 2017) on any new ivory sale proposals from the four African countries followed (www.cites.org). In the 2000s, Chinese demand for ivory greatly surpassed that of Japan where demand for ivory appeared to substantially decline (CITES 2014). As a consequence, prices rose steeply in China and in Africa (Wittemyer et al. 2011, 2014). Debates about the benefits and consequences of the sale of national ivory stockpiles are highly polarized with limited consensus (Stiles 2004, 't Sas-Rolfes et al. 2014, Bennett 2015, Biggs et al. 2017).

Analysis of ivory seizure data indicates that the volume of illegally trafficked ivory has increased substantially since 2006 (Underwood et al. 2013; Milliken 2016; CITES 2018, 2019). Undercover investigations and DNA forensics point to the laundering of illegal ivory in legal domestic ivory markets. Seizure analyses indicate that the majority of illegally trafficked ivory is destined for Asia, especially China, Viet Nam and Thailand (CITES 2016, 2018 and 2019; Lui 2015; Krishnasamy 2016). In response to this and other concerns, China closed its legal domestic ivory market in 2017; Hong Kong SAR took steps to do the same by 2021 (https://www.info.gov.hk/gia/general/201706/02/P2017060100655.htm), and Thailand tightened its domestic Asian Elephant ivory trade regulations in 2015 (http://www.mfa.go.th/main/en/media-center/14/52929-Thailand-Submits-First-Progress-Report-on-Implemen.html). A substantial drop in ivory prices in mainland China has been associated with this action (Vigne and Martin 2017, Meijer et al. 2018). Significant illegal ivory markets remain in several Southeast Asian countries, such as Lao PDR and Viet Nam (www.cites.org; Vigne and Martin 2017).

Non-consumptive Tourism: 
African Savanna Elephants have significant tourism draw for wildlife watching and photographic tourism throughout their range substantially contributing to local economies (Naidoo et al. 2016). Tourism operations occur in national and local government protected areas, as well as on land under private or communal tenure.

Trophy Hunting: In some range states sport hunting of African Savanna Elephants is legally permitted and forms an integral element of those countries’ wildlife management and community support programs (e.g., Naidoo et al. 2016). Botswana, Mozambique, Namibia, South Africa, Tanzania and Zimbabwe have trophy hunting industries and in 2020 applied to CITES for a combined quota of 2,404 tusks (https://cites.org/eng/resources/quotas/index.php Accessed 16 August 2020). Depending on the governance and management structures, government bodies, private operations and local communities derive revenue from trophy hunting.

Other Trade: A legal domestic trade in African Savanna Elephant hides is present in South Africa. There have been limited reports of African Savanna Elephant poaching for other body parts beyond ivory, such as meat, bone, skin and hair (Blignaut et al. 2008). International live trade in African Savanna Elephants occurs between South Africa and other countries, mainly in an effort to re-establish or fortify dwindling populations in some protected areas. Live trade in young African Savanna Elephants from Zimbabwe (Russo and Cruise 2016) and Eswatini (Madowo 2019) to Asia and elsewhere for zoological collections also occurs. Controversy surrounds these transactions as the conservation benefit to the species has been questioned. Domestic trade in African Savanna Elephants exists in South Africa at a low level for reintroduction purposes between reserves (Pretorius et al. 2018)." + } + ] + }, + "https://apiv3.iucnredlist.org/api/v3/species/narrative/id/2467/region/europe": { + "name": "2467", + "region_identifier": "europe", + "result": [ + { + "species_id": 2467, + "taxonomicnotes": "The taxonomy is not in doubt. There are five traditionally recognised geographical populations. The species was once commonly known in the North Atlantic and adjacent Arctic as the Greenland right whale. However, the common name bowhead whale is now used almost exclusively for the species.", + "rationale": "This species is assessed as Not Applicable as it is of marginal occurrence in the European Mammal Assessment region.", + "geographicrange": "Bowhead whales are found only in Arctic and subarctic regions. They spend much of their lives in and near the pack ice, migrating to the high arctic in summer, and retreating southward in winter with the advancing ice edge (Moore and Reeves 1993). The IWC recognises five stocks: Bering-Chukchi-Beaufort Sea; Hudson Bay-Fox Basin; Davis Strait-Baffin Bay; Spitsbergen; and the Okhotsk Sea (Rugh et al. 2003). The Spitsbergen stock extends from the east coast of Greenland across the Greenland Sea, the Barents Sea and the Kara Sea as far as Severnaya Zemlya, and going as far south as the ice front, exceptionally reaching Iceland and the coast of Finnmark (Norway).", + "population": "Current population size
The range-wide abundance is not known with precision but numbers over 10,000 individuals, with 10,500 (8,200-13,500) (in 2001) in the Bering-Chukchi-Beaufort Seas (Zeh and Punt 2005), and a provisional estimate of 7,300 (3,100-16,900) for a part of the range of the Hudson Bay-Foxe Basin and Baffin Bay-Davis Strait stocks (Cosens et al. 2006). There are no reliable abundance estimates for the small Okhotsk Sea and Spitsbergen stocks.

Population trends
The Bering-Chukchi-Beaufort (BCB) population has been monitored for more than 30 years and has been increasing over this period at an estimated rate of 3.4% (1.7%-5%) per year in the presence of subsistence hunting (Zeh and Punt 2005). No quantitative estimates of trends in the other bowhead populations are available, but Inuit hunters and elders report that they are observing more bowheads in the eastern Canadian Arctic than they did in the 1960s-1970s, and that the geographic distribution of the whales has expanded in recent years. No estimates of population trend are available for the Svalbard-Barents Sea and Okhotsk Sea stocks.

Pre-whaling population sizes
All bowhead populations were severely depleted by commercial whaling, which was established in the north-eastern Atlantic by 1611 (Ross 1993). Basque whalers took bowheads in the north-west Atlantic (Labrador) in the 16th century, but ambiguities over the species identity of whales taken in early commercial whaling make pre-1600 catch records difficult to interpret. Minimum pre-whaling stock sizes are estimated to have been 24,000 for the Svalbard-Barents Sea stock, 12,000 for the Hudson Bay-Foxe Basin and Baffin Bay-Davis Strait stocks, and 3,000 for the Okhotsk Sea stock (Woodby and Botkin 1993). The Spitsbergen and Okhotsk Sea stocks are at a small fraction of their pre-whaling levels.

Demographic parameters
A high longevity (>100 years) is suggested by biochemical methods and the finding of old-fashioned stone harpoon heads in harvested animals (George et al. 1999). If this high longevity is confirmed, it would be among the longest known for a mammal.", + "populationtrend": "unknown", + "habitat": "The seasonal distribution is strongly influenced by pack ice (Moore and Reeves 1993). During the winter they occur in areas near the ice edge, in polynyas, and in areas of unconsolidated pack ice. During the spring these whales use leads and cracks in the ice to penetrate areas that were inaccessible during the winter due to heavy ice coverage. During the summer and autumn they concentrate in areas where zooplankton production is high or where large-scale biophysical processes create local concentrations of calanoid copepods (Finley 1990, Finley et al. 1998).

Small to medium-sized crustaceans, especially krill and copepods, form the bulk of the bowhead's diet (Lowry et al. 2004). They also feed on mysids and gammarid amphipods, and the diet includes at least 60 species. Bowheads skim feed at the surface and feed in the water column. It has recently been suggested that they also feed near the bottom, but probably do not directly ingest sediments as gray whales routinely do. During surface skim feeding, coordinated group patterns have been observed, including whales feeding in echelon (V-shaped) formation.", + "threats": "Heavy commercial hunting, beginning in the 1500s, depleted all populations of bowheads. The Bering-Chukchi-Beaufort Sea stock has recovered substantially since the end of commercial hunting in the early 20th century to around 10,000 animals, while recent provisional estimates of the Hudson Bay-Foxe Basin and Baffin Bay-Davis Strait stocks suggest that a significant recovery has probably occurred. There is no reliable evidence of recovery of the Svalbard-Barents Sea and Okhotsk Sea stocks.

Limited aboriginal subsistence whaling on the BCB stock (by native peoples of Alaska and Chukotka) is permitted by the IWC on the basis of advice from its Scientific Committee (most recently under its new aboriginal subsistence whaling management procedure). These takes have not impeded the recovery of the stock. Very small takes by indigenous hunters are allowed in Canadian waters, so far too few to seriously impede recovery of the stocks, but there will be pressure to increase these takes given the recent, higher population estimates for the eastern Canadian Arctic.

There has been concern since the 1970s that disturbance from oil and gas exploration and extraction activities in the Arctic region might affect bowhead whales. There is also evidence of incidental mortality and serious injury caused by entanglement in fishing gear and ship strikes (Philo et al. 1992, 1993; Finley 2000). Environmental threats, such as pollution (Bratton et al. 1993), and disturbance from tourist traffic (Finley 2000) may affect bowhead whales but the impacts have not yet been well characterized or quantified.

During this century, a profound reduction in the extent of sea ice in the Arctic is expected, and possibly a complete disappearance in summer, as mean Arctic temperatures rise faster than the global average (Anonymous 2005). The implications of this for bowhead whales are unclear but warrant monitoring.", + "conservationmeasures": "The International Whaling Commission has protected bowhead whales from commercial whaling since its inception in 1946. All range states except Canada are members of the IWC. Limited aboriginal subsistence whaling is allowed by the IWC on bowhead whales from the BCB stock on the basis of scientific advice. Indigenous hunting in Canada is co-managed by the national government and regional bodies created under land-claim agreements.", + "usetrade": null + } + ] + }, + "https://apiv3.iucnredlist.org/api/v3/species/synonym/Loxodonta%20africana": { + "name": "loxodonta africana", + "count": 3, + "result": [ + { + "accepted_id": 181007989, + "accepted_name": "Loxodonta cyclotis", + "authority": "Matschie, 1900", + "synonym": "Loxodonta africana", + "syn_authority": null + }, + { + "accepted_id": 181008073, + "accepted_name": "Loxodonta africana", + "authority": "(Blumenbach, 1797)", + "synonym": "Elephas africana", + "syn_authority": "Blumenbach, 1797" + }, + { + "accepted_id": 181008073, + "accepted_name": "Loxodonta africana", + "authority": "(Blumenbach, 1797)", + "synonym": "Loxodonta africana", + "syn_authority": null + } + ] + }, + "https://apiv3.iucnredlist.org/api/v3/speciescount": { + "count": "150754", + "note1": "Above total includes species, subspecies and subpopulation", + "speciescount": "147517", + "note2": "Above total includes species only" + }, + "https://apiv3.iucnredlist.org/api/v3/speciescount/region/europe": { + "count": "16232", + "note1": "Above total includes species, subspecies and subpopulation", + "speciescount": "16132", + "note2": "Above total includes species only", + "region_identifier": "europe" + }, + "https://apiv3.iucnredlist.org/api/v3/threats/species/id/181008073": { + "id": "181008073", + "result": [ + { + "code": "1.1", + "title": "Housing & urban areas", + "timing": "Ongoing", + "scope": "Minority (<50%)", + "severity": "Unknown", + "score": "Unknown", + "invasive": null + }, + { + "code": "11.2", + "title": "Droughts", + "timing": "Ongoing", + "scope": "Majority (50-90%)", + "severity": "Slow, Significant Declines", + "score": "Medium Impact: 6", + "invasive": null + }, + { + "code": "11.5", + "title": "Other impacts", + "timing": "Ongoing", + "scope": "Whole (>90%)", + "severity": "Unknown", + "score": "Unknown", + "invasive": null + }, + { + "code": "1.2", + "title": "Commercial & industrial areas", + "timing": "Ongoing", + "scope": "Minority (<50%)", + "severity": "Unknown", + "score": "Unknown", + "invasive": null + }, + { + "code": "1.3", + "title": "Tourism & recreation areas", + "timing": "Ongoing", + "scope": "Minority (<50%)", + "severity": "Unknown", + "score": "Unknown", + "invasive": null + }, + { + "code": "2.1", + "title": "Annual & perennial non-timber crops", + "timing": "Ongoing", + "scope": "Majority (50-90%)", + "severity": "Unknown", + "score": "Unknown", + "invasive": null + }, + { + "code": "2.1", + "title": "Annual & perennial non-timber crops", + "timing": "Ongoing", + "scope": "Minority (<50%)", + "severity": "Unknown", + "score": "Unknown", + "invasive": null + }, + { + "code": "2.1.1", + "title": "Shifting agriculture", + "timing": "Ongoing", + "scope": "Majority (50-90%)", + "severity": "Unknown", + "score": "Unknown", + "invasive": null + }, + { + "code": "2.1.2", + "title": "Small-holder farming", + "timing": "Ongoing", + "scope": "Minority (<50%)", + "severity": "Unknown", + "score": "Unknown", + "invasive": null + }, + { + "code": "2.1.3", + "title": "Agro-industry farming", + "timing": "Ongoing", + "scope": "Minority (<50%)", + "severity": "Unknown", + "score": "Unknown", + "invasive": null + }, + { + "code": "2.2", + "title": "Wood & pulp plantations", + "timing": "Ongoing", + "scope": "Minority (<50%)", + "severity": "Slow, Significant Declines", + "score": "Low Impact: 5", + "invasive": null + }, + { + "code": "2.2", + "title": "Wood & pulp plantations", + "timing": "Ongoing", + "scope": "Minority (<50%)", + "severity": "Unknown", + "score": "Unknown", + "invasive": null + }, + { + "code": "2.2.1", + "title": "Small-holder plantations", + "timing": "Ongoing", + "scope": "Minority (<50%)", + "severity": "Unknown", + "score": "Unknown", + "invasive": null + }, + { + "code": "2.2.2", + "title": "Agro-industry plantations", + "timing": "Ongoing", + "scope": "Minority (<50%)", + "severity": "Slow, Significant Declines", + "score": "Low Impact: 5", + "invasive": null + }, + { + "code": "2.3", + "title": "Livestock farming & ranching", + "timing": "Ongoing", + "scope": "Minority (<50%)", + "severity": "Causing/Could cause fluctuations", + "score": "Low Impact: 5", + "invasive": null + }, + { + "code": "2.3", + "title": "Livestock farming & ranching", + "timing": "Ongoing", + "scope": "Minority (<50%)", + "severity": "Slow, Significant Declines", + "score": "Low Impact: 5", + "invasive": null + }, + { + "code": "2.3.1", + "title": "Nomadic grazing", + "timing": "Ongoing", + "scope": "Minority (<50%)", + "severity": "Causing/Could cause fluctuations", + "score": "Low Impact: 5", + "invasive": null + }, + { + "code": "2.3.2", + "title": "Small-holder grazing, ranching or farming", + "timing": "Ongoing", + "scope": "Minority (<50%)", + "severity": "Slow, Significant Declines", + "score": "Low Impact: 5", + "invasive": null + }, + { + "code": "2.3.3", + "title": "Agro-industry grazing, ranching or farming", + "timing": "Ongoing", + "scope": "Minority (<50%)", + "severity": "Slow, Significant Declines", + "score": "Low Impact: 5", + "invasive": null + }, + { + "code": "3.1", + "title": "Oil & gas drilling", + "timing": "Ongoing", + "scope": "Minority (<50%)", + "severity": "Slow, Significant Declines", + "score": "Low Impact: 5", + "invasive": null + }, + { + "code": "3.2", + "title": "Mining & quarrying", + "timing": "Ongoing", + "scope": "Minority (<50%)", + "severity": "Negligible declines", + "score": "Low Impact: 4", + "invasive": null + }, + { + "code": "4.1", + "title": "Roads & railroads", + "timing": "Ongoing", + "scope": "Majority (50-90%)", + "severity": "Slow, Significant Declines", + "score": "Medium Impact: 6", + "invasive": null + }, + { + "code": "4.2", + "title": "Utility & service lines", + "timing": "Ongoing", + "scope": "Minority (<50%)", + "severity": "Causing/Could cause fluctuations", + "score": "Low Impact: 5", + "invasive": null + }, + { + "code": "5.1", + "title": "Hunting & trapping terrestrial animals", + "timing": "Ongoing", + "scope": "Majority (50-90%)", + "severity": "Negligible declines", + "score": "Low Impact: 5", + "invasive": null + }, + { + "code": "5.1", + "title": "Hunting & trapping terrestrial animals", + "timing": "Ongoing", + "scope": "Majority (50-90%)", + "severity": "Rapid Declines", + "score": "Medium Impact: 7", + "invasive": null + }, + { + "code": "5.1", + "title": "Hunting & trapping terrestrial animals", + "timing": "Ongoing", + "scope": "Majority (50-90%)", + "severity": "Slow, Significant Declines", + "score": "Medium Impact: 6", + "invasive": null + }, + { + "code": "5.1.1", + "title": "Intentional use (species is the target)", + "timing": "Ongoing", + "scope": "Majority (50-90%)", + "severity": "Rapid Declines", + "score": "Medium Impact: 7", + "invasive": null + }, + { + "code": "5.1.2", + "title": "Unintentional effects (species is not the target)", + "timing": "Ongoing", + "scope": "Majority (50-90%)", + "severity": "Slow, Significant Declines", + "score": "Medium Impact: 6", + "invasive": null + }, + { + "code": "5.1.3", + "title": "Persecution/control", + "timing": "Ongoing", + "scope": "Majority (50-90%)", + "severity": "Negligible declines", + "score": "Low Impact: 5", + "invasive": null + }, + { + "code": "5.3", + "title": "Logging & wood harvesting", + "timing": "Ongoing", + "scope": "Minority (<50%)", + "severity": "Unknown", + "score": "Unknown", + "invasive": null + }, + { + "code": "5.3.3", + "title": "Unintentional effects: (subsistence/small scale) [harvest]", + "timing": "Ongoing", + "scope": "Minority (<50%)", + "severity": "Unknown", + "score": "Unknown", + "invasive": null + }, + { + "code": "5.3.4", + "title": "Unintentional effects: (large scale) [harvest]", + "timing": "Ongoing", + "scope": "Minority (<50%)", + "severity": "Unknown", + "score": "Unknown", + "invasive": null + }, + { + "code": "6.1", + "title": "Recreational activities", + "timing": "Ongoing", + "scope": "Minority (<50%)", + "severity": "Unknown", + "score": "Unknown", + "invasive": null + }, + { + "code": "6.2", + "title": "War, civil unrest & military exercises", + "timing": "Ongoing", + "scope": "Minority (<50%)", + "severity": "Slow, Significant Declines", + "score": "Low Impact: 5", + "invasive": null + }, + { + "code": "7.1", + "title": "Fire & fire suppression", + "timing": "Ongoing", + "scope": "Minority (<50%)", + "severity": "Unknown", + "score": "Unknown", + "invasive": null + }, + { + "code": "7.1.3", + "title": "Trend Unknown/Unrecorded", + "timing": "Ongoing", + "scope": "Minority (<50%)", + "severity": "Unknown", + "score": "Unknown", + "invasive": null + }, + { + "code": "7.2", + "title": "Dams & water management/use", + "timing": "Ongoing", + "scope": "Minority (<50%)", + "severity": "Unknown", + "score": "Unknown", + "invasive": null + }, + { + "code": "7.2.11", + "title": "Dams (size unknown)", + "timing": "Ongoing", + "scope": "Minority (<50%)", + "severity": "Unknown", + "score": "Unknown", + "invasive": null + }, + { + "code": "8.1", + "title": "Invasive non-native/alien species/diseases", + "timing": "Ongoing", + "scope": null, + "severity": "Unknown", + "score": "Unknown", + "invasive": null + }, + { + "code": "8.1.1", + "title": "Unspecified species", + "timing": "Ongoing", + "scope": null, + "severity": "Unknown", + "score": "Unknown", + "invasive": null + } + ] + }, + "https://apiv3.iucnredlist.org/api/v3/threats/species/id/2467/region/europe": { + "id": "2467", + "region_identifier": "europe", + "result": [ + { + "code": "9.2", + "title": "Industrial & military effluents", + "timing": "Ongoing", + "scope": null, + "severity": null, + "score": null, + "invasive": null + }, + { + "code": "9.2.3", + "title": "Type Unknown/Unrecorded", + "timing": "Ongoing", + "scope": null, + "severity": null, + "score": null, + "invasive": null + } + ] + }, + "https://apiv3.iucnredlist.org/api/v3/threats/species/name/Loxodonta%20africana": { + "name": "Loxodonta africana", + "result": [ + { + "code": "1.1", + "title": "Housing & urban areas", + "timing": "Ongoing", + "scope": "Minority (<50%)", + "severity": "Unknown", + "score": "Unknown", + "invasive": null + }, + { + "code": "11.2", + "title": "Droughts", + "timing": "Ongoing", + "scope": "Majority (50-90%)", + "severity": "Slow, Significant Declines", + "score": "Medium Impact: 6", + "invasive": null + }, + { + "code": "11.5", + "title": "Other impacts", + "timing": "Ongoing", + "scope": "Whole (>90%)", + "severity": "Unknown", + "score": "Unknown", + "invasive": null + }, + { + "code": "1.2", + "title": "Commercial & industrial areas", + "timing": "Ongoing", + "scope": "Minority (<50%)", + "severity": "Unknown", + "score": "Unknown", + "invasive": null + }, + { + "code": "1.3", + "title": "Tourism & recreation areas", + "timing": "Ongoing", + "scope": "Minority (<50%)", + "severity": "Unknown", + "score": "Unknown", + "invasive": null + }, + { + "code": "2.1", + "title": "Annual & perennial non-timber crops", + "timing": "Ongoing", + "scope": "Majority (50-90%)", + "severity": "Unknown", + "score": "Unknown", + "invasive": null + }, + { + "code": "2.1", + "title": "Annual & perennial non-timber crops", + "timing": "Ongoing", + "scope": "Minority (<50%)", + "severity": "Unknown", + "score": "Unknown", + "invasive": null + }, + { + "code": "2.1.1", + "title": "Shifting agriculture", + "timing": "Ongoing", + "scope": "Majority (50-90%)", + "severity": "Unknown", + "score": "Unknown", + "invasive": null + }, + { + "code": "2.1.2", + "title": "Small-holder farming", + "timing": "Ongoing", + "scope": "Minority (<50%)", + "severity": "Unknown", + "score": "Unknown", + "invasive": null + }, + { + "code": "2.1.3", + "title": "Agro-industry farming", + "timing": "Ongoing", + "scope": "Minority (<50%)", + "severity": "Unknown", + "score": "Unknown", + "invasive": null + }, + { + "code": "2.2", + "title": "Wood & pulp plantations", + "timing": "Ongoing", + "scope": "Minority (<50%)", + "severity": "Slow, Significant Declines", + "score": "Low Impact: 5", + "invasive": null + }, + { + "code": "2.2", + "title": "Wood & pulp plantations", + "timing": "Ongoing", + "scope": "Minority (<50%)", + "severity": "Unknown", + "score": "Unknown", + "invasive": null + }, + { + "code": "2.2.1", + "title": "Small-holder plantations", + "timing": "Ongoing", + "scope": "Minority (<50%)", + "severity": "Unknown", + "score": "Unknown", + "invasive": null + }, + { + "code": "2.2.2", + "title": "Agro-industry plantations", + "timing": "Ongoing", + "scope": "Minority (<50%)", + "severity": "Slow, Significant Declines", + "score": "Low Impact: 5", + "invasive": null + }, + { + "code": "2.3", + "title": "Livestock farming & ranching", + "timing": "Ongoing", + "scope": "Minority (<50%)", + "severity": "Causing/Could cause fluctuations", + "score": "Low Impact: 5", + "invasive": null + }, + { + "code": "2.3", + "title": "Livestock farming & ranching", + "timing": "Ongoing", + "scope": "Minority (<50%)", + "severity": "Slow, Significant Declines", + "score": "Low Impact: 5", + "invasive": null + }, + { + "code": "2.3.1", + "title": "Nomadic grazing", + "timing": "Ongoing", + "scope": "Minority (<50%)", + "severity": "Causing/Could cause fluctuations", + "score": "Low Impact: 5", + "invasive": null + }, + { + "code": "2.3.2", + "title": "Small-holder grazing, ranching or farming", + "timing": "Ongoing", + "scope": "Minority (<50%)", + "severity": "Slow, Significant Declines", + "score": "Low Impact: 5", + "invasive": null + }, + { + "code": "2.3.3", + "title": "Agro-industry grazing, ranching or farming", + "timing": "Ongoing", + "scope": "Minority (<50%)", + "severity": "Slow, Significant Declines", + "score": "Low Impact: 5", + "invasive": null + }, + { + "code": "3.1", + "title": "Oil & gas drilling", + "timing": "Ongoing", + "scope": "Minority (<50%)", + "severity": "Slow, Significant Declines", + "score": "Low Impact: 5", + "invasive": null + }, + { + "code": "3.2", + "title": "Mining & quarrying", + "timing": "Ongoing", + "scope": "Minority (<50%)", + "severity": "Negligible declines", + "score": "Low Impact: 4", + "invasive": null + }, + { + "code": "4.1", + "title": "Roads & railroads", + "timing": "Ongoing", + "scope": "Majority (50-90%)", + "severity": "Slow, Significant Declines", + "score": "Medium Impact: 6", + "invasive": null + }, + { + "code": "4.2", + "title": "Utility & service lines", + "timing": "Ongoing", + "scope": "Minority (<50%)", + "severity": "Causing/Could cause fluctuations", + "score": "Low Impact: 5", + "invasive": null + }, + { + "code": "5.1", + "title": "Hunting & trapping terrestrial animals", + "timing": "Ongoing", + "scope": "Majority (50-90%)", + "severity": "Negligible declines", + "score": "Low Impact: 5", + "invasive": null + }, + { + "code": "5.1", + "title": "Hunting & trapping terrestrial animals", + "timing": "Ongoing", + "scope": "Majority (50-90%)", + "severity": "Rapid Declines", + "score": "Medium Impact: 7", + "invasive": null + }, + { + "code": "5.1", + "title": "Hunting & trapping terrestrial animals", + "timing": "Ongoing", + "scope": "Majority (50-90%)", + "severity": "Slow, Significant Declines", + "score": "Medium Impact: 6", + "invasive": null + }, + { + "code": "5.1.1", + "title": "Intentional use (species is the target)", + "timing": "Ongoing", + "scope": "Majority (50-90%)", + "severity": "Rapid Declines", + "score": "Medium Impact: 7", + "invasive": null + }, + { + "code": "5.1.2", + "title": "Unintentional effects (species is not the target)", + "timing": "Ongoing", + "scope": "Majority (50-90%)", + "severity": "Slow, Significant Declines", + "score": "Medium Impact: 6", + "invasive": null + }, + { + "code": "5.1.3", + "title": "Persecution/control", + "timing": "Ongoing", + "scope": "Majority (50-90%)", + "severity": "Negligible declines", + "score": "Low Impact: 5", + "invasive": null + }, + { + "code": "5.3", + "title": "Logging & wood harvesting", + "timing": "Ongoing", + "scope": "Minority (<50%)", + "severity": "Unknown", + "score": "Unknown", + "invasive": null + }, + { + "code": "5.3.3", + "title": "Unintentional effects: (subsistence/small scale) [harvest]", + "timing": "Ongoing", + "scope": "Minority (<50%)", + "severity": "Unknown", + "score": "Unknown", + "invasive": null + }, + { + "code": "5.3.4", + "title": "Unintentional effects: (large scale) [harvest]", + "timing": "Ongoing", + "scope": "Minority (<50%)", + "severity": "Unknown", + "score": "Unknown", + "invasive": null + }, + { + "code": "6.1", + "title": "Recreational activities", + "timing": "Ongoing", + "scope": "Minority (<50%)", + "severity": "Unknown", + "score": "Unknown", + "invasive": null + }, + { + "code": "6.2", + "title": "War, civil unrest & military exercises", + "timing": "Ongoing", + "scope": "Minority (<50%)", + "severity": "Slow, Significant Declines", + "score": "Low Impact: 5", + "invasive": null + }, + { + "code": "7.1", + "title": "Fire & fire suppression", + "timing": "Ongoing", + "scope": "Minority (<50%)", + "severity": "Unknown", + "score": "Unknown", + "invasive": null + }, + { + "code": "7.1.3", + "title": "Trend Unknown/Unrecorded", + "timing": "Ongoing", + "scope": "Minority (<50%)", + "severity": "Unknown", + "score": "Unknown", + "invasive": null + }, + { + "code": "7.2", + "title": "Dams & water management/use", + "timing": "Ongoing", + "scope": "Minority (<50%)", + "severity": "Unknown", + "score": "Unknown", + "invasive": null + }, + { + "code": "7.2.11", + "title": "Dams (size unknown)", + "timing": "Ongoing", + "scope": "Minority (<50%)", + "severity": "Unknown", + "score": "Unknown", + "invasive": null + }, + { + "code": "8.1", + "title": "Invasive non-native/alien species/diseases", + "timing": "Ongoing", + "scope": null, + "severity": "Unknown", + "score": "Unknown", + "invasive": null + }, + { + "code": "8.1.1", + "title": "Unspecified species", + "timing": "Ongoing", + "scope": null, + "severity": "Unknown", + "score": "Unknown", + "invasive": null + } + ] + }, + "https://apiv3.iucnredlist.org/api/v3/threats/species/name/Fratercula%20arctica/region/europe": { + "name": "Fratercula arctica", + "region_identifier": "europe", + "result": [ + { + "code": "11.1", + "title": "Habitat shifting & alteration", + "timing": "Ongoing", + "scope": "Unknown", + "severity": "Unknown", + "score": "Unknown", + "invasive": null + }, + { + "code": "11.3", + "title": "Temperature extremes", + "timing": "Ongoing", + "scope": "Unknown", + "severity": "Unknown", + "score": "Unknown", + "invasive": null + }, + { + "code": "11.4", + "title": "Storms & flooding", + "timing": "Ongoing", + "scope": "Unknown", + "severity": "Rapid Declines", + "score": "Unknown", + "invasive": null + }, + { + "code": "11.5", + "title": "Other impacts", + "timing": "Ongoing", + "scope": "Unknown", + "severity": "Unknown", + "score": "Unknown", + "invasive": null + }, + { + "code": "3.3", + "title": "Renewable energy", + "timing": "Ongoing", + "scope": "Minority (<50%)", + "severity": "Causing/Could cause fluctuations", + "score": "Low Impact: 5", + "invasive": null + }, + { + "code": "5.1", + "title": "Hunting & trapping terrestrial animals", + "timing": "Ongoing", + "scope": "Minority (<50%)", + "severity": "Slow, Significant Declines", + "score": "Low Impact: 5", + "invasive": null + }, + { + "code": "5.1.1", + "title": "Intentional use (species is the target)", + "timing": "Ongoing", + "scope": "Minority (<50%)", + "severity": "Slow, Significant Declines", + "score": "Low Impact: 5", + "invasive": null + }, + { + "code": "5.4", + "title": "Fishing & harvesting aquatic resources", + "timing": "Ongoing", + "scope": "Unknown", + "severity": "Rapid Declines", + "score": "Unknown", + "invasive": null + }, + { + "code": "5.4.4", + "title": "Unintentional effects: (large scale) [harvest]", + "timing": "Ongoing", + "scope": "Unknown", + "severity": "Rapid Declines", + "score": "Unknown", + "invasive": null + }, + { + "code": "8.1", + "title": "Invasive non-native/alien species/diseases (Neovison vison)", + "timing": "Ongoing", + "scope": "Majority (50-90%)", + "severity": "Slow, Significant Declines", + "score": "Medium Impact: 6", + "invasive": "Neovison vison" + }, + { + "code": "8.1", + "title": "Invasive non-native/alien species/diseases (Unspecified Rattus)", + "timing": "Ongoing", + "scope": "Majority (50-90%)", + "severity": "Slow, Significant Declines", + "score": "Medium Impact: 6", + "invasive": "Unspecified Rattus" + }, + { + "code": "8.1.2", + "title": "Named species (Neovison vison)", + "timing": "Ongoing", + "scope": "Majority (50-90%)", + "severity": "Slow, Significant Declines", + "score": "Medium Impact: 6", + "invasive": "Neovison vison" + }, + { + "code": "8.1.2", + "title": "Named species (Unspecified Rattus)", + "timing": "Ongoing", + "scope": "Majority (50-90%)", + "severity": "Slow, Significant Declines", + "score": "Medium Impact: 6", + "invasive": "Unspecified Rattus" + }, + { + "code": "9.2", + "title": "Industrial & military effluents", + "timing": "Ongoing", + "scope": "Unknown", + "severity": "Unknown", + "score": "Unknown", + "invasive": null + }, + { + "code": "9.2", + "title": "Industrial & military effluents", + "timing": "Past, Likely to Return", + "scope": "Unknown", + "severity": "Rapid Declines", + "score": "Past Impact", + "invasive": null + }, + { + "code": "9.2.1", + "title": "Oil spills", + "timing": "Past, Likely to Return", + "scope": "Unknown", + "severity": "Rapid Declines", + "score": "Past Impact", + "invasive": null + }, + { + "code": "9.2.3", + "title": "Type Unknown/Unrecorded", + "timing": "Ongoing", + "scope": "Unknown", + "severity": "Unknown", + "score": "Unknown", + "invasive": null + } + ] + }, + "https://apiv3.iucnredlist.org/api/v3/version": { + "version": "2022-1" + }, + "https://apiv3.iucnredlist.org/api/v3/weblink/Loxodonta%20africana": { + "rlurl": "https://www.iucnredlist.org/species/181008073/204401095", + "species": "loxodonta africana" + } +} diff --git a/sites/all/libraries/vendor/jbroutier/iucn-api-client/tests/Unit/AssessmentTest.php b/sites/all/libraries/vendor/jbroutier/iucn-api-client/tests/Unit/AssessmentTest.php new file mode 100644 index 0000000000..de04ddb72f --- /dev/null +++ b/sites/all/libraries/vendor/jbroutier/iucn-api-client/tests/Unit/AssessmentTest.php @@ -0,0 +1,24 @@ + 2021, + 'code' => 'VU', + 'category' => 'Vulnerable', + 'year' => 2022, + ]); + + self::assertEquals(2021, $assessment->getAssessmentYear()); + self::assertEquals('VU', $assessment->getCategoryCode()); + self::assertEquals('Vulnerable', $assessment->getCategoryName()); + self::assertEquals(2022, $assessment->getPublicationYear()); + } +} diff --git a/sites/all/libraries/vendor/jbroutier/iucn-api-client/tests/Unit/CitationTest.php b/sites/all/libraries/vendor/jbroutier/iucn-api-client/tests/Unit/CitationTest.php new file mode 100644 index 0000000000..1d58eccf3c --- /dev/null +++ b/sites/all/libraries/vendor/jbroutier/iucn-api-client/tests/Unit/CitationTest.php @@ -0,0 +1,18 @@ + 'Lorem ipsum dolor sit amet' + ]); + + self::assertEquals('Lorem ipsum dolor sit amet', $citation->getCitation()); + } +} diff --git a/sites/all/libraries/vendor/jbroutier/iucn-api-client/tests/Unit/CommonNameTest.php b/sites/all/libraries/vendor/jbroutier/iucn-api-client/tests/Unit/CommonNameTest.php new file mode 100644 index 0000000000..c6f35a1344 --- /dev/null +++ b/sites/all/libraries/vendor/jbroutier/iucn-api-client/tests/Unit/CommonNameTest.php @@ -0,0 +1,22 @@ + 'eng', + 'taxonname' => 'Red panda', + 'primary' => true, + ]); + + self::assertEquals('eng', $commonName->getLanguage()); + self::assertEquals('Red panda', $commonName->getName()); + self::assertTrue($commonName->isPrimary()); + } +} diff --git a/sites/all/libraries/vendor/jbroutier/iucn-api-client/tests/Unit/ConservationMeasureTest.php b/sites/all/libraries/vendor/jbroutier/iucn-api-client/tests/Unit/ConservationMeasureTest.php new file mode 100644 index 0000000000..56d0f183a2 --- /dev/null +++ b/sites/all/libraries/vendor/jbroutier/iucn-api-client/tests/Unit/ConservationMeasureTest.php @@ -0,0 +1,20 @@ + '4.2', + 'title' => 'Lorem ipsum dolor sit amet', + ]); + + self::assertEquals('4.2', $conservationMeasure->getCode()); + self::assertEquals('Lorem ipsum dolor sit amet', $conservationMeasure->getTitle()); + } +} diff --git a/sites/all/libraries/vendor/jbroutier/iucn-api-client/tests/Unit/CountryTest.php b/sites/all/libraries/vendor/jbroutier/iucn-api-client/tests/Unit/CountryTest.php new file mode 100644 index 0000000000..f7a8dfac0a --- /dev/null +++ b/sites/all/libraries/vendor/jbroutier/iucn-api-client/tests/Unit/CountryTest.php @@ -0,0 +1,20 @@ + 'DE', + 'country' => 'Germany', + ]); + + self::assertEquals('DE', $country->getCode()); + self::assertEquals('Germany', $country->getName()); + } +} diff --git a/sites/all/libraries/vendor/jbroutier/iucn-api-client/tests/Unit/GroupTest.php b/sites/all/libraries/vendor/jbroutier/iucn-api-client/tests/Unit/GroupTest.php new file mode 100644 index 0000000000..725f80ed8c --- /dev/null +++ b/sites/all/libraries/vendor/jbroutier/iucn-api-client/tests/Unit/GroupTest.php @@ -0,0 +1,18 @@ + 'mammals', + ]); + + self::assertEquals('mammals', $group->getCode()); + } +} diff --git a/sites/all/libraries/vendor/jbroutier/iucn-api-client/tests/Unit/GrowthFormTest.php b/sites/all/libraries/vendor/jbroutier/iucn-api-client/tests/Unit/GrowthFormTest.php new file mode 100644 index 0000000000..526bf35dcf --- /dev/null +++ b/sites/all/libraries/vendor/jbroutier/iucn-api-client/tests/Unit/GrowthFormTest.php @@ -0,0 +1,18 @@ + 'Tree - large', + ]); + + self::assertEquals('Tree - large', $growthForm->getName()); + } +} diff --git a/sites/all/libraries/vendor/jbroutier/iucn-api-client/tests/Unit/HabitatTest.php b/sites/all/libraries/vendor/jbroutier/iucn-api-client/tests/Unit/HabitatTest.php new file mode 100644 index 0000000000..30cf3d713d --- /dev/null +++ b/sites/all/libraries/vendor/jbroutier/iucn-api-client/tests/Unit/HabitatTest.php @@ -0,0 +1,26 @@ + '12.1', + 'habitat' => 'Marine Intertidal - Rocky Shoreline', + 'majorimportance' => 'Yes', + 'season' => 'Summer', + 'suitability' => 'Suitable', + ]); + + self::assertEquals('12.1', $habitat->getCode()); + self::assertEquals('Yes', $habitat->getMajorImportance()); + self::assertEquals('Marine Intertidal - Rocky Shoreline', $habitat->getName()); + self::assertEquals('Summer', $habitat->getSeason()); + self::assertEquals('Suitable', $habitat->getSuitability()); + } +} diff --git a/sites/all/libraries/vendor/jbroutier/iucn-api-client/tests/Unit/NarrativeTextTest.php b/sites/all/libraries/vendor/jbroutier/iucn-api-client/tests/Unit/NarrativeTextTest.php new file mode 100644 index 0000000000..b7aa0cfe93 --- /dev/null +++ b/sites/all/libraries/vendor/jbroutier/iucn-api-client/tests/Unit/NarrativeTextTest.php @@ -0,0 +1,34 @@ + 'Nullam ut odio nec', + 'geographicrange' => 'Pellentesque luctus ante', + 'habitat' => 'Mauris sagittis orci', + 'population' => 'In dictum enim eget', + 'populationtrend' => 'Vivamus a sapien a tortor', + 'rationale' => 'In dictum enim eget mauris', + 'taxonomicnotes' => 'Maecenas pretium augue', + 'threats' => 'Donec vitae elit laoreet', + 'usetrade' => 'Cras feugiat diam', + ]); + + self::assertEquals('Nullam ut odio nec', $narrativeText->getConservationMeasures()); + self::assertEquals('Pellentesque luctus ante', $narrativeText->getGeographicRange()); + self::assertEquals('Mauris sagittis orci', $narrativeText->getHabitat()); + self::assertEquals('In dictum enim eget', $narrativeText->getPopulation()); + self::assertEquals('Vivamus a sapien a tortor', $narrativeText->getPopulationTrend()); + self::assertEquals('In dictum enim eget mauris', $narrativeText->getRationale()); + self::assertEquals('Maecenas pretium augue', $narrativeText->getTaxonomicNotes()); + self::assertEquals('Donec vitae elit laoreet', $narrativeText->getThreats()); + self::assertEquals('Cras feugiat diam', $narrativeText->getUseTrade()); + } +} diff --git a/sites/all/libraries/vendor/jbroutier/iucn-api-client/tests/Unit/OccurrenceTest.php b/sites/all/libraries/vendor/jbroutier/iucn-api-client/tests/Unit/OccurrenceTest.php new file mode 100644 index 0000000000..9d0db86934 --- /dev/null +++ b/sites/all/libraries/vendor/jbroutier/iucn-api-client/tests/Unit/OccurrenceTest.php @@ -0,0 +1,26 @@ + 'BI', + 'country' => 'Burundi', + 'distribution_code' => 'Regionally Extinct', + 'origin' => 'Native', + 'presence' => 'Regionally Extinct', + ]); + + self::assertEquals('BI', $occurrence->getCountryCode()); + self::assertEquals('Burundi', $occurrence->getCountryName()); + self::assertEquals('Regionally Extinct', $occurrence->getDistributionCode()); + self::assertEquals('Native', $occurrence->getOrigin()); + self::assertEquals('Regionally Extinct', $occurrence->getPresence()); + } +} diff --git a/sites/all/libraries/vendor/jbroutier/iucn-api-client/tests/Unit/RegionTest.php b/sites/all/libraries/vendor/jbroutier/iucn-api-client/tests/Unit/RegionTest.php new file mode 100644 index 0000000000..269086f71f --- /dev/null +++ b/sites/all/libraries/vendor/jbroutier/iucn-api-client/tests/Unit/RegionTest.php @@ -0,0 +1,20 @@ + 'europe', + 'name' => 'Europe', + ]); + + self::assertEquals('europe', $region->getIdentifier()); + self::assertEquals('Europe', $region->getName()); + } +} diff --git a/sites/all/libraries/vendor/jbroutier/iucn-api-client/tests/Unit/SpeciesDetailsTest.php b/sites/all/libraries/vendor/jbroutier/iucn-api-client/tests/Unit/SpeciesDetailsTest.php new file mode 100644 index 0000000000..55d3381693 --- /dev/null +++ b/sites/all/libraries/vendor/jbroutier/iucn-api-client/tests/Unit/SpeciesDetailsTest.php @@ -0,0 +1,80 @@ + true, + 'amended_reason' => '', + 'aoo_km2' => 24000, + 'assessment_date' => '2020-11-13', + 'assessor' => 'John Doe', + 'authority' => '(Blumenbach, 1797)', + 'category' => 'EN', + 'class' => 'MAMMALIA', + 'criteria' => 'A2abd', + 'depth_lower' => 5000, + 'depth_upper' => 0, + 'elevation_lower' => 100, + 'elevation_upper' => 3200, + 'eoo_km2' => 48250, + 'errata_flag' => true, + 'errata_reason' => 'Vestibulum eget augue tempor', + 'family' => 'ELEPHANTIDAE', + 'freshwater_system' => true, + 'genus' => 'Loxodonta', + 'kingdom' => 'ANIMALIA', + 'main_common_name' => 'African Savanna Elephant', + 'marine_system' => true, + 'order' => 'PROBOSCIDEA', + 'phylum' => 'CHORDATA', + 'population_trend' => 'Decreasing', + 'published_year' => 2022, + 'reviewer' => 'Jane Doe', + 'scientific_name' => 'Loxodonta africana', + 'taxonid' => 181008073, + 'terrestrial_system' => true, + ]); + + self::assertTrue($speciesDetails->isAmended()); + self::assertEquals('', $speciesDetails->getAmendedReason()); + self::assertEquals(24000, $speciesDetails->getAOO()); + self::assertEquals('2020-11-13', $speciesDetails->getAssessmentDate()); + self::assertEquals('John Doe', $speciesDetails->getAssessor()); + self::assertEquals('(Blumenbach, 1797)', $speciesDetails->getAuthority()); + self::assertEquals('EN', $speciesDetails->getCategory()); + self::assertEquals('MAMMALIA', $speciesDetails->getClass()); + self::assertEquals('A2abd', $speciesDetails->getCriteria()); + self::assertEquals(5000, $speciesDetails->getDepthLower()); + self::assertEquals(0, $speciesDetails->getDepthUpper()); + self::assertEquals(100, $speciesDetails->getElevationLower()); + self::assertEquals(3200, $speciesDetails->getElevationUpper()); + self::assertEquals(48250, $speciesDetails->getEOO()); + self::assertTrue($speciesDetails->isErrata()); + self::assertEquals('Vestibulum eget augue tempor', $speciesDetails->getErrataReason()); + self::assertEquals('ELEPHANTIDAE', $speciesDetails->getFamily()); + self::assertTrue($speciesDetails->isFreshwaterSystem()); + self::assertEquals('Loxodonta', $speciesDetails->getGenus()); + self::assertEquals('ANIMALIA', $speciesDetails->getKingdom()); + self::assertEquals('African Savanna Elephant', $speciesDetails->getMainCommonName()); + self::assertTrue($speciesDetails->isMarineSystem()); + self::assertEquals('PROBOSCIDEA', $speciesDetails->getOrder()); + self::assertEquals('CHORDATA', $speciesDetails->getPhylum()); + self::assertEquals('Decreasing', $speciesDetails->getPopulationTrend()); + self::assertEquals(2022, $speciesDetails->getPublicationYear()); + self::assertEquals('Jane Doe', $speciesDetails->getReviewer()); + self::assertEquals('Loxodonta africana', $speciesDetails->getScientificName()); + self::assertTrue($speciesDetails->isTerrestrialSystem()); + self::assertEquals(181008073, $speciesDetails->getTaxonId()); + } +} diff --git a/sites/all/libraries/vendor/jbroutier/iucn-api-client/tests/Unit/SpeciesTest.php b/sites/all/libraries/vendor/jbroutier/iucn-api-client/tests/Unit/SpeciesTest.php new file mode 100644 index 0000000000..7ba7421d97 --- /dev/null +++ b/sites/all/libraries/vendor/jbroutier/iucn-api-client/tests/Unit/SpeciesTest.php @@ -0,0 +1,28 @@ + 'LR/lc', + 'scientific_name' => 'Agarista mexicana var. pinetorum', + 'subpopulation' => 'nothingii', + 'subspecies' => 'pinetorum', + 'rank' => 'var.', + 'taxonid' => 37432, + ]); + + self::assertEquals('LR/lc', $species->getCategory()); + self::assertEquals('Agarista mexicana var. pinetorum', $species->getScientificName()); + self::assertEquals('nothingii', $species->getSubpopulation()); + self::assertEquals('pinetorum', $species->getSubspeciesName()); + self::assertEquals('var.', $species->getSubspeciesRank()); + self::assertEquals(37432, $species->getTaxonId()); + } +} diff --git a/sites/all/libraries/vendor/jbroutier/iucn-api-client/tests/Unit/SynonymTest.php b/sites/all/libraries/vendor/jbroutier/iucn-api-client/tests/Unit/SynonymTest.php new file mode 100644 index 0000000000..e6656b1e34 --- /dev/null +++ b/sites/all/libraries/vendor/jbroutier/iucn-api-client/tests/Unit/SynonymTest.php @@ -0,0 +1,26 @@ + 181008073, + 'accepted_name' => 'Loxodonta africana', + 'authority' => '(Blumenbach, 1797)', + 'synonym' => 'Elephas africana', + 'syn_authority' => 'Blumenbach, 1797', + ]); + + self::assertEquals(181008073, $synonym->getAcceptedId()); + self::assertEquals('Loxodonta africana', $synonym->getAcceptedName()); + self::assertEquals('(Blumenbach, 1797)', $synonym->getAcceptedNameAuthority()); + self::assertEquals('Elephas africana', $synonym->getSynonym()); + self::assertEquals('Blumenbach, 1797', $synonym->getSynonymAuthority()); + } +} diff --git a/sites/all/libraries/vendor/jbroutier/iucn-api-client/tests/Unit/ThreatTest.php b/sites/all/libraries/vendor/jbroutier/iucn-api-client/tests/Unit/ThreatTest.php new file mode 100644 index 0000000000..dcfb394789 --- /dev/null +++ b/sites/all/libraries/vendor/jbroutier/iucn-api-client/tests/Unit/ThreatTest.php @@ -0,0 +1,30 @@ + '5.1', + 'invasive' => 'Homo sapiens', + 'scope' => 'Majority (50-90%)', + 'score' => 'Low Impact: 5', + 'severity' => 'Negligible declines', + 'timing' => 'Ongoing', + 'title' => 'Hunting & trapping terrestrial animals', + ]); + + self::assertEquals('5.1', $threat->getCode()); + self::assertEquals('Homo sapiens', $threat->getInvasive()); + self::assertEquals('Majority (50-90%)', $threat->getScope()); + self::assertEquals('Low Impact: 5', $threat->getScore()); + self::assertEquals('Negligible declines', $threat->getSeverity()); + self::assertEquals('Ongoing', $threat->getTiming()); + self::assertEquals('Hunting & trapping terrestrial animals', $threat->getTitle()); + } +} diff --git a/sites/all/modules/custom/scratchpads/scratchpads_species/modules/iucn/iucn.module b/sites/all/modules/custom/scratchpads/scratchpads_species/modules/iucn/iucn.module index 15c31b60ea..875d8bd0a9 100644 --- a/sites/all/modules/custom/scratchpads/scratchpads_species/modules/iucn/iucn.module +++ b/sites/all/modules/custom/scratchpads/scratchpads_species/modules/iucn/iucn.module @@ -62,6 +62,36 @@ function iucn_block_view($delta = ''){ if($cache->data){ $content['content']['#markup'] = $cache->data; }else{ + + try { + $aterm = $term->name; + $species = $iucnClient->getSpeciesByName($term->name); + + $contentMarkupToDisplay =""; + + // credit: https://github.com/jbroutier/iucn-api-client#getting-started + $contentMarkupToDisplay .= '

'.$species->getKingdom().'

'; // ANIMALIA + $contentMarkupToDisplay .= '

'.$species->getPhylum().'

'; // CHORDATA + $contentMarkupToDisplay .= '

'.$species->getClass().'

'; // MAMMALIA + $contentMarkupToDisplay .= '

'.$species->getOrder().'

'; // CARNIVORA + $contentMarkupToDisplay .= '

'.$species->getFamily().'

'; // AILURIDAE + $contentMarkupToDisplay .= '

'.$species->getGenus().'

'; // Ailurus + $contentMarkupToDisplay .= '

'.$species->getMainCommonName().'

'; // Red Panda + + $content['content']['#markup'] = $contentMarkupToDisplay; + + cache_set($term->tid, $content['content']['#markup'], 'cache_iucn'); + + } catch (\IucnApi\Exception\IucnApiException $exception) { + // Something doesn't look good… + $content['content']['#markup'] = t('There was an error downloading the information for %term_name', array( + '$term_name' => $term->name + )); + } + + + + /* $request = drupal_http_request('http://api.iucnredlist.org/index/species/' . preg_replace('/[^A-Za-z\-]/','',str_replace(' ', '-', $term->name)) . '.js', array( 'timeout' => '3.0' )); @@ -88,6 +118,8 @@ function iucn_block_view($delta = ''){ cache_set($term->tid, $content['content']['#markup'], 'cache_iucn'); } } + + */ } } } From 580432fde973e79ae20a433ca6f77df589b98f3c Mon Sep 17 00:00:00 2001 From: Rob Davis Date: Thu, 1 Dec 2022 12:38:38 +0000 Subject: [PATCH 13/22] https://github.com/NaturalHistoryMuseum/scratchpads2/issues/6611 removed submodules but kept folders, this means that all the vendor libraries will be controlled under main scratchpads repo --- .gitignore | 6 +- .../all/libraries/vendor/doctrine/collections | 1 - .../collections/.doctrine-project.json | 32 + .../doctrine/collections/CONTRIBUTING.md | 44 + .../vendor/doctrine/collections/LICENSE | 19 + .../vendor/doctrine/collections/README.md | 6 + .../vendor/doctrine/collections/composer.json | 61 + .../docs/en/derived-collections.rst | 26 + .../docs/en/expression-builder.rst | 173 +++ .../collections/docs/en/expressions.rst | 102 ++ .../doctrine/collections/docs/en/index.rst | 333 +++++ .../collections/docs/en/lazy-collections.rst | 26 + .../doctrine/collections/docs/en/sidebar.rst | 8 + .../Collections/AbstractLazyCollection.php | 389 ++++++ .../Common/Collections/ArrayCollection.php | 466 +++++++ .../Common/Collections/Collection.php | 99 ++ .../Doctrine/Common/Collections/Criteria.php | 245 ++++ .../Expr/ClosureExpressionVisitor.php | 269 ++++ .../Common/Collections/Expr/Comparison.php | 74 ++ .../Collections/Expr/CompositeExpression.php | 67 + .../Common/Collections/Expr/Expression.php | 12 + .../Collections/Expr/ExpressionVisitor.php | 59 + .../Common/Collections/Expr/Value.php | 29 + .../Common/Collections/ExpressionBuilder.php | 181 +++ .../Common/Collections/ReadableCollection.php | 213 +++ .../Common/Collections/Selectable.php | 30 + .../doctrine/collections/phpcs.xml.dist | 46 + .../doctrine/collections/phpstan.neon.dist | 11 + .../doctrine/collections/phpunit.xml.dist | 21 + .../doctrine/collections/psalm.xml.dist | 48 + .../AbstractLazyArrayCollectionTest.php | 36 + .../AbstractLazyCollectionTest.php | 19 + .../Collections/ArrayCollectionTest.php | 24 + .../Collections/BaseArrayCollectionTest.php | 348 +++++ .../Common/Collections/BaseCollectionTest.php | 255 ++++ .../ClosureExpressionVisitorTest.php | 564 ++++++++ .../Common/Collections/CollectionTest.php | 96 ++ .../Tests/Common/Collections/CriteriaTest.php | 132 ++ .../Collections/DerivedCollectionTest.php | 28 + .../Collections/Expr/ComparisonTest.php | 30 + .../Expr/CompositeExpressionTest.php | 76 ++ .../Common/Collections/Expr/ValueTest.php | 33 + .../Collections/ExpressionBuilderTest.php | 147 +++ .../Doctrine/Tests/DerivedArrayCollection.php | 33 + .../Doctrine/Tests/LazyArrayCollection.php | 33 + .../libraries/vendor/doctrine/deprecations | 1 - .../vendor/doctrine/deprecations/LICENSE | 19 + .../vendor/doctrine/deprecations/README.md | 154 +++ .../doctrine/deprecations/composer.json | 32 + .../lib/Doctrine/Deprecations/Deprecation.php | 266 ++++ .../PHPUnit/VerifyDeprecations.php | 66 + .../vendor/doctrine/deprecations/phpcs.xml | 22 + .../doctrine/deprecations/phpunit.xml.dist | 8 + .../deprecations/test_fixtures/src/Foo.php | 22 + .../test_fixtures/src/RootDeprecation.php | 20 + .../test_fixtures/vendor/doctrine/foo/Bar.php | 24 + .../test_fixtures/vendor/doctrine/foo/Baz.php | 14 + .../Doctrine/Deprecations/DeprecationTest.php | 251 ++++ .../Deprecations/VerifyDeprecationsTest.php | 35 + .../libraries/vendor/guzzle/guzzle/.gitignore | 27 - .../vendor/michelf/php-markdown/.gitignore | 3 - sites/all/libraries/vendor/psr/container | 1 - .../libraries/vendor/psr/container/LICENSE | 21 + .../libraries/vendor/psr/container/README.md | 13 + .../vendor/psr/container/composer.json | 22 + .../src/ContainerExceptionInterface.php | 12 + .../psr/container/src/ContainerInterface.php | 36 + .../src/NotFoundExceptionInterface.php | 10 + sites/all/libraries/vendor/psr/log | 1 - sites/all/libraries/vendor/psr/log/LICENSE | 19 + .../vendor/psr/log/Psr/Log/AbstractLogger.php | 128 ++ .../log/Psr/Log/InvalidArgumentException.php | 7 + .../vendor/psr/log/Psr/Log/LogLevel.php | 18 + .../psr/log/Psr/Log/LoggerAwareInterface.php | 18 + .../psr/log/Psr/Log/LoggerAwareTrait.php | 26 + .../psr/log/Psr/Log/LoggerInterface.php | 125 ++ .../vendor/psr/log/Psr/Log/LoggerTrait.php | 142 ++ .../vendor/psr/log/Psr/Log/NullLogger.php | 30 + .../vendor/psr/log/Psr/Log/Test/DummyTest.php | 18 + .../log/Psr/Log/Test/LoggerInterfaceTest.php | 138 ++ .../psr/log/Psr/Log/Test/TestLogger.php | 147 +++ sites/all/libraries/vendor/psr/log/README.md | 58 + .../libraries/vendor/psr/log/composer.json | 26 + .../vendor/symfony/deprecation-contracts | 1 - .../symfony/deprecation-contracts/.gitignore | 3 + .../deprecation-contracts/CHANGELOG.md | 5 + .../symfony/deprecation-contracts/LICENSE | 19 + .../symfony/deprecation-contracts/README.md | 26 + .../deprecation-contracts/composer.json | 35 + .../deprecation-contracts/function.php | 27 + .../all/libraries/vendor/symfony/http-client | 1 - .../vendor/symfony/http-client-contracts | 1 - .../symfony/http-client-contracts/.gitignore | 3 + .../http-client-contracts/CHANGELOG.md | 5 + .../http-client-contracts/ChunkInterface.php | 71 + .../Exception/ClientExceptionInterface.php | 21 + .../Exception/DecodingExceptionInterface.php | 21 + .../Exception/ExceptionInterface.php | 21 + .../Exception/HttpExceptionInterface.php | 24 + .../RedirectionExceptionInterface.php | 21 + .../Exception/ServerExceptionInterface.php | 21 + .../Exception/TimeoutExceptionInterface.php | 21 + .../Exception/TransportExceptionInterface.php | 21 + .../HttpClientInterface.php | 95 ++ .../symfony/http-client-contracts/LICENSE | 19 + .../symfony/http-client-contracts/README.md | 9 + .../ResponseInterface.php | 109 ++ .../ResponseStreamInterface.php | 26 + .../Test/Fixtures/web/index.php | 192 +++ .../Test/HttpClientTestCase.php | 1137 +++++++++++++++++ .../Test/TestHttpServer.php | 46 + .../http-client-contracts/composer.json | 37 + .../vendor/symfony/http-client/.gitattributes | 4 + .../vendor/symfony/http-client/.gitignore | 3 + .../symfony/http-client/AmpHttpClient.php | 176 +++ .../http-client/AsyncDecoratorTrait.php | 48 + .../vendor/symfony/http-client/CHANGELOG.md | 54 + .../symfony/http-client/CachingHttpClient.php | 152 +++ .../symfony/http-client/Chunk/DataChunk.php | 87 ++ .../symfony/http-client/Chunk/ErrorChunk.php | 140 ++ .../symfony/http-client/Chunk/FirstChunk.php | 28 + .../http-client/Chunk/InformationalChunk.php | 35 + .../symfony/http-client/Chunk/LastChunk.php | 28 + .../http-client/Chunk/ServerSentEvent.php | 79 ++ .../symfony/http-client/CurlHttpClient.php | 551 ++++++++ .../DataCollector/HttpClientDataCollector.php | 170 +++ .../symfony/http-client/DecoratorTrait.php | 66 + .../DependencyInjection/HttpClientPass.php | 51 + .../http-client/EventSourceHttpClient.php | 159 +++ .../http-client/Exception/ClientException.php | 24 + .../Exception/EventSourceException.php | 21 + .../Exception/HttpExceptionTrait.php | 78 ++ .../Exception/InvalidArgumentException.php | 21 + .../http-client/Exception/JsonException.php | 23 + .../Exception/RedirectionException.php | 24 + .../http-client/Exception/ServerException.php | 24 + .../Exception/TimeoutException.php | 21 + .../Exception/TransportException.php | 21 + .../vendor/symfony/http-client/HttpClient.php | 78 ++ .../symfony/http-client/HttpClientTrait.php | 686 ++++++++++ .../symfony/http-client/HttpOptions.php | 331 +++++ .../symfony/http-client/HttplugClient.php | 271 ++++ .../symfony/http-client/Internal/AmpBody.php | 142 ++ .../http-client/Internal/AmpClientState.php | 217 ++++ .../http-client/Internal/AmpListener.php | 183 +++ .../http-client/Internal/AmpResolver.php | 52 + .../symfony/http-client/Internal/Canary.php | 40 + .../http-client/Internal/ClientState.php | 26 + .../http-client/Internal/CurlClientState.php | 148 +++ .../symfony/http-client/Internal/DnsCache.php | 39 + .../http-client/Internal/HttplugWaitLoop.php | 141 ++ .../Internal/NativeClientState.php | 47 + .../http-client/Internal/PushedResponse.php | 41 + .../vendor/symfony/http-client/LICENSE | 19 + .../symfony/http-client/MockHttpClient.php | 124 ++ .../symfony/http-client/NativeHttpClient.php | 468 +++++++ .../NoPrivateNetworkHttpClient.php | 132 ++ .../symfony/http-client/Psr18Client.php | 243 ++++ .../vendor/symfony/http-client/README.md | 27 + .../http-client/Response/AmpResponse.php | 461 +++++++ .../http-client/Response/AsyncContext.php | 195 +++ .../http-client/Response/AsyncResponse.php | 478 +++++++ .../Response/CommonResponseTrait.php | 185 +++ .../http-client/Response/CurlResponse.php | 474 +++++++ .../http-client/Response/HttplugPromise.php | 80 ++ .../http-client/Response/MockResponse.php | 339 +++++ .../http-client/Response/NativeResponse.php | 376 ++++++ .../http-client/Response/ResponseStream.php | 54 + .../http-client/Response/StreamWrapper.php | 313 +++++ .../Response/StreamableInterface.php | 35 + .../Response/TraceableResponse.php | 219 ++++ .../Response/TransportResponseTrait.php | 312 +++++ .../Retry/GenericRetryStrategy.php | 115 ++ .../Retry/RetryStrategyInterface.php | 36 + .../http-client/RetryableHttpClient.php | 171 +++ .../symfony/http-client/ScopingHttpClient.php | 131 ++ .../http-client/Tests/AmpHttpClientTest.php | 28 + .../Tests/AsyncDecoratorTraitTest.php | 367 ++++++ .../Tests/CachingHttpClientTest.php | 110 ++ .../Tests/Chunk/ServerSentEventTest.php | 79 ++ .../http-client/Tests/CurlHttpClientTest.php | 129 ++ .../HttpClientDataCollectorTest.php | 180 +++ .../HttpClientPassTest.php | 65 + .../Tests/EventSourceHttpClientTest.php | 169 +++ .../Exception/HttpExceptionTraitTest.php | 66 + .../Tests/Fixtures/assertion_failure.php | 3 + .../http-client/Tests/Fixtures/tls/server.crt | 20 + .../http-client/Tests/Fixtures/tls/server.key | 27 + .../http-client/Tests/HttpClientTest.php | 26 + .../http-client/Tests/HttpClientTestCase.php | 458 +++++++ .../http-client/Tests/HttpClientTraitTest.php | 277 ++++ .../http-client/Tests/HttpOptionsTest.php | 42 + .../http-client/Tests/HttplugClientTest.php | 270 ++++ .../http-client/Tests/MockHttpClientTest.php | 509 ++++++++ .../Tests/NativeHttpClientTest.php | 48 + .../Tests/NoPrivateNetworkHttpClientTest.php | 164 +++ .../http-client/Tests/Psr18ClientTest.php | 104 ++ .../Tests/Response/HttplugPromiseTest.php | 36 + .../Tests/Response/MockResponseTest.php | 119 ++ .../Tests/Retry/GenericRetryStrategyTest.php | 121 ++ .../Tests/RetryableHttpClientTest.php | 247 ++++ .../Tests/ScopingHttpClientTest.php | 103 ++ .../symfony/http-client/Tests/TestLogger.php | 24 + .../Tests/TraceableHttpClientTest.php | 235 ++++ .../http-client/TraceableHttpClient.php | 120 ++ .../vendor/symfony/http-client/composer.json | 53 + .../symfony/http-client/phpunit.xml.dist | 30 + .../libraries/vendor/symfony/polyfill-php73 | 1 - .../vendor/symfony/polyfill-php73/LICENSE | 19 + .../vendor/symfony/polyfill-php73/Php73.php | 43 + .../vendor/symfony/polyfill-php73/README.md | 18 + .../Resources/stubs/JsonException.php | 16 + .../symfony/polyfill-php73/bootstrap.php | 31 + .../symfony/polyfill-php73/composer.json | 36 + .../libraries/vendor/symfony/polyfill-php80 | 1 - .../vendor/symfony/polyfill-php80/LICENSE | 19 + .../vendor/symfony/polyfill-php80/Php80.php | 115 ++ .../symfony/polyfill-php80/PhpToken.php | 103 ++ .../vendor/symfony/polyfill-php80/README.md | 25 + .../Resources/stubs/Attribute.php | 31 + .../Resources/stubs/PhpToken.php | 16 + .../Resources/stubs/Stringable.php | 20 + .../Resources/stubs/UnhandledMatchError.php | 16 + .../Resources/stubs/ValueError.php | 16 + .../symfony/polyfill-php80/bootstrap.php | 42 + .../symfony/polyfill-php80/composer.json | 40 + .../vendor/symfony/service-contracts | 1 - .../symfony/service-contracts/.gitignore | 3 + .../service-contracts/Attribute/Required.php | 25 + .../Attribute/SubscribedService.php | 33 + .../symfony/service-contracts/CHANGELOG.md | 5 + .../vendor/symfony/service-contracts/LICENSE | 19 + .../symfony/service-contracts/README.md | 9 + .../service-contracts/ResetInterface.php | 30 + .../service-contracts/ServiceLocatorTrait.php | 128 ++ .../ServiceProviderInterface.php | 36 + .../ServiceSubscriberInterface.php | 53 + .../ServiceSubscriberTrait.php | 109 ++ .../Test/ServiceLocatorTest.php | 95 ++ .../symfony/service-contracts/composer.json | 42 + 240 files changed, 23868 insertions(+), 42 deletions(-) delete mode 160000 sites/all/libraries/vendor/doctrine/collections create mode 100644 sites/all/libraries/vendor/doctrine/collections/.doctrine-project.json create mode 100644 sites/all/libraries/vendor/doctrine/collections/CONTRIBUTING.md create mode 100644 sites/all/libraries/vendor/doctrine/collections/LICENSE create mode 100644 sites/all/libraries/vendor/doctrine/collections/README.md create mode 100644 sites/all/libraries/vendor/doctrine/collections/composer.json create mode 100644 sites/all/libraries/vendor/doctrine/collections/docs/en/derived-collections.rst create mode 100644 sites/all/libraries/vendor/doctrine/collections/docs/en/expression-builder.rst create mode 100644 sites/all/libraries/vendor/doctrine/collections/docs/en/expressions.rst create mode 100644 sites/all/libraries/vendor/doctrine/collections/docs/en/index.rst create mode 100644 sites/all/libraries/vendor/doctrine/collections/docs/en/lazy-collections.rst create mode 100644 sites/all/libraries/vendor/doctrine/collections/docs/en/sidebar.rst create mode 100644 sites/all/libraries/vendor/doctrine/collections/lib/Doctrine/Common/Collections/AbstractLazyCollection.php create mode 100644 sites/all/libraries/vendor/doctrine/collections/lib/Doctrine/Common/Collections/ArrayCollection.php create mode 100644 sites/all/libraries/vendor/doctrine/collections/lib/Doctrine/Common/Collections/Collection.php create mode 100644 sites/all/libraries/vendor/doctrine/collections/lib/Doctrine/Common/Collections/Criteria.php create mode 100644 sites/all/libraries/vendor/doctrine/collections/lib/Doctrine/Common/Collections/Expr/ClosureExpressionVisitor.php create mode 100644 sites/all/libraries/vendor/doctrine/collections/lib/Doctrine/Common/Collections/Expr/Comparison.php create mode 100644 sites/all/libraries/vendor/doctrine/collections/lib/Doctrine/Common/Collections/Expr/CompositeExpression.php create mode 100644 sites/all/libraries/vendor/doctrine/collections/lib/Doctrine/Common/Collections/Expr/Expression.php create mode 100644 sites/all/libraries/vendor/doctrine/collections/lib/Doctrine/Common/Collections/Expr/ExpressionVisitor.php create mode 100644 sites/all/libraries/vendor/doctrine/collections/lib/Doctrine/Common/Collections/Expr/Value.php create mode 100644 sites/all/libraries/vendor/doctrine/collections/lib/Doctrine/Common/Collections/ExpressionBuilder.php create mode 100644 sites/all/libraries/vendor/doctrine/collections/lib/Doctrine/Common/Collections/ReadableCollection.php create mode 100644 sites/all/libraries/vendor/doctrine/collections/lib/Doctrine/Common/Collections/Selectable.php create mode 100644 sites/all/libraries/vendor/doctrine/collections/phpcs.xml.dist create mode 100644 sites/all/libraries/vendor/doctrine/collections/phpstan.neon.dist create mode 100644 sites/all/libraries/vendor/doctrine/collections/phpunit.xml.dist create mode 100644 sites/all/libraries/vendor/doctrine/collections/psalm.xml.dist create mode 100644 sites/all/libraries/vendor/doctrine/collections/tests/Doctrine/Tests/Common/Collections/AbstractLazyArrayCollectionTest.php create mode 100644 sites/all/libraries/vendor/doctrine/collections/tests/Doctrine/Tests/Common/Collections/AbstractLazyCollectionTest.php create mode 100644 sites/all/libraries/vendor/doctrine/collections/tests/Doctrine/Tests/Common/Collections/ArrayCollectionTest.php create mode 100644 sites/all/libraries/vendor/doctrine/collections/tests/Doctrine/Tests/Common/Collections/BaseArrayCollectionTest.php create mode 100644 sites/all/libraries/vendor/doctrine/collections/tests/Doctrine/Tests/Common/Collections/BaseCollectionTest.php create mode 100644 sites/all/libraries/vendor/doctrine/collections/tests/Doctrine/Tests/Common/Collections/ClosureExpressionVisitorTest.php create mode 100644 sites/all/libraries/vendor/doctrine/collections/tests/Doctrine/Tests/Common/Collections/CollectionTest.php create mode 100644 sites/all/libraries/vendor/doctrine/collections/tests/Doctrine/Tests/Common/Collections/CriteriaTest.php create mode 100644 sites/all/libraries/vendor/doctrine/collections/tests/Doctrine/Tests/Common/Collections/DerivedCollectionTest.php create mode 100644 sites/all/libraries/vendor/doctrine/collections/tests/Doctrine/Tests/Common/Collections/Expr/ComparisonTest.php create mode 100644 sites/all/libraries/vendor/doctrine/collections/tests/Doctrine/Tests/Common/Collections/Expr/CompositeExpressionTest.php create mode 100644 sites/all/libraries/vendor/doctrine/collections/tests/Doctrine/Tests/Common/Collections/Expr/ValueTest.php create mode 100644 sites/all/libraries/vendor/doctrine/collections/tests/Doctrine/Tests/Common/Collections/ExpressionBuilderTest.php create mode 100644 sites/all/libraries/vendor/doctrine/collections/tests/Doctrine/Tests/DerivedArrayCollection.php create mode 100644 sites/all/libraries/vendor/doctrine/collections/tests/Doctrine/Tests/LazyArrayCollection.php delete mode 160000 sites/all/libraries/vendor/doctrine/deprecations create mode 100644 sites/all/libraries/vendor/doctrine/deprecations/LICENSE create mode 100644 sites/all/libraries/vendor/doctrine/deprecations/README.md create mode 100644 sites/all/libraries/vendor/doctrine/deprecations/composer.json create mode 100644 sites/all/libraries/vendor/doctrine/deprecations/lib/Doctrine/Deprecations/Deprecation.php create mode 100644 sites/all/libraries/vendor/doctrine/deprecations/lib/Doctrine/Deprecations/PHPUnit/VerifyDeprecations.php create mode 100644 sites/all/libraries/vendor/doctrine/deprecations/phpcs.xml create mode 100644 sites/all/libraries/vendor/doctrine/deprecations/phpunit.xml.dist create mode 100644 sites/all/libraries/vendor/doctrine/deprecations/test_fixtures/src/Foo.php create mode 100644 sites/all/libraries/vendor/doctrine/deprecations/test_fixtures/src/RootDeprecation.php create mode 100644 sites/all/libraries/vendor/doctrine/deprecations/test_fixtures/vendor/doctrine/foo/Bar.php create mode 100644 sites/all/libraries/vendor/doctrine/deprecations/test_fixtures/vendor/doctrine/foo/Baz.php create mode 100644 sites/all/libraries/vendor/doctrine/deprecations/tests/Doctrine/Deprecations/DeprecationTest.php create mode 100644 sites/all/libraries/vendor/doctrine/deprecations/tests/Doctrine/Deprecations/VerifyDeprecationsTest.php delete mode 100644 sites/all/libraries/vendor/guzzle/guzzle/.gitignore delete mode 100644 sites/all/libraries/vendor/michelf/php-markdown/.gitignore delete mode 160000 sites/all/libraries/vendor/psr/container create mode 100644 sites/all/libraries/vendor/psr/container/LICENSE create mode 100644 sites/all/libraries/vendor/psr/container/README.md create mode 100644 sites/all/libraries/vendor/psr/container/composer.json create mode 100644 sites/all/libraries/vendor/psr/container/src/ContainerExceptionInterface.php create mode 100644 sites/all/libraries/vendor/psr/container/src/ContainerInterface.php create mode 100644 sites/all/libraries/vendor/psr/container/src/NotFoundExceptionInterface.php delete mode 160000 sites/all/libraries/vendor/psr/log create mode 100644 sites/all/libraries/vendor/psr/log/LICENSE create mode 100644 sites/all/libraries/vendor/psr/log/Psr/Log/AbstractLogger.php create mode 100644 sites/all/libraries/vendor/psr/log/Psr/Log/InvalidArgumentException.php create mode 100644 sites/all/libraries/vendor/psr/log/Psr/Log/LogLevel.php create mode 100644 sites/all/libraries/vendor/psr/log/Psr/Log/LoggerAwareInterface.php create mode 100644 sites/all/libraries/vendor/psr/log/Psr/Log/LoggerAwareTrait.php create mode 100644 sites/all/libraries/vendor/psr/log/Psr/Log/LoggerInterface.php create mode 100644 sites/all/libraries/vendor/psr/log/Psr/Log/LoggerTrait.php create mode 100644 sites/all/libraries/vendor/psr/log/Psr/Log/NullLogger.php create mode 100644 sites/all/libraries/vendor/psr/log/Psr/Log/Test/DummyTest.php create mode 100644 sites/all/libraries/vendor/psr/log/Psr/Log/Test/LoggerInterfaceTest.php create mode 100644 sites/all/libraries/vendor/psr/log/Psr/Log/Test/TestLogger.php create mode 100644 sites/all/libraries/vendor/psr/log/README.md create mode 100644 sites/all/libraries/vendor/psr/log/composer.json delete mode 160000 sites/all/libraries/vendor/symfony/deprecation-contracts create mode 100644 sites/all/libraries/vendor/symfony/deprecation-contracts/.gitignore create mode 100644 sites/all/libraries/vendor/symfony/deprecation-contracts/CHANGELOG.md create mode 100644 sites/all/libraries/vendor/symfony/deprecation-contracts/LICENSE create mode 100644 sites/all/libraries/vendor/symfony/deprecation-contracts/README.md create mode 100644 sites/all/libraries/vendor/symfony/deprecation-contracts/composer.json create mode 100644 sites/all/libraries/vendor/symfony/deprecation-contracts/function.php delete mode 160000 sites/all/libraries/vendor/symfony/http-client delete mode 160000 sites/all/libraries/vendor/symfony/http-client-contracts create mode 100644 sites/all/libraries/vendor/symfony/http-client-contracts/.gitignore create mode 100644 sites/all/libraries/vendor/symfony/http-client-contracts/CHANGELOG.md create mode 100644 sites/all/libraries/vendor/symfony/http-client-contracts/ChunkInterface.php create mode 100644 sites/all/libraries/vendor/symfony/http-client-contracts/Exception/ClientExceptionInterface.php create mode 100644 sites/all/libraries/vendor/symfony/http-client-contracts/Exception/DecodingExceptionInterface.php create mode 100644 sites/all/libraries/vendor/symfony/http-client-contracts/Exception/ExceptionInterface.php create mode 100644 sites/all/libraries/vendor/symfony/http-client-contracts/Exception/HttpExceptionInterface.php create mode 100644 sites/all/libraries/vendor/symfony/http-client-contracts/Exception/RedirectionExceptionInterface.php create mode 100644 sites/all/libraries/vendor/symfony/http-client-contracts/Exception/ServerExceptionInterface.php create mode 100644 sites/all/libraries/vendor/symfony/http-client-contracts/Exception/TimeoutExceptionInterface.php create mode 100644 sites/all/libraries/vendor/symfony/http-client-contracts/Exception/TransportExceptionInterface.php create mode 100644 sites/all/libraries/vendor/symfony/http-client-contracts/HttpClientInterface.php create mode 100644 sites/all/libraries/vendor/symfony/http-client-contracts/LICENSE create mode 100644 sites/all/libraries/vendor/symfony/http-client-contracts/README.md create mode 100644 sites/all/libraries/vendor/symfony/http-client-contracts/ResponseInterface.php create mode 100644 sites/all/libraries/vendor/symfony/http-client-contracts/ResponseStreamInterface.php create mode 100644 sites/all/libraries/vendor/symfony/http-client-contracts/Test/Fixtures/web/index.php create mode 100644 sites/all/libraries/vendor/symfony/http-client-contracts/Test/HttpClientTestCase.php create mode 100644 sites/all/libraries/vendor/symfony/http-client-contracts/Test/TestHttpServer.php create mode 100644 sites/all/libraries/vendor/symfony/http-client-contracts/composer.json create mode 100644 sites/all/libraries/vendor/symfony/http-client/.gitattributes create mode 100644 sites/all/libraries/vendor/symfony/http-client/.gitignore create mode 100644 sites/all/libraries/vendor/symfony/http-client/AmpHttpClient.php create mode 100644 sites/all/libraries/vendor/symfony/http-client/AsyncDecoratorTrait.php create mode 100644 sites/all/libraries/vendor/symfony/http-client/CHANGELOG.md create mode 100644 sites/all/libraries/vendor/symfony/http-client/CachingHttpClient.php create mode 100644 sites/all/libraries/vendor/symfony/http-client/Chunk/DataChunk.php create mode 100644 sites/all/libraries/vendor/symfony/http-client/Chunk/ErrorChunk.php create mode 100644 sites/all/libraries/vendor/symfony/http-client/Chunk/FirstChunk.php create mode 100644 sites/all/libraries/vendor/symfony/http-client/Chunk/InformationalChunk.php create mode 100644 sites/all/libraries/vendor/symfony/http-client/Chunk/LastChunk.php create mode 100644 sites/all/libraries/vendor/symfony/http-client/Chunk/ServerSentEvent.php create mode 100644 sites/all/libraries/vendor/symfony/http-client/CurlHttpClient.php create mode 100644 sites/all/libraries/vendor/symfony/http-client/DataCollector/HttpClientDataCollector.php create mode 100644 sites/all/libraries/vendor/symfony/http-client/DecoratorTrait.php create mode 100644 sites/all/libraries/vendor/symfony/http-client/DependencyInjection/HttpClientPass.php create mode 100644 sites/all/libraries/vendor/symfony/http-client/EventSourceHttpClient.php create mode 100644 sites/all/libraries/vendor/symfony/http-client/Exception/ClientException.php create mode 100644 sites/all/libraries/vendor/symfony/http-client/Exception/EventSourceException.php create mode 100644 sites/all/libraries/vendor/symfony/http-client/Exception/HttpExceptionTrait.php create mode 100644 sites/all/libraries/vendor/symfony/http-client/Exception/InvalidArgumentException.php create mode 100644 sites/all/libraries/vendor/symfony/http-client/Exception/JsonException.php create mode 100644 sites/all/libraries/vendor/symfony/http-client/Exception/RedirectionException.php create mode 100644 sites/all/libraries/vendor/symfony/http-client/Exception/ServerException.php create mode 100644 sites/all/libraries/vendor/symfony/http-client/Exception/TimeoutException.php create mode 100644 sites/all/libraries/vendor/symfony/http-client/Exception/TransportException.php create mode 100644 sites/all/libraries/vendor/symfony/http-client/HttpClient.php create mode 100644 sites/all/libraries/vendor/symfony/http-client/HttpClientTrait.php create mode 100644 sites/all/libraries/vendor/symfony/http-client/HttpOptions.php create mode 100644 sites/all/libraries/vendor/symfony/http-client/HttplugClient.php create mode 100644 sites/all/libraries/vendor/symfony/http-client/Internal/AmpBody.php create mode 100644 sites/all/libraries/vendor/symfony/http-client/Internal/AmpClientState.php create mode 100644 sites/all/libraries/vendor/symfony/http-client/Internal/AmpListener.php create mode 100644 sites/all/libraries/vendor/symfony/http-client/Internal/AmpResolver.php create mode 100644 sites/all/libraries/vendor/symfony/http-client/Internal/Canary.php create mode 100644 sites/all/libraries/vendor/symfony/http-client/Internal/ClientState.php create mode 100644 sites/all/libraries/vendor/symfony/http-client/Internal/CurlClientState.php create mode 100644 sites/all/libraries/vendor/symfony/http-client/Internal/DnsCache.php create mode 100644 sites/all/libraries/vendor/symfony/http-client/Internal/HttplugWaitLoop.php create mode 100644 sites/all/libraries/vendor/symfony/http-client/Internal/NativeClientState.php create mode 100644 sites/all/libraries/vendor/symfony/http-client/Internal/PushedResponse.php create mode 100644 sites/all/libraries/vendor/symfony/http-client/LICENSE create mode 100644 sites/all/libraries/vendor/symfony/http-client/MockHttpClient.php create mode 100644 sites/all/libraries/vendor/symfony/http-client/NativeHttpClient.php create mode 100644 sites/all/libraries/vendor/symfony/http-client/NoPrivateNetworkHttpClient.php create mode 100644 sites/all/libraries/vendor/symfony/http-client/Psr18Client.php create mode 100644 sites/all/libraries/vendor/symfony/http-client/README.md create mode 100644 sites/all/libraries/vendor/symfony/http-client/Response/AmpResponse.php create mode 100644 sites/all/libraries/vendor/symfony/http-client/Response/AsyncContext.php create mode 100644 sites/all/libraries/vendor/symfony/http-client/Response/AsyncResponse.php create mode 100644 sites/all/libraries/vendor/symfony/http-client/Response/CommonResponseTrait.php create mode 100644 sites/all/libraries/vendor/symfony/http-client/Response/CurlResponse.php create mode 100644 sites/all/libraries/vendor/symfony/http-client/Response/HttplugPromise.php create mode 100644 sites/all/libraries/vendor/symfony/http-client/Response/MockResponse.php create mode 100644 sites/all/libraries/vendor/symfony/http-client/Response/NativeResponse.php create mode 100644 sites/all/libraries/vendor/symfony/http-client/Response/ResponseStream.php create mode 100644 sites/all/libraries/vendor/symfony/http-client/Response/StreamWrapper.php create mode 100644 sites/all/libraries/vendor/symfony/http-client/Response/StreamableInterface.php create mode 100644 sites/all/libraries/vendor/symfony/http-client/Response/TraceableResponse.php create mode 100644 sites/all/libraries/vendor/symfony/http-client/Response/TransportResponseTrait.php create mode 100644 sites/all/libraries/vendor/symfony/http-client/Retry/GenericRetryStrategy.php create mode 100644 sites/all/libraries/vendor/symfony/http-client/Retry/RetryStrategyInterface.php create mode 100644 sites/all/libraries/vendor/symfony/http-client/RetryableHttpClient.php create mode 100644 sites/all/libraries/vendor/symfony/http-client/ScopingHttpClient.php create mode 100644 sites/all/libraries/vendor/symfony/http-client/Tests/AmpHttpClientTest.php create mode 100644 sites/all/libraries/vendor/symfony/http-client/Tests/AsyncDecoratorTraitTest.php create mode 100644 sites/all/libraries/vendor/symfony/http-client/Tests/CachingHttpClientTest.php create mode 100644 sites/all/libraries/vendor/symfony/http-client/Tests/Chunk/ServerSentEventTest.php create mode 100644 sites/all/libraries/vendor/symfony/http-client/Tests/CurlHttpClientTest.php create mode 100755 sites/all/libraries/vendor/symfony/http-client/Tests/DataCollector/HttpClientDataCollectorTest.php create mode 100755 sites/all/libraries/vendor/symfony/http-client/Tests/DependencyInjection/HttpClientPassTest.php create mode 100644 sites/all/libraries/vendor/symfony/http-client/Tests/EventSourceHttpClientTest.php create mode 100644 sites/all/libraries/vendor/symfony/http-client/Tests/Exception/HttpExceptionTraitTest.php create mode 100644 sites/all/libraries/vendor/symfony/http-client/Tests/Fixtures/assertion_failure.php create mode 100644 sites/all/libraries/vendor/symfony/http-client/Tests/Fixtures/tls/server.crt create mode 100644 sites/all/libraries/vendor/symfony/http-client/Tests/Fixtures/tls/server.key create mode 100644 sites/all/libraries/vendor/symfony/http-client/Tests/HttpClientTest.php create mode 100644 sites/all/libraries/vendor/symfony/http-client/Tests/HttpClientTestCase.php create mode 100644 sites/all/libraries/vendor/symfony/http-client/Tests/HttpClientTraitTest.php create mode 100644 sites/all/libraries/vendor/symfony/http-client/Tests/HttpOptionsTest.php create mode 100644 sites/all/libraries/vendor/symfony/http-client/Tests/HttplugClientTest.php create mode 100644 sites/all/libraries/vendor/symfony/http-client/Tests/MockHttpClientTest.php create mode 100644 sites/all/libraries/vendor/symfony/http-client/Tests/NativeHttpClientTest.php create mode 100755 sites/all/libraries/vendor/symfony/http-client/Tests/NoPrivateNetworkHttpClientTest.php create mode 100644 sites/all/libraries/vendor/symfony/http-client/Tests/Psr18ClientTest.php create mode 100644 sites/all/libraries/vendor/symfony/http-client/Tests/Response/HttplugPromiseTest.php create mode 100644 sites/all/libraries/vendor/symfony/http-client/Tests/Response/MockResponseTest.php create mode 100644 sites/all/libraries/vendor/symfony/http-client/Tests/Retry/GenericRetryStrategyTest.php create mode 100644 sites/all/libraries/vendor/symfony/http-client/Tests/RetryableHttpClientTest.php create mode 100644 sites/all/libraries/vendor/symfony/http-client/Tests/ScopingHttpClientTest.php create mode 100644 sites/all/libraries/vendor/symfony/http-client/Tests/TestLogger.php create mode 100755 sites/all/libraries/vendor/symfony/http-client/Tests/TraceableHttpClientTest.php create mode 100644 sites/all/libraries/vendor/symfony/http-client/TraceableHttpClient.php create mode 100644 sites/all/libraries/vendor/symfony/http-client/composer.json create mode 100644 sites/all/libraries/vendor/symfony/http-client/phpunit.xml.dist delete mode 160000 sites/all/libraries/vendor/symfony/polyfill-php73 create mode 100644 sites/all/libraries/vendor/symfony/polyfill-php73/LICENSE create mode 100644 sites/all/libraries/vendor/symfony/polyfill-php73/Php73.php create mode 100644 sites/all/libraries/vendor/symfony/polyfill-php73/README.md create mode 100644 sites/all/libraries/vendor/symfony/polyfill-php73/Resources/stubs/JsonException.php create mode 100644 sites/all/libraries/vendor/symfony/polyfill-php73/bootstrap.php create mode 100644 sites/all/libraries/vendor/symfony/polyfill-php73/composer.json delete mode 160000 sites/all/libraries/vendor/symfony/polyfill-php80 create mode 100644 sites/all/libraries/vendor/symfony/polyfill-php80/LICENSE create mode 100644 sites/all/libraries/vendor/symfony/polyfill-php80/Php80.php create mode 100644 sites/all/libraries/vendor/symfony/polyfill-php80/PhpToken.php create mode 100644 sites/all/libraries/vendor/symfony/polyfill-php80/README.md create mode 100644 sites/all/libraries/vendor/symfony/polyfill-php80/Resources/stubs/Attribute.php create mode 100644 sites/all/libraries/vendor/symfony/polyfill-php80/Resources/stubs/PhpToken.php create mode 100644 sites/all/libraries/vendor/symfony/polyfill-php80/Resources/stubs/Stringable.php create mode 100644 sites/all/libraries/vendor/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php create mode 100644 sites/all/libraries/vendor/symfony/polyfill-php80/Resources/stubs/ValueError.php create mode 100644 sites/all/libraries/vendor/symfony/polyfill-php80/bootstrap.php create mode 100644 sites/all/libraries/vendor/symfony/polyfill-php80/composer.json delete mode 160000 sites/all/libraries/vendor/symfony/service-contracts create mode 100644 sites/all/libraries/vendor/symfony/service-contracts/.gitignore create mode 100644 sites/all/libraries/vendor/symfony/service-contracts/Attribute/Required.php create mode 100644 sites/all/libraries/vendor/symfony/service-contracts/Attribute/SubscribedService.php create mode 100644 sites/all/libraries/vendor/symfony/service-contracts/CHANGELOG.md create mode 100644 sites/all/libraries/vendor/symfony/service-contracts/LICENSE create mode 100644 sites/all/libraries/vendor/symfony/service-contracts/README.md create mode 100644 sites/all/libraries/vendor/symfony/service-contracts/ResetInterface.php create mode 100644 sites/all/libraries/vendor/symfony/service-contracts/ServiceLocatorTrait.php create mode 100644 sites/all/libraries/vendor/symfony/service-contracts/ServiceProviderInterface.php create mode 100644 sites/all/libraries/vendor/symfony/service-contracts/ServiceSubscriberInterface.php create mode 100644 sites/all/libraries/vendor/symfony/service-contracts/ServiceSubscriberTrait.php create mode 100644 sites/all/libraries/vendor/symfony/service-contracts/Test/ServiceLocatorTest.php create mode 100644 sites/all/libraries/vendor/symfony/service-contracts/composer.json diff --git a/.gitignore b/.gitignore index 447f24c270..61f31f14bc 100644 --- a/.gitignore +++ b/.gitignore @@ -32,7 +32,7 @@ scratchpads_twitter.ini nohup.out docker-compose.dev.yml -# machine generated css and js +# machine generated css and jssites/all/libraries/vendor sites/default/files/advagg_css/ sites/default/files/advagg_js/ sites/default/files/css/ @@ -54,4 +54,6 @@ sites/default/files # will be removed and not tracked sites/all/libraries/vendor/jbroutier/iucn-api-client/.git sites/all/libraries/vendor/jbroutier/iucn-api-client/.github -sites/all/libraries/vendor/jbroutier/iucn-api-client/.gitignore \ No newline at end of file +sites/all/libraries/vendor/jbroutier/iucn-api-client/.gitignore + +sites/all/libraries/vendor/**/.git diff --git a/sites/all/libraries/vendor/doctrine/collections b/sites/all/libraries/vendor/doctrine/collections deleted file mode 160000 index 2b44dd4cbc..0000000000 --- a/sites/all/libraries/vendor/doctrine/collections +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 2b44dd4cbca8b5744327de78bafef5945c7e7b5e diff --git a/sites/all/libraries/vendor/doctrine/collections/.doctrine-project.json b/sites/all/libraries/vendor/doctrine/collections/.doctrine-project.json new file mode 100644 index 0000000000..9c89e50c0e --- /dev/null +++ b/sites/all/libraries/vendor/doctrine/collections/.doctrine-project.json @@ -0,0 +1,32 @@ +{ + "active": true, + "name": "Collections", + "slug": "collections", + "docsSlug": "doctrine-collections", + "versions": [ + { + "name": "2.0", + "branchName": "2.0.x", + "slug": "latest", + "upcoming": true + }, + { + "name": "1.8", + "branchName": "1.8.x", + "slug": "1.8", + "upcoming": true + }, + { + "name": "1.7", + "branchName": "1.7.x", + "slug": "1.7", + "current": true + }, + { + "name": "1.6", + "branchName": "1.6.x", + "slug": "1.6", + "maintained": false + } + ] +} diff --git a/sites/all/libraries/vendor/doctrine/collections/CONTRIBUTING.md b/sites/all/libraries/vendor/doctrine/collections/CONTRIBUTING.md new file mode 100644 index 0000000000..7a0a8c9a1a --- /dev/null +++ b/sites/all/libraries/vendor/doctrine/collections/CONTRIBUTING.md @@ -0,0 +1,44 @@ +# Contribute to Doctrine + +Thank you for contributing to Doctrine! + +Before we can merge your Pull-Request here are some guidelines that you need to follow. +These guidelines exist not to annoy you, but to keep the code base clean, +unified and future proof. + +## Coding Standard + +We use the [Doctrine Coding Standard](https://github.com/doctrine/coding-standard). + +## Unit-Tests + +Please try to add a test for your pull-request. + +* If you want to contribute new functionality add unit- or functional tests + depending on the scope of the feature. + +You can run the unit-tests by calling ``vendor/bin/phpunit`` from the root of the project. +It will run all the project tests. + +In order to do that, you will need a fresh copy of doctrine/collections, and you +will have to run a composer installation in the project: + +```sh +git clone git@github.com:doctrine/collections.git +cd collections +curl -sS https://getcomposer.org/installer | php -- +./composer.phar install +``` + +## Github Actions + +We automatically run your pull request through Github Actions against supported +PHP versions. If you break the tests, we cannot merge your code, so please make +sure that your code is working before opening up a Pull-Request. + +## Getting merged + +Please allow us time to review your pull requests. We will give our best to review +everything as fast as possible, but cannot always live up to our own expectations. + +Thank you very much again for your contribution! diff --git a/sites/all/libraries/vendor/doctrine/collections/LICENSE b/sites/all/libraries/vendor/doctrine/collections/LICENSE new file mode 100644 index 0000000000..5e781fce4b --- /dev/null +++ b/sites/all/libraries/vendor/doctrine/collections/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2006-2013 Doctrine Project + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/sites/all/libraries/vendor/doctrine/collections/README.md b/sites/all/libraries/vendor/doctrine/collections/README.md new file mode 100644 index 0000000000..048fb614df --- /dev/null +++ b/sites/all/libraries/vendor/doctrine/collections/README.md @@ -0,0 +1,6 @@ +# Doctrine Collections + +[![Build Status](https://github.com/doctrine/collections/workflows/Continuous%20Integration/badge.svg)](https://github.com/doctrine/collections/actions) +[![Code Coverage](https://codecov.io/gh/doctrine/collections/branch/2.0.x/graph/badge.svg)](https://codecov.io/gh/doctrine/collections/branch/2.0.x) + +Collections Abstraction library diff --git a/sites/all/libraries/vendor/doctrine/collections/composer.json b/sites/all/libraries/vendor/doctrine/collections/composer.json new file mode 100644 index 0000000000..d9422dd119 --- /dev/null +++ b/sites/all/libraries/vendor/doctrine/collections/composer.json @@ -0,0 +1,61 @@ +{ + "name": "doctrine/collections", + "description": "PHP Doctrine Collections library that adds additional functionality on top of PHP arrays.", + "license": "MIT", + "type": "library", + "keywords": [ + "php", + "collections", + "array", + "iterators" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "homepage": "https://www.doctrine-project.org/projects/collections.html", + "require": { + "php": "^7.1.3 || ^8.0", + "doctrine/deprecations": "^0.5.3 || ^1" + }, + "require-dev": { + "doctrine/coding-standard": "^9.0 || ^10.0", + "phpstan/phpstan": "^1.4.8", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.1.5", + "vimeo/psalm": "^4.22" + }, + "autoload": { + "psr-4": { + "Doctrine\\Common\\Collections\\": "lib/Doctrine/Common/Collections" + } + }, + "autoload-dev": { + "psr-4": { + "Doctrine\\Tests\\": "tests/Doctrine/Tests" + } + }, + "config": { + "allow-plugins": { + "composer/package-versions-deprecated": true, + "dealerdirect/phpcodesniffer-composer-installer": true + } + } +} diff --git a/sites/all/libraries/vendor/doctrine/collections/docs/en/derived-collections.rst b/sites/all/libraries/vendor/doctrine/collections/docs/en/derived-collections.rst new file mode 100644 index 0000000000..03d9da5d61 --- /dev/null +++ b/sites/all/libraries/vendor/doctrine/collections/docs/en/derived-collections.rst @@ -0,0 +1,26 @@ +Derived Collections +=================== + +You can create custom collection classes by extending the +``Doctrine\Common\Collections\ArrayCollection`` class. If the +``__construct`` semantics are different from the default ``ArrayCollection`` +you can override the ``createFrom`` method: + +.. code-block:: php + final class DerivedArrayCollection extends ArrayCollection + { + /** @var \stdClass */ + private $foo; + + public function __construct(\stdClass $foo, array $elements = []) + { + $this->foo = $foo; + + parent::__construct($elements); + } + + protected function createFrom(array $elements) : self + { + return new static($this->foo, $elements); + } + } diff --git a/sites/all/libraries/vendor/doctrine/collections/docs/en/expression-builder.rst b/sites/all/libraries/vendor/doctrine/collections/docs/en/expression-builder.rst new file mode 100644 index 0000000000..ad4055b543 --- /dev/null +++ b/sites/all/libraries/vendor/doctrine/collections/docs/en/expression-builder.rst @@ -0,0 +1,173 @@ +Expression Builder +================== + +The Expression Builder is a convenient fluent interface for +building expressions to be used with the ``Doctrine\Common\Collections\Criteria`` +class: + +.. code-block:: php + $expressionBuilder = Criteria::expr(); + + $criteria = new Criteria(); + $criteria->where($expressionBuilder->eq('name', 'jwage')); + $criteria->orWhere($expressionBuilder->eq('name', 'romanb')); + + $collection->matching($criteria); + +The ``ExpressionBuilder`` has the following API: + +andX +---- + +.. code-block:: php + $expressionBuilder = Criteria::expr(); + + $expression = $expressionBuilder->andX( + $expressionBuilder->eq('foo', 1), + $expressionBuilder->eq('bar', 1) + ); + + $collection->matching(new Criteria($expression)); + +orX +--- + +.. code-block:: php + $expressionBuilder = Criteria::expr(); + + $expression = $expressionBuilder->orX( + $expressionBuilder->eq('foo', 1), + $expressionBuilder->eq('bar', 1) + ); + + $collection->matching(new Criteria($expression)); + +eq +--- + +.. code-block:: php + $expressionBuilder = Criteria::expr(); + + $expression = $expressionBuilder->eq('foo', 1); + + $collection->matching(new Criteria($expression)); + +gt +--- + +.. code-block:: php + $expressionBuilder = Criteria::expr(); + + $expression = $expressionBuilder->gt('foo', 1); + + $collection->matching(new Criteria($expression)); + +lt +--- + +.. code-block:: php + $expressionBuilder = Criteria::expr(); + + $expression = $expressionBuilder->lt('foo', 1); + + $collection->matching(new Criteria($expression)); + +gte +--- + +.. code-block:: php + $expressionBuilder = Criteria::expr(); + + $expression = $expressionBuilder->gte('foo', 1); + + $collection->matching(new Criteria($expression)); + +lte +--- + +.. code-block:: php + $expressionBuilder = Criteria::expr(); + + $expression = $expressionBuilder->lte('foo', 1); + + $collection->matching(new Criteria($expression)); + +neq +--- + +.. code-block:: php + $expressionBuilder = Criteria::expr(); + + $expression = $expressionBuilder->neq('foo', 1); + + $collection->matching(new Criteria($expression)); + +isNull +------ + +.. code-block:: php + $expressionBuilder = Criteria::expr(); + + $expression = $expressionBuilder->isNull('foo'); + + $collection->matching(new Criteria($expression)); + +in +--- + +.. code-block:: php + $expressionBuilder = Criteria::expr(); + + $expression = $expressionBuilder->in('foo', ['value1', 'value2']); + + $collection->matching(new Criteria($expression)); + +notIn +----- + +.. code-block:: php + $expressionBuilder = Criteria::expr(); + + $expression = $expressionBuilder->notIn('foo', ['value1', 'value2']); + + $collection->matching(new Criteria($expression)); + +contains +-------- + +.. code-block:: php + $expressionBuilder = Criteria::expr(); + + $expression = $expressionBuilder->contains('foo', 'value1'); + + $collection->matching(new Criteria($expression)); + +memberOf +-------- + +.. code-block:: php + $expressionBuilder = Criteria::expr(); + + $expression = $expressionBuilder->memberOf('foo', ['value1', 'value2']); + + $collection->matching(new Criteria($expression)); + +startsWith +---------- + +.. code-block:: php + $expressionBuilder = Criteria::expr(); + + $expression = $expressionBuilder->startsWith('foo', 'hello'); + + $collection->matching(new Criteria($expression)); + +endsWith +-------- + +.. code-block:: php + $expressionBuilder = Criteria::expr(); + + $expression = $expressionBuilder->endsWith('foo', 'world'); + + $collection->matching(new Criteria($expression)); diff --git a/sites/all/libraries/vendor/doctrine/collections/docs/en/expressions.rst b/sites/all/libraries/vendor/doctrine/collections/docs/en/expressions.rst new file mode 100644 index 0000000000..db943b246b --- /dev/null +++ b/sites/all/libraries/vendor/doctrine/collections/docs/en/expressions.rst @@ -0,0 +1,102 @@ +Expressions +=========== + +The ``Doctrine\Common\Collections\Expr\Comparison`` class +can be used to create expressions to be used with the +``Doctrine\Common\Collections\Criteria`` class. It has the +following operator constants: + +- ``Comparison::EQ`` +- ``Comparison::NEQ`` +- ``Comparison::LT`` +- ``Comparison::LTE`` +- ``Comparison::GT`` +- ``Comparison::GTE`` +- ``Comparison::IS`` +- ``Comparison::IN`` +- ``Comparison::NIN`` +- ``Comparison::CONTAINS`` +- ``Comparison::MEMBER_OF`` +- ``Comparison::STARTS_WITH`` +- ``Comparison::ENDS_WITH`` + +The ``Doctrine\Common\Collections\Criteria`` class has the following +API to be used with expressions: + +where +----- + +Sets the where expression to evaluate when this Criteria is searched for. + +.. code-block:: php + $expr = new Comparison('key', Comparison::EQ, 'value'); + + $criteria->where($expr); + +andWhere +-------- + +Appends the where expression to evaluate when this Criteria is searched for +using an AND with previous expression. + +.. code-block:: php + $expr = new Comparison('key', Comparison::EQ, 'value'); + + $criteria->andWhere($expr); + +orWhere +------- + +Appends the where expression to evaluate when this Criteria is searched for +using an OR with previous expression. + +.. code-block:: php + $expr1 = new Comparison('key', Comparison::EQ, 'value1'); + $expr2 = new Comparison('key', Comparison::EQ, 'value2'); + + $criteria->where($expr1); + $criteria->orWhere($expr2); + +orderBy +------- + +Sets the ordering of the result of this Criteria. + +.. code-block:: php + $criteria->orderBy(['name' => Criteria::ASC]); + +setFirstResult +-------------- + +Set the number of first result that this Criteria should return. + +.. code-block:: php + $criteria->setFirstResult(0); + +getFirstResult +-------------- + +Gets the current first result option of this Criteria. + +.. code-block:: php + $criteria->setFirstResult(10); + + echo $criteria->getFirstResult(); // 10 + +setMaxResults +------------- + +Sets the max results that this Criteria should return. + +.. code-block:: php + $criteria->setMaxResults(20); + +getMaxResults +------------- + +Gets the current max results option of this Criteria. + +.. code-block:: php + $criteria->setMaxResults(20); + + echo $criteria->getMaxResults(); // 20 diff --git a/sites/all/libraries/vendor/doctrine/collections/docs/en/index.rst b/sites/all/libraries/vendor/doctrine/collections/docs/en/index.rst new file mode 100644 index 0000000000..8ca97229b4 --- /dev/null +++ b/sites/all/libraries/vendor/doctrine/collections/docs/en/index.rst @@ -0,0 +1,333 @@ +Introduction +============ + +Doctrine Collections is a library that contains classes for working with +arrays of data. Here is an example using the simple +``Doctrine\Common\Collections\ArrayCollection`` class: + +.. code-block:: php + filter(function($element) { + return $element > 1; + }); // [2, 3] + +Collection Methods +================== + +Doctrine Collections provides an interface named ``Doctrine\Common\Collections\Collection`` +that resembles the nature of a regular PHP array. That is, +it is essentially an **ordered map** that can also be used +like a list. + +A Collection has an internal iterator just like a PHP array. In addition, +a Collection can be iterated with external iterators, which is preferable. +To use an external iterator simply use the foreach language construct to +iterate over the collection, which calls ``getIterator()`` internally, or +explicitly retrieve an iterator though ``getIterator()`` which can then be +used to iterate over the collection. You can not rely on the internal iterator +of the collection being at a certain position unless you explicitly positioned it before. + +Methods that do not alter the collection or have template types +appearing in invariant or contravariant positions are not directly +defined in ``Doctrine\Common\Collections\Collection``, but are inherited +from the ``Doctrine\Common\Collections\ReadableCollection`` interface. + +The methods available on the interface are: + +add +--- + +Adds an element at the end of the collection. + +.. code-block:: php + $collection->add('test'); + +clear +----- + +Clears the collection, removing all elements. + +.. code-block:: php + $collection->clear(); + +contains +-------- + +Checks whether an element is contained in the collection. This is an O(n) operation, where n is the size of the collection. + +.. code-block:: php + $collection = new Collection(['test']); + + $contains = $collection->contains('test'); // true + +containsKey +----------- + +Checks whether the collection contains an element with the specified key/index. + +.. code-block:: php + $collection = new Collection(['test' => true]); + + $contains = $collection->containsKey('test'); // true + +current +------- + +Gets the element of the collection at the current iterator position. + +.. code-block:: php + $collection = new Collection(['first', 'second', 'third']); + + $current = $collection->current(); // first + +get +--- + +Gets the element at the specified key/index. + +.. code-block:: php + $collection = new Collection([ + 'key' => 'value', + ]); + + $value = $collection->get('key'); // value + +getKeys +------- + +Gets all keys/indices of the collection. + +.. code-block:: php + $collection = new Collection(['a', 'b', 'c']); + + $keys = $collection->getKeys(); // [0, 1, 2] + +getValues +--------- + +Gets all values of the collection. + +.. code-block:: php + $collection = new Collection([ + 'key1' => 'value1', + 'key2' => 'value2', + 'key3' => 'value3', + ]); + + $values = $collection->getValues(); // ['value1', 'value2', 'value3'] + +isEmpty +------- + +Checks whether the collection is empty (contains no elements). + +.. code-block:: php + $collection = new Collection(['a', 'b', 'c']); + + $isEmpty = $collection->isEmpty(); // false + +first +----- + +Sets the internal iterator to the first element in the collection and returns this element. + +.. code-block:: php + $collection = new Collection(['first', 'second', 'third']); + + $first = $collection->first(); // first + +exists +------ + +Tests for the existence of an element that satisfies the given predicate. + +.. code-block:: php + $collection = new Collection(['first', 'second', 'third']); + + $exists = $collection->exists(function($key, $value) { + return $value === 'first'; + }); // true + +filter +------ + +Returns all the elements of this collection for which your callback function returns `true`. +The order and keys of the elements are preserved. + +.. code-block:: php + $collection = new ArrayCollection([1, 2, 3]); + + $filteredCollection = $collection->filter(function($element) { + return $element > 1; + }); // [2, 3] + +forAll +------ + +Tests whether the given predicate holds for all elements of this collection. + +.. code-block:: php + $collection = new ArrayCollection([1, 2, 3]); + + $forAll = $collection->forAll(function($key, $value) { + return $value > 1; + }); // false + +indexOf +------- + +Gets the index/key of a given element. The comparison of two elements is strict, that means not only the value but also the type must match. For objects this means reference equality. + +.. code-block:: php + $collection = new ArrayCollection([1, 2, 3]); + + $indexOf = $collection->indexOf(3); // 2 + +key +--- + +Gets the key/index of the element at the current iterator position. + +.. code-block:: php + $collection = new ArrayCollection([1, 2, 3]); + + $collection->next(); + + $key = $collection->key(); // 1 + +last +---- + +Sets the internal iterator to the last element in the collection and returns this element. + +.. code-block:: php + $collection = new ArrayCollection([1, 2, 3]); + + $last = $collection->last(); // 3 + +map +--- + +Applies the given function to each element in the collection and returns a new collection with the elements returned by the function. + +.. code-block:: php + $collection = new ArrayCollection([1, 2, 3]); + + $mappedCollection = $collection->map(function($value) { + return $value + 1; + }); // [2, 3, 4] + +next +---- + +Moves the internal iterator position to the next element and returns this element. + +.. code-block:: php + $collection = new ArrayCollection([1, 2, 3]); + + $next = $collection->next(); // 2 + +partition +--------- + +Partitions this collection in two collections according to a predicate. Keys are preserved in the resulting collections. + +.. code-block:: php + $collection = new ArrayCollection([1, 2, 3]); + + $mappedCollection = $collection->partition(function($key, $value) { + return $value > 1 + }); // [[2, 3], [1]] + +remove +------ + +Removes the element at the specified index from the collection. + +.. code-block:: php + $collection = new ArrayCollection([1, 2, 3]); + + $collection->remove(0); // [2, 3] + +removeElement +------------- + +Removes the specified element from the collection, if it is found. + +.. code-block:: php + $collection = new ArrayCollection([1, 2, 3]); + + $collection->removeElement(3); // [1, 2] + +set +--- + +Sets an element in the collection at the specified key/index. + +.. code-block:: php + $collection = new ArrayCollection(); + + $collection->set('name', 'jwage'); + +slice +----- + +Extracts a slice of $length elements starting at position $offset from the Collection. If $length is null it returns all elements from $offset to the end of the Collection. Keys have to be preserved by this method. Calling this method will only return the selected slice and NOT change the elements contained in the collection slice is called on. + +.. code-block:: php + $collection = new ArrayCollection([0, 1, 2, 3, 4, 5]); + + $slice = $collection->slice(1, 2); // [1, 2] + +toArray +------- + +Gets a native PHP array representation of the collection. + +.. code-block:: php + $collection = new ArrayCollection([0, 1, 2, 3, 4, 5]); + + $array = $collection->toArray(); // [0, 1, 2, 3, 4, 5] + +Selectable Methods +================== + +Some Doctrine Collections, like ``Doctrine\Common\Collections\ArrayCollection``, +implement an interface named ``Doctrine\Common\Collections\Selectable`` +that offers the usage of a powerful expressions API, where conditions +can be applied to a collection to get a result with matching elements +only. + +matching +-------- + +Selects all elements from a selectable that match the expression and +returns a new collection containing these elements. + +.. code-block:: php + use Doctrine\Common\Collections\Criteria; + use Doctrine\Common\Collections\Expr\Comparison; + + $collection = new ArrayCollection([ + [ + 'name' => 'jwage', + ], + [ + 'name' => 'romanb', + ], + ]); + + $expr = new Comparison('name', '=', 'jwage'); + + $criteria = new Criteria(); + + $criteria->where($expr); + + $matched = $collection->matching($criteria); // ['jwage'] + +You can read more about expressions :ref:`here `. diff --git a/sites/all/libraries/vendor/doctrine/collections/docs/en/lazy-collections.rst b/sites/all/libraries/vendor/doctrine/collections/docs/en/lazy-collections.rst new file mode 100644 index 0000000000..b9cafb60bf --- /dev/null +++ b/sites/all/libraries/vendor/doctrine/collections/docs/en/lazy-collections.rst @@ -0,0 +1,26 @@ +Lazy Collections +================ + +To create a lazy collection you can extend the +``Doctrine\Common\Collections\AbstractLazyCollection`` class +and define the ``doInitialize`` method. Here is an example where +we lazily query the database for a collection of user records: + +.. code-block:: php + use Doctrine\DBAL\Connection; + + class UsersLazyCollection extends AbstractLazyCollection + { + /** @var Connection */ + private $connection; + + public function __construct(Connection $connection) + { + $this->connection = $connection; + } + + protected function doInitialize() : void + { + $this->collection = $this->connection->fetchAll('SELECT * FROM users'); + } + } diff --git a/sites/all/libraries/vendor/doctrine/collections/docs/en/sidebar.rst b/sites/all/libraries/vendor/doctrine/collections/docs/en/sidebar.rst new file mode 100644 index 0000000000..69279a0bb9 --- /dev/null +++ b/sites/all/libraries/vendor/doctrine/collections/docs/en/sidebar.rst @@ -0,0 +1,8 @@ +.. toctree:: + :depth: 3 + + index + expressions + expression-builder + derived-collections + lazy-collections diff --git a/sites/all/libraries/vendor/doctrine/collections/lib/Doctrine/Common/Collections/AbstractLazyCollection.php b/sites/all/libraries/vendor/doctrine/collections/lib/Doctrine/Common/Collections/AbstractLazyCollection.php new file mode 100644 index 0000000000..baab4d599c --- /dev/null +++ b/sites/all/libraries/vendor/doctrine/collections/lib/Doctrine/Common/Collections/AbstractLazyCollection.php @@ -0,0 +1,389 @@ + + */ +abstract class AbstractLazyCollection implements Collection +{ + /** + * The backed collection to use + * + * @psalm-var Collection|null + * @var Collection|null + */ + protected $collection; + + /** @var bool */ + protected $initialized = false; + + /** + * {@inheritDoc} + * + * @return int + */ + #[ReturnTypeWillChange] + public function count() + { + $this->initialize(); + + return $this->collection->count(); + } + + /** + * {@inheritDoc} + */ + public function add($element) + { + $this->initialize(); + + return $this->collection->add($element); + } + + /** + * {@inheritDoc} + */ + public function clear() + { + $this->initialize(); + $this->collection->clear(); + } + + /** + * {@inheritDoc} + * + * @template TMaybeContained + */ + public function contains($element) + { + $this->initialize(); + + return $this->collection->contains($element); + } + + /** + * {@inheritDoc} + */ + public function isEmpty() + { + $this->initialize(); + + return $this->collection->isEmpty(); + } + + /** + * {@inheritDoc} + */ + public function remove($key) + { + $this->initialize(); + + return $this->collection->remove($key); + } + + /** + * {@inheritDoc} + */ + public function removeElement($element) + { + $this->initialize(); + + return $this->collection->removeElement($element); + } + + /** + * {@inheritDoc} + */ + public function containsKey($key) + { + $this->initialize(); + + return $this->collection->containsKey($key); + } + + /** + * {@inheritDoc} + */ + public function get($key) + { + $this->initialize(); + + return $this->collection->get($key); + } + + /** + * {@inheritDoc} + */ + public function getKeys() + { + $this->initialize(); + + return $this->collection->getKeys(); + } + + /** + * {@inheritDoc} + */ + public function getValues() + { + $this->initialize(); + + return $this->collection->getValues(); + } + + /** + * {@inheritDoc} + */ + public function set($key, $value) + { + $this->initialize(); + $this->collection->set($key, $value); + } + + /** + * {@inheritDoc} + */ + public function toArray() + { + $this->initialize(); + + return $this->collection->toArray(); + } + + /** + * {@inheritDoc} + */ + public function first() + { + $this->initialize(); + + return $this->collection->first(); + } + + /** + * {@inheritDoc} + */ + public function last() + { + $this->initialize(); + + return $this->collection->last(); + } + + /** + * {@inheritDoc} + */ + public function key() + { + $this->initialize(); + + return $this->collection->key(); + } + + /** + * {@inheritDoc} + */ + public function current() + { + $this->initialize(); + + return $this->collection->current(); + } + + /** + * {@inheritDoc} + */ + public function next() + { + $this->initialize(); + + return $this->collection->next(); + } + + /** + * {@inheritDoc} + */ + public function exists(Closure $p) + { + $this->initialize(); + + return $this->collection->exists($p); + } + + /** + * {@inheritDoc} + */ + public function filter(Closure $p) + { + $this->initialize(); + + return $this->collection->filter($p); + } + + /** + * {@inheritDoc} + */ + public function forAll(Closure $p) + { + $this->initialize(); + + return $this->collection->forAll($p); + } + + /** + * {@inheritDoc} + */ + public function map(Closure $func) + { + $this->initialize(); + + return $this->collection->map($func); + } + + /** + * {@inheritDoc} + */ + public function partition(Closure $p) + { + $this->initialize(); + + return $this->collection->partition($p); + } + + /** + * {@inheritDoc} + * + * @template TMaybeContained + */ + public function indexOf($element) + { + $this->initialize(); + + return $this->collection->indexOf($element); + } + + /** + * {@inheritDoc} + */ + public function slice($offset, $length = null) + { + $this->initialize(); + + return $this->collection->slice($offset, $length); + } + + /** + * {@inheritDoc} + * + * @return Traversable + * @psalm-return Traversable + */ + #[ReturnTypeWillChange] + public function getIterator() + { + $this->initialize(); + + return $this->collection->getIterator(); + } + + /** + * @param TKey $offset + * + * @return bool + */ + #[ReturnTypeWillChange] + public function offsetExists($offset) + { + $this->initialize(); + + return $this->collection->offsetExists($offset); + } + + /** + * @param TKey $offset + * + * @return mixed + */ + #[ReturnTypeWillChange] + public function offsetGet($offset) + { + $this->initialize(); + + return $this->collection->offsetGet($offset); + } + + /** + * @param TKey|null $offset + * @param T $value + * + * @return void + */ + #[ReturnTypeWillChange] + public function offsetSet($offset, $value) + { + $this->initialize(); + $this->collection->offsetSet($offset, $value); + } + + /** + * @param TKey $offset + * + * @return void + */ + #[ReturnTypeWillChange] + public function offsetUnset($offset) + { + $this->initialize(); + $this->collection->offsetUnset($offset); + } + + /** + * Is the lazy collection already initialized? + * + * @return bool + * + * @psalm-assert-if-true Collection $this->collection + */ + public function isInitialized() + { + return $this->initialized; + } + + /** + * Initialize the collection + * + * @return void + * + * @psalm-assert Collection $this->collection + */ + protected function initialize() + { + if ($this->initialized) { + return; + } + + $this->doInitialize(); + $this->initialized = true; + + if ($this->collection === null) { + throw new LogicException('You must initialize the collection property in the doInitialize() method.'); + } + } + + /** + * Do the initialization logic + * + * @return void + */ + abstract protected function doInitialize(); +} diff --git a/sites/all/libraries/vendor/doctrine/collections/lib/Doctrine/Common/Collections/ArrayCollection.php b/sites/all/libraries/vendor/doctrine/collections/lib/Doctrine/Common/Collections/ArrayCollection.php new file mode 100644 index 0000000000..186f6ec4cc --- /dev/null +++ b/sites/all/libraries/vendor/doctrine/collections/lib/Doctrine/Common/Collections/ArrayCollection.php @@ -0,0 +1,466 @@ + + * @template-implements Selectable + * @psalm-consistent-constructor + */ +class ArrayCollection implements Collection, Selectable +{ + /** + * An array containing the entries of this collection. + * + * @psalm-var array + * @var mixed[] + */ + private $elements; + + /** + * Initializes a new ArrayCollection. + * + * @param array $elements + * @psalm-param array $elements + */ + public function __construct(array $elements = []) + { + $this->elements = $elements; + } + + /** + * {@inheritDoc} + */ + public function toArray() + { + return $this->elements; + } + + /** + * {@inheritDoc} + */ + public function first() + { + return reset($this->elements); + } + + /** + * Creates a new instance from the specified elements. + * + * This method is provided for derived classes to specify how a new + * instance should be created when constructor semantics have changed. + * + * @param array $elements Elements. + * @psalm-param array $elements + * + * @return static + * @psalm-return static + * + * @psalm-template K of array-key + * @psalm-template V + */ + protected function createFrom(array $elements) + { + return new static($elements); + } + + /** + * {@inheritDoc} + */ + public function last() + { + return end($this->elements); + } + + /** + * {@inheritDoc} + */ + public function key() + { + return key($this->elements); + } + + /** + * {@inheritDoc} + */ + public function next() + { + return next($this->elements); + } + + /** + * {@inheritDoc} + */ + public function current() + { + return current($this->elements); + } + + /** + * {@inheritDoc} + */ + public function remove($key) + { + if (! isset($this->elements[$key]) && ! array_key_exists($key, $this->elements)) { + return null; + } + + $removed = $this->elements[$key]; + unset($this->elements[$key]); + + return $removed; + } + + /** + * {@inheritDoc} + */ + public function removeElement($element) + { + $key = array_search($element, $this->elements, true); + + if ($key === false) { + return false; + } + + unset($this->elements[$key]); + + return true; + } + + /** + * Required by interface ArrayAccess. + * + * @param TKey $offset + * + * @return bool + */ + #[ReturnTypeWillChange] + public function offsetExists($offset) + { + return $this->containsKey($offset); + } + + /** + * Required by interface ArrayAccess. + * + * @param TKey $offset + * + * @return mixed + */ + #[ReturnTypeWillChange] + public function offsetGet($offset) + { + return $this->get($offset); + } + + /** + * Required by interface ArrayAccess. + * + * @param TKey|null $offset + * @param T $value + * + * @return void + */ + #[ReturnTypeWillChange] + public function offsetSet($offset, $value) + { + if (! isset($offset)) { + $this->add($value); + + return; + } + + $this->set($offset, $value); + } + + /** + * Required by interface ArrayAccess. + * + * @param TKey $offset + * + * @return void + */ + #[ReturnTypeWillChange] + public function offsetUnset($offset) + { + $this->remove($offset); + } + + /** + * {@inheritDoc} + */ + public function containsKey($key) + { + return isset($this->elements[$key]) || array_key_exists($key, $this->elements); + } + + /** + * {@inheritDoc} + * + * @template TMaybeContained + */ + public function contains($element) + { + return in_array($element, $this->elements, true); + } + + /** + * {@inheritDoc} + */ + public function exists(Closure $p) + { + foreach ($this->elements as $key => $element) { + if ($p($key, $element)) { + return true; + } + } + + return false; + } + + /** + * {@inheritDoc} + * + * @psalm-param TMaybeContained $element + * + * @psalm-return (TMaybeContained is T ? TKey|false : false) + * + * @template TMaybeContained + */ + public function indexOf($element) + { + return array_search($element, $this->elements, true); + } + + /** + * {@inheritDoc} + */ + public function get($key) + { + return $this->elements[$key] ?? null; + } + + /** + * {@inheritDoc} + */ + public function getKeys() + { + return array_keys($this->elements); + } + + /** + * {@inheritDoc} + */ + public function getValues() + { + return array_values($this->elements); + } + + /** + * {@inheritDoc} + * + * @return int + */ + #[ReturnTypeWillChange] + public function count() + { + return count($this->elements); + } + + /** + * {@inheritDoc} + */ + public function set($key, $value) + { + $this->elements[$key] = $value; + } + + /** + * {@inheritDoc} + * + * @psalm-suppress InvalidPropertyAssignmentValue + * + * This breaks assumptions about the template type, but it would + * be a backwards-incompatible change to remove this method + */ + public function add($element) + { + $this->elements[] = $element; + + return true; + } + + /** + * {@inheritDoc} + */ + public function isEmpty() + { + return empty($this->elements); + } + + /** + * {@inheritDoc} + * + * @return Traversable + * @psalm-return Traversable + */ + #[ReturnTypeWillChange] + public function getIterator() + { + return new ArrayIterator($this->elements); + } + + /** + * {@inheritDoc} + * + * @psalm-param Closure(T):U $func + * + * @return static + * @psalm-return static + * + * @psalm-template U + */ + public function map(Closure $func) + { + return $this->createFrom(array_map($func, $this->elements)); + } + + /** + * {@inheritDoc} + * + * @return static + * @psalm-return static + */ + public function filter(Closure $p) + { + return $this->createFrom(array_filter($this->elements, $p, ARRAY_FILTER_USE_BOTH)); + } + + /** + * {@inheritDoc} + */ + public function forAll(Closure $p) + { + foreach ($this->elements as $key => $element) { + if (! $p($key, $element)) { + return false; + } + } + + return true; + } + + /** + * {@inheritDoc} + */ + public function partition(Closure $p) + { + $matches = $noMatches = []; + + foreach ($this->elements as $key => $element) { + if ($p($key, $element)) { + $matches[$key] = $element; + } else { + $noMatches[$key] = $element; + } + } + + return [$this->createFrom($matches), $this->createFrom($noMatches)]; + } + + /** + * Returns a string representation of this object. + * + * @return string + */ + public function __toString() + { + return self::class . '@' . spl_object_hash($this); + } + + /** + * {@inheritDoc} + */ + public function clear() + { + $this->elements = []; + } + + /** + * {@inheritDoc} + */ + public function slice($offset, $length = null) + { + return array_slice($this->elements, $offset, $length, true); + } + + /** + * {@inheritDoc} + */ + public function matching(Criteria $criteria) + { + $expr = $criteria->getWhereExpression(); + $filtered = $this->elements; + + if ($expr) { + $visitor = new ClosureExpressionVisitor(); + $filter = $visitor->dispatch($expr); + $filtered = array_filter($filtered, $filter); + } + + $orderings = $criteria->getOrderings(); + + if ($orderings) { + $next = null; + foreach (array_reverse($orderings) as $field => $ordering) { + $next = ClosureExpressionVisitor::sortByField($field, $ordering === Criteria::DESC ? -1 : 1, $next); + } + + uasort($filtered, $next); + } + + $offset = $criteria->getFirstResult(); + $length = $criteria->getMaxResults(); + + if ($offset || $length) { + $filtered = array_slice($filtered, (int) $offset, $length); + } + + return $this->createFrom($filtered); + } +} diff --git a/sites/all/libraries/vendor/doctrine/collections/lib/Doctrine/Common/Collections/Collection.php b/sites/all/libraries/vendor/doctrine/collections/lib/Doctrine/Common/Collections/Collection.php new file mode 100644 index 0000000000..fb091923c7 --- /dev/null +++ b/sites/all/libraries/vendor/doctrine/collections/lib/Doctrine/Common/Collections/Collection.php @@ -0,0 +1,99 @@ +ordered map
that can also be used + * like a list. + * + * A Collection has an internal iterator just like a PHP array. In addition, + * a Collection can be iterated with external iterators, which is preferable. + * To use an external iterator simply use the foreach language construct to + * iterate over the collection (which calls {@link getIterator()} internally) or + * explicitly retrieve an iterator though {@link getIterator()} which can then be + * used to iterate over the collection. + * You can not rely on the internal iterator of the collection being at a certain + * position unless you explicitly positioned it before. Prefer iteration with + * external iterators. + * + * @psalm-template TKey of array-key + * @psalm-template T + * @template-extends ReadableCollection + * @template-extends ArrayAccess + */ +interface Collection extends ReadableCollection, ArrayAccess +{ + /** + * Adds an element at the end of the collection. + * + * @param mixed $element The element to add. + * @psalm-param T $element + * + * @return true Always TRUE. + */ + public function add($element); + + /** + * Clears the collection, removing all elements. + * + * @return void + */ + public function clear(); + + /** + * Removes the element at the specified index from the collection. + * + * @param string|int $key The key/index of the element to remove. + * @psalm-param TKey $key + * + * @return mixed The removed element or NULL, if the collection did not contain the element. + * @psalm-return T|null + */ + public function remove($key); + + /** + * Removes the specified element from the collection, if it is found. + * + * @param mixed $element The element to remove. + * @psalm-param T $element + * + * @return bool TRUE if this collection contained the specified element, FALSE otherwise. + */ + public function removeElement($element); + + /** + * Sets an element in the collection at the specified key/index. + * + * @param string|int $key The key/index of the element to set. + * @param mixed $value The element to set. + * @psalm-param TKey $key + * @psalm-param T $value + * + * @return void + */ + public function set($key, $value); + + /** + * {@inheritdoc} + * + * @return Collection A collection with the results of the filter operation. + * @psalm-return Collection + */ + public function filter(Closure $p); + + /** + * {@inheritdoc} + * + * @return Collection[] An array with two elements. The first element contains the collection + * of elements where the predicate returned TRUE, the second element + * contains the collection of elements where the predicate returned FALSE. + * @psalm-return array{0: Collection, 1: Collection} + */ + public function partition(Closure $p); +} diff --git a/sites/all/libraries/vendor/doctrine/collections/lib/Doctrine/Common/Collections/Criteria.php b/sites/all/libraries/vendor/doctrine/collections/lib/Doctrine/Common/Collections/Criteria.php new file mode 100644 index 0000000000..c1639d3f3e --- /dev/null +++ b/sites/all/libraries/vendor/doctrine/collections/lib/Doctrine/Common/Collections/Criteria.php @@ -0,0 +1,245 @@ +expression = $expression; + + if ($firstResult === null && func_num_args() > 2) { + Deprecation::trigger( + 'doctrine/collections', + 'https://github.com/doctrine/collections/pull/311', + 'Passing null as $firstResult to the constructor of %s is deprecated. Pass 0 instead or omit the argument.', + self::class + ); + } + + $this->setFirstResult($firstResult); + $this->setMaxResults($maxResults); + + if ($orderings === null) { + return; + } + + $this->orderBy($orderings); + } + + /** + * Sets the where expression to evaluate when this Criteria is searched for. + * + * @return $this + */ + public function where(Expression $expression) + { + $this->expression = $expression; + + return $this; + } + + /** + * Appends the where expression to evaluate when this Criteria is searched for + * using an AND with previous expression. + * + * @return $this + */ + public function andWhere(Expression $expression) + { + if ($this->expression === null) { + return $this->where($expression); + } + + $this->expression = new CompositeExpression( + CompositeExpression::TYPE_AND, + [$this->expression, $expression] + ); + + return $this; + } + + /** + * Appends the where expression to evaluate when this Criteria is searched for + * using an OR with previous expression. + * + * @return $this + */ + public function orWhere(Expression $expression) + { + if ($this->expression === null) { + return $this->where($expression); + } + + $this->expression = new CompositeExpression( + CompositeExpression::TYPE_OR, + [$this->expression, $expression] + ); + + return $this; + } + + /** + * Gets the expression attached to this Criteria. + * + * @return Expression|null + */ + public function getWhereExpression() + { + return $this->expression; + } + + /** + * Gets the current orderings of this Criteria. + * + * @return string[] + */ + public function getOrderings() + { + return $this->orderings; + } + + /** + * Sets the ordering of the result of this Criteria. + * + * Keys are field and values are the order, being either ASC or DESC. + * + * @see Criteria::ASC + * @see Criteria::DESC + * + * @param string[] $orderings + * + * @return $this + */ + public function orderBy(array $orderings) + { + $this->orderings = array_map( + static function (string $ordering): string { + return strtoupper($ordering) === Criteria::ASC ? Criteria::ASC : Criteria::DESC; + }, + $orderings + ); + + return $this; + } + + /** + * Gets the current first result option of this Criteria. + * + * @return int|null + */ + public function getFirstResult() + { + return $this->firstResult; + } + + /** + * Set the number of first result that this Criteria should return. + * + * @param int|null $firstResult The value to set. + * + * @return $this + */ + public function setFirstResult($firstResult) + { + if ($firstResult === null) { + Deprecation::triggerIfCalledFromOutside( + 'doctrine/collections', + 'https://github.com/doctrine/collections/pull/311', + 'Passing null to %s() is deprecated, pass 0 instead.', + __METHOD__ + ); + } + + $this->firstResult = $firstResult; + + return $this; + } + + /** + * Gets maxResults. + * + * @return int|null + */ + public function getMaxResults() + { + return $this->maxResults; + } + + /** + * Sets maxResults. + * + * @param int|null $maxResults The value to set. + * + * @return $this + */ + public function setMaxResults($maxResults) + { + $this->maxResults = $maxResults; + + return $this; + } +} diff --git a/sites/all/libraries/vendor/doctrine/collections/lib/Doctrine/Common/Collections/Expr/ClosureExpressionVisitor.php b/sites/all/libraries/vendor/doctrine/collections/lib/Doctrine/Common/Collections/Expr/ClosureExpressionVisitor.php new file mode 100644 index 0000000000..c10b8d516f --- /dev/null +++ b/sites/all/libraries/vendor/doctrine/collections/lib/Doctrine/Common/Collections/Expr/ClosureExpressionVisitor.php @@ -0,0 +1,269 @@ +$accessor(); + } + } + + if (preg_match('/^is[A-Z]+/', $field) === 1 && method_exists($object, $field)) { + return $object->$field(); + } + + // __call should be triggered for get. + $accessor = $accessors[0] . $field; + + if (method_exists($object, '__call')) { + return $object->$accessor(); + } + + if ($object instanceof ArrayAccess) { + return $object[$field]; + } + + if (isset($object->$field)) { + return $object->$field; + } + + // camelcase field name to support different variable naming conventions + $ccField = preg_replace_callback('/_(.?)/', static function ($matches) { + return strtoupper($matches[1]); + }, $field); + + foreach ($accessors as $accessor) { + $accessor .= $ccField; + + if (method_exists($object, $accessor)) { + return $object->$accessor(); + } + } + + return $object->$field; + } + + /** + * Helper for sorting arrays of objects based on multiple fields + orientations. + * + * @param string $name + * @param int $orientation + * + * @return Closure + */ + public static function sortByField($name, $orientation = 1, ?Closure $next = null) + { + if (! $next) { + $next = static function (): int { + return 0; + }; + } + + return static function ($a, $b) use ($name, $next, $orientation): int { + $aValue = ClosureExpressionVisitor::getObjectFieldValue($a, $name); + + $bValue = ClosureExpressionVisitor::getObjectFieldValue($b, $name); + + if ($aValue === $bValue) { + return $next($a, $b); + } + + return ($aValue > $bValue ? 1 : -1) * $orientation; + }; + } + + /** + * {@inheritDoc} + */ + public function walkComparison(Comparison $comparison) + { + $field = $comparison->getField(); + $value = $comparison->getValue()->getValue(); // shortcut for walkValue() + + switch ($comparison->getOperator()) { + case Comparison::EQ: + return static function ($object) use ($field, $value): bool { + return ClosureExpressionVisitor::getObjectFieldValue($object, $field) === $value; + }; + + case Comparison::NEQ: + return static function ($object) use ($field, $value): bool { + return ClosureExpressionVisitor::getObjectFieldValue($object, $field) !== $value; + }; + + case Comparison::LT: + return static function ($object) use ($field, $value): bool { + return ClosureExpressionVisitor::getObjectFieldValue($object, $field) < $value; + }; + + case Comparison::LTE: + return static function ($object) use ($field, $value): bool { + return ClosureExpressionVisitor::getObjectFieldValue($object, $field) <= $value; + }; + + case Comparison::GT: + return static function ($object) use ($field, $value): bool { + return ClosureExpressionVisitor::getObjectFieldValue($object, $field) > $value; + }; + + case Comparison::GTE: + return static function ($object) use ($field, $value): bool { + return ClosureExpressionVisitor::getObjectFieldValue($object, $field) >= $value; + }; + + case Comparison::IN: + return static function ($object) use ($field, $value): bool { + $fieldValue = ClosureExpressionVisitor::getObjectFieldValue($object, $field); + + return in_array($fieldValue, $value, is_scalar($fieldValue)); + }; + + case Comparison::NIN: + return static function ($object) use ($field, $value): bool { + $fieldValue = ClosureExpressionVisitor::getObjectFieldValue($object, $field); + + return ! in_array($fieldValue, $value, is_scalar($fieldValue)); + }; + + case Comparison::CONTAINS: + return static function ($object) use ($field, $value) { + return strpos(ClosureExpressionVisitor::getObjectFieldValue($object, $field), $value) !== false; + }; + + case Comparison::MEMBER_OF: + return static function ($object) use ($field, $value): bool { + $fieldValues = ClosureExpressionVisitor::getObjectFieldValue($object, $field); + + if (! is_array($fieldValues)) { + $fieldValues = iterator_to_array($fieldValues); + } + + return in_array($value, $fieldValues, true); + }; + + case Comparison::STARTS_WITH: + return static function ($object) use ($field, $value): bool { + return strpos(ClosureExpressionVisitor::getObjectFieldValue($object, $field), $value) === 0; + }; + + case Comparison::ENDS_WITH: + return static function ($object) use ($field, $value): bool { + return $value === substr(ClosureExpressionVisitor::getObjectFieldValue($object, $field), -strlen($value)); + }; + + default: + throw new RuntimeException('Unknown comparison operator: ' . $comparison->getOperator()); + } + } + + /** + * {@inheritDoc} + */ + public function walkValue(Value $value) + { + return $value->getValue(); + } + + /** + * {@inheritDoc} + */ + public function walkCompositeExpression(CompositeExpression $expr) + { + $expressionList = []; + + foreach ($expr->getExpressionList() as $child) { + $expressionList[] = $this->dispatch($child); + } + + switch ($expr->getType()) { + case CompositeExpression::TYPE_AND: + return $this->andExpressions($expressionList); + + case CompositeExpression::TYPE_OR: + return $this->orExpressions($expressionList); + + default: + throw new RuntimeException('Unknown composite ' . $expr->getType()); + } + } + + /** @param callable[] $expressions */ + private function andExpressions(array $expressions): callable + { + return static function ($object) use ($expressions): bool { + foreach ($expressions as $expression) { + if (! $expression($object)) { + return false; + } + } + + return true; + }; + } + + /** @param callable[] $expressions */ + private function orExpressions(array $expressions): callable + { + return static function ($object) use ($expressions): bool { + foreach ($expressions as $expression) { + if ($expression($object)) { + return true; + } + } + + return false; + }; + } +} diff --git a/sites/all/libraries/vendor/doctrine/collections/lib/Doctrine/Common/Collections/Expr/Comparison.php b/sites/all/libraries/vendor/doctrine/collections/lib/Doctrine/Common/Collections/Expr/Comparison.php new file mode 100644 index 0000000000..6762b8be06 --- /dev/null +++ b/sites/all/libraries/vendor/doctrine/collections/lib/Doctrine/Common/Collections/Expr/Comparison.php @@ -0,0 +1,74 @@ +'; + public const LT = '<'; + public const LTE = '<='; + public const GT = '>'; + public const GTE = '>='; + public const IS = '='; // no difference with EQ + public const IN = 'IN'; + public const NIN = 'NIN'; + public const CONTAINS = 'CONTAINS'; + public const MEMBER_OF = 'MEMBER_OF'; + public const STARTS_WITH = 'STARTS_WITH'; + public const ENDS_WITH = 'ENDS_WITH'; + + /** @var string */ + private $field; + + /** @var string */ + private $op; + + /** @var Value */ + private $value; + + /** + * @param string $field + * @param string $operator + * @param mixed $value + */ + public function __construct($field, $operator, $value) + { + if (! ($value instanceof Value)) { + $value = new Value($value); + } + + $this->field = $field; + $this->op = $operator; + $this->value = $value; + } + + /** @return string */ + public function getField() + { + return $this->field; + } + + /** @return Value */ + public function getValue() + { + return $this->value; + } + + /** @return string */ + public function getOperator() + { + return $this->op; + } + + /** + * {@inheritDoc} + */ + public function visit(ExpressionVisitor $visitor) + { + return $visitor->walkComparison($this); + } +} diff --git a/sites/all/libraries/vendor/doctrine/collections/lib/Doctrine/Common/Collections/Expr/CompositeExpression.php b/sites/all/libraries/vendor/doctrine/collections/lib/Doctrine/Common/Collections/Expr/CompositeExpression.php new file mode 100644 index 0000000000..174b21b8a6 --- /dev/null +++ b/sites/all/libraries/vendor/doctrine/collections/lib/Doctrine/Common/Collections/Expr/CompositeExpression.php @@ -0,0 +1,67 @@ +type = $type; + + foreach ($expressions as $expr) { + if ($expr instanceof Value) { + throw new RuntimeException('Values are not supported expressions as children of and/or expressions.'); + } + + if (! ($expr instanceof Expression)) { + throw new RuntimeException('No expression given to CompositeExpression.'); + } + + $this->expressions[] = $expr; + } + } + + /** + * Returns the list of expressions nested in this composite. + * + * @return Expression[] + */ + public function getExpressionList() + { + return $this->expressions; + } + + /** @return string */ + public function getType() + { + return $this->type; + } + + /** + * {@inheritDoc} + */ + public function visit(ExpressionVisitor $visitor) + { + return $visitor->walkCompositeExpression($this); + } +} diff --git a/sites/all/libraries/vendor/doctrine/collections/lib/Doctrine/Common/Collections/Expr/Expression.php b/sites/all/libraries/vendor/doctrine/collections/lib/Doctrine/Common/Collections/Expr/Expression.php new file mode 100644 index 0000000000..566d6d50de --- /dev/null +++ b/sites/all/libraries/vendor/doctrine/collections/lib/Doctrine/Common/Collections/Expr/Expression.php @@ -0,0 +1,12 @@ +walkComparison($expr); + + case $expr instanceof Value: + return $this->walkValue($expr); + + case $expr instanceof CompositeExpression: + return $this->walkCompositeExpression($expr); + + default: + throw new RuntimeException('Unknown Expression ' . get_class($expr)); + } + } +} diff --git a/sites/all/libraries/vendor/doctrine/collections/lib/Doctrine/Common/Collections/Expr/Value.php b/sites/all/libraries/vendor/doctrine/collections/lib/Doctrine/Common/Collections/Expr/Value.php new file mode 100644 index 0000000000..693d345b03 --- /dev/null +++ b/sites/all/libraries/vendor/doctrine/collections/lib/Doctrine/Common/Collections/Expr/Value.php @@ -0,0 +1,29 @@ +value = $value; + } + + /** @return mixed */ + public function getValue() + { + return $this->value; + } + + /** + * {@inheritDoc} + */ + public function visit(ExpressionVisitor $visitor) + { + return $visitor->walkValue($this); + } +} diff --git a/sites/all/libraries/vendor/doctrine/collections/lib/Doctrine/Common/Collections/ExpressionBuilder.php b/sites/all/libraries/vendor/doctrine/collections/lib/Doctrine/Common/Collections/ExpressionBuilder.php new file mode 100644 index 0000000000..ebb21caafe --- /dev/null +++ b/sites/all/libraries/vendor/doctrine/collections/lib/Doctrine/Common/Collections/ExpressionBuilder.php @@ -0,0 +1,181 @@ + + */ +interface ReadableCollection extends Countable, IteratorAggregate +{ + /** + * Checks whether an element is contained in the collection. + * This is an O(n) operation, where n is the size of the collection. + * + * @param mixed $element The element to search for. + * @psalm-param TMaybeContained $element + * + * @return bool TRUE if the collection contains the element, FALSE otherwise. + * @psalm-return (TMaybeContained is T ? bool : false) + * + * @template TMaybeContained + */ + public function contains($element); + + /** + * Checks whether the collection is empty (contains no elements). + * + * @return bool TRUE if the collection is empty, FALSE otherwise. + */ + public function isEmpty(); + + /** + * Checks whether the collection contains an element with the specified key/index. + * + * @param string|int $key The key/index to check for. + * @psalm-param TKey $key + * + * @return bool TRUE if the collection contains an element with the specified key/index, + * FALSE otherwise. + */ + public function containsKey($key); + + /** + * Gets the element at the specified key/index. + * + * @param string|int $key The key/index of the element to retrieve. + * @psalm-param TKey $key + * + * @return mixed + * @psalm-return T|null + */ + public function get($key); + + /** + * Gets all keys/indices of the collection. + * + * @return int[]|string[] The keys/indices of the collection, in the order of the corresponding + * elements in the collection. + * @psalm-return list + */ + public function getKeys(); + + /** + * Gets all values of the collection. + * + * @return mixed[] The values of all elements in the collection, in the + * order they appear in the collection. + * @psalm-return list + */ + public function getValues(); + + /** + * Gets a native PHP array representation of the collection. + * + * @return mixed[] + * @psalm-return array + */ + public function toArray(); + + /** + * Sets the internal iterator to the first element in the collection and returns this element. + * + * @return mixed + * @psalm-return T|false + */ + public function first(); + + /** + * Sets the internal iterator to the last element in the collection and returns this element. + * + * @return mixed + * @psalm-return T|false + */ + public function last(); + + /** + * Gets the key/index of the element at the current iterator position. + * + * @return int|string|null + * @psalm-return TKey|null + */ + public function key(); + + /** + * Gets the element of the collection at the current iterator position. + * + * @return mixed + * @psalm-return T|false + */ + public function current(); + + /** + * Moves the internal iterator position to the next element and returns this element. + * + * @return mixed + * @psalm-return T|false + */ + public function next(); + + /** + * Extracts a slice of $length elements starting at position $offset from the Collection. + * + * If $length is null it returns all elements from $offset to the end of the Collection. + * Keys have to be preserved by this method. Calling this method will only return the + * selected slice and NOT change the elements contained in the collection slice is called on. + * + * @param int $offset The offset to start from. + * @param int|null $length The maximum number of elements to return, or null for no limit. + * + * @return mixed[] + * @psalm-return array + */ + public function slice($offset, $length = null); + + /** + * Tests for the existence of an element that satisfies the given predicate. + * + * @param Closure $p The predicate. + * @psalm-param Closure(TKey, T):bool $p + * + * @return bool TRUE if the predicate is TRUE for at least one element, FALSE otherwise. + */ + public function exists(Closure $p); + + /** + * Returns all the elements of this collection that satisfy the predicate p. + * The order of the elements is preserved. + * + * @param Closure $p The predicate used for filtering. + * @psalm-param Closure(T):bool $p + * + * @return ReadableCollection A collection with the results of the filter operation. + * @psalm-return ReadableCollection + */ + public function filter(Closure $p); + + /** + * Applies the given function to each element in the collection and returns + * a new collection with the elements returned by the function. + * + * @psalm-param Closure(T):U $func + * + * @return Collection + * @psalm-return Collection + * + * @psalm-template U + */ + public function map(Closure $func); + + /** + * Partitions this collection in two collections according to a predicate. + * Keys are preserved in the resulting collections. + * + * @param Closure $p The predicate on which to partition. + * @psalm-param Closure(TKey, T):bool $p + * + * @return ReadableCollection[] An array with two elements. The first element contains the collection + * of elements where the predicate returned TRUE, the second element + * contains the collection of elements where the predicate returned FALSE. + * @psalm-return array{0: ReadableCollection, 1: ReadableCollection} + */ + public function partition(Closure $p); + + /** + * Tests whether the given predicate p holds for all elements of this collection. + * + * @param Closure $p The predicate. + * @psalm-param Closure(TKey, T):bool $p + * + * @return bool TRUE, if the predicate yields TRUE for all elements, FALSE otherwise. + */ + public function forAll(Closure $p); + + /** + * Gets the index/key of a given element. The comparison of two elements is strict, + * that means not only the value but also the type must match. + * For objects this means reference equality. + * + * @param mixed $element The element to search for. + * @psalm-param TMaybeContained $element + * + * @return int|string|bool The key/index of the element or FALSE if the element was not found. + * @psalm-return (TMaybeContained is T ? TKey|false : false) + * + * @template TMaybeContained + */ + public function indexOf($element); +} diff --git a/sites/all/libraries/vendor/doctrine/collections/lib/Doctrine/Common/Collections/Selectable.php b/sites/all/libraries/vendor/doctrine/collections/lib/Doctrine/Common/Collections/Selectable.php new file mode 100644 index 0000000000..9f7586de3a --- /dev/null +++ b/sites/all/libraries/vendor/doctrine/collections/lib/Doctrine/Common/Collections/Selectable.php @@ -0,0 +1,30 @@ +&Selectable + * @psalm-return Collection&Selectable + */ + public function matching(Criteria $criteria); +} diff --git a/sites/all/libraries/vendor/doctrine/collections/phpcs.xml.dist b/sites/all/libraries/vendor/doctrine/collections/phpcs.xml.dist new file mode 100644 index 0000000000..2ea04bb31d --- /dev/null +++ b/sites/all/libraries/vendor/doctrine/collections/phpcs.xml.dist @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + lib + tests + + + + + + + + + + + + + + tests/* + + + + tests/* + + + + lib/Doctrine/Common/Collections/AbstractLazyCollection.php + + + + tests/Doctrine/Tests/Common/Collections/ClosureExpressionVisitorTest.php + + + tests/Doctrine/Tests/Common/Collections/ClosureExpressionVisitorTest.php + + diff --git a/sites/all/libraries/vendor/doctrine/collections/phpstan.neon.dist b/sites/all/libraries/vendor/doctrine/collections/phpstan.neon.dist new file mode 100644 index 0000000000..0b85a3dc94 --- /dev/null +++ b/sites/all/libraries/vendor/doctrine/collections/phpstan.neon.dist @@ -0,0 +1,11 @@ +parameters: + level: 8 + paths: + - lib + ignoreErrors: + - + message: '~Parameter #1 \$key of method Doctrine\\Common\\Collections\\ArrayCollection::set\(\) expects TKey of \(int\|string\), int\|string given\.~' + path: 'lib/Doctrine/Common/Collections/ArrayCollection.php' + - + message: '~Cannot call method .* on Doctrine\\Common\\Collections\\Collection\|null\.~' + path: 'lib/Doctrine/Common/Collections/AbstractLazyCollection.php' diff --git a/sites/all/libraries/vendor/doctrine/collections/phpunit.xml.dist b/sites/all/libraries/vendor/doctrine/collections/phpunit.xml.dist new file mode 100644 index 0000000000..f9eb964804 --- /dev/null +++ b/sites/all/libraries/vendor/doctrine/collections/phpunit.xml.dist @@ -0,0 +1,21 @@ + + + + + + ./tests/Doctrine/ + + + + + + ./lib/Doctrine/ + + + diff --git a/sites/all/libraries/vendor/doctrine/collections/psalm.xml.dist b/sites/all/libraries/vendor/doctrine/collections/psalm.xml.dist new file mode 100644 index 0000000000..53ad4f2f45 --- /dev/null +++ b/sites/all/libraries/vendor/doctrine/collections/psalm.xml.dist @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/sites/all/libraries/vendor/doctrine/collections/tests/Doctrine/Tests/Common/Collections/AbstractLazyArrayCollectionTest.php b/sites/all/libraries/vendor/doctrine/collections/tests/Doctrine/Tests/Common/Collections/AbstractLazyArrayCollectionTest.php new file mode 100644 index 0000000000..522537a2f1 --- /dev/null +++ b/sites/all/libraries/vendor/doctrine/collections/tests/Doctrine/Tests/Common/Collections/AbstractLazyArrayCollectionTest.php @@ -0,0 +1,36 @@ + + */ + protected function buildCollection(array $elements = []): Collection + { + return new LazyArrayCollection(new ArrayCollection($elements)); + } + + public function testLazyCollection(): void + { + $collection = $this->buildCollection(['a', 'b', 'c']); + assert($collection instanceof LazyArrayCollection); + + self::assertFalse($collection->isInitialized()); + self::assertCount(3, $collection); + } +} diff --git a/sites/all/libraries/vendor/doctrine/collections/tests/Doctrine/Tests/Common/Collections/AbstractLazyCollectionTest.php b/sites/all/libraries/vendor/doctrine/collections/tests/Doctrine/Tests/Common/Collections/AbstractLazyCollectionTest.php new file mode 100644 index 0000000000..77a6f6b14d --- /dev/null +++ b/sites/all/libraries/vendor/doctrine/collections/tests/Doctrine/Tests/Common/Collections/AbstractLazyCollectionTest.php @@ -0,0 +1,19 @@ +collection = new LazyArrayCollection(new ArrayCollection()); + } +} diff --git a/sites/all/libraries/vendor/doctrine/collections/tests/Doctrine/Tests/Common/Collections/ArrayCollectionTest.php b/sites/all/libraries/vendor/doctrine/collections/tests/Doctrine/Tests/Common/Collections/ArrayCollectionTest.php new file mode 100644 index 0000000000..d36b0cec4b --- /dev/null +++ b/sites/all/libraries/vendor/doctrine/collections/tests/Doctrine/Tests/Common/Collections/ArrayCollectionTest.php @@ -0,0 +1,24 @@ + + */ + protected function buildCollection(array $elements = []): Collection + { + return new ArrayCollection($elements); + } +} diff --git a/sites/all/libraries/vendor/doctrine/collections/tests/Doctrine/Tests/Common/Collections/BaseArrayCollectionTest.php b/sites/all/libraries/vendor/doctrine/collections/tests/Doctrine/Tests/Common/Collections/BaseArrayCollectionTest.php new file mode 100644 index 0000000000..e16b236fba --- /dev/null +++ b/sites/all/libraries/vendor/doctrine/collections/tests/Doctrine/Tests/Common/Collections/BaseArrayCollectionTest.php @@ -0,0 +1,348 @@ + + */ + abstract protected function buildCollection(array $elements = []): Collection; + + /** @param object $obj */ + protected function isSelectable($obj): bool + { + return $obj instanceof Selectable; + } + + /** + * @param mixed[] $elements + * + * @dataProvider provideDifferentElements + */ + public function testToArray($elements): void + { + $collection = $this->buildCollection($elements); + + self::assertSame($elements, $collection->toArray()); + } + + /** + * @param mixed[] $elements + * + * @dataProvider provideDifferentElements + */ + public function testFirst($elements): void + { + $collection = $this->buildCollection($elements); + self::assertSame(reset($elements), $collection->first()); + } + + /** + * @param mixed[] $elements + * + * @dataProvider provideDifferentElements + */ + public function testLast($elements): void + { + $collection = $this->buildCollection($elements); + self::assertSame(end($elements), $collection->last()); + } + + /** + * @param mixed[] $elements + * + * @dataProvider provideDifferentElements + */ + public function testKey($elements): void + { + $collection = $this->buildCollection($elements); + + self::assertSame(key($elements), $collection->key()); + + next($elements); + $collection->next(); + + self::assertSame(key($elements), $collection->key()); + } + + /** + * @param mixed[] $elements + * + * @dataProvider provideDifferentElements + */ + public function testNext($elements): void + { + $collection = $this->buildCollection($elements); + + while (true) { + $collectionNext = $collection->next(); + $arrayNext = next($elements); + + if (! $collectionNext || ! $arrayNext) { + break; + } + + self::assertSame($arrayNext, $collectionNext, 'Returned value of ArrayCollection::next() and next() not match'); + self::assertSame(key($elements), $collection->key(), 'Keys not match'); + self::assertSame(current($elements), $collection->current(), 'Current values not match'); + } + } + + /** + * @param mixed[] $elements + * + * @dataProvider provideDifferentElements + */ + public function testCurrent($elements): void + { + $collection = $this->buildCollection($elements); + + self::assertSame(current($elements), $collection->current()); + + next($elements); + $collection->next(); + + self::assertSame(current($elements), $collection->current()); + } + + /** + * @param mixed[] $elements + * + * @dataProvider provideDifferentElements + */ + public function testGetKeys($elements): void + { + $collection = $this->buildCollection($elements); + + self::assertSame(array_keys($elements), $collection->getKeys()); + } + + /** + * @param mixed[] $elements + * + * @dataProvider provideDifferentElements + */ + public function testGetValues($elements): void + { + $collection = $this->buildCollection($elements); + + self::assertSame(array_values($elements), $collection->getValues()); + } + + /** + * @param mixed[] $elements + * + * @dataProvider provideDifferentElements + */ + public function testCount($elements): void + { + $collection = $this->buildCollection($elements); + + self::assertSame(count($elements), $collection->count()); + } + + /** + * @param mixed[] $elements + * + * @dataProvider provideDifferentElements + */ + public function testIterator($elements): void + { + $collection = $this->buildCollection($elements); + + $iterations = 0; + foreach ($collection->getIterator() as $key => $item) { + self::assertSame($elements[$key], $item, 'Item ' . $key . ' not match'); + ++$iterations; + } + + self::assertEquals(count($elements), $iterations, 'Number of iterations not match'); + } + + /** @psalm-return array */ + public function provideDifferentElements(): array + { + return [ + 'indexed' => [[1, 2, 3, 4, 5]], + 'associative' => [['A' => 'a', 'B' => 'b', 'C' => 'c']], + 'mixed' => [['A' => 'a', 1, 'B' => 'b', 2, 3]], + ]; + } + + public function testRemove(): void + { + $elements = [1, 'A' => 'a', 2, 'B' => 'b', 3]; + $collection = $this->buildCollection($elements); + + self::assertEquals(1, $collection->remove(0)); + unset($elements[0]); + + self::assertEquals(null, $collection->remove('non-existent')); + unset($elements['non-existent']); + + self::assertEquals(2, $collection->remove(1)); + unset($elements[1]); + + self::assertEquals('a', $collection->remove('A')); + unset($elements['A']); + + self::assertEquals($elements, $collection->toArray()); + } + + public function testRemoveElement(): void + { + $elements = [1, 'A' => 'a', 2, 'B' => 'b', 3, 'A2' => 'a', 'B2' => 'b']; + $collection = $this->buildCollection($elements); + + self::assertTrue($collection->removeElement(1)); + unset($elements[0]); + + self::assertFalse($collection->removeElement('non-existent')); + + self::assertTrue($collection->removeElement('a')); + unset($elements['A']); + + self::assertTrue($collection->removeElement('a')); + unset($elements['A2']); + + self::assertEquals($elements, $collection->toArray()); + } + + public function testContainsKey(): void + { + $elements = [1, 'A' => 'a', 2, 'null' => null, 3, 'A2' => 'a', 'B2' => 'b']; + $collection = $this->buildCollection($elements); + + self::assertTrue($collection->containsKey(0), 'Contains index 0'); + self::assertTrue($collection->containsKey('A'), 'Contains key "A"'); + self::assertTrue($collection->containsKey('null'), 'Contains key "null", with value null'); + self::assertFalse($collection->containsKey('non-existent'), "Doesn't contain key"); + } + + public function testEmpty(): void + { + $collection = $this->buildCollection(); + self::assertTrue($collection->isEmpty(), 'Empty collection'); + + $collection->add(1); + self::assertFalse($collection->isEmpty(), 'Not empty collection'); + } + + public function testContains(): void + { + $elements = [1, 'A' => 'a', 2, 'null' => null, 3, 'A2' => 'a', 'zero' => 0]; + $collection = $this->buildCollection($elements); + + self::assertTrue($collection->contains(0), 'Contains Zero'); + self::assertTrue($collection->contains('a'), 'Contains "a"'); + self::assertTrue($collection->contains(null), 'Contains Null'); + self::assertFalse($collection->contains('non-existent'), "Doesn't contain an element"); + } + + public function testExists(): void + { + $elements = [1, 'A' => 'a', 2, 'null' => null, 3, 'A2' => 'a', 'zero' => 0]; + $collection = $this->buildCollection($elements); + + self::assertTrue($collection->exists(static function ($key, $element) { + return $key === 'A' && $element === 'a'; + }), 'Element exists'); + + self::assertFalse($collection->exists(static function ($key, $element) { + return $key === 'non-existent' && $element === 'non-existent'; + }), 'Element not exists'); + } + + public function testIndexOf(): void + { + $elements = [1, 'A' => 'a', 2, 'null' => null, 3, 'A2' => 'a', 'zero' => 0]; + $collection = $this->buildCollection($elements); + + self::assertSame(array_search(2, $elements, true), $collection->indexOf(2), 'Index of 2'); + self::assertSame(array_search(null, $elements, true), $collection->indexOf(null), 'Index of null'); + self::assertSame(array_search('non-existent', $elements, true), $collection->indexOf('non-existent'), 'Index of non existent'); + } + + public function testGet(): void + { + $elements = [1, 'A' => 'a', 2, 'null' => null, 3, 'A2' => 'a', 'zero' => 0]; + $collection = $this->buildCollection($elements); + + self::assertSame(2, $collection->get(1), 'Get element by index'); + self::assertSame('a', $collection->get('A'), 'Get element by name'); + self::assertSame(null, $collection->get('non-existent'), 'Get non existent element'); + } + + public function testMatchingWithSortingPreserveKeys(): void + { + $object1 = new stdClass(); + $object2 = new stdClass(); + + $object1->sortField = 2; + $object2->sortField = 1; + + $collection = $this->buildCollection([ + 'object1' => $object1, + 'object2' => $object2, + ]); + + if (! $this->isSelectable($collection)) { + $this->markTestSkipped('Collection does not support Selectable interface'); + } + + self::assertSame( + [ + 'object2' => $object2, + 'object1' => $object1, + ], + $collection + ->matching(new Criteria(null, ['sortField' => Criteria::ASC])) + ->toArray() + ); + } + + public function testMultiColumnSortAppliesAllSorts(): void + { + $collection = $this->buildCollection([ + ['foo' => 1, 'bar' => 2], + ['foo' => 2, 'bar' => 4], + ['foo' => 2, 'bar' => 3], + ]); + + $expected = [ + 1 => ['foo' => 2, 'bar' => 4], + 2 => ['foo' => 2, 'bar' => 3], + 0 => ['foo' => 1, 'bar' => 2], + ]; + + if (! $this->isSelectable($collection)) { + $this->markTestSkipped('Collection does not support Selectable interface'); + } + + self::assertSame( + $expected, + $collection + ->matching(new Criteria(null, ['foo' => Criteria::DESC, 'bar' => Criteria::DESC])) + ->toArray() + ); + } +} diff --git a/sites/all/libraries/vendor/doctrine/collections/tests/Doctrine/Tests/Common/Collections/BaseCollectionTest.php b/sites/all/libraries/vendor/doctrine/collections/tests/Doctrine/Tests/Common/Collections/BaseCollectionTest.php new file mode 100644 index 0000000000..61d0c200fd --- /dev/null +++ b/sites/all/libraries/vendor/doctrine/collections/tests/Doctrine/Tests/Common/Collections/BaseCollectionTest.php @@ -0,0 +1,255 @@ + */ + protected $collection; + + public function testIssetAndUnset(): void + { + self::assertFalse(isset($this->collection[0])); + $this->collection->add('testing'); + self::assertTrue(isset($this->collection[0])); + unset($this->collection[0]); + self::assertFalse(isset($this->collection[0])); + } + + public function testRemovingNonExistentEntryReturnsNull(): void + { + self::assertEquals(null, $this->collection->remove('testing_does_not_exist')); + } + + public function testExists(): void + { + $this->collection->add('one'); + $this->collection->add('two'); + $exists = $this->collection->exists(static function ($k, $e) { + return $e === 'one'; + }); + self::assertTrue($exists); + $exists = $this->collection->exists(static function ($k, $e) { + return $e === 'other'; + }); + self::assertFalse($exists); + } + + public function testMap(): void + { + $this->collection->add(1); + $this->collection->add(2); + $res = $this->collection->map(static function ($e) { + return $e * 2; + }); + self::assertEquals([2, 4], $res->toArray()); + } + + public function testFilter(): void + { + $this->collection->add(1); + $this->collection->add('foo'); + $this->collection->add(3); + $res = $this->collection->filter(static function ($e) { + return is_numeric($e); + }); + self::assertEquals([0 => 1, 2 => 3], $res->toArray()); + } + + public function testFilterByValueAndKey(): void + { + $this->collection->add(1); + $this->collection->add('foo'); + $this->collection->add(3); + $this->collection->add(4); + $this->collection->add(5); + $res = $this->collection->filter(static function ($v, $k) { + return is_numeric($v) && $k % 2 === 0; + }); + self::assertSame([0 => 1, 2 => 3, 4 => 5], $res->toArray()); + } + + public function testFirstAndLast(): void + { + $this->collection->add('one'); + $this->collection->add('two'); + + self::assertEquals($this->collection->first(), 'one'); + self::assertEquals($this->collection->last(), 'two'); + } + + public function testArrayAccess(): void + { + $this->collection[] = 'one'; + $this->collection[] = 'two'; + + self::assertEquals($this->collection[0], 'one'); + self::assertEquals($this->collection[1], 'two'); + + unset($this->collection[0]); + self::assertEquals($this->collection->count(), 1); + } + + public function testContainsKey(): void + { + $this->collection[5] = 'five'; + self::assertTrue($this->collection->containsKey(5)); + } + + public function testContains(): void + { + $this->collection[0] = 'test'; + self::assertTrue($this->collection->contains('test')); + } + + public function testSearch(): void + { + $this->collection[0] = 'test'; + self::assertEquals(0, $this->collection->indexOf('test')); + } + + public function testGet(): void + { + $this->collection[0] = 'test'; + self::assertEquals('test', $this->collection->get(0)); + } + + public function testGetKeys(): void + { + $this->collection[] = 'one'; + $this->collection[] = 'two'; + self::assertEquals([0, 1], $this->collection->getKeys()); + } + + public function testGetValues(): void + { + $this->collection[] = 'one'; + $this->collection[] = 'two'; + self::assertEquals(['one', 'two'], $this->collection->getValues()); + } + + public function testCount(): void + { + $this->collection[] = 'one'; + $this->collection[] = 'two'; + self::assertEquals($this->collection->count(), 2); + self::assertEquals(count($this->collection), 2); + } + + public function testForAll(): void + { + $this->collection[] = 'one'; + $this->collection[] = 'two'; + self::assertEquals($this->collection->forAll(static function ($k, $e) { + return is_string($e); + }), true); + self::assertEquals($this->collection->forAll(static function ($k, $e) { + return is_array($e); + }), false); + } + + public function testPartition(): void + { + $this->collection[] = true; + $this->collection[] = false; + $partition = $this->collection->partition(static function ($k, $e) { + return $e === true; + }); + self::assertEquals($partition[0][0], true); + self::assertEquals($partition[1][0], false); + } + + public function testClear(): void + { + $this->collection[] = 'one'; + $this->collection[] = 'two'; + $this->collection->clear(); + self::assertEquals($this->collection->isEmpty(), true); + } + + public function testRemove(): void + { + $this->collection[] = 'one'; + $this->collection[] = 'two'; + $el = $this->collection->remove(0); + + self::assertEquals('one', $el); + self::assertEquals($this->collection->contains('one'), false); + self::assertNull($this->collection->remove(0)); + } + + public function testRemoveElement(): void + { + $this->collection[] = 'one'; + $this->collection[] = 'two'; + + self::assertTrue($this->collection->removeElement('two')); + self::assertFalse($this->collection->contains('two')); + self::assertFalse($this->collection->removeElement('two')); + } + + public function testSlice(): void + { + $this->collection[] = 'one'; + $this->collection[] = 'two'; + $this->collection[] = 'three'; + + $slice = $this->collection->slice(0, 1); + self::assertIsArray($slice); + self::assertEquals(['one'], $slice); + + $slice = $this->collection->slice(1); + self::assertEquals([1 => 'two', 2 => 'three'], $slice); + + $slice = $this->collection->slice(1, 1); + self::assertEquals([1 => 'two'], $slice); + } + + protected function fillMatchingFixture(): void + { + $std1 = new stdClass(); + $std1->foo = 'bar'; + $this->collection[] = $std1; + + $std2 = new stdClass(); + $std2->foo = 'baz'; + $this->collection[] = $std2; + } + + public function testCanRemoveNullValuesByKey(): void + { + $this->collection->add(null); + $this->collection->remove(0); + self::assertTrue($this->collection->isEmpty()); + } + + public function testCanVerifyExistingKeysWithNullValues(): void + { + $this->collection->set('key', null); + self::assertTrue($this->collection->containsKey('key')); + } + + public function testMatchingAlwaysReturnsCollection(): void + { + if (! $this->collection instanceof Selectable) { + self::markTestSkipped(sprintf('Collection does not implement %s', Selectable::class)); + } + + $criteria = Criteria::create(); + + self::assertInstanceOf(Collection::class, $this->collection->matching($criteria)); + self::assertInstanceOf(Selectable::class, $this->collection->matching($criteria)); + } +} diff --git a/sites/all/libraries/vendor/doctrine/collections/tests/Doctrine/Tests/Common/Collections/ClosureExpressionVisitorTest.php b/sites/all/libraries/vendor/doctrine/collections/tests/Doctrine/Tests/Common/Collections/ClosureExpressionVisitorTest.php new file mode 100644 index 0000000000..4b45ef65c2 --- /dev/null +++ b/sites/all/libraries/vendor/doctrine/collections/tests/Doctrine/Tests/Common/Collections/ClosureExpressionVisitorTest.php @@ -0,0 +1,564 @@ +visitor = new ClosureExpressionVisitor(); + $this->builder = new ExpressionBuilder(); + } + + public function testEmbeddedObjectComparison(): void + { + $closure = $this->visitor->walkComparison($this->builder->eq('foo.foo', 1)); + $this->assertTrue($closure(new TestObject(new TestObject(1)))); + $this->assertFalse($closure(new TestObject(new TestObject(2)))); + } + + public function testGetObjectFieldValueIsAccessor(): void + { + $object = new TestObject(1, 2, true); + + self::assertTrue($this->visitor->getObjectFieldValue($object, 'baz')); + } + + public function testGetObjectFieldValueIsAccessorWithIsPrefix(): void + { + $object = new TestObject(1, 2, true); + + self::assertTrue($this->visitor->getObjectFieldValue($object, 'isBaz')); + } + + public function testGetObjectFieldValueIsAccessorCamelCase(): void + { + $object = new TestObjectNotCamelCase(1); + + self::assertEquals(1, $this->visitor->getObjectFieldValue($object, 'foo_bar')); + self::assertEquals(1, $this->visitor->getObjectFieldValue($object, 'foobar')); + self::assertEquals(1, $this->visitor->getObjectFieldValue($object, 'fooBar')); + } + + public function testGetObjectFieldValueIsAccessorBoth(): void + { + $object = new TestObjectBothCamelCaseAndUnderscore(1, 2); + + self::assertEquals(2, $this->visitor->getObjectFieldValue($object, 'foo_bar')); + self::assertEquals(2, $this->visitor->getObjectFieldValue($object, 'foobar')); + self::assertEquals(2, $this->visitor->getObjectFieldValue($object, 'fooBar')); + } + + public function testGetObjectFieldValueIsAccessorOnePublic(): void + { + $object = new TestObjectPublicCamelCaseAndPrivateUnderscore(1, 2); + + self::assertEquals(2, $this->visitor->getObjectFieldValue($object, 'foo_bar')); + self::assertEquals(2, $this->visitor->getObjectFieldValue($object, 'foobar')); + self::assertEquals(2, $this->visitor->getObjectFieldValue($object, 'fooBar')); + } + + public function testGetObjectFieldValueIsAccessorBothPublic(): void + { + $object = new TestObjectPublicCamelCaseAndPrivateUnderscore(1, 2); + + self::assertEquals(2, $this->visitor->getObjectFieldValue($object, 'foo_bar')); + self::assertEquals(2, $this->visitor->getObjectFieldValue($object, 'foobar')); + self::assertEquals(2, $this->visitor->getObjectFieldValue($object, 'fooBar')); + } + + public function testGetObjectFieldValueBlankAccessor(): void + { + $object = new TestObjectBlankGetter(1); + + self::assertEquals(1, $this->visitor->getObjectFieldValue($object, 'foobar')); + self::assertEquals(1, $this->visitor->getObjectFieldValue($object, 'fooBar')); + } + + public function testGetObjectFieldValueMagicCallMethod(): void + { + $object = new TestObject(1, 2, true, 3); + + self::assertEquals(3, $this->visitor->getObjectFieldValue($object, 'qux')); + } + + public function testGetObjectFieldValueArrayAccess(): void + { + $object = self::createMock(ArrayAccess::class); + $object->expects(self::once()) + ->method('offsetGet') + ->with('foo') + ->willReturn(33); + + self::assertSame(33, $this->visitor->getObjectFieldValue($object, 'foo')); + } + + public function testGetObjectFieldValuePublicPropertyIsNull(): void + { + $object = new stdClass(); + $object->foo = null; + + self::assertSame(null, $this->visitor->getObjectFieldValue($object, 'foo')); + } + + public function testWalkEqualsComparison(): void + { + $closure = $this->visitor->walkComparison($this->builder->eq('foo', 1)); + + self::assertTrue($closure(new TestObject(1))); + self::assertFalse($closure(new TestObject(2))); + } + + public function testWalkNotEqualsComparison(): void + { + $closure = $this->visitor->walkComparison($this->builder->neq('foo', 1)); + + self::assertFalse($closure(new TestObject(1))); + self::assertTrue($closure(new TestObject(2))); + } + + public function testWalkLessThanComparison(): void + { + $closure = $this->visitor->walkComparison($this->builder->lt('foo', 1)); + + self::assertFalse($closure(new TestObject(1))); + self::assertTrue($closure(new TestObject(0))); + } + + public function testWalkLessThanEqualsComparison(): void + { + $closure = $this->visitor->walkComparison($this->builder->lte('foo', 1)); + + self::assertFalse($closure(new TestObject(2))); + self::assertTrue($closure(new TestObject(1))); + self::assertTrue($closure(new TestObject(0))); + } + + public function testWalkGreaterThanEqualsComparison(): void + { + $closure = $this->visitor->walkComparison($this->builder->gte('foo', 1)); + + self::assertTrue($closure(new TestObject(2))); + self::assertTrue($closure(new TestObject(1))); + self::assertFalse($closure(new TestObject(0))); + } + + public function testWalkGreaterThanComparison(): void + { + $closure = $this->visitor->walkComparison($this->builder->gt('foo', 1)); + + self::assertTrue($closure(new TestObject(2))); + self::assertFalse($closure(new TestObject(1))); + self::assertFalse($closure(new TestObject(0))); + } + + public function testWalkInComparison(): void + { + $closure = $this->visitor->walkComparison($this->builder->in('foo', [1, 2, 3, '04'])); + + self::assertTrue($closure(new TestObject(2))); + self::assertTrue($closure(new TestObject(1))); + self::assertFalse($closure(new TestObject(0))); + self::assertFalse($closure(new TestObject(4))); + self::assertTrue($closure(new TestObject('04'))); + } + + public function testWalkInComparisonObjects(): void + { + $closure = $this->visitor->walkComparison($this->builder->in('foo', [new TestObject(1), new TestObject(2), new TestObject(4)])); + + self::assertTrue($closure(new TestObject(new TestObject(2)))); + self::assertTrue($closure(new TestObject(new TestObject(1)))); + self::assertFalse($closure(new TestObject(new TestObject(0)))); + self::assertTrue($closure(new TestObject(new TestObject(4)))); + self::assertFalse($closure(new TestObject(new TestObject('baz')))); + } + + public function testWalkNotInComparison(): void + { + $closure = $this->visitor->walkComparison($this->builder->notIn('foo', [1, 2, 3, '04'])); + + self::assertFalse($closure(new TestObject(1))); + self::assertFalse($closure(new TestObject(2))); + self::assertTrue($closure(new TestObject(0))); + self::assertTrue($closure(new TestObject(4))); + self::assertFalse($closure(new TestObject('04'))); + } + + public function testWalkNotInComparisonObjects(): void + { + $closure = $this->visitor->walkComparison($this->builder->notIn('foo', [new TestObject(1), new TestObject(2), new TestObject(4)])); + + self::assertFalse($closure(new TestObject(new TestObject(1)))); + self::assertFalse($closure(new TestObject(new TestObject(2)))); + self::assertTrue($closure(new TestObject(new TestObject(0)))); + self::assertFalse($closure(new TestObject(new TestObject(4)))); + self::assertTrue($closure(new TestObject(new TestObject('baz')))); + } + + public function testWalkContainsComparison(): void + { + $closure = $this->visitor->walkComparison($this->builder->contains('foo', 'hello')); + + self::assertTrue($closure(new TestObject('hello world'))); + self::assertFalse($closure(new TestObject('world'))); + } + + public function testWalkMemberOfComparisonWithObject(): void + { + $closure = $this->visitor->walkComparison($this->builder->memberof('foo', 2)); + + self::assertTrue($closure(new TestObject([1, 2, 3]))); + self::assertTrue($closure(new TestObject([2]))); + self::assertTrue($closure(new TestObject(new ArrayIterator([2])))); + self::assertFalse($closure(new TestObject([1, 3, 5]))); + self::assertFalse($closure(new TestObject([1, '02']))); + self::assertFalse($closure(new TestObject(new ArrayIterator([4])))); + } + + public function testWalkStartsWithComparison(): void + { + $closure = $this->visitor->walkComparison($this->builder->startsWith('foo', 'hello')); + + self::assertTrue($closure(new TestObject('hello world'))); + self::assertFalse($closure(new TestObject('world'))); + } + + public function testWalkEndsWithComparison(): void + { + $closure = $this->visitor->walkComparison($this->builder->endsWith('foo', 'world')); + + self::assertTrue($closure(new TestObject('hello world'))); + self::assertFalse($closure(new TestObject('hello'))); + } + + public function testWalkUnknownOperatorComparisonThrowException(): void + { + self::expectException(RuntimeException::class); + self::expectExceptionMessage('Unknown comparison operator: unknown'); + + $closure = $this->visitor->walkComparison(new Comparison('foo', 'unknown', 2)); + + $closure(new TestObject(2)); + } + + public function testWalkAndCompositeExpression(): void + { + $closure = $this->visitor->walkCompositeExpression( + $this->builder->andX( + $this->builder->eq('foo', 1), + $this->builder->eq('bar', 1) + ) + ); + + self::assertTrue($closure(new TestObject(1, 1))); + self::assertFalse($closure(new TestObject(1, 0))); + self::assertFalse($closure(new TestObject(0, 1))); + self::assertFalse($closure(new TestObject(0, 0))); + } + + public function testWalkOrCompositeExpression(): void + { + $closure = $this->visitor->walkCompositeExpression( + $this->builder->orX( + $this->builder->eq('foo', 1), + $this->builder->eq('bar', 1) + ) + ); + + self::assertTrue($closure(new TestObject(1, 1))); + self::assertTrue($closure(new TestObject(1, 0))); + self::assertTrue($closure(new TestObject(0, 1))); + self::assertFalse($closure(new TestObject(0, 0))); + } + + public function testWalkOrAndCompositeExpression(): void + { + $closure = $this->visitor->walkCompositeExpression( + $this->builder->orX( + $this->builder->andX( + $this->builder->eq('foo', 1), + $this->builder->eq('bar', 1) + ), + $this->builder->andX( + $this->builder->eq('foo', 2), + $this->builder->eq('bar', 2) + ) + ) + ); + + self::assertTrue($closure(new TestObject(1, 1))); + self::assertTrue($closure(new TestObject(2, 2))); + self::assertFalse($closure(new TestObject(1, 2))); + self::assertFalse($closure(new TestObject(2, 1))); + self::assertFalse($closure(new TestObject(0, 0))); + } + + public function testWalkAndOrCompositeExpression(): void + { + $closure = $this->visitor->walkCompositeExpression( + $this->builder->andX( + $this->builder->orX( + $this->builder->eq('foo', 1), + $this->builder->eq('foo', 2) + ), + $this->builder->orX( + $this->builder->eq('bar', 3), + $this->builder->eq('bar', 4) + ) + ) + ); + + self::assertTrue($closure(new TestObject(1, 3))); + self::assertTrue($closure(new TestObject(1, 4))); + self::assertTrue($closure(new TestObject(2, 3))); + self::assertTrue($closure(new TestObject(2, 4))); + self::assertFalse($closure(new TestObject(1, 0))); + self::assertFalse($closure(new TestObject(2, 0))); + self::assertFalse($closure(new TestObject(0, 3))); + self::assertFalse($closure(new TestObject(0, 4))); + } + + public function testWalkUnknownCompositeExpressionThrowException(): void + { + self::expectException(RuntimeException::class); + self::expectExceptionMessage('Unknown composite Unknown'); + + $closure = $this->visitor->walkCompositeExpression( + new CompositeExpression('Unknown', []) + ); + + $closure(new TestObject()); + } + + public function testSortByFieldAscending(): void + { + $objects = [new TestObject('b'), new TestObject('a'), new TestObject('c')]; + $sort = ClosureExpressionVisitor::sortByField('foo'); + + usort($objects, $sort); + + self::assertEquals('a', $objects[0]->getFoo()); + self::assertEquals('b', $objects[1]->getFoo()); + self::assertEquals('c', $objects[2]->getFoo()); + } + + public function testSortByFieldDescending(): void + { + $objects = [new TestObject('b'), new TestObject('a'), new TestObject('c')]; + $sort = ClosureExpressionVisitor::sortByField('foo', -1); + + usort($objects, $sort); + + self::assertEquals('c', $objects[0]->getFoo()); + self::assertEquals('b', $objects[1]->getFoo()); + self::assertEquals('a', $objects[2]->getFoo()); + } + + public function testSortByFieldKeepOrderWhenSameValue(): void + { + $firstElement = new TestObject('a'); + $secondElement = new TestObject('a'); + + $objects = [$firstElement, $secondElement]; + $sort = ClosureExpressionVisitor::sortByField('foo'); + + usort($objects, $sort); + + self::assertSame([$firstElement, $secondElement], $objects); + } + + public function testSortDelegate(): void + { + $objects = [new TestObject('a', 'c'), new TestObject('a', 'b'), new TestObject('a', 'a')]; + $sort = ClosureExpressionVisitor::sortByField('bar', 1); + $sort = ClosureExpressionVisitor::sortByField('foo', 1, $sort); + + usort($objects, $sort); + + self::assertEquals('a', $objects[0]->getBar()); + self::assertEquals('b', $objects[1]->getBar()); + self::assertEquals('c', $objects[2]->getBar()); + } + + public function testArrayComparison(): void + { + $closure = $this->visitor->walkComparison($this->builder->eq('foo', 42)); + + self::assertTrue($closure(['foo' => 42])); + } +} + +class TestObject +{ + /** @var mixed */ + private $foo; + + /** @var mixed */ + private $bar; + + /** @var mixed */ + private $baz; + + /** @var mixed */ + private $qux; + + /** + * @param mixed $foo + * @param mixed $bar + * @param mixed $baz + * @param mixed $qux + */ + public function __construct($foo = null, $bar = null, $baz = null, $qux = null) + { + $this->foo = $foo; + $this->bar = $bar; + $this->baz = $baz; + $this->qux = $qux; + } + + /** + * @param mixed[] $arguments + * + * @return mixed + */ + public function __call(string $name, array $arguments) + { + if ($name === 'getqux') { + return $this->qux; + } + } + + /** @return mixed */ + public function getFoo() + { + return $this->foo; + } + + /** @return mixed */ + public function getBar() + { + return $this->bar; + } + + /** @return mixed */ + public function isBaz() + { + return $this->baz; + } +} + +class TestObjectNotCamelCase +{ + /** @var int|null */ + private $foo_bar; + + public function __construct(?int $foo_bar) + { + $this->foo_bar = $foo_bar; + } + + public function getFooBar(): ?int + { + return $this->foo_bar; + } +} + +class TestObjectBothCamelCaseAndUnderscore +{ + /** @var int|null */ + private $foo_bar; + + /** @var int|null */ + private $fooBar; + + public function __construct(?int $foo_bar = null, ?int $fooBar = null) + { + $this->foo_bar = $foo_bar; + $this->fooBar = $fooBar; + } + + public function getFooBar(): ?int + { + return $this->fooBar; + } +} + +class TestObjectPublicCamelCaseAndPrivateUnderscore +{ + /** @var int|null */ + private $foo_bar; + + /** @var int|null */ + public $fooBar; + + public function __construct(?int $foo_bar = null, ?int $fooBar = null) + { + $this->foo_bar = $foo_bar; + $this->fooBar = $fooBar; + } + + public function getFooBar(): ?int + { + return $this->fooBar; + } +} + +class TestObjectBothPublic +{ + /** @var mixed */ + public $foo_bar; + /** @var mixed */ + public $fooBar; + + /** + * @param mixed $foo_bar + * @param mixed $fooBar + */ + public function __construct($foo_bar = null, $fooBar = null) + { + $this->foo_bar = $foo_bar; + $this->fooBar = $fooBar; + } + + /** @return mixed */ + public function getFooBar() + { + return $this->foo_bar; + } +} + +class TestObjectBlankGetter +{ + /** @var int|null */ + public $fooBar; + + public function __construct(?int $fooBar = null) + { + $this->fooBar = $fooBar; + } + + public function fooBar(): ?int + { + return $this->fooBar; + } +} diff --git a/sites/all/libraries/vendor/doctrine/collections/tests/Doctrine/Tests/Common/Collections/CollectionTest.php b/sites/all/libraries/vendor/doctrine/collections/tests/Doctrine/Tests/Common/Collections/CollectionTest.php new file mode 100644 index 0000000000..d3593463f8 --- /dev/null +++ b/sites/all/libraries/vendor/doctrine/collections/tests/Doctrine/Tests/Common/Collections/CollectionTest.php @@ -0,0 +1,96 @@ +collection = new ArrayCollection(); + } + + public function testToString(): void + { + $this->collection->add('testing'); + self::assertTrue(is_string((string) $this->collection)); + } + + /** @group DDC-1637 */ + public function testMatching(): void + { + $this->fillMatchingFixture(); + + $col = $this->collection->matching(new Criteria(Criteria::expr()->eq('foo', 'bar'))); + self::assertInstanceOf(Collection::class, $col); + self::assertNotSame($col, $this->collection); + self::assertEquals(1, count($col)); + } + + public function testMatchingCallable(): void + { + $this->fillMatchingFixture(); + $this->collection[0]->foo = 1; + + $col = $this->collection->matching( + new Criteria( + new Value(static function (stdClass $test): bool { + return $test->foo === 1; + }) + ) + ); + + self::assertInstanceOf(Collection::class, $col); + self::assertNotSame($col, $this->collection); + self::assertEquals(1, count($col)); + } + + public function testMatchingUnknownThrowException(): void + { + self::expectException(RuntimeException::class); + self::expectExceptionMessage('Unknown Expression GenericExpression'); + + $genericExpression = $this->getMockBuilder(Expression::class) + ->setMockClassName('GenericExpression') + ->getMock(); + + $this->collection->matching(new Criteria($genericExpression)); + } + + /** @group DDC-1637 */ + public function testMatchingOrdering(): void + { + $this->fillMatchingFixture(); + + $col = $this->collection->matching(new Criteria(null, ['foo' => 'DESC'])); + + self::assertInstanceOf(Collection::class, $col); + self::assertNotSame($col, $this->collection); + self::assertEquals(2, count($col)); + self::assertEquals('baz', $col->first()->foo); + self::assertEquals('bar', $col->last()->foo); + } + + /** @group DDC-1637 */ + public function testMatchingSlice(): void + { + $this->fillMatchingFixture(); + + $col = $this->collection->matching(new Criteria(null, null, 1, 1)); + + self::assertInstanceOf(Collection::class, $col); + self::assertNotSame($col, $this->collection); + self::assertEquals(1, count($col)); + self::assertEquals('baz', $col[0]->foo); + } +} diff --git a/sites/all/libraries/vendor/doctrine/collections/tests/Doctrine/Tests/Common/Collections/CriteriaTest.php b/sites/all/libraries/vendor/doctrine/collections/tests/Doctrine/Tests/Common/Collections/CriteriaTest.php new file mode 100644 index 0000000000..b537189906 --- /dev/null +++ b/sites/all/libraries/vendor/doctrine/collections/tests/Doctrine/Tests/Common/Collections/CriteriaTest.php @@ -0,0 +1,132 @@ + 'ASC'], 10, 20); + + self::assertSame($expr, $criteria->getWhereExpression()); + self::assertSame(['foo' => 'ASC'], $criteria->getOrderings()); + self::assertSame(10, $criteria->getFirstResult()); + self::assertSame(20, $criteria->getMaxResults()); + } + + public function testDeprecatedNullOffset(): void + { + $expr = new Comparison('field', '=', 'value'); + + $this->expectDeprecationWithIdentifier('https://github.com/doctrine/collections/pull/311'); + $criteria = new Criteria($expr, ['foo' => 'ASC'], null, 20); + + self::assertSame($expr, $criteria->getWhereExpression()); + self::assertSame(['foo' => 'ASC'], $criteria->getOrderings()); + self::assertNull($criteria->getFirstResult()); + self::assertSame(20, $criteria->getMaxResults()); + } + + public function testDefaultConstructor(): void + { + $this->expectNoDeprecationWithIdentifier('https://github.com/doctrine/collections/pull/311'); + $criteria = new Criteria(); + + self::assertNull($criteria->getWhereExpression()); + self::assertSame([], $criteria->getOrderings()); + self::assertNull($criteria->getFirstResult()); + self::assertNull($criteria->getMaxResults()); + } + + public function testWhere(): void + { + $expr = new Comparison('field', '=', 'value'); + $criteria = new Criteria(); + + $criteria->where($expr); + + self::assertSame($expr, $criteria->getWhereExpression()); + } + + public function testAndWhere(): void + { + $expr = new Comparison('field', '=', 'value'); + $criteria = new Criteria(); + + $criteria->where($expr); + $expr = $criteria->getWhereExpression(); + $criteria->andWhere($expr); + + $where = $criteria->getWhereExpression(); + self::assertInstanceOf(CompositeExpression::class, $where); + + self::assertEquals(CompositeExpression::TYPE_AND, $where->getType()); + self::assertSame([$expr, $expr], $where->getExpressionList()); + } + + public function testAndWhereWithoutWhere(): void + { + $expr = new Comparison('field', '=', 'value'); + $criteria = new Criteria(); + + $criteria->andWhere($expr); + + self::assertSame($expr, $criteria->getWhereExpression()); + } + + public function testOrWhere(): void + { + $expr = new Comparison('field', '=', 'value'); + $criteria = new Criteria(); + + $criteria->where($expr); + $expr = $criteria->getWhereExpression(); + $criteria->orWhere($expr); + + $where = $criteria->getWhereExpression(); + self::assertInstanceOf(CompositeExpression::class, $where); + + self::assertEquals(CompositeExpression::TYPE_OR, $where->getType()); + self::assertSame([$expr, $expr], $where->getExpressionList()); + } + + public function testOrWhereWithoutWhere(): void + { + $expr = new Comparison('field', '=', 'value'); + $criteria = new Criteria(); + + $criteria->orWhere($expr); + + self::assertSame($expr, $criteria->getWhereExpression()); + } + + public function testOrderings(): void + { + $criteria = Criteria::create() + ->orderBy(['foo' => 'ASC']); + + self::assertEquals(['foo' => 'ASC'], $criteria->getOrderings()); + } + + public function testExpr(): void + { + self::assertInstanceOf(ExpressionBuilder::class, Criteria::expr()); + } +} diff --git a/sites/all/libraries/vendor/doctrine/collections/tests/Doctrine/Tests/Common/Collections/DerivedCollectionTest.php b/sites/all/libraries/vendor/doctrine/collections/tests/Doctrine/Tests/Common/Collections/DerivedCollectionTest.php new file mode 100644 index 0000000000..1b7e8b3bda --- /dev/null +++ b/sites/all/libraries/vendor/doctrine/collections/tests/Doctrine/Tests/Common/Collections/DerivedCollectionTest.php @@ -0,0 +1,28 @@ +map($closure)); + self::assertInstanceOf(DerivedArrayCollection::class, $collection->filter($closure)); + self::assertContainsOnlyInstancesOf(DerivedArrayCollection::class, $collection->partition($closure)); + self::assertInstanceOf(DerivedArrayCollection::class, $collection->matching(new Criteria())); + } +} diff --git a/sites/all/libraries/vendor/doctrine/collections/tests/Doctrine/Tests/Common/Collections/Expr/ComparisonTest.php b/sites/all/libraries/vendor/doctrine/collections/tests/Doctrine/Tests/Common/Collections/Expr/ComparisonTest.php new file mode 100644 index 0000000000..3ead131312 --- /dev/null +++ b/sites/all/libraries/vendor/doctrine/collections/tests/Doctrine/Tests/Common/Collections/Expr/ComparisonTest.php @@ -0,0 +1,30 @@ +expects(self::once()) + ->method('walkComparison') + ->with($comparison) + ->willReturn($callableExpected); + + self::assertSame($callableExpected, $comparison->visit($visitor)); + } +} diff --git a/sites/all/libraries/vendor/doctrine/collections/tests/Doctrine/Tests/Common/Collections/Expr/CompositeExpressionTest.php b/sites/all/libraries/vendor/doctrine/collections/tests/Doctrine/Tests/Common/Collections/Expr/CompositeExpressionTest.php new file mode 100644 index 0000000000..605efaf6b1 --- /dev/null +++ b/sites/all/libraries/vendor/doctrine/collections/tests/Doctrine/Tests/Common/Collections/Expr/CompositeExpressionTest.php @@ -0,0 +1,76 @@ + */ + public function invalidDataProvider(): array + { + return [ + ['expression' => new Value('value')], + ['expression' => 'wrong-type'], + ]; + } + + /** + * @param mixed $expression + * + * @dataProvider invalidDataProvider + */ + public function testExceptions($expression): void + { + $type = CompositeExpression::TYPE_AND; + $expressions = [$expression]; + + $this->expectException(RuntimeException::class); + new CompositeExpression($type, $expressions); + } + + public function testGetType(): void + { + $compositeExpression = $this->createCompositeExpression(); + + $expectedType = CompositeExpression::TYPE_AND; + $actualType = $compositeExpression->getType(); + + self::assertSame($expectedType, $actualType); + } + + protected function createCompositeExpression(): CompositeExpression + { + $type = CompositeExpression::TYPE_AND; + $expressions = [$this->createMock(Expression::class)]; + + return new CompositeExpression($type, $expressions); + } + + public function testGetExpressionList(): void + { + $compositeExpression = $this->createCompositeExpression(); + $expectedExpressionList = [$this->createMock(Expression::class)]; + $actualExpressionList = $compositeExpression->getExpressionList(); + + self::assertEquals($expectedExpressionList, $actualExpressionList); + } + + public function testVisitor(): void + { + $compositeExpression = $this->createCompositeExpression(); + + $visitor = $this->getMockForAbstractClass(ExpressionVisitor::class); + $visitor + ->expects($this->once()) + ->method('walkCompositeExpression'); + + $compositeExpression->visit($visitor); + } +} diff --git a/sites/all/libraries/vendor/doctrine/collections/tests/Doctrine/Tests/Common/Collections/Expr/ValueTest.php b/sites/all/libraries/vendor/doctrine/collections/tests/Doctrine/Tests/Common/Collections/Expr/ValueTest.php new file mode 100644 index 0000000000..083884d28b --- /dev/null +++ b/sites/all/libraries/vendor/doctrine/collections/tests/Doctrine/Tests/Common/Collections/Expr/ValueTest.php @@ -0,0 +1,33 @@ +getValue(); + + self::assertEquals($value, $actualValue); + } + + public function testVisitor(): void + { + $visitor = $this->getMockForAbstractClass(ExpressionVisitor::class); + $visitor + ->expects($this->once()) + ->method('walkValue'); + + $value = 'foo'; + $valueExpression = new Value($value); + $valueExpression->visit($visitor); + } +} diff --git a/sites/all/libraries/vendor/doctrine/collections/tests/Doctrine/Tests/Common/Collections/ExpressionBuilderTest.php b/sites/all/libraries/vendor/doctrine/collections/tests/Doctrine/Tests/Common/Collections/ExpressionBuilderTest.php new file mode 100644 index 0000000000..a634050e74 --- /dev/null +++ b/sites/all/libraries/vendor/doctrine/collections/tests/Doctrine/Tests/Common/Collections/ExpressionBuilderTest.php @@ -0,0 +1,147 @@ +builder = new ExpressionBuilder(); + } + + public function testAndX(): void + { + $expr = $this->builder->andX($this->builder->eq('a', 'b')); + + self::assertInstanceOf(CompositeExpression::class, $expr); + self::assertEquals(CompositeExpression::TYPE_AND, $expr->getType()); + } + + public function testOrX(): void + { + $expr = $this->builder->orX($this->builder->eq('a', 'b')); + + self::assertInstanceOf(CompositeExpression::class, $expr); + self::assertEquals(CompositeExpression::TYPE_OR, $expr->getType()); + } + + public function testInvalidAndXArgument(): void + { + $this->expectException(RuntimeException::class); + $this->builder->andX('foo'); + } + + public function testEq(): void + { + $expr = $this->builder->eq('a', 'b'); + + self::assertInstanceOf(Comparison::class, $expr); + self::assertEquals(Comparison::EQ, $expr->getOperator()); + } + + public function testNeq(): void + { + $expr = $this->builder->neq('a', 'b'); + + self::assertInstanceOf(Comparison::class, $expr); + self::assertEquals(Comparison::NEQ, $expr->getOperator()); + } + + public function testLt(): void + { + $expr = $this->builder->lt('a', 'b'); + + self::assertInstanceOf(Comparison::class, $expr); + self::assertEquals(Comparison::LT, $expr->getOperator()); + } + + public function testGt(): void + { + $expr = $this->builder->gt('a', 'b'); + + self::assertInstanceOf(Comparison::class, $expr); + self::assertEquals(Comparison::GT, $expr->getOperator()); + } + + public function testGte(): void + { + $expr = $this->builder->gte('a', 'b'); + + self::assertInstanceOf(Comparison::class, $expr); + self::assertEquals(Comparison::GTE, $expr->getOperator()); + } + + public function testLte(): void + { + $expr = $this->builder->lte('a', 'b'); + + self::assertInstanceOf(Comparison::class, $expr); + self::assertEquals(Comparison::LTE, $expr->getOperator()); + } + + public function testIn(): void + { + $expr = $this->builder->in('a', ['b']); + + self::assertInstanceOf(Comparison::class, $expr); + self::assertEquals(Comparison::IN, $expr->getOperator()); + } + + public function testNotIn(): void + { + $expr = $this->builder->notIn('a', ['b']); + + self::assertInstanceOf(Comparison::class, $expr); + self::assertEquals(Comparison::NIN, $expr->getOperator()); + } + + public function testIsNull(): void + { + $expr = $this->builder->isNull('a'); + + self::assertInstanceOf(Comparison::class, $expr); + self::assertEquals(Comparison::EQ, $expr->getOperator()); + } + + public function testContains(): void + { + $expr = $this->builder->contains('a', 'b'); + + self::assertInstanceOf(Comparison::class, $expr); + self::assertEquals(Comparison::CONTAINS, $expr->getOperator()); + } + + public function testMemberOf(): void + { + $expr = $this->builder->memberOf('b', ['a']); + + self::assertInstanceOf(Comparison::class, $expr); + self::assertEquals(Comparison::MEMBER_OF, $expr->getOperator()); + } + + public function testStartsWith(): void + { + $expr = $this->builder->startsWith('a', 'b'); + + self::assertInstanceOf(Comparison::class, $expr); + self::assertEquals(Comparison::STARTS_WITH, $expr->getOperator()); + } + + public function testEndsWith(): void + { + $expr = $this->builder->endsWith('a', 'b'); + + self::assertInstanceOf(Comparison::class, $expr); + self::assertEquals(Comparison::ENDS_WITH, $expr->getOperator()); + } +} diff --git a/sites/all/libraries/vendor/doctrine/collections/tests/Doctrine/Tests/DerivedArrayCollection.php b/sites/all/libraries/vendor/doctrine/collections/tests/Doctrine/Tests/DerivedArrayCollection.php new file mode 100644 index 0000000000..d1f71069b1 --- /dev/null +++ b/sites/all/libraries/vendor/doctrine/collections/tests/Doctrine/Tests/DerivedArrayCollection.php @@ -0,0 +1,33 @@ +foo = $foo; + + parent::__construct($elements); + } + + /** + * @param mixed[] $elements + * + * @return self + */ + protected function createFrom(array $elements): self + { + return new static($this->foo, $elements); + } +} diff --git a/sites/all/libraries/vendor/doctrine/collections/tests/Doctrine/Tests/LazyArrayCollection.php b/sites/all/libraries/vendor/doctrine/collections/tests/Doctrine/Tests/LazyArrayCollection.php new file mode 100644 index 0000000000..a26b4f7fa2 --- /dev/null +++ b/sites/all/libraries/vendor/doctrine/collections/tests/Doctrine/Tests/LazyArrayCollection.php @@ -0,0 +1,33 @@ + + */ + private $collectionOnInitialization; + + /** @param Collection $collection */ + public function __construct(Collection $collection) + { + $this->collectionOnInitialization = $collection; + } + + /** + * Do the initialization logic. + */ + protected function doInitialize(): void + { + $this->collection = $this->collectionOnInitialization; + } +} diff --git a/sites/all/libraries/vendor/doctrine/deprecations b/sites/all/libraries/vendor/doctrine/deprecations deleted file mode 160000 index 0e2a4f1f8c..0000000000 --- a/sites/all/libraries/vendor/doctrine/deprecations +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 0e2a4f1f8cdfc7a92ec3b01c9334898c806b30de diff --git a/sites/all/libraries/vendor/doctrine/deprecations/LICENSE b/sites/all/libraries/vendor/doctrine/deprecations/LICENSE new file mode 100644 index 0000000000..156905cdd0 --- /dev/null +++ b/sites/all/libraries/vendor/doctrine/deprecations/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2020-2021 Doctrine Project + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/sites/all/libraries/vendor/doctrine/deprecations/README.md b/sites/all/libraries/vendor/doctrine/deprecations/README.md new file mode 100644 index 0000000000..22f0cced3e --- /dev/null +++ b/sites/all/libraries/vendor/doctrine/deprecations/README.md @@ -0,0 +1,154 @@ +# Doctrine Deprecations + +A small (side-effect free by default) layer on top of +`trigger_error(E_USER_DEPRECATED)` or PSR-3 logging. + +- no side-effects by default, making it a perfect fit for libraries that don't know how the error handler works they operate under +- options to avoid having to rely on error handlers global state by using PSR-3 logging +- deduplicate deprecation messages to avoid excessive triggering and reduce overhead + +We recommend to collect Deprecations using a PSR logger instead of relying on +the global error handler. + +## Usage from consumer perspective: + +Enable Doctrine deprecations to be sent to a PSR3 logger: + +```php +\Doctrine\Deprecations\Deprecation::enableWithPsrLogger($logger); +``` + +Enable Doctrine deprecations to be sent as `@trigger_error($message, E_USER_DEPRECATED)` +messages. + +```php +\Doctrine\Deprecations\Deprecation::enableWithTriggerError(); +``` + +If you only want to enable deprecation tracking, without logging or calling `trigger_error` then call: + +```php +\Doctrine\Deprecations\Deprecation::enableTrackingDeprecations(); +``` + +Tracking is enabled with all three modes and provides access to all triggered +deprecations and their individual count: + +```php +$deprecations = \Doctrine\Deprecations\Deprecation::getTriggeredDeprecations(); + +foreach ($deprecations as $identifier => $count) { + echo $identifier . " was triggered " . $count . " times\n"; +} +``` + +### Suppressing Specific Deprecations + +Disable triggering about specific deprecations: + +```php +\Doctrine\Deprecations\Deprecation::ignoreDeprecations("https://link/to/deprecations-description-identifier"); +``` + +Disable all deprecations from a package + +```php +\Doctrine\Deprecations\Deprecation::ignorePackage("doctrine/orm"); +``` + +### Other Operations + +When used within PHPUnit or other tools that could collect multiple instances of the same deprecations +the deduplication can be disabled: + +```php +\Doctrine\Deprecations\Deprecation::withoutDeduplication(); +``` + +Disable deprecation tracking again: + +```php +\Doctrine\Deprecations\Deprecation::disable(); +``` + +## Usage from a library/producer perspective: + +When you want to unconditionally trigger a deprecation even when called +from the library itself then the `trigger` method is the way to go: + +```php +\Doctrine\Deprecations\Deprecation::trigger( + "doctrine/orm", + "https://link/to/deprecations-description", + "message" +); +``` + +If variable arguments are provided at the end, they are used with `sprintf` on +the message. + +```php +\Doctrine\Deprecations\Deprecation::trigger( + "doctrine/orm", + "https://github.com/doctrine/orm/issue/1234", + "message %s %d", + "foo", + 1234 +); +``` + +When you want to trigger a deprecation only when it is called by a function +outside of the current package, but not trigger when the package itself is the cause, +then use: + +```php +\Doctrine\Deprecations\Deprecation::triggerIfCalledFromOutside( + "doctrine/orm", + "https://link/to/deprecations-description", + "message" +); +``` + +Based on the issue link each deprecation message is only triggered once per +request. + +A limited stacktrace is included in the deprecation message to find the +offending location. + +Note: A producer/library should never call `Deprecation::enableWith` methods +and leave the decision how to handle deprecations to application and +frameworks. + +## Usage in PHPUnit tests + +There is a `VerifyDeprecations` trait that you can use to make assertions on +the occurrence of deprecations within a test. + +```php +use Doctrine\Deprecations\PHPUnit\VerifyDeprecations; + +class MyTest extends TestCase +{ + use VerifyDeprecations; + + public function testSomethingDeprecation() + { + $this->expectDeprecationWithIdentifier('https://github.com/doctrine/orm/issue/1234'); + + triggerTheCodeWithDeprecation(); + } + + public function testSomethingDeprecationFixed() + { + $this->expectNoDeprecationWithIdentifier('https://github.com/doctrine/orm/issue/1234'); + + triggerTheCodeWithoutDeprecation(); + } +} +``` + +## What is a deprecation identifier? + +An identifier for deprecations is just a link to any resource, most often a +Github Issue or Pull Request explaining the deprecation and potentially its +alternative. diff --git a/sites/all/libraries/vendor/doctrine/deprecations/composer.json b/sites/all/libraries/vendor/doctrine/deprecations/composer.json new file mode 100644 index 0000000000..c79e38cdcd --- /dev/null +++ b/sites/all/libraries/vendor/doctrine/deprecations/composer.json @@ -0,0 +1,32 @@ +{ + "name": "doctrine/deprecations", + "type": "library", + "description": "A small layer on top of trigger_error(E_USER_DEPRECATED) or PSR-3 logging with options to disable all deprecations or selectively for packages.", + "homepage": "https://www.doctrine-project.org/", + "license": "MIT", + "require": { + "php": "^7.1|^8.0" + }, + "require-dev": { + "phpunit/phpunit": "^7.5|^8.5|^9.5", + "psr/log": "^1|^2|^3", + "doctrine/coding-standard": "^9" + }, + "suggest": { + "psr/log": "Allows logging deprecations via PSR-3 logger implementation" + }, + "autoload": { + "psr-4": {"Doctrine\\Deprecations\\": "lib/Doctrine/Deprecations"} + }, + "autoload-dev": { + "psr-4": { + "DeprecationTests\\": "test_fixtures/src", + "Doctrine\\Foo\\": "test_fixtures/vendor/doctrine/foo" + } + }, + "config": { + "allow-plugins": { + "dealerdirect/phpcodesniffer-composer-installer": true + } + } +} diff --git a/sites/all/libraries/vendor/doctrine/deprecations/lib/Doctrine/Deprecations/Deprecation.php b/sites/all/libraries/vendor/doctrine/deprecations/lib/Doctrine/Deprecations/Deprecation.php new file mode 100644 index 0000000000..1029372faa --- /dev/null +++ b/sites/all/libraries/vendor/doctrine/deprecations/lib/Doctrine/Deprecations/Deprecation.php @@ -0,0 +1,266 @@ + */ + private static $ignoredPackages = []; + + /** @var array */ + private static $ignoredLinks = []; + + /** @var bool */ + private static $deduplication = true; + + /** + * Trigger a deprecation for the given package and identfier. + * + * The link should point to a Github issue or Wiki entry detailing the + * deprecation. It is additionally used to de-duplicate the trigger of the + * same deprecation during a request. + * + * @param mixed $args + */ + public static function trigger(string $package, string $link, string $message, ...$args): void + { + if (self::$type === self::TYPE_NONE) { + return; + } + + if (array_key_exists($link, self::$ignoredLinks)) { + self::$ignoredLinks[$link]++; + } else { + self::$ignoredLinks[$link] = 1; + } + + if (self::$deduplication === true && self::$ignoredLinks[$link] > 1) { + return; + } + + if (isset(self::$ignoredPackages[$package])) { + return; + } + + $backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2); + + $message = sprintf($message, ...$args); + + self::delegateTriggerToBackend($message, $backtrace, $link, $package); + } + + /** + * Trigger a deprecation for the given package and identifier when called from outside. + * + * "Outside" means we assume that $package is currently installed as a + * dependency and the caller is not a file in that package. When $package + * is installed as a root package then deprecations triggered from the + * tests folder are also considered "outside". + * + * This deprecation method assumes that you are using Composer to install + * the dependency and are using the default /vendor/ folder and not a + * Composer plugin to change the install location. The assumption is also + * that $package is the exact composer packge name. + * + * Compared to {@link trigger()} this method causes some overhead when + * deprecation tracking is enabled even during deduplication, because it + * needs to call {@link debug_backtrace()} + * + * @param mixed $args + */ + public static function triggerIfCalledFromOutside(string $package, string $link, string $message, ...$args): void + { + if (self::$type === self::TYPE_NONE) { + return; + } + + $backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2); + + // first check that the caller is not from a tests folder, in which case we always let deprecations pass + if (strpos($backtrace[1]['file'], DIRECTORY_SEPARATOR . 'tests' . DIRECTORY_SEPARATOR) === false) { + $path = DIRECTORY_SEPARATOR . 'vendor' . DIRECTORY_SEPARATOR . $package . DIRECTORY_SEPARATOR; + + if (strpos($backtrace[0]['file'], $path) === false) { + return; + } + + if (strpos($backtrace[1]['file'], $path) !== false) { + return; + } + } + + if (array_key_exists($link, self::$ignoredLinks)) { + self::$ignoredLinks[$link]++; + } else { + self::$ignoredLinks[$link] = 1; + } + + if (self::$deduplication === true && self::$ignoredLinks[$link] > 1) { + return; + } + + if (isset(self::$ignoredPackages[$package])) { + return; + } + + $message = sprintf($message, ...$args); + + self::delegateTriggerToBackend($message, $backtrace, $link, $package); + } + + /** + * @param array $backtrace + */ + private static function delegateTriggerToBackend(string $message, array $backtrace, string $link, string $package): void + { + if ((self::$type & self::TYPE_PSR_LOGGER) > 0) { + $context = [ + 'file' => $backtrace[0]['file'], + 'line' => $backtrace[0]['line'], + 'package' => $package, + 'link' => $link, + ]; + + self::$logger->notice($message, $context); + } + + if (! ((self::$type & self::TYPE_TRIGGER_ERROR) > 0)) { + return; + } + + $message .= sprintf( + ' (%s:%d called by %s:%d, %s, package %s)', + self::basename($backtrace[0]['file']), + $backtrace[0]['line'], + self::basename($backtrace[1]['file']), + $backtrace[1]['line'], + $link, + $package + ); + + @trigger_error($message, E_USER_DEPRECATED); + } + + /** + * A non-local-aware version of PHPs basename function. + */ + private static function basename(string $filename): string + { + $pos = strrpos($filename, DIRECTORY_SEPARATOR); + + if ($pos === false) { + return $filename; + } + + return substr($filename, $pos + 1); + } + + public static function enableTrackingDeprecations(): void + { + self::$type |= self::TYPE_TRACK_DEPRECATIONS; + } + + public static function enableWithTriggerError(): void + { + self::$type |= self::TYPE_TRIGGER_ERROR; + } + + public static function enableWithPsrLogger(LoggerInterface $logger): void + { + self::$type |= self::TYPE_PSR_LOGGER; + self::$logger = $logger; + } + + public static function withoutDeduplication(): void + { + self::$deduplication = false; + } + + public static function disable(): void + { + self::$type = self::TYPE_NONE; + self::$logger = null; + self::$deduplication = true; + + foreach (self::$ignoredLinks as $link => $count) { + self::$ignoredLinks[$link] = 0; + } + } + + public static function ignorePackage(string $packageName): void + { + self::$ignoredPackages[$packageName] = true; + } + + public static function ignoreDeprecations(string ...$links): void + { + foreach ($links as $link) { + self::$ignoredLinks[$link] = 0; + } + } + + public static function getUniqueTriggeredDeprecationsCount(): int + { + return array_reduce(self::$ignoredLinks, static function (int $carry, int $count) { + return $carry + $count; + }, 0); + } + + /** + * Returns each triggered deprecation link identifier and the amount of occurrences. + * + * @return array + */ + public static function getTriggeredDeprecations(): array + { + return self::$ignoredLinks; + } +} diff --git a/sites/all/libraries/vendor/doctrine/deprecations/lib/Doctrine/Deprecations/PHPUnit/VerifyDeprecations.php b/sites/all/libraries/vendor/doctrine/deprecations/lib/Doctrine/Deprecations/PHPUnit/VerifyDeprecations.php new file mode 100644 index 0000000000..4c3366a971 --- /dev/null +++ b/sites/all/libraries/vendor/doctrine/deprecations/lib/Doctrine/Deprecations/PHPUnit/VerifyDeprecations.php @@ -0,0 +1,66 @@ + */ + private $doctrineDeprecationsExpectations = []; + + /** @var array */ + private $doctrineNoDeprecationsExpectations = []; + + public function expectDeprecationWithIdentifier(string $identifier): void + { + $this->doctrineDeprecationsExpectations[$identifier] = Deprecation::getTriggeredDeprecations()[$identifier] ?? 0; + } + + public function expectNoDeprecationWithIdentifier(string $identifier): void + { + $this->doctrineNoDeprecationsExpectations[$identifier] = Deprecation::getTriggeredDeprecations()[$identifier] ?? 0; + } + + /** + * @before + */ + public function enableDeprecationTracking(): void + { + Deprecation::enableTrackingDeprecations(); + } + + /** + * @after + */ + public function verifyDeprecationsAreTriggered(): void + { + foreach ($this->doctrineDeprecationsExpectations as $identifier => $expectation) { + $actualCount = Deprecation::getTriggeredDeprecations()[$identifier] ?? 0; + + $this->assertTrue( + $actualCount > $expectation, + sprintf( + "Expected deprecation with identifier '%s' was not triggered by code executed in test.", + $identifier + ) + ); + } + + foreach ($this->doctrineNoDeprecationsExpectations as $identifier => $expectation) { + $actualCount = Deprecation::getTriggeredDeprecations()[$identifier] ?? 0; + + $this->assertTrue( + $actualCount === $expectation, + sprintf( + "Expected deprecation with identifier '%s' was triggered by code executed in test, but expected not to.", + $identifier + ) + ); + } + } +} diff --git a/sites/all/libraries/vendor/doctrine/deprecations/phpcs.xml b/sites/all/libraries/vendor/doctrine/deprecations/phpcs.xml new file mode 100644 index 0000000000..f115e43dd0 --- /dev/null +++ b/sites/all/libraries/vendor/doctrine/deprecations/phpcs.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + lib + tests + + + + + + diff --git a/sites/all/libraries/vendor/doctrine/deprecations/phpunit.xml.dist b/sites/all/libraries/vendor/doctrine/deprecations/phpunit.xml.dist new file mode 100644 index 0000000000..4740c06042 --- /dev/null +++ b/sites/all/libraries/vendor/doctrine/deprecations/phpunit.xml.dist @@ -0,0 +1,8 @@ + + + + + tests + + + diff --git a/sites/all/libraries/vendor/doctrine/deprecations/test_fixtures/src/Foo.php b/sites/all/libraries/vendor/doctrine/deprecations/test_fixtures/src/Foo.php new file mode 100644 index 0000000000..c4b8ebecee --- /dev/null +++ b/sites/all/libraries/vendor/doctrine/deprecations/test_fixtures/src/Foo.php @@ -0,0 +1,22 @@ +oldFunc(); + } + + public static function triggerDependencyWithDeprecationFromInside(): void + { + $bar = new Bar(); + $bar->newFunc(); + } +} diff --git a/sites/all/libraries/vendor/doctrine/deprecations/test_fixtures/src/RootDeprecation.php b/sites/all/libraries/vendor/doctrine/deprecations/test_fixtures/src/RootDeprecation.php new file mode 100644 index 0000000000..feccd48650 --- /dev/null +++ b/sites/all/libraries/vendor/doctrine/deprecations/test_fixtures/src/RootDeprecation.php @@ -0,0 +1,20 @@ +oldFunc(); + } +} diff --git a/sites/all/libraries/vendor/doctrine/deprecations/test_fixtures/vendor/doctrine/foo/Baz.php b/sites/all/libraries/vendor/doctrine/deprecations/test_fixtures/vendor/doctrine/foo/Baz.php new file mode 100644 index 0000000000..62b2bb1034 --- /dev/null +++ b/sites/all/libraries/vendor/doctrine/deprecations/test_fixtures/vendor/doctrine/foo/Baz.php @@ -0,0 +1,14 @@ +oldFunc(); + } +} diff --git a/sites/all/libraries/vendor/doctrine/deprecations/tests/Doctrine/Deprecations/DeprecationTest.php b/sites/all/libraries/vendor/doctrine/deprecations/tests/Doctrine/Deprecations/DeprecationTest.php new file mode 100644 index 0000000000..e59c1466c0 --- /dev/null +++ b/sites/all/libraries/vendor/doctrine/deprecations/tests/Doctrine/Deprecations/DeprecationTest.php @@ -0,0 +1,251 @@ +setAccessible(true); + $reflectionProperty->setValue([]); + + $reflectionProperty = new ReflectionProperty(Deprecation::class, 'ignoredLinks'); + $reflectionProperty->setAccessible(true); + $reflectionProperty->setValue([]); + + Deprecation::enableTrackingDeprecations(); + } + + public function expectDeprecation(): void + { + if (method_exists(TestCase::class, 'expectDeprecation')) { + parent::expectDeprecation(); + } else { + parent::expectException(Deprecated::class); + } + } + + public function expectDeprecationMessage(string $message): void + { + if (method_exists(TestCase::class, 'expectDeprecationMessage')) { + parent::expectDeprecationMessage($message); + } else { + parent::expectExceptionMessage($message); + } + } + + public function expectErrorHandler(string $expectedMessage, string $identifier, int $times = 1): void + { + set_error_handler(function ($type, $message) use ($expectedMessage, $identifier, $times): void { + $this->assertStringMatchesFormat( + $expectedMessage, + $message + ); + $this->assertEquals([$identifier => $times], Deprecation::getTriggeredDeprecations()); + }); + } + + public function testDeprecation(): void + { + Deprecation::enableWithTriggerError(); + + $this->expectDeprecationWithIdentifier('https://github.com/doctrine/deprecations/1234'); + + $this->expectErrorHandler( + 'this is deprecated foo 1234 (DeprecationTest.php:%d called by TestCase.php:%d, https://github.com/doctrine/deprecations/1234, package doctrine/orm)', + 'https://github.com/doctrine/deprecations/1234' + ); + + Deprecation::trigger( + 'doctrine/orm', + 'https://github.com/doctrine/deprecations/1234', + 'this is deprecated %s %d', + 'foo', + 1234 + ); + + $this->assertEquals(1, Deprecation::getUniqueTriggeredDeprecationsCount()); + + Deprecation::trigger( + 'doctrine/orm', + 'https://github.com/doctrine/deprecations/1234', + 'this is deprecated %s %d', + 'foo', + 1234 + ); + + $this->assertEquals(2, Deprecation::getUniqueTriggeredDeprecationsCount()); + } + + public function testDeprecationWithoutDeduplication(): void + { + Deprecation::enableWithTriggerError(); + Deprecation::withoutDeduplication(); + + $this->expectErrorHandler( + 'this is deprecated foo 2222 (DeprecationTest.php:%d called by TestCase.php:%d, https://github.com/doctrine/deprecations/2222, package doctrine/orm)', + 'https://github.com/doctrine/deprecations/2222' + ); + + Deprecation::trigger( + 'doctrine/orm', + 'https://github.com/doctrine/deprecations/2222', + 'this is deprecated %s %d', + 'foo', + 2222 + ); + + $this->assertEquals(1, Deprecation::getUniqueTriggeredDeprecationsCount()); + + $this->expectErrorHandler( + 'this is deprecated foo 2222 (DeprecationTest.php:%d called by TestCase.php:%d, https://github.com/doctrine/deprecations/2222, package doctrine/orm)', + 'https://github.com/doctrine/deprecations/2222', + 2 + ); + + Deprecation::trigger( + 'doctrine/orm', + 'https://github.com/doctrine/deprecations/2222', + 'this is deprecated %s %d', + 'foo', + 2222 + ); + + $this->assertEquals(2, Deprecation::getUniqueTriggeredDeprecationsCount()); + } + + public function testDeprecationResetsCounts(): void + { + try { + Deprecation::trigger( + 'doctrine/orm', + 'https://github.com/doctrine/deprecations/1234', + 'this is deprecated %s %d', + 'foo', + 1234 + ); + } catch (Throwable $e) { + Deprecation::disable(); + + $this->assertEquals(0, Deprecation::getUniqueTriggeredDeprecationsCount()); + $this->assertEquals(['https://github.com/doctrine/deprecations/1234' => 0], Deprecation::getTriggeredDeprecations()); + } + } + + public function expectDeprecationMock(string $message, string $identifier, string $package): MockObject + { + $mock = $this->createMock(LoggerInterface::class); + $mock->method('notice')->with($message, $this->callback(function ($context) use ($identifier, $package) { + $this->assertEquals($package, $context['package']); + $this->assertEquals($identifier, $context['link']); + + return true; + })); + + return $mock; + } + + public function testDeprecationWithPsrLogger(): void + { + $this->expectDeprecationWithIdentifier('https://github.com/doctrine/deprecations/2222'); + + $mock = $this->expectDeprecationMock( + 'this is deprecated foo 1234', + 'https://github.com/doctrine/deprecations/2222', + 'doctrine/orm' + ); + Deprecation::enableWithPsrLogger($mock); + + Deprecation::trigger( + 'doctrine/orm', + 'https://github.com/doctrine/deprecations/2222', + 'this is deprecated %s %d', + 'foo', + 1234 + ); + } + + public function testDeprecationWithIgnoredPackage(): void + { + Deprecation::enableWithTriggerError(); + Deprecation::ignorePackage('doctrine/orm'); + + Deprecation::trigger( + 'doctrine/orm', + 'https://github.com/doctrine/orm/issue/1234', + 'this is deprecated %s %d', + 'foo', + 1234 + ); + + $this->assertEquals(1, Deprecation::getUniqueTriggeredDeprecationsCount()); + $this->assertEquals(['https://github.com/doctrine/orm/issue/1234' => 1], Deprecation::getTriggeredDeprecations()); + } + + public function testDeprecationIfCalledFromOutside(): void + { + Deprecation::enableWithTriggerError(); + + $this->expectErrorHandler( + 'Bar::oldFunc() is deprecated, use Bar::newFunc() instead. (Bar.php:16 called by Foo.php:14, https://github.com/doctrine/foo, package doctrine/foo)', + 'https://github.com/doctrine/foo' + ); + + Foo::triggerDependencyWithDeprecation(); + } + + public function testDeprecationIfCalledFromOutsideNotTriggeringFromInside(): void + { + Deprecation::enableWithTriggerError(); + + Foo::triggerDependencyWithDeprecationFromInside(); + + $this->assertEquals(0, Deprecation::getUniqueTriggeredDeprecationsCount()); + } + + public function testDeprecationIfCalledFromOutsideNotTriggeringFromInsideClass(): void + { + Deprecation::enableWithTriggerError(); + + $baz = new Baz(); + $baz->usingOldFunc(); + + $this->assertEquals(0, Deprecation::getUniqueTriggeredDeprecationsCount()); + } + + public function testDeprecationCalledFromOutsideInRoot(): void + { + Deprecation::enableWithTriggerError(); + + $this->expectDeprecationWithIdentifier('https://github.com/doctrine/deprecations/4444'); + + $this->expectErrorHandler( + 'this is deprecated foo 1234 (RootDeprecation.php:%d called by DeprecationTest.php:%d, https://github.com/doctrine/deprecations/4444, package doctrine/orm)', + 'https://github.com/doctrine/deprecations/4444' + ); + + RootDeprecation::run(); + + $this->assertEquals(1, Deprecation::getUniqueTriggeredDeprecationsCount()); + } +} diff --git a/sites/all/libraries/vendor/doctrine/deprecations/tests/Doctrine/Deprecations/VerifyDeprecationsTest.php b/sites/all/libraries/vendor/doctrine/deprecations/tests/Doctrine/Deprecations/VerifyDeprecationsTest.php new file mode 100644 index 0000000000..8681f7d69c --- /dev/null +++ b/sites/all/libraries/vendor/doctrine/deprecations/tests/Doctrine/Deprecations/VerifyDeprecationsTest.php @@ -0,0 +1,35 @@ +expectDeprecationWithIdentifier('http://example.com'); + + Deprecation::trigger('doctrine/dbal', 'http://example.com', 'message'); + } + + public function testExpectNoDeprecationWithIdentifier(): void + { + $this->expectNoDeprecationWithIdentifier('http://example.com'); + + Deprecation::trigger('doctrine/dbal', 'http://otherexample.com', 'message'); + } +} diff --git a/sites/all/libraries/vendor/guzzle/guzzle/.gitignore b/sites/all/libraries/vendor/guzzle/guzzle/.gitignore deleted file mode 100644 index 893035d5ba..0000000000 --- a/sites/all/libraries/vendor/guzzle/guzzle/.gitignore +++ /dev/null @@ -1,27 +0,0 @@ -# Ingore common cruft -.DS_STORE -coverage -.idea - -# Ignore binary files -guzzle.phar -guzzle-min.phar - -# Ignore potentially sensitive phpunit file -phpunit.xml - -# Ignore composer generated files -composer.phar -composer.lock -composer-test.lock -vendor/ - -# Ignore build files -build/ -phing/build.properties - -# Ignore subsplit working directory -.subsplit - -docs/_build -docs/*.pyc diff --git a/sites/all/libraries/vendor/michelf/php-markdown/.gitignore b/sites/all/libraries/vendor/michelf/php-markdown/.gitignore deleted file mode 100644 index 5bd6475b79..0000000000 --- a/sites/all/libraries/vendor/michelf/php-markdown/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -*~ -/composer.lock -/vendor/ diff --git a/sites/all/libraries/vendor/psr/container b/sites/all/libraries/vendor/psr/container deleted file mode 160000 index 513e0666f7..0000000000 --- a/sites/all/libraries/vendor/psr/container +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 513e0666f7216c7459170d56df27dfcefe1689ea diff --git a/sites/all/libraries/vendor/psr/container/LICENSE b/sites/all/libraries/vendor/psr/container/LICENSE new file mode 100644 index 0000000000..2877a4894e --- /dev/null +++ b/sites/all/libraries/vendor/psr/container/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2013-2016 container-interop +Copyright (c) 2016 PHP Framework Interoperability Group + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/sites/all/libraries/vendor/psr/container/README.md b/sites/all/libraries/vendor/psr/container/README.md new file mode 100644 index 0000000000..1b9d9e5708 --- /dev/null +++ b/sites/all/libraries/vendor/psr/container/README.md @@ -0,0 +1,13 @@ +Container interface +============== + +This repository holds all interfaces related to [PSR-11 (Container Interface)][psr-url]. + +Note that this is not a Container implementation of its own. It is merely abstractions that describe the components of a Dependency Injection Container. + +The installable [package][package-url] and [implementations][implementation-url] are listed on Packagist. + +[psr-url]: https://www.php-fig.org/psr/psr-11/ +[package-url]: https://packagist.org/packages/psr/container +[implementation-url]: https://packagist.org/providers/psr/container-implementation + diff --git a/sites/all/libraries/vendor/psr/container/composer.json b/sites/all/libraries/vendor/psr/container/composer.json new file mode 100644 index 0000000000..017f41ea69 --- /dev/null +++ b/sites/all/libraries/vendor/psr/container/composer.json @@ -0,0 +1,22 @@ +{ + "name": "psr/container", + "type": "library", + "description": "Common Container Interface (PHP FIG PSR-11)", + "keywords": ["psr", "psr-11", "container", "container-interop", "container-interface"], + "homepage": "https://github.com/php-fig/container", + "license": "MIT", + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "require": { + "php": ">=7.4.0" + }, + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + } +} diff --git a/sites/all/libraries/vendor/psr/container/src/ContainerExceptionInterface.php b/sites/all/libraries/vendor/psr/container/src/ContainerExceptionInterface.php new file mode 100644 index 0000000000..0f213f2fed --- /dev/null +++ b/sites/all/libraries/vendor/psr/container/src/ContainerExceptionInterface.php @@ -0,0 +1,12 @@ +log(LogLevel::EMERGENCY, $message, $context); + } + + /** + * Action must be taken immediately. + * + * Example: Entire website down, database unavailable, etc. This should + * trigger the SMS alerts and wake you up. + * + * @param string $message + * @param mixed[] $context + * + * @return void + */ + public function alert($message, array $context = array()) + { + $this->log(LogLevel::ALERT, $message, $context); + } + + /** + * Critical conditions. + * + * Example: Application component unavailable, unexpected exception. + * + * @param string $message + * @param mixed[] $context + * + * @return void + */ + public function critical($message, array $context = array()) + { + $this->log(LogLevel::CRITICAL, $message, $context); + } + + /** + * Runtime errors that do not require immediate action but should typically + * be logged and monitored. + * + * @param string $message + * @param mixed[] $context + * + * @return void + */ + public function error($message, array $context = array()) + { + $this->log(LogLevel::ERROR, $message, $context); + } + + /** + * Exceptional occurrences that are not errors. + * + * Example: Use of deprecated APIs, poor use of an API, undesirable things + * that are not necessarily wrong. + * + * @param string $message + * @param mixed[] $context + * + * @return void + */ + public function warning($message, array $context = array()) + { + $this->log(LogLevel::WARNING, $message, $context); + } + + /** + * Normal but significant events. + * + * @param string $message + * @param mixed[] $context + * + * @return void + */ + public function notice($message, array $context = array()) + { + $this->log(LogLevel::NOTICE, $message, $context); + } + + /** + * Interesting events. + * + * Example: User logs in, SQL logs. + * + * @param string $message + * @param mixed[] $context + * + * @return void + */ + public function info($message, array $context = array()) + { + $this->log(LogLevel::INFO, $message, $context); + } + + /** + * Detailed debug information. + * + * @param string $message + * @param mixed[] $context + * + * @return void + */ + public function debug($message, array $context = array()) + { + $this->log(LogLevel::DEBUG, $message, $context); + } +} diff --git a/sites/all/libraries/vendor/psr/log/Psr/Log/InvalidArgumentException.php b/sites/all/libraries/vendor/psr/log/Psr/Log/InvalidArgumentException.php new file mode 100644 index 0000000000..67f852d1db --- /dev/null +++ b/sites/all/libraries/vendor/psr/log/Psr/Log/InvalidArgumentException.php @@ -0,0 +1,7 @@ +logger = $logger; + } +} diff --git a/sites/all/libraries/vendor/psr/log/Psr/Log/LoggerInterface.php b/sites/all/libraries/vendor/psr/log/Psr/Log/LoggerInterface.php new file mode 100644 index 0000000000..2206cfde41 --- /dev/null +++ b/sites/all/libraries/vendor/psr/log/Psr/Log/LoggerInterface.php @@ -0,0 +1,125 @@ +log(LogLevel::EMERGENCY, $message, $context); + } + + /** + * Action must be taken immediately. + * + * Example: Entire website down, database unavailable, etc. This should + * trigger the SMS alerts and wake you up. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function alert($message, array $context = array()) + { + $this->log(LogLevel::ALERT, $message, $context); + } + + /** + * Critical conditions. + * + * Example: Application component unavailable, unexpected exception. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function critical($message, array $context = array()) + { + $this->log(LogLevel::CRITICAL, $message, $context); + } + + /** + * Runtime errors that do not require immediate action but should typically + * be logged and monitored. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function error($message, array $context = array()) + { + $this->log(LogLevel::ERROR, $message, $context); + } + + /** + * Exceptional occurrences that are not errors. + * + * Example: Use of deprecated APIs, poor use of an API, undesirable things + * that are not necessarily wrong. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function warning($message, array $context = array()) + { + $this->log(LogLevel::WARNING, $message, $context); + } + + /** + * Normal but significant events. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function notice($message, array $context = array()) + { + $this->log(LogLevel::NOTICE, $message, $context); + } + + /** + * Interesting events. + * + * Example: User logs in, SQL logs. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function info($message, array $context = array()) + { + $this->log(LogLevel::INFO, $message, $context); + } + + /** + * Detailed debug information. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function debug($message, array $context = array()) + { + $this->log(LogLevel::DEBUG, $message, $context); + } + + /** + * Logs with an arbitrary level. + * + * @param mixed $level + * @param string $message + * @param array $context + * + * @return void + * + * @throws \Psr\Log\InvalidArgumentException + */ + abstract public function log($level, $message, array $context = array()); +} diff --git a/sites/all/libraries/vendor/psr/log/Psr/Log/NullLogger.php b/sites/all/libraries/vendor/psr/log/Psr/Log/NullLogger.php new file mode 100644 index 0000000000..c8f7293b1c --- /dev/null +++ b/sites/all/libraries/vendor/psr/log/Psr/Log/NullLogger.php @@ -0,0 +1,30 @@ +logger) { }` + * blocks. + */ +class NullLogger extends AbstractLogger +{ + /** + * Logs with an arbitrary level. + * + * @param mixed $level + * @param string $message + * @param array $context + * + * @return void + * + * @throws \Psr\Log\InvalidArgumentException + */ + public function log($level, $message, array $context = array()) + { + // noop + } +} diff --git a/sites/all/libraries/vendor/psr/log/Psr/Log/Test/DummyTest.php b/sites/all/libraries/vendor/psr/log/Psr/Log/Test/DummyTest.php new file mode 100644 index 0000000000..9638c11018 --- /dev/null +++ b/sites/all/libraries/vendor/psr/log/Psr/Log/Test/DummyTest.php @@ -0,0 +1,18 @@ + ". + * + * Example ->error('Foo') would yield "error Foo". + * + * @return string[] + */ + abstract public function getLogs(); + + public function testImplements() + { + $this->assertInstanceOf('Psr\Log\LoggerInterface', $this->getLogger()); + } + + /** + * @dataProvider provideLevelsAndMessages + */ + public function testLogsAtAllLevels($level, $message) + { + $logger = $this->getLogger(); + $logger->{$level}($message, array('user' => 'Bob')); + $logger->log($level, $message, array('user' => 'Bob')); + + $expected = array( + $level.' message of level '.$level.' with context: Bob', + $level.' message of level '.$level.' with context: Bob', + ); + $this->assertEquals($expected, $this->getLogs()); + } + + public function provideLevelsAndMessages() + { + return array( + LogLevel::EMERGENCY => array(LogLevel::EMERGENCY, 'message of level emergency with context: {user}'), + LogLevel::ALERT => array(LogLevel::ALERT, 'message of level alert with context: {user}'), + LogLevel::CRITICAL => array(LogLevel::CRITICAL, 'message of level critical with context: {user}'), + LogLevel::ERROR => array(LogLevel::ERROR, 'message of level error with context: {user}'), + LogLevel::WARNING => array(LogLevel::WARNING, 'message of level warning with context: {user}'), + LogLevel::NOTICE => array(LogLevel::NOTICE, 'message of level notice with context: {user}'), + LogLevel::INFO => array(LogLevel::INFO, 'message of level info with context: {user}'), + LogLevel::DEBUG => array(LogLevel::DEBUG, 'message of level debug with context: {user}'), + ); + } + + /** + * @expectedException \Psr\Log\InvalidArgumentException + */ + public function testThrowsOnInvalidLevel() + { + $logger = $this->getLogger(); + $logger->log('invalid level', 'Foo'); + } + + public function testContextReplacement() + { + $logger = $this->getLogger(); + $logger->info('{Message {nothing} {user} {foo.bar} a}', array('user' => 'Bob', 'foo.bar' => 'Bar')); + + $expected = array('info {Message {nothing} Bob Bar a}'); + $this->assertEquals($expected, $this->getLogs()); + } + + public function testObjectCastToString() + { + if (method_exists($this, 'createPartialMock')) { + $dummy = $this->createPartialMock('Psr\Log\Test\DummyTest', array('__toString')); + } else { + $dummy = $this->getMock('Psr\Log\Test\DummyTest', array('__toString')); + } + $dummy->expects($this->once()) + ->method('__toString') + ->will($this->returnValue('DUMMY')); + + $this->getLogger()->warning($dummy); + + $expected = array('warning DUMMY'); + $this->assertEquals($expected, $this->getLogs()); + } + + public function testContextCanContainAnything() + { + $closed = fopen('php://memory', 'r'); + fclose($closed); + + $context = array( + 'bool' => true, + 'null' => null, + 'string' => 'Foo', + 'int' => 0, + 'float' => 0.5, + 'nested' => array('with object' => new DummyTest), + 'object' => new \DateTime, + 'resource' => fopen('php://memory', 'r'), + 'closed' => $closed, + ); + + $this->getLogger()->warning('Crazy context data', $context); + + $expected = array('warning Crazy context data'); + $this->assertEquals($expected, $this->getLogs()); + } + + public function testContextExceptionKeyCanBeExceptionOrOtherValues() + { + $logger = $this->getLogger(); + $logger->warning('Random message', array('exception' => 'oops')); + $logger->critical('Uncaught Exception!', array('exception' => new \LogicException('Fail'))); + + $expected = array( + 'warning Random message', + 'critical Uncaught Exception!' + ); + $this->assertEquals($expected, $this->getLogs()); + } +} diff --git a/sites/all/libraries/vendor/psr/log/Psr/Log/Test/TestLogger.php b/sites/all/libraries/vendor/psr/log/Psr/Log/Test/TestLogger.php new file mode 100644 index 0000000000..1be3230496 --- /dev/null +++ b/sites/all/libraries/vendor/psr/log/Psr/Log/Test/TestLogger.php @@ -0,0 +1,147 @@ + $level, + 'message' => $message, + 'context' => $context, + ]; + + $this->recordsByLevel[$record['level']][] = $record; + $this->records[] = $record; + } + + public function hasRecords($level) + { + return isset($this->recordsByLevel[$level]); + } + + public function hasRecord($record, $level) + { + if (is_string($record)) { + $record = ['message' => $record]; + } + return $this->hasRecordThatPasses(function ($rec) use ($record) { + if ($rec['message'] !== $record['message']) { + return false; + } + if (isset($record['context']) && $rec['context'] !== $record['context']) { + return false; + } + return true; + }, $level); + } + + public function hasRecordThatContains($message, $level) + { + return $this->hasRecordThatPasses(function ($rec) use ($message) { + return strpos($rec['message'], $message) !== false; + }, $level); + } + + public function hasRecordThatMatches($regex, $level) + { + return $this->hasRecordThatPasses(function ($rec) use ($regex) { + return preg_match($regex, $rec['message']) > 0; + }, $level); + } + + public function hasRecordThatPasses(callable $predicate, $level) + { + if (!isset($this->recordsByLevel[$level])) { + return false; + } + foreach ($this->recordsByLevel[$level] as $i => $rec) { + if (call_user_func($predicate, $rec, $i)) { + return true; + } + } + return false; + } + + public function __call($method, $args) + { + if (preg_match('/(.*)(Debug|Info|Notice|Warning|Error|Critical|Alert|Emergency)(.*)/', $method, $matches) > 0) { + $genericMethod = $matches[1] . ('Records' !== $matches[3] ? 'Record' : '') . $matches[3]; + $level = strtolower($matches[2]); + if (method_exists($this, $genericMethod)) { + $args[] = $level; + return call_user_func_array([$this, $genericMethod], $args); + } + } + throw new \BadMethodCallException('Call to undefined method ' . get_class($this) . '::' . $method . '()'); + } + + public function reset() + { + $this->records = []; + $this->recordsByLevel = []; + } +} diff --git a/sites/all/libraries/vendor/psr/log/README.md b/sites/all/libraries/vendor/psr/log/README.md new file mode 100644 index 0000000000..a9f20c437b --- /dev/null +++ b/sites/all/libraries/vendor/psr/log/README.md @@ -0,0 +1,58 @@ +PSR Log +======= + +This repository holds all interfaces/classes/traits related to +[PSR-3](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md). + +Note that this is not a logger of its own. It is merely an interface that +describes a logger. See the specification for more details. + +Installation +------------ + +```bash +composer require psr/log +``` + +Usage +----- + +If you need a logger, you can use the interface like this: + +```php +logger = $logger; + } + + public function doSomething() + { + if ($this->logger) { + $this->logger->info('Doing work'); + } + + try { + $this->doSomethingElse(); + } catch (Exception $exception) { + $this->logger->error('Oh no!', array('exception' => $exception)); + } + + // do something useful + } +} +``` + +You can then pick one of the implementations of the interface to get a logger. + +If you want to implement the interface, you can require this package and +implement `Psr\Log\LoggerInterface` in your code. Please read the +[specification text](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md) +for details. diff --git a/sites/all/libraries/vendor/psr/log/composer.json b/sites/all/libraries/vendor/psr/log/composer.json new file mode 100644 index 0000000000..ca05695377 --- /dev/null +++ b/sites/all/libraries/vendor/psr/log/composer.json @@ -0,0 +1,26 @@ +{ + "name": "psr/log", + "description": "Common interface for logging libraries", + "keywords": ["psr", "psr-3", "log"], + "homepage": "https://github.com/php-fig/log", + "license": "MIT", + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "require": { + "php": ">=5.3.0" + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "Psr/Log/" + } + }, + "extra": { + "branch-alias": { + "dev-master": "1.1.x-dev" + } + } +} diff --git a/sites/all/libraries/vendor/symfony/deprecation-contracts b/sites/all/libraries/vendor/symfony/deprecation-contracts deleted file mode 160000 index e8b495ea28..0000000000 --- a/sites/all/libraries/vendor/symfony/deprecation-contracts +++ /dev/null @@ -1 +0,0 @@ -Subproject commit e8b495ea28c1d97b5e0c121748d6f9b53d075c66 diff --git a/sites/all/libraries/vendor/symfony/deprecation-contracts/.gitignore b/sites/all/libraries/vendor/symfony/deprecation-contracts/.gitignore new file mode 100644 index 0000000000..c49a5d8df5 --- /dev/null +++ b/sites/all/libraries/vendor/symfony/deprecation-contracts/.gitignore @@ -0,0 +1,3 @@ +vendor/ +composer.lock +phpunit.xml diff --git a/sites/all/libraries/vendor/symfony/deprecation-contracts/CHANGELOG.md b/sites/all/libraries/vendor/symfony/deprecation-contracts/CHANGELOG.md new file mode 100644 index 0000000000..7932e26132 --- /dev/null +++ b/sites/all/libraries/vendor/symfony/deprecation-contracts/CHANGELOG.md @@ -0,0 +1,5 @@ +CHANGELOG +========= + +The changelog is maintained for all Symfony contracts at the following URL: +https://github.com/symfony/contracts/blob/main/CHANGELOG.md diff --git a/sites/all/libraries/vendor/symfony/deprecation-contracts/LICENSE b/sites/all/libraries/vendor/symfony/deprecation-contracts/LICENSE new file mode 100644 index 0000000000..406242ff28 --- /dev/null +++ b/sites/all/libraries/vendor/symfony/deprecation-contracts/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2020-2022 Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/sites/all/libraries/vendor/symfony/deprecation-contracts/README.md b/sites/all/libraries/vendor/symfony/deprecation-contracts/README.md new file mode 100644 index 0000000000..4957933a6c --- /dev/null +++ b/sites/all/libraries/vendor/symfony/deprecation-contracts/README.md @@ -0,0 +1,26 @@ +Symfony Deprecation Contracts +============================= + +A generic function and convention to trigger deprecation notices. + +This package provides a single global function named `trigger_deprecation()` that triggers silenced deprecation notices. + +By using a custom PHP error handler such as the one provided by the Symfony ErrorHandler component, +the triggered deprecations can be caught and logged for later discovery, both on dev and prod environments. + +The function requires at least 3 arguments: + - the name of the Composer package that is triggering the deprecation + - the version of the package that introduced the deprecation + - the message of the deprecation + - more arguments can be provided: they will be inserted in the message using `printf()` formatting + +Example: +```php +trigger_deprecation('symfony/blockchain', '8.9', 'Using "%s" is deprecated, use "%s" instead.', 'bitcoin', 'fabcoin'); +``` + +This will generate the following message: +`Since symfony/blockchain 8.9: Using "bitcoin" is deprecated, use "fabcoin" instead.` + +While not necessarily recommended, the deprecation notices can be completely ignored by declaring an empty +`function trigger_deprecation() {}` in your application. diff --git a/sites/all/libraries/vendor/symfony/deprecation-contracts/composer.json b/sites/all/libraries/vendor/symfony/deprecation-contracts/composer.json new file mode 100644 index 0000000000..cc7cc12372 --- /dev/null +++ b/sites/all/libraries/vendor/symfony/deprecation-contracts/composer.json @@ -0,0 +1,35 @@ +{ + "name": "symfony/deprecation-contracts", + "type": "library", + "description": "A generic function and convention to trigger deprecation notices", + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=7.1" + }, + "autoload": { + "files": [ + "function.php" + ] + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-main": "2.5-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + } +} diff --git a/sites/all/libraries/vendor/symfony/deprecation-contracts/function.php b/sites/all/libraries/vendor/symfony/deprecation-contracts/function.php new file mode 100644 index 0000000000..d4371504a0 --- /dev/null +++ b/sites/all/libraries/vendor/symfony/deprecation-contracts/function.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +if (!function_exists('trigger_deprecation')) { + /** + * Triggers a silenced deprecation notice. + * + * @param string $package The name of the Composer package that is triggering the deprecation + * @param string $version The version of the package that introduced the deprecation + * @param string $message The message of the deprecation + * @param mixed ...$args Values to insert in the message using printf() formatting + * + * @author Nicolas Grekas + */ + function trigger_deprecation(string $package, string $version, string $message, ...$args): void + { + @trigger_error(($package || $version ? "Since $package $version: " : '').($args ? vsprintf($message, $args) : $message), \E_USER_DEPRECATED); + } +} diff --git a/sites/all/libraries/vendor/symfony/http-client b/sites/all/libraries/vendor/symfony/http-client deleted file mode 160000 index 8f29b0f06c..0000000000 --- a/sites/all/libraries/vendor/symfony/http-client +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 8f29b0f06c9ff48c8431e78eb90c8bd6f82cb12b diff --git a/sites/all/libraries/vendor/symfony/http-client-contracts b/sites/all/libraries/vendor/symfony/http-client-contracts deleted file mode 160000 index ba6a9f0e8f..0000000000 --- a/sites/all/libraries/vendor/symfony/http-client-contracts +++ /dev/null @@ -1 +0,0 @@ -Subproject commit ba6a9f0e8f3edd190520ee3b9a958596b6ca2e70 diff --git a/sites/all/libraries/vendor/symfony/http-client-contracts/.gitignore b/sites/all/libraries/vendor/symfony/http-client-contracts/.gitignore new file mode 100644 index 0000000000..c49a5d8df5 --- /dev/null +++ b/sites/all/libraries/vendor/symfony/http-client-contracts/.gitignore @@ -0,0 +1,3 @@ +vendor/ +composer.lock +phpunit.xml diff --git a/sites/all/libraries/vendor/symfony/http-client-contracts/CHANGELOG.md b/sites/all/libraries/vendor/symfony/http-client-contracts/CHANGELOG.md new file mode 100644 index 0000000000..7932e26132 --- /dev/null +++ b/sites/all/libraries/vendor/symfony/http-client-contracts/CHANGELOG.md @@ -0,0 +1,5 @@ +CHANGELOG +========= + +The changelog is maintained for all Symfony contracts at the following URL: +https://github.com/symfony/contracts/blob/main/CHANGELOG.md diff --git a/sites/all/libraries/vendor/symfony/http-client-contracts/ChunkInterface.php b/sites/all/libraries/vendor/symfony/http-client-contracts/ChunkInterface.php new file mode 100644 index 0000000000..0800cb3665 --- /dev/null +++ b/sites/all/libraries/vendor/symfony/http-client-contracts/ChunkInterface.php @@ -0,0 +1,71 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\HttpClient; + +use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface; + +/** + * The interface of chunks returned by ResponseStreamInterface::current(). + * + * When the chunk is first, last or timeout, the content MUST be empty. + * When an unchecked timeout or a network error occurs, a TransportExceptionInterface + * MUST be thrown by the destructor unless one was already thrown by another method. + * + * @author Nicolas Grekas + */ +interface ChunkInterface +{ + /** + * Tells when the idle timeout has been reached. + * + * @throws TransportExceptionInterface on a network error + */ + public function isTimeout(): bool; + + /** + * Tells when headers just arrived. + * + * @throws TransportExceptionInterface on a network error or when the idle timeout is reached + */ + public function isFirst(): bool; + + /** + * Tells when the body just completed. + * + * @throws TransportExceptionInterface on a network error or when the idle timeout is reached + */ + public function isLast(): bool; + + /** + * Returns a [status code, headers] tuple when a 1xx status code was just received. + * + * @throws TransportExceptionInterface on a network error or when the idle timeout is reached + */ + public function getInformationalStatus(): ?array; + + /** + * Returns the content of the response chunk. + * + * @throws TransportExceptionInterface on a network error or when the idle timeout is reached + */ + public function getContent(): string; + + /** + * Returns the offset of the chunk in the response body. + */ + public function getOffset(): int; + + /** + * In case of error, returns the message that describes it. + */ + public function getError(): ?string; +} diff --git a/sites/all/libraries/vendor/symfony/http-client-contracts/Exception/ClientExceptionInterface.php b/sites/all/libraries/vendor/symfony/http-client-contracts/Exception/ClientExceptionInterface.php new file mode 100644 index 0000000000..22d2b456a6 --- /dev/null +++ b/sites/all/libraries/vendor/symfony/http-client-contracts/Exception/ClientExceptionInterface.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\HttpClient\Exception; + +/** + * When a 4xx response is returned. + * + * @author Nicolas Grekas + */ +interface ClientExceptionInterface extends HttpExceptionInterface +{ +} diff --git a/sites/all/libraries/vendor/symfony/http-client-contracts/Exception/DecodingExceptionInterface.php b/sites/all/libraries/vendor/symfony/http-client-contracts/Exception/DecodingExceptionInterface.php new file mode 100644 index 0000000000..971a7a29b3 --- /dev/null +++ b/sites/all/libraries/vendor/symfony/http-client-contracts/Exception/DecodingExceptionInterface.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\HttpClient\Exception; + +/** + * When a content-type cannot be decoded to the expected representation. + * + * @author Nicolas Grekas + */ +interface DecodingExceptionInterface extends ExceptionInterface +{ +} diff --git a/sites/all/libraries/vendor/symfony/http-client-contracts/Exception/ExceptionInterface.php b/sites/all/libraries/vendor/symfony/http-client-contracts/Exception/ExceptionInterface.php new file mode 100644 index 0000000000..e553b47a1d --- /dev/null +++ b/sites/all/libraries/vendor/symfony/http-client-contracts/Exception/ExceptionInterface.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\HttpClient\Exception; + +/** + * The base interface for all exceptions in the contract. + * + * @author Nicolas Grekas + */ +interface ExceptionInterface extends \Throwable +{ +} diff --git a/sites/all/libraries/vendor/symfony/http-client-contracts/Exception/HttpExceptionInterface.php b/sites/all/libraries/vendor/symfony/http-client-contracts/Exception/HttpExceptionInterface.php new file mode 100644 index 0000000000..17865ed367 --- /dev/null +++ b/sites/all/libraries/vendor/symfony/http-client-contracts/Exception/HttpExceptionInterface.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\HttpClient\Exception; + +use Symfony\Contracts\HttpClient\ResponseInterface; + +/** + * Base interface for HTTP-related exceptions. + * + * @author Anton Chernikov + */ +interface HttpExceptionInterface extends ExceptionInterface +{ + public function getResponse(): ResponseInterface; +} diff --git a/sites/all/libraries/vendor/symfony/http-client-contracts/Exception/RedirectionExceptionInterface.php b/sites/all/libraries/vendor/symfony/http-client-contracts/Exception/RedirectionExceptionInterface.php new file mode 100644 index 0000000000..edd9b8a9bb --- /dev/null +++ b/sites/all/libraries/vendor/symfony/http-client-contracts/Exception/RedirectionExceptionInterface.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\HttpClient\Exception; + +/** + * When a 3xx response is returned and the "max_redirects" option has been reached. + * + * @author Nicolas Grekas + */ +interface RedirectionExceptionInterface extends HttpExceptionInterface +{ +} diff --git a/sites/all/libraries/vendor/symfony/http-client-contracts/Exception/ServerExceptionInterface.php b/sites/all/libraries/vendor/symfony/http-client-contracts/Exception/ServerExceptionInterface.php new file mode 100644 index 0000000000..9bfe1354b5 --- /dev/null +++ b/sites/all/libraries/vendor/symfony/http-client-contracts/Exception/ServerExceptionInterface.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\HttpClient\Exception; + +/** + * When a 5xx response is returned. + * + * @author Nicolas Grekas + */ +interface ServerExceptionInterface extends HttpExceptionInterface +{ +} diff --git a/sites/all/libraries/vendor/symfony/http-client-contracts/Exception/TimeoutExceptionInterface.php b/sites/all/libraries/vendor/symfony/http-client-contracts/Exception/TimeoutExceptionInterface.php new file mode 100644 index 0000000000..08acf9fb6d --- /dev/null +++ b/sites/all/libraries/vendor/symfony/http-client-contracts/Exception/TimeoutExceptionInterface.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\HttpClient\Exception; + +/** + * When an idle timeout occurs. + * + * @author Nicolas Grekas + */ +interface TimeoutExceptionInterface extends TransportExceptionInterface +{ +} diff --git a/sites/all/libraries/vendor/symfony/http-client-contracts/Exception/TransportExceptionInterface.php b/sites/all/libraries/vendor/symfony/http-client-contracts/Exception/TransportExceptionInterface.php new file mode 100644 index 0000000000..0c8d131a05 --- /dev/null +++ b/sites/all/libraries/vendor/symfony/http-client-contracts/Exception/TransportExceptionInterface.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\HttpClient\Exception; + +/** + * When any error happens at the transport level. + * + * @author Nicolas Grekas + */ +interface TransportExceptionInterface extends ExceptionInterface +{ +} diff --git a/sites/all/libraries/vendor/symfony/http-client-contracts/HttpClientInterface.php b/sites/all/libraries/vendor/symfony/http-client-contracts/HttpClientInterface.php new file mode 100644 index 0000000000..158c1a7d06 --- /dev/null +++ b/sites/all/libraries/vendor/symfony/http-client-contracts/HttpClientInterface.php @@ -0,0 +1,95 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\HttpClient; + +use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface; +use Symfony\Contracts\HttpClient\Test\HttpClientTestCase; + +/** + * Provides flexible methods for requesting HTTP resources synchronously or asynchronously. + * + * @see HttpClientTestCase for a reference test suite + * + * @method static withOptions(array $options) Returns a new instance of the client with new default options + * + * @author Nicolas Grekas + */ +interface HttpClientInterface +{ + public const OPTIONS_DEFAULTS = [ + 'auth_basic' => null, // array|string - an array containing the username as first value, and optionally the + // password as the second one; or string like username:password - enabling HTTP Basic + // authentication (RFC 7617) + 'auth_bearer' => null, // string - a token enabling HTTP Bearer authorization (RFC 6750) + 'query' => [], // string[] - associative array of query string values to merge with the request's URL + 'headers' => [], // iterable|string[]|string[][] - headers names provided as keys or as part of values + 'body' => '', // array|string|resource|\Traversable|\Closure - the callback SHOULD yield a string + // smaller than the amount requested as argument; the empty string signals EOF; if + // an array is passed, it is meant as a form payload of field names and values + 'json' => null, // mixed - if set, implementations MUST set the "body" option to the JSON-encoded + // value and set the "content-type" header to a JSON-compatible value if it is not + // explicitly defined in the headers option - typically "application/json" + 'user_data' => null, // mixed - any extra data to attach to the request (scalar, callable, object...) that + // MUST be available via $response->getInfo('user_data') - not used internally + 'max_redirects' => 20, // int - the maximum number of redirects to follow; a value lower than or equal to 0 + // means redirects should not be followed; "Authorization" and "Cookie" headers MUST + // NOT follow except for the initial host name + 'http_version' => null, // string - defaults to the best supported version, typically 1.1 or 2.0 + 'base_uri' => null, // string - the URI to resolve relative URLs, following rules in RFC 3986, section 2 + 'buffer' => true, // bool|resource|\Closure - whether the content of the response should be buffered or not, + // or a stream resource where the response body should be written, + // or a closure telling if/where the response should be buffered based on its headers + 'on_progress' => null, // callable(int $dlNow, int $dlSize, array $info) - throwing any exceptions MUST abort + // the request; it MUST be called on DNS resolution, on arrival of headers and on + // completion; it SHOULD be called on upload/download of data and at least 1/s + 'resolve' => [], // string[] - a map of host to IP address that SHOULD replace DNS resolution + 'proxy' => null, // string - by default, the proxy-related env vars handled by curl SHOULD be honored + 'no_proxy' => null, // string - a comma separated list of hosts that do not require a proxy to be reached + 'timeout' => null, // float - the idle timeout - defaults to ini_get('default_socket_timeout') + 'max_duration' => 0, // float - the maximum execution time for the request+response as a whole; + // a value lower than or equal to 0 means it is unlimited + 'bindto' => '0', // string - the interface or the local socket to bind to + 'verify_peer' => true, // see https://php.net/context.ssl for the following options + 'verify_host' => true, + 'cafile' => null, + 'capath' => null, + 'local_cert' => null, + 'local_pk' => null, + 'passphrase' => null, + 'ciphers' => null, + 'peer_fingerprint' => null, + 'capture_peer_cert_chain' => false, + 'extra' => [], // array - additional options that can be ignored if unsupported, unlike regular options + ]; + + /** + * Requests an HTTP resource. + * + * Responses MUST be lazy, but their status code MUST be + * checked even if none of their public methods are called. + * + * Implementations are not required to support all options described above; they can also + * support more custom options; but in any case, they MUST throw a TransportExceptionInterface + * when an unsupported option is passed. + * + * @throws TransportExceptionInterface When an unsupported option is passed + */ + public function request(string $method, string $url, array $options = []): ResponseInterface; + + /** + * Yields responses chunk by chunk as they complete. + * + * @param ResponseInterface|iterable $responses One or more responses created by the current HTTP client + * @param float|null $timeout The idle timeout before yielding timeout chunks + */ + public function stream($responses, float $timeout = null): ResponseStreamInterface; +} diff --git a/sites/all/libraries/vendor/symfony/http-client-contracts/LICENSE b/sites/all/libraries/vendor/symfony/http-client-contracts/LICENSE new file mode 100644 index 0000000000..74cdc2dbf6 --- /dev/null +++ b/sites/all/libraries/vendor/symfony/http-client-contracts/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2018-2022 Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/sites/all/libraries/vendor/symfony/http-client-contracts/README.md b/sites/all/libraries/vendor/symfony/http-client-contracts/README.md new file mode 100644 index 0000000000..03b3a69b70 --- /dev/null +++ b/sites/all/libraries/vendor/symfony/http-client-contracts/README.md @@ -0,0 +1,9 @@ +Symfony HttpClient Contracts +============================ + +A set of abstractions extracted out of the Symfony components. + +Can be used to build on semantics that the Symfony components proved useful - and +that already have battle tested implementations. + +See https://github.com/symfony/contracts/blob/main/README.md for more information. diff --git a/sites/all/libraries/vendor/symfony/http-client-contracts/ResponseInterface.php b/sites/all/libraries/vendor/symfony/http-client-contracts/ResponseInterface.php new file mode 100644 index 0000000000..df7148816e --- /dev/null +++ b/sites/all/libraries/vendor/symfony/http-client-contracts/ResponseInterface.php @@ -0,0 +1,109 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\HttpClient; + +use Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface; +use Symfony\Contracts\HttpClient\Exception\DecodingExceptionInterface; +use Symfony\Contracts\HttpClient\Exception\ExceptionInterface; +use Symfony\Contracts\HttpClient\Exception\RedirectionExceptionInterface; +use Symfony\Contracts\HttpClient\Exception\ServerExceptionInterface; +use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface; + +/** + * A (lazily retrieved) HTTP response. + * + * @author Nicolas Grekas + */ +interface ResponseInterface +{ + /** + * Gets the HTTP status code of the response. + * + * @throws TransportExceptionInterface when a network error occurs + */ + public function getStatusCode(): int; + + /** + * Gets the HTTP headers of the response. + * + * @param bool $throw Whether an exception should be thrown on 3/4/5xx status codes + * + * @return string[][] The headers of the response keyed by header names in lowercase + * + * @throws TransportExceptionInterface When a network error occurs + * @throws RedirectionExceptionInterface On a 3xx when $throw is true and the "max_redirects" option has been reached + * @throws ClientExceptionInterface On a 4xx when $throw is true + * @throws ServerExceptionInterface On a 5xx when $throw is true + */ + public function getHeaders(bool $throw = true): array; + + /** + * Gets the response body as a string. + * + * @param bool $throw Whether an exception should be thrown on 3/4/5xx status codes + * + * @throws TransportExceptionInterface When a network error occurs + * @throws RedirectionExceptionInterface On a 3xx when $throw is true and the "max_redirects" option has been reached + * @throws ClientExceptionInterface On a 4xx when $throw is true + * @throws ServerExceptionInterface On a 5xx when $throw is true + */ + public function getContent(bool $throw = true): string; + + /** + * Gets the response body decoded as array, typically from a JSON payload. + * + * @param bool $throw Whether an exception should be thrown on 3/4/5xx status codes + * + * @throws DecodingExceptionInterface When the body cannot be decoded to an array + * @throws TransportExceptionInterface When a network error occurs + * @throws RedirectionExceptionInterface On a 3xx when $throw is true and the "max_redirects" option has been reached + * @throws ClientExceptionInterface On a 4xx when $throw is true + * @throws ServerExceptionInterface On a 5xx when $throw is true + */ + public function toArray(bool $throw = true): array; + + /** + * Closes the response stream and all related buffers. + * + * No further chunk will be yielded after this method has been called. + */ + public function cancel(): void; + + /** + * Returns info coming from the transport layer. + * + * This method SHOULD NOT throw any ExceptionInterface and SHOULD be non-blocking. + * The returned info is "live": it can be empty and can change from one call to + * another, as the request/response progresses. + * + * The following info MUST be returned: + * - canceled (bool) - true if the response was canceled using ResponseInterface::cancel(), false otherwise + * - error (string|null) - the error message when the transfer was aborted, null otherwise + * - http_code (int) - the last response code or 0 when it is not known yet + * - http_method (string) - the HTTP verb of the last request + * - redirect_count (int) - the number of redirects followed while executing the request + * - redirect_url (string|null) - the resolved location of redirect responses, null otherwise + * - response_headers (array) - an array modelled after the special $http_response_header variable + * - start_time (float) - the time when the request was sent or 0.0 when it's pending + * - url (string) - the last effective URL of the request + * - user_data (mixed) - the value of the "user_data" request option, null if not set + * + * When the "capture_peer_cert_chain" option is true, the "peer_certificate_chain" + * attribute SHOULD list the peer certificates as an array of OpenSSL X.509 resources. + * + * Other info SHOULD be named after curl_getinfo()'s associative return value. + * + * @return mixed An array of all available info, or one of them when $type is + * provided, or null when an unsupported type is requested + */ + public function getInfo(string $type = null); +} diff --git a/sites/all/libraries/vendor/symfony/http-client-contracts/ResponseStreamInterface.php b/sites/all/libraries/vendor/symfony/http-client-contracts/ResponseStreamInterface.php new file mode 100644 index 0000000000..fa3e5db6c8 --- /dev/null +++ b/sites/all/libraries/vendor/symfony/http-client-contracts/ResponseStreamInterface.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\HttpClient; + +/** + * Yields response chunks, returned by HttpClientInterface::stream(). + * + * @author Nicolas Grekas + * + * @extends \Iterator + */ +interface ResponseStreamInterface extends \Iterator +{ + public function key(): ResponseInterface; + + public function current(): ChunkInterface; +} diff --git a/sites/all/libraries/vendor/symfony/http-client-contracts/Test/Fixtures/web/index.php b/sites/all/libraries/vendor/symfony/http-client-contracts/Test/Fixtures/web/index.php new file mode 100644 index 0000000000..30a7049758 --- /dev/null +++ b/sites/all/libraries/vendor/symfony/http-client-contracts/Test/Fixtures/web/index.php @@ -0,0 +1,192 @@ + $v) { + switch ($k) { + default: + if (0 !== strpos($k, 'HTTP_')) { + continue 2; + } + // no break + case 'SERVER_NAME': + case 'SERVER_PROTOCOL': + case 'REQUEST_URI': + case 'REQUEST_METHOD': + case 'PHP_AUTH_USER': + case 'PHP_AUTH_PW': + $vars[$k] = $v; + } +} + +$json = json_encode($vars, \JSON_PRETTY_PRINT | \JSON_UNESCAPED_SLASHES | \JSON_UNESCAPED_UNICODE); + +switch ($vars['REQUEST_URI']) { + default: + exit; + + case '/head': + header('Content-Length: '.strlen($json), true); + break; + + case '/': + case '/?a=a&b=b': + case 'http://127.0.0.1:8057/': + case 'http://localhost:8057/': + ob_start('ob_gzhandler'); + break; + + case '/103': + header('HTTP/1.1 103 Early Hints'); + header('Link: ; rel=preload; as=style', false); + header('Link: ; rel=preload; as=script', false); + flush(); + usleep(1000); + echo "HTTP/1.1 200 OK\r\n"; + echo "Date: Fri, 26 May 2017 10:02:11 GMT\r\n"; + echo "Content-Length: 13\r\n"; + echo "\r\n"; + echo 'Here the body'; + exit; + + case '/404': + header('Content-Type: application/json', true, 404); + break; + + case '/404-gzipped': + header('Content-Type: text/plain', true, 404); + ob_start('ob_gzhandler'); + @ob_flush(); + flush(); + usleep(300000); + echo 'some text'; + exit; + + case '/301': + if ('Basic Zm9vOmJhcg==' === $vars['HTTP_AUTHORIZATION']) { + header('Location: http://127.0.0.1:8057/302', true, 301); + } + break; + + case '/301/bad-tld': + header('Location: http://foo.example.', true, 301); + break; + + case '/301/invalid': + header('Location: //?foo=bar', true, 301); + break; + + case '/302': + if (!isset($vars['HTTP_AUTHORIZATION'])) { + header('Location: http://localhost:8057/', true, 302); + } + break; + + case '/302/relative': + header('Location: ..', true, 302); + break; + + case '/304': + header('Content-Length: 10', true, 304); + echo '12345'; + + return; + + case '/307': + header('Location: http://localhost:8057/post', true, 307); + break; + + case '/length-broken': + header('Content-Length: 1000'); + break; + + case '/post': + $output = json_encode($_POST + ['REQUEST_METHOD' => $vars['REQUEST_METHOD']], \JSON_PRETTY_PRINT | \JSON_UNESCAPED_SLASHES | \JSON_UNESCAPED_UNICODE); + header('Content-Type: application/json', true); + header('Content-Length: '.strlen($output)); + echo $output; + exit; + + case '/timeout-header': + usleep(300000); + break; + + case '/timeout-body': + echo '<1>'; + @ob_flush(); + flush(); + usleep(500000); + echo '<2>'; + exit; + + case '/timeout-long': + ignore_user_abort(false); + sleep(1); + while (true) { + echo '<1>'; + @ob_flush(); + flush(); + usleep(500); + } + exit; + + case '/chunked': + header('Transfer-Encoding: chunked'); + echo "8\r\nSymfony \r\n5\r\nis aw\r\n6\r\nesome!\r\n0\r\n\r\n"; + exit; + + case '/chunked-broken': + header('Transfer-Encoding: chunked'); + echo "8\r\nSymfony \r\n5\r\nis aw\r\n6\r\ne"; + exit; + + case '/gzip-broken': + header('Content-Encoding: gzip'); + echo str_repeat('-', 1000); + exit; + + case '/max-duration': + ignore_user_abort(false); + while (true) { + echo '<1>'; + @ob_flush(); + flush(); + usleep(500); + } + exit; + + case '/json': + header('Content-Type: application/json'); + echo json_encode([ + 'documents' => [ + ['id' => '/json/1'], + ['id' => '/json/2'], + ['id' => '/json/3'], + ], + ]); + exit; + + case '/json/1': + case '/json/2': + case '/json/3': + header('Content-Type: application/json'); + echo json_encode([ + 'title' => $vars['REQUEST_URI'], + ]); + + exit; +} + +header('Content-Type: application/json', true); + +echo $json; diff --git a/sites/all/libraries/vendor/symfony/http-client-contracts/Test/HttpClientTestCase.php b/sites/all/libraries/vendor/symfony/http-client-contracts/Test/HttpClientTestCase.php new file mode 100644 index 0000000000..7acd6b79c1 --- /dev/null +++ b/sites/all/libraries/vendor/symfony/http-client-contracts/Test/HttpClientTestCase.php @@ -0,0 +1,1137 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\HttpClient\Test; + +use PHPUnit\Framework\TestCase; +use Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface; +use Symfony\Contracts\HttpClient\Exception\RedirectionExceptionInterface; +use Symfony\Contracts\HttpClient\Exception\TimeoutExceptionInterface; +use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface; +use Symfony\Contracts\HttpClient\HttpClientInterface; + +/** + * A reference test suite for HttpClientInterface implementations. + */ +abstract class HttpClientTestCase extends TestCase +{ + public static function setUpBeforeClass(): void + { + TestHttpServer::start(); + } + + abstract protected function getHttpClient(string $testCase): HttpClientInterface; + + public function testGetRequest() + { + $client = $this->getHttpClient(__FUNCTION__); + $response = $client->request('GET', 'http://localhost:8057', [ + 'headers' => ['Foo' => 'baR'], + 'user_data' => $data = new \stdClass(), + ]); + + $this->assertSame([], $response->getInfo('response_headers')); + $this->assertSame($data, $response->getInfo()['user_data']); + $this->assertSame(200, $response->getStatusCode()); + + $info = $response->getInfo(); + $this->assertNull($info['error']); + $this->assertSame(0, $info['redirect_count']); + $this->assertSame('HTTP/1.1 200 OK', $info['response_headers'][0]); + $this->assertSame('Host: localhost:8057', $info['response_headers'][1]); + $this->assertSame('http://localhost:8057/', $info['url']); + + $headers = $response->getHeaders(); + + $this->assertSame('localhost:8057', $headers['host'][0]); + $this->assertSame(['application/json'], $headers['content-type']); + + $body = json_decode($response->getContent(), true); + $this->assertSame($body, $response->toArray()); + + $this->assertSame('HTTP/1.1', $body['SERVER_PROTOCOL']); + $this->assertSame('/', $body['REQUEST_URI']); + $this->assertSame('GET', $body['REQUEST_METHOD']); + $this->assertSame('localhost:8057', $body['HTTP_HOST']); + $this->assertSame('baR', $body['HTTP_FOO']); + + $response = $client->request('GET', 'http://localhost:8057/length-broken'); + + $this->expectException(TransportExceptionInterface::class); + $response->getContent(); + } + + public function testHeadRequest() + { + $client = $this->getHttpClient(__FUNCTION__); + $response = $client->request('HEAD', 'http://localhost:8057/head', [ + 'headers' => ['Foo' => 'baR'], + 'user_data' => $data = new \stdClass(), + 'buffer' => false, + ]); + + $this->assertSame([], $response->getInfo('response_headers')); + $this->assertSame(200, $response->getStatusCode()); + + $info = $response->getInfo(); + $this->assertSame('HTTP/1.1 200 OK', $info['response_headers'][0]); + $this->assertSame('Host: localhost:8057', $info['response_headers'][1]); + + $headers = $response->getHeaders(); + + $this->assertSame('localhost:8057', $headers['host'][0]); + $this->assertSame(['application/json'], $headers['content-type']); + $this->assertTrue(0 < $headers['content-length'][0]); + + $this->assertSame('', $response->getContent()); + } + + public function testNonBufferedGetRequest() + { + $client = $this->getHttpClient(__FUNCTION__); + $response = $client->request('GET', 'http://localhost:8057', [ + 'buffer' => false, + 'headers' => ['Foo' => 'baR'], + ]); + + $body = $response->toArray(); + $this->assertSame('baR', $body['HTTP_FOO']); + + $this->expectException(TransportExceptionInterface::class); + $response->getContent(); + } + + public function testBufferSink() + { + $sink = fopen('php://temp', 'w+'); + $client = $this->getHttpClient(__FUNCTION__); + $response = $client->request('GET', 'http://localhost:8057', [ + 'buffer' => $sink, + 'headers' => ['Foo' => 'baR'], + ]); + + $body = $response->toArray(); + $this->assertSame('baR', $body['HTTP_FOO']); + + rewind($sink); + $sink = stream_get_contents($sink); + $this->assertSame($sink, $response->getContent()); + } + + public function testConditionalBuffering() + { + $client = $this->getHttpClient(__FUNCTION__); + $response = $client->request('GET', 'http://localhost:8057'); + $firstContent = $response->getContent(); + $secondContent = $response->getContent(); + + $this->assertSame($firstContent, $secondContent); + + $response = $client->request('GET', 'http://localhost:8057', ['buffer' => function () { return false; }]); + $response->getContent(); + + $this->expectException(TransportExceptionInterface::class); + $response->getContent(); + } + + public function testReentrantBufferCallback() + { + $client = $this->getHttpClient(__FUNCTION__); + + $response = $client->request('GET', 'http://localhost:8057', ['buffer' => function () use (&$response) { + $response->cancel(); + + return true; + }]); + + $this->assertSame(200, $response->getStatusCode()); + + $this->expectException(TransportExceptionInterface::class); + $response->getContent(); + } + + public function testThrowingBufferCallback() + { + $client = $this->getHttpClient(__FUNCTION__); + + $response = $client->request('GET', 'http://localhost:8057', ['buffer' => function () { + throw new \Exception('Boo.'); + }]); + + $this->assertSame(200, $response->getStatusCode()); + + $this->expectException(TransportExceptionInterface::class); + $this->expectExceptionMessage('Boo'); + $response->getContent(); + } + + public function testUnsupportedOption() + { + $client = $this->getHttpClient(__FUNCTION__); + + $this->expectException(\InvalidArgumentException::class); + $client->request('GET', 'http://localhost:8057', [ + 'capture_peer_cert' => 1.0, + ]); + } + + public function testHttpVersion() + { + $client = $this->getHttpClient(__FUNCTION__); + $response = $client->request('GET', 'http://localhost:8057', [ + 'http_version' => 1.0, + ]); + + $this->assertSame(200, $response->getStatusCode()); + $this->assertSame('HTTP/1.0 200 OK', $response->getInfo('response_headers')[0]); + + $body = $response->toArray(); + + $this->assertSame('HTTP/1.0', $body['SERVER_PROTOCOL']); + $this->assertSame('GET', $body['REQUEST_METHOD']); + $this->assertSame('/', $body['REQUEST_URI']); + } + + public function testChunkedEncoding() + { + $client = $this->getHttpClient(__FUNCTION__); + $response = $client->request('GET', 'http://localhost:8057/chunked'); + + $this->assertSame(['chunked'], $response->getHeaders()['transfer-encoding']); + $this->assertSame('Symfony is awesome!', $response->getContent()); + + $response = $client->request('GET', 'http://localhost:8057/chunked-broken'); + + $this->expectException(TransportExceptionInterface::class); + $response->getContent(); + } + + public function testClientError() + { + $client = $this->getHttpClient(__FUNCTION__); + $response = $client->request('GET', 'http://localhost:8057/404'); + + $client->stream($response)->valid(); + + $this->assertSame(404, $response->getInfo('http_code')); + + try { + $response->getHeaders(); + $this->fail(ClientExceptionInterface::class.' expected'); + } catch (ClientExceptionInterface $e) { + } + + try { + $response->getContent(); + $this->fail(ClientExceptionInterface::class.' expected'); + } catch (ClientExceptionInterface $e) { + } + + $this->assertSame(404, $response->getStatusCode()); + $this->assertSame(['application/json'], $response->getHeaders(false)['content-type']); + $this->assertNotEmpty($response->getContent(false)); + + $response = $client->request('GET', 'http://localhost:8057/404'); + + try { + foreach ($client->stream($response) as $chunk) { + $this->assertTrue($chunk->isFirst()); + } + $this->fail(ClientExceptionInterface::class.' expected'); + } catch (ClientExceptionInterface $e) { + } + } + + public function testIgnoreErrors() + { + $client = $this->getHttpClient(__FUNCTION__); + $response = $client->request('GET', 'http://localhost:8057/404'); + + $this->assertSame(404, $response->getStatusCode()); + } + + public function testDnsError() + { + $client = $this->getHttpClient(__FUNCTION__); + $response = $client->request('GET', 'http://localhost:8057/301/bad-tld'); + + try { + $response->getStatusCode(); + $this->fail(TransportExceptionInterface::class.' expected'); + } catch (TransportExceptionInterface $e) { + $this->addToAssertionCount(1); + } + + try { + $response->getStatusCode(); + $this->fail(TransportExceptionInterface::class.' still expected'); + } catch (TransportExceptionInterface $e) { + $this->addToAssertionCount(1); + } + + $response = $client->request('GET', 'http://localhost:8057/301/bad-tld'); + + try { + foreach ($client->stream($response) as $r => $chunk) { + } + $this->fail(TransportExceptionInterface::class.' expected'); + } catch (TransportExceptionInterface $e) { + $this->addToAssertionCount(1); + } + + $this->assertSame($response, $r); + $this->assertNotNull($chunk->getError()); + + $this->expectException(TransportExceptionInterface::class); + foreach ($client->stream($response) as $chunk) { + } + } + + public function testInlineAuth() + { + $client = $this->getHttpClient(__FUNCTION__); + $response = $client->request('GET', 'http://foo:bar%3Dbar@localhost:8057'); + + $body = $response->toArray(); + + $this->assertSame('foo', $body['PHP_AUTH_USER']); + $this->assertSame('bar=bar', $body['PHP_AUTH_PW']); + } + + public function testBadRequestBody() + { + $client = $this->getHttpClient(__FUNCTION__); + + $this->expectException(TransportExceptionInterface::class); + + $response = $client->request('POST', 'http://localhost:8057/', [ + 'body' => function () { yield []; }, + ]); + + $response->getStatusCode(); + } + + public function test304() + { + $client = $this->getHttpClient(__FUNCTION__); + $response = $client->request('GET', 'http://localhost:8057/304', [ + 'headers' => ['If-Match' => '"abc"'], + 'buffer' => false, + ]); + + $this->assertSame(304, $response->getStatusCode()); + $this->assertSame('', $response->getContent(false)); + } + + /** + * @testWith [[]] + * [["Content-Length: 7"]] + */ + public function testRedirects(array $headers = []) + { + $client = $this->getHttpClient(__FUNCTION__); + $response = $client->request('POST', 'http://localhost:8057/301', [ + 'auth_basic' => 'foo:bar', + 'headers' => $headers, + 'body' => function () { + yield 'foo=bar'; + }, + ]); + + $body = $response->toArray(); + $this->assertSame('GET', $body['REQUEST_METHOD']); + $this->assertSame('Basic Zm9vOmJhcg==', $body['HTTP_AUTHORIZATION']); + $this->assertSame('http://localhost:8057/', $response->getInfo('url')); + + $this->assertSame(2, $response->getInfo('redirect_count')); + $this->assertNull($response->getInfo('redirect_url')); + + $expected = [ + 'HTTP/1.1 301 Moved Permanently', + 'Location: http://127.0.0.1:8057/302', + 'Content-Type: application/json', + 'HTTP/1.1 302 Found', + 'Location: http://localhost:8057/', + 'Content-Type: application/json', + 'HTTP/1.1 200 OK', + 'Content-Type: application/json', + ]; + + $filteredHeaders = array_values(array_filter($response->getInfo('response_headers'), function ($h) { + return \in_array(substr($h, 0, 4), ['HTTP', 'Loca', 'Cont'], true) && 'Content-Encoding: gzip' !== $h; + })); + + $this->assertSame($expected, $filteredHeaders); + } + + public function testInvalidRedirect() + { + $client = $this->getHttpClient(__FUNCTION__); + $response = $client->request('GET', 'http://localhost:8057/301/invalid'); + + $this->assertSame(301, $response->getStatusCode()); + $this->assertSame(['//?foo=bar'], $response->getHeaders(false)['location']); + $this->assertSame(0, $response->getInfo('redirect_count')); + $this->assertNull($response->getInfo('redirect_url')); + + $this->expectException(RedirectionExceptionInterface::class); + $response->getHeaders(); + } + + public function testRelativeRedirects() + { + $client = $this->getHttpClient(__FUNCTION__); + $response = $client->request('GET', 'http://localhost:8057/302/relative'); + + $body = $response->toArray(); + + $this->assertSame('/', $body['REQUEST_URI']); + $this->assertNull($response->getInfo('redirect_url')); + + $response = $client->request('GET', 'http://localhost:8057/302/relative', [ + 'max_redirects' => 0, + ]); + + $this->assertSame(302, $response->getStatusCode()); + $this->assertSame('http://localhost:8057/', $response->getInfo('redirect_url')); + } + + public function testRedirect307() + { + $client = $this->getHttpClient(__FUNCTION__); + + $response = $client->request('POST', 'http://localhost:8057/307', [ + 'body' => function () { + yield 'foo=bar'; + }, + 'max_redirects' => 0, + ]); + + $this->assertSame(307, $response->getStatusCode()); + + $response = $client->request('POST', 'http://localhost:8057/307', [ + 'body' => 'foo=bar', + ]); + + $body = $response->toArray(); + + $this->assertSame(['foo' => 'bar', 'REQUEST_METHOD' => 'POST'], $body); + } + + public function testMaxRedirects() + { + $client = $this->getHttpClient(__FUNCTION__); + $response = $client->request('GET', 'http://localhost:8057/301', [ + 'max_redirects' => 1, + 'auth_basic' => 'foo:bar', + ]); + + try { + $response->getHeaders(); + $this->fail(RedirectionExceptionInterface::class.' expected'); + } catch (RedirectionExceptionInterface $e) { + } + + $this->assertSame(302, $response->getStatusCode()); + $this->assertSame(1, $response->getInfo('redirect_count')); + $this->assertSame('http://localhost:8057/', $response->getInfo('redirect_url')); + + $expected = [ + 'HTTP/1.1 301 Moved Permanently', + 'Location: http://127.0.0.1:8057/302', + 'Content-Type: application/json', + 'HTTP/1.1 302 Found', + 'Location: http://localhost:8057/', + 'Content-Type: application/json', + ]; + + $filteredHeaders = array_values(array_filter($response->getInfo('response_headers'), function ($h) { + return \in_array(substr($h, 0, 4), ['HTTP', 'Loca', 'Cont'], true); + })); + + $this->assertSame($expected, $filteredHeaders); + } + + public function testStream() + { + $client = $this->getHttpClient(__FUNCTION__); + + $response = $client->request('GET', 'http://localhost:8057'); + $chunks = $client->stream($response); + $result = []; + + foreach ($chunks as $r => $chunk) { + if ($chunk->isTimeout()) { + $result[] = 't'; + } elseif ($chunk->isLast()) { + $result[] = 'l'; + } elseif ($chunk->isFirst()) { + $result[] = 'f'; + } + } + + $this->assertSame($response, $r); + $this->assertSame(['f', 'l'], $result); + + $chunk = null; + $i = 0; + + foreach ($client->stream($response) as $chunk) { + ++$i; + } + + $this->assertSame(1, $i); + $this->assertTrue($chunk->isLast()); + } + + public function testAddToStream() + { + $client = $this->getHttpClient(__FUNCTION__); + + $r1 = $client->request('GET', 'http://localhost:8057'); + + $completed = []; + + $pool = [$r1]; + + while ($pool) { + $chunks = $client->stream($pool); + $pool = []; + + foreach ($chunks as $r => $chunk) { + if (!$chunk->isLast()) { + continue; + } + + if ($r1 === $r) { + $r2 = $client->request('GET', 'http://localhost:8057'); + $pool[] = $r2; + } + + $completed[] = $r; + } + } + + $this->assertSame([$r1, $r2], $completed); + } + + public function testCompleteTypeError() + { + $client = $this->getHttpClient(__FUNCTION__); + + $this->expectException(\TypeError::class); + $client->stream(123); + } + + public function testOnProgress() + { + $client = $this->getHttpClient(__FUNCTION__); + $response = $client->request('POST', 'http://localhost:8057/post', [ + 'headers' => ['Content-Length' => 14], + 'body' => 'foo=0123456789', + 'on_progress' => function (...$state) use (&$steps) { $steps[] = $state; }, + ]); + + $body = $response->toArray(); + + $this->assertSame(['foo' => '0123456789', 'REQUEST_METHOD' => 'POST'], $body); + $this->assertSame([0, 0], \array_slice($steps[0], 0, 2)); + $lastStep = \array_slice($steps, -1)[0]; + $this->assertSame([57, 57], \array_slice($lastStep, 0, 2)); + $this->assertSame('http://localhost:8057/post', $steps[0][2]['url']); + } + + public function testPostJson() + { + $client = $this->getHttpClient(__FUNCTION__); + + $response = $client->request('POST', 'http://localhost:8057/post', [ + 'json' => ['foo' => 'bar'], + ]); + + $body = $response->toArray(); + + $this->assertStringContainsString('json', $body['content-type']); + unset($body['content-type']); + $this->assertSame(['foo' => 'bar', 'REQUEST_METHOD' => 'POST'], $body); + } + + public function testPostArray() + { + $client = $this->getHttpClient(__FUNCTION__); + + $response = $client->request('POST', 'http://localhost:8057/post', [ + 'body' => ['foo' => 'bar'], + ]); + + $this->assertSame(['foo' => 'bar', 'REQUEST_METHOD' => 'POST'], $response->toArray()); + } + + public function testPostResource() + { + $client = $this->getHttpClient(__FUNCTION__); + + $h = fopen('php://temp', 'w+'); + fwrite($h, 'foo=0123456789'); + rewind($h); + + $response = $client->request('POST', 'http://localhost:8057/post', [ + 'body' => $h, + ]); + + $body = $response->toArray(); + + $this->assertSame(['foo' => '0123456789', 'REQUEST_METHOD' => 'POST'], $body); + } + + public function testPostCallback() + { + $client = $this->getHttpClient(__FUNCTION__); + + $response = $client->request('POST', 'http://localhost:8057/post', [ + 'body' => function () { + yield 'foo'; + yield ''; + yield '='; + yield '0123456789'; + }, + ]); + + $this->assertSame(['foo' => '0123456789', 'REQUEST_METHOD' => 'POST'], $response->toArray()); + } + + public function testCancel() + { + $client = $this->getHttpClient(__FUNCTION__); + $response = $client->request('GET', 'http://localhost:8057/timeout-header'); + + $response->cancel(); + $this->expectException(TransportExceptionInterface::class); + $response->getHeaders(); + } + + public function testInfoOnCanceledResponse() + { + $client = $this->getHttpClient(__FUNCTION__); + + $response = $client->request('GET', 'http://localhost:8057/timeout-header'); + + $this->assertFalse($response->getInfo('canceled')); + $response->cancel(); + $this->assertTrue($response->getInfo('canceled')); + } + + public function testCancelInStream() + { + $client = $this->getHttpClient(__FUNCTION__); + $response = $client->request('GET', 'http://localhost:8057/404'); + + foreach ($client->stream($response) as $chunk) { + $response->cancel(); + } + + $this->expectException(TransportExceptionInterface::class); + + foreach ($client->stream($response) as $chunk) { + } + } + + public function testOnProgressCancel() + { + $client = $this->getHttpClient(__FUNCTION__); + $response = $client->request('GET', 'http://localhost:8057/timeout-body', [ + 'on_progress' => function ($dlNow) { + if (0 < $dlNow) { + throw new \Exception('Aborting the request.'); + } + }, + ]); + + try { + foreach ($client->stream([$response]) as $chunk) { + } + $this->fail(ClientExceptionInterface::class.' expected'); + } catch (TransportExceptionInterface $e) { + $this->assertSame('Aborting the request.', $e->getPrevious()->getMessage()); + } + + $this->assertNotNull($response->getInfo('error')); + $this->expectException(TransportExceptionInterface::class); + $response->getContent(); + } + + public function testOnProgressError() + { + $client = $this->getHttpClient(__FUNCTION__); + $response = $client->request('GET', 'http://localhost:8057/timeout-body', [ + 'on_progress' => function ($dlNow) { + if (0 < $dlNow) { + throw new \Error('BUG.'); + } + }, + ]); + + try { + foreach ($client->stream([$response]) as $chunk) { + } + $this->fail('Error expected'); + } catch (\Error $e) { + $this->assertSame('BUG.', $e->getMessage()); + } + + $this->assertNotNull($response->getInfo('error')); + $this->expectException(TransportExceptionInterface::class); + $response->getContent(); + } + + public function testResolve() + { + $client = $this->getHttpClient(__FUNCTION__); + $response = $client->request('GET', 'http://symfony.com:8057/', [ + 'resolve' => ['symfony.com' => '127.0.0.1'], + ]); + + $this->assertSame(200, $response->getStatusCode()); + $this->assertSame(200, $client->request('GET', 'http://symfony.com:8057/')->getStatusCode()); + + $response = null; + $this->expectException(TransportExceptionInterface::class); + $client->request('GET', 'http://symfony.com:8057/', ['timeout' => 1]); + } + + public function testIdnResolve() + { + $client = $this->getHttpClient(__FUNCTION__); + + $response = $client->request('GET', 'http://0-------------------------------------------------------------0.com:8057/', [ + 'resolve' => ['0-------------------------------------------------------------0.com' => '127.0.0.1'], + ]); + + $this->assertSame(200, $response->getStatusCode()); + + $response = $client->request('GET', 'http://Bücher.example:8057/', [ + 'resolve' => ['xn--bcher-kva.example' => '127.0.0.1'], + ]); + + $this->assertSame(200, $response->getStatusCode()); + } + + public function testNotATimeout() + { + $client = $this->getHttpClient(__FUNCTION__); + $response = $client->request('GET', 'http://localhost:8057/timeout-header', [ + 'timeout' => 0.9, + ]); + sleep(1); + $this->assertSame(200, $response->getStatusCode()); + } + + public function testTimeoutOnAccess() + { + $client = $this->getHttpClient(__FUNCTION__); + $response = $client->request('GET', 'http://localhost:8057/timeout-header', [ + 'timeout' => 0.1, + ]); + + $this->expectException(TransportExceptionInterface::class); + $response->getHeaders(); + } + + public function testTimeoutIsNotAFatalError() + { + usleep(300000); // wait for the previous test to release the server + $client = $this->getHttpClient(__FUNCTION__); + $response = $client->request('GET', 'http://localhost:8057/timeout-body', [ + 'timeout' => 0.25, + ]); + + try { + $response->getContent(); + $this->fail(TimeoutExceptionInterface::class.' expected'); + } catch (TimeoutExceptionInterface $e) { + } + + for ($i = 0; $i < 10; ++$i) { + try { + $this->assertSame('<1><2>', $response->getContent()); + break; + } catch (TimeoutExceptionInterface $e) { + } + } + + if (10 === $i) { + throw $e; + } + } + + public function testTimeoutOnStream() + { + $client = $this->getHttpClient(__FUNCTION__); + $response = $client->request('GET', 'http://localhost:8057/timeout-body'); + + $this->assertSame(200, $response->getStatusCode()); + $chunks = $client->stream([$response], 0.2); + + $result = []; + + foreach ($chunks as $r => $chunk) { + if ($chunk->isTimeout()) { + $result[] = 't'; + } else { + $result[] = $chunk->getContent(); + } + } + + $this->assertSame(['<1>', 't'], $result); + + $chunks = $client->stream([$response]); + + foreach ($chunks as $r => $chunk) { + $this->assertSame('<2>', $chunk->getContent()); + $this->assertSame('<1><2>', $r->getContent()); + + return; + } + + $this->fail('The response should have completed'); + } + + public function testUncheckedTimeoutThrows() + { + $client = $this->getHttpClient(__FUNCTION__); + $response = $client->request('GET', 'http://localhost:8057/timeout-body'); + $chunks = $client->stream([$response], 0.1); + + $this->expectException(TransportExceptionInterface::class); + + foreach ($chunks as $r => $chunk) { + } + } + + public function testTimeoutWithActiveConcurrentStream() + { + $p1 = TestHttpServer::start(8067); + $p2 = TestHttpServer::start(8077); + + $client = $this->getHttpClient(__FUNCTION__); + $streamingResponse = $client->request('GET', 'http://localhost:8067/max-duration'); + $blockingResponse = $client->request('GET', 'http://localhost:8077/timeout-body', [ + 'timeout' => 0.25, + ]); + + $this->assertSame(200, $streamingResponse->getStatusCode()); + $this->assertSame(200, $blockingResponse->getStatusCode()); + + $this->expectException(TransportExceptionInterface::class); + + try { + $blockingResponse->getContent(); + } finally { + $p1->stop(); + $p2->stop(); + } + } + + public function testTimeoutOnInitialize() + { + $p1 = TestHttpServer::start(8067); + $p2 = TestHttpServer::start(8077); + + $client = $this->getHttpClient(__FUNCTION__); + $start = microtime(true); + $responses = []; + + $responses[] = $client->request('GET', 'http://localhost:8067/timeout-header', ['timeout' => 0.25]); + $responses[] = $client->request('GET', 'http://localhost:8077/timeout-header', ['timeout' => 0.25]); + $responses[] = $client->request('GET', 'http://localhost:8067/timeout-header', ['timeout' => 0.25]); + $responses[] = $client->request('GET', 'http://localhost:8077/timeout-header', ['timeout' => 0.25]); + + try { + foreach ($responses as $response) { + try { + $response->getContent(); + $this->fail(TransportExceptionInterface::class.' expected'); + } catch (TransportExceptionInterface $e) { + } + } + $responses = []; + + $duration = microtime(true) - $start; + + $this->assertLessThan(1.0, $duration); + } finally { + $p1->stop(); + $p2->stop(); + } + } + + public function testTimeoutOnDestruct() + { + $p1 = TestHttpServer::start(8067); + $p2 = TestHttpServer::start(8077); + + $client = $this->getHttpClient(__FUNCTION__); + $start = microtime(true); + $responses = []; + + $responses[] = $client->request('GET', 'http://localhost:8067/timeout-header', ['timeout' => 0.25]); + $responses[] = $client->request('GET', 'http://localhost:8077/timeout-header', ['timeout' => 0.25]); + $responses[] = $client->request('GET', 'http://localhost:8067/timeout-header', ['timeout' => 0.25]); + $responses[] = $client->request('GET', 'http://localhost:8077/timeout-header', ['timeout' => 0.25]); + + try { + while ($response = array_shift($responses)) { + try { + unset($response); + $this->fail(TransportExceptionInterface::class.' expected'); + } catch (TransportExceptionInterface $e) { + } + } + + $duration = microtime(true) - $start; + + $this->assertLessThan(1.0, $duration); + } finally { + $p1->stop(); + $p2->stop(); + } + } + + public function testDestruct() + { + $client = $this->getHttpClient(__FUNCTION__); + + $start = microtime(true); + $client->request('GET', 'http://localhost:8057/timeout-long'); + $client = null; + $duration = microtime(true) - $start; + + $this->assertGreaterThan(1, $duration); + $this->assertLessThan(4, $duration); + } + + public function testGetContentAfterDestruct() + { + $client = $this->getHttpClient(__FUNCTION__); + + try { + $client->request('GET', 'http://localhost:8057/404'); + $this->fail(ClientExceptionInterface::class.' expected'); + } catch (ClientExceptionInterface $e) { + $this->assertSame('GET', $e->getResponse()->toArray(false)['REQUEST_METHOD']); + } + } + + public function testGetEncodedContentAfterDestruct() + { + $client = $this->getHttpClient(__FUNCTION__); + + try { + $client->request('GET', 'http://localhost:8057/404-gzipped'); + $this->fail(ClientExceptionInterface::class.' expected'); + } catch (ClientExceptionInterface $e) { + $this->assertSame('some text', $e->getResponse()->getContent(false)); + } + } + + public function testProxy() + { + $client = $this->getHttpClient(__FUNCTION__); + $response = $client->request('GET', 'http://localhost:8057/', [ + 'proxy' => 'http://localhost:8057', + ]); + + $body = $response->toArray(); + $this->assertSame('localhost:8057', $body['HTTP_HOST']); + $this->assertMatchesRegularExpression('#^http://(localhost|127\.0\.0\.1):8057/$#', $body['REQUEST_URI']); + + $response = $client->request('GET', 'http://localhost:8057/', [ + 'proxy' => 'http://foo:b%3Dar@localhost:8057', + ]); + + $body = $response->toArray(); + $this->assertSame('Basic Zm9vOmI9YXI=', $body['HTTP_PROXY_AUTHORIZATION']); + + $_SERVER['http_proxy'] = 'http://localhost:8057'; + try { + $response = $client->request('GET', 'http://localhost:8057/'); + $body = $response->toArray(); + $this->assertSame('localhost:8057', $body['HTTP_HOST']); + $this->assertMatchesRegularExpression('#^http://(localhost|127\.0\.0\.1):8057/$#', $body['REQUEST_URI']); + } finally { + unset($_SERVER['http_proxy']); + } + } + + public function testNoProxy() + { + putenv('no_proxy='.$_SERVER['no_proxy'] = 'example.com, localhost'); + + try { + $client = $this->getHttpClient(__FUNCTION__); + $response = $client->request('GET', 'http://localhost:8057/', [ + 'proxy' => 'http://localhost:8057', + ]); + + $body = $response->toArray(); + + $this->assertSame('HTTP/1.1', $body['SERVER_PROTOCOL']); + $this->assertSame('/', $body['REQUEST_URI']); + $this->assertSame('GET', $body['REQUEST_METHOD']); + } finally { + putenv('no_proxy'); + unset($_SERVER['no_proxy']); + } + } + + /** + * @requires extension zlib + */ + public function testAutoEncodingRequest() + { + $client = $this->getHttpClient(__FUNCTION__); + $response = $client->request('GET', 'http://localhost:8057'); + + $this->assertSame(200, $response->getStatusCode()); + + $headers = $response->getHeaders(); + + $this->assertSame(['Accept-Encoding'], $headers['vary']); + $this->assertStringContainsString('gzip', $headers['content-encoding'][0]); + + $body = $response->toArray(); + + $this->assertStringContainsString('gzip', $body['HTTP_ACCEPT_ENCODING']); + } + + public function testBaseUri() + { + $client = $this->getHttpClient(__FUNCTION__); + $response = $client->request('GET', '../404', [ + 'base_uri' => 'http://localhost:8057/abc/', + ]); + + $this->assertSame(404, $response->getStatusCode()); + $this->assertSame(['application/json'], $response->getHeaders(false)['content-type']); + } + + public function testQuery() + { + $client = $this->getHttpClient(__FUNCTION__); + $response = $client->request('GET', 'http://localhost:8057/?a=a', [ + 'query' => ['b' => 'b'], + ]); + + $body = $response->toArray(); + $this->assertSame('GET', $body['REQUEST_METHOD']); + $this->assertSame('/?a=a&b=b', $body['REQUEST_URI']); + } + + public function testInformationalResponse() + { + $client = $this->getHttpClient(__FUNCTION__); + $response = $client->request('GET', 'http://localhost:8057/103'); + + $this->assertSame('Here the body', $response->getContent()); + $this->assertSame(200, $response->getStatusCode()); + } + + public function testInformationalResponseStream() + { + $client = $this->getHttpClient(__FUNCTION__); + $response = $client->request('GET', 'http://localhost:8057/103'); + + $chunks = []; + foreach ($client->stream($response) as $chunk) { + $chunks[] = $chunk; + } + + $this->assertSame(103, $chunks[0]->getInformationalStatus()[0]); + $this->assertSame(['; rel=preload; as=style', '; rel=preload; as=script'], $chunks[0]->getInformationalStatus()[1]['link']); + $this->assertTrue($chunks[1]->isFirst()); + $this->assertSame('Here the body', $chunks[2]->getContent()); + $this->assertTrue($chunks[3]->isLast()); + $this->assertNull($chunks[3]->getInformationalStatus()); + + $this->assertSame(['date', 'content-length'], array_keys($response->getHeaders())); + $this->assertContains('Link: ; rel=preload; as=style', $response->getInfo('response_headers')); + } + + /** + * @requires extension zlib + */ + public function testUserlandEncodingRequest() + { + $client = $this->getHttpClient(__FUNCTION__); + $response = $client->request('GET', 'http://localhost:8057', [ + 'headers' => ['Accept-Encoding' => 'gzip'], + ]); + + $headers = $response->getHeaders(); + + $this->assertSame(['Accept-Encoding'], $headers['vary']); + $this->assertStringContainsString('gzip', $headers['content-encoding'][0]); + + $body = $response->getContent(); + $this->assertSame("\x1F", $body[0]); + + $body = json_decode(gzdecode($body), true); + $this->assertSame('gzip', $body['HTTP_ACCEPT_ENCODING']); + } + + /** + * @requires extension zlib + */ + public function testGzipBroken() + { + $client = $this->getHttpClient(__FUNCTION__); + $response = $client->request('GET', 'http://localhost:8057/gzip-broken'); + + $this->expectException(TransportExceptionInterface::class); + $response->getContent(); + } + + public function testMaxDuration() + { + $client = $this->getHttpClient(__FUNCTION__); + $response = $client->request('GET', 'http://localhost:8057/max-duration', [ + 'max_duration' => 0.1, + ]); + + $start = microtime(true); + + try { + $response->getContent(); + } catch (TransportExceptionInterface $e) { + $this->addToAssertionCount(1); + } + + $duration = microtime(true) - $start; + + $this->assertLessThan(10, $duration); + } + + public function testWithOptions() + { + $client = $this->getHttpClient(__FUNCTION__); + if (!method_exists($client, 'withOptions')) { + $this->markTestSkipped(sprintf('Not implementing "%s::withOptions()" is deprecated.', get_debug_type($client))); + } + + $client2 = $client->withOptions(['base_uri' => 'http://localhost:8057/']); + + $this->assertNotSame($client, $client2); + $this->assertSame(\get_class($client), \get_class($client2)); + + $response = $client2->request('GET', '/'); + $this->assertSame(200, $response->getStatusCode()); + } +} diff --git a/sites/all/libraries/vendor/symfony/http-client-contracts/Test/TestHttpServer.php b/sites/all/libraries/vendor/symfony/http-client-contracts/Test/TestHttpServer.php new file mode 100644 index 0000000000..55a744aef4 --- /dev/null +++ b/sites/all/libraries/vendor/symfony/http-client-contracts/Test/TestHttpServer.php @@ -0,0 +1,46 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\HttpClient\Test; + +use Symfony\Component\Process\PhpExecutableFinder; +use Symfony\Component\Process\Process; + +class TestHttpServer +{ + private static $process = []; + + /** + * @return Process + */ + public static function start(int $port = 8057) + { + if (isset(self::$process[$port])) { + self::$process[$port]->stop(); + } else { + register_shutdown_function(static function () use ($port) { + self::$process[$port]->stop(); + }); + } + + $finder = new PhpExecutableFinder(); + $process = new Process(array_merge([$finder->find(false)], $finder->findArguments(), ['-dopcache.enable=0', '-dvariables_order=EGPCS', '-S', '127.0.0.1:'.$port])); + $process->setWorkingDirectory(__DIR__.'/Fixtures/web'); + $process->start(); + self::$process[$port] = $process; + + do { + usleep(50000); + } while (!@fopen('http://127.0.0.1:'.$port, 'r')); + + return $process; + } +} diff --git a/sites/all/libraries/vendor/symfony/http-client-contracts/composer.json b/sites/all/libraries/vendor/symfony/http-client-contracts/composer.json new file mode 100644 index 0000000000..b76cab852f --- /dev/null +++ b/sites/all/libraries/vendor/symfony/http-client-contracts/composer.json @@ -0,0 +1,37 @@ +{ + "name": "symfony/http-client-contracts", + "type": "library", + "description": "Generic abstractions related to HTTP clients", + "keywords": ["abstractions", "contracts", "decoupling", "interfaces", "interoperability", "standards"], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=7.2.5" + }, + "suggest": { + "symfony/http-client-implementation": "" + }, + "autoload": { + "psr-4": { "Symfony\\Contracts\\HttpClient\\": "" } + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-main": "2.5-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + } +} diff --git a/sites/all/libraries/vendor/symfony/http-client/.gitattributes b/sites/all/libraries/vendor/symfony/http-client/.gitattributes new file mode 100644 index 0000000000..84c7add058 --- /dev/null +++ b/sites/all/libraries/vendor/symfony/http-client/.gitattributes @@ -0,0 +1,4 @@ +/Tests export-ignore +/phpunit.xml.dist export-ignore +/.gitattributes export-ignore +/.gitignore export-ignore diff --git a/sites/all/libraries/vendor/symfony/http-client/.gitignore b/sites/all/libraries/vendor/symfony/http-client/.gitignore new file mode 100644 index 0000000000..c49a5d8df5 --- /dev/null +++ b/sites/all/libraries/vendor/symfony/http-client/.gitignore @@ -0,0 +1,3 @@ +vendor/ +composer.lock +phpunit.xml diff --git a/sites/all/libraries/vendor/symfony/http-client/AmpHttpClient.php b/sites/all/libraries/vendor/symfony/http-client/AmpHttpClient.php new file mode 100644 index 0000000000..96a5e0aa4f --- /dev/null +++ b/sites/all/libraries/vendor/symfony/http-client/AmpHttpClient.php @@ -0,0 +1,176 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient; + +use Amp\CancelledException; +use Amp\Http\Client\DelegateHttpClient; +use Amp\Http\Client\InterceptedHttpClient; +use Amp\Http\Client\PooledHttpClient; +use Amp\Http\Client\Request; +use Amp\Http\Tunnel\Http1TunnelConnector; +use Psr\Log\LoggerAwareInterface; +use Psr\Log\LoggerAwareTrait; +use Symfony\Component\HttpClient\Exception\TransportException; +use Symfony\Component\HttpClient\Internal\AmpClientState; +use Symfony\Component\HttpClient\Response\AmpResponse; +use Symfony\Component\HttpClient\Response\ResponseStream; +use Symfony\Contracts\HttpClient\HttpClientInterface; +use Symfony\Contracts\HttpClient\ResponseInterface; +use Symfony\Contracts\HttpClient\ResponseStreamInterface; +use Symfony\Contracts\Service\ResetInterface; + +if (!interface_exists(DelegateHttpClient::class)) { + throw new \LogicException('You cannot use "Symfony\Component\HttpClient\AmpHttpClient" as the "amphp/http-client" package is not installed. Try running "composer require amphp/http-client".'); +} + +/** + * A portable implementation of the HttpClientInterface contracts based on Amp's HTTP client. + * + * @author Nicolas Grekas + */ +final class AmpHttpClient implements HttpClientInterface, LoggerAwareInterface, ResetInterface +{ + use HttpClientTrait; + use LoggerAwareTrait; + + private $defaultOptions = self::OPTIONS_DEFAULTS; + private static $emptyDefaults = self::OPTIONS_DEFAULTS; + + /** @var AmpClientState */ + private $multi; + + /** + * @param array $defaultOptions Default requests' options + * @param callable $clientConfigurator A callable that builds a {@see DelegateHttpClient} from a {@see PooledHttpClient}; + * passing null builds an {@see InterceptedHttpClient} with 2 retries on failures + * @param int $maxHostConnections The maximum number of connections to a single host + * @param int $maxPendingPushes The maximum number of pushed responses to accept in the queue + * + * @see HttpClientInterface::OPTIONS_DEFAULTS for available options + */ + public function __construct(array $defaultOptions = [], callable $clientConfigurator = null, int $maxHostConnections = 6, int $maxPendingPushes = 50) + { + $this->defaultOptions['buffer'] = $this->defaultOptions['buffer'] ?? \Closure::fromCallable([__CLASS__, 'shouldBuffer']); + + if ($defaultOptions) { + [, $this->defaultOptions] = self::prepareRequest(null, null, $defaultOptions, $this->defaultOptions); + } + + $this->multi = new AmpClientState($clientConfigurator, $maxHostConnections, $maxPendingPushes, $this->logger); + } + + /** + * @see HttpClientInterface::OPTIONS_DEFAULTS for available options + * + * {@inheritdoc} + */ + public function request(string $method, string $url, array $options = []): ResponseInterface + { + [$url, $options] = self::prepareRequest($method, $url, $options, $this->defaultOptions); + + $options['proxy'] = self::getProxy($options['proxy'], $url, $options['no_proxy']); + + if (null !== $options['proxy'] && !class_exists(Http1TunnelConnector::class)) { + throw new \LogicException('You cannot use the "proxy" option as the "amphp/http-tunnel" package is not installed. Try running "composer require amphp/http-tunnel".'); + } + + if ($options['bindto']) { + if (0 === strpos($options['bindto'], 'if!')) { + throw new TransportException(__CLASS__.' cannot bind to network interfaces, use e.g. CurlHttpClient instead.'); + } + if (0 === strpos($options['bindto'], 'host!')) { + $options['bindto'] = substr($options['bindto'], 5); + } + } + + if (('' !== $options['body'] || 'POST' === $method || isset($options['normalized_headers']['content-length'])) && !isset($options['normalized_headers']['content-type'])) { + $options['headers'][] = 'Content-Type: application/x-www-form-urlencoded'; + } + + if (!isset($options['normalized_headers']['user-agent'])) { + $options['headers'][] = 'User-Agent: Symfony HttpClient/Amp'; + } + + if (0 < $options['max_duration']) { + $options['timeout'] = min($options['max_duration'], $options['timeout']); + } + + if ($options['resolve']) { + $this->multi->dnsCache = $options['resolve'] + $this->multi->dnsCache; + } + + if ($options['peer_fingerprint'] && !isset($options['peer_fingerprint']['pin-sha256'])) { + throw new TransportException(__CLASS__.' supports only "pin-sha256" fingerprints.'); + } + + $request = new Request(implode('', $url), $method); + + if ($options['http_version']) { + switch ((float) $options['http_version']) { + case 1.0: $request->setProtocolVersions(['1.0']); break; + case 1.1: $request->setProtocolVersions(['1.1', '1.0']); break; + default: $request->setProtocolVersions(['2', '1.1', '1.0']); break; + } + } + + foreach ($options['headers'] as $v) { + $h = explode(': ', $v, 2); + $request->addHeader($h[0], $h[1]); + } + + $request->setTcpConnectTimeout(1000 * $options['timeout']); + $request->setTlsHandshakeTimeout(1000 * $options['timeout']); + $request->setTransferTimeout(1000 * $options['max_duration']); + if (method_exists($request, 'setInactivityTimeout')) { + $request->setInactivityTimeout(0); + } + + if ('' !== $request->getUri()->getUserInfo() && !$request->hasHeader('authorization')) { + $auth = explode(':', $request->getUri()->getUserInfo(), 2); + $auth = array_map('rawurldecode', $auth) + [1 => '']; + $request->setHeader('Authorization', 'Basic '.base64_encode(implode(':', $auth))); + } + + return new AmpResponse($this->multi, $request, $options, $this->logger); + } + + /** + * {@inheritdoc} + */ + public function stream($responses, float $timeout = null): ResponseStreamInterface + { + if ($responses instanceof AmpResponse) { + $responses = [$responses]; + } elseif (!is_iterable($responses)) { + throw new \TypeError(sprintf('"%s()" expects parameter 1 to be an iterable of AmpResponse objects, "%s" given.', __METHOD__, get_debug_type($responses))); + } + + return new ResponseStream(AmpResponse::stream($responses, $timeout)); + } + + public function reset() + { + $this->multi->dnsCache = []; + + foreach ($this->multi->pushedResponses as $authority => $pushedResponses) { + foreach ($pushedResponses as [$pushedUrl, $pushDeferred]) { + $pushDeferred->fail(new CancelledException()); + + if ($this->logger) { + $this->logger->debug(sprintf('Unused pushed response: "%s"', $pushedUrl)); + } + } + } + + $this->multi->pushedResponses = []; + } +} diff --git a/sites/all/libraries/vendor/symfony/http-client/AsyncDecoratorTrait.php b/sites/all/libraries/vendor/symfony/http-client/AsyncDecoratorTrait.php new file mode 100644 index 0000000000..aff402d83c --- /dev/null +++ b/sites/all/libraries/vendor/symfony/http-client/AsyncDecoratorTrait.php @@ -0,0 +1,48 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient; + +use Symfony\Component\HttpClient\Response\AsyncResponse; +use Symfony\Component\HttpClient\Response\ResponseStream; +use Symfony\Contracts\HttpClient\ResponseInterface; +use Symfony\Contracts\HttpClient\ResponseStreamInterface; + +/** + * Eases with processing responses while streaming them. + * + * @author Nicolas Grekas + */ +trait AsyncDecoratorTrait +{ + use DecoratorTrait; + + /** + * {@inheritdoc} + * + * @return AsyncResponse + */ + abstract public function request(string $method, string $url, array $options = []): ResponseInterface; + + /** + * {@inheritdoc} + */ + public function stream($responses, float $timeout = null): ResponseStreamInterface + { + if ($responses instanceof AsyncResponse) { + $responses = [$responses]; + } elseif (!is_iterable($responses)) { + throw new \TypeError(sprintf('"%s()" expects parameter 1 to be an iterable of AsyncResponse objects, "%s" given.', __METHOD__, get_debug_type($responses))); + } + + return new ResponseStream(AsyncResponse::stream($responses, $timeout, static::class)); + } +} diff --git a/sites/all/libraries/vendor/symfony/http-client/CHANGELOG.md b/sites/all/libraries/vendor/symfony/http-client/CHANGELOG.md new file mode 100644 index 0000000000..7c2fc2273b --- /dev/null +++ b/sites/all/libraries/vendor/symfony/http-client/CHANGELOG.md @@ -0,0 +1,54 @@ +CHANGELOG +========= + +5.4 +--- + + * Add `MockHttpClient::setResponseFactory()` method to be able to set response factory after client creating + +5.3 +--- + + * Implement `HttpClientInterface::withOptions()` from `symfony/contracts` v2.4 + * Add `DecoratorTrait` to ease writing simple decorators + +5.2.0 +----- + + * added `AsyncDecoratorTrait` to ease processing responses without breaking async + * added support for pausing responses with a new `pause_handler` callable exposed as an info item + * added `StreamableInterface` to ease turning responses into PHP streams + * added `MockResponse::getRequestMethod()` and `getRequestUrl()` to allow inspecting which request has been sent + * added `EventSourceHttpClient` a Server-Sent events stream implementing the [EventSource specification](https://www.w3.org/TR/eventsource/#eventsource) + * added option "extra.curl" to allow setting additional curl options in `CurlHttpClient` + * added `RetryableHttpClient` to automatically retry failed HTTP requests. + * added `extra.trace_content` option to `TraceableHttpClient` to prevent it from keeping the content in memory + +5.1.0 +----- + + * added `NoPrivateNetworkHttpClient` decorator + * added `AmpHttpClient`, a portable HTTP/2 implementation based on Amp + * added `LoggerAwareInterface` to `ScopingHttpClient` and `TraceableHttpClient` + * made `HttpClient::create()` return an `AmpHttpClient` when `amphp/http-client` is found but curl is not or too old + +4.4.0 +----- + + * added `canceled` to `ResponseInterface::getInfo()` + * added `HttpClient::createForBaseUri()` + * added `HttplugClient` with support for sync and async requests + * added `max_duration` option + * added support for NTLM authentication + * added `StreamWrapper` to cast any `ResponseInterface` instances to PHP streams. + * added `$response->toStream()` to cast responses to regular PHP streams + * made `Psr18Client` implement relevant PSR-17 factories and have streaming responses + * added `TraceableHttpClient`, `HttpClientDataCollector` and `HttpClientPass` to integrate with the web profiler + * allow enabling buffering conditionally with a Closure + * allow option "buffer" to be a stream resource + * allow arbitrary values for the "json" option + +4.3.0 +----- + + * added the component diff --git a/sites/all/libraries/vendor/symfony/http-client/CachingHttpClient.php b/sites/all/libraries/vendor/symfony/http-client/CachingHttpClient.php new file mode 100644 index 0000000000..e1d7023d9a --- /dev/null +++ b/sites/all/libraries/vendor/symfony/http-client/CachingHttpClient.php @@ -0,0 +1,152 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient; + +use Symfony\Component\HttpClient\Response\MockResponse; +use Symfony\Component\HttpClient\Response\ResponseStream; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\HttpCache\HttpCache; +use Symfony\Component\HttpKernel\HttpCache\StoreInterface; +use Symfony\Component\HttpKernel\HttpClientKernel; +use Symfony\Contracts\HttpClient\HttpClientInterface; +use Symfony\Contracts\HttpClient\ResponseInterface; +use Symfony\Contracts\HttpClient\ResponseStreamInterface; +use Symfony\Contracts\Service\ResetInterface; + +/** + * Adds caching on top of an HTTP client. + * + * The implementation buffers responses in memory and doesn't stream directly from the network. + * You can disable/enable this layer by setting option "no_cache" under "extra" to true/false. + * By default, caching is enabled unless the "buffer" option is set to false. + * + * @author Nicolas Grekas + */ +class CachingHttpClient implements HttpClientInterface, ResetInterface +{ + use HttpClientTrait; + + private $client; + private $cache; + private $defaultOptions = self::OPTIONS_DEFAULTS; + + public function __construct(HttpClientInterface $client, StoreInterface $store, array $defaultOptions = []) + { + if (!class_exists(HttpClientKernel::class)) { + throw new \LogicException(sprintf('Using "%s" requires that the HttpKernel component version 4.3 or higher is installed, try running "composer require symfony/http-kernel:^5.4".', __CLASS__)); + } + + $this->client = $client; + $kernel = new HttpClientKernel($client); + $this->cache = new HttpCache($kernel, $store, null, $defaultOptions); + + unset($defaultOptions['debug']); + unset($defaultOptions['default_ttl']); + unset($defaultOptions['private_headers']); + unset($defaultOptions['allow_reload']); + unset($defaultOptions['allow_revalidate']); + unset($defaultOptions['stale_while_revalidate']); + unset($defaultOptions['stale_if_error']); + unset($defaultOptions['trace_level']); + unset($defaultOptions['trace_header']); + + if ($defaultOptions) { + [, $this->defaultOptions] = self::prepareRequest(null, null, $defaultOptions, $this->defaultOptions); + } + } + + /** + * {@inheritdoc} + */ + public function request(string $method, string $url, array $options = []): ResponseInterface + { + [$url, $options] = $this->prepareRequest($method, $url, $options, $this->defaultOptions, true); + $url = implode('', $url); + + if (!empty($options['body']) || !empty($options['extra']['no_cache']) || !\in_array($method, ['GET', 'HEAD', 'OPTIONS'])) { + return $this->client->request($method, $url, $options); + } + + $request = Request::create($url, $method); + $request->attributes->set('http_client_options', $options); + + foreach ($options['normalized_headers'] as $name => $values) { + if ('cookie' !== $name) { + foreach ($values as $value) { + $request->headers->set($name, substr($value, 2 + \strlen($name)), false); + } + + continue; + } + + foreach ($values as $cookies) { + foreach (explode('; ', substr($cookies, \strlen('Cookie: '))) as $cookie) { + if ('' !== $cookie) { + $cookie = explode('=', $cookie, 2); + $request->cookies->set($cookie[0], $cookie[1] ?? ''); + } + } + } + } + + $response = $this->cache->handle($request); + $response = new MockResponse($response->getContent(), [ + 'http_code' => $response->getStatusCode(), + 'response_headers' => $response->headers->allPreserveCase(), + ]); + + return MockResponse::fromRequest($method, $url, $options, $response); + } + + /** + * {@inheritdoc} + */ + public function stream($responses, float $timeout = null): ResponseStreamInterface + { + if ($responses instanceof ResponseInterface) { + $responses = [$responses]; + } elseif (!is_iterable($responses)) { + throw new \TypeError(sprintf('"%s()" expects parameter 1 to be an iterable of ResponseInterface objects, "%s" given.', __METHOD__, get_debug_type($responses))); + } + + $mockResponses = []; + $clientResponses = []; + + foreach ($responses as $response) { + if ($response instanceof MockResponse) { + $mockResponses[] = $response; + } else { + $clientResponses[] = $response; + } + } + + if (!$mockResponses) { + return $this->client->stream($clientResponses, $timeout); + } + + if (!$clientResponses) { + return new ResponseStream(MockResponse::stream($mockResponses, $timeout)); + } + + return new ResponseStream((function () use ($mockResponses, $clientResponses, $timeout) { + yield from MockResponse::stream($mockResponses, $timeout); + yield $this->client->stream($clientResponses, $timeout); + })()); + } + + public function reset() + { + if ($this->client instanceof ResetInterface) { + $this->client->reset(); + } + } +} diff --git a/sites/all/libraries/vendor/symfony/http-client/Chunk/DataChunk.php b/sites/all/libraries/vendor/symfony/http-client/Chunk/DataChunk.php new file mode 100644 index 0000000000..37ca848541 --- /dev/null +++ b/sites/all/libraries/vendor/symfony/http-client/Chunk/DataChunk.php @@ -0,0 +1,87 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient\Chunk; + +use Symfony\Contracts\HttpClient\ChunkInterface; + +/** + * @author Nicolas Grekas + * + * @internal + */ +class DataChunk implements ChunkInterface +{ + private $offset = 0; + private $content = ''; + + public function __construct(int $offset = 0, string $content = '') + { + $this->offset = $offset; + $this->content = $content; + } + + /** + * {@inheritdoc} + */ + public function isTimeout(): bool + { + return false; + } + + /** + * {@inheritdoc} + */ + public function isFirst(): bool + { + return false; + } + + /** + * {@inheritdoc} + */ + public function isLast(): bool + { + return false; + } + + /** + * {@inheritdoc} + */ + public function getInformationalStatus(): ?array + { + return null; + } + + /** + * {@inheritdoc} + */ + public function getContent(): string + { + return $this->content; + } + + /** + * {@inheritdoc} + */ + public function getOffset(): int + { + return $this->offset; + } + + /** + * {@inheritdoc} + */ + public function getError(): ?string + { + return null; + } +} diff --git a/sites/all/libraries/vendor/symfony/http-client/Chunk/ErrorChunk.php b/sites/all/libraries/vendor/symfony/http-client/Chunk/ErrorChunk.php new file mode 100644 index 0000000000..a19f433620 --- /dev/null +++ b/sites/all/libraries/vendor/symfony/http-client/Chunk/ErrorChunk.php @@ -0,0 +1,140 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient\Chunk; + +use Symfony\Component\HttpClient\Exception\TimeoutException; +use Symfony\Component\HttpClient\Exception\TransportException; +use Symfony\Contracts\HttpClient\ChunkInterface; + +/** + * @author Nicolas Grekas + * + * @internal + */ +class ErrorChunk implements ChunkInterface +{ + private $didThrow = false; + private $offset; + private $errorMessage; + private $error; + + /** + * @param \Throwable|string $error + */ + public function __construct(int $offset, $error) + { + $this->offset = $offset; + + if (\is_string($error)) { + $this->errorMessage = $error; + } else { + $this->error = $error; + $this->errorMessage = $error->getMessage(); + } + } + + /** + * {@inheritdoc} + */ + public function isTimeout(): bool + { + $this->didThrow = true; + + if (null !== $this->error) { + throw new TransportException($this->errorMessage, 0, $this->error); + } + + return true; + } + + /** + * {@inheritdoc} + */ + public function isFirst(): bool + { + $this->didThrow = true; + throw null !== $this->error ? new TransportException($this->errorMessage, 0, $this->error) : new TimeoutException($this->errorMessage); + } + + /** + * {@inheritdoc} + */ + public function isLast(): bool + { + $this->didThrow = true; + throw null !== $this->error ? new TransportException($this->errorMessage, 0, $this->error) : new TimeoutException($this->errorMessage); + } + + /** + * {@inheritdoc} + */ + public function getInformationalStatus(): ?array + { + $this->didThrow = true; + throw null !== $this->error ? new TransportException($this->errorMessage, 0, $this->error) : new TimeoutException($this->errorMessage); + } + + /** + * {@inheritdoc} + */ + public function getContent(): string + { + $this->didThrow = true; + throw null !== $this->error ? new TransportException($this->errorMessage, 0, $this->error) : new TimeoutException($this->errorMessage); + } + + /** + * {@inheritdoc} + */ + public function getOffset(): int + { + return $this->offset; + } + + /** + * {@inheritdoc} + */ + public function getError(): ?string + { + return $this->errorMessage; + } + + /** + * @return bool Whether the wrapped error has been thrown or not + */ + public function didThrow(bool $didThrow = null): bool + { + if (null !== $didThrow && $this->didThrow !== $didThrow) { + return !$this->didThrow = $didThrow; + } + + return $this->didThrow; + } + + public function __sleep(): array + { + throw new \BadMethodCallException('Cannot serialize '.__CLASS__); + } + + public function __wakeup() + { + throw new \BadMethodCallException('Cannot unserialize '.__CLASS__); + } + + public function __destruct() + { + if (!$this->didThrow) { + $this->didThrow = true; + throw null !== $this->error ? new TransportException($this->errorMessage, 0, $this->error) : new TimeoutException($this->errorMessage); + } + } +} diff --git a/sites/all/libraries/vendor/symfony/http-client/Chunk/FirstChunk.php b/sites/all/libraries/vendor/symfony/http-client/Chunk/FirstChunk.php new file mode 100644 index 0000000000..d891ca856d --- /dev/null +++ b/sites/all/libraries/vendor/symfony/http-client/Chunk/FirstChunk.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient\Chunk; + +/** + * @author Nicolas Grekas + * + * @internal + */ +class FirstChunk extends DataChunk +{ + /** + * {@inheritdoc} + */ + public function isFirst(): bool + { + return true; + } +} diff --git a/sites/all/libraries/vendor/symfony/http-client/Chunk/InformationalChunk.php b/sites/all/libraries/vendor/symfony/http-client/Chunk/InformationalChunk.php new file mode 100644 index 0000000000..c4452f15a0 --- /dev/null +++ b/sites/all/libraries/vendor/symfony/http-client/Chunk/InformationalChunk.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient\Chunk; + +/** + * @author Nicolas Grekas + * + * @internal + */ +class InformationalChunk extends DataChunk +{ + private $status; + + public function __construct(int $statusCode, array $headers) + { + $this->status = [$statusCode, $headers]; + } + + /** + * {@inheritdoc} + */ + public function getInformationalStatus(): ?array + { + return $this->status; + } +} diff --git a/sites/all/libraries/vendor/symfony/http-client/Chunk/LastChunk.php b/sites/all/libraries/vendor/symfony/http-client/Chunk/LastChunk.php new file mode 100644 index 0000000000..84095d3925 --- /dev/null +++ b/sites/all/libraries/vendor/symfony/http-client/Chunk/LastChunk.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient\Chunk; + +/** + * @author Nicolas Grekas + * + * @internal + */ +class LastChunk extends DataChunk +{ + /** + * {@inheritdoc} + */ + public function isLast(): bool + { + return true; + } +} diff --git a/sites/all/libraries/vendor/symfony/http-client/Chunk/ServerSentEvent.php b/sites/all/libraries/vendor/symfony/http-client/Chunk/ServerSentEvent.php new file mode 100644 index 0000000000..f7ff4b9631 --- /dev/null +++ b/sites/all/libraries/vendor/symfony/http-client/Chunk/ServerSentEvent.php @@ -0,0 +1,79 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient\Chunk; + +use Symfony\Contracts\HttpClient\ChunkInterface; + +/** + * @author Antoine Bluchet + * @author Nicolas Grekas + */ +final class ServerSentEvent extends DataChunk implements ChunkInterface +{ + private $data = ''; + private $id = ''; + private $type = 'message'; + private $retry = 0; + + public function __construct(string $content) + { + parent::__construct(-1, $content); + + // remove BOM + if (0 === strpos($content, "\xEF\xBB\xBF")) { + $content = substr($content, 3); + } + + foreach (preg_split("/(?:\r\n|[\r\n])/", $content) as $line) { + if (0 === $i = strpos($line, ':')) { + continue; + } + + $i = false === $i ? \strlen($line) : $i; + $field = substr($line, 0, $i); + $i += 1 + (' ' === ($line[1 + $i] ?? '')); + + switch ($field) { + case 'id': $this->id = substr($line, $i); break; + case 'event': $this->type = substr($line, $i); break; + case 'data': $this->data .= ('' === $this->data ? '' : "\n").substr($line, $i); break; + case 'retry': + $retry = substr($line, $i); + + if ('' !== $retry && \strlen($retry) === strspn($retry, '0123456789')) { + $this->retry = $retry / 1000.0; + } + break; + } + } + } + + public function getId(): string + { + return $this->id; + } + + public function getType(): string + { + return $this->type; + } + + public function getData(): string + { + return $this->data; + } + + public function getRetry(): float + { + return $this->retry; + } +} diff --git a/sites/all/libraries/vendor/symfony/http-client/CurlHttpClient.php b/sites/all/libraries/vendor/symfony/http-client/CurlHttpClient.php new file mode 100644 index 0000000000..3807244dc6 --- /dev/null +++ b/sites/all/libraries/vendor/symfony/http-client/CurlHttpClient.php @@ -0,0 +1,551 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient; + +use Psr\Log\LoggerAwareInterface; +use Psr\Log\LoggerInterface; +use Symfony\Component\HttpClient\Exception\InvalidArgumentException; +use Symfony\Component\HttpClient\Exception\TransportException; +use Symfony\Component\HttpClient\Internal\CurlClientState; +use Symfony\Component\HttpClient\Internal\PushedResponse; +use Symfony\Component\HttpClient\Response\CurlResponse; +use Symfony\Component\HttpClient\Response\ResponseStream; +use Symfony\Contracts\HttpClient\HttpClientInterface; +use Symfony\Contracts\HttpClient\ResponseInterface; +use Symfony\Contracts\HttpClient\ResponseStreamInterface; +use Symfony\Contracts\Service\ResetInterface; + +/** + * A performant implementation of the HttpClientInterface contracts based on the curl extension. + * + * This provides fully concurrent HTTP requests, with transparent + * HTTP/2 push when a curl version that supports it is installed. + * + * @author Nicolas Grekas + */ +final class CurlHttpClient implements HttpClientInterface, LoggerAwareInterface, ResetInterface +{ + use HttpClientTrait; + + private $defaultOptions = self::OPTIONS_DEFAULTS + [ + 'auth_ntlm' => null, // array|string - an array containing the username as first value, and optionally the + // password as the second one; or string like username:password - enabling NTLM auth + 'extra' => [ + 'curl' => [], // A list of extra curl options indexed by their corresponding CURLOPT_* + ], + ]; + private static $emptyDefaults = self::OPTIONS_DEFAULTS + ['auth_ntlm' => null]; + + /** + * @var LoggerInterface|null + */ + private $logger; + + /** + * An internal object to share state between the client and its responses. + * + * @var CurlClientState + */ + private $multi; + + /** + * @param array $defaultOptions Default request's options + * @param int $maxHostConnections The maximum number of connections to a single host + * @param int $maxPendingPushes The maximum number of pushed responses to accept in the queue + * + * @see HttpClientInterface::OPTIONS_DEFAULTS for available options + */ + public function __construct(array $defaultOptions = [], int $maxHostConnections = 6, int $maxPendingPushes = 50) + { + if (!\extension_loaded('curl')) { + throw new \LogicException('You cannot use the "Symfony\Component\HttpClient\CurlHttpClient" as the "curl" extension is not installed.'); + } + + $this->defaultOptions['buffer'] = $this->defaultOptions['buffer'] ?? \Closure::fromCallable([__CLASS__, 'shouldBuffer']); + + if ($defaultOptions) { + [, $this->defaultOptions] = self::prepareRequest(null, null, $defaultOptions, $this->defaultOptions); + } + + $this->multi = new CurlClientState($maxHostConnections, $maxPendingPushes); + } + + public function setLogger(LoggerInterface $logger): void + { + $this->logger = $this->multi->logger = $logger; + } + + /** + * @see HttpClientInterface::OPTIONS_DEFAULTS for available options + * + * {@inheritdoc} + */ + public function request(string $method, string $url, array $options = []): ResponseInterface + { + [$url, $options] = self::prepareRequest($method, $url, $options, $this->defaultOptions); + $scheme = $url['scheme']; + $authority = $url['authority']; + $host = parse_url($authority, \PHP_URL_HOST); + $proxy = $options['proxy'] + ?? ('https:' === $url['scheme'] ? $_SERVER['https_proxy'] ?? $_SERVER['HTTPS_PROXY'] ?? null : null) + // Ignore HTTP_PROXY except on the CLI to work around httpoxy set of vulnerabilities + ?? $_SERVER['http_proxy'] ?? (\in_array(\PHP_SAPI, ['cli', 'phpdbg'], true) ? $_SERVER['HTTP_PROXY'] ?? null : null) ?? $_SERVER['all_proxy'] ?? $_SERVER['ALL_PROXY'] ?? null; + $url = implode('', $url); + + if (!isset($options['normalized_headers']['user-agent'])) { + $options['headers'][] = 'User-Agent: Symfony HttpClient/Curl'; + } + + $curlopts = [ + \CURLOPT_URL => $url, + \CURLOPT_TCP_NODELAY => true, + \CURLOPT_PROTOCOLS => \CURLPROTO_HTTP | \CURLPROTO_HTTPS, + \CURLOPT_REDIR_PROTOCOLS => \CURLPROTO_HTTP | \CURLPROTO_HTTPS, + \CURLOPT_FOLLOWLOCATION => true, + \CURLOPT_MAXREDIRS => 0 < $options['max_redirects'] ? $options['max_redirects'] : 0, + \CURLOPT_COOKIEFILE => '', // Keep track of cookies during redirects + \CURLOPT_TIMEOUT => 0, + \CURLOPT_PROXY => $proxy, + \CURLOPT_NOPROXY => $options['no_proxy'] ?? $_SERVER['no_proxy'] ?? $_SERVER['NO_PROXY'] ?? '', + \CURLOPT_SSL_VERIFYPEER => $options['verify_peer'], + \CURLOPT_SSL_VERIFYHOST => $options['verify_host'] ? 2 : 0, + \CURLOPT_CAINFO => $options['cafile'], + \CURLOPT_CAPATH => $options['capath'], + \CURLOPT_SSL_CIPHER_LIST => $options['ciphers'], + \CURLOPT_SSLCERT => $options['local_cert'], + \CURLOPT_SSLKEY => $options['local_pk'], + \CURLOPT_KEYPASSWD => $options['passphrase'], + \CURLOPT_CERTINFO => $options['capture_peer_cert_chain'], + ]; + + if (1.0 === (float) $options['http_version']) { + $curlopts[\CURLOPT_HTTP_VERSION] = \CURL_HTTP_VERSION_1_0; + } elseif (1.1 === (float) $options['http_version']) { + $curlopts[\CURLOPT_HTTP_VERSION] = \CURL_HTTP_VERSION_1_1; + } elseif (\defined('CURL_VERSION_HTTP2') && (\CURL_VERSION_HTTP2 & CurlClientState::$curlVersion['features']) && ('https:' === $scheme || 2.0 === (float) $options['http_version'])) { + $curlopts[\CURLOPT_HTTP_VERSION] = \CURL_HTTP_VERSION_2_0; + } + + if (isset($options['auth_ntlm'])) { + $curlopts[\CURLOPT_HTTPAUTH] = \CURLAUTH_NTLM; + $curlopts[\CURLOPT_HTTP_VERSION] = \CURL_HTTP_VERSION_1_1; + + if (\is_array($options['auth_ntlm'])) { + $count = \count($options['auth_ntlm']); + if ($count <= 0 || $count > 2) { + throw new InvalidArgumentException(sprintf('Option "auth_ntlm" must contain 1 or 2 elements, %d given.', $count)); + } + + $options['auth_ntlm'] = implode(':', $options['auth_ntlm']); + } + + if (!\is_string($options['auth_ntlm'])) { + throw new InvalidArgumentException(sprintf('Option "auth_ntlm" must be a string or an array, "%s" given.', get_debug_type($options['auth_ntlm']))); + } + + $curlopts[\CURLOPT_USERPWD] = $options['auth_ntlm']; + } + + if (!\ZEND_THREAD_SAFE) { + $curlopts[\CURLOPT_DNS_USE_GLOBAL_CACHE] = false; + } + + if (\defined('CURLOPT_HEADEROPT') && \defined('CURLHEADER_SEPARATE')) { + $curlopts[\CURLOPT_HEADEROPT] = \CURLHEADER_SEPARATE; + } + + // curl's resolve feature varies by host:port but ours varies by host only, let's handle this with our own DNS map + if (isset($this->multi->dnsCache->hostnames[$host])) { + $options['resolve'] += [$host => $this->multi->dnsCache->hostnames[$host]]; + } + + if ($options['resolve'] || $this->multi->dnsCache->evictions) { + // First reset any old DNS cache entries then add the new ones + $resolve = $this->multi->dnsCache->evictions; + $this->multi->dnsCache->evictions = []; + $port = parse_url($authority, \PHP_URL_PORT) ?: ('http:' === $scheme ? 80 : 443); + + if ($resolve && 0x072A00 > CurlClientState::$curlVersion['version_number']) { + // DNS cache removals require curl 7.42 or higher + $this->multi->reset(); + } + + foreach ($options['resolve'] as $host => $ip) { + $resolve[] = null === $ip ? "-$host:$port" : "$host:$port:$ip"; + $this->multi->dnsCache->hostnames[$host] = $ip; + $this->multi->dnsCache->removals["-$host:$port"] = "-$host:$port"; + } + + $curlopts[\CURLOPT_RESOLVE] = $resolve; + } + + if ('POST' === $method) { + // Use CURLOPT_POST to have browser-like POST-to-GET redirects for 301, 302 and 303 + $curlopts[\CURLOPT_POST] = true; + } elseif ('HEAD' === $method) { + $curlopts[\CURLOPT_NOBODY] = true; + } else { + $curlopts[\CURLOPT_CUSTOMREQUEST] = $method; + } + + if ('\\' !== \DIRECTORY_SEPARATOR && $options['timeout'] < 1) { + $curlopts[\CURLOPT_NOSIGNAL] = true; + } + + if (\extension_loaded('zlib') && !isset($options['normalized_headers']['accept-encoding'])) { + $options['headers'][] = 'Accept-Encoding: gzip'; // Expose only one encoding, some servers mess up when more are provided + } + + foreach ($options['headers'] as $header) { + if (':' === $header[-2] && \strlen($header) - 2 === strpos($header, ': ')) { + // curl requires a special syntax to send empty headers + $curlopts[\CURLOPT_HTTPHEADER][] = substr_replace($header, ';', -2); + } else { + $curlopts[\CURLOPT_HTTPHEADER][] = $header; + } + } + + // Prevent curl from sending its default Accept and Expect headers + foreach (['accept', 'expect'] as $header) { + if (!isset($options['normalized_headers'][$header][0])) { + $curlopts[\CURLOPT_HTTPHEADER][] = $header.':'; + } + } + + if (!\is_string($body = $options['body'])) { + if (\is_resource($body)) { + $curlopts[\CURLOPT_INFILE] = $body; + } else { + $eof = false; + $buffer = ''; + $curlopts[\CURLOPT_READFUNCTION] = static function ($ch, $fd, $length) use ($body, &$buffer, &$eof) { + return self::readRequestBody($length, $body, $buffer, $eof); + }; + } + + if (isset($options['normalized_headers']['content-length'][0])) { + $curlopts[\CURLOPT_INFILESIZE] = substr($options['normalized_headers']['content-length'][0], \strlen('Content-Length: ')); + } elseif (!isset($options['normalized_headers']['transfer-encoding'])) { + $curlopts[\CURLOPT_HTTPHEADER][] = 'Transfer-Encoding: chunked'; // Enable chunked request bodies + } + + if ('POST' !== $method) { + $curlopts[\CURLOPT_UPLOAD] = true; + + if (!isset($options['normalized_headers']['content-type'])) { + $curlopts[\CURLOPT_HTTPHEADER][] = 'Content-Type: application/x-www-form-urlencoded'; + } + } + } elseif ('' !== $body || 'POST' === $method) { + $curlopts[\CURLOPT_POSTFIELDS] = $body; + } + + if ($options['peer_fingerprint']) { + if (!isset($options['peer_fingerprint']['pin-sha256'])) { + throw new TransportException(__CLASS__.' supports only "pin-sha256" fingerprints.'); + } + + $curlopts[\CURLOPT_PINNEDPUBLICKEY] = 'sha256//'.implode(';sha256//', $options['peer_fingerprint']['pin-sha256']); + } + + if ($options['bindto']) { + if (file_exists($options['bindto'])) { + $curlopts[\CURLOPT_UNIX_SOCKET_PATH] = $options['bindto']; + } elseif (!str_starts_with($options['bindto'], 'if!') && preg_match('/^(.*):(\d+)$/', $options['bindto'], $matches)) { + $curlopts[\CURLOPT_INTERFACE] = $matches[1]; + $curlopts[\CURLOPT_LOCALPORT] = $matches[2]; + } else { + $curlopts[\CURLOPT_INTERFACE] = $options['bindto']; + } + } + + if (0 < $options['max_duration']) { + $curlopts[\CURLOPT_TIMEOUT_MS] = 1000 * $options['max_duration']; + } + + if (!empty($options['extra']['curl']) && \is_array($options['extra']['curl'])) { + $this->validateExtraCurlOptions($options['extra']['curl']); + $curlopts += $options['extra']['curl']; + } + + if ($pushedResponse = $this->multi->pushedResponses[$url] ?? null) { + unset($this->multi->pushedResponses[$url]); + + if (self::acceptPushForRequest($method, $options, $pushedResponse)) { + $this->logger && $this->logger->debug(sprintf('Accepting pushed response: "%s %s"', $method, $url)); + + // Reinitialize the pushed response with request's options + $ch = $pushedResponse->handle; + $pushedResponse = $pushedResponse->response; + $pushedResponse->__construct($this->multi, $url, $options, $this->logger); + } else { + $this->logger && $this->logger->debug(sprintf('Rejecting pushed response: "%s"', $url)); + $pushedResponse = null; + } + } + + if (!$pushedResponse) { + $ch = curl_init(); + $this->logger && $this->logger->info(sprintf('Request: "%s %s"', $method, $url)); + $curlopts += [\CURLOPT_SHARE => $this->multi->share]; + } + + foreach ($curlopts as $opt => $value) { + if (null !== $value && !curl_setopt($ch, $opt, $value) && \CURLOPT_CERTINFO !== $opt && (!\defined('CURLOPT_HEADEROPT') || \CURLOPT_HEADEROPT !== $opt)) { + $constantName = $this->findConstantName($opt); + throw new TransportException(sprintf('Curl option "%s" is not supported.', $constantName ?? $opt)); + } + } + + return $pushedResponse ?? new CurlResponse($this->multi, $ch, $options, $this->logger, $method, self::createRedirectResolver($options, $host), CurlClientState::$curlVersion['version_number']); + } + + /** + * {@inheritdoc} + */ + public function stream($responses, float $timeout = null): ResponseStreamInterface + { + if ($responses instanceof CurlResponse) { + $responses = [$responses]; + } elseif (!is_iterable($responses)) { + throw new \TypeError(sprintf('"%s()" expects parameter 1 to be an iterable of CurlResponse objects, "%s" given.', __METHOD__, get_debug_type($responses))); + } + + if (\is_resource($this->multi->handle) || $this->multi->handle instanceof \CurlMultiHandle) { + $active = 0; + while (\CURLM_CALL_MULTI_PERFORM === curl_multi_exec($this->multi->handle, $active)) { + } + } + + return new ResponseStream(CurlResponse::stream($responses, $timeout)); + } + + public function reset() + { + $this->multi->reset(); + } + + /** + * Accepts pushed responses only if their headers related to authentication match the request. + */ + private static function acceptPushForRequest(string $method, array $options, PushedResponse $pushedResponse): bool + { + if ('' !== $options['body'] || $method !== $pushedResponse->requestHeaders[':method'][0]) { + return false; + } + + foreach (['proxy', 'no_proxy', 'bindto', 'local_cert', 'local_pk'] as $k) { + if ($options[$k] !== $pushedResponse->parentOptions[$k]) { + return false; + } + } + + foreach (['authorization', 'cookie', 'range', 'proxy-authorization'] as $k) { + $normalizedHeaders = $options['normalized_headers'][$k] ?? []; + foreach ($normalizedHeaders as $i => $v) { + $normalizedHeaders[$i] = substr($v, \strlen($k) + 2); + } + + if (($pushedResponse->requestHeaders[$k] ?? []) !== $normalizedHeaders) { + return false; + } + } + + return true; + } + + /** + * Wraps the request's body callback to allow it to return strings longer than curl requested. + */ + private static function readRequestBody(int $length, \Closure $body, string &$buffer, bool &$eof): string + { + if (!$eof && \strlen($buffer) < $length) { + if (!\is_string($data = $body($length))) { + throw new TransportException(sprintf('The return value of the "body" option callback must be a string, "%s" returned.', get_debug_type($data))); + } + + $buffer .= $data; + $eof = '' === $data; + } + + $data = substr($buffer, 0, $length); + $buffer = substr($buffer, $length); + + return $data; + } + + /** + * Resolves relative URLs on redirects and deals with authentication headers. + * + * Work around CVE-2018-1000007: Authorization and Cookie headers should not follow redirects - fixed in Curl 7.64 + */ + private static function createRedirectResolver(array $options, string $host): \Closure + { + $redirectHeaders = []; + if (0 < $options['max_redirects']) { + $redirectHeaders['host'] = $host; + $redirectHeaders['with_auth'] = $redirectHeaders['no_auth'] = array_filter($options['headers'], static function ($h) { + return 0 !== stripos($h, 'Host:'); + }); + + if (isset($options['normalized_headers']['authorization'][0]) || isset($options['normalized_headers']['cookie'][0])) { + $redirectHeaders['no_auth'] = array_filter($options['headers'], static function ($h) { + return 0 !== stripos($h, 'Authorization:') && 0 !== stripos($h, 'Cookie:'); + }); + } + } + + return static function ($ch, string $location, bool $noContent) use (&$redirectHeaders) { + try { + $location = self::parseUrl($location); + } catch (InvalidArgumentException $e) { + return null; + } + + if ($noContent && $redirectHeaders) { + $filterContentHeaders = static function ($h) { + return 0 !== stripos($h, 'Content-Length:') && 0 !== stripos($h, 'Content-Type:') && 0 !== stripos($h, 'Transfer-Encoding:'); + }; + $redirectHeaders['no_auth'] = array_filter($redirectHeaders['no_auth'], $filterContentHeaders); + $redirectHeaders['with_auth'] = array_filter($redirectHeaders['with_auth'], $filterContentHeaders); + } + + if ($redirectHeaders && $host = parse_url('http:'.$location['authority'], \PHP_URL_HOST)) { + $requestHeaders = $redirectHeaders['host'] === $host ? $redirectHeaders['with_auth'] : $redirectHeaders['no_auth']; + curl_setopt($ch, \CURLOPT_HTTPHEADER, $requestHeaders); + } elseif ($noContent && $redirectHeaders) { + curl_setopt($ch, \CURLOPT_HTTPHEADER, $redirectHeaders['with_auth']); + } + + $url = self::parseUrl(curl_getinfo($ch, \CURLINFO_EFFECTIVE_URL)); + $url = self::resolveUrl($location, $url); + + curl_setopt($ch, \CURLOPT_PROXY, $options['proxy'] + ?? ('https:' === $url['scheme'] ? $_SERVER['https_proxy'] ?? $_SERVER['HTTPS_PROXY'] ?? null : null) + // Ignore HTTP_PROXY except on the CLI to work around httpoxy set of vulnerabilities + ?? $_SERVER['http_proxy'] ?? (\in_array(\PHP_SAPI, ['cli', 'phpdbg'], true) ? $_SERVER['HTTP_PROXY'] ?? null : null) ?? $_SERVER['all_proxy'] ?? $_SERVER['ALL_PROXY'] ?? null + ); + + return implode('', $url); + }; + } + + private function findConstantName(int $opt): ?string + { + $constants = array_filter(get_defined_constants(), static function ($v, $k) use ($opt) { + return $v === $opt && 'C' === $k[0] && (str_starts_with($k, 'CURLOPT_') || str_starts_with($k, 'CURLINFO_')); + }, \ARRAY_FILTER_USE_BOTH); + + return key($constants); + } + + /** + * Prevents overriding options that are set internally throughout the request. + */ + private function validateExtraCurlOptions(array $options): void + { + $curloptsToConfig = [ + // options used in CurlHttpClient + \CURLOPT_HTTPAUTH => 'auth_ntlm', + \CURLOPT_USERPWD => 'auth_ntlm', + \CURLOPT_RESOLVE => 'resolve', + \CURLOPT_NOSIGNAL => 'timeout', + \CURLOPT_HTTPHEADER => 'headers', + \CURLOPT_INFILE => 'body', + \CURLOPT_READFUNCTION => 'body', + \CURLOPT_INFILESIZE => 'body', + \CURLOPT_POSTFIELDS => 'body', + \CURLOPT_UPLOAD => 'body', + \CURLOPT_INTERFACE => 'bindto', + \CURLOPT_TIMEOUT_MS => 'max_duration', + \CURLOPT_TIMEOUT => 'max_duration', + \CURLOPT_MAXREDIRS => 'max_redirects', + \CURLOPT_PROXY => 'proxy', + \CURLOPT_NOPROXY => 'no_proxy', + \CURLOPT_SSL_VERIFYPEER => 'verify_peer', + \CURLOPT_SSL_VERIFYHOST => 'verify_host', + \CURLOPT_CAINFO => 'cafile', + \CURLOPT_CAPATH => 'capath', + \CURLOPT_SSL_CIPHER_LIST => 'ciphers', + \CURLOPT_SSLCERT => 'local_cert', + \CURLOPT_SSLKEY => 'local_pk', + \CURLOPT_KEYPASSWD => 'passphrase', + \CURLOPT_CERTINFO => 'capture_peer_cert_chain', + \CURLOPT_USERAGENT => 'normalized_headers', + \CURLOPT_REFERER => 'headers', + // options used in CurlResponse + \CURLOPT_NOPROGRESS => 'on_progress', + \CURLOPT_PROGRESSFUNCTION => 'on_progress', + ]; + + if (\defined('CURLOPT_UNIX_SOCKET_PATH')) { + $curloptsToConfig[\CURLOPT_UNIX_SOCKET_PATH] = 'bindto'; + } + + if (\defined('CURLOPT_PINNEDPUBLICKEY')) { + $curloptsToConfig[\CURLOPT_PINNEDPUBLICKEY] = 'peer_fingerprint'; + } + + $curloptsToCheck = [ + \CURLOPT_PRIVATE, + \CURLOPT_HEADERFUNCTION, + \CURLOPT_WRITEFUNCTION, + \CURLOPT_VERBOSE, + \CURLOPT_STDERR, + \CURLOPT_RETURNTRANSFER, + \CURLOPT_URL, + \CURLOPT_FOLLOWLOCATION, + \CURLOPT_HEADER, + \CURLOPT_CONNECTTIMEOUT, + \CURLOPT_CONNECTTIMEOUT_MS, + \CURLOPT_HTTP_VERSION, + \CURLOPT_PORT, + \CURLOPT_DNS_USE_GLOBAL_CACHE, + \CURLOPT_PROTOCOLS, + \CURLOPT_REDIR_PROTOCOLS, + \CURLOPT_COOKIEFILE, + \CURLINFO_REDIRECT_COUNT, + ]; + + if (\defined('CURLOPT_HTTP09_ALLOWED')) { + $curloptsToCheck[] = \CURLOPT_HTTP09_ALLOWED; + } + + if (\defined('CURLOPT_HEADEROPT')) { + $curloptsToCheck[] = \CURLOPT_HEADEROPT; + } + + $methodOpts = [ + \CURLOPT_POST, + \CURLOPT_PUT, + \CURLOPT_CUSTOMREQUEST, + \CURLOPT_HTTPGET, + \CURLOPT_NOBODY, + ]; + + foreach ($options as $opt => $optValue) { + if (isset($curloptsToConfig[$opt])) { + $constName = $this->findConstantName($opt) ?? $opt; + throw new InvalidArgumentException(sprintf('Cannot set "%s" with "extra.curl", use option "%s" instead.', $constName, $curloptsToConfig[$opt])); + } + + if (\in_array($opt, $methodOpts)) { + throw new InvalidArgumentException('The HTTP method cannot be overridden using "extra.curl".'); + } + + if (\in_array($opt, $curloptsToCheck)) { + $constName = $this->findConstantName($opt) ?? $opt; + throw new InvalidArgumentException(sprintf('Cannot set "%s" with "extra.curl".', $constName)); + } + } + } +} diff --git a/sites/all/libraries/vendor/symfony/http-client/DataCollector/HttpClientDataCollector.php b/sites/all/libraries/vendor/symfony/http-client/DataCollector/HttpClientDataCollector.php new file mode 100644 index 0000000000..db8bbbdd69 --- /dev/null +++ b/sites/all/libraries/vendor/symfony/http-client/DataCollector/HttpClientDataCollector.php @@ -0,0 +1,170 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient\DataCollector; + +use Symfony\Component\HttpClient\TraceableHttpClient; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\DataCollector\DataCollector; +use Symfony\Component\HttpKernel\DataCollector\LateDataCollectorInterface; +use Symfony\Component\VarDumper\Caster\ImgStub; + +/** + * @author Jérémy Romey + */ +final class HttpClientDataCollector extends DataCollector implements LateDataCollectorInterface +{ + /** + * @var TraceableHttpClient[] + */ + private $clients = []; + + public function registerClient(string $name, TraceableHttpClient $client) + { + $this->clients[$name] = $client; + } + + /** + * {@inheritdoc} + */ + public function collect(Request $request, Response $response, \Throwable $exception = null) + { + $this->reset(); + + foreach ($this->clients as $name => $client) { + [$errorCount, $traces] = $this->collectOnClient($client); + + $this->data['clients'][$name] = [ + 'traces' => $traces, + 'error_count' => $errorCount, + ]; + + $this->data['request_count'] += \count($traces); + $this->data['error_count'] += $errorCount; + } + } + + public function lateCollect() + { + foreach ($this->clients as $client) { + $client->reset(); + } + } + + public function getClients(): array + { + return $this->data['clients'] ?? []; + } + + public function getRequestCount(): int + { + return $this->data['request_count'] ?? 0; + } + + public function getErrorCount(): int + { + return $this->data['error_count'] ?? 0; + } + + /** + * {@inheritdoc} + */ + public function getName(): string + { + return 'http_client'; + } + + public function reset() + { + $this->data = [ + 'clients' => [], + 'request_count' => 0, + 'error_count' => 0, + ]; + } + + private function collectOnClient(TraceableHttpClient $client): array + { + $traces = $client->getTracedRequests(); + $errorCount = 0; + $baseInfo = [ + 'response_headers' => 1, + 'retry_count' => 1, + 'redirect_count' => 1, + 'redirect_url' => 1, + 'user_data' => 1, + 'error' => 1, + 'url' => 1, + ]; + + foreach ($traces as $i => $trace) { + if (400 <= ($trace['info']['http_code'] ?? 0)) { + ++$errorCount; + } + + $info = $trace['info']; + $traces[$i]['http_code'] = $info['http_code'] ?? 0; + + unset($info['filetime'], $info['http_code'], $info['ssl_verify_result'], $info['content_type']); + + if (($info['http_method'] ?? null) === $trace['method']) { + unset($info['http_method']); + } + + if (($info['url'] ?? null) === $trace['url']) { + unset($info['url']); + } + + foreach ($info as $k => $v) { + if (!$v || (is_numeric($v) && 0 > $v)) { + unset($info[$k]); + } + } + + if (\is_string($content = $trace['content'])) { + $contentType = 'application/octet-stream'; + + foreach ($info['response_headers'] ?? [] as $h) { + if (0 === stripos($h, 'content-type: ')) { + $contentType = substr($h, \strlen('content-type: ')); + break; + } + } + + if (0 === strpos($contentType, 'image/') && class_exists(ImgStub::class)) { + $content = new ImgStub($content, $contentType, ''); + } else { + $content = [$content]; + } + + $content = ['response_content' => $content]; + } elseif (\is_array($content)) { + $content = ['response_json' => $content]; + } else { + $content = []; + } + + if (isset($info['retry_count'])) { + $content['retries'] = $info['previous_info']; + unset($info['previous_info']); + } + + $debugInfo = array_diff_key($info, $baseInfo); + $info = ['info' => $debugInfo] + array_diff_key($info, $debugInfo) + $content; + unset($traces[$i]['info']); // break PHP reference used by TraceableHttpClient + $traces[$i]['info'] = $this->cloneVar($info); + $traces[$i]['options'] = $this->cloneVar($trace['options']); + } + + return [$errorCount, $traces]; + } +} diff --git a/sites/all/libraries/vendor/symfony/http-client/DecoratorTrait.php b/sites/all/libraries/vendor/symfony/http-client/DecoratorTrait.php new file mode 100644 index 0000000000..790fc32a59 --- /dev/null +++ b/sites/all/libraries/vendor/symfony/http-client/DecoratorTrait.php @@ -0,0 +1,66 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient; + +use Symfony\Contracts\HttpClient\HttpClientInterface; +use Symfony\Contracts\HttpClient\ResponseInterface; +use Symfony\Contracts\HttpClient\ResponseStreamInterface; +use Symfony\Contracts\Service\ResetInterface; + +/** + * Eases with writing decorators. + * + * @author Nicolas Grekas + */ +trait DecoratorTrait +{ + private $client; + + public function __construct(HttpClientInterface $client = null) + { + $this->client = $client ?? HttpClient::create(); + } + + /** + * {@inheritdoc} + */ + public function request(string $method, string $url, array $options = []): ResponseInterface + { + return $this->client->request($method, $url, $options); + } + + /** + * {@inheritdoc} + */ + public function stream($responses, float $timeout = null): ResponseStreamInterface + { + return $this->client->stream($responses, $timeout); + } + + /** + * {@inheritdoc} + */ + public function withOptions(array $options): self + { + $clone = clone $this; + $clone->client = $this->client->withOptions($options); + + return $clone; + } + + public function reset() + { + if ($this->client instanceof ResetInterface) { + $this->client->reset(); + } + } +} diff --git a/sites/all/libraries/vendor/symfony/http-client/DependencyInjection/HttpClientPass.php b/sites/all/libraries/vendor/symfony/http-client/DependencyInjection/HttpClientPass.php new file mode 100644 index 0000000000..73f8865134 --- /dev/null +++ b/sites/all/libraries/vendor/symfony/http-client/DependencyInjection/HttpClientPass.php @@ -0,0 +1,51 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient\DependencyInjection; + +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\HttpClient\TraceableHttpClient; + +final class HttpClientPass implements CompilerPassInterface +{ + private $clientTag; + + public function __construct(string $clientTag = 'http_client.client') + { + if (0 < \func_num_args()) { + trigger_deprecation('symfony/http-client', '5.3', 'Configuring "%s" is deprecated.', __CLASS__); + } + + $this->clientTag = $clientTag; + } + + /** + * {@inheritdoc} + */ + public function process(ContainerBuilder $container) + { + if (!$container->hasDefinition('data_collector.http_client')) { + return; + } + + foreach ($container->findTaggedServiceIds($this->clientTag) as $id => $tags) { + $container->register('.debug.'.$id, TraceableHttpClient::class) + ->setArguments([new Reference('.debug.'.$id.'.inner'), new Reference('debug.stopwatch', ContainerInterface::IGNORE_ON_INVALID_REFERENCE)]) + ->addTag('kernel.reset', ['method' => 'reset']) + ->setDecoratedService($id); + $container->getDefinition('data_collector.http_client') + ->addMethodCall('registerClient', [$id, new Reference('.debug.'.$id)]); + } + } +} diff --git a/sites/all/libraries/vendor/symfony/http-client/EventSourceHttpClient.php b/sites/all/libraries/vendor/symfony/http-client/EventSourceHttpClient.php new file mode 100644 index 0000000000..60e4e821d1 --- /dev/null +++ b/sites/all/libraries/vendor/symfony/http-client/EventSourceHttpClient.php @@ -0,0 +1,159 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient; + +use Symfony\Component\HttpClient\Chunk\ServerSentEvent; +use Symfony\Component\HttpClient\Exception\EventSourceException; +use Symfony\Component\HttpClient\Response\AsyncContext; +use Symfony\Component\HttpClient\Response\AsyncResponse; +use Symfony\Contracts\HttpClient\ChunkInterface; +use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface; +use Symfony\Contracts\HttpClient\HttpClientInterface; +use Symfony\Contracts\HttpClient\ResponseInterface; +use Symfony\Contracts\Service\ResetInterface; + +/** + * @author Antoine Bluchet + * @author Nicolas Grekas + */ +final class EventSourceHttpClient implements HttpClientInterface, ResetInterface +{ + use AsyncDecoratorTrait, HttpClientTrait { + AsyncDecoratorTrait::withOptions insteadof HttpClientTrait; + } + + private $reconnectionTime; + + public function __construct(HttpClientInterface $client = null, float $reconnectionTime = 10.0) + { + $this->client = $client ?? HttpClient::create(); + $this->reconnectionTime = $reconnectionTime; + } + + public function connect(string $url, array $options = []): ResponseInterface + { + return $this->request('GET', $url, self::mergeDefaultOptions($options, [ + 'buffer' => false, + 'headers' => [ + 'Accept' => 'text/event-stream', + 'Cache-Control' => 'no-cache', + ], + ], true)); + } + + public function request(string $method, string $url, array $options = []): ResponseInterface + { + $state = new class() { + public $buffer = null; + public $lastEventId = null; + public $reconnectionTime; + public $lastError = null; + }; + $state->reconnectionTime = $this->reconnectionTime; + + if ($accept = self::normalizeHeaders($options['headers'] ?? [])['accept'] ?? []) { + $state->buffer = \in_array($accept, [['Accept: text/event-stream'], ['accept: text/event-stream']], true) ? '' : null; + + if (null !== $state->buffer) { + $options['extra']['trace_content'] = false; + } + } + + return new AsyncResponse($this->client, $method, $url, $options, static function (ChunkInterface $chunk, AsyncContext $context) use ($state, $method, $url, $options) { + if (null !== $state->buffer) { + $context->setInfo('reconnection_time', $state->reconnectionTime); + $isTimeout = false; + } + $lastError = $state->lastError; + $state->lastError = null; + + try { + $isTimeout = $chunk->isTimeout(); + + if (null !== $chunk->getInformationalStatus() || $context->getInfo('canceled')) { + yield $chunk; + + return; + } + } catch (TransportExceptionInterface $e) { + $state->lastError = $lastError ?? microtime(true); + + if (null === $state->buffer || ($isTimeout && microtime(true) - $state->lastError < $state->reconnectionTime)) { + yield $chunk; + } else { + $options['headers']['Last-Event-ID'] = $state->lastEventId; + $state->buffer = ''; + $state->lastError = microtime(true); + $context->getResponse()->cancel(); + $context->replaceRequest($method, $url, $options); + if ($isTimeout) { + yield $chunk; + } else { + $context->pause($state->reconnectionTime); + } + } + + return; + } + + if ($chunk->isFirst()) { + if (preg_match('/^text\/event-stream(;|$)/i', $context->getHeaders()['content-type'][0] ?? '')) { + $state->buffer = ''; + } elseif (null !== $lastError || (null !== $state->buffer && 200 === $context->getStatusCode())) { + throw new EventSourceException(sprintf('Response content-type is "%s" while "text/event-stream" was expected for "%s".', $context->getHeaders()['content-type'][0] ?? '', $context->getInfo('url'))); + } else { + $context->passthru(); + } + + if (null === $lastError) { + yield $chunk; + } + + return; + } + + $rx = '/((?:\r\n|[\r\n]){2,})/'; + $content = $state->buffer.$chunk->getContent(); + + if ($chunk->isLast()) { + $rx = substr_replace($rx, '|$', -2, 0); + } + $events = preg_split($rx, $content, -1, \PREG_SPLIT_DELIM_CAPTURE); + $state->buffer = array_pop($events); + + for ($i = 0; isset($events[$i]); $i += 2) { + $event = new ServerSentEvent($events[$i].$events[1 + $i]); + + if ('' !== $event->getId()) { + $context->setInfo('last_event_id', $state->lastEventId = $event->getId()); + } + + if ($event->getRetry()) { + $context->setInfo('reconnection_time', $state->reconnectionTime = $event->getRetry()); + } + + yield $event; + } + + if (preg_match('/^(?::[^\r\n]*+(?:\r\n|[\r\n]))+$/m', $state->buffer)) { + $content = $state->buffer; + $state->buffer = ''; + + yield $context->createChunk($content); + } + + if ($chunk->isLast()) { + yield $chunk; + } + }); + } +} diff --git a/sites/all/libraries/vendor/symfony/http-client/Exception/ClientException.php b/sites/all/libraries/vendor/symfony/http-client/Exception/ClientException.php new file mode 100644 index 0000000000..4264534c01 --- /dev/null +++ b/sites/all/libraries/vendor/symfony/http-client/Exception/ClientException.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient\Exception; + +use Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface; + +/** + * Represents a 4xx response. + * + * @author Nicolas Grekas + */ +final class ClientException extends \RuntimeException implements ClientExceptionInterface +{ + use HttpExceptionTrait; +} diff --git a/sites/all/libraries/vendor/symfony/http-client/Exception/EventSourceException.php b/sites/all/libraries/vendor/symfony/http-client/Exception/EventSourceException.php new file mode 100644 index 0000000000..30ab7957c5 --- /dev/null +++ b/sites/all/libraries/vendor/symfony/http-client/Exception/EventSourceException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient\Exception; + +use Symfony\Contracts\HttpClient\Exception\DecodingExceptionInterface; + +/** + * @author Nicolas Grekas + */ +final class EventSourceException extends \RuntimeException implements DecodingExceptionInterface +{ +} diff --git a/sites/all/libraries/vendor/symfony/http-client/Exception/HttpExceptionTrait.php b/sites/all/libraries/vendor/symfony/http-client/Exception/HttpExceptionTrait.php new file mode 100644 index 0000000000..8cbaa1cd10 --- /dev/null +++ b/sites/all/libraries/vendor/symfony/http-client/Exception/HttpExceptionTrait.php @@ -0,0 +1,78 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient\Exception; + +use Symfony\Contracts\HttpClient\ResponseInterface; + +/** + * @author Nicolas Grekas + * + * @internal + */ +trait HttpExceptionTrait +{ + private $response; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + $code = $response->getInfo('http_code'); + $url = $response->getInfo('url'); + $message = sprintf('HTTP %d returned for "%s".', $code, $url); + + $httpCodeFound = false; + $isJson = false; + foreach (array_reverse($response->getInfo('response_headers')) as $h) { + if (str_starts_with($h, 'HTTP/')) { + if ($httpCodeFound) { + break; + } + + $message = sprintf('%s returned for "%s".', $h, $url); + $httpCodeFound = true; + } + + if (0 === stripos($h, 'content-type:')) { + if (preg_match('/\bjson\b/i', $h)) { + $isJson = true; + } + + if ($httpCodeFound) { + break; + } + } + } + + // Try to guess a better error message using common API error formats + // The MIME type isn't explicitly checked because some formats inherit from others + // Ex: JSON:API follows RFC 7807 semantics, Hydra can be used in any JSON-LD-compatible format + if ($isJson && $body = json_decode($response->getContent(false), true)) { + if (isset($body['hydra:title']) || isset($body['hydra:description'])) { + // see http://www.hydra-cg.com/spec/latest/core/#description-of-http-status-codes-and-errors + $separator = isset($body['hydra:title'], $body['hydra:description']) ? "\n\n" : ''; + $message = ($body['hydra:title'] ?? '').$separator.($body['hydra:description'] ?? ''); + } elseif ((isset($body['title']) || isset($body['detail'])) + && (\is_scalar($body['title'] ?? '') && \is_scalar($body['detail'] ?? ''))) { + // see RFC 7807 and https://jsonapi.org/format/#error-objects + $separator = isset($body['title'], $body['detail']) ? "\n\n" : ''; + $message = ($body['title'] ?? '').$separator.($body['detail'] ?? ''); + } + } + + parent::__construct($message, $code); + } + + public function getResponse(): ResponseInterface + { + return $this->response; + } +} diff --git a/sites/all/libraries/vendor/symfony/http-client/Exception/InvalidArgumentException.php b/sites/all/libraries/vendor/symfony/http-client/Exception/InvalidArgumentException.php new file mode 100644 index 0000000000..6c2fae76fc --- /dev/null +++ b/sites/all/libraries/vendor/symfony/http-client/Exception/InvalidArgumentException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient\Exception; + +use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface; + +/** + * @author Nicolas Grekas + */ +final class InvalidArgumentException extends \InvalidArgumentException implements TransportExceptionInterface +{ +} diff --git a/sites/all/libraries/vendor/symfony/http-client/Exception/JsonException.php b/sites/all/libraries/vendor/symfony/http-client/Exception/JsonException.php new file mode 100644 index 0000000000..54502e6269 --- /dev/null +++ b/sites/all/libraries/vendor/symfony/http-client/Exception/JsonException.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient\Exception; + +use Symfony\Contracts\HttpClient\Exception\DecodingExceptionInterface; + +/** + * Thrown by responses' toArray() method when their content cannot be JSON-decoded. + * + * @author Nicolas Grekas + */ +final class JsonException extends \JsonException implements DecodingExceptionInterface +{ +} diff --git a/sites/all/libraries/vendor/symfony/http-client/Exception/RedirectionException.php b/sites/all/libraries/vendor/symfony/http-client/Exception/RedirectionException.php new file mode 100644 index 0000000000..5b936702ca --- /dev/null +++ b/sites/all/libraries/vendor/symfony/http-client/Exception/RedirectionException.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient\Exception; + +use Symfony\Contracts\HttpClient\Exception\RedirectionExceptionInterface; + +/** + * Represents a 3xx response. + * + * @author Nicolas Grekas + */ +final class RedirectionException extends \RuntimeException implements RedirectionExceptionInterface +{ + use HttpExceptionTrait; +} diff --git a/sites/all/libraries/vendor/symfony/http-client/Exception/ServerException.php b/sites/all/libraries/vendor/symfony/http-client/Exception/ServerException.php new file mode 100644 index 0000000000..c6f827310c --- /dev/null +++ b/sites/all/libraries/vendor/symfony/http-client/Exception/ServerException.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient\Exception; + +use Symfony\Contracts\HttpClient\Exception\ServerExceptionInterface; + +/** + * Represents a 5xx response. + * + * @author Nicolas Grekas + */ +final class ServerException extends \RuntimeException implements ServerExceptionInterface +{ + use HttpExceptionTrait; +} diff --git a/sites/all/libraries/vendor/symfony/http-client/Exception/TimeoutException.php b/sites/all/libraries/vendor/symfony/http-client/Exception/TimeoutException.php new file mode 100644 index 0000000000..a9155cc8f6 --- /dev/null +++ b/sites/all/libraries/vendor/symfony/http-client/Exception/TimeoutException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient\Exception; + +use Symfony\Contracts\HttpClient\Exception\TimeoutExceptionInterface; + +/** + * @author Nicolas Grekas + */ +final class TimeoutException extends TransportException implements TimeoutExceptionInterface +{ +} diff --git a/sites/all/libraries/vendor/symfony/http-client/Exception/TransportException.php b/sites/all/libraries/vendor/symfony/http-client/Exception/TransportException.php new file mode 100644 index 0000000000..a3a80c6dc6 --- /dev/null +++ b/sites/all/libraries/vendor/symfony/http-client/Exception/TransportException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient\Exception; + +use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface; + +/** + * @author Nicolas Grekas + */ +class TransportException extends \RuntimeException implements TransportExceptionInterface +{ +} diff --git a/sites/all/libraries/vendor/symfony/http-client/HttpClient.php b/sites/all/libraries/vendor/symfony/http-client/HttpClient.php new file mode 100644 index 0000000000..30fe339a37 --- /dev/null +++ b/sites/all/libraries/vendor/symfony/http-client/HttpClient.php @@ -0,0 +1,78 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient; + +use Amp\Http\Client\Connection\ConnectionLimitingPool; +use Symfony\Contracts\HttpClient\HttpClientInterface; + +/** + * A factory to instantiate the best possible HTTP client for the runtime. + * + * @author Nicolas Grekas + */ +final class HttpClient +{ + /** + * @param array $defaultOptions Default request's options + * @param int $maxHostConnections The maximum number of connections to a single host + * @param int $maxPendingPushes The maximum number of pushed responses to accept in the queue + * + * @see HttpClientInterface::OPTIONS_DEFAULTS for available options + */ + public static function create(array $defaultOptions = [], int $maxHostConnections = 6, int $maxPendingPushes = 50): HttpClientInterface + { + if ($amp = class_exists(ConnectionLimitingPool::class)) { + if (!\extension_loaded('curl')) { + return new AmpHttpClient($defaultOptions, null, $maxHostConnections, $maxPendingPushes); + } + + // Skip curl when HTTP/2 push is unsupported or buggy, see https://bugs.php.net/77535 + if (\PHP_VERSION_ID < 70217 || (\PHP_VERSION_ID >= 70300 && \PHP_VERSION_ID < 70304) || !\defined('CURLMOPT_PUSHFUNCTION')) { + return new AmpHttpClient($defaultOptions, null, $maxHostConnections, $maxPendingPushes); + } + + static $curlVersion = null; + $curlVersion = $curlVersion ?? curl_version(); + + // HTTP/2 push crashes before curl 7.61 + if (0x073D00 > $curlVersion['version_number'] || !(\CURL_VERSION_HTTP2 & $curlVersion['features'])) { + return new AmpHttpClient($defaultOptions, null, $maxHostConnections, $maxPendingPushes); + } + } + + if (\extension_loaded('curl')) { + if ('\\' !== \DIRECTORY_SEPARATOR || isset($defaultOptions['cafile']) || isset($defaultOptions['capath']) || \ini_get('curl.cainfo') || \ini_get('openssl.cafile') || \ini_get('openssl.capath')) { + return new CurlHttpClient($defaultOptions, $maxHostConnections, $maxPendingPushes); + } + + @trigger_error('Configure the "curl.cainfo", "openssl.cafile" or "openssl.capath" php.ini setting to enable the CurlHttpClient', \E_USER_WARNING); + } + + if ($amp) { + return new AmpHttpClient($defaultOptions, null, $maxHostConnections, $maxPendingPushes); + } + + @trigger_error((\extension_loaded('curl') ? 'Upgrade' : 'Install').' the curl extension or run "composer require amphp/http-client" to perform async HTTP operations, including full HTTP/2 support', \E_USER_NOTICE); + + return new NativeHttpClient($defaultOptions, $maxHostConnections); + } + + /** + * Creates a client that adds options (e.g. authentication headers) only when the request URL matches the provided base URI. + */ + public static function createForBaseUri(string $baseUri, array $defaultOptions = [], int $maxHostConnections = 6, int $maxPendingPushes = 50): HttpClientInterface + { + $client = self::create([], $maxHostConnections, $maxPendingPushes); + + return ScopingHttpClient::forBaseUri($client, $baseUri, $defaultOptions); + } +} diff --git a/sites/all/libraries/vendor/symfony/http-client/HttpClientTrait.php b/sites/all/libraries/vendor/symfony/http-client/HttpClientTrait.php new file mode 100644 index 0000000000..57ffc51352 --- /dev/null +++ b/sites/all/libraries/vendor/symfony/http-client/HttpClientTrait.php @@ -0,0 +1,686 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient; + +use Symfony\Component\HttpClient\Exception\InvalidArgumentException; +use Symfony\Component\HttpClient\Exception\TransportException; + +/** + * Provides the common logic from writing HttpClientInterface implementations. + * + * All private methods are static to prevent implementers from creating memory leaks via circular references. + * + * @author Nicolas Grekas + */ +trait HttpClientTrait +{ + private static $CHUNK_SIZE = 16372; + + /** + * {@inheritdoc} + */ + public function withOptions(array $options): self + { + $clone = clone $this; + $clone->defaultOptions = self::mergeDefaultOptions($options, $this->defaultOptions); + + return $clone; + } + + /** + * Validates and normalizes method, URL and options, and merges them with defaults. + * + * @throws InvalidArgumentException When a not-supported option is found + */ + private static function prepareRequest(?string $method, ?string $url, array $options, array $defaultOptions = [], bool $allowExtraOptions = false): array + { + if (null !== $method) { + if (\strlen($method) !== strspn($method, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ')) { + throw new InvalidArgumentException(sprintf('Invalid HTTP method "%s", only uppercase letters are accepted.', $method)); + } + if (!$method) { + throw new InvalidArgumentException('The HTTP method cannot be empty.'); + } + } + + $options = self::mergeDefaultOptions($options, $defaultOptions, $allowExtraOptions); + + $buffer = $options['buffer'] ?? true; + + if ($buffer instanceof \Closure) { + $options['buffer'] = static function (array $headers) use ($buffer) { + if (!\is_bool($buffer = $buffer($headers))) { + if (!\is_array($bufferInfo = @stream_get_meta_data($buffer))) { + throw new \LogicException(sprintf('The closure passed as option "buffer" must return bool or stream resource, got "%s".', get_debug_type($buffer))); + } + + if (false === strpbrk($bufferInfo['mode'], 'acew+')) { + throw new \LogicException(sprintf('The stream returned by the closure passed as option "buffer" must be writeable, got mode "%s".', $bufferInfo['mode'])); + } + } + + return $buffer; + }; + } elseif (!\is_bool($buffer)) { + if (!\is_array($bufferInfo = @stream_get_meta_data($buffer))) { + throw new InvalidArgumentException(sprintf('Option "buffer" must be bool, stream resource or Closure, "%s" given.', get_debug_type($buffer))); + } + + if (false === strpbrk($bufferInfo['mode'], 'acew+')) { + throw new InvalidArgumentException(sprintf('The stream in option "buffer" must be writeable, mode "%s" given.', $bufferInfo['mode'])); + } + } + + if (isset($options['json'])) { + if (isset($options['body']) && '' !== $options['body']) { + throw new InvalidArgumentException('Define either the "json" or the "body" option, setting both is not supported.'); + } + $options['body'] = self::jsonEncode($options['json']); + unset($options['json']); + + if (!isset($options['normalized_headers']['content-type'])) { + $options['normalized_headers']['content-type'] = ['Content-Type: application/json']; + } + } + + if (!isset($options['normalized_headers']['accept'])) { + $options['normalized_headers']['accept'] = ['Accept: */*']; + } + + if (isset($options['body'])) { + $options['body'] = self::normalizeBody($options['body']); + + if (\is_string($options['body']) + && (string) \strlen($options['body']) !== substr($h = $options['normalized_headers']['content-length'][0] ?? '', 16) + && ('' !== $h || '' !== $options['body']) + ) { + if ('chunked' === substr($options['normalized_headers']['transfer-encoding'][0] ?? '', \strlen('Transfer-Encoding: '))) { + unset($options['normalized_headers']['transfer-encoding']); + $options['body'] = self::dechunk($options['body']); + } + + $options['normalized_headers']['content-length'] = [substr_replace($h ?: 'Content-Length: ', \strlen($options['body']), 16)]; + } + } + + if (isset($options['peer_fingerprint'])) { + $options['peer_fingerprint'] = self::normalizePeerFingerprint($options['peer_fingerprint']); + } + + // Validate on_progress + if (isset($options['on_progress']) && !\is_callable($onProgress = $options['on_progress'])) { + throw new InvalidArgumentException(sprintf('Option "on_progress" must be callable, "%s" given.', get_debug_type($onProgress))); + } + + if (\is_array($options['auth_basic'] ?? null)) { + $count = \count($options['auth_basic']); + if ($count <= 0 || $count > 2) { + throw new InvalidArgumentException(sprintf('Option "auth_basic" must contain 1 or 2 elements, "%s" given.', $count)); + } + + $options['auth_basic'] = implode(':', $options['auth_basic']); + } + + if (!\is_string($options['auth_basic'] ?? '')) { + throw new InvalidArgumentException(sprintf('Option "auth_basic" must be string or an array, "%s" given.', get_debug_type($options['auth_basic']))); + } + + if (isset($options['auth_bearer'])) { + if (!\is_string($options['auth_bearer'])) { + throw new InvalidArgumentException(sprintf('Option "auth_bearer" must be a string, "%s" given.', get_debug_type($options['auth_bearer']))); + } + if (preg_match('{[^\x21-\x7E]}', $options['auth_bearer'])) { + throw new InvalidArgumentException('Invalid character found in option "auth_bearer": '.json_encode($options['auth_bearer']).'.'); + } + } + + if (isset($options['auth_basic'], $options['auth_bearer'])) { + throw new InvalidArgumentException('Define either the "auth_basic" or the "auth_bearer" option, setting both is not supported.'); + } + + if (null !== $url) { + // Merge auth with headers + if (($options['auth_basic'] ?? false) && !($options['normalized_headers']['authorization'] ?? false)) { + $options['normalized_headers']['authorization'] = ['Authorization: Basic '.base64_encode($options['auth_basic'])]; + } + // Merge bearer with headers + if (($options['auth_bearer'] ?? false) && !($options['normalized_headers']['authorization'] ?? false)) { + $options['normalized_headers']['authorization'] = ['Authorization: Bearer '.$options['auth_bearer']]; + } + + unset($options['auth_basic'], $options['auth_bearer']); + + // Parse base URI + if (\is_string($options['base_uri'])) { + $options['base_uri'] = self::parseUrl($options['base_uri']); + } + + // Validate and resolve URL + $url = self::parseUrl($url, $options['query']); + $url = self::resolveUrl($url, $options['base_uri'], $defaultOptions['query'] ?? []); + } + + // Finalize normalization of options + $options['http_version'] = (string) ($options['http_version'] ?? '') ?: null; + if (0 > $options['timeout'] = (float) ($options['timeout'] ?? \ini_get('default_socket_timeout'))) { + $options['timeout'] = 172800.0; // 2 days + } + + $options['max_duration'] = isset($options['max_duration']) ? (float) $options['max_duration'] : 0; + $options['headers'] = array_merge(...array_values($options['normalized_headers'])); + + return [$url, $options]; + } + + /** + * @throws InvalidArgumentException When an invalid option is found + */ + private static function mergeDefaultOptions(array $options, array $defaultOptions, bool $allowExtraOptions = false): array + { + $options['normalized_headers'] = self::normalizeHeaders($options['headers'] ?? []); + + if ($defaultOptions['headers'] ?? false) { + $options['normalized_headers'] += self::normalizeHeaders($defaultOptions['headers']); + } + + $options['headers'] = array_merge(...array_values($options['normalized_headers']) ?: [[]]); + + if ($resolve = $options['resolve'] ?? false) { + $options['resolve'] = []; + foreach ($resolve as $k => $v) { + $options['resolve'][substr(self::parseUrl('http://'.$k)['authority'], 2)] = (string) $v; + } + } + + // Option "query" is never inherited from defaults + $options['query'] = $options['query'] ?? []; + + $options += $defaultOptions; + + if (isset(self::$emptyDefaults)) { + foreach (self::$emptyDefaults as $k => $v) { + if (!isset($options[$k])) { + $options[$k] = $v; + } + } + } + + if (isset($defaultOptions['extra'])) { + $options['extra'] += $defaultOptions['extra']; + } + + if ($resolve = $defaultOptions['resolve'] ?? false) { + foreach ($resolve as $k => $v) { + $options['resolve'] += [substr(self::parseUrl('http://'.$k)['authority'], 2) => (string) $v]; + } + } + + if ($allowExtraOptions || !$defaultOptions) { + return $options; + } + + // Look for unsupported options + foreach ($options as $name => $v) { + if (\array_key_exists($name, $defaultOptions) || 'normalized_headers' === $name) { + continue; + } + + if ('auth_ntlm' === $name) { + if (!\extension_loaded('curl')) { + $msg = 'try installing the "curl" extension to use "%s" instead.'; + } else { + $msg = 'try using "%s" instead.'; + } + + throw new InvalidArgumentException(sprintf('Option "auth_ntlm" is not supported by "%s", '.$msg, __CLASS__, CurlHttpClient::class)); + } + + $alternatives = []; + + foreach ($defaultOptions as $k => $v) { + if (levenshtein($name, $k) <= \strlen($name) / 3 || str_contains($k, $name)) { + $alternatives[] = $k; + } + } + + throw new InvalidArgumentException(sprintf('Unsupported option "%s" passed to "%s", did you mean "%s"?', $name, __CLASS__, implode('", "', $alternatives ?: array_keys($defaultOptions)))); + } + + return $options; + } + + /** + * @return string[][] + * + * @throws InvalidArgumentException When an invalid header is found + */ + private static function normalizeHeaders(array $headers): array + { + $normalizedHeaders = []; + + foreach ($headers as $name => $values) { + if (\is_object($values) && method_exists($values, '__toString')) { + $values = (string) $values; + } + + if (\is_int($name)) { + if (!\is_string($values)) { + throw new InvalidArgumentException(sprintf('Invalid value for header "%s": expected string, "%s" given.', $name, get_debug_type($values))); + } + [$name, $values] = explode(':', $values, 2); + $values = [ltrim($values)]; + } elseif (!is_iterable($values)) { + if (\is_object($values)) { + throw new InvalidArgumentException(sprintf('Invalid value for header "%s": expected string, "%s" given.', $name, get_debug_type($values))); + } + + $values = (array) $values; + } + + $lcName = strtolower($name); + $normalizedHeaders[$lcName] = []; + + foreach ($values as $value) { + $normalizedHeaders[$lcName][] = $value = $name.': '.$value; + + if (\strlen($value) !== strcspn($value, "\r\n\0")) { + throw new InvalidArgumentException(sprintf('Invalid header: CR/LF/NUL found in "%s".', $value)); + } + } + } + + return $normalizedHeaders; + } + + /** + * @param array|string|resource|\Traversable|\Closure $body + * + * @return string|resource|\Closure + * + * @throws InvalidArgumentException When an invalid body is passed + */ + private static function normalizeBody($body) + { + if (\is_array($body)) { + array_walk_recursive($body, $caster = static function (&$v) use (&$caster) { + if (\is_object($v)) { + if ($vars = get_object_vars($v)) { + array_walk_recursive($vars, $caster); + $v = $vars; + } elseif (method_exists($v, '__toString')) { + $v = (string) $v; + } + } + }); + + return http_build_query($body, '', '&'); + } + + if (\is_string($body)) { + return $body; + } + + $generatorToCallable = static function (\Generator $body): \Closure { + return static function () use ($body) { + while ($body->valid()) { + $chunk = $body->current(); + $body->next(); + + if ('' !== $chunk) { + return $chunk; + } + } + + return ''; + }; + }; + + if ($body instanceof \Generator) { + return $generatorToCallable($body); + } + + if ($body instanceof \Traversable) { + return $generatorToCallable((static function ($body) { yield from $body; })($body)); + } + + if ($body instanceof \Closure) { + $r = new \ReflectionFunction($body); + $body = $r->getClosure(); + + if ($r->isGenerator()) { + $body = $body(self::$CHUNK_SIZE); + + return $generatorToCallable($body); + } + + return $body; + } + + if (!\is_array(@stream_get_meta_data($body))) { + throw new InvalidArgumentException(sprintf('Option "body" must be string, stream resource, iterable or callable, "%s" given.', get_debug_type($body))); + } + + return $body; + } + + private static function dechunk(string $body): string + { + $h = fopen('php://temp', 'w+'); + stream_filter_append($h, 'dechunk', \STREAM_FILTER_WRITE); + fwrite($h, $body); + $body = stream_get_contents($h, -1, 0); + rewind($h); + ftruncate($h, 0); + + if (fwrite($h, '-') && '' !== stream_get_contents($h, -1, 0)) { + throw new TransportException('Request body has broken chunked encoding.'); + } + + return $body; + } + + /** + * @param string|string[] $fingerprint + * + * @throws InvalidArgumentException When an invalid fingerprint is passed + */ + private static function normalizePeerFingerprint($fingerprint): array + { + if (\is_string($fingerprint)) { + switch (\strlen($fingerprint = str_replace(':', '', $fingerprint))) { + case 32: $fingerprint = ['md5' => $fingerprint]; break; + case 40: $fingerprint = ['sha1' => $fingerprint]; break; + case 44: $fingerprint = ['pin-sha256' => [$fingerprint]]; break; + case 64: $fingerprint = ['sha256' => $fingerprint]; break; + default: throw new InvalidArgumentException(sprintf('Cannot auto-detect fingerprint algorithm for "%s".', $fingerprint)); + } + } elseif (\is_array($fingerprint)) { + foreach ($fingerprint as $algo => $hash) { + $fingerprint[$algo] = 'pin-sha256' === $algo ? (array) $hash : str_replace(':', '', $hash); + } + } else { + throw new InvalidArgumentException(sprintf('Option "peer_fingerprint" must be string or array, "%s" given.', get_debug_type($fingerprint))); + } + + return $fingerprint; + } + + /** + * @param mixed $value + * + * @throws InvalidArgumentException When the value cannot be json-encoded + */ + private static function jsonEncode($value, int $flags = null, int $maxDepth = 512): string + { + $flags = $flags ?? (\JSON_HEX_TAG | \JSON_HEX_APOS | \JSON_HEX_AMP | \JSON_HEX_QUOT | \JSON_PRESERVE_ZERO_FRACTION); + + try { + $value = json_encode($value, $flags | (\PHP_VERSION_ID >= 70300 ? \JSON_THROW_ON_ERROR : 0), $maxDepth); + } catch (\JsonException $e) { + throw new InvalidArgumentException('Invalid value for "json" option: '.$e->getMessage()); + } + + if (\PHP_VERSION_ID < 70300 && \JSON_ERROR_NONE !== json_last_error() && (false === $value || !($flags & \JSON_PARTIAL_OUTPUT_ON_ERROR))) { + throw new InvalidArgumentException('Invalid value for "json" option: '.json_last_error_msg()); + } + + return $value; + } + + /** + * Resolves a URL against a base URI. + * + * @see https://tools.ietf.org/html/rfc3986#section-5.2.2 + * + * @throws InvalidArgumentException When an invalid URL is passed + */ + private static function resolveUrl(array $url, ?array $base, array $queryDefaults = []): array + { + if (null !== $base && '' === ($base['scheme'] ?? '').($base['authority'] ?? '')) { + throw new InvalidArgumentException(sprintf('Invalid "base_uri" option: host or scheme is missing in "%s".', implode('', $base))); + } + + if (null === $url['scheme'] && (null === $base || null === $base['scheme'])) { + throw new InvalidArgumentException(sprintf('Invalid URL: scheme is missing in "%s". Did you forget to add "http(s)://"?', implode('', $base ?? $url))); + } + + if (null === $base && '' === $url['scheme'].$url['authority']) { + throw new InvalidArgumentException(sprintf('Invalid URL: no "base_uri" option was provided and host or scheme is missing in "%s".', implode('', $url))); + } + + if (null !== $url['scheme']) { + $url['path'] = self::removeDotSegments($url['path'] ?? ''); + } else { + if (null !== $url['authority']) { + $url['path'] = self::removeDotSegments($url['path'] ?? ''); + } else { + if (null === $url['path']) { + $url['path'] = $base['path']; + $url['query'] = $url['query'] ?? $base['query']; + } else { + if ('/' !== $url['path'][0]) { + if (null === $base['path']) { + $url['path'] = '/'.$url['path']; + } else { + $segments = explode('/', $base['path']); + array_splice($segments, -1, 1, [$url['path']]); + $url['path'] = implode('/', $segments); + } + } + + $url['path'] = self::removeDotSegments($url['path']); + } + + $url['authority'] = $base['authority']; + + if ($queryDefaults) { + $url['query'] = '?'.self::mergeQueryString(substr($url['query'] ?? '', 1), $queryDefaults, false); + } + } + + $url['scheme'] = $base['scheme']; + } + + if ('' === ($url['path'] ?? '')) { + $url['path'] = '/'; + } + + if ('?' === ($url['query'] ?? '')) { + $url['query'] = null; + } + + return $url; + } + + /** + * Parses a URL and fixes its encoding if needed. + * + * @throws InvalidArgumentException When an invalid URL is passed + */ + private static function parseUrl(string $url, array $query = [], array $allowedSchemes = ['http' => 80, 'https' => 443]): array + { + if (false === $parts = parse_url($url)) { + throw new InvalidArgumentException(sprintf('Malformed URL "%s".', $url)); + } + + if ($query) { + $parts['query'] = self::mergeQueryString($parts['query'] ?? null, $query, true); + } + + $port = $parts['port'] ?? 0; + + if (null !== $scheme = $parts['scheme'] ?? null) { + if (!isset($allowedSchemes[$scheme = strtolower($scheme)])) { + throw new InvalidArgumentException(sprintf('Unsupported scheme in "%s".', $url)); + } + + $port = $allowedSchemes[$scheme] === $port ? 0 : $port; + $scheme .= ':'; + } + + if (null !== $host = $parts['host'] ?? null) { + if (!\defined('INTL_IDNA_VARIANT_UTS46') && preg_match('/[\x80-\xFF]/', $host)) { + throw new InvalidArgumentException(sprintf('Unsupported IDN "%s", try enabling the "intl" PHP extension or running "composer require symfony/polyfill-intl-idn".', $host)); + } + + $host = \defined('INTL_IDNA_VARIANT_UTS46') ? idn_to_ascii($host, \IDNA_DEFAULT | \IDNA_USE_STD3_RULES | \IDNA_CHECK_BIDI | \IDNA_CHECK_CONTEXTJ | \IDNA_NONTRANSITIONAL_TO_ASCII, \INTL_IDNA_VARIANT_UTS46) ?: strtolower($host) : strtolower($host); + $host .= $port ? ':'.$port : ''; + } + + foreach (['user', 'pass', 'path', 'query', 'fragment'] as $part) { + if (!isset($parts[$part])) { + continue; + } + + if (str_contains($parts[$part], '%')) { + // https://tools.ietf.org/html/rfc3986#section-2.3 + $parts[$part] = preg_replace_callback('/%(?:2[DE]|3[0-9]|[46][1-9A-F]|5F|[57][0-9A]|7E)++/i', function ($m) { return rawurldecode($m[0]); }, $parts[$part]); + } + + // https://tools.ietf.org/html/rfc3986#section-3.3 + $parts[$part] = preg_replace_callback("#[^-A-Za-z0-9._~!$&/'()*+,;=:@%]++#", function ($m) { return rawurlencode($m[0]); }, $parts[$part]); + } + + return [ + 'scheme' => $scheme, + 'authority' => null !== $host ? '//'.(isset($parts['user']) ? $parts['user'].(isset($parts['pass']) ? ':'.$parts['pass'] : '').'@' : '').$host : null, + 'path' => isset($parts['path'][0]) ? $parts['path'] : null, + 'query' => isset($parts['query']) ? '?'.$parts['query'] : null, + 'fragment' => isset($parts['fragment']) ? '#'.$parts['fragment'] : null, + ]; + } + + /** + * Removes dot-segments from a path. + * + * @see https://tools.ietf.org/html/rfc3986#section-5.2.4 + */ + private static function removeDotSegments(string $path) + { + $result = ''; + + while (!\in_array($path, ['', '.', '..'], true)) { + if ('.' === $path[0] && (str_starts_with($path, $p = '../') || str_starts_with($path, $p = './'))) { + $path = substr($path, \strlen($p)); + } elseif ('/.' === $path || str_starts_with($path, '/./')) { + $path = substr_replace($path, '/', 0, 3); + } elseif ('/..' === $path || str_starts_with($path, '/../')) { + $i = strrpos($result, '/'); + $result = $i ? substr($result, 0, $i) : ''; + $path = substr_replace($path, '/', 0, 4); + } else { + $i = strpos($path, '/', 1) ?: \strlen($path); + $result .= substr($path, 0, $i); + $path = substr($path, $i); + } + } + + return $result; + } + + /** + * Merges and encodes a query array with a query string. + * + * @throws InvalidArgumentException When an invalid query-string value is passed + */ + private static function mergeQueryString(?string $queryString, array $queryArray, bool $replace): ?string + { + if (!$queryArray) { + return $queryString; + } + + $query = []; + + if (null !== $queryString) { + foreach (explode('&', $queryString) as $v) { + if ('' !== $v) { + $k = urldecode(explode('=', $v, 2)[0]); + $query[$k] = (isset($query[$k]) ? $query[$k].'&' : '').$v; + } + } + } + + if ($replace) { + foreach ($queryArray as $k => $v) { + if (null === $v) { + unset($query[$k]); + } + } + } + + $queryString = http_build_query($queryArray, '', '&', \PHP_QUERY_RFC3986); + $queryArray = []; + + if ($queryString) { + foreach (explode('&', $queryString) as $v) { + $queryArray[rawurldecode(explode('=', $v, 2)[0])] = $v; + } + } + + return implode('&', $replace ? array_replace($query, $queryArray) : ($query + $queryArray)); + } + + /** + * Loads proxy configuration from the same environment variables as curl when no proxy is explicitly set. + */ + private static function getProxy(?string $proxy, array $url, ?string $noProxy): ?array + { + if (null === $proxy) { + // Ignore HTTP_PROXY except on the CLI to work around httpoxy set of vulnerabilities + $proxy = $_SERVER['http_proxy'] ?? (\in_array(\PHP_SAPI, ['cli', 'phpdbg'], true) ? $_SERVER['HTTP_PROXY'] ?? null : null) ?? $_SERVER['all_proxy'] ?? $_SERVER['ALL_PROXY'] ?? null; + + if ('https:' === $url['scheme']) { + $proxy = $_SERVER['https_proxy'] ?? $_SERVER['HTTPS_PROXY'] ?? $proxy; + } + } + + if (null === $proxy) { + return null; + } + + $proxy = (parse_url($proxy) ?: []) + ['scheme' => 'http']; + + if (!isset($proxy['host'])) { + throw new TransportException('Invalid HTTP proxy: host is missing.'); + } + + if ('http' === $proxy['scheme']) { + $proxyUrl = 'tcp://'.$proxy['host'].':'.($proxy['port'] ?? '80'); + } elseif ('https' === $proxy['scheme']) { + $proxyUrl = 'ssl://'.$proxy['host'].':'.($proxy['port'] ?? '443'); + } else { + throw new TransportException(sprintf('Unsupported proxy scheme "%s": "http" or "https" expected.', $proxy['scheme'])); + } + + $noProxy = $noProxy ?? $_SERVER['no_proxy'] ?? $_SERVER['NO_PROXY'] ?? ''; + $noProxy = $noProxy ? preg_split('/[\s,]+/', $noProxy) : []; + + return [ + 'url' => $proxyUrl, + 'auth' => isset($proxy['user']) ? 'Basic '.base64_encode(rawurldecode($proxy['user']).':'.rawurldecode($proxy['pass'] ?? '')) : null, + 'no_proxy' => $noProxy, + ]; + } + + private static function shouldBuffer(array $headers): bool + { + if (null === $contentType = $headers['content-type'][0] ?? null) { + return false; + } + + if (false !== $i = strpos($contentType, ';')) { + $contentType = substr($contentType, 0, $i); + } + + return $contentType && preg_match('#^(?:text/|application/(?:.+\+)?(?:json|xml)$)#i', $contentType); + } +} diff --git a/sites/all/libraries/vendor/symfony/http-client/HttpOptions.php b/sites/all/libraries/vendor/symfony/http-client/HttpOptions.php new file mode 100644 index 0000000000..da55f9965f --- /dev/null +++ b/sites/all/libraries/vendor/symfony/http-client/HttpOptions.php @@ -0,0 +1,331 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient; + +use Symfony\Contracts\HttpClient\HttpClientInterface; + +/** + * A helper providing autocompletion for available options. + * + * @see HttpClientInterface for a description of each options. + * + * @author Nicolas Grekas + */ +class HttpOptions +{ + private $options = []; + + public function toArray(): array + { + return $this->options; + } + + /** + * @return $this + */ + public function setAuthBasic(string $user, string $password = '') + { + $this->options['auth_basic'] = $user; + + if ('' !== $password) { + $this->options['auth_basic'] .= ':'.$password; + } + + return $this; + } + + /** + * @return $this + */ + public function setAuthBearer(string $token) + { + $this->options['auth_bearer'] = $token; + + return $this; + } + + /** + * @return $this + */ + public function setQuery(array $query) + { + $this->options['query'] = $query; + + return $this; + } + + /** + * @return $this + */ + public function setHeaders(iterable $headers) + { + $this->options['headers'] = $headers; + + return $this; + } + + /** + * @param array|string|resource|\Traversable|\Closure $body + * + * @return $this + */ + public function setBody($body) + { + $this->options['body'] = $body; + + return $this; + } + + /** + * @param mixed $json + * + * @return $this + */ + public function setJson($json) + { + $this->options['json'] = $json; + + return $this; + } + + /** + * @return $this + */ + public function setUserData($data) + { + $this->options['user_data'] = $data; + + return $this; + } + + /** + * @return $this + */ + public function setMaxRedirects(int $max) + { + $this->options['max_redirects'] = $max; + + return $this; + } + + /** + * @return $this + */ + public function setHttpVersion(string $version) + { + $this->options['http_version'] = $version; + + return $this; + } + + /** + * @return $this + */ + public function setBaseUri(string $uri) + { + $this->options['base_uri'] = $uri; + + return $this; + } + + /** + * @return $this + */ + public function buffer(bool $buffer) + { + $this->options['buffer'] = $buffer; + + return $this; + } + + /** + * @return $this + */ + public function setOnProgress(callable $callback) + { + $this->options['on_progress'] = $callback; + + return $this; + } + + /** + * @return $this + */ + public function resolve(array $hostIps) + { + $this->options['resolve'] = $hostIps; + + return $this; + } + + /** + * @return $this + */ + public function setProxy(string $proxy) + { + $this->options['proxy'] = $proxy; + + return $this; + } + + /** + * @return $this + */ + public function setNoProxy(string $noProxy) + { + $this->options['no_proxy'] = $noProxy; + + return $this; + } + + /** + * @return $this + */ + public function setTimeout(float $timeout) + { + $this->options['timeout'] = $timeout; + + return $this; + } + + /** + * @return $this + */ + public function setMaxDuration(float $maxDuration) + { + $this->options['max_duration'] = $maxDuration; + + return $this; + } + + /** + * @return $this + */ + public function bindTo(string $bindto) + { + $this->options['bindto'] = $bindto; + + return $this; + } + + /** + * @return $this + */ + public function verifyPeer(bool $verify) + { + $this->options['verify_peer'] = $verify; + + return $this; + } + + /** + * @return $this + */ + public function verifyHost(bool $verify) + { + $this->options['verify_host'] = $verify; + + return $this; + } + + /** + * @return $this + */ + public function setCaFile(string $cafile) + { + $this->options['cafile'] = $cafile; + + return $this; + } + + /** + * @return $this + */ + public function setCaPath(string $capath) + { + $this->options['capath'] = $capath; + + return $this; + } + + /** + * @return $this + */ + public function setLocalCert(string $cert) + { + $this->options['local_cert'] = $cert; + + return $this; + } + + /** + * @return $this + */ + public function setLocalPk(string $pk) + { + $this->options['local_pk'] = $pk; + + return $this; + } + + /** + * @return $this + */ + public function setPassphrase(string $passphrase) + { + $this->options['passphrase'] = $passphrase; + + return $this; + } + + /** + * @return $this + */ + public function setCiphers(string $ciphers) + { + $this->options['ciphers'] = $ciphers; + + return $this; + } + + /** + * @param string|array $fingerprint + * + * @return $this + */ + public function setPeerFingerprint($fingerprint) + { + $this->options['peer_fingerprint'] = $fingerprint; + + return $this; + } + + /** + * @return $this + */ + public function capturePeerCertChain(bool $capture) + { + $this->options['capture_peer_cert_chain'] = $capture; + + return $this; + } + + /** + * @return $this + */ + public function setExtra(string $name, $value) + { + $this->options['extra'][$name] = $value; + + return $this; + } +} diff --git a/sites/all/libraries/vendor/symfony/http-client/HttplugClient.php b/sites/all/libraries/vendor/symfony/http-client/HttplugClient.php new file mode 100644 index 0000000000..ec3b01d999 --- /dev/null +++ b/sites/all/libraries/vendor/symfony/http-client/HttplugClient.php @@ -0,0 +1,271 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient; + +use GuzzleHttp\Promise\Promise as GuzzlePromise; +use GuzzleHttp\Promise\RejectedPromise; +use GuzzleHttp\Promise\Utils; +use Http\Client\Exception\NetworkException; +use Http\Client\Exception\RequestException; +use Http\Client\HttpAsyncClient; +use Http\Client\HttpClient as HttplugInterface; +use Http\Discovery\Exception\NotFoundException; +use Http\Discovery\Psr17FactoryDiscovery; +use Http\Message\RequestFactory; +use Http\Message\StreamFactory; +use Http\Message\UriFactory; +use Http\Promise\Promise; +use Nyholm\Psr7\Factory\Psr17Factory; +use Nyholm\Psr7\Request; +use Nyholm\Psr7\Uri; +use Psr\Http\Message\RequestFactoryInterface; +use Psr\Http\Message\RequestInterface; +use Psr\Http\Message\ResponseFactoryInterface; +use Psr\Http\Message\ResponseInterface as Psr7ResponseInterface; +use Psr\Http\Message\StreamFactoryInterface; +use Psr\Http\Message\StreamInterface; +use Psr\Http\Message\UriFactoryInterface; +use Psr\Http\Message\UriInterface; +use Symfony\Component\HttpClient\Internal\HttplugWaitLoop; +use Symfony\Component\HttpClient\Response\HttplugPromise; +use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface; +use Symfony\Contracts\HttpClient\HttpClientInterface; +use Symfony\Contracts\HttpClient\ResponseInterface; +use Symfony\Contracts\Service\ResetInterface; + +if (!interface_exists(HttplugInterface::class)) { + throw new \LogicException('You cannot use "Symfony\Component\HttpClient\HttplugClient" as the "php-http/httplug" package is not installed. Try running "composer require php-http/httplug".'); +} + +if (!interface_exists(RequestFactory::class)) { + throw new \LogicException('You cannot use "Symfony\Component\HttpClient\HttplugClient" as the "php-http/message-factory" package is not installed. Try running "composer require nyholm/psr7".'); +} + +/** + * An adapter to turn a Symfony HttpClientInterface into an Httplug client. + * + * Run "composer require nyholm/psr7" to install an efficient implementation of response + * and stream factories with flex-provided autowiring aliases. + * + * @author Nicolas Grekas + */ +final class HttplugClient implements HttplugInterface, HttpAsyncClient, RequestFactory, StreamFactory, UriFactory, ResetInterface +{ + private $client; + private $responseFactory; + private $streamFactory; + + /** + * @var \SplObjectStorage|null + */ + private $promisePool; + + private $waitLoop; + + public function __construct(HttpClientInterface $client = null, ResponseFactoryInterface $responseFactory = null, StreamFactoryInterface $streamFactory = null) + { + $this->client = $client ?? HttpClient::create(); + $this->responseFactory = $responseFactory; + $this->streamFactory = $streamFactory ?? ($responseFactory instanceof StreamFactoryInterface ? $responseFactory : null); + $this->promisePool = class_exists(Utils::class) ? new \SplObjectStorage() : null; + + if (null === $this->responseFactory || null === $this->streamFactory) { + if (!class_exists(Psr17Factory::class) && !class_exists(Psr17FactoryDiscovery::class)) { + throw new \LogicException('You cannot use the "Symfony\Component\HttpClient\HttplugClient" as no PSR-17 factories have been provided. Try running "composer require nyholm/psr7".'); + } + + try { + $psr17Factory = class_exists(Psr17Factory::class, false) ? new Psr17Factory() : null; + $this->responseFactory = $this->responseFactory ?? $psr17Factory ?? Psr17FactoryDiscovery::findResponseFactory(); + $this->streamFactory = $this->streamFactory ?? $psr17Factory ?? Psr17FactoryDiscovery::findStreamFactory(); + } catch (NotFoundException $e) { + throw new \LogicException('You cannot use the "Symfony\Component\HttpClient\HttplugClient" as no PSR-17 factories have been found. Try running "composer require nyholm/psr7".', 0, $e); + } + } + + $this->waitLoop = new HttplugWaitLoop($this->client, $this->promisePool, $this->responseFactory, $this->streamFactory); + } + + /** + * {@inheritdoc} + */ + public function sendRequest(RequestInterface $request): Psr7ResponseInterface + { + try { + return $this->waitLoop->createPsr7Response($this->sendPsr7Request($request)); + } catch (TransportExceptionInterface $e) { + throw new NetworkException($e->getMessage(), $request, $e); + } + } + + /** + * {@inheritdoc} + * + * @return HttplugPromise + */ + public function sendAsyncRequest(RequestInterface $request): Promise + { + if (!$promisePool = $this->promisePool) { + throw new \LogicException(sprintf('You cannot use "%s()" as the "guzzlehttp/promises" package is not installed. Try running "composer require guzzlehttp/promises".', __METHOD__)); + } + + try { + $response = $this->sendPsr7Request($request, true); + } catch (NetworkException $e) { + return new HttplugPromise(new RejectedPromise($e)); + } + + $waitLoop = $this->waitLoop; + + $promise = new GuzzlePromise(static function () use ($response, $waitLoop) { + $waitLoop->wait($response); + }, static function () use ($response, $promisePool) { + $response->cancel(); + unset($promisePool[$response]); + }); + + $promisePool[$response] = [$request, $promise]; + + return new HttplugPromise($promise); + } + + /** + * Resolves pending promises that complete before the timeouts are reached. + * + * When $maxDuration is null and $idleTimeout is reached, promises are rejected. + * + * @return int The number of remaining pending promises + */ + public function wait(float $maxDuration = null, float $idleTimeout = null): int + { + return $this->waitLoop->wait(null, $maxDuration, $idleTimeout); + } + + /** + * {@inheritdoc} + */ + public function createRequest($method, $uri, array $headers = [], $body = null, $protocolVersion = '1.1'): RequestInterface + { + if ($this->responseFactory instanceof RequestFactoryInterface) { + $request = $this->responseFactory->createRequest($method, $uri); + } elseif (class_exists(Request::class)) { + $request = new Request($method, $uri); + } elseif (class_exists(Psr17FactoryDiscovery::class)) { + $request = Psr17FactoryDiscovery::findRequestFactory()->createRequest($method, $uri); + } else { + throw new \LogicException(sprintf('You cannot use "%s()" as the "nyholm/psr7" package is not installed. Try running "composer require nyholm/psr7".', __METHOD__)); + } + + $request = $request + ->withProtocolVersion($protocolVersion) + ->withBody($this->createStream($body)) + ; + + foreach ($headers as $name => $value) { + $request = $request->withAddedHeader($name, $value); + } + + return $request; + } + + /** + * {@inheritdoc} + */ + public function createStream($body = null): StreamInterface + { + if ($body instanceof StreamInterface) { + return $body; + } + + if (\is_string($body ?? '')) { + $stream = $this->streamFactory->createStream($body ?? ''); + } elseif (\is_resource($body)) { + $stream = $this->streamFactory->createStreamFromResource($body); + } else { + throw new \InvalidArgumentException(sprintf('"%s()" expects string, resource or StreamInterface, "%s" given.', __METHOD__, get_debug_type($body))); + } + + if ($stream->isSeekable()) { + $stream->seek(0); + } + + return $stream; + } + + /** + * {@inheritdoc} + */ + public function createUri($uri): UriInterface + { + if ($uri instanceof UriInterface) { + return $uri; + } + + if ($this->responseFactory instanceof UriFactoryInterface) { + return $this->responseFactory->createUri($uri); + } + + if (class_exists(Uri::class)) { + return new Uri($uri); + } + + if (class_exists(Psr17FactoryDiscovery::class)) { + return Psr17FactoryDiscovery::findUrlFactory()->createUri($uri); + } + + throw new \LogicException(sprintf('You cannot use "%s()" as the "nyholm/psr7" package is not installed. Try running "composer require nyholm/psr7".', __METHOD__)); + } + + public function __sleep(): array + { + throw new \BadMethodCallException('Cannot serialize '.__CLASS__); + } + + public function __wakeup() + { + throw new \BadMethodCallException('Cannot unserialize '.__CLASS__); + } + + public function __destruct() + { + $this->wait(); + } + + public function reset() + { + if ($this->client instanceof ResetInterface) { + $this->client->reset(); + } + } + + private function sendPsr7Request(RequestInterface $request, bool $buffer = null): ResponseInterface + { + try { + $body = $request->getBody(); + + if ($body->isSeekable()) { + $body->seek(0); + } + + return $this->client->request($request->getMethod(), (string) $request->getUri(), [ + 'headers' => $request->getHeaders(), + 'body' => $body->getContents(), + 'http_version' => '1.0' === $request->getProtocolVersion() ? '1.0' : null, + 'buffer' => $buffer, + ]); + } catch (\InvalidArgumentException $e) { + throw new RequestException($e->getMessage(), $request, $e); + } catch (TransportExceptionInterface $e) { + throw new NetworkException($e->getMessage(), $request, $e); + } + } +} diff --git a/sites/all/libraries/vendor/symfony/http-client/Internal/AmpBody.php b/sites/all/libraries/vendor/symfony/http-client/Internal/AmpBody.php new file mode 100644 index 0000000000..b99742b13b --- /dev/null +++ b/sites/all/libraries/vendor/symfony/http-client/Internal/AmpBody.php @@ -0,0 +1,142 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient\Internal; + +use Amp\ByteStream\InputStream; +use Amp\ByteStream\ResourceInputStream; +use Amp\Http\Client\RequestBody; +use Amp\Promise; +use Amp\Success; +use Symfony\Component\HttpClient\Exception\TransportException; + +/** + * @author Nicolas Grekas + * + * @internal + */ +class AmpBody implements RequestBody, InputStream +{ + private $body; + private $info; + private $onProgress; + private $offset = 0; + private $length = -1; + private $uploaded; + + public function __construct($body, &$info, \Closure $onProgress) + { + $this->body = $body; + $this->info = &$info; + $this->onProgress = $onProgress; + + if (\is_resource($body)) { + $this->offset = ftell($body); + $this->length = fstat($body)['size']; + $this->body = new ResourceInputStream($body); + } elseif (\is_string($body)) { + $this->length = \strlen($body); + } + } + + public function createBodyStream(): InputStream + { + if (null !== $this->uploaded) { + $this->uploaded = null; + + if (\is_string($this->body)) { + $this->offset = 0; + } elseif ($this->body instanceof ResourceInputStream) { + fseek($this->body->getResource(), $this->offset); + } + } + + return $this; + } + + public function getHeaders(): Promise + { + return new Success([]); + } + + public function getBodyLength(): Promise + { + return new Success($this->length - $this->offset); + } + + public function read(): Promise + { + $this->info['size_upload'] += $this->uploaded; + $this->uploaded = 0; + ($this->onProgress)(); + + $chunk = $this->doRead(); + $chunk->onResolve(function ($e, $data) { + if (null !== $data) { + $this->uploaded = \strlen($data); + } else { + $this->info['upload_content_length'] = $this->info['size_upload']; + } + }); + + return $chunk; + } + + public static function rewind(RequestBody $body): RequestBody + { + if (!$body instanceof self) { + return $body; + } + + $body->uploaded = null; + + if ($body->body instanceof ResourceInputStream) { + fseek($body->body->getResource(), $body->offset); + + return new $body($body->body, $body->info, $body->onProgress); + } + + if (\is_string($body->body)) { + $body->offset = 0; + } + + return $body; + } + + private function doRead(): Promise + { + if ($this->body instanceof ResourceInputStream) { + return $this->body->read(); + } + + if (null === $this->offset || !$this->length) { + return new Success(); + } + + if (\is_string($this->body)) { + $this->offset = null; + + return new Success($this->body); + } + + if ('' === $data = ($this->body)(16372)) { + $this->offset = null; + + return new Success(); + } + + if (!\is_string($data)) { + throw new TransportException(sprintf('Return value of the "body" option callback must be string, "%s" returned.', get_debug_type($data))); + } + + return new Success($data); + } +} diff --git a/sites/all/libraries/vendor/symfony/http-client/Internal/AmpClientState.php b/sites/all/libraries/vendor/symfony/http-client/Internal/AmpClientState.php new file mode 100644 index 0000000000..3061f0802d --- /dev/null +++ b/sites/all/libraries/vendor/symfony/http-client/Internal/AmpClientState.php @@ -0,0 +1,217 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient\Internal; + +use Amp\CancellationToken; +use Amp\Deferred; +use Amp\Http\Client\Connection\ConnectionLimitingPool; +use Amp\Http\Client\Connection\DefaultConnectionFactory; +use Amp\Http\Client\InterceptedHttpClient; +use Amp\Http\Client\Interceptor\RetryRequests; +use Amp\Http\Client\PooledHttpClient; +use Amp\Http\Client\Request; +use Amp\Http\Client\Response; +use Amp\Http\Tunnel\Http1TunnelConnector; +use Amp\Http\Tunnel\Https1TunnelConnector; +use Amp\Promise; +use Amp\Socket\Certificate; +use Amp\Socket\ClientTlsContext; +use Amp\Socket\ConnectContext; +use Amp\Socket\Connector; +use Amp\Socket\DnsConnector; +use Amp\Socket\SocketAddress; +use Amp\Success; +use Psr\Log\LoggerInterface; + +/** + * Internal representation of the Amp client's state. + * + * @author Nicolas Grekas + * + * @internal + */ +final class AmpClientState extends ClientState +{ + public $dnsCache = []; + public $responseCount = 0; + public $pushedResponses = []; + + private $clients = []; + private $clientConfigurator; + private $maxHostConnections; + private $maxPendingPushes; + private $logger; + + public function __construct(?callable $clientConfigurator, int $maxHostConnections, int $maxPendingPushes, ?LoggerInterface &$logger) + { + $this->clientConfigurator = $clientConfigurator ?? static function (PooledHttpClient $client) { + return new InterceptedHttpClient($client, new RetryRequests(2)); + }; + $this->maxHostConnections = $maxHostConnections; + $this->maxPendingPushes = $maxPendingPushes; + $this->logger = &$logger; + } + + /** + * @return Promise + */ + public function request(array $options, Request $request, CancellationToken $cancellation, array &$info, \Closure $onProgress, &$handle): Promise + { + if ($options['proxy']) { + if ($request->hasHeader('proxy-authorization')) { + $options['proxy']['auth'] = $request->getHeader('proxy-authorization'); + } + + // Matching "no_proxy" should follow the behavior of curl + $host = $request->getUri()->getHost(); + foreach ($options['proxy']['no_proxy'] as $rule) { + $dotRule = '.'.ltrim($rule, '.'); + + if ('*' === $rule || $host === $rule || substr($host, -\strlen($dotRule)) === $dotRule) { + $options['proxy'] = null; + break; + } + } + } + + $request = clone $request; + + if ($request->hasHeader('proxy-authorization')) { + $request->removeHeader('proxy-authorization'); + } + + if ($options['capture_peer_cert_chain']) { + $info['peer_certificate_chain'] = []; + } + + $request->addEventListener(new AmpListener($info, $options['peer_fingerprint']['pin-sha256'] ?? [], $onProgress, $handle)); + $request->setPushHandler(function ($request, $response) use ($options): Promise { + return $this->handlePush($request, $response, $options); + }); + + ($request->hasHeader('content-length') ? new Success((int) $request->getHeader('content-length')) : $request->getBody()->getBodyLength()) + ->onResolve(static function ($e, $bodySize) use (&$info) { + if (null !== $bodySize && 0 <= $bodySize) { + $info['upload_content_length'] = ((1 + $info['upload_content_length']) ?? 1) - 1 + $bodySize; + } + }); + + [$client, $connector] = $this->getClient($options); + $response = $client->request($request, $cancellation); + $response->onResolve(static function ($e) use ($connector, &$handle) { + if (null === $e) { + $handle = $connector->handle; + } + }); + + return $response; + } + + private function getClient(array $options): array + { + $options = [ + 'bindto' => $options['bindto'] ?: '0', + 'verify_peer' => $options['verify_peer'], + 'capath' => $options['capath'], + 'cafile' => $options['cafile'], + 'local_cert' => $options['local_cert'], + 'local_pk' => $options['local_pk'], + 'ciphers' => $options['ciphers'], + 'capture_peer_cert_chain' => $options['capture_peer_cert_chain'] || $options['peer_fingerprint'], + 'proxy' => $options['proxy'], + ]; + + $key = md5(serialize($options)); + + if (isset($this->clients[$key])) { + return $this->clients[$key]; + } + + $context = new ClientTlsContext(''); + $options['verify_peer'] || $context = $context->withoutPeerVerification(); + $options['cafile'] && $context = $context->withCaFile($options['cafile']); + $options['capath'] && $context = $context->withCaPath($options['capath']); + $options['local_cert'] && $context = $context->withCertificate(new Certificate($options['local_cert'], $options['local_pk'])); + $options['ciphers'] && $context = $context->withCiphers($options['ciphers']); + $options['capture_peer_cert_chain'] && $context = $context->withPeerCapturing(); + + $connector = $handleConnector = new class() implements Connector { + public $connector; + public $uri; + public $handle; + + public function connect(string $uri, ConnectContext $context = null, CancellationToken $token = null): Promise + { + $result = $this->connector->connect($this->uri ?? $uri, $context, $token); + $result->onResolve(function ($e, $socket) { + $this->handle = null !== $socket ? $socket->getResource() : false; + }); + + return $result; + } + }; + $connector->connector = new DnsConnector(new AmpResolver($this->dnsCache)); + + $context = (new ConnectContext()) + ->withTcpNoDelay() + ->withTlsContext($context); + + if ($options['bindto']) { + if (file_exists($options['bindto'])) { + $connector->uri = 'unix://'.$options['bindto']; + } else { + $context = $context->withBindTo($options['bindto']); + } + } + + if ($options['proxy']) { + $proxyUrl = parse_url($options['proxy']['url']); + $proxySocket = new SocketAddress($proxyUrl['host'], $proxyUrl['port']); + $proxyHeaders = $options['proxy']['auth'] ? ['Proxy-Authorization' => $options['proxy']['auth']] : []; + + if ('ssl' === $proxyUrl['scheme']) { + $connector = new Https1TunnelConnector($proxySocket, $context->getTlsContext(), $proxyHeaders, $connector); + } else { + $connector = new Http1TunnelConnector($proxySocket, $proxyHeaders, $connector); + } + } + + $maxHostConnections = 0 < $this->maxHostConnections ? $this->maxHostConnections : \PHP_INT_MAX; + $pool = new DefaultConnectionFactory($connector, $context); + $pool = ConnectionLimitingPool::byAuthority($maxHostConnections, $pool); + + return $this->clients[$key] = [($this->clientConfigurator)(new PooledHttpClient($pool)), $handleConnector]; + } + + private function handlePush(Request $request, Promise $response, array $options): Promise + { + $deferred = new Deferred(); + $authority = $request->getUri()->getAuthority(); + + if ($this->maxPendingPushes <= \count($this->pushedResponses[$authority] ?? [])) { + $fifoUrl = key($this->pushedResponses[$authority]); + unset($this->pushedResponses[$authority][$fifoUrl]); + $this->logger && $this->logger->debug(sprintf('Evicting oldest pushed response: "%s"', $fifoUrl)); + } + + $url = (string) $request->getUri(); + $this->logger && $this->logger->debug(sprintf('Queueing pushed response: "%s"', $url)); + $this->pushedResponses[$authority][] = [$url, $deferred, $request, $response, [ + 'proxy' => $options['proxy'], + 'bindto' => $options['bindto'], + 'local_cert' => $options['local_cert'], + 'local_pk' => $options['local_pk'], + ]]; + + return $deferred->promise(); + } +} diff --git a/sites/all/libraries/vendor/symfony/http-client/Internal/AmpListener.php b/sites/all/libraries/vendor/symfony/http-client/Internal/AmpListener.php new file mode 100644 index 0000000000..cb3235bca3 --- /dev/null +++ b/sites/all/libraries/vendor/symfony/http-client/Internal/AmpListener.php @@ -0,0 +1,183 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient\Internal; + +use Amp\Http\Client\Connection\Stream; +use Amp\Http\Client\EventListener; +use Amp\Http\Client\Request; +use Amp\Promise; +use Amp\Success; +use Symfony\Component\HttpClient\Exception\TransportException; + +/** + * @author Nicolas Grekas + * + * @internal + */ +class AmpListener implements EventListener +{ + private $info; + private $pinSha256; + private $onProgress; + private $handle; + + public function __construct(array &$info, array $pinSha256, \Closure $onProgress, &$handle) + { + $info += [ + 'connect_time' => 0.0, + 'pretransfer_time' => 0.0, + 'starttransfer_time' => 0.0, + 'total_time' => 0.0, + 'namelookup_time' => 0.0, + 'primary_ip' => '', + 'primary_port' => 0, + ]; + + $this->info = &$info; + $this->pinSha256 = $pinSha256; + $this->onProgress = $onProgress; + $this->handle = &$handle; + } + + public function startRequest(Request $request): Promise + { + $this->info['start_time'] = $this->info['start_time'] ?? microtime(true); + ($this->onProgress)(); + + return new Success(); + } + + public function startDnsResolution(Request $request): Promise + { + ($this->onProgress)(); + + return new Success(); + } + + public function startConnectionCreation(Request $request): Promise + { + ($this->onProgress)(); + + return new Success(); + } + + public function startTlsNegotiation(Request $request): Promise + { + ($this->onProgress)(); + + return new Success(); + } + + public function startSendingRequest(Request $request, Stream $stream): Promise + { + $host = $stream->getRemoteAddress()->getHost(); + + if (false !== strpos($host, ':')) { + $host = '['.$host.']'; + } + + $this->info['primary_ip'] = $host; + $this->info['primary_port'] = $stream->getRemoteAddress()->getPort(); + $this->info['pretransfer_time'] = microtime(true) - $this->info['start_time']; + $this->info['debug'] .= sprintf("* Connected to %s (%s) port %d\n", $request->getUri()->getHost(), $host, $this->info['primary_port']); + + if ((isset($this->info['peer_certificate_chain']) || $this->pinSha256) && null !== $tlsInfo = $stream->getTlsInfo()) { + foreach ($tlsInfo->getPeerCertificates() as $cert) { + $this->info['peer_certificate_chain'][] = openssl_x509_read($cert->toPem()); + } + + if ($this->pinSha256) { + $pin = openssl_pkey_get_public($this->info['peer_certificate_chain'][0]); + $pin = openssl_pkey_get_details($pin)['key']; + $pin = \array_slice(explode("\n", $pin), 1, -2); + $pin = base64_decode(implode('', $pin)); + $pin = base64_encode(hash('sha256', $pin, true)); + + if (!\in_array($pin, $this->pinSha256, true)) { + throw new TransportException(sprintf('SSL public key does not match pinned public key for "%s".', $this->info['url'])); + } + } + } + ($this->onProgress)(); + + $uri = $request->getUri(); + $requestUri = $uri->getPath() ?: '/'; + + if ('' !== $query = $uri->getQuery()) { + $requestUri .= '?'.$query; + } + + if ('CONNECT' === $method = $request->getMethod()) { + $requestUri = $uri->getHost().': '.($uri->getPort() ?? ('https' === $uri->getScheme() ? 443 : 80)); + } + + $this->info['debug'] .= sprintf("> %s %s HTTP/%s \r\n", $method, $requestUri, $request->getProtocolVersions()[0]); + + foreach ($request->getRawHeaders() as [$name, $value]) { + $this->info['debug'] .= $name.': '.$value."\r\n"; + } + $this->info['debug'] .= "\r\n"; + + return new Success(); + } + + public function completeSendingRequest(Request $request, Stream $stream): Promise + { + ($this->onProgress)(); + + return new Success(); + } + + public function startReceivingResponse(Request $request, Stream $stream): Promise + { + $this->info['starttransfer_time'] = microtime(true) - $this->info['start_time']; + ($this->onProgress)(); + + return new Success(); + } + + public function completeReceivingResponse(Request $request, Stream $stream): Promise + { + $this->handle = null; + ($this->onProgress)(); + + return new Success(); + } + + public function completeDnsResolution(Request $request): Promise + { + $this->info['namelookup_time'] = microtime(true) - $this->info['start_time']; + ($this->onProgress)(); + + return new Success(); + } + + public function completeConnectionCreation(Request $request): Promise + { + $this->info['connect_time'] = microtime(true) - $this->info['start_time']; + ($this->onProgress)(); + + return new Success(); + } + + public function completeTlsNegotiation(Request $request): Promise + { + ($this->onProgress)(); + + return new Success(); + } + + public function abort(Request $request, \Throwable $cause): Promise + { + return new Success(); + } +} diff --git a/sites/all/libraries/vendor/symfony/http-client/Internal/AmpResolver.php b/sites/all/libraries/vendor/symfony/http-client/Internal/AmpResolver.php new file mode 100644 index 0000000000..d31476a583 --- /dev/null +++ b/sites/all/libraries/vendor/symfony/http-client/Internal/AmpResolver.php @@ -0,0 +1,52 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient\Internal; + +use Amp\Dns; +use Amp\Dns\Record; +use Amp\Promise; +use Amp\Success; + +/** + * Handles local overrides for the DNS resolver. + * + * @author Nicolas Grekas + * + * @internal + */ +class AmpResolver implements Dns\Resolver +{ + private $dnsMap; + + public function __construct(array &$dnsMap) + { + $this->dnsMap = &$dnsMap; + } + + public function resolve(string $name, int $typeRestriction = null): Promise + { + if (!isset($this->dnsMap[$name]) || !\in_array($typeRestriction, [Record::A, null], true)) { + return Dns\resolver()->resolve($name, $typeRestriction); + } + + return new Success([new Record($this->dnsMap[$name], Record::A, null)]); + } + + public function query(string $name, int $type): Promise + { + if (!isset($this->dnsMap[$name]) || Record::A !== $type) { + return Dns\resolver()->query($name, $type); + } + + return new Success([new Record($this->dnsMap[$name], Record::A, null)]); + } +} diff --git a/sites/all/libraries/vendor/symfony/http-client/Internal/Canary.php b/sites/all/libraries/vendor/symfony/http-client/Internal/Canary.php new file mode 100644 index 0000000000..3d14b5fd1a --- /dev/null +++ b/sites/all/libraries/vendor/symfony/http-client/Internal/Canary.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient\Internal; + +/** + * @author Nicolas Grekas + * + * @internal + */ +final class Canary +{ + private $canceller; + + public function __construct(\Closure $canceller) + { + $this->canceller = $canceller; + } + + public function cancel() + { + if (($canceller = $this->canceller) instanceof \Closure) { + $this->canceller = null; + $canceller(); + } + } + + public function __destruct() + { + $this->cancel(); + } +} diff --git a/sites/all/libraries/vendor/symfony/http-client/Internal/ClientState.php b/sites/all/libraries/vendor/symfony/http-client/Internal/ClientState.php new file mode 100644 index 0000000000..52fe3c8c05 --- /dev/null +++ b/sites/all/libraries/vendor/symfony/http-client/Internal/ClientState.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient\Internal; + +/** + * Internal representation of the client state. + * + * @author Alexander M. Turek + * + * @internal + */ +class ClientState +{ + public $handlesActivity = []; + public $openHandles = []; + public $lastTimeout; +} diff --git a/sites/all/libraries/vendor/symfony/http-client/Internal/CurlClientState.php b/sites/all/libraries/vendor/symfony/http-client/Internal/CurlClientState.php new file mode 100644 index 0000000000..7d51c15cac --- /dev/null +++ b/sites/all/libraries/vendor/symfony/http-client/Internal/CurlClientState.php @@ -0,0 +1,148 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient\Internal; + +use Psr\Log\LoggerInterface; +use Symfony\Component\HttpClient\Response\CurlResponse; + +/** + * Internal representation of the cURL client's state. + * + * @author Alexander M. Turek + * + * @internal + */ +final class CurlClientState extends ClientState +{ + /** @var \CurlMultiHandle|resource|null */ + public $handle; + /** @var \CurlShareHandle|resource|null */ + public $share; + /** @var PushedResponse[] */ + public $pushedResponses = []; + /** @var DnsCache */ + public $dnsCache; + /** @var float[] */ + public $pauseExpiries = []; + public $execCounter = \PHP_INT_MIN; + /** @var LoggerInterface|null */ + public $logger; + + public static $curlVersion; + + public function __construct(int $maxHostConnections, int $maxPendingPushes) + { + self::$curlVersion = self::$curlVersion ?? curl_version(); + + $this->handle = curl_multi_init(); + $this->dnsCache = new DnsCache(); + $this->reset(); + + // Don't enable HTTP/1.1 pipelining: it forces responses to be sent in order + if (\defined('CURLPIPE_MULTIPLEX')) { + curl_multi_setopt($this->handle, \CURLMOPT_PIPELINING, \CURLPIPE_MULTIPLEX); + } + if (\defined('CURLMOPT_MAX_HOST_CONNECTIONS')) { + $maxHostConnections = curl_multi_setopt($this->handle, \CURLMOPT_MAX_HOST_CONNECTIONS, 0 < $maxHostConnections ? $maxHostConnections : \PHP_INT_MAX) ? 0 : $maxHostConnections; + } + if (\defined('CURLMOPT_MAXCONNECTS') && 0 < $maxHostConnections) { + curl_multi_setopt($this->handle, \CURLMOPT_MAXCONNECTS, $maxHostConnections); + } + + // Skip configuring HTTP/2 push when it's unsupported or buggy, see https://bugs.php.net/77535 + if (0 >= $maxPendingPushes || \PHP_VERSION_ID < 70217 || (\PHP_VERSION_ID >= 70300 && \PHP_VERSION_ID < 70304)) { + return; + } + + // HTTP/2 push crashes before curl 7.61 + if (!\defined('CURLMOPT_PUSHFUNCTION') || 0x073D00 > self::$curlVersion['version_number'] || !(\CURL_VERSION_HTTP2 & self::$curlVersion['features'])) { + return; + } + + // Clone to prevent a circular reference + $multi = clone $this; + $multi->handle = null; + $multi->share = null; + $multi->pushedResponses = &$this->pushedResponses; + $multi->logger = &$this->logger; + $multi->handlesActivity = &$this->handlesActivity; + $multi->openHandles = &$this->openHandles; + + curl_multi_setopt($this->handle, \CURLMOPT_PUSHFUNCTION, static function ($parent, $pushed, array $requestHeaders) use ($multi, $maxPendingPushes) { + return $multi->handlePush($parent, $pushed, $requestHeaders, $maxPendingPushes); + }); + } + + public function reset() + { + foreach ($this->pushedResponses as $url => $response) { + $this->logger && $this->logger->debug(sprintf('Unused pushed response: "%s"', $url)); + curl_multi_remove_handle($this->handle, $response->handle); + curl_close($response->handle); + } + + $this->pushedResponses = []; + $this->dnsCache->evictions = $this->dnsCache->evictions ?: $this->dnsCache->removals; + $this->dnsCache->removals = $this->dnsCache->hostnames = []; + + $this->share = curl_share_init(); + + curl_share_setopt($this->share, \CURLSHOPT_SHARE, \CURL_LOCK_DATA_DNS); + curl_share_setopt($this->share, \CURLSHOPT_SHARE, \CURL_LOCK_DATA_SSL_SESSION); + + if (\defined('CURL_LOCK_DATA_CONNECT') && \PHP_VERSION_ID >= 80000) { + curl_share_setopt($this->share, \CURLSHOPT_SHARE, \CURL_LOCK_DATA_CONNECT); + } + } + + private function handlePush($parent, $pushed, array $requestHeaders, int $maxPendingPushes): int + { + $headers = []; + $origin = curl_getinfo($parent, \CURLINFO_EFFECTIVE_URL); + + foreach ($requestHeaders as $h) { + if (false !== $i = strpos($h, ':', 1)) { + $headers[substr($h, 0, $i)][] = substr($h, 1 + $i); + } + } + + if (!isset($headers[':method']) || !isset($headers[':scheme']) || !isset($headers[':authority']) || !isset($headers[':path'])) { + $this->logger && $this->logger->debug(sprintf('Rejecting pushed response from "%s": pushed headers are invalid', $origin)); + + return \CURL_PUSH_DENY; + } + + $url = $headers[':scheme'][0].'://'.$headers[':authority'][0]; + + // curl before 7.65 doesn't validate the pushed ":authority" header, + // but this is a MUST in the HTTP/2 RFC; let's restrict pushes to the original host, + // ignoring domains mentioned as alt-name in the certificate for now (same as curl). + if (!str_starts_with($origin, $url.'/')) { + $this->logger && $this->logger->debug(sprintf('Rejecting pushed response from "%s": server is not authoritative for "%s"', $origin, $url)); + + return \CURL_PUSH_DENY; + } + + if ($maxPendingPushes <= \count($this->pushedResponses)) { + $fifoUrl = key($this->pushedResponses); + unset($this->pushedResponses[$fifoUrl]); + $this->logger && $this->logger->debug(sprintf('Evicting oldest pushed response: "%s"', $fifoUrl)); + } + + $url .= $headers[':path'][0]; + $this->logger && $this->logger->debug(sprintf('Queueing pushed response: "%s"', $url)); + + $this->pushedResponses[$url] = new PushedResponse(new CurlResponse($this, $pushed), $headers, $this->openHandles[(int) $parent][1] ?? [], $pushed); + + return \CURL_PUSH_OK; + } +} diff --git a/sites/all/libraries/vendor/symfony/http-client/Internal/DnsCache.php b/sites/all/libraries/vendor/symfony/http-client/Internal/DnsCache.php new file mode 100644 index 0000000000..bd23f77f8a --- /dev/null +++ b/sites/all/libraries/vendor/symfony/http-client/Internal/DnsCache.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient\Internal; + +/** + * Cache for resolved DNS queries. + * + * @author Alexander M. Turek + * + * @internal + */ +final class DnsCache +{ + /** + * Resolved hostnames (hostname => IP address). + * + * @var string[] + */ + public $hostnames = []; + + /** + * @var string[] + */ + public $removals = []; + + /** + * @var string[] + */ + public $evictions = []; +} diff --git a/sites/all/libraries/vendor/symfony/http-client/Internal/HttplugWaitLoop.php b/sites/all/libraries/vendor/symfony/http-client/Internal/HttplugWaitLoop.php new file mode 100644 index 0000000000..9f5658f560 --- /dev/null +++ b/sites/all/libraries/vendor/symfony/http-client/Internal/HttplugWaitLoop.php @@ -0,0 +1,141 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient\Internal; + +use Http\Client\Exception\NetworkException; +use Http\Promise\Promise; +use Psr\Http\Message\RequestInterface as Psr7RequestInterface; +use Psr\Http\Message\ResponseFactoryInterface; +use Psr\Http\Message\ResponseInterface as Psr7ResponseInterface; +use Psr\Http\Message\StreamFactoryInterface; +use Symfony\Component\HttpClient\Response\StreamableInterface; +use Symfony\Component\HttpClient\Response\StreamWrapper; +use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface; +use Symfony\Contracts\HttpClient\HttpClientInterface; +use Symfony\Contracts\HttpClient\ResponseInterface; + +/** + * @author Nicolas Grekas + * + * @internal + */ +final class HttplugWaitLoop +{ + private $client; + private $promisePool; + private $responseFactory; + private $streamFactory; + + /** + * @param \SplObjectStorage|null $promisePool + */ + public function __construct(HttpClientInterface $client, ?\SplObjectStorage $promisePool, ResponseFactoryInterface $responseFactory, StreamFactoryInterface $streamFactory) + { + $this->client = $client; + $this->promisePool = $promisePool; + $this->responseFactory = $responseFactory; + $this->streamFactory = $streamFactory; + } + + public function wait(?ResponseInterface $pendingResponse, float $maxDuration = null, float $idleTimeout = null): int + { + if (!$this->promisePool) { + return 0; + } + + $guzzleQueue = \GuzzleHttp\Promise\Utils::queue(); + + if (0.0 === $remainingDuration = $maxDuration) { + $idleTimeout = 0.0; + } elseif (null !== $maxDuration) { + $startTime = microtime(true); + $idleTimeout = max(0.0, min($maxDuration / 5, $idleTimeout ?? $maxDuration)); + } + + do { + foreach ($this->client->stream($this->promisePool, $idleTimeout) as $response => $chunk) { + try { + if (null !== $maxDuration && $chunk->isTimeout()) { + goto check_duration; + } + + if ($chunk->isFirst()) { + // Deactivate throwing on 3/4/5xx + $response->getStatusCode(); + } + + if (!$chunk->isLast()) { + goto check_duration; + } + + if ([, $promise] = $this->promisePool[$response] ?? null) { + unset($this->promisePool[$response]); + $promise->resolve($this->createPsr7Response($response, true)); + } + } catch (\Exception $e) { + if ([$request, $promise] = $this->promisePool[$response] ?? null) { + unset($this->promisePool[$response]); + + if ($e instanceof TransportExceptionInterface) { + $e = new NetworkException($e->getMessage(), $request, $e); + } + + $promise->reject($e); + } + } + + $guzzleQueue->run(); + + if ($pendingResponse === $response) { + return $this->promisePool->count(); + } + + check_duration: + if (null !== $maxDuration && $idleTimeout && $idleTimeout > $remainingDuration = max(0.0, $maxDuration - microtime(true) + $startTime)) { + $idleTimeout = $remainingDuration / 5; + break; + } + } + + if (!$count = $this->promisePool->count()) { + return 0; + } + } while (null === $maxDuration || 0 < $remainingDuration); + + return $count; + } + + public function createPsr7Response(ResponseInterface $response, bool $buffer = false): Psr7ResponseInterface + { + $psrResponse = $this->responseFactory->createResponse($response->getStatusCode()); + + foreach ($response->getHeaders(false) as $name => $values) { + foreach ($values as $value) { + $psrResponse = $psrResponse->withAddedHeader($name, $value); + } + } + + if ($response instanceof StreamableInterface) { + $body = $this->streamFactory->createStreamFromResource($response->toStream(false)); + } elseif (!$buffer) { + $body = $this->streamFactory->createStreamFromResource(StreamWrapper::createResource($response, $this->client)); + } else { + $body = $this->streamFactory->createStream($response->getContent(false)); + } + + if ($body->isSeekable()) { + $body->seek(0); + } + + return $psrResponse->withBody($body); + } +} diff --git a/sites/all/libraries/vendor/symfony/http-client/Internal/NativeClientState.php b/sites/all/libraries/vendor/symfony/http-client/Internal/NativeClientState.php new file mode 100644 index 0000000000..20b2727f59 --- /dev/null +++ b/sites/all/libraries/vendor/symfony/http-client/Internal/NativeClientState.php @@ -0,0 +1,47 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient\Internal; + +/** + * Internal representation of the native client's state. + * + * @author Alexander M. Turek + * + * @internal + */ +final class NativeClientState extends ClientState +{ + /** @var int */ + public $id; + /** @var int */ + public $maxHostConnections = \PHP_INT_MAX; + /** @var int */ + public $responseCount = 0; + /** @var string[] */ + public $dnsCache = []; + /** @var bool */ + public $sleep = false; + /** @var int[] */ + public $hosts = []; + + public function __construct() + { + $this->id = random_int(\PHP_INT_MIN, \PHP_INT_MAX); + } + + public function reset() + { + $this->responseCount = 0; + $this->dnsCache = []; + $this->hosts = []; + } +} diff --git a/sites/all/libraries/vendor/symfony/http-client/Internal/PushedResponse.php b/sites/all/libraries/vendor/symfony/http-client/Internal/PushedResponse.php new file mode 100644 index 0000000000..08fca60dcb --- /dev/null +++ b/sites/all/libraries/vendor/symfony/http-client/Internal/PushedResponse.php @@ -0,0 +1,41 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient\Internal; + +use Symfony\Component\HttpClient\Response\CurlResponse; + +/** + * A pushed response with its request headers. + * + * @author Alexander M. Turek + * + * @internal + */ +final class PushedResponse +{ + public $response; + + /** @var string[] */ + public $requestHeaders; + + public $parentOptions = []; + + public $handle; + + public function __construct(CurlResponse $response, array $requestHeaders, array $parentOptions, $handle) + { + $this->response = $response; + $this->requestHeaders = $requestHeaders; + $this->parentOptions = $parentOptions; + $this->handle = $handle; + } +} diff --git a/sites/all/libraries/vendor/symfony/http-client/LICENSE b/sites/all/libraries/vendor/symfony/http-client/LICENSE new file mode 100644 index 0000000000..74cdc2dbf6 --- /dev/null +++ b/sites/all/libraries/vendor/symfony/http-client/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2018-2022 Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/sites/all/libraries/vendor/symfony/http-client/MockHttpClient.php b/sites/all/libraries/vendor/symfony/http-client/MockHttpClient.php new file mode 100644 index 0000000000..fecba0ee56 --- /dev/null +++ b/sites/all/libraries/vendor/symfony/http-client/MockHttpClient.php @@ -0,0 +1,124 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient; + +use Symfony\Component\HttpClient\Exception\TransportException; +use Symfony\Component\HttpClient\Response\MockResponse; +use Symfony\Component\HttpClient\Response\ResponseStream; +use Symfony\Contracts\HttpClient\HttpClientInterface; +use Symfony\Contracts\HttpClient\ResponseInterface; +use Symfony\Contracts\HttpClient\ResponseStreamInterface; +use Symfony\Contracts\Service\ResetInterface; + +/** + * A test-friendly HttpClient that doesn't make actual HTTP requests. + * + * @author Nicolas Grekas + */ +class MockHttpClient implements HttpClientInterface, ResetInterface +{ + use HttpClientTrait; + + private $responseFactory; + private $requestsCount = 0; + private $defaultOptions = []; + + /** + * @param callable|callable[]|ResponseInterface|ResponseInterface[]|iterable|null $responseFactory + */ + public function __construct($responseFactory = null, ?string $baseUri = 'https://example.com') + { + $this->setResponseFactory($responseFactory); + $this->defaultOptions['base_uri'] = $baseUri; + } + + /** + * @param callable|callable[]|ResponseInterface|ResponseInterface[]|iterable|null $responseFactory + */ + public function setResponseFactory($responseFactory): void + { + if ($responseFactory instanceof ResponseInterface) { + $responseFactory = [$responseFactory]; + } + + if (!$responseFactory instanceof \Iterator && null !== $responseFactory && !\is_callable($responseFactory)) { + $responseFactory = (static function () use ($responseFactory) { + yield from $responseFactory; + })(); + } + + $this->responseFactory = $responseFactory; + } + + /** + * {@inheritdoc} + */ + public function request(string $method, string $url, array $options = []): ResponseInterface + { + [$url, $options] = $this->prepareRequest($method, $url, $options, $this->defaultOptions, true); + $url = implode('', $url); + + if (null === $this->responseFactory) { + $response = new MockResponse(); + } elseif (\is_callable($this->responseFactory)) { + $response = ($this->responseFactory)($method, $url, $options); + } elseif (!$this->responseFactory->valid()) { + throw new TransportException('The response factory iterator passed to MockHttpClient is empty.'); + } else { + $responseFactory = $this->responseFactory->current(); + $response = \is_callable($responseFactory) ? $responseFactory($method, $url, $options) : $responseFactory; + $this->responseFactory->next(); + } + ++$this->requestsCount; + + if (!$response instanceof ResponseInterface) { + throw new TransportException(sprintf('The response factory passed to MockHttpClient must return/yield an instance of ResponseInterface, "%s" given.', \is_object($response) ? \get_class($response) : \gettype($response))); + } + + return MockResponse::fromRequest($method, $url, $options, $response); + } + + /** + * {@inheritdoc} + */ + public function stream($responses, float $timeout = null): ResponseStreamInterface + { + if ($responses instanceof ResponseInterface) { + $responses = [$responses]; + } elseif (!is_iterable($responses)) { + throw new \TypeError(sprintf('"%s()" expects parameter 1 to be an iterable of MockResponse objects, "%s" given.', __METHOD__, get_debug_type($responses))); + } + + return new ResponseStream(MockResponse::stream($responses, $timeout)); + } + + public function getRequestsCount(): int + { + return $this->requestsCount; + } + + /** + * {@inheritdoc} + */ + public function withOptions(array $options): self + { + $clone = clone $this; + $clone->defaultOptions = self::mergeDefaultOptions($options, $this->defaultOptions, true); + + return $clone; + } + + public function reset() + { + $this->requestsCount = 0; + } +} diff --git a/sites/all/libraries/vendor/symfony/http-client/NativeHttpClient.php b/sites/all/libraries/vendor/symfony/http-client/NativeHttpClient.php new file mode 100644 index 0000000000..63fcc1ca91 --- /dev/null +++ b/sites/all/libraries/vendor/symfony/http-client/NativeHttpClient.php @@ -0,0 +1,468 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient; + +use Psr\Log\LoggerAwareInterface; +use Psr\Log\LoggerAwareTrait; +use Symfony\Component\HttpClient\Exception\InvalidArgumentException; +use Symfony\Component\HttpClient\Exception\TransportException; +use Symfony\Component\HttpClient\Internal\NativeClientState; +use Symfony\Component\HttpClient\Response\NativeResponse; +use Symfony\Component\HttpClient\Response\ResponseStream; +use Symfony\Contracts\HttpClient\HttpClientInterface; +use Symfony\Contracts\HttpClient\ResponseInterface; +use Symfony\Contracts\HttpClient\ResponseStreamInterface; +use Symfony\Contracts\Service\ResetInterface; + +/** + * A portable implementation of the HttpClientInterface contracts based on PHP stream wrappers. + * + * PHP stream wrappers are able to fetch response bodies concurrently, + * but each request is opened synchronously. + * + * @author Nicolas Grekas + */ +final class NativeHttpClient implements HttpClientInterface, LoggerAwareInterface, ResetInterface +{ + use HttpClientTrait; + use LoggerAwareTrait; + + private $defaultOptions = self::OPTIONS_DEFAULTS; + private static $emptyDefaults = self::OPTIONS_DEFAULTS; + + /** @var NativeClientState */ + private $multi; + + /** + * @param array $defaultOptions Default request's options + * @param int $maxHostConnections The maximum number of connections to open + * + * @see HttpClientInterface::OPTIONS_DEFAULTS for available options + */ + public function __construct(array $defaultOptions = [], int $maxHostConnections = 6) + { + $this->defaultOptions['buffer'] = $this->defaultOptions['buffer'] ?? \Closure::fromCallable([__CLASS__, 'shouldBuffer']); + + if ($defaultOptions) { + [, $this->defaultOptions] = self::prepareRequest(null, null, $defaultOptions, $this->defaultOptions); + } + + $this->multi = new NativeClientState(); + $this->multi->maxHostConnections = 0 < $maxHostConnections ? $maxHostConnections : \PHP_INT_MAX; + } + + /** + * @see HttpClientInterface::OPTIONS_DEFAULTS for available options + * + * {@inheritdoc} + */ + public function request(string $method, string $url, array $options = []): ResponseInterface + { + [$url, $options] = self::prepareRequest($method, $url, $options, $this->defaultOptions); + + if ($options['bindto']) { + if (file_exists($options['bindto'])) { + throw new TransportException(__CLASS__.' cannot bind to local Unix sockets, use e.g. CurlHttpClient instead.'); + } + if (str_starts_with($options['bindto'], 'if!')) { + throw new TransportException(__CLASS__.' cannot bind to network interfaces, use e.g. CurlHttpClient instead.'); + } + if (str_starts_with($options['bindto'], 'host!')) { + $options['bindto'] = substr($options['bindto'], 5); + } + } + + $hasContentLength = isset($options['normalized_headers']['content-length']); + $hasBody = '' !== $options['body'] || 'POST' === $method || $hasContentLength; + + $options['body'] = self::getBodyAsString($options['body']); + + if ('chunked' === substr($options['normalized_headers']['transfer-encoding'][0] ?? '', \strlen('Transfer-Encoding: '))) { + unset($options['normalized_headers']['transfer-encoding']); + $options['headers'] = array_merge(...array_values($options['normalized_headers'])); + $options['body'] = self::dechunk($options['body']); + } + if ('' === $options['body'] && $hasBody && !$hasContentLength) { + $options['headers'][] = 'Content-Length: 0'; + } + if ($hasBody && !isset($options['normalized_headers']['content-type'])) { + $options['headers'][] = 'Content-Type: application/x-www-form-urlencoded'; + } + + if (\extension_loaded('zlib') && !isset($options['normalized_headers']['accept-encoding'])) { + // gzip is the most widely available algo, no need to deal with deflate + $options['headers'][] = 'Accept-Encoding: gzip'; + } + + if ($options['peer_fingerprint']) { + if (isset($options['peer_fingerprint']['pin-sha256']) && 1 === \count($options['peer_fingerprint'])) { + throw new TransportException(__CLASS__.' cannot verify "pin-sha256" fingerprints, please provide a "sha256" one.'); + } + + unset($options['peer_fingerprint']['pin-sha256']); + } + + $info = [ + 'response_headers' => [], + 'url' => $url, + 'error' => null, + 'canceled' => false, + 'http_method' => $method, + 'http_code' => 0, + 'redirect_count' => 0, + 'start_time' => 0.0, + 'connect_time' => 0.0, + 'redirect_time' => 0.0, + 'pretransfer_time' => 0.0, + 'starttransfer_time' => 0.0, + 'total_time' => 0.0, + 'namelookup_time' => 0.0, + 'size_upload' => 0, + 'size_download' => 0, + 'size_body' => \strlen($options['body']), + 'primary_ip' => '', + 'primary_port' => 'http:' === $url['scheme'] ? 80 : 443, + 'debug' => \extension_loaded('curl') ? '' : "* Enable the curl extension for better performance\n", + ]; + + if ($onProgress = $options['on_progress']) { + // Memoize the last progress to ease calling the callback periodically when no network transfer happens + $lastProgress = [0, 0]; + $maxDuration = 0 < $options['max_duration'] ? $options['max_duration'] : \INF; + $onProgress = static function (...$progress) use ($onProgress, &$lastProgress, &$info, $maxDuration) { + if ($info['total_time'] >= $maxDuration) { + throw new TransportException(sprintf('Max duration was reached for "%s".', implode('', $info['url']))); + } + + $progressInfo = $info; + $progressInfo['url'] = implode('', $info['url']); + unset($progressInfo['size_body']); + + if ($progress && -1 === $progress[0]) { + // Response completed + $lastProgress[0] = max($lastProgress); + } else { + $lastProgress = $progress ?: $lastProgress; + } + + $onProgress($lastProgress[0], $lastProgress[1], $progressInfo); + }; + } elseif (0 < $options['max_duration']) { + $maxDuration = $options['max_duration']; + $onProgress = static function () use (&$info, $maxDuration): void { + if ($info['total_time'] >= $maxDuration) { + throw new TransportException(sprintf('Max duration was reached for "%s".', implode('', $info['url']))); + } + }; + } + + // Always register a notification callback to compute live stats about the response + $notification = static function (int $code, int $severity, ?string $msg, int $msgCode, int $dlNow, int $dlSize) use ($onProgress, &$info) { + $info['total_time'] = microtime(true) - $info['start_time']; + + if (\STREAM_NOTIFY_PROGRESS === $code) { + $info['starttransfer_time'] = $info['starttransfer_time'] ?: $info['total_time']; + $info['size_upload'] += $dlNow ? 0 : $info['size_body']; + $info['size_download'] = $dlNow; + } elseif (\STREAM_NOTIFY_CONNECT === $code) { + $info['connect_time'] = $info['total_time']; + $info['debug'] .= $info['request_header']; + unset($info['request_header']); + } else { + return; + } + + if ($onProgress) { + $onProgress($dlNow, $dlSize); + } + }; + + if ($options['resolve']) { + $this->multi->dnsCache = $options['resolve'] + $this->multi->dnsCache; + } + + $this->logger && $this->logger->info(sprintf('Request: "%s %s"', $method, implode('', $url))); + + if (!isset($options['normalized_headers']['user-agent'])) { + $options['headers'][] = 'User-Agent: Symfony HttpClient/Native'; + } + + if (0 < $options['max_duration']) { + $options['timeout'] = min($options['max_duration'], $options['timeout']); + } + + $bindto = $options['bindto']; + if (!$bindto && (70322 === \PHP_VERSION_ID || 70410 === \PHP_VERSION_ID)) { + $bindto = '0:0'; + } + + $context = [ + 'http' => [ + 'protocol_version' => min($options['http_version'] ?: '1.1', '1.1'), + 'method' => $method, + 'content' => $options['body'], + 'ignore_errors' => true, + 'curl_verify_ssl_peer' => $options['verify_peer'], + 'curl_verify_ssl_host' => $options['verify_host'], + 'auto_decode' => false, // Disable dechunk filter, it's incompatible with stream_select() + 'timeout' => $options['timeout'], + 'follow_location' => false, // We follow redirects ourselves - the native logic is too limited + ], + 'ssl' => array_filter([ + 'verify_peer' => $options['verify_peer'], + 'verify_peer_name' => $options['verify_host'], + 'cafile' => $options['cafile'], + 'capath' => $options['capath'], + 'local_cert' => $options['local_cert'], + 'local_pk' => $options['local_pk'], + 'passphrase' => $options['passphrase'], + 'ciphers' => $options['ciphers'], + 'peer_fingerprint' => $options['peer_fingerprint'], + 'capture_peer_cert_chain' => $options['capture_peer_cert_chain'], + 'allow_self_signed' => (bool) $options['peer_fingerprint'], + 'SNI_enabled' => true, + 'disable_compression' => true, + ], static function ($v) { return null !== $v; }), + 'socket' => [ + 'bindto' => $bindto, + 'tcp_nodelay' => true, + ], + ]; + + $context = stream_context_create($context, ['notification' => $notification]); + + $resolver = static function ($multi) use ($context, $options, $url, &$info, $onProgress) { + [$host, $port] = self::parseHostPort($url, $info); + + if (!isset($options['normalized_headers']['host'])) { + $options['headers'][] = 'Host: '.$host.$port; + } + + $proxy = self::getProxy($options['proxy'], $url, $options['no_proxy']); + + if (!self::configureHeadersAndProxy($context, $host, $options['headers'], $proxy, 'https:' === $url['scheme'])) { + $ip = self::dnsResolve($host, $multi, $info, $onProgress); + $url['authority'] = substr_replace($url['authority'], $ip, -\strlen($host) - \strlen($port), \strlen($host)); + } + + return [self::createRedirectResolver($options, $host, $proxy, $info, $onProgress), implode('', $url)]; + }; + + return new NativeResponse($this->multi, $context, implode('', $url), $options, $info, $resolver, $onProgress, $this->logger); + } + + /** + * {@inheritdoc} + */ + public function stream($responses, float $timeout = null): ResponseStreamInterface + { + if ($responses instanceof NativeResponse) { + $responses = [$responses]; + } elseif (!is_iterable($responses)) { + throw new \TypeError(sprintf('"%s()" expects parameter 1 to be an iterable of NativeResponse objects, "%s" given.', __METHOD__, get_debug_type($responses))); + } + + return new ResponseStream(NativeResponse::stream($responses, $timeout)); + } + + public function reset() + { + $this->multi->reset(); + } + + private static function getBodyAsString($body): string + { + if (\is_resource($body)) { + return stream_get_contents($body); + } + + if (!$body instanceof \Closure) { + return $body; + } + + $result = ''; + + while ('' !== $data = $body(self::$CHUNK_SIZE)) { + if (!\is_string($data)) { + throw new TransportException(sprintf('Return value of the "body" option callback must be string, "%s" returned.', get_debug_type($data))); + } + + $result .= $data; + } + + return $result; + } + + /** + * Extracts the host and the port from the URL. + */ + private static function parseHostPort(array $url, array &$info): array + { + if ($port = parse_url($url['authority'], \PHP_URL_PORT) ?: '') { + $info['primary_port'] = $port; + $port = ':'.$port; + } else { + $info['primary_port'] = 'http:' === $url['scheme'] ? 80 : 443; + } + + return [parse_url($url['authority'], \PHP_URL_HOST), $port]; + } + + /** + * Resolves the IP of the host using the local DNS cache if possible. + */ + private static function dnsResolve($host, NativeClientState $multi, array &$info, ?\Closure $onProgress): string + { + if (null === $ip = $multi->dnsCache[$host] ?? null) { + $info['debug'] .= "* Hostname was NOT found in DNS cache\n"; + $now = microtime(true); + + if (!$ip = gethostbynamel($host)) { + throw new TransportException(sprintf('Could not resolve host "%s".', $host)); + } + + $info['namelookup_time'] = microtime(true) - ($info['start_time'] ?: $now); + $multi->dnsCache[$host] = $ip = $ip[0]; + $info['debug'] .= "* Added {$host}:0:{$ip} to DNS cache\n"; + } else { + $info['debug'] .= "* Hostname was found in DNS cache\n"; + } + + $info['primary_ip'] = $ip; + + if ($onProgress) { + // Notify DNS resolution + $onProgress(); + } + + return $ip; + } + + /** + * Handles redirects - the native logic is too buggy to be used. + */ + private static function createRedirectResolver(array $options, string $host, ?array $proxy, array &$info, ?\Closure $onProgress): \Closure + { + $redirectHeaders = []; + if (0 < $maxRedirects = $options['max_redirects']) { + $redirectHeaders = ['host' => $host]; + $redirectHeaders['with_auth'] = $redirectHeaders['no_auth'] = array_filter($options['headers'], static function ($h) { + return 0 !== stripos($h, 'Host:'); + }); + + if (isset($options['normalized_headers']['authorization']) || isset($options['normalized_headers']['cookie'])) { + $redirectHeaders['no_auth'] = array_filter($redirectHeaders['no_auth'], static function ($h) { + return 0 !== stripos($h, 'Authorization:') && 0 !== stripos($h, 'Cookie:'); + }); + } + } + + return static function (NativeClientState $multi, ?string $location, $context) use (&$redirectHeaders, $proxy, &$info, $maxRedirects, $onProgress): ?string { + if (null === $location || $info['http_code'] < 300 || 400 <= $info['http_code']) { + $info['redirect_url'] = null; + + return null; + } + + try { + $url = self::parseUrl($location); + } catch (InvalidArgumentException $e) { + $info['redirect_url'] = null; + + return null; + } + + $url = self::resolveUrl($url, $info['url']); + $info['redirect_url'] = implode('', $url); + + if ($info['redirect_count'] >= $maxRedirects) { + return null; + } + + $info['url'] = $url; + ++$info['redirect_count']; + $info['redirect_time'] = microtime(true) - $info['start_time']; + + // Do like curl and browsers: turn POST to GET on 301, 302 and 303 + if (\in_array($info['http_code'], [301, 302, 303], true)) { + $options = stream_context_get_options($context)['http']; + + if ('POST' === $options['method'] || 303 === $info['http_code']) { + $info['http_method'] = $options['method'] = 'HEAD' === $options['method'] ? 'HEAD' : 'GET'; + $options['content'] = ''; + $filterContentHeaders = static function ($h) { + return 0 !== stripos($h, 'Content-Length:') && 0 !== stripos($h, 'Content-Type:') && 0 !== stripos($h, 'Transfer-Encoding:'); + }; + $options['header'] = array_filter($options['header'], $filterContentHeaders); + $redirectHeaders['no_auth'] = array_filter($redirectHeaders['no_auth'], $filterContentHeaders); + $redirectHeaders['with_auth'] = array_filter($redirectHeaders['with_auth'], $filterContentHeaders); + + stream_context_set_option($context, ['http' => $options]); + } + } + + [$host, $port] = self::parseHostPort($url, $info); + + if (false !== (parse_url($location, \PHP_URL_HOST) ?? false)) { + // Authorization and Cookie headers MUST NOT follow except for the initial host name + $requestHeaders = $redirectHeaders['host'] === $host ? $redirectHeaders['with_auth'] : $redirectHeaders['no_auth']; + $requestHeaders[] = 'Host: '.$host.$port; + $dnsResolve = !self::configureHeadersAndProxy($context, $host, $requestHeaders, $proxy, 'https:' === $url['scheme']); + } else { + $dnsResolve = isset(stream_context_get_options($context)['ssl']['peer_name']); + } + + if ($dnsResolve) { + $ip = self::dnsResolve($host, $multi, $info, $onProgress); + $url['authority'] = substr_replace($url['authority'], $ip, -\strlen($host) - \strlen($port), \strlen($host)); + } + + return implode('', $url); + }; + } + + private static function configureHeadersAndProxy($context, string $host, array $requestHeaders, ?array $proxy, bool $isSsl): bool + { + if (null === $proxy) { + stream_context_set_option($context, 'http', 'header', $requestHeaders); + stream_context_set_option($context, 'ssl', 'peer_name', $host); + + return false; + } + + // Matching "no_proxy" should follow the behavior of curl + + foreach ($proxy['no_proxy'] as $rule) { + $dotRule = '.'.ltrim($rule, '.'); + + if ('*' === $rule || $host === $rule || str_ends_with($host, $dotRule)) { + stream_context_set_option($context, 'http', 'proxy', null); + stream_context_set_option($context, 'http', 'request_fulluri', false); + stream_context_set_option($context, 'http', 'header', $requestHeaders); + stream_context_set_option($context, 'ssl', 'peer_name', $host); + + return false; + } + } + + if (null !== $proxy['auth']) { + $requestHeaders[] = 'Proxy-Authorization: '.$proxy['auth']; + } + + stream_context_set_option($context, 'http', 'proxy', $proxy['url']); + stream_context_set_option($context, 'http', 'request_fulluri', !$isSsl); + stream_context_set_option($context, 'http', 'header', $requestHeaders); + stream_context_set_option($context, 'ssl', 'peer_name', null); + + return true; + } +} diff --git a/sites/all/libraries/vendor/symfony/http-client/NoPrivateNetworkHttpClient.php b/sites/all/libraries/vendor/symfony/http-client/NoPrivateNetworkHttpClient.php new file mode 100644 index 0000000000..911cce9da4 --- /dev/null +++ b/sites/all/libraries/vendor/symfony/http-client/NoPrivateNetworkHttpClient.php @@ -0,0 +1,132 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient; + +use Psr\Log\LoggerAwareInterface; +use Psr\Log\LoggerInterface; +use Symfony\Component\HttpClient\Exception\InvalidArgumentException; +use Symfony\Component\HttpClient\Exception\TransportException; +use Symfony\Component\HttpFoundation\IpUtils; +use Symfony\Contracts\HttpClient\HttpClientInterface; +use Symfony\Contracts\HttpClient\ResponseInterface; +use Symfony\Contracts\HttpClient\ResponseStreamInterface; +use Symfony\Contracts\Service\ResetInterface; + +/** + * Decorator that blocks requests to private networks by default. + * + * @author Hallison Boaventura + */ +final class NoPrivateNetworkHttpClient implements HttpClientInterface, LoggerAwareInterface, ResetInterface +{ + use HttpClientTrait; + + private const PRIVATE_SUBNETS = [ + '127.0.0.0/8', + '10.0.0.0/8', + '192.168.0.0/16', + '172.16.0.0/12', + '169.254.0.0/16', + '0.0.0.0/8', + '240.0.0.0/4', + '::1/128', + 'fc00::/7', + 'fe80::/10', + '::ffff:0:0/96', + '::/128', + ]; + + private $client; + private $subnets; + + /** + * @param string|array|null $subnets String or array of subnets using CIDR notation that will be used by IpUtils. + * If null is passed, the standard private subnets will be used. + */ + public function __construct(HttpClientInterface $client, $subnets = null) + { + if (!(\is_array($subnets) || \is_string($subnets) || null === $subnets)) { + throw new \TypeError(sprintf('Argument 2 passed to "%s()" must be of the type array, string or null. "%s" given.', __METHOD__, get_debug_type($subnets))); + } + + if (!class_exists(IpUtils::class)) { + throw new \LogicException(sprintf('You cannot use "%s" if the HttpFoundation component is not installed. Try running "composer require symfony/http-foundation".', __CLASS__)); + } + + $this->client = $client; + $this->subnets = $subnets; + } + + /** + * {@inheritdoc} + */ + public function request(string $method, string $url, array $options = []): ResponseInterface + { + $onProgress = $options['on_progress'] ?? null; + if (null !== $onProgress && !\is_callable($onProgress)) { + throw new InvalidArgumentException(sprintf('Option "on_progress" must be callable, "%s" given.', get_debug_type($onProgress))); + } + + $subnets = $this->subnets; + $lastPrimaryIp = ''; + + $options['on_progress'] = function (int $dlNow, int $dlSize, array $info) use ($onProgress, $subnets, &$lastPrimaryIp): void { + if ($info['primary_ip'] !== $lastPrimaryIp) { + if ($info['primary_ip'] && IpUtils::checkIp($info['primary_ip'], $subnets ?? self::PRIVATE_SUBNETS)) { + throw new TransportException(sprintf('IP "%s" is blocked for "%s".', $info['primary_ip'], $info['url'])); + } + + $lastPrimaryIp = $info['primary_ip']; + } + + null !== $onProgress && $onProgress($dlNow, $dlSize, $info); + }; + + return $this->client->request($method, $url, $options); + } + + /** + * {@inheritdoc} + */ + public function stream($responses, float $timeout = null): ResponseStreamInterface + { + return $this->client->stream($responses, $timeout); + } + + /** + * {@inheritdoc} + */ + public function setLogger(LoggerInterface $logger): void + { + if ($this->client instanceof LoggerAwareInterface) { + $this->client->setLogger($logger); + } + } + + /** + * {@inheritdoc} + */ + public function withOptions(array $options): self + { + $clone = clone $this; + $clone->client = $this->client->withOptions($options); + + return $clone; + } + + public function reset() + { + if ($this->client instanceof ResetInterface) { + $this->client->reset(); + } + } +} diff --git a/sites/all/libraries/vendor/symfony/http-client/Psr18Client.php b/sites/all/libraries/vendor/symfony/http-client/Psr18Client.php new file mode 100644 index 0000000000..c62df84ecd --- /dev/null +++ b/sites/all/libraries/vendor/symfony/http-client/Psr18Client.php @@ -0,0 +1,243 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient; + +use Http\Discovery\Exception\NotFoundException; +use Http\Discovery\Psr17FactoryDiscovery; +use Nyholm\Psr7\Factory\Psr17Factory; +use Nyholm\Psr7\Request; +use Nyholm\Psr7\Uri; +use Psr\Http\Client\ClientInterface; +use Psr\Http\Client\NetworkExceptionInterface; +use Psr\Http\Client\RequestExceptionInterface; +use Psr\Http\Message\RequestFactoryInterface; +use Psr\Http\Message\RequestInterface; +use Psr\Http\Message\ResponseFactoryInterface; +use Psr\Http\Message\ResponseInterface; +use Psr\Http\Message\StreamFactoryInterface; +use Psr\Http\Message\StreamInterface; +use Psr\Http\Message\UriFactoryInterface; +use Psr\Http\Message\UriInterface; +use Symfony\Component\HttpClient\Response\StreamableInterface; +use Symfony\Component\HttpClient\Response\StreamWrapper; +use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface; +use Symfony\Contracts\HttpClient\HttpClientInterface; +use Symfony\Contracts\Service\ResetInterface; + +if (!interface_exists(RequestFactoryInterface::class)) { + throw new \LogicException('You cannot use the "Symfony\Component\HttpClient\Psr18Client" as the "psr/http-factory" package is not installed. Try running "composer require nyholm/psr7".'); +} + +if (!interface_exists(ClientInterface::class)) { + throw new \LogicException('You cannot use the "Symfony\Component\HttpClient\Psr18Client" as the "psr/http-client" package is not installed. Try running "composer require psr/http-client".'); +} + +/** + * An adapter to turn a Symfony HttpClientInterface into a PSR-18 ClientInterface. + * + * Run "composer require psr/http-client" to install the base ClientInterface. Run + * "composer require nyholm/psr7" to install an efficient implementation of response + * and stream factories with flex-provided autowiring aliases. + * + * @author Nicolas Grekas + */ +final class Psr18Client implements ClientInterface, RequestFactoryInterface, StreamFactoryInterface, UriFactoryInterface, ResetInterface +{ + private $client; + private $responseFactory; + private $streamFactory; + + public function __construct(HttpClientInterface $client = null, ResponseFactoryInterface $responseFactory = null, StreamFactoryInterface $streamFactory = null) + { + $this->client = $client ?? HttpClient::create(); + $this->responseFactory = $responseFactory; + $this->streamFactory = $streamFactory ?? ($responseFactory instanceof StreamFactoryInterface ? $responseFactory : null); + + if (null !== $this->responseFactory && null !== $this->streamFactory) { + return; + } + + if (!class_exists(Psr17Factory::class) && !class_exists(Psr17FactoryDiscovery::class)) { + throw new \LogicException('You cannot use the "Symfony\Component\HttpClient\Psr18Client" as no PSR-17 factories have been provided. Try running "composer require nyholm/psr7".'); + } + + try { + $psr17Factory = class_exists(Psr17Factory::class, false) ? new Psr17Factory() : null; + $this->responseFactory = $this->responseFactory ?? $psr17Factory ?? Psr17FactoryDiscovery::findResponseFactory(); + $this->streamFactory = $this->streamFactory ?? $psr17Factory ?? Psr17FactoryDiscovery::findStreamFactory(); + } catch (NotFoundException $e) { + throw new \LogicException('You cannot use the "Symfony\Component\HttpClient\HttplugClient" as no PSR-17 factories have been found. Try running "composer require nyholm/psr7".', 0, $e); + } + } + + /** + * {@inheritdoc} + */ + public function sendRequest(RequestInterface $request): ResponseInterface + { + try { + $body = $request->getBody(); + + if ($body->isSeekable()) { + $body->seek(0); + } + + $response = $this->client->request($request->getMethod(), (string) $request->getUri(), [ + 'headers' => $request->getHeaders(), + 'body' => $body->getContents(), + 'http_version' => '1.0' === $request->getProtocolVersion() ? '1.0' : null, + ]); + + $psrResponse = $this->responseFactory->createResponse($response->getStatusCode()); + + foreach ($response->getHeaders(false) as $name => $values) { + foreach ($values as $value) { + try { + $psrResponse = $psrResponse->withAddedHeader($name, $value); + } catch (\InvalidArgumentException $e) { + // ignore invalid header + } + } + } + + $body = $response instanceof StreamableInterface ? $response->toStream(false) : StreamWrapper::createResource($response, $this->client); + $body = $this->streamFactory->createStreamFromResource($body); + + if ($body->isSeekable()) { + $body->seek(0); + } + + return $psrResponse->withBody($body); + } catch (TransportExceptionInterface $e) { + if ($e instanceof \InvalidArgumentException) { + throw new Psr18RequestException($e, $request); + } + + throw new Psr18NetworkException($e, $request); + } + } + + /** + * {@inheritdoc} + */ + public function createRequest(string $method, $uri): RequestInterface + { + if ($this->responseFactory instanceof RequestFactoryInterface) { + return $this->responseFactory->createRequest($method, $uri); + } + + if (class_exists(Request::class)) { + return new Request($method, $uri); + } + + if (class_exists(Psr17FactoryDiscovery::class)) { + return Psr17FactoryDiscovery::findRequestFactory()->createRequest($method, $uri); + } + + throw new \LogicException(sprintf('You cannot use "%s()" as the "nyholm/psr7" package is not installed. Try running "composer require nyholm/psr7".', __METHOD__)); + } + + /** + * {@inheritdoc} + */ + public function createStream(string $content = ''): StreamInterface + { + $stream = $this->streamFactory->createStream($content); + + if ($stream->isSeekable()) { + $stream->seek(0); + } + + return $stream; + } + + /** + * {@inheritdoc} + */ + public function createStreamFromFile(string $filename, string $mode = 'r'): StreamInterface + { + return $this->streamFactory->createStreamFromFile($filename, $mode); + } + + /** + * {@inheritdoc} + */ + public function createStreamFromResource($resource): StreamInterface + { + return $this->streamFactory->createStreamFromResource($resource); + } + + /** + * {@inheritdoc} + */ + public function createUri(string $uri = ''): UriInterface + { + if ($this->responseFactory instanceof UriFactoryInterface) { + return $this->responseFactory->createUri($uri); + } + + if (class_exists(Uri::class)) { + return new Uri($uri); + } + + if (class_exists(Psr17FactoryDiscovery::class)) { + return Psr17FactoryDiscovery::findUrlFactory()->createUri($uri); + } + + throw new \LogicException(sprintf('You cannot use "%s()" as the "nyholm/psr7" package is not installed. Try running "composer require nyholm/psr7".', __METHOD__)); + } + + public function reset() + { + if ($this->client instanceof ResetInterface) { + $this->client->reset(); + } + } +} + +/** + * @internal + */ +class Psr18NetworkException extends \RuntimeException implements NetworkExceptionInterface +{ + private $request; + + public function __construct(TransportExceptionInterface $e, RequestInterface $request) + { + parent::__construct($e->getMessage(), 0, $e); + $this->request = $request; + } + + public function getRequest(): RequestInterface + { + return $this->request; + } +} + +/** + * @internal + */ +class Psr18RequestException extends \InvalidArgumentException implements RequestExceptionInterface +{ + private $request; + + public function __construct(TransportExceptionInterface $e, RequestInterface $request) + { + parent::__construct($e->getMessage(), 0, $e); + $this->request = $request; + } + + public function getRequest(): RequestInterface + { + return $this->request; + } +} diff --git a/sites/all/libraries/vendor/symfony/http-client/README.md b/sites/all/libraries/vendor/symfony/http-client/README.md new file mode 100644 index 0000000000..0c55ccc118 --- /dev/null +++ b/sites/all/libraries/vendor/symfony/http-client/README.md @@ -0,0 +1,27 @@ +HttpClient component +==================== + +The HttpClient component provides powerful methods to fetch HTTP resources synchronously or asynchronously. + +Sponsor +------- + +The Httpclient component for Symfony 5.4/6.0 is [backed][1] by [Klaxoon][2]. + +Klaxoon is a platform that empowers organizations to run effective and +productive workshops easily in a hybrid environment. Anytime, Anywhere. + +Help Symfony by [sponsoring][3] its development! + +Resources +--------- + + * [Documentation](https://symfony.com/doc/current/components/http_client.html) + * [Contributing](https://symfony.com/doc/current/contributing/index.html) + * [Report issues](https://github.com/symfony/symfony/issues) and + [send Pull Requests](https://github.com/symfony/symfony/pulls) + in the [main Symfony repository](https://github.com/symfony/symfony) + +[1]: https://symfony.com/backers +[2]: https://klaxoon.com +[3]: https://symfony.com/sponsor diff --git a/sites/all/libraries/vendor/symfony/http-client/Response/AmpResponse.php b/sites/all/libraries/vendor/symfony/http-client/Response/AmpResponse.php new file mode 100644 index 0000000000..6d0ce6e33e --- /dev/null +++ b/sites/all/libraries/vendor/symfony/http-client/Response/AmpResponse.php @@ -0,0 +1,461 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient\Response; + +use Amp\ByteStream\StreamException; +use Amp\CancellationTokenSource; +use Amp\Coroutine; +use Amp\Deferred; +use Amp\Http\Client\HttpException; +use Amp\Http\Client\Request; +use Amp\Http\Client\Response; +use Amp\Loop; +use Amp\Promise; +use Amp\Success; +use Psr\Log\LoggerInterface; +use Symfony\Component\HttpClient\Chunk\FirstChunk; +use Symfony\Component\HttpClient\Chunk\InformationalChunk; +use Symfony\Component\HttpClient\Exception\InvalidArgumentException; +use Symfony\Component\HttpClient\Exception\TransportException; +use Symfony\Component\HttpClient\HttpClientTrait; +use Symfony\Component\HttpClient\Internal\AmpBody; +use Symfony\Component\HttpClient\Internal\AmpClientState; +use Symfony\Component\HttpClient\Internal\Canary; +use Symfony\Component\HttpClient\Internal\ClientState; +use Symfony\Contracts\HttpClient\ResponseInterface; + +/** + * @author Nicolas Grekas + * + * @internal + */ +final class AmpResponse implements ResponseInterface, StreamableInterface +{ + use CommonResponseTrait; + use TransportResponseTrait; + + private static $nextId = 'a'; + + private $multi; + private $options; + private $canceller; + private $onProgress; + + private static $delay; + + /** + * @internal + */ + public function __construct(AmpClientState $multi, Request $request, array $options, ?LoggerInterface $logger) + { + $this->multi = $multi; + $this->options = &$options; + $this->logger = $logger; + $this->timeout = $options['timeout']; + $this->shouldBuffer = $options['buffer']; + + if ($this->inflate = \extension_loaded('zlib') && !$request->hasHeader('accept-encoding')) { + $request->setHeader('Accept-Encoding', 'gzip'); + } + + $this->initializer = static function (self $response) { + return null !== $response->options; + }; + + $info = &$this->info; + $headers = &$this->headers; + $canceller = $this->canceller = new CancellationTokenSource(); + $handle = &$this->handle; + + $info['url'] = (string) $request->getUri(); + $info['http_method'] = $request->getMethod(); + $info['start_time'] = null; + $info['redirect_url'] = null; + $info['redirect_time'] = 0.0; + $info['redirect_count'] = 0; + $info['size_upload'] = 0.0; + $info['size_download'] = 0.0; + $info['upload_content_length'] = -1.0; + $info['download_content_length'] = -1.0; + $info['user_data'] = $options['user_data']; + $info['max_duration'] = $options['max_duration']; + $info['debug'] = ''; + + $onProgress = $options['on_progress'] ?? static function () {}; + $onProgress = $this->onProgress = static function () use (&$info, $onProgress) { + $info['total_time'] = microtime(true) - $info['start_time']; + $onProgress((int) $info['size_download'], ((int) (1 + $info['download_content_length']) ?: 1) - 1, (array) $info); + }; + + $pauseDeferred = new Deferred(); + $pause = new Success(); + + $throttleWatcher = null; + + $this->id = $id = self::$nextId++; + Loop::defer(static function () use ($request, $multi, &$id, &$info, &$headers, $canceller, &$options, $onProgress, &$handle, $logger, &$pause) { + return new Coroutine(self::generateResponse($request, $multi, $id, $info, $headers, $canceller, $options, $onProgress, $handle, $logger, $pause)); + }); + + $info['pause_handler'] = static function (float $duration) use (&$throttleWatcher, &$pauseDeferred, &$pause) { + if (null !== $throttleWatcher) { + Loop::cancel($throttleWatcher); + } + + $pause = $pauseDeferred->promise(); + + if ($duration <= 0) { + $deferred = $pauseDeferred; + $pauseDeferred = new Deferred(); + $deferred->resolve(); + } else { + $throttleWatcher = Loop::delay(ceil(1000 * $duration), static function () use (&$pauseDeferred) { + $deferred = $pauseDeferred; + $pauseDeferred = new Deferred(); + $deferred->resolve(); + }); + } + }; + + $multi->lastTimeout = null; + $multi->openHandles[$id] = $id; + ++$multi->responseCount; + + $this->canary = new Canary(static function () use ($canceller, $multi, $id) { + $canceller->cancel(); + unset($multi->openHandles[$id], $multi->handlesActivity[$id]); + }); + } + + /** + * {@inheritdoc} + */ + public function getInfo(string $type = null) + { + return null !== $type ? $this->info[$type] ?? null : $this->info; + } + + public function __sleep(): array + { + throw new \BadMethodCallException('Cannot serialize '.__CLASS__); + } + + public function __wakeup() + { + throw new \BadMethodCallException('Cannot unserialize '.__CLASS__); + } + + public function __destruct() + { + try { + $this->doDestruct(); + } finally { + // Clear the DNS cache when all requests completed + if (0 >= --$this->multi->responseCount) { + $this->multi->responseCount = 0; + $this->multi->dnsCache = []; + } + } + } + + /** + * {@inheritdoc} + */ + private static function schedule(self $response, array &$runningResponses): void + { + if (isset($runningResponses[0])) { + $runningResponses[0][1][$response->id] = $response; + } else { + $runningResponses[0] = [$response->multi, [$response->id => $response]]; + } + + if (!isset($response->multi->openHandles[$response->id])) { + $response->multi->handlesActivity[$response->id][] = null; + $response->multi->handlesActivity[$response->id][] = null !== $response->info['error'] ? new TransportException($response->info['error']) : null; + } + } + + /** + * {@inheritdoc} + * + * @param AmpClientState $multi + */ + private static function perform(ClientState $multi, array &$responses = null): void + { + if ($responses) { + foreach ($responses as $response) { + try { + if ($response->info['start_time']) { + $response->info['total_time'] = microtime(true) - $response->info['start_time']; + ($response->onProgress)(); + } + } catch (\Throwable $e) { + $multi->handlesActivity[$response->id][] = null; + $multi->handlesActivity[$response->id][] = $e; + } + } + } + } + + /** + * {@inheritdoc} + * + * @param AmpClientState $multi + */ + private static function select(ClientState $multi, float $timeout): int + { + $timeout += microtime(true); + self::$delay = Loop::defer(static function () use ($timeout) { + if (0 < $timeout -= microtime(true)) { + self::$delay = Loop::delay(ceil(1000 * $timeout), [Loop::class, 'stop']); + } else { + Loop::stop(); + } + }); + + Loop::run(); + + return null === self::$delay ? 1 : 0; + } + + private static function generateResponse(Request $request, AmpClientState $multi, string $id, array &$info, array &$headers, CancellationTokenSource $canceller, array &$options, \Closure $onProgress, &$handle, ?LoggerInterface $logger, Promise &$pause) + { + $request->setInformationalResponseHandler(static function (Response $response) use ($multi, $id, &$info, &$headers) { + self::addResponseHeaders($response, $info, $headers); + $multi->handlesActivity[$id][] = new InformationalChunk($response->getStatus(), $response->getHeaders()); + self::stopLoop(); + }); + + try { + /* @var Response $response */ + if (null === $response = yield from self::getPushedResponse($request, $multi, $info, $headers, $options, $logger)) { + $logger && $logger->info(sprintf('Request: "%s %s"', $info['http_method'], $info['url'])); + + $response = yield from self::followRedirects($request, $multi, $info, $headers, $canceller, $options, $onProgress, $handle, $logger, $pause); + } + + $options = null; + + $multi->handlesActivity[$id][] = new FirstChunk(); + + if ('HEAD' === $response->getRequest()->getMethod() || \in_array($info['http_code'], [204, 304], true)) { + $multi->handlesActivity[$id][] = null; + $multi->handlesActivity[$id][] = null; + self::stopLoop(); + + return; + } + + if ($response->hasHeader('content-length')) { + $info['download_content_length'] = (float) $response->getHeader('content-length'); + } + + $body = $response->getBody(); + + while (true) { + self::stopLoop(); + + yield $pause; + + if (null === $data = yield $body->read()) { + break; + } + + $info['size_download'] += \strlen($data); + $multi->handlesActivity[$id][] = $data; + } + + $multi->handlesActivity[$id][] = null; + $multi->handlesActivity[$id][] = null; + } catch (\Throwable $e) { + $multi->handlesActivity[$id][] = null; + $multi->handlesActivity[$id][] = $e; + } finally { + $info['download_content_length'] = $info['size_download']; + } + + self::stopLoop(); + } + + private static function followRedirects(Request $originRequest, AmpClientState $multi, array &$info, array &$headers, CancellationTokenSource $canceller, array $options, \Closure $onProgress, &$handle, ?LoggerInterface $logger, Promise &$pause) + { + yield $pause; + + $originRequest->setBody(new AmpBody($options['body'], $info, $onProgress)); + $response = yield $multi->request($options, $originRequest, $canceller->getToken(), $info, $onProgress, $handle); + $previousUrl = null; + + while (true) { + self::addResponseHeaders($response, $info, $headers); + $status = $response->getStatus(); + + if (!\in_array($status, [301, 302, 303, 307, 308], true) || null === $location = $response->getHeader('location')) { + return $response; + } + + $urlResolver = new class() { + use HttpClientTrait { + parseUrl as public; + resolveUrl as public; + } + }; + + try { + $previousUrl = $previousUrl ?? $urlResolver::parseUrl($info['url']); + $location = $urlResolver::parseUrl($location); + $location = $urlResolver::resolveUrl($location, $previousUrl); + $info['redirect_url'] = implode('', $location); + } catch (InvalidArgumentException $e) { + return $response; + } + + if (0 >= $options['max_redirects'] || $info['redirect_count'] >= $options['max_redirects']) { + return $response; + } + + $logger && $logger->info(sprintf('Redirecting: "%s %s"', $status, $info['url'])); + + try { + // Discard body of redirects + while (null !== yield $response->getBody()->read()) { + } + } catch (HttpException|StreamException $e) { + // Ignore streaming errors on previous responses + } + + ++$info['redirect_count']; + $info['url'] = $info['redirect_url']; + $info['redirect_url'] = null; + $previousUrl = $location; + + $request = new Request($info['url'], $info['http_method']); + $request->setProtocolVersions($originRequest->getProtocolVersions()); + $request->setTcpConnectTimeout($originRequest->getTcpConnectTimeout()); + $request->setTlsHandshakeTimeout($originRequest->getTlsHandshakeTimeout()); + $request->setTransferTimeout($originRequest->getTransferTimeout()); + + if (\in_array($status, [301, 302, 303], true)) { + $originRequest->removeHeader('transfer-encoding'); + $originRequest->removeHeader('content-length'); + $originRequest->removeHeader('content-type'); + + // Do like curl and browsers: turn POST to GET on 301, 302 and 303 + if ('POST' === $response->getRequest()->getMethod() || 303 === $status) { + $info['http_method'] = 'HEAD' === $response->getRequest()->getMethod() ? 'HEAD' : 'GET'; + $request->setMethod($info['http_method']); + } + } else { + $request->setBody(AmpBody::rewind($response->getRequest()->getBody())); + } + + foreach ($originRequest->getRawHeaders() as [$name, $value]) { + $request->setHeader($name, $value); + } + + if ($request->getUri()->getAuthority() !== $originRequest->getUri()->getAuthority()) { + $request->removeHeader('authorization'); + $request->removeHeader('cookie'); + $request->removeHeader('host'); + } + + yield $pause; + + $response = yield $multi->request($options, $request, $canceller->getToken(), $info, $onProgress, $handle); + $info['redirect_time'] = microtime(true) - $info['start_time']; + } + } + + private static function addResponseHeaders(Response $response, array &$info, array &$headers): void + { + $info['http_code'] = $response->getStatus(); + + if ($headers) { + $info['debug'] .= "< \r\n"; + $headers = []; + } + + $h = sprintf('HTTP/%s %s %s', $response->getProtocolVersion(), $response->getStatus(), $response->getReason()); + $info['debug'] .= "< {$h}\r\n"; + $info['response_headers'][] = $h; + + foreach ($response->getRawHeaders() as [$name, $value]) { + $headers[strtolower($name)][] = $value; + $h = $name.': '.$value; + $info['debug'] .= "< {$h}\r\n"; + $info['response_headers'][] = $h; + } + + $info['debug'] .= "< \r\n"; + } + + /** + * Accepts pushed responses only if their headers related to authentication match the request. + */ + private static function getPushedResponse(Request $request, AmpClientState $multi, array &$info, array &$headers, array $options, ?LoggerInterface $logger) + { + if ('' !== $options['body']) { + return null; + } + + $authority = $request->getUri()->getAuthority(); + + foreach ($multi->pushedResponses[$authority] ?? [] as $i => [$pushedUrl, $pushDeferred, $pushedRequest, $pushedResponse, $parentOptions]) { + if ($info['url'] !== $pushedUrl || $info['http_method'] !== $pushedRequest->getMethod()) { + continue; + } + + foreach ($parentOptions as $k => $v) { + if ($options[$k] !== $v) { + continue 2; + } + } + + foreach (['authorization', 'cookie', 'range', 'proxy-authorization'] as $k) { + if ($pushedRequest->getHeaderArray($k) !== $request->getHeaderArray($k)) { + continue 2; + } + } + + $response = yield $pushedResponse; + + foreach ($response->getHeaderArray('vary') as $vary) { + foreach (preg_split('/\s*+,\s*+/', $vary) as $v) { + if ('*' === $v || ($pushedRequest->getHeaderArray($v) !== $request->getHeaderArray($v) && 'accept-encoding' !== strtolower($v))) { + $logger && $logger->debug(sprintf('Skipping pushed response: "%s"', $info['url'])); + continue 3; + } + } + } + + $pushDeferred->resolve(); + $logger && $logger->debug(sprintf('Accepting pushed response: "%s %s"', $info['http_method'], $info['url'])); + self::addResponseHeaders($response, $info, $headers); + unset($multi->pushedResponses[$authority][$i]); + + if (!$multi->pushedResponses[$authority]) { + unset($multi->pushedResponses[$authority]); + } + + return $response; + } + } + + private static function stopLoop(): void + { + if (null !== self::$delay) { + Loop::cancel(self::$delay); + self::$delay = null; + } + + Loop::defer([Loop::class, 'stop']); + } +} diff --git a/sites/all/libraries/vendor/symfony/http-client/Response/AsyncContext.php b/sites/all/libraries/vendor/symfony/http-client/Response/AsyncContext.php new file mode 100644 index 0000000000..646458e13c --- /dev/null +++ b/sites/all/libraries/vendor/symfony/http-client/Response/AsyncContext.php @@ -0,0 +1,195 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient\Response; + +use Symfony\Component\HttpClient\Chunk\DataChunk; +use Symfony\Component\HttpClient\Chunk\LastChunk; +use Symfony\Component\HttpClient\Exception\TransportException; +use Symfony\Contracts\HttpClient\ChunkInterface; +use Symfony\Contracts\HttpClient\HttpClientInterface; +use Symfony\Contracts\HttpClient\ResponseInterface; + +/** + * A DTO to work with AsyncResponse. + * + * @author Nicolas Grekas + */ +final class AsyncContext +{ + private $passthru; + private $client; + private $response; + private $info = []; + private $content; + private $offset; + + public function __construct(&$passthru, HttpClientInterface $client, ResponseInterface &$response, array &$info, $content, int $offset) + { + $this->passthru = &$passthru; + $this->client = $client; + $this->response = &$response; + $this->info = &$info; + $this->content = $content; + $this->offset = $offset; + } + + /** + * Returns the HTTP status without consuming the response. + */ + public function getStatusCode(): int + { + return $this->response->getInfo('http_code'); + } + + /** + * Returns the headers without consuming the response. + */ + public function getHeaders(): array + { + $headers = []; + + foreach ($this->response->getInfo('response_headers') as $h) { + if (11 <= \strlen($h) && '/' === $h[4] && preg_match('#^HTTP/\d+(?:\.\d+)? ([123456789]\d\d)(?: |$)#', $h, $m)) { + $headers = []; + } elseif (2 === \count($m = explode(':', $h, 2))) { + $headers[strtolower($m[0])][] = ltrim($m[1]); + } + } + + return $headers; + } + + /** + * @return resource|null The PHP stream resource where the content is buffered, if it is + */ + public function getContent() + { + return $this->content; + } + + /** + * Creates a new chunk of content. + */ + public function createChunk(string $data): ChunkInterface + { + return new DataChunk($this->offset, $data); + } + + /** + * Pauses the request for the given number of seconds. + */ + public function pause(float $duration): void + { + if (\is_callable($pause = $this->response->getInfo('pause_handler'))) { + $pause($duration); + } elseif (0 < $duration) { + usleep(1E6 * $duration); + } + } + + /** + * Cancels the request and returns the last chunk to yield. + */ + public function cancel(): ChunkInterface + { + $this->info['canceled'] = true; + $this->info['error'] = 'Response has been canceled.'; + $this->response->cancel(); + + return new LastChunk(); + } + + /** + * Returns the current info of the response. + */ + public function getInfo(string $type = null) + { + if (null !== $type) { + return $this->info[$type] ?? $this->response->getInfo($type); + } + + return $this->info + $this->response->getInfo(); + } + + /** + * Attaches an info to the response. + * + * @return $this + */ + public function setInfo(string $type, $value): self + { + if ('canceled' === $type && $value !== $this->info['canceled']) { + throw new \LogicException('You cannot set the "canceled" info directly.'); + } + + if (null === $value) { + unset($this->info[$type]); + } else { + $this->info[$type] = $value; + } + + return $this; + } + + /** + * Returns the currently processed response. + */ + public function getResponse(): ResponseInterface + { + return $this->response; + } + + /** + * Replaces the currently processed response by doing a new request. + */ + public function replaceRequest(string $method, string $url, array $options = []): ResponseInterface + { + $this->info['previous_info'][] = $info = $this->response->getInfo(); + if (null !== $onProgress = $options['on_progress'] ?? null) { + $thisInfo = &$this->info; + $options['on_progress'] = static function (int $dlNow, int $dlSize, array $info) use (&$thisInfo, $onProgress) { + $onProgress($dlNow, $dlSize, $thisInfo + $info); + }; + } + if (0 < ($info['max_duration'] ?? 0) && 0 < ($info['total_time'] ?? 0)) { + if (0 >= $options['max_duration'] = $info['max_duration'] - $info['total_time']) { + throw new TransportException(sprintf('Max duration was reached for "%s".', $info['url'])); + } + } + + return $this->response = $this->client->request($method, $url, ['buffer' => false] + $options); + } + + /** + * Replaces the currently processed response by another one. + */ + public function replaceResponse(ResponseInterface $response): ResponseInterface + { + $this->info['previous_info'][] = $this->response->getInfo(); + + return $this->response = $response; + } + + /** + * Replaces or removes the chunk filter iterator. + * + * @param ?callable(ChunkInterface, self): ?\Iterator $passthru + */ + public function passthru(callable $passthru = null): void + { + $this->passthru = $passthru ?? static function ($chunk, $context) { + $context->passthru = null; + + yield $chunk; + }; + } +} diff --git a/sites/all/libraries/vendor/symfony/http-client/Response/AsyncResponse.php b/sites/all/libraries/vendor/symfony/http-client/Response/AsyncResponse.php new file mode 100644 index 0000000000..80c9f7da37 --- /dev/null +++ b/sites/all/libraries/vendor/symfony/http-client/Response/AsyncResponse.php @@ -0,0 +1,478 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient\Response; + +use Symfony\Component\HttpClient\Chunk\ErrorChunk; +use Symfony\Component\HttpClient\Chunk\FirstChunk; +use Symfony\Component\HttpClient\Chunk\LastChunk; +use Symfony\Component\HttpClient\Exception\TransportException; +use Symfony\Contracts\HttpClient\ChunkInterface; +use Symfony\Contracts\HttpClient\Exception\ExceptionInterface; +use Symfony\Contracts\HttpClient\Exception\HttpExceptionInterface; +use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface; +use Symfony\Contracts\HttpClient\HttpClientInterface; +use Symfony\Contracts\HttpClient\ResponseInterface; + +/** + * Provides a single extension point to process a response's content stream. + * + * @author Nicolas Grekas + */ +final class AsyncResponse implements ResponseInterface, StreamableInterface +{ + use CommonResponseTrait; + + private const FIRST_CHUNK_YIELDED = 1; + private const LAST_CHUNK_YIELDED = 2; + + private $client; + private $response; + private $info = ['canceled' => false]; + private $passthru; + private $stream; + private $yieldedState; + + /** + * @param ?callable(ChunkInterface, AsyncContext): ?\Iterator $passthru + */ + public function __construct(HttpClientInterface $client, string $method, string $url, array $options, callable $passthru = null) + { + $this->client = $client; + $this->shouldBuffer = $options['buffer'] ?? true; + + if (null !== $onProgress = $options['on_progress'] ?? null) { + $thisInfo = &$this->info; + $options['on_progress'] = static function (int $dlNow, int $dlSize, array $info) use (&$thisInfo, $onProgress) { + $onProgress($dlNow, $dlSize, $thisInfo + $info); + }; + } + $this->response = $client->request($method, $url, ['buffer' => false] + $options); + $this->passthru = $passthru; + $this->initializer = static function (self $response, float $timeout = null) { + if (null === $response->shouldBuffer) { + return false; + } + + while (true) { + foreach (self::stream([$response], $timeout) as $chunk) { + if ($chunk->isTimeout() && $response->passthru) { + foreach (self::passthru($response->client, $response, new ErrorChunk($response->offset, new TransportException($chunk->getError()))) as $chunk) { + if ($chunk->isFirst()) { + return false; + } + } + + continue 2; + } + + if ($chunk->isFirst()) { + return false; + } + } + + return false; + } + }; + if (\array_key_exists('user_data', $options)) { + $this->info['user_data'] = $options['user_data']; + } + if (\array_key_exists('max_duration', $options)) { + $this->info['max_duration'] = $options['max_duration']; + } + } + + public function getStatusCode(): int + { + if ($this->initializer) { + self::initialize($this); + } + + return $this->response->getStatusCode(); + } + + public function getHeaders(bool $throw = true): array + { + if ($this->initializer) { + self::initialize($this); + } + + $headers = $this->response->getHeaders(false); + + if ($throw) { + $this->checkStatusCode(); + } + + return $headers; + } + + public function getInfo(string $type = null) + { + if (null !== $type) { + return $this->info[$type] ?? $this->response->getInfo($type); + } + + return $this->info + $this->response->getInfo(); + } + + /** + * {@inheritdoc} + */ + public function toStream(bool $throw = true) + { + if ($throw) { + // Ensure headers arrived + $this->getHeaders(true); + } + + $handle = function () { + $stream = $this->response instanceof StreamableInterface ? $this->response->toStream(false) : StreamWrapper::createResource($this->response); + + return stream_get_meta_data($stream)['wrapper_data']->stream_cast(\STREAM_CAST_FOR_SELECT); + }; + + $stream = StreamWrapper::createResource($this); + stream_get_meta_data($stream)['wrapper_data'] + ->bindHandles($handle, $this->content); + + return $stream; + } + + /** + * {@inheritdoc} + */ + public function cancel(): void + { + if ($this->info['canceled']) { + return; + } + + $this->info['canceled'] = true; + $this->info['error'] = 'Response has been canceled.'; + $this->close(); + $client = $this->client; + $this->client = null; + + if (!$this->passthru) { + return; + } + + try { + foreach (self::passthru($client, $this, new LastChunk()) as $chunk) { + // no-op + } + + $this->passthru = null; + } catch (ExceptionInterface $e) { + // ignore any errors when canceling + } + } + + public function __destruct() + { + $httpException = null; + + if ($this->initializer && null === $this->getInfo('error')) { + try { + self::initialize($this, -0.0); + $this->getHeaders(true); + } catch (HttpExceptionInterface $httpException) { + // no-op + } + } + + if ($this->passthru && null === $this->getInfo('error')) { + $this->info['canceled'] = true; + + try { + foreach (self::passthru($this->client, $this, new LastChunk()) as $chunk) { + // no-op + } + } catch (ExceptionInterface $e) { + // ignore any errors when destructing + } + } + + if (null !== $httpException) { + throw $httpException; + } + } + + /** + * @internal + */ + public static function stream(iterable $responses, float $timeout = null, string $class = null): \Generator + { + while ($responses) { + $wrappedResponses = []; + $asyncMap = new \SplObjectStorage(); + $client = null; + + foreach ($responses as $r) { + if (!$r instanceof self) { + throw new \TypeError(sprintf('"%s::stream()" expects parameter 1 to be an iterable of AsyncResponse objects, "%s" given.', $class ?? static::class, get_debug_type($r))); + } + + if (null !== $e = $r->info['error'] ?? null) { + yield $r => $chunk = new ErrorChunk($r->offset, new TransportException($e)); + $chunk->didThrow() ?: $chunk->getContent(); + continue; + } + + if (null === $client) { + $client = $r->client; + } elseif ($r->client !== $client) { + throw new TransportException('Cannot stream AsyncResponse objects with many clients.'); + } + + $asyncMap[$r->response] = $r; + $wrappedResponses[] = $r->response; + + if ($r->stream) { + yield from self::passthruStream($response = $r->response, $r, new FirstChunk(), $asyncMap); + + if (!isset($asyncMap[$response])) { + array_pop($wrappedResponses); + } + + if ($r->response !== $response && !isset($asyncMap[$r->response])) { + $asyncMap[$r->response] = $r; + $wrappedResponses[] = $r->response; + } + } + } + + if (!$client || !$wrappedResponses) { + return; + } + + foreach ($client->stream($wrappedResponses, $timeout) as $response => $chunk) { + $r = $asyncMap[$response]; + + if (null === $chunk->getError()) { + if ($chunk->isFirst()) { + // Ensure no exception is thrown on destruct for the wrapped response + $r->response->getStatusCode(); + } elseif (0 === $r->offset && null === $r->content && $chunk->isLast()) { + $r->content = fopen('php://memory', 'w+'); + } + } + + if (!$r->passthru) { + if (null !== $chunk->getError() || $chunk->isLast()) { + unset($asyncMap[$response]); + } elseif (null !== $r->content && '' !== ($content = $chunk->getContent()) && \strlen($content) !== fwrite($r->content, $content)) { + $chunk = new ErrorChunk($r->offset, new TransportException(sprintf('Failed writing %d bytes to the response buffer.', \strlen($content)))); + $r->info['error'] = $chunk->getError(); + $r->response->cancel(); + } + + yield $r => $chunk; + continue; + } + + if (null !== $chunk->getError()) { + // no-op + } elseif ($chunk->isFirst()) { + $r->yieldedState = self::FIRST_CHUNK_YIELDED; + } elseif (self::FIRST_CHUNK_YIELDED !== $r->yieldedState && null === $chunk->getInformationalStatus()) { + throw new \LogicException(sprintf('Instance of "%s" is already consumed and cannot be managed by "%s". A decorated client should not call any of the response\'s methods in its "request()" method.', get_debug_type($response), $class ?? static::class)); + } + + foreach (self::passthru($r->client, $r, $chunk, $asyncMap) as $chunk) { + yield $r => $chunk; + } + + if ($r->response !== $response && isset($asyncMap[$response])) { + break; + } + } + + if (null === $chunk->getError() && $chunk->isLast()) { + $r->yieldedState = self::LAST_CHUNK_YIELDED; + } + if (null === $chunk->getError() && self::LAST_CHUNK_YIELDED !== $r->yieldedState && $r->response === $response && null !== $r->client) { + throw new \LogicException('A chunk passthru must yield an "isLast()" chunk before ending a stream.'); + } + + $responses = []; + foreach ($asyncMap as $response) { + $r = $asyncMap[$response]; + + if (null !== $r->client) { + $responses[] = $asyncMap[$response]; + } + } + } + } + + /** + * @param \SplObjectStorage|null $asyncMap + */ + private static function passthru(HttpClientInterface $client, self $r, ChunkInterface $chunk, \SplObjectStorage $asyncMap = null): \Generator + { + $r->stream = null; + $response = $r->response; + $context = new AsyncContext($r->passthru, $client, $r->response, $r->info, $r->content, $r->offset); + if (null === $stream = ($r->passthru)($chunk, $context)) { + if ($r->response === $response && (null !== $chunk->getError() || $chunk->isLast())) { + throw new \LogicException('A chunk passthru cannot swallow the last chunk.'); + } + + return; + } + + if (!$stream instanceof \Iterator) { + throw new \LogicException(sprintf('A chunk passthru must return an "Iterator", "%s" returned.', get_debug_type($stream))); + } + $r->stream = $stream; + + yield from self::passthruStream($response, $r, null, $asyncMap); + } + + /** + * @param \SplObjectStorage|null $asyncMap + */ + private static function passthruStream(ResponseInterface $response, self $r, ?ChunkInterface $chunk, ?\SplObjectStorage $asyncMap): \Generator + { + while (true) { + try { + if (null !== $chunk && $r->stream) { + $r->stream->next(); + } + + if (!$r->stream || !$r->stream->valid() || !$r->stream) { + $r->stream = null; + break; + } + } catch (\Throwable $e) { + unset($asyncMap[$response]); + $r->stream = null; + $r->info['error'] = $e->getMessage(); + $r->response->cancel(); + + yield $r => $chunk = new ErrorChunk($r->offset, $e); + $chunk->didThrow() ?: $chunk->getContent(); + break; + } + + $chunk = $r->stream->current(); + + if (!$chunk instanceof ChunkInterface) { + throw new \LogicException(sprintf('A chunk passthru must yield instances of "%s", "%s" yielded.', ChunkInterface::class, get_debug_type($chunk))); + } + + if (null !== $chunk->getError()) { + // no-op + } elseif ($chunk->isFirst()) { + $e = $r->openBuffer(); + + yield $r => $chunk; + + if ($r->initializer && null === $r->getInfo('error')) { + // Ensure the HTTP status code is always checked + $r->getHeaders(true); + } + + if (null === $e) { + continue; + } + + $r->response->cancel(); + $chunk = new ErrorChunk($r->offset, $e); + } elseif ('' !== $content = $chunk->getContent()) { + if (null !== $r->shouldBuffer) { + throw new \LogicException('A chunk passthru must yield an "isFirst()" chunk before any content chunk.'); + } + + if (null !== $r->content && \strlen($content) !== fwrite($r->content, $content)) { + $chunk = new ErrorChunk($r->offset, new TransportException(sprintf('Failed writing %d bytes to the response buffer.', \strlen($content)))); + $r->info['error'] = $chunk->getError(); + $r->response->cancel(); + } + } + + if (null !== $chunk->getError() || $chunk->isLast()) { + $stream = $r->stream; + $r->stream = null; + unset($asyncMap[$response]); + } + + if (null === $chunk->getError()) { + $r->offset += \strlen($content); + + yield $r => $chunk; + + if (!$chunk->isLast()) { + continue; + } + + $stream->next(); + + if ($stream->valid()) { + throw new \LogicException('A chunk passthru cannot yield after an "isLast()" chunk.'); + } + + $r->passthru = null; + } else { + if ($chunk instanceof ErrorChunk) { + $chunk->didThrow(false); + } else { + try { + $chunk = new ErrorChunk($chunk->getOffset(), !$chunk->isTimeout() ?: $chunk->getError()); + } catch (TransportExceptionInterface $e) { + $chunk = new ErrorChunk($chunk->getOffset(), $e); + } + } + + yield $r => $chunk; + $chunk->didThrow() ?: $chunk->getContent(); + } + + break; + } + } + + private function openBuffer(): ?\Throwable + { + if (null === $shouldBuffer = $this->shouldBuffer) { + throw new \LogicException('A chunk passthru cannot yield more than one "isFirst()" chunk.'); + } + + $e = $this->shouldBuffer = null; + + if ($shouldBuffer instanceof \Closure) { + try { + $shouldBuffer = $shouldBuffer($this->getHeaders(false)); + + if (null !== $e = $this->response->getInfo('error')) { + throw new TransportException($e); + } + } catch (\Throwable $e) { + $this->info['error'] = $e->getMessage(); + $this->response->cancel(); + } + } + + if (true === $shouldBuffer) { + $this->content = fopen('php://temp', 'w+'); + } elseif (\is_resource($shouldBuffer)) { + $this->content = $shouldBuffer; + } + + return $e; + } + + private function close(): void + { + $this->response->cancel(); + } +} diff --git a/sites/all/libraries/vendor/symfony/http-client/Response/CommonResponseTrait.php b/sites/all/libraries/vendor/symfony/http-client/Response/CommonResponseTrait.php new file mode 100644 index 0000000000..11a8d6ca79 --- /dev/null +++ b/sites/all/libraries/vendor/symfony/http-client/Response/CommonResponseTrait.php @@ -0,0 +1,185 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient\Response; + +use Symfony\Component\HttpClient\Exception\ClientException; +use Symfony\Component\HttpClient\Exception\JsonException; +use Symfony\Component\HttpClient\Exception\RedirectionException; +use Symfony\Component\HttpClient\Exception\ServerException; +use Symfony\Component\HttpClient\Exception\TransportException; + +/** + * Implements common logic for response classes. + * + * @author Nicolas Grekas + * + * @internal + */ +trait CommonResponseTrait +{ + /** + * @var callable|null A callback that tells whether we're waiting for response headers + */ + private $initializer; + private $shouldBuffer; + private $content; + private $offset = 0; + private $jsonData; + + /** + * {@inheritdoc} + */ + public function getContent(bool $throw = true): string + { + if ($this->initializer) { + self::initialize($this); + } + + if ($throw) { + $this->checkStatusCode(); + } + + if (null === $this->content) { + $content = null; + + foreach (self::stream([$this]) as $chunk) { + if (!$chunk->isLast()) { + $content .= $chunk->getContent(); + } + } + + if (null !== $content) { + return $content; + } + + if (null === $this->content) { + throw new TransportException('Cannot get the content of the response twice: buffering is disabled.'); + } + } else { + foreach (self::stream([$this]) as $chunk) { + // Chunks are buffered in $this->content already + } + } + + rewind($this->content); + + return stream_get_contents($this->content); + } + + /** + * {@inheritdoc} + */ + public function toArray(bool $throw = true): array + { + if ('' === $content = $this->getContent($throw)) { + throw new JsonException('Response body is empty.'); + } + + if (null !== $this->jsonData) { + return $this->jsonData; + } + + try { + $content = json_decode($content, true, 512, \JSON_BIGINT_AS_STRING | (\PHP_VERSION_ID >= 70300 ? \JSON_THROW_ON_ERROR : 0)); + } catch (\JsonException $e) { + throw new JsonException($e->getMessage().sprintf(' for "%s".', $this->getInfo('url')), $e->getCode()); + } + + if (\PHP_VERSION_ID < 70300 && \JSON_ERROR_NONE !== json_last_error()) { + throw new JsonException(json_last_error_msg().sprintf(' for "%s".', $this->getInfo('url')), json_last_error()); + } + + if (!\is_array($content)) { + throw new JsonException(sprintf('JSON content was expected to decode to an array, "%s" returned for "%s".', get_debug_type($content), $this->getInfo('url'))); + } + + if (null !== $this->content) { + // Option "buffer" is true + return $this->jsonData = $content; + } + + return $content; + } + + /** + * {@inheritdoc} + */ + public function toStream(bool $throw = true) + { + if ($throw) { + // Ensure headers arrived + $this->getHeaders($throw); + } + + $stream = StreamWrapper::createResource($this); + stream_get_meta_data($stream)['wrapper_data'] + ->bindHandles($this->handle, $this->content); + + return $stream; + } + + public function __sleep(): array + { + throw new \BadMethodCallException('Cannot serialize '.__CLASS__); + } + + public function __wakeup() + { + throw new \BadMethodCallException('Cannot unserialize '.__CLASS__); + } + + /** + * Closes the response and all its network handles. + */ + abstract protected function close(): void; + + private static function initialize(self $response): void + { + if (null !== $response->getInfo('error')) { + throw new TransportException($response->getInfo('error')); + } + + try { + if (($response->initializer)($response, -0.0)) { + foreach (self::stream([$response], -0.0) as $chunk) { + if ($chunk->isFirst()) { + break; + } + } + } + } catch (\Throwable $e) { + // Persist timeouts thrown during initialization + $response->info['error'] = $e->getMessage(); + $response->close(); + throw $e; + } + + $response->initializer = null; + } + + private function checkStatusCode() + { + $code = $this->getInfo('http_code'); + + if (500 <= $code) { + throw new ServerException($this); + } + + if (400 <= $code) { + throw new ClientException($this); + } + + if (300 <= $code) { + throw new RedirectionException($this); + } + } +} diff --git a/sites/all/libraries/vendor/symfony/http-client/Response/CurlResponse.php b/sites/all/libraries/vendor/symfony/http-client/Response/CurlResponse.php new file mode 100644 index 0000000000..b03a49a946 --- /dev/null +++ b/sites/all/libraries/vendor/symfony/http-client/Response/CurlResponse.php @@ -0,0 +1,474 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient\Response; + +use Psr\Log\LoggerInterface; +use Symfony\Component\HttpClient\Chunk\FirstChunk; +use Symfony\Component\HttpClient\Chunk\InformationalChunk; +use Symfony\Component\HttpClient\Exception\TransportException; +use Symfony\Component\HttpClient\Internal\Canary; +use Symfony\Component\HttpClient\Internal\ClientState; +use Symfony\Component\HttpClient\Internal\CurlClientState; +use Symfony\Contracts\HttpClient\ResponseInterface; + +/** + * @author Nicolas Grekas + * + * @internal + */ +final class CurlResponse implements ResponseInterface, StreamableInterface +{ + use CommonResponseTrait { + getContent as private doGetContent; + } + use TransportResponseTrait; + + private static $performing = false; + private $multi; + private $debugBuffer; + + /** + * @param \CurlHandle|resource|string $ch + * + * @internal + */ + public function __construct(CurlClientState $multi, $ch, array $options = null, LoggerInterface $logger = null, string $method = 'GET', callable $resolveRedirect = null, int $curlVersion = null) + { + $this->multi = $multi; + + if (\is_resource($ch) || $ch instanceof \CurlHandle) { + $this->handle = $ch; + $this->debugBuffer = fopen('php://temp', 'w+'); + if (0x074000 === $curlVersion) { + fwrite($this->debugBuffer, 'Due to a bug in curl 7.64.0, the debug log is disabled; use another version to work around the issue.'); + } else { + curl_setopt($ch, \CURLOPT_VERBOSE, true); + curl_setopt($ch, \CURLOPT_STDERR, $this->debugBuffer); + } + } else { + $this->info['url'] = $ch; + $ch = $this->handle; + } + + $this->id = $id = (int) $ch; + $this->logger = $logger; + $this->shouldBuffer = $options['buffer'] ?? true; + $this->timeout = $options['timeout'] ?? null; + $this->info['http_method'] = $method; + $this->info['user_data'] = $options['user_data'] ?? null; + $this->info['max_duration'] = $options['max_duration'] ?? null; + $this->info['start_time'] = $this->info['start_time'] ?? microtime(true); + $info = &$this->info; + $headers = &$this->headers; + $debugBuffer = $this->debugBuffer; + + if (!$info['response_headers']) { + // Used to keep track of what we're waiting for + curl_setopt($ch, \CURLOPT_PRIVATE, \in_array($method, ['GET', 'HEAD', 'OPTIONS', 'TRACE'], true) && 1.0 < (float) ($options['http_version'] ?? 1.1) ? 'H2' : 'H0'); // H = headers + retry counter + } + + curl_setopt($ch, \CURLOPT_HEADERFUNCTION, static function ($ch, string $data) use (&$info, &$headers, $options, $multi, $id, &$location, $resolveRedirect, $logger): int { + if (0 !== substr_compare($data, "\r\n", -2)) { + return 0; + } + + $len = 0; + + foreach (explode("\r\n", substr($data, 0, -2)) as $data) { + $len += 2 + self::parseHeaderLine($ch, $data, $info, $headers, $options, $multi, $id, $location, $resolveRedirect, $logger); + } + + return $len; + }); + + if (null === $options) { + // Pushed response: buffer until requested + curl_setopt($ch, \CURLOPT_WRITEFUNCTION, static function ($ch, string $data) use ($multi, $id): int { + $multi->handlesActivity[$id][] = $data; + curl_pause($ch, \CURLPAUSE_RECV); + + return \strlen($data); + }); + + return; + } + + $execCounter = $multi->execCounter; + $this->info['pause_handler'] = static function (float $duration) use ($ch, $multi, $execCounter) { + if (0 < $duration) { + if ($execCounter === $multi->execCounter) { + $multi->execCounter = !\is_float($execCounter) ? 1 + $execCounter : \PHP_INT_MIN; + curl_multi_remove_handle($multi->handle, $ch); + } + + $lastExpiry = end($multi->pauseExpiries); + $multi->pauseExpiries[(int) $ch] = $duration += microtime(true); + if (false !== $lastExpiry && $lastExpiry > $duration) { + asort($multi->pauseExpiries); + } + curl_pause($ch, \CURLPAUSE_ALL); + } else { + unset($multi->pauseExpiries[(int) $ch]); + curl_pause($ch, \CURLPAUSE_CONT); + curl_multi_add_handle($multi->handle, $ch); + } + }; + + $this->inflate = !isset($options['normalized_headers']['accept-encoding']); + curl_pause($ch, \CURLPAUSE_CONT); + + if ($onProgress = $options['on_progress']) { + $url = isset($info['url']) ? ['url' => $info['url']] : []; + curl_setopt($ch, \CURLOPT_NOPROGRESS, false); + curl_setopt($ch, \CURLOPT_PROGRESSFUNCTION, static function ($ch, $dlSize, $dlNow) use ($onProgress, &$info, $url, $multi, $debugBuffer) { + try { + rewind($debugBuffer); + $debug = ['debug' => stream_get_contents($debugBuffer)]; + $onProgress($dlNow, $dlSize, $url + curl_getinfo($ch) + $info + $debug); + } catch (\Throwable $e) { + $multi->handlesActivity[(int) $ch][] = null; + $multi->handlesActivity[(int) $ch][] = $e; + + return 1; // Abort the request + } + + return null; + }); + } + + curl_setopt($ch, \CURLOPT_WRITEFUNCTION, static function ($ch, string $data) use ($multi, $id): int { + if ('H' === (curl_getinfo($ch, \CURLINFO_PRIVATE)[0] ?? null)) { + $multi->handlesActivity[$id][] = null; + $multi->handlesActivity[$id][] = new TransportException(sprintf('Unsupported protocol for "%s"', curl_getinfo($ch, \CURLINFO_EFFECTIVE_URL))); + + return 0; + } + + curl_setopt($ch, \CURLOPT_WRITEFUNCTION, static function ($ch, string $data) use ($multi, $id): int { + $multi->handlesActivity[$id][] = $data; + + return \strlen($data); + }); + + $multi->handlesActivity[$id][] = $data; + + return \strlen($data); + }); + + $this->initializer = static function (self $response) { + $waitFor = curl_getinfo($ch = $response->handle, \CURLINFO_PRIVATE); + + return 'H' === $waitFor[0]; + }; + + // Schedule the request in a non-blocking way + $multi->lastTimeout = null; + $multi->openHandles[$id] = [$ch, $options]; + curl_multi_add_handle($multi->handle, $ch); + + $this->canary = new Canary(static function () use ($ch, $multi, $id) { + unset($multi->pauseExpiries[$id], $multi->openHandles[$id], $multi->handlesActivity[$id]); + curl_setopt($ch, \CURLOPT_PRIVATE, '_0'); + + if (self::$performing) { + return; + } + + curl_multi_remove_handle($multi->handle, $ch); + curl_setopt_array($ch, [ + \CURLOPT_NOPROGRESS => true, + \CURLOPT_PROGRESSFUNCTION => null, + \CURLOPT_HEADERFUNCTION => null, + \CURLOPT_WRITEFUNCTION => null, + \CURLOPT_READFUNCTION => null, + \CURLOPT_INFILE => null, + ]); + + if (!$multi->openHandles) { + // Schedule DNS cache eviction for the next request + $multi->dnsCache->evictions = $multi->dnsCache->evictions ?: $multi->dnsCache->removals; + $multi->dnsCache->removals = $multi->dnsCache->hostnames = []; + } + }); + } + + /** + * {@inheritdoc} + */ + public function getInfo(string $type = null) + { + if (!$info = $this->finalInfo) { + $info = array_merge($this->info, curl_getinfo($this->handle)); + $info['url'] = $this->info['url'] ?? $info['url']; + $info['redirect_url'] = $this->info['redirect_url'] ?? null; + + // workaround curl not subtracting the time offset for pushed responses + if (isset($this->info['url']) && $info['start_time'] / 1000 < $info['total_time']) { + $info['total_time'] -= $info['starttransfer_time'] ?: $info['total_time']; + $info['starttransfer_time'] = 0.0; + } + + rewind($this->debugBuffer); + $info['debug'] = stream_get_contents($this->debugBuffer); + $waitFor = curl_getinfo($this->handle, \CURLINFO_PRIVATE); + + if ('H' !== $waitFor[0] && 'C' !== $waitFor[0]) { + curl_setopt($this->handle, \CURLOPT_VERBOSE, false); + rewind($this->debugBuffer); + ftruncate($this->debugBuffer, 0); + $this->finalInfo = $info; + } + } + + return null !== $type ? $info[$type] ?? null : $info; + } + + /** + * {@inheritdoc} + */ + public function getContent(bool $throw = true): string + { + $performing = self::$performing; + self::$performing = $performing || '_0' === curl_getinfo($this->handle, \CURLINFO_PRIVATE); + + try { + return $this->doGetContent($throw); + } finally { + self::$performing = $performing; + } + } + + public function __destruct() + { + try { + if (null === $this->timeout) { + return; // Unused pushed response + } + + $this->doDestruct(); + } finally { + if (\is_resource($this->handle) || $this->handle instanceof \CurlHandle) { + curl_setopt($this->handle, \CURLOPT_VERBOSE, false); + } + } + } + + /** + * {@inheritdoc} + */ + private static function schedule(self $response, array &$runningResponses): void + { + if (isset($runningResponses[$i = (int) $response->multi->handle])) { + $runningResponses[$i][1][$response->id] = $response; + } else { + $runningResponses[$i] = [$response->multi, [$response->id => $response]]; + } + + if ('_0' === curl_getinfo($ch = $response->handle, \CURLINFO_PRIVATE)) { + // Response already completed + $response->multi->handlesActivity[$response->id][] = null; + $response->multi->handlesActivity[$response->id][] = null !== $response->info['error'] ? new TransportException($response->info['error']) : null; + } + } + + /** + * {@inheritdoc} + * + * @param CurlClientState $multi + */ + private static function perform(ClientState $multi, array &$responses = null): void + { + if (self::$performing) { + if ($responses) { + $response = current($responses); + $multi->handlesActivity[(int) $response->handle][] = null; + $multi->handlesActivity[(int) $response->handle][] = new TransportException(sprintf('Userland callback cannot use the client nor the response while processing "%s".', curl_getinfo($response->handle, \CURLINFO_EFFECTIVE_URL))); + } + + return; + } + + try { + self::$performing = true; + ++$multi->execCounter; + $active = 0; + while (\CURLM_CALL_MULTI_PERFORM === ($err = curl_multi_exec($multi->handle, $active))) { + } + + if (\CURLM_OK !== $err) { + throw new TransportException(curl_multi_strerror($err)); + } + + while ($info = curl_multi_info_read($multi->handle)) { + if (\CURLMSG_DONE !== $info['msg']) { + continue; + } + $result = $info['result']; + $id = (int) $ch = $info['handle']; + $waitFor = @curl_getinfo($ch, \CURLINFO_PRIVATE) ?: '_0'; + + if (\in_array($result, [\CURLE_SEND_ERROR, \CURLE_RECV_ERROR, /* CURLE_HTTP2 */ 16, /* CURLE_HTTP2_STREAM */ 92], true) && $waitFor[1] && 'C' !== $waitFor[0]) { + curl_multi_remove_handle($multi->handle, $ch); + $waitFor[1] = (string) ((int) $waitFor[1] - 1); // decrement the retry counter + curl_setopt($ch, \CURLOPT_PRIVATE, $waitFor); + curl_setopt($ch, \CURLOPT_FORBID_REUSE, true); + + if (0 === curl_multi_add_handle($multi->handle, $ch)) { + continue; + } + } + + if (\CURLE_RECV_ERROR === $result && 'H' === $waitFor[0] && 400 <= ($responses[(int) $ch]->info['http_code'] ?? 0)) { + $multi->handlesActivity[$id][] = new FirstChunk(); + } + + $multi->handlesActivity[$id][] = null; + $multi->handlesActivity[$id][] = \in_array($result, [\CURLE_OK, \CURLE_TOO_MANY_REDIRECTS], true) || '_0' === $waitFor || curl_getinfo($ch, \CURLINFO_SIZE_DOWNLOAD) === curl_getinfo($ch, \CURLINFO_CONTENT_LENGTH_DOWNLOAD) ? null : new TransportException(ucfirst(curl_error($ch) ?: curl_strerror($result)).sprintf(' for "%s".', curl_getinfo($ch, \CURLINFO_EFFECTIVE_URL))); + } + } finally { + self::$performing = false; + } + } + + /** + * {@inheritdoc} + * + * @param CurlClientState $multi + */ + private static function select(ClientState $multi, float $timeout): int + { + if (\PHP_VERSION_ID < 70211) { + // workaround https://bugs.php.net/76480 + $timeout = min($timeout, 0.01); + } + + if ($multi->pauseExpiries) { + $now = microtime(true); + + foreach ($multi->pauseExpiries as $id => $pauseExpiry) { + if ($now < $pauseExpiry) { + $timeout = min($timeout, $pauseExpiry - $now); + break; + } + + unset($multi->pauseExpiries[$id]); + curl_pause($multi->openHandles[$id][0], \CURLPAUSE_CONT); + curl_multi_add_handle($multi->handle, $multi->openHandles[$id][0]); + } + } + + if (0 !== $selected = curl_multi_select($multi->handle, $timeout)) { + return $selected; + } + + if ($multi->pauseExpiries && 0 < $timeout -= microtime(true) - $now) { + usleep((int) (1E6 * $timeout)); + } + + return 0; + } + + /** + * Parses header lines as curl yields them to us. + */ + private static function parseHeaderLine($ch, string $data, array &$info, array &$headers, ?array $options, CurlClientState $multi, int $id, ?string &$location, ?callable $resolveRedirect, ?LoggerInterface $logger): int + { + $waitFor = @curl_getinfo($ch, \CURLINFO_PRIVATE) ?: '_0'; + + if ('H' !== $waitFor[0]) { + return \strlen($data); // Ignore HTTP trailers + } + + if ('' !== $data) { + // Regular header line: add it to the list + self::addResponseHeaders([$data], $info, $headers); + + if (!str_starts_with($data, 'HTTP/')) { + if (0 === stripos($data, 'Location:')) { + $location = trim(substr($data, 9)); + } + + return \strlen($data); + } + + if (\function_exists('openssl_x509_read') && $certinfo = curl_getinfo($ch, \CURLINFO_CERTINFO)) { + $info['peer_certificate_chain'] = array_map('openssl_x509_read', array_column($certinfo, 'Cert')); + } + + if (300 <= $info['http_code'] && $info['http_code'] < 400) { + if (curl_getinfo($ch, \CURLINFO_REDIRECT_COUNT) === $options['max_redirects']) { + curl_setopt($ch, \CURLOPT_FOLLOWLOCATION, false); + } elseif (303 === $info['http_code'] || ('POST' === $info['http_method'] && \in_array($info['http_code'], [301, 302], true))) { + curl_setopt($ch, \CURLOPT_POSTFIELDS, ''); + } + } + + return \strlen($data); + } + + // End of headers: handle informational responses, redirects, etc. + + if (200 > $statusCode = curl_getinfo($ch, \CURLINFO_RESPONSE_CODE)) { + $multi->handlesActivity[$id][] = new InformationalChunk($statusCode, $headers); + $location = null; + + return \strlen($data); + } + + $info['redirect_url'] = null; + + if (300 <= $statusCode && $statusCode < 400 && null !== $location) { + if ($noContent = 303 === $statusCode || ('POST' === $info['http_method'] && \in_array($statusCode, [301, 302], true))) { + $info['http_method'] = 'HEAD' === $info['http_method'] ? 'HEAD' : 'GET'; + curl_setopt($ch, \CURLOPT_CUSTOMREQUEST, $info['http_method']); + } + + if (null === $info['redirect_url'] = $resolveRedirect($ch, $location, $noContent)) { + $options['max_redirects'] = curl_getinfo($ch, \CURLINFO_REDIRECT_COUNT); + curl_setopt($ch, \CURLOPT_FOLLOWLOCATION, false); + curl_setopt($ch, \CURLOPT_MAXREDIRS, $options['max_redirects']); + } else { + $url = parse_url($location ?? ':'); + + if (isset($url['host']) && null !== $ip = $multi->dnsCache->hostnames[$url['host'] = strtolower($url['host'])] ?? null) { + // Populate DNS cache for redirects if needed + $port = $url['port'] ?? ('http' === ($url['scheme'] ?? parse_url(curl_getinfo($ch, \CURLINFO_EFFECTIVE_URL), \PHP_URL_SCHEME)) ? 80 : 443); + curl_setopt($ch, \CURLOPT_RESOLVE, ["{$url['host']}:$port:$ip"]); + $multi->dnsCache->removals["-{$url['host']}:$port"] = "-{$url['host']}:$port"; + } + } + } + + if (401 === $statusCode && isset($options['auth_ntlm']) && 0 === strncasecmp($headers['www-authenticate'][0] ?? '', 'NTLM ', 5)) { + // Continue with NTLM auth + } elseif ($statusCode < 300 || 400 <= $statusCode || null === $location || curl_getinfo($ch, \CURLINFO_REDIRECT_COUNT) === $options['max_redirects']) { + // Headers and redirects completed, time to get the response's content + $multi->handlesActivity[$id][] = new FirstChunk(); + + if ('HEAD' === $info['http_method'] || \in_array($statusCode, [204, 304], true)) { + $waitFor = '_0'; // no content expected + $multi->handlesActivity[$id][] = null; + $multi->handlesActivity[$id][] = null; + } else { + $waitFor[0] = 'C'; // C = content + } + + curl_setopt($ch, \CURLOPT_PRIVATE, $waitFor); + } elseif (null !== $info['redirect_url'] && $logger) { + $logger->info(sprintf('Redirecting: "%s %s"', $info['http_code'], $info['redirect_url'])); + } + + $location = null; + + return \strlen($data); + } +} diff --git a/sites/all/libraries/vendor/symfony/http-client/Response/HttplugPromise.php b/sites/all/libraries/vendor/symfony/http-client/Response/HttplugPromise.php new file mode 100644 index 0000000000..2efacca763 --- /dev/null +++ b/sites/all/libraries/vendor/symfony/http-client/Response/HttplugPromise.php @@ -0,0 +1,80 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient\Response; + +use GuzzleHttp\Promise\Create; +use GuzzleHttp\Promise\PromiseInterface as GuzzlePromiseInterface; +use Http\Promise\Promise as HttplugPromiseInterface; +use Psr\Http\Message\ResponseInterface as Psr7ResponseInterface; + +/** + * @author Tobias Nyholm + * + * @internal + */ +final class HttplugPromise implements HttplugPromiseInterface +{ + private $promise; + + public function __construct(GuzzlePromiseInterface $promise) + { + $this->promise = $promise; + } + + public function then(callable $onFulfilled = null, callable $onRejected = null): self + { + return new self($this->promise->then( + $this->wrapThenCallback($onFulfilled), + $this->wrapThenCallback($onRejected) + )); + } + + public function cancel(): void + { + $this->promise->cancel(); + } + + /** + * {@inheritdoc} + */ + public function getState(): string + { + return $this->promise->getState(); + } + + /** + * {@inheritdoc} + * + * @return Psr7ResponseInterface|mixed + */ + public function wait($unwrap = true) + { + $result = $this->promise->wait($unwrap); + + while ($result instanceof HttplugPromiseInterface || $result instanceof GuzzlePromiseInterface) { + $result = $result->wait($unwrap); + } + + return $result; + } + + private function wrapThenCallback(?callable $callback): ?callable + { + if (null === $callback) { + return null; + } + + return static function ($value) use ($callback) { + return Create::promiseFor($callback($value)); + }; + } +} diff --git a/sites/all/libraries/vendor/symfony/http-client/Response/MockResponse.php b/sites/all/libraries/vendor/symfony/http-client/Response/MockResponse.php new file mode 100644 index 0000000000..6420aa05de --- /dev/null +++ b/sites/all/libraries/vendor/symfony/http-client/Response/MockResponse.php @@ -0,0 +1,339 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient\Response; + +use Symfony\Component\HttpClient\Chunk\ErrorChunk; +use Symfony\Component\HttpClient\Chunk\FirstChunk; +use Symfony\Component\HttpClient\Exception\InvalidArgumentException; +use Symfony\Component\HttpClient\Exception\TransportException; +use Symfony\Component\HttpClient\Internal\ClientState; +use Symfony\Contracts\HttpClient\ResponseInterface; + +/** + * A test-friendly response. + * + * @author Nicolas Grekas + */ +class MockResponse implements ResponseInterface, StreamableInterface +{ + use CommonResponseTrait; + use TransportResponseTrait { + doDestruct as public __destruct; + } + + private $body; + private $requestOptions = []; + private $requestUrl; + private $requestMethod; + + private static $mainMulti; + private static $idSequence = 0; + + /** + * @param string|string[]|iterable $body The response body as a string or an iterable of strings, + * yielding an empty string simulates an idle timeout, + * throwing an exception yields an ErrorChunk + * + * @see ResponseInterface::getInfo() for possible info, e.g. "response_headers" + */ + public function __construct($body = '', array $info = []) + { + $this->body = is_iterable($body) ? $body : (string) $body; + $this->info = $info + ['http_code' => 200] + $this->info; + + if (!isset($info['response_headers'])) { + return; + } + + $responseHeaders = []; + + foreach ($info['response_headers'] as $k => $v) { + foreach ((array) $v as $v) { + $responseHeaders[] = (\is_string($k) ? $k.': ' : '').$v; + } + } + + $this->info['response_headers'] = []; + self::addResponseHeaders($responseHeaders, $this->info, $this->headers); + } + + /** + * Returns the options used when doing the request. + */ + public function getRequestOptions(): array + { + return $this->requestOptions; + } + + /** + * Returns the URL used when doing the request. + */ + public function getRequestUrl(): string + { + return $this->requestUrl; + } + + /** + * Returns the method used when doing the request. + */ + public function getRequestMethod(): string + { + return $this->requestMethod; + } + + /** + * {@inheritdoc} + */ + public function getInfo(string $type = null) + { + return null !== $type ? $this->info[$type] ?? null : $this->info; + } + + /** + * {@inheritdoc} + */ + public function cancel(): void + { + $this->info['canceled'] = true; + $this->info['error'] = 'Response has been canceled.'; + try { + $this->body = null; + } catch (TransportException $e) { + // ignore errors when canceling + } + } + + /** + * {@inheritdoc} + */ + protected function close(): void + { + $this->inflate = null; + $this->body = []; + } + + /** + * @internal + */ + public static function fromRequest(string $method, string $url, array $options, ResponseInterface $mock): self + { + $response = new self([]); + $response->requestOptions = $options; + $response->id = ++self::$idSequence; + $response->shouldBuffer = $options['buffer'] ?? true; + $response->initializer = static function (self $response) { + return \is_array($response->body[0] ?? null); + }; + + $response->info['redirect_count'] = 0; + $response->info['redirect_url'] = null; + $response->info['start_time'] = microtime(true); + $response->info['http_method'] = $method; + $response->info['http_code'] = 0; + $response->info['user_data'] = $options['user_data'] ?? null; + $response->info['max_duration'] = $options['max_duration'] ?? null; + $response->info['url'] = $url; + + if ($mock instanceof self) { + $mock->requestOptions = $response->requestOptions; + $mock->requestMethod = $method; + $mock->requestUrl = $url; + } + + self::writeRequest($response, $options, $mock); + $response->body[] = [$options, $mock]; + + return $response; + } + + /** + * {@inheritdoc} + */ + protected static function schedule(self $response, array &$runningResponses): void + { + if (!$response->id) { + throw new InvalidArgumentException('MockResponse instances must be issued by MockHttpClient before processing.'); + } + + $multi = self::$mainMulti ?? self::$mainMulti = new ClientState(); + + if (!isset($runningResponses[0])) { + $runningResponses[0] = [$multi, []]; + } + + $runningResponses[0][1][$response->id] = $response; + } + + /** + * {@inheritdoc} + */ + protected static function perform(ClientState $multi, array &$responses): void + { + foreach ($responses as $response) { + $id = $response->id; + + if (null === $response->body) { + // Canceled response + $response->body = []; + } elseif ([] === $response->body) { + // Error chunk + $multi->handlesActivity[$id][] = null; + $multi->handlesActivity[$id][] = null !== $response->info['error'] ? new TransportException($response->info['error']) : null; + } elseif (null === $chunk = array_shift($response->body)) { + // Last chunk + $multi->handlesActivity[$id][] = null; + $multi->handlesActivity[$id][] = array_shift($response->body); + } elseif (\is_array($chunk)) { + // First chunk + try { + $offset = 0; + $chunk[1]->getStatusCode(); + $chunk[1]->getHeaders(false); + self::readResponse($response, $chunk[0], $chunk[1], $offset); + $multi->handlesActivity[$id][] = new FirstChunk(); + $buffer = $response->requestOptions['buffer'] ?? null; + + if ($buffer instanceof \Closure && $response->content = $buffer($response->headers) ?: null) { + $response->content = \is_resource($response->content) ? $response->content : fopen('php://temp', 'w+'); + } + } catch (\Throwable $e) { + $multi->handlesActivity[$id][] = null; + $multi->handlesActivity[$id][] = $e; + } + } elseif ($chunk instanceof \Throwable) { + $multi->handlesActivity[$id][] = null; + $multi->handlesActivity[$id][] = $chunk; + } else { + // Data or timeout chunk + $multi->handlesActivity[$id][] = $chunk; + } + } + } + + /** + * {@inheritdoc} + */ + protected static function select(ClientState $multi, float $timeout): int + { + return 42; + } + + /** + * Simulates sending the request. + */ + private static function writeRequest(self $response, array $options, ResponseInterface $mock) + { + $onProgress = $options['on_progress'] ?? static function () {}; + $response->info += $mock->getInfo() ?: []; + + // simulate "size_upload" if it is set + if (isset($response->info['size_upload'])) { + $response->info['size_upload'] = 0.0; + } + + // simulate "total_time" if it is not set + if (!isset($response->info['total_time'])) { + $response->info['total_time'] = microtime(true) - $response->info['start_time']; + } + + // "notify" DNS resolution + $onProgress(0, 0, $response->info); + + // consume the request body + if (\is_resource($body = $options['body'] ?? '')) { + $data = stream_get_contents($body); + if (isset($response->info['size_upload'])) { + $response->info['size_upload'] += \strlen($data); + } + } elseif ($body instanceof \Closure) { + while ('' !== $data = $body(16372)) { + if (!\is_string($data)) { + throw new TransportException(sprintf('Return value of the "body" option callback must be string, "%s" returned.', get_debug_type($data))); + } + + // "notify" upload progress + if (isset($response->info['size_upload'])) { + $response->info['size_upload'] += \strlen($data); + } + + $onProgress(0, 0, $response->info); + } + } + } + + /** + * Simulates reading the response. + */ + private static function readResponse(self $response, array $options, ResponseInterface $mock, int &$offset) + { + $onProgress = $options['on_progress'] ?? static function () {}; + + // populate info related to headers + $info = $mock->getInfo() ?: []; + $response->info['http_code'] = ($info['http_code'] ?? 0) ?: $mock->getStatusCode() ?: 200; + $response->addResponseHeaders($info['response_headers'] ?? [], $response->info, $response->headers); + $dlSize = isset($response->headers['content-encoding']) || 'HEAD' === $response->info['http_method'] || \in_array($response->info['http_code'], [204, 304], true) ? 0 : (int) ($response->headers['content-length'][0] ?? 0); + + $response->info = [ + 'start_time' => $response->info['start_time'], + 'user_data' => $response->info['user_data'], + 'max_duration' => $response->info['max_duration'], + 'http_code' => $response->info['http_code'], + ] + $info + $response->info; + + if (null !== $response->info['error']) { + throw new TransportException($response->info['error']); + } + + if (!isset($response->info['total_time'])) { + $response->info['total_time'] = microtime(true) - $response->info['start_time']; + } + + // "notify" headers arrival + $onProgress(0, $dlSize, $response->info); + + // cast response body to activity list + $body = $mock instanceof self ? $mock->body : $mock->getContent(false); + + if (!\is_string($body)) { + try { + foreach ($body as $chunk) { + if ('' === $chunk = (string) $chunk) { + // simulate an idle timeout + $response->body[] = new ErrorChunk($offset, sprintf('Idle timeout reached for "%s".', $response->info['url'])); + } else { + $response->body[] = $chunk; + $offset += \strlen($chunk); + // "notify" download progress + $onProgress($offset, $dlSize, $response->info); + } + } + } catch (\Throwable $e) { + $response->body[] = $e; + } + } elseif ('' !== $body) { + $response->body[] = $body; + $offset = \strlen($body); + } + + if (!isset($response->info['total_time'])) { + $response->info['total_time'] = microtime(true) - $response->info['start_time']; + } + + // "notify" completion + $onProgress($offset, $dlSize, $response->info); + + if ($dlSize && $offset !== $dlSize) { + throw new TransportException(sprintf('Transfer closed with %d bytes remaining to read.', $dlSize - $offset)); + } + } +} diff --git a/sites/all/libraries/vendor/symfony/http-client/Response/NativeResponse.php b/sites/all/libraries/vendor/symfony/http-client/Response/NativeResponse.php new file mode 100644 index 0000000000..c00e946f63 --- /dev/null +++ b/sites/all/libraries/vendor/symfony/http-client/Response/NativeResponse.php @@ -0,0 +1,376 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient\Response; + +use Psr\Log\LoggerInterface; +use Symfony\Component\HttpClient\Chunk\FirstChunk; +use Symfony\Component\HttpClient\Exception\TransportException; +use Symfony\Component\HttpClient\Internal\Canary; +use Symfony\Component\HttpClient\Internal\ClientState; +use Symfony\Component\HttpClient\Internal\NativeClientState; +use Symfony\Contracts\HttpClient\ResponseInterface; + +/** + * @author Nicolas Grekas + * + * @internal + */ +final class NativeResponse implements ResponseInterface, StreamableInterface +{ + use CommonResponseTrait; + use TransportResponseTrait; + + private $context; + private $url; + private $resolver; + private $onProgress; + private $remaining; + private $buffer; + private $multi; + private $pauseExpiry = 0; + + /** + * @internal + */ + public function __construct(NativeClientState $multi, $context, string $url, array $options, array &$info, callable $resolver, ?callable $onProgress, ?LoggerInterface $logger) + { + $this->multi = $multi; + $this->id = $id = (int) $context; + $this->context = $context; + $this->url = $url; + $this->logger = $logger; + $this->timeout = $options['timeout']; + $this->info = &$info; + $this->resolver = $resolver; + $this->onProgress = $onProgress; + $this->inflate = !isset($options['normalized_headers']['accept-encoding']); + $this->shouldBuffer = $options['buffer'] ?? true; + + // Temporary resource to dechunk the response stream + $this->buffer = fopen('php://temp', 'w+'); + + $info['user_data'] = $options['user_data']; + $info['max_duration'] = $options['max_duration']; + ++$multi->responseCount; + + $this->initializer = static function (self $response) { + return null === $response->remaining; + }; + + $pauseExpiry = &$this->pauseExpiry; + $info['pause_handler'] = static function (float $duration) use (&$pauseExpiry) { + $pauseExpiry = 0 < $duration ? microtime(true) + $duration : 0; + }; + + $this->canary = new Canary(static function () use ($multi, $id) { + if (null !== ($host = $multi->openHandles[$id][6] ?? null) && 0 >= --$multi->hosts[$host]) { + unset($multi->hosts[$host]); + } + unset($multi->openHandles[$id], $multi->handlesActivity[$id]); + }); + } + + /** + * {@inheritdoc} + */ + public function getInfo(string $type = null) + { + if (!$info = $this->finalInfo) { + $info = $this->info; + $info['url'] = implode('', $info['url']); + unset($info['size_body'], $info['request_header']); + + if (null === $this->buffer) { + $this->finalInfo = $info; + } + } + + return null !== $type ? $info[$type] ?? null : $info; + } + + public function __destruct() + { + try { + $this->doDestruct(); + } finally { + // Clear the DNS cache when all requests completed + if (0 >= --$this->multi->responseCount) { + $this->multi->responseCount = 0; + $this->multi->dnsCache = []; + } + } + } + + private function open(): void + { + $url = $this->url; + + set_error_handler(function ($type, $msg) use (&$url) { + if (\E_NOTICE !== $type || 'fopen(): Content-type not specified assuming application/x-www-form-urlencoded' !== $msg) { + throw new TransportException($msg); + } + + $this->logger && $this->logger->info(sprintf('%s for "%s".', $msg, $url ?? $this->url)); + }); + + try { + $this->info['start_time'] = microtime(true); + + [$resolver, $url] = ($this->resolver)($this->multi); + + while (true) { + $context = stream_context_get_options($this->context); + + if ($proxy = $context['http']['proxy'] ?? null) { + $this->info['debug'] .= "* Establish HTTP proxy tunnel to {$proxy}\n"; + $this->info['request_header'] = $url; + } else { + $this->info['debug'] .= "* Trying {$this->info['primary_ip']}...\n"; + $this->info['request_header'] = $this->info['url']['path'].$this->info['url']['query']; + } + + $this->info['request_header'] = sprintf("> %s %s HTTP/%s \r\n", $context['http']['method'], $this->info['request_header'], $context['http']['protocol_version']); + $this->info['request_header'] .= implode("\r\n", $context['http']['header'])."\r\n\r\n"; + + if (\array_key_exists('peer_name', $context['ssl']) && null === $context['ssl']['peer_name']) { + unset($context['ssl']['peer_name']); + $this->context = stream_context_create([], ['options' => $context] + stream_context_get_params($this->context)); + } + + // Send request and follow redirects when needed + $this->handle = $h = fopen($url, 'r', false, $this->context); + self::addResponseHeaders(stream_get_meta_data($h)['wrapper_data'], $this->info, $this->headers, $this->info['debug']); + $url = $resolver($this->multi, $this->headers['location'][0] ?? null, $this->context); + + if (null === $url) { + break; + } + + $this->logger && $this->logger->info(sprintf('Redirecting: "%s %s"', $this->info['http_code'], $url ?? $this->url)); + } + } catch (\Throwable $e) { + $this->close(); + $this->multi->handlesActivity[$this->id][] = null; + $this->multi->handlesActivity[$this->id][] = $e; + + return; + } finally { + $this->info['pretransfer_time'] = $this->info['total_time'] = microtime(true) - $this->info['start_time']; + restore_error_handler(); + } + + if (isset($context['ssl']['capture_peer_cert_chain']) && isset(($context = stream_context_get_options($this->context))['ssl']['peer_certificate_chain'])) { + $this->info['peer_certificate_chain'] = $context['ssl']['peer_certificate_chain']; + } + + stream_set_blocking($h, false); + $this->context = $this->resolver = null; + + // Create dechunk buffers + if (isset($this->headers['content-length'])) { + $this->remaining = (int) $this->headers['content-length'][0]; + } elseif ('chunked' === ($this->headers['transfer-encoding'][0] ?? null)) { + stream_filter_append($this->buffer, 'dechunk', \STREAM_FILTER_WRITE); + $this->remaining = -1; + } else { + $this->remaining = -2; + } + + $this->multi->handlesActivity[$this->id] = [new FirstChunk()]; + + if ('HEAD' === $context['http']['method'] || \in_array($this->info['http_code'], [204, 304], true)) { + $this->multi->handlesActivity[$this->id][] = null; + $this->multi->handlesActivity[$this->id][] = null; + + return; + } + + $host = parse_url($this->info['redirect_url'] ?? $this->url, \PHP_URL_HOST); + $this->multi->lastTimeout = null; + $this->multi->openHandles[$this->id] = [&$this->pauseExpiry, $h, $this->buffer, $this->onProgress, &$this->remaining, &$this->info, $host]; + $this->multi->hosts[$host] = 1 + ($this->multi->hosts[$host] ?? 0); + } + + /** + * {@inheritdoc} + */ + private function close(): void + { + $this->canary->cancel(); + $this->handle = $this->buffer = $this->inflate = $this->onProgress = null; + } + + /** + * {@inheritdoc} + */ + private static function schedule(self $response, array &$runningResponses): void + { + if (!isset($runningResponses[$i = $response->multi->id])) { + $runningResponses[$i] = [$response->multi, []]; + } + + $runningResponses[$i][1][$response->id] = $response; + + if (null === $response->buffer) { + // Response already completed + $response->multi->handlesActivity[$response->id][] = null; + $response->multi->handlesActivity[$response->id][] = null !== $response->info['error'] ? new TransportException($response->info['error']) : null; + } + } + + /** + * {@inheritdoc} + * + * @param NativeClientState $multi + */ + private static function perform(ClientState $multi, array &$responses = null): void + { + foreach ($multi->openHandles as $i => [$pauseExpiry, $h, $buffer, $onProgress]) { + if ($pauseExpiry) { + if (microtime(true) < $pauseExpiry) { + continue; + } + + $multi->openHandles[$i][0] = 0; + } + + $hasActivity = false; + $remaining = &$multi->openHandles[$i][4]; + $info = &$multi->openHandles[$i][5]; + $e = null; + + // Read incoming buffer and write it to the dechunk one + try { + if ($remaining && '' !== $data = (string) fread($h, 0 > $remaining ? 16372 : $remaining)) { + fwrite($buffer, $data); + $hasActivity = true; + $multi->sleep = false; + + if (-1 !== $remaining) { + $remaining -= \strlen($data); + } + } + } catch (\Throwable $e) { + $hasActivity = $onProgress = false; + } + + if (!$hasActivity) { + if ($onProgress) { + try { + // Notify the progress callback so that it can e.g. cancel + // the request if the stream is inactive for too long + $info['total_time'] = microtime(true) - $info['start_time']; + $onProgress(); + } catch (\Throwable $e) { + // no-op + } + } + } elseif ('' !== $data = stream_get_contents($buffer, -1, 0)) { + rewind($buffer); + ftruncate($buffer, 0); + + if (null === $e) { + $multi->handlesActivity[$i][] = $data; + } + } + + if (null !== $e || !$remaining || feof($h)) { + // Stream completed + $info['total_time'] = microtime(true) - $info['start_time']; + $info['starttransfer_time'] = $info['starttransfer_time'] ?: $info['total_time']; + + if ($onProgress) { + try { + $onProgress(-1); + } catch (\Throwable $e) { + // no-op + } + } + + if (null === $e) { + if (0 < $remaining) { + $e = new TransportException(sprintf('Transfer closed with %s bytes remaining to read.', $remaining)); + } elseif (-1 === $remaining && fwrite($buffer, '-') && '' !== stream_get_contents($buffer, -1, 0)) { + $e = new TransportException('Transfer closed with outstanding data remaining from chunked response.'); + } + } + + $multi->handlesActivity[$i][] = null; + $multi->handlesActivity[$i][] = $e; + if (null !== ($host = $multi->openHandles[$i][6] ?? null) && 0 >= --$multi->hosts[$host]) { + unset($multi->hosts[$host]); + } + unset($multi->openHandles[$i]); + $multi->sleep = false; + } + } + + if (null === $responses) { + return; + } + + $maxHosts = $multi->maxHostConnections; + + foreach ($responses as $i => $response) { + if (null !== $response->remaining || null === $response->buffer) { + continue; + } + + if ($response->pauseExpiry && microtime(true) < $response->pauseExpiry) { + // Create empty open handles to tell we still have pending requests + $multi->openHandles[$i] = [\INF, null, null, null]; + } elseif ($maxHosts && $maxHosts > ($multi->hosts[parse_url($response->url, \PHP_URL_HOST)] ?? 0)) { + // Open the next pending request - this is a blocking operation so we do only one of them + $response->open(); + $multi->sleep = false; + self::perform($multi); + $maxHosts = 0; + } + } + } + + /** + * {@inheritdoc} + * + * @param NativeClientState $multi + */ + private static function select(ClientState $multi, float $timeout): int + { + if (!$multi->sleep = !$multi->sleep) { + return -1; + } + + $_ = $handles = []; + $now = null; + + foreach ($multi->openHandles as [$pauseExpiry, $h]) { + if (null === $h) { + continue; + } + + if ($pauseExpiry && ($now ?? $now = microtime(true)) < $pauseExpiry) { + $timeout = min($timeout, $pauseExpiry - $now); + continue; + } + + $handles[] = $h; + } + + if (!$handles) { + usleep((int) (1E6 * $timeout)); + + return 0; + } + + return stream_select($handles, $_, $_, (int) $timeout, (int) (1E6 * ($timeout - (int) $timeout))); + } +} diff --git a/sites/all/libraries/vendor/symfony/http-client/Response/ResponseStream.php b/sites/all/libraries/vendor/symfony/http-client/Response/ResponseStream.php new file mode 100644 index 0000000000..f86d2d4077 --- /dev/null +++ b/sites/all/libraries/vendor/symfony/http-client/Response/ResponseStream.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient\Response; + +use Symfony\Contracts\HttpClient\ChunkInterface; +use Symfony\Contracts\HttpClient\ResponseInterface; +use Symfony\Contracts\HttpClient\ResponseStreamInterface; + +/** + * @author Nicolas Grekas + */ +final class ResponseStream implements ResponseStreamInterface +{ + private $generator; + + public function __construct(\Generator $generator) + { + $this->generator = $generator; + } + + public function key(): ResponseInterface + { + return $this->generator->key(); + } + + public function current(): ChunkInterface + { + return $this->generator->current(); + } + + public function next(): void + { + $this->generator->next(); + } + + public function rewind(): void + { + $this->generator->rewind(); + } + + public function valid(): bool + { + return $this->generator->valid(); + } +} diff --git a/sites/all/libraries/vendor/symfony/http-client/Response/StreamWrapper.php b/sites/all/libraries/vendor/symfony/http-client/Response/StreamWrapper.php new file mode 100644 index 0000000000..50a7c36623 --- /dev/null +++ b/sites/all/libraries/vendor/symfony/http-client/Response/StreamWrapper.php @@ -0,0 +1,313 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient\Response; + +use Symfony\Contracts\HttpClient\Exception\ExceptionInterface; +use Symfony\Contracts\HttpClient\HttpClientInterface; +use Symfony\Contracts\HttpClient\ResponseInterface; + +/** + * Allows turning ResponseInterface instances to PHP streams. + * + * @author Nicolas Grekas + */ +class StreamWrapper +{ + /** @var resource|null */ + public $context; + + /** @var HttpClientInterface */ + private $client; + + /** @var ResponseInterface */ + private $response; + + /** @var resource|string|null */ + private $content; + + /** @var resource|null */ + private $handle; + + private $blocking = true; + private $timeout; + private $eof = false; + private $offset = 0; + + /** + * Creates a PHP stream resource from a ResponseInterface. + * + * @return resource + */ + public static function createResource(ResponseInterface $response, HttpClientInterface $client = null) + { + if ($response instanceof StreamableInterface) { + $stack = debug_backtrace(\DEBUG_BACKTRACE_PROVIDE_OBJECT | \DEBUG_BACKTRACE_IGNORE_ARGS, 2); + + if ($response !== ($stack[1]['object'] ?? null)) { + return $response->toStream(false); + } + } + + if (null === $client && !method_exists($response, 'stream')) { + throw new \InvalidArgumentException(sprintf('Providing a client to "%s()" is required when the response doesn\'t have any "stream()" method.', __CLASS__)); + } + + static $registered = false; + + if (!$registered = $registered || stream_wrapper_register(strtr(__CLASS__, '\\', '-'), __CLASS__)) { + throw new \RuntimeException(error_get_last()['message'] ?? 'Registering the "symfony" stream wrapper failed.'); + } + + $context = [ + 'client' => $client ?? $response, + 'response' => $response, + ]; + + return fopen(strtr(__CLASS__, '\\', '-').'://'.$response->getInfo('url'), 'r', false, stream_context_create(['symfony' => $context])); + } + + public function getResponse(): ResponseInterface + { + return $this->response; + } + + /** + * @param resource|callable|null $handle The resource handle that should be monitored when + * stream_select() is used on the created stream + * @param resource|null $content The seekable resource where the response body is buffered + */ + public function bindHandles(&$handle, &$content): void + { + $this->handle = &$handle; + $this->content = &$content; + $this->offset = null; + } + + public function stream_open(string $path, string $mode, int $options): bool + { + if ('r' !== $mode) { + if ($options & \STREAM_REPORT_ERRORS) { + trigger_error(sprintf('Invalid mode "%s": only "r" is supported.', $mode), \E_USER_WARNING); + } + + return false; + } + + $context = stream_context_get_options($this->context)['symfony'] ?? null; + $this->client = $context['client'] ?? null; + $this->response = $context['response'] ?? null; + $this->context = null; + + if (null !== $this->client && null !== $this->response) { + return true; + } + + if ($options & \STREAM_REPORT_ERRORS) { + trigger_error('Missing options "client" or "response" in "symfony" stream context.', \E_USER_WARNING); + } + + return false; + } + + public function stream_read(int $count) + { + if (\is_resource($this->content)) { + // Empty the internal activity list + foreach ($this->client->stream([$this->response], 0) as $chunk) { + try { + if (!$chunk->isTimeout() && $chunk->isFirst()) { + $this->response->getStatusCode(); // ignore 3/4/5xx + } + } catch (ExceptionInterface $e) { + trigger_error($e->getMessage(), \E_USER_WARNING); + + return false; + } + } + + if (0 !== fseek($this->content, $this->offset ?? 0)) { + return false; + } + + if ('' !== $data = fread($this->content, $count)) { + fseek($this->content, 0, \SEEK_END); + $this->offset += \strlen($data); + + return $data; + } + } + + if (\is_string($this->content)) { + if (\strlen($this->content) <= $count) { + $data = $this->content; + $this->content = null; + } else { + $data = substr($this->content, 0, $count); + $this->content = substr($this->content, $count); + } + $this->offset += \strlen($data); + + return $data; + } + + foreach ($this->client->stream([$this->response], $this->blocking ? $this->timeout : 0) as $chunk) { + try { + $this->eof = true; + $this->eof = !$chunk->isTimeout(); + + if (!$this->eof && !$this->blocking) { + return ''; + } + + $this->eof = $chunk->isLast(); + + if ($chunk->isFirst()) { + $this->response->getStatusCode(); // ignore 3/4/5xx + } + + if ('' !== $data = $chunk->getContent()) { + if (\strlen($data) > $count) { + if (null === $this->content) { + $this->content = substr($data, $count); + } + $data = substr($data, 0, $count); + } + $this->offset += \strlen($data); + + return $data; + } + } catch (ExceptionInterface $e) { + trigger_error($e->getMessage(), \E_USER_WARNING); + + return false; + } + } + + return ''; + } + + public function stream_set_option(int $option, int $arg1, ?int $arg2): bool + { + if (\STREAM_OPTION_BLOCKING === $option) { + $this->blocking = (bool) $arg1; + } elseif (\STREAM_OPTION_READ_TIMEOUT === $option) { + $this->timeout = $arg1 + $arg2 / 1e6; + } else { + return false; + } + + return true; + } + + public function stream_tell(): int + { + return $this->offset ?? 0; + } + + public function stream_eof(): bool + { + return $this->eof && !\is_string($this->content); + } + + public function stream_seek(int $offset, int $whence = \SEEK_SET): bool + { + if (null === $this->content && null === $this->offset) { + $this->response->getStatusCode(); + $this->offset = 0; + } + + if (!\is_resource($this->content) || 0 !== fseek($this->content, 0, \SEEK_END)) { + return false; + } + + $size = ftell($this->content); + + if (\SEEK_CUR === $whence) { + $offset += $this->offset ?? 0; + } + + if (\SEEK_END === $whence || $size < $offset) { + foreach ($this->client->stream([$this->response]) as $chunk) { + try { + if ($chunk->isFirst()) { + $this->response->getStatusCode(); // ignore 3/4/5xx + } + + // Chunks are buffered in $this->content already + $size += \strlen($chunk->getContent()); + + if (\SEEK_END !== $whence && $offset <= $size) { + break; + } + } catch (ExceptionInterface $e) { + trigger_error($e->getMessage(), \E_USER_WARNING); + + return false; + } + } + + if (\SEEK_END === $whence) { + $offset += $size; + } + } + + if (0 <= $offset && $offset <= $size) { + $this->eof = false; + $this->offset = $offset; + + return true; + } + + return false; + } + + public function stream_cast(int $castAs) + { + if (\STREAM_CAST_FOR_SELECT === $castAs) { + $this->response->getHeaders(false); + + return (\is_callable($this->handle) ? ($this->handle)() : $this->handle) ?? false; + } + + return false; + } + + public function stream_stat(): array + { + try { + $headers = $this->response->getHeaders(false); + } catch (ExceptionInterface $e) { + trigger_error($e->getMessage(), \E_USER_WARNING); + $headers = []; + } + + return [ + 'dev' => 0, + 'ino' => 0, + 'mode' => 33060, + 'nlink' => 0, + 'uid' => 0, + 'gid' => 0, + 'rdev' => 0, + 'size' => (int) ($headers['content-length'][0] ?? -1), + 'atime' => 0, + 'mtime' => strtotime($headers['last-modified'][0] ?? '') ?: 0, + 'ctime' => 0, + 'blksize' => 0, + 'blocks' => 0, + ]; + } + + private function __construct() + { + } +} diff --git a/sites/all/libraries/vendor/symfony/http-client/Response/StreamableInterface.php b/sites/all/libraries/vendor/symfony/http-client/Response/StreamableInterface.php new file mode 100644 index 0000000000..eb1f9335c7 --- /dev/null +++ b/sites/all/libraries/vendor/symfony/http-client/Response/StreamableInterface.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient\Response; + +use Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface; +use Symfony\Contracts\HttpClient\Exception\RedirectionExceptionInterface; +use Symfony\Contracts\HttpClient\Exception\ServerExceptionInterface; +use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface; + +/** + * @author Nicolas Grekas + */ +interface StreamableInterface +{ + /** + * Casts the response to a PHP stream resource. + * + * @return resource + * + * @throws TransportExceptionInterface When a network error occurs + * @throws RedirectionExceptionInterface On a 3xx when $throw is true and the "max_redirects" option has been reached + * @throws ClientExceptionInterface On a 4xx when $throw is true + * @throws ServerExceptionInterface On a 5xx when $throw is true + */ + public function toStream(bool $throw = true); +} diff --git a/sites/all/libraries/vendor/symfony/http-client/Response/TraceableResponse.php b/sites/all/libraries/vendor/symfony/http-client/Response/TraceableResponse.php new file mode 100644 index 0000000000..d656c0a5f9 --- /dev/null +++ b/sites/all/libraries/vendor/symfony/http-client/Response/TraceableResponse.php @@ -0,0 +1,219 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient\Response; + +use Symfony\Component\HttpClient\Chunk\ErrorChunk; +use Symfony\Component\HttpClient\Exception\ClientException; +use Symfony\Component\HttpClient\Exception\RedirectionException; +use Symfony\Component\HttpClient\Exception\ServerException; +use Symfony\Component\HttpClient\TraceableHttpClient; +use Symfony\Component\Stopwatch\StopwatchEvent; +use Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface; +use Symfony\Contracts\HttpClient\Exception\RedirectionExceptionInterface; +use Symfony\Contracts\HttpClient\Exception\ServerExceptionInterface; +use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface; +use Symfony\Contracts\HttpClient\HttpClientInterface; +use Symfony\Contracts\HttpClient\ResponseInterface; + +/** + * @author Nicolas Grekas + * + * @internal + */ +class TraceableResponse implements ResponseInterface, StreamableInterface +{ + private $client; + private $response; + private $content; + private $event; + + public function __construct(HttpClientInterface $client, ResponseInterface $response, &$content, StopwatchEvent $event = null) + { + $this->client = $client; + $this->response = $response; + $this->content = &$content; + $this->event = $event; + } + + public function __sleep(): array + { + throw new \BadMethodCallException('Cannot serialize '.__CLASS__); + } + + public function __wakeup() + { + throw new \BadMethodCallException('Cannot unserialize '.__CLASS__); + } + + public function __destruct() + { + try { + $this->response->__destruct(); + } finally { + if ($this->event && $this->event->isStarted()) { + $this->event->stop(); + } + } + } + + public function getStatusCode(): int + { + try { + return $this->response->getStatusCode(); + } finally { + if ($this->event && $this->event->isStarted()) { + $this->event->lap(); + } + } + } + + public function getHeaders(bool $throw = true): array + { + try { + return $this->response->getHeaders($throw); + } finally { + if ($this->event && $this->event->isStarted()) { + $this->event->lap(); + } + } + } + + public function getContent(bool $throw = true): string + { + try { + if (false === $this->content) { + return $this->response->getContent($throw); + } + + return $this->content = $this->response->getContent(false); + } finally { + if ($this->event && $this->event->isStarted()) { + $this->event->stop(); + } + if ($throw) { + $this->checkStatusCode($this->response->getStatusCode()); + } + } + } + + public function toArray(bool $throw = true): array + { + try { + if (false === $this->content) { + return $this->response->toArray($throw); + } + + return $this->content = $this->response->toArray(false); + } finally { + if ($this->event && $this->event->isStarted()) { + $this->event->stop(); + } + if ($throw) { + $this->checkStatusCode($this->response->getStatusCode()); + } + } + } + + public function cancel(): void + { + $this->response->cancel(); + + if ($this->event && $this->event->isStarted()) { + $this->event->stop(); + } + } + + public function getInfo(string $type = null) + { + return $this->response->getInfo($type); + } + + /** + * Casts the response to a PHP stream resource. + * + * @return resource + * + * @throws TransportExceptionInterface When a network error occurs + * @throws RedirectionExceptionInterface On a 3xx when $throw is true and the "max_redirects" option has been reached + * @throws ClientExceptionInterface On a 4xx when $throw is true + * @throws ServerExceptionInterface On a 5xx when $throw is true + */ + public function toStream(bool $throw = true) + { + if ($throw) { + // Ensure headers arrived + $this->response->getHeaders(true); + } + + if ($this->response instanceof StreamableInterface) { + return $this->response->toStream(false); + } + + return StreamWrapper::createResource($this->response, $this->client); + } + + /** + * @internal + */ + public static function stream(HttpClientInterface $client, iterable $responses, ?float $timeout): \Generator + { + $wrappedResponses = []; + $traceableMap = new \SplObjectStorage(); + + foreach ($responses as $r) { + if (!$r instanceof self) { + throw new \TypeError(sprintf('"%s::stream()" expects parameter 1 to be an iterable of TraceableResponse objects, "%s" given.', TraceableHttpClient::class, get_debug_type($r))); + } + + $traceableMap[$r->response] = $r; + $wrappedResponses[] = $r->response; + if ($r->event && !$r->event->isStarted()) { + $r->event->start(); + } + } + + foreach ($client->stream($wrappedResponses, $timeout) as $r => $chunk) { + if ($traceableMap[$r]->event && $traceableMap[$r]->event->isStarted()) { + try { + if ($chunk->isTimeout() || !$chunk->isLast()) { + $traceableMap[$r]->event->lap(); + } else { + $traceableMap[$r]->event->stop(); + } + } catch (TransportExceptionInterface $e) { + $traceableMap[$r]->event->stop(); + if ($chunk instanceof ErrorChunk) { + $chunk->didThrow(false); + } else { + $chunk = new ErrorChunk($chunk->getOffset(), $e); + } + } + } + yield $traceableMap[$r] => $chunk; + } + } + + private function checkStatusCode(int $code) + { + if (500 <= $code) { + throw new ServerException($this); + } + + if (400 <= $code) { + throw new ClientException($this); + } + + if (300 <= $code) { + throw new RedirectionException($this); + } + } +} diff --git a/sites/all/libraries/vendor/symfony/http-client/Response/TransportResponseTrait.php b/sites/all/libraries/vendor/symfony/http-client/Response/TransportResponseTrait.php new file mode 100644 index 0000000000..566d61e176 --- /dev/null +++ b/sites/all/libraries/vendor/symfony/http-client/Response/TransportResponseTrait.php @@ -0,0 +1,312 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient\Response; + +use Symfony\Component\HttpClient\Chunk\DataChunk; +use Symfony\Component\HttpClient\Chunk\ErrorChunk; +use Symfony\Component\HttpClient\Chunk\FirstChunk; +use Symfony\Component\HttpClient\Chunk\LastChunk; +use Symfony\Component\HttpClient\Exception\TransportException; +use Symfony\Component\HttpClient\Internal\ClientState; + +/** + * Implements common logic for transport-level response classes. + * + * @author Nicolas Grekas + * + * @internal + */ +trait TransportResponseTrait +{ + private $canary; + private $headers = []; + private $info = [ + 'response_headers' => [], + 'http_code' => 0, + 'error' => null, + 'canceled' => false, + ]; + + /** @var object|resource */ + private $handle; + private $id; + private $timeout = 0; + private $inflate; + private $finalInfo; + private $logger; + + /** + * {@inheritdoc} + */ + public function getStatusCode(): int + { + if ($this->initializer) { + self::initialize($this); + } + + return $this->info['http_code']; + } + + /** + * {@inheritdoc} + */ + public function getHeaders(bool $throw = true): array + { + if ($this->initializer) { + self::initialize($this); + } + + if ($throw) { + $this->checkStatusCode(); + } + + return $this->headers; + } + + /** + * {@inheritdoc} + */ + public function cancel(): void + { + $this->info['canceled'] = true; + $this->info['error'] = 'Response has been canceled.'; + $this->close(); + } + + /** + * Closes the response and all its network handles. + */ + protected function close(): void + { + $this->canary->cancel(); + $this->inflate = null; + } + + /** + * Adds pending responses to the activity list. + */ + abstract protected static function schedule(self $response, array &$runningResponses): void; + + /** + * Performs all pending non-blocking operations. + */ + abstract protected static function perform(ClientState $multi, array &$responses): void; + + /** + * Waits for network activity. + */ + abstract protected static function select(ClientState $multi, float $timeout): int; + + private static function addResponseHeaders(array $responseHeaders, array &$info, array &$headers, string &$debug = ''): void + { + foreach ($responseHeaders as $h) { + if (11 <= \strlen($h) && '/' === $h[4] && preg_match('#^HTTP/\d+(?:\.\d+)? (\d\d\d)(?: |$)#', $h, $m)) { + if ($headers) { + $debug .= "< \r\n"; + $headers = []; + } + $info['http_code'] = (int) $m[1]; + } elseif (2 === \count($m = explode(':', $h, 2))) { + $headers[strtolower($m[0])][] = ltrim($m[1]); + } + + $debug .= "< {$h}\r\n"; + $info['response_headers'][] = $h; + } + + $debug .= "< \r\n"; + } + + /** + * Ensures the request is always sent and that the response code was checked. + */ + private function doDestruct() + { + $this->shouldBuffer = true; + + if ($this->initializer && null === $this->info['error']) { + self::initialize($this); + $this->checkStatusCode(); + } + } + + /** + * Implements an event loop based on a buffer activity queue. + * + * @param iterable $responses + * + * @internal + */ + public static function stream(iterable $responses, float $timeout = null): \Generator + { + $runningResponses = []; + + foreach ($responses as $response) { + self::schedule($response, $runningResponses); + } + + $lastActivity = microtime(true); + $elapsedTimeout = 0; + + if ($fromLastTimeout = 0.0 === $timeout && '-0' === (string) $timeout) { + $timeout = null; + } elseif ($fromLastTimeout = 0 > $timeout) { + $timeout = -$timeout; + } + + while (true) { + $hasActivity = false; + $timeoutMax = 0; + $timeoutMin = $timeout ?? \INF; + + /** @var ClientState $multi */ + foreach ($runningResponses as $i => [$multi]) { + $responses = &$runningResponses[$i][1]; + self::perform($multi, $responses); + + foreach ($responses as $j => $response) { + $timeoutMax = $timeout ?? max($timeoutMax, $response->timeout); + $timeoutMin = min($timeoutMin, $response->timeout, 1); + $chunk = false; + + if ($fromLastTimeout && null !== $multi->lastTimeout) { + $elapsedTimeout = microtime(true) - $multi->lastTimeout; + } + + if (isset($multi->handlesActivity[$j])) { + $multi->lastTimeout = null; + } elseif (!isset($multi->openHandles[$j])) { + unset($responses[$j]); + continue; + } elseif ($elapsedTimeout >= $timeoutMax) { + $multi->handlesActivity[$j] = [new ErrorChunk($response->offset, sprintf('Idle timeout reached for "%s".', $response->getInfo('url')))]; + $multi->lastTimeout ?? $multi->lastTimeout = $lastActivity; + } else { + continue; + } + + while ($multi->handlesActivity[$j] ?? false) { + $hasActivity = true; + $elapsedTimeout = 0; + + if (\is_string($chunk = array_shift($multi->handlesActivity[$j]))) { + if (null !== $response->inflate && false === $chunk = @inflate_add($response->inflate, $chunk)) { + $multi->handlesActivity[$j] = [null, new TransportException(sprintf('Error while processing content unencoding for "%s".', $response->getInfo('url')))]; + continue; + } + + if ('' !== $chunk && null !== $response->content && \strlen($chunk) !== fwrite($response->content, $chunk)) { + $multi->handlesActivity[$j] = [null, new TransportException(sprintf('Failed writing %d bytes to the response buffer.', \strlen($chunk)))]; + continue; + } + + $chunkLen = \strlen($chunk); + $chunk = new DataChunk($response->offset, $chunk); + $response->offset += $chunkLen; + } elseif (null === $chunk) { + $e = $multi->handlesActivity[$j][0]; + unset($responses[$j], $multi->handlesActivity[$j]); + $response->close(); + + if (null !== $e) { + $response->info['error'] = $e->getMessage(); + + if ($e instanceof \Error) { + throw $e; + } + + $chunk = new ErrorChunk($response->offset, $e); + } else { + if (0 === $response->offset && null === $response->content) { + $response->content = fopen('php://memory', 'w+'); + } + + $chunk = new LastChunk($response->offset); + } + } elseif ($chunk instanceof ErrorChunk) { + unset($responses[$j]); + $elapsedTimeout = $timeoutMax; + } elseif ($chunk instanceof FirstChunk) { + if ($response->logger) { + $info = $response->getInfo(); + $response->logger->info(sprintf('Response: "%s %s"', $info['http_code'], $info['url'])); + } + + $response->inflate = \extension_loaded('zlib') && $response->inflate && 'gzip' === ($response->headers['content-encoding'][0] ?? null) ? inflate_init(\ZLIB_ENCODING_GZIP) : null; + + if ($response->shouldBuffer instanceof \Closure) { + try { + $response->shouldBuffer = ($response->shouldBuffer)($response->headers); + + if (null !== $response->info['error']) { + throw new TransportException($response->info['error']); + } + } catch (\Throwable $e) { + $response->close(); + $multi->handlesActivity[$j] = [null, $e]; + } + } + + if (true === $response->shouldBuffer) { + $response->content = fopen('php://temp', 'w+'); + } elseif (\is_resource($response->shouldBuffer)) { + $response->content = $response->shouldBuffer; + } + $response->shouldBuffer = null; + + yield $response => $chunk; + + if ($response->initializer && null === $response->info['error']) { + // Ensure the HTTP status code is always checked + $response->getHeaders(true); + } + + continue; + } + + yield $response => $chunk; + } + + unset($multi->handlesActivity[$j]); + + if ($chunk instanceof ErrorChunk && !$chunk->didThrow()) { + // Ensure transport exceptions are always thrown + $chunk->getContent(); + } + } + + if (!$responses) { + unset($runningResponses[$i]); + } + + // Prevent memory leaks + $multi->handlesActivity = $multi->handlesActivity ?: []; + $multi->openHandles = $multi->openHandles ?: []; + } + + if (!$runningResponses) { + break; + } + + if ($hasActivity) { + $lastActivity = microtime(true); + continue; + } + + if (-1 === self::select($multi, min($timeoutMin, $timeoutMax - $elapsedTimeout))) { + usleep(min(500, 1E6 * $timeoutMin)); + } + + $elapsedTimeout = microtime(true) - $lastActivity; + } + } +} diff --git a/sites/all/libraries/vendor/symfony/http-client/Retry/GenericRetryStrategy.php b/sites/all/libraries/vendor/symfony/http-client/Retry/GenericRetryStrategy.php new file mode 100644 index 0000000000..ebe10a2186 --- /dev/null +++ b/sites/all/libraries/vendor/symfony/http-client/Retry/GenericRetryStrategy.php @@ -0,0 +1,115 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient\Retry; + +use Symfony\Component\HttpClient\Exception\InvalidArgumentException; +use Symfony\Component\HttpClient\Response\AsyncContext; +use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface; + +/** + * Decides to retry the request when HTTP status codes belong to the given list of codes. + * + * @author Jérémy Derussé + */ +class GenericRetryStrategy implements RetryStrategyInterface +{ + public const IDEMPOTENT_METHODS = ['GET', 'HEAD', 'PUT', 'DELETE', 'OPTIONS', 'TRACE']; + public const DEFAULT_RETRY_STATUS_CODES = [ + 0 => self::IDEMPOTENT_METHODS, // for transport exceptions + 423, + 425, + 429, + 500 => self::IDEMPOTENT_METHODS, + 502, + 503, + 504 => self::IDEMPOTENT_METHODS, + 507 => self::IDEMPOTENT_METHODS, + 510 => self::IDEMPOTENT_METHODS, + ]; + + private $statusCodes; + private $delayMs; + private $multiplier; + private $maxDelayMs; + private $jitter; + + /** + * @param array $statusCodes List of HTTP status codes that trigger a retry + * @param int $delayMs Amount of time to delay (or the initial value when multiplier is used) + * @param float $multiplier Multiplier to apply to the delay each time a retry occurs + * @param int $maxDelayMs Maximum delay to allow (0 means no maximum) + * @param float $jitter Probability of randomness int delay (0 = none, 1 = 100% random) + */ + public function __construct(array $statusCodes = self::DEFAULT_RETRY_STATUS_CODES, int $delayMs = 1000, float $multiplier = 2.0, int $maxDelayMs = 0, float $jitter = 0.1) + { + $this->statusCodes = $statusCodes; + + if ($delayMs < 0) { + throw new InvalidArgumentException(sprintf('Delay must be greater than or equal to zero: "%s" given.', $delayMs)); + } + $this->delayMs = $delayMs; + + if ($multiplier < 1) { + throw new InvalidArgumentException(sprintf('Multiplier must be greater than or equal to one: "%s" given.', $multiplier)); + } + $this->multiplier = $multiplier; + + if ($maxDelayMs < 0) { + throw new InvalidArgumentException(sprintf('Max delay must be greater than or equal to zero: "%s" given.', $maxDelayMs)); + } + $this->maxDelayMs = $maxDelayMs; + + if ($jitter < 0 || $jitter > 1) { + throw new InvalidArgumentException(sprintf('Jitter must be between 0 and 1: "%s" given.', $jitter)); + } + $this->jitter = $jitter; + } + + public function shouldRetry(AsyncContext $context, ?string $responseContent, ?TransportExceptionInterface $exception): ?bool + { + $statusCode = $context->getStatusCode(); + if (\in_array($statusCode, $this->statusCodes, true)) { + return true; + } + if (isset($this->statusCodes[$statusCode]) && \is_array($this->statusCodes[$statusCode])) { + return \in_array($context->getInfo('http_method'), $this->statusCodes[$statusCode], true); + } + if (null === $exception) { + return false; + } + + if (\in_array(0, $this->statusCodes, true)) { + return true; + } + if (isset($this->statusCodes[0]) && \is_array($this->statusCodes[0])) { + return \in_array($context->getInfo('http_method'), $this->statusCodes[0], true); + } + + return false; + } + + public function getDelay(AsyncContext $context, ?string $responseContent, ?TransportExceptionInterface $exception): int + { + $delay = $this->delayMs * $this->multiplier ** $context->getInfo('retry_count'); + + if ($this->jitter > 0) { + $randomness = $delay * $this->jitter; + $delay = $delay + random_int(-$randomness, +$randomness); + } + + if ($delay > $this->maxDelayMs && 0 !== $this->maxDelayMs) { + return $this->maxDelayMs; + } + + return (int) $delay; + } +} diff --git a/sites/all/libraries/vendor/symfony/http-client/Retry/RetryStrategyInterface.php b/sites/all/libraries/vendor/symfony/http-client/Retry/RetryStrategyInterface.php new file mode 100644 index 0000000000..25764336ea --- /dev/null +++ b/sites/all/libraries/vendor/symfony/http-client/Retry/RetryStrategyInterface.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient\Retry; + +use Symfony\Component\HttpClient\Response\AsyncContext; +use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface; + +/** + * @author Jérémy Derussé + * @author Nicolas Grekas + */ +interface RetryStrategyInterface +{ + /** + * Returns whether the request should be retried. + * + * @param ?string $responseContent Null is passed when the body did not arrive yet + * + * @return bool|null Returns null to signal that the body is required to take a decision + */ + public function shouldRetry(AsyncContext $context, ?string $responseContent, ?TransportExceptionInterface $exception): ?bool; + + /** + * Returns the time to wait in milliseconds. + */ + public function getDelay(AsyncContext $context, ?string $responseContent, ?TransportExceptionInterface $exception): int; +} diff --git a/sites/all/libraries/vendor/symfony/http-client/RetryableHttpClient.php b/sites/all/libraries/vendor/symfony/http-client/RetryableHttpClient.php new file mode 100644 index 0000000000..bec13784b1 --- /dev/null +++ b/sites/all/libraries/vendor/symfony/http-client/RetryableHttpClient.php @@ -0,0 +1,171 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient; + +use Psr\Log\LoggerInterface; +use Psr\Log\NullLogger; +use Symfony\Component\HttpClient\Response\AsyncContext; +use Symfony\Component\HttpClient\Response\AsyncResponse; +use Symfony\Component\HttpClient\Retry\GenericRetryStrategy; +use Symfony\Component\HttpClient\Retry\RetryStrategyInterface; +use Symfony\Contracts\HttpClient\ChunkInterface; +use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface; +use Symfony\Contracts\HttpClient\HttpClientInterface; +use Symfony\Contracts\HttpClient\ResponseInterface; +use Symfony\Contracts\Service\ResetInterface; + +/** + * Automatically retries failing HTTP requests. + * + * @author Jérémy Derussé + */ +class RetryableHttpClient implements HttpClientInterface, ResetInterface +{ + use AsyncDecoratorTrait; + + private $strategy; + private $maxRetries; + private $logger; + + /** + * @param int $maxRetries The maximum number of times to retry + */ + public function __construct(HttpClientInterface $client, RetryStrategyInterface $strategy = null, int $maxRetries = 3, LoggerInterface $logger = null) + { + $this->client = $client; + $this->strategy = $strategy ?? new GenericRetryStrategy(); + $this->maxRetries = $maxRetries; + $this->logger = $logger ?? new NullLogger(); + } + + public function request(string $method, string $url, array $options = []): ResponseInterface + { + if ($this->maxRetries <= 0) { + return new AsyncResponse($this->client, $method, $url, $options); + } + + $retryCount = 0; + $content = ''; + $firstChunk = null; + + return new AsyncResponse($this->client, $method, $url, $options, function (ChunkInterface $chunk, AsyncContext $context) use ($method, $url, $options, &$retryCount, &$content, &$firstChunk) { + $exception = null; + try { + if ($context->getInfo('canceled') || $chunk->isTimeout() || null !== $chunk->getInformationalStatus()) { + yield $chunk; + + return; + } + } catch (TransportExceptionInterface $exception) { + // catch TransportExceptionInterface to send it to the strategy + } + if (null !== $exception) { + // always retry request that fail to resolve DNS + if ('' !== $context->getInfo('primary_ip')) { + $shouldRetry = $this->strategy->shouldRetry($context, null, $exception); + if (null === $shouldRetry) { + throw new \LogicException(sprintf('The "%s::shouldRetry()" method must not return null when called with an exception.', \get_class($this->strategy))); + } + + if (false === $shouldRetry) { + yield from $this->passthru($context, $firstChunk, $content, $chunk); + + return; + } + } + } elseif ($chunk->isFirst()) { + if (false === $shouldRetry = $this->strategy->shouldRetry($context, null, null)) { + yield from $this->passthru($context, $firstChunk, $content, $chunk); + + return; + } + + // Body is needed to decide + if (null === $shouldRetry) { + $firstChunk = $chunk; + $content = ''; + + return; + } + } else { + if (!$chunk->isLast()) { + $content .= $chunk->getContent(); + + return; + } + + if (null === $shouldRetry = $this->strategy->shouldRetry($context, $content, null)) { + throw new \LogicException(sprintf('The "%s::shouldRetry()" method must not return null when called with a body.', \get_class($this->strategy))); + } + + if (false === $shouldRetry) { + yield from $this->passthru($context, $firstChunk, $content, $chunk); + + return; + } + } + + $context->getResponse()->cancel(); + + $delay = $this->getDelayFromHeader($context->getHeaders()) ?? $this->strategy->getDelay($context, !$exception && $chunk->isLast() ? $content : null, $exception); + ++$retryCount; + $content = ''; + $firstChunk = null; + + $this->logger->info('Try #{count} after {delay}ms'.($exception ? ': '.$exception->getMessage() : ', status code: '.$context->getStatusCode()), [ + 'count' => $retryCount, + 'delay' => $delay, + ]); + + $context->setInfo('retry_count', $retryCount); + $context->replaceRequest($method, $url, $options); + $context->pause($delay / 1000); + + if ($retryCount >= $this->maxRetries) { + $context->passthru(); + } + }); + } + + private function getDelayFromHeader(array $headers): ?int + { + if (null !== $after = $headers['retry-after'][0] ?? null) { + if (is_numeric($after)) { + return (int) ($after * 1000); + } + + if (false !== $time = strtotime($after)) { + return max(0, $time - time()) * 1000; + } + } + + return null; + } + + private function passthru(AsyncContext $context, ?ChunkInterface $firstChunk, string &$content, ChunkInterface $lastChunk): \Generator + { + $context->passthru(); + + if (null !== $firstChunk) { + yield $firstChunk; + } + + if ('' !== $content) { + $chunk = $context->createChunk($content); + $content = ''; + + yield $chunk; + } + + yield $lastChunk; + } +} diff --git a/sites/all/libraries/vendor/symfony/http-client/ScopingHttpClient.php b/sites/all/libraries/vendor/symfony/http-client/ScopingHttpClient.php new file mode 100644 index 0000000000..85fa26acd8 --- /dev/null +++ b/sites/all/libraries/vendor/symfony/http-client/ScopingHttpClient.php @@ -0,0 +1,131 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient; + +use Psr\Log\LoggerAwareInterface; +use Psr\Log\LoggerInterface; +use Symfony\Component\HttpClient\Exception\InvalidArgumentException; +use Symfony\Contracts\HttpClient\HttpClientInterface; +use Symfony\Contracts\HttpClient\ResponseInterface; +use Symfony\Contracts\HttpClient\ResponseStreamInterface; +use Symfony\Contracts\Service\ResetInterface; + +/** + * Auto-configure the default options based on the requested URL. + * + * @author Anthony Martin + */ +class ScopingHttpClient implements HttpClientInterface, ResetInterface, LoggerAwareInterface +{ + use HttpClientTrait; + + private $client; + private $defaultOptionsByRegexp; + private $defaultRegexp; + + public function __construct(HttpClientInterface $client, array $defaultOptionsByRegexp, string $defaultRegexp = null) + { + $this->client = $client; + $this->defaultOptionsByRegexp = $defaultOptionsByRegexp; + $this->defaultRegexp = $defaultRegexp; + + if (null !== $defaultRegexp && !isset($defaultOptionsByRegexp[$defaultRegexp])) { + throw new InvalidArgumentException(sprintf('No options are mapped to the provided "%s" default regexp.', $defaultRegexp)); + } + } + + public static function forBaseUri(HttpClientInterface $client, string $baseUri, array $defaultOptions = [], string $regexp = null): self + { + if (null === $regexp) { + $regexp = preg_quote(implode('', self::resolveUrl(self::parseUrl('.'), self::parseUrl($baseUri)))); + } + + $defaultOptions['base_uri'] = $baseUri; + + return new self($client, [$regexp => $defaultOptions], $regexp); + } + + /** + * {@inheritdoc} + */ + public function request(string $method, string $url, array $options = []): ResponseInterface + { + $e = null; + $url = self::parseUrl($url, $options['query'] ?? []); + + if (\is_string($options['base_uri'] ?? null)) { + $options['base_uri'] = self::parseUrl($options['base_uri']); + } + + try { + $url = implode('', self::resolveUrl($url, $options['base_uri'] ?? null)); + } catch (InvalidArgumentException $e) { + if (null === $this->defaultRegexp) { + throw $e; + } + + $defaultOptions = $this->defaultOptionsByRegexp[$this->defaultRegexp]; + $options = self::mergeDefaultOptions($options, $defaultOptions, true); + if (\is_string($options['base_uri'] ?? null)) { + $options['base_uri'] = self::parseUrl($options['base_uri']); + } + $url = implode('', self::resolveUrl($url, $options['base_uri'] ?? null, $defaultOptions['query'] ?? [])); + } + + foreach ($this->defaultOptionsByRegexp as $regexp => $defaultOptions) { + if (preg_match("{{$regexp}}A", $url)) { + if (null === $e || $regexp !== $this->defaultRegexp) { + $options = self::mergeDefaultOptions($options, $defaultOptions, true); + } + break; + } + } + + return $this->client->request($method, $url, $options); + } + + /** + * {@inheritdoc} + */ + public function stream($responses, float $timeout = null): ResponseStreamInterface + { + return $this->client->stream($responses, $timeout); + } + + public function reset() + { + if ($this->client instanceof ResetInterface) { + $this->client->reset(); + } + } + + /** + * {@inheritdoc} + */ + public function setLogger(LoggerInterface $logger): void + { + if ($this->client instanceof LoggerAwareInterface) { + $this->client->setLogger($logger); + } + } + + /** + * {@inheritdoc} + */ + public function withOptions(array $options): self + { + $clone = clone $this; + $clone->client = $this->client->withOptions($options); + + return $clone; + } +} diff --git a/sites/all/libraries/vendor/symfony/http-client/Tests/AmpHttpClientTest.php b/sites/all/libraries/vendor/symfony/http-client/Tests/AmpHttpClientTest.php new file mode 100644 index 0000000000..e17b45a0ce --- /dev/null +++ b/sites/all/libraries/vendor/symfony/http-client/Tests/AmpHttpClientTest.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient\Tests; + +use Symfony\Component\HttpClient\AmpHttpClient; +use Symfony\Contracts\HttpClient\HttpClientInterface; + +class AmpHttpClientTest extends HttpClientTestCase +{ + protected function getHttpClient(string $testCase): HttpClientInterface + { + return new AmpHttpClient(['verify_peer' => false, 'verify_host' => false, 'timeout' => 5]); + } + + public function testProxy() + { + $this->markTestSkipped('A real proxy server would be needed.'); + } +} diff --git a/sites/all/libraries/vendor/symfony/http-client/Tests/AsyncDecoratorTraitTest.php b/sites/all/libraries/vendor/symfony/http-client/Tests/AsyncDecoratorTraitTest.php new file mode 100644 index 0000000000..199d2cf5d0 --- /dev/null +++ b/sites/all/libraries/vendor/symfony/http-client/Tests/AsyncDecoratorTraitTest.php @@ -0,0 +1,367 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient\Tests; + +use Symfony\Component\HttpClient\AsyncDecoratorTrait; +use Symfony\Component\HttpClient\DecoratorTrait; +use Symfony\Component\HttpClient\Exception\TransportException; +use Symfony\Component\HttpClient\HttpClient; +use Symfony\Component\HttpClient\Response\AsyncContext; +use Symfony\Component\HttpClient\Response\AsyncResponse; +use Symfony\Contracts\HttpClient\ChunkInterface; +use Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface; +use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface; +use Symfony\Contracts\HttpClient\HttpClientInterface; +use Symfony\Contracts\HttpClient\ResponseInterface; + +class AsyncDecoratorTraitTest extends NativeHttpClientTest +{ + protected function getHttpClient(string $testCase, \Closure $chunkFilter = null, HttpClientInterface $decoratedClient = null): HttpClientInterface + { + if ('testHandleIsRemovedOnException' === $testCase) { + $this->markTestSkipped("AsyncDecoratorTrait doesn't cache handles"); + } + + if ('testTimeoutOnDestruct' === $testCase) { + return HttpClient::create(); + } + + $chunkFilter = $chunkFilter ?? static function (ChunkInterface $chunk, AsyncContext $context) { yield $chunk; }; + + return new class($decoratedClient ?? parent::getHttpClient($testCase), $chunkFilter) implements HttpClientInterface { + use AsyncDecoratorTrait; + + private $chunkFilter; + + public function __construct(HttpClientInterface $client, \Closure $chunkFilter = null) + { + $this->chunkFilter = $chunkFilter; + $this->client = $client; + } + + public function request(string $method, string $url, array $options = []): ResponseInterface + { + return new AsyncResponse($this->client, $method, $url, $options, $this->chunkFilter); + } + }; + } + + public function testTimeoutOnDestruct() + { + if (HttpClient::create() instanceof NativeHttpClient) { + $this->markTestSkipped('NativeHttpClient doesn\'t support opening concurrent requests.'); + } + + HttpClientTestCase::testTimeoutOnDestruct(); + } + + public function testRetry404() + { + $client = $this->getHttpClient(__FUNCTION__, function (ChunkInterface $chunk, AsyncContext $context) { + $this->assertTrue($chunk->isFirst()); + $this->assertSame(404, $context->getStatusCode()); + $context->getResponse()->cancel(); + $context->replaceRequest('GET', 'http://localhost:8057/'); + $context->passthru(); + }); + + $response = $client->request('GET', 'http://localhost:8057/404'); + + foreach ($client->stream($response) as $chunk) { + } + $this->assertTrue($chunk->isLast()); + $this->assertSame(200, $response->getStatusCode()); + } + + public function testRetry404WithThrow() + { + $client = $this->getHttpClient(__FUNCTION__, function (ChunkInterface $chunk, AsyncContext $context) { + $this->assertTrue($chunk->isFirst()); + $this->assertSame(404, $context->getStatusCode()); + $context->getResponse()->cancel(); + $context->replaceRequest('GET', 'http://localhost:8057/404'); + $context->passthru(); + }); + + $response = $client->request('GET', 'http://localhost:8057/404'); + + $this->expectException(ClientExceptionInterface::class); + $response->getContent(true); + } + + public function testRetryTransportError() + { + $client = $this->getHttpClient(__FUNCTION__, function (ChunkInterface $chunk, AsyncContext $context) { + try { + if ($chunk->isFirst()) { + $this->assertSame(200, $context->getStatusCode()); + } + } catch (TransportExceptionInterface $e) { + $context->getResponse()->cancel(); + $context->replaceRequest('GET', 'http://localhost:8057/'); + $context->passthru(); + } + }); + + $response = $client->request('GET', 'http://localhost:8057/chunked-broken'); + + $this->assertSame(200, $response->getStatusCode()); + } + + public function testJsonTransclusion() + { + $client = $this->getHttpClient(__FUNCTION__, function (ChunkInterface $chunk, AsyncContext $context) { + if ('' === $content = $chunk->getContent()) { + yield $chunk; + + return; + } + + $this->assertSame('{"documents":[{"id":"\/json\/1"},{"id":"\/json\/2"},{"id":"\/json\/3"}]}', $content); + + $steps = preg_split('{\{"id":"\\\/json\\\/(\d)"\}}', $content, -1, \PREG_SPLIT_DELIM_CAPTURE); + $steps[7] = $context->getResponse(); + $steps[1] = $context->replaceRequest('GET', 'http://localhost:8057/json/1'); + $steps[3] = $context->replaceRequest('GET', 'http://localhost:8057/json/2'); + $steps[5] = $context->replaceRequest('GET', 'http://localhost:8057/json/3'); + + yield $context->createChunk(array_shift($steps)); + + $context->replaceResponse(array_shift($steps)); + $context->passthru(static function (ChunkInterface $chunk, AsyncContext $context) use (&$steps) { + if ($chunk->isFirst()) { + return; + } + + if ($steps && $chunk->isLast()) { + $chunk = $context->createChunk(array_shift($steps)); + $context->replaceResponse(array_shift($steps)); + } + + yield $chunk; + }); + }); + + $response = $client->request('GET', 'http://localhost:8057/json'); + + $this->assertSame('{"documents":[{"title":"\/json\/1"},{"title":"\/json\/2"},{"title":"\/json\/3"}]}', $response->getContent()); + } + + public function testPreflightRequest() + { + $client = new class(parent::getHttpClient(__FUNCTION__)) implements HttpClientInterface { + use AsyncDecoratorTrait; + + public function request(string $method, string $url, array $options = []): ResponseInterface + { + $chunkFilter = static function (ChunkInterface $chunk, AsyncContext $context) use ($method, $url, $options) { + $context->replaceRequest($method, $url, $options); + $context->passthru(); + }; + + return new AsyncResponse($this->client, 'GET', 'http://localhost:8057', $options, $chunkFilter); + } + }; + + $response = $client->request('GET', 'http://localhost:8057/json'); + + $this->assertSame('{"documents":[{"id":"\/json\/1"},{"id":"\/json\/2"},{"id":"\/json\/3"}]}', $response->getContent()); + $this->assertSame('http://localhost:8057/', $response->getInfo('previous_info')[0]['url']); + } + + public function testProcessingHappensOnce() + { + $lastChunks = 0; + $client = $this->getHttpClient(__FUNCTION__, function (ChunkInterface $chunk, AsyncContext $context) use (&$lastChunks) { + $lastChunks += $chunk->isLast(); + + yield $chunk; + }); + + $response = $client->request('GET', 'http://localhost:8057/'); + + foreach ($client->stream($response) as $chunk) { + } + $this->assertTrue($chunk->isLast()); + $this->assertSame(1, $lastChunks); + + $chunk = null; + foreach ($client->stream($response) as $chunk) { + } + $this->assertTrue($chunk->isLast()); + $this->assertSame(1, $lastChunks); + } + + public function testLastChunkIsYieldOnHttpExceptionAtDestructTime() + { + $lastChunk = null; + $client = $this->getHttpClient(__FUNCTION__, function (ChunkInterface $chunk, AsyncContext $context) use (&$lastChunk) { + $lastChunk = $chunk; + + yield $chunk; + }); + + try { + $client->request('GET', 'http://localhost:8057/404'); + $this->fail(ClientExceptionInterface::class.' expected'); + } catch (ClientExceptionInterface $e) { + } + + $this->assertTrue($lastChunk->isLast()); + } + + public function testBufferPurePassthru() + { + $client = $this->getHttpClient(__FUNCTION__, function (ChunkInterface $chunk, AsyncContext $context) { + $context->passthru(); + + yield $chunk; + }); + + $response = $client->request('GET', 'http://localhost:8057/'); + + $this->assertStringContainsString('SERVER_PROTOCOL', $response->getContent()); + $this->assertStringContainsString('HTTP_HOST', $response->getContent()); + } + + public function testRetryTimeout() + { + $cpt = 0; + $client = $this->getHttpClient(__FUNCTION__, function (ChunkInterface $chunk, AsyncContext $context) use (&$cpt) { + try { + $this->assertTrue($chunk->isTimeout()); + yield $chunk; + } catch (TransportExceptionInterface $e) { + if ($cpt++ < 3) { + $context->getResponse()->cancel(); + $context->replaceRequest('GET', 'http://localhost:8057/timeout-header', ['timeout' => 0.1]); + } else { + $context->passthru(); + $context->getResponse()->cancel(); + $context->replaceRequest('GET', 'http://localhost:8057/timeout-header', ['timeout' => 10]); + } + } + }); + + $response = $client->request('GET', 'http://localhost:8057/timeout-header', ['timeout' => 0.1]); + + $this->assertSame(200, $response->getStatusCode()); + } + + public function testRecurciveStream() + { + $client = new class(parent::getHttpClient(__FUNCTION__)) implements HttpClientInterface { + use AsyncDecoratorTrait; + + public function request(string $method, string $url, array $options = []): ResponseInterface + { + return new AsyncResponse($this->client, $method, $url, $options); + } + }; + + $response = $client->request('GET', 'http://localhost:8057/json'); + $content = ''; + foreach ($client->stream($response) as $chunk) { + $content .= $chunk->getContent(); + foreach ($client->stream($response) as $chunk) { + $content .= $chunk->getContent(); + } + } + + $this->assertSame('{"documents":[{"id":"\/json\/1"},{"id":"\/json\/2"},{"id":"\/json\/3"}]}', $content); + } + + public function testInfoPassToDecorator() + { + $lastInfo = null; + $options = ['on_progress' => function (int $dlNow, int $dlSize, array $info) use (&$lastInfo) { + $lastInfo = $info; + }]; + $client = $this->getHttpClient(__FUNCTION__, function (ChunkInterface $chunk, AsyncContext $context) use ($options) { + $context->setInfo('foo', 'test'); + $context->getResponse()->cancel(); + $context->replaceRequest('GET', 'http://localhost:8057/', $options); + $context->passthru(); + }); + + $client->request('GET', 'http://localhost:8057')->getContent(); + $this->assertArrayHasKey('foo', $lastInfo); + $this->assertSame('test', $lastInfo['foo']); + $this->assertArrayHasKey('previous_info', $lastInfo); + } + + public function testMultipleYieldInInitializer() + { + $first = null; + $client = $this->getHttpClient(__FUNCTION__, function (ChunkInterface $chunk, AsyncContext $context) use (&$first) { + if ($chunk->isFirst()) { + $first = $chunk; + + return; + } + $context->passthru(); + yield $first; + yield $context->createChunk('injectedFoo'); + yield $chunk; + }); + + $response = $client->request('GET', 'http://localhost:8057/404', ['timeout' => 0.1]); + + $this->assertSame(404, $response->getStatusCode()); + $this->assertStringContainsString('injectedFoo', $response->getContent(false)); + } + + public function testConsumingDecoratedClient() + { + $client = $this->getHttpClient(__FUNCTION__, null, new class(parent::getHttpClient(__FUNCTION__)) implements HttpClientInterface { + use DecoratorTrait; + + public function request(string $method, string $url, array $options = []): ResponseInterface + { + $response = $this->client->request($method, $url, $options); + $response->getStatusCode(); // should be avoided and breaks compatibility with AsyncDecoratorTrait + + return $response; + } + }); + + $response = $client->request('GET', 'http://localhost:8057/'); + + $this->expectException(\LogicException::class); + $this->expectExceptionMessage('Instance of "Symfony\Component\HttpClient\Response\NativeResponse" is already consumed and cannot be managed by "Symfony\Component\HttpClient\Response\AsyncResponse". A decorated client should not call any of the response\'s methods in its "request()" method.'); + $response->getStatusCode(); + } + + public function testMaxDuration() + { + $sawFirst = false; + $client = $this->getHttpClient(__FUNCTION__, function (ChunkInterface $chunk, AsyncContext $context) use (&$sawFirst) { + try { + if (!$chunk->isFirst() || !$sawFirst) { + $sawFirst = $sawFirst || $chunk->isFirst(); + yield $chunk; + } + } catch (TransportExceptionInterface $e) { + $context->getResponse()->cancel(); + $context->replaceRequest('GET', 'http://localhost:8057/timeout-body', ['timeout' => 0.4]); + } + }); + + $response = $client->request('GET', 'http://localhost:8057/timeout-body', ['max_duration' => 0.75, 'timeout' => 0.4]); + + $this->assertSame(0.75, $response->getInfo('max_duration')); + + $this->expectException(TransportException::class); + $this->expectExceptionMessage('Max duration was reached for "http://localhost:8057/timeout-body".'); + $response->getContent(); + } +} diff --git a/sites/all/libraries/vendor/symfony/http-client/Tests/CachingHttpClientTest.php b/sites/all/libraries/vendor/symfony/http-client/Tests/CachingHttpClientTest.php new file mode 100644 index 0000000000..ad07f86451 --- /dev/null +++ b/sites/all/libraries/vendor/symfony/http-client/Tests/CachingHttpClientTest.php @@ -0,0 +1,110 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient\Tests; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpClient\CachingHttpClient; +use Symfony\Component\HttpClient\MockHttpClient; +use Symfony\Component\HttpClient\Response\MockResponse; +use Symfony\Component\HttpKernel\HttpCache\Store; +use Symfony\Contracts\HttpClient\ResponseInterface; + +class CachingHttpClientTest extends TestCase +{ + public function testRequestHeaders() + { + $options = [ + 'headers' => [ + 'Application-Name' => 'test1234', + 'Test-Name-Header' => 'test12345', + ], + ]; + + $mockClient = new MockHttpClient(); + $store = new Store(sys_get_temp_dir().'/sf_http_cache'); + $client = new CachingHttpClient($mockClient, $store, $options); + + $response = $client->request('GET', 'http://example.com/foo-bar'); + + rmdir(sys_get_temp_dir().'/sf_http_cache'); + self::assertInstanceOf(MockResponse::class, $response); + self::assertSame($response->getRequestOptions()['normalized_headers']['application-name'][0], 'Application-Name: test1234'); + self::assertSame($response->getRequestOptions()['normalized_headers']['test-name-header'][0], 'Test-Name-Header: test12345'); + } + + public function testDoesNotEvaluateResponseBody() + { + $body = file_get_contents(__DIR__.'/Fixtures/assertion_failure.php'); + $response = $this->runRequest(new MockResponse($body, ['response_headers' => ['X-Body-Eval' => true]])); + $headers = $response->getHeaders(); + + $this->assertSame($body, $response->getContent()); + $this->assertArrayNotHasKey('x-body-eval', $headers); + } + + public function testDoesNotIncludeFile() + { + $file = __DIR__.'/Fixtures/assertion_failure.php'; + + $response = $this->runRequest(new MockResponse( + 'test', ['response_headers' => [ + 'X-Body-Eval' => true, + 'X-Body-File' => $file, + ]] + )); + $headers = $response->getHeaders(); + + $this->assertSame('test', $response->getContent()); + $this->assertArrayNotHasKey('x-body-eval', $headers); + $this->assertArrayNotHasKey('x-body-file', $headers); + } + + public function testDoesNotReadFile() + { + $file = __DIR__.'/Fixtures/assertion_failure.php'; + + $response = $this->runRequest(new MockResponse( + 'test', ['response_headers' => [ + 'X-Body-File' => $file, + ]] + )); + $headers = $response->getHeaders(); + + $this->assertSame('test', $response->getContent()); + $this->assertArrayNotHasKey('x-body-file', $headers); + } + + public function testRemovesXContentDigest() + { + $response = $this->runRequest(new MockResponse( + 'test', [ + 'response_headers' => [ + 'X-Content-Digest' => 'some-hash', + ], + ])); + $headers = $response->getHeaders(); + + $this->assertArrayNotHasKey('x-content-digest', $headers); + } + + private function runRequest(MockResponse $mockResponse): ResponseInterface + { + $mockClient = new MockHttpClient($mockResponse); + + $store = new Store(sys_get_temp_dir().'/sf_http_cache'); + $client = new CachingHttpClient($mockClient, $store); + + $response = $client->request('GET', 'http://test'); + + return $response; + } +} diff --git a/sites/all/libraries/vendor/symfony/http-client/Tests/Chunk/ServerSentEventTest.php b/sites/all/libraries/vendor/symfony/http-client/Tests/Chunk/ServerSentEventTest.php new file mode 100644 index 0000000000..1c0d6834a7 --- /dev/null +++ b/sites/all/libraries/vendor/symfony/http-client/Tests/Chunk/ServerSentEventTest.php @@ -0,0 +1,79 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient\Tests\Chunk; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpClient\Chunk\ServerSentEvent; + +/** + * @author Antoine Bluchet + */ +class ServerSentEventTest extends TestCase +{ + public function testParse() + { + $rawData = <<assertSame("test\ntest", $sse->getData()); + $this->assertSame('12', $sse->getId()); + $this->assertSame('testEvent', $sse->getType()); + } + + public function testParseValid() + { + $rawData = <<assertSame('', $sse->getData()); + $this->assertSame('', $sse->getId()); + $this->assertSame('testEvent', $sse->getType()); + } + + public function testParseRetry() + { + $rawData = <<assertSame('', $sse->getData()); + $this->assertSame('', $sse->getId()); + $this->assertSame('message', $sse->getType()); + $this->assertSame(0.012, $sse->getRetry()); + } + + public function testParseNewLine() + { + $rawData = << +data +data: +data: +data: +data: +STR; + $sse = new ServerSentEvent($rawData); + $this->assertSame("\n\n \n\n\n", $sse->getData()); + } +} diff --git a/sites/all/libraries/vendor/symfony/http-client/Tests/CurlHttpClientTest.php b/sites/all/libraries/vendor/symfony/http-client/Tests/CurlHttpClientTest.php new file mode 100644 index 0000000000..284a243496 --- /dev/null +++ b/sites/all/libraries/vendor/symfony/http-client/Tests/CurlHttpClientTest.php @@ -0,0 +1,129 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient\Tests; + +use Symfony\Component\HttpClient\CurlHttpClient; +use Symfony\Component\HttpClient\Exception\InvalidArgumentException; +use Symfony\Contracts\HttpClient\HttpClientInterface; + +/** + * @requires extension curl + */ +class CurlHttpClientTest extends HttpClientTestCase +{ + protected function getHttpClient(string $testCase): HttpClientInterface + { + if (false !== strpos($testCase, 'Push')) { + if (\PHP_VERSION_ID >= 70300 && \PHP_VERSION_ID < 70304) { + $this->markTestSkipped('PHP 7.3.0 to 7.3.3 don\'t support HTTP/2 PUSH'); + } + + if (!\defined('CURLMOPT_PUSHFUNCTION') || 0x073D00 > ($v = curl_version())['version_number'] || !(\CURL_VERSION_HTTP2 & $v['features'])) { + $this->markTestSkipped('curl <7.61 is used or it is not compiled with support for HTTP/2 PUSH'); + } + } + + return new CurlHttpClient(['verify_peer' => false, 'verify_host' => false]); + } + + public function testBindToPort() + { + $client = $this->getHttpClient(__FUNCTION__); + $response = $client->request('GET', 'http://localhost:8057', ['bindto' => '127.0.0.1:9876']); + $response->getStatusCode(); + + $r = new \ReflectionProperty($response, 'handle'); + $r->setAccessible(true); + + $curlInfo = curl_getinfo($r->getValue($response)); + + self::assertSame('127.0.0.1', $curlInfo['local_ip']); + self::assertSame(9876, $curlInfo['local_port']); + } + + public function testTimeoutIsNotAFatalError() + { + if ('\\' === \DIRECTORY_SEPARATOR) { + $this->markTestSkipped('Too transient on Windows'); + } + + parent::testTimeoutIsNotAFatalError(); + } + + public function testHandleIsReinitOnReset() + { + $httpClient = $this->getHttpClient(__FUNCTION__); + + $r = new \ReflectionProperty($httpClient, 'multi'); + $r->setAccessible(true); + $clientState = $r->getValue($httpClient); + $initialShareId = $clientState->share; + $httpClient->reset(); + self::assertNotSame($initialShareId, $clientState->share); + } + + public function testProcessAfterReset() + { + $client = $this->getHttpClient(__FUNCTION__); + + $response = $client->request('GET', 'http://127.0.0.1:8057/json'); + + $client->reset(); + + $this->assertSame(['application/json'], $response->getHeaders()['content-type']); + } + + public function testOverridingRefererUsingCurlOptions() + { + $httpClient = $this->getHttpClient(__FUNCTION__); + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('Cannot set "CURLOPT_REFERER" with "extra.curl", use option "headers" instead.'); + + $httpClient->request('GET', 'http://localhost:8057/', [ + 'extra' => [ + 'curl' => [ + \CURLOPT_REFERER => 'Banana', + ], + ], + ]); + } + + public function testOverridingHttpMethodUsingCurlOptions() + { + $httpClient = $this->getHttpClient(__FUNCTION__); + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('The HTTP method cannot be overridden using "extra.curl".'); + + $httpClient->request('POST', 'http://localhost:8057/', [ + 'extra' => [ + 'curl' => [ + \CURLOPT_HTTPGET => true, + ], + ], + ]); + } + + public function testOverridingInternalAttributesUsingCurlOptions() + { + $httpClient = $this->getHttpClient(__FUNCTION__); + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('Cannot set "CURLOPT_PRIVATE" with "extra.curl".'); + + $httpClient->request('POST', 'http://localhost:8057/', [ + 'extra' => [ + 'curl' => [ + \CURLOPT_PRIVATE => 'overriden private', + ], + ], + ]); + } +} diff --git a/sites/all/libraries/vendor/symfony/http-client/Tests/DataCollector/HttpClientDataCollectorTest.php b/sites/all/libraries/vendor/symfony/http-client/Tests/DataCollector/HttpClientDataCollectorTest.php new file mode 100755 index 0000000000..76bbbe7c57 --- /dev/null +++ b/sites/all/libraries/vendor/symfony/http-client/Tests/DataCollector/HttpClientDataCollectorTest.php @@ -0,0 +1,180 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient\Tests\DataCollector; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpClient\DataCollector\HttpClientDataCollector; +use Symfony\Component\HttpClient\NativeHttpClient; +use Symfony\Component\HttpClient\TraceableHttpClient; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Contracts\HttpClient\Test\TestHttpServer; + +class HttpClientDataCollectorTest extends TestCase +{ + public static function setUpBeforeClass(): void + { + TestHttpServer::start(); + } + + public function testItCollectsRequestCount() + { + $httpClient1 = $this->httpClientThatHasTracedRequests([ + [ + 'method' => 'GET', + 'url' => 'http://localhost:8057/', + ], + [ + 'method' => 'GET', + 'url' => 'http://localhost:8057/301', + ], + ]); + $httpClient2 = $this->httpClientThatHasTracedRequests([ + [ + 'method' => 'GET', + 'url' => 'http://localhost:8057/404', + ], + ]); + $httpClient3 = $this->httpClientThatHasTracedRequests([]); + $sut = new HttpClientDataCollector(); + $sut->registerClient('http_client1', $httpClient1); + $sut->registerClient('http_client2', $httpClient2); + $sut->registerClient('http_client3', $httpClient3); + $this->assertEquals(0, $sut->getRequestCount()); + $sut->collect(new Request(), new Response()); + $this->assertEquals(3, $sut->getRequestCount()); + } + + public function testItCollectsErrorCount() + { + $httpClient1 = $this->httpClientThatHasTracedRequests([ + [ + 'method' => 'GET', + 'url' => 'http://localhost:8057/', + ], + [ + 'method' => 'GET', + 'url' => 'http://localhost:8057/301', + ], + ]); + $httpClient2 = $this->httpClientThatHasTracedRequests([ + [ + 'method' => 'GET', + 'url' => '/404', + 'options' => ['base_uri' => 'http://localhost:8057/'], + ], + ]); + $httpClient3 = $this->httpClientThatHasTracedRequests([]); + $sut = new HttpClientDataCollector(); + $sut->registerClient('http_client1', $httpClient1); + $sut->registerClient('http_client2', $httpClient2); + $sut->registerClient('http_client3', $httpClient3); + $this->assertEquals(0, $sut->getErrorCount()); + $sut->collect(new Request(), new Response()); + $this->assertEquals(1, $sut->getErrorCount()); + } + + public function testItCollectsErrorCountByClient() + { + $httpClient1 = $this->httpClientThatHasTracedRequests([ + [ + 'method' => 'GET', + 'url' => 'http://localhost:8057/', + ], + [ + 'method' => 'GET', + 'url' => 'http://localhost:8057/301', + ], + ]); + $httpClient2 = $this->httpClientThatHasTracedRequests([ + [ + 'method' => 'GET', + 'url' => '/404', + 'options' => ['base_uri' => 'http://localhost:8057/'], + ], + ]); + $httpClient3 = $this->httpClientThatHasTracedRequests([]); + $sut = new HttpClientDataCollector(); + $sut->registerClient('http_client1', $httpClient1); + $sut->registerClient('http_client2', $httpClient2); + $sut->registerClient('http_client3', $httpClient3); + $this->assertEquals([], $sut->getClients()); + $sut->collect(new Request(), new Response()); + $collectedData = $sut->getClients(); + $this->assertEquals(0, $collectedData['http_client1']['error_count']); + $this->assertEquals(1, $collectedData['http_client2']['error_count']); + $this->assertEquals(0, $collectedData['http_client3']['error_count']); + } + + public function testItCollectsTracesByClient() + { + $httpClient1 = $this->httpClientThatHasTracedRequests([ + [ + 'method' => 'GET', + 'url' => 'http://localhost:8057/', + ], + [ + 'method' => 'GET', + 'url' => 'http://localhost:8057/301', + ], + ]); + $httpClient2 = $this->httpClientThatHasTracedRequests([ + [ + 'method' => 'GET', + 'url' => '/404', + 'options' => ['base_uri' => 'http://localhost:8057/'], + ], + ]); + $httpClient3 = $this->httpClientThatHasTracedRequests([]); + $sut = new HttpClientDataCollector(); + $sut->registerClient('http_client1', $httpClient1); + $sut->registerClient('http_client2', $httpClient2); + $sut->registerClient('http_client3', $httpClient3); + $this->assertEquals([], $sut->getClients()); + $sut->collect(new Request(), new Response()); + $collectedData = $sut->getClients(); + $this->assertCount(2, $collectedData['http_client1']['traces']); + $this->assertCount(1, $collectedData['http_client2']['traces']); + $this->assertCount(0, $collectedData['http_client3']['traces']); + } + + public function testItIsEmptyAfterReset() + { + $httpClient1 = $this->httpClientThatHasTracedRequests([ + [ + 'method' => 'GET', + 'url' => 'http://localhost:8057/', + ], + ]); + $sut = new HttpClientDataCollector(); + $sut->registerClient('http_client1', $httpClient1); + $sut->collect(new Request(), new Response()); + $collectedData = $sut->getClients(); + $this->assertCount(1, $collectedData['http_client1']['traces']); + $sut->reset(); + $this->assertEquals([], $sut->getClients()); + $this->assertEquals(0, $sut->getErrorCount()); + $this->assertEquals(0, $sut->getRequestCount()); + } + + private function httpClientThatHasTracedRequests($tracedRequests): TraceableHttpClient + { + $httpClient = new TraceableHttpClient(new NativeHttpClient()); + + foreach ($tracedRequests as $request) { + $response = $httpClient->request($request['method'], $request['url'], $request['options'] ?? []); + $response->getContent(false); // disables exceptions from destructors + } + + return $httpClient; + } +} diff --git a/sites/all/libraries/vendor/symfony/http-client/Tests/DependencyInjection/HttpClientPassTest.php b/sites/all/libraries/vendor/symfony/http-client/Tests/DependencyInjection/HttpClientPassTest.php new file mode 100755 index 0000000000..eb04f88226 --- /dev/null +++ b/sites/all/libraries/vendor/symfony/http-client/Tests/DependencyInjection/HttpClientPassTest.php @@ -0,0 +1,65 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient\Tests\DependencyInjection; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\HttpClient\DataCollector\HttpClientDataCollector; +use Symfony\Component\HttpClient\DependencyInjection\HttpClientPass; +use Symfony\Component\HttpClient\TraceableHttpClient; +use Symfony\Contracts\HttpClient\HttpClientInterface; + +class HttpClientPassTest extends TestCase +{ + public function testItRequiresDataCollector() + { + $container = $this->buildContainerBuilder('http_client'); + $sut = new HttpClientPass(); + $sut->process($container); + + $this->assertFalse($container->hasDefinition('.debug.http_client')); + } + + public function testItDecoratesHttpClientWithTraceableHttpClient() + { + $container = $this->buildContainerBuilder('foo'); + $container->register('data_collector.http_client', HttpClientDataCollector::class); + $sut = new HttpClientPass(); + $sut->process($container); + $this->assertTrue($container->hasDefinition('.debug.foo')); + $this->assertSame(TraceableHttpClient::class, $container->getDefinition('.debug.foo')->getClass()); + $this->assertSame(['foo', null, 0], $container->getDefinition('.debug.foo')->getDecoratedService()); + } + + public function testItRegistersDebugHttpClientToCollector() + { + $container = $this->buildContainerBuilder('foo_client'); + $container->register('data_collector.http_client', HttpClientDataCollector::class); + $sut = new HttpClientPass(); + $sut->process($container); + $this->assertEquals( + [['registerClient', ['foo_client', new Reference('.debug.foo_client')]]], + $container->getDefinition('data_collector.http_client')->getMethodCalls() + ); + } + + private function buildContainerBuilder(string $clientId = 'http_client'): ContainerBuilder + { + $container = new ContainerBuilder(); + $container->setParameter('kernel.debug', true); + + $container->register($clientId, HttpClientInterface::class)->addTag('http_client.client')->setArgument(0, []); + + return $container; + } +} diff --git a/sites/all/libraries/vendor/symfony/http-client/Tests/EventSourceHttpClientTest.php b/sites/all/libraries/vendor/symfony/http-client/Tests/EventSourceHttpClientTest.php new file mode 100644 index 0000000000..b738c15a18 --- /dev/null +++ b/sites/all/libraries/vendor/symfony/http-client/Tests/EventSourceHttpClientTest.php @@ -0,0 +1,169 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient\Tests; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpClient\Chunk\DataChunk; +use Symfony\Component\HttpClient\Chunk\ErrorChunk; +use Symfony\Component\HttpClient\Chunk\FirstChunk; +use Symfony\Component\HttpClient\Chunk\ServerSentEvent; +use Symfony\Component\HttpClient\EventSourceHttpClient; +use Symfony\Component\HttpClient\Exception\EventSourceException; +use Symfony\Component\HttpClient\Response\MockResponse; +use Symfony\Component\HttpClient\Response\ResponseStream; +use Symfony\Contracts\HttpClient\HttpClientInterface; + +/** + * @author Antoine Bluchet + */ +class EventSourceHttpClientTest extends TestCase +{ + public function testGetServerSentEvents() + { + $data = << +data +data: +data +data: + +id: 60 +data +TXT; + + $chunk = new DataChunk(0, $data); + $response = new MockResponse('', ['canceled' => false, 'http_method' => 'GET', 'url' => 'http://localhost:8080/events', 'response_headers' => ['content-type: text/event-stream']]); + $responseStream = new ResponseStream((function () use ($response, $chunk) { + yield $response => new FirstChunk(); + yield $response => $chunk; + yield $response => new ErrorChunk(0, 'timeout'); + })()); + + $hasCorrectHeaders = function ($options) { + $this->assertSame(['Accept: text/event-stream', 'Cache-Control: no-cache'], $options['headers']); + + return true; + }; + + $httpClient = $this->createMock(HttpClientInterface::class); + $httpClient->method('request')->with('GET', 'http://localhost:8080/events', $this->callback($hasCorrectHeaders))->willReturn($response); + + $httpClient->method('stream')->willReturn($responseStream); + + $es = new EventSourceHttpClient($httpClient); + $res = $es->connect('http://localhost:8080/events'); + + $expected = [ + new FirstChunk(), + new ServerSentEvent("event: builderror\nid: 46\ndata: {\"foo\": \"bar\"}\n\n"), + new ServerSentEvent("event: reload\nid: 47\ndata: {}\n\n"), + new ServerSentEvent("event: reload\nid: 48\ndata: {}\n\n"), + new ServerSentEvent("data: test\ndata:test\nid: 49\nevent: testEvent\n\n\n"), + new ServerSentEvent("id: 50\ndata: \ndata\ndata: \ndata\ndata: \n\n"), + ]; + $i = 0; + + $this->expectExceptionMessage('Response has been canceled'); + while ($res) { + if ($i > 0) { + $res->cancel(); + } + foreach ($es->stream($res) as $chunk) { + if ($chunk->isTimeout()) { + continue; + } + + if ($chunk->isLast()) { + continue; + } + + $this->assertEquals($expected[$i++], $chunk); + } + } + } + + /** + * @dataProvider contentTypeProvider + */ + public function testContentType($contentType, $expected) + { + $chunk = new DataChunk(0, ''); + $response = new MockResponse('', ['canceled' => false, 'http_method' => 'GET', 'url' => 'http://localhost:8080/events', 'response_headers' => ['content-type: '.$contentType]]); + $responseStream = new ResponseStream((function () use ($response, $chunk) { + yield $response => new FirstChunk(); + yield $response => $chunk; + yield $response => new ErrorChunk(0, 'timeout'); + })()); + + $hasCorrectHeaders = function ($options) { + $this->assertSame(['Accept: text/event-stream', 'Cache-Control: no-cache'], $options['headers']); + + return true; + }; + + $httpClient = $this->createMock(HttpClientInterface::class); + $httpClient->method('request')->with('GET', 'http://localhost:8080/events', $this->callback($hasCorrectHeaders))->willReturn($response); + + $httpClient->method('stream')->willReturn($responseStream); + + $es = new EventSourceHttpClient($httpClient); + $res = $es->connect('http://localhost:8080/events'); + + if ($expected instanceof EventSourceException) { + $this->expectExceptionMessage($expected->getMessage()); + } + + foreach ($es->stream($res) as $chunk) { + if ($chunk->isTimeout()) { + continue; + } + + if ($chunk->isLast()) { + return; + } + } + } + + public function contentTypeProvider() + { + return [ + ['text/event-stream', true], + ['text/event-stream;charset=utf-8', true], + ['text/event-stream;charset=UTF-8', true], + ['Text/EVENT-STREAM;Charset="utf-8"', true], + ['text/event-stream; charset="utf-8"', true], + ['text/event-stream; charset=iso-8859-15', true], + ['text/html', new EventSourceException('Response content-type is "text/html" while "text/event-stream" was expected for "http://localhost:8080/events".')], + ['text/html; charset="utf-8"', new EventSourceException('Response content-type is "text/html; charset="utf-8"" while "text/event-stream" was expected for "http://localhost:8080/events".')], + ['text/event-streambla', new EventSourceException('Response content-type is "text/event-streambla" while "text/event-stream" was expected for "http://localhost:8080/events".')], + ]; + } +} diff --git a/sites/all/libraries/vendor/symfony/http-client/Tests/Exception/HttpExceptionTraitTest.php b/sites/all/libraries/vendor/symfony/http-client/Tests/Exception/HttpExceptionTraitTest.php new file mode 100644 index 0000000000..f7b4ce59e9 --- /dev/null +++ b/sites/all/libraries/vendor/symfony/http-client/Tests/Exception/HttpExceptionTraitTest.php @@ -0,0 +1,66 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient\Tests\Exception; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpClient\Exception\HttpExceptionTrait; +use Symfony\Contracts\HttpClient\ResponseInterface; + +/** + * @author Kévin Dunglas + */ +class HttpExceptionTraitTest extends TestCase +{ + public function provideParseError(): iterable + { + $errorWithoutMessage = 'HTTP/1.1 400 Bad Request returned for "http://example.com".'; + + $errorWithMessage = <<createMock(ResponseInterface::class); + $response + ->method('getInfo') + ->willReturnMap([ + ['http_code', 400], + ['url', 'http://example.com'], + ['response_headers', [ + 'HTTP/1.1 400 Bad Request', + 'Content-Type: '.$mimeType, + ]], + ]); + $response->method('getContent')->willReturn($json); + + $e = new TestException($response); + $this->assertSame(400, $e->getCode()); + $this->assertSame($expectedMessage, $e->getMessage()); + } +} + +class TestException extends \Exception +{ + use HttpExceptionTrait; +} diff --git a/sites/all/libraries/vendor/symfony/http-client/Tests/Fixtures/assertion_failure.php b/sites/all/libraries/vendor/symfony/http-client/Tests/Fixtures/assertion_failure.php new file mode 100644 index 0000000000..52f31b45a2 --- /dev/null +++ b/sites/all/libraries/vendor/symfony/http-client/Tests/Fixtures/assertion_failure.php @@ -0,0 +1,3 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient\Tests; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpClient\HttpClient; +use Symfony\Component\HttpClient\NativeHttpClient; +use Symfony\Contracts\HttpClient\HttpClientInterface; + +class HttpClientTest extends TestCase +{ + public function testCreateClient() + { + $this->assertInstanceOf(HttpClientInterface::class, HttpClient::create()); + $this->assertNotInstanceOf(NativeHttpClient::class, HttpClient::create()); + } +} diff --git a/sites/all/libraries/vendor/symfony/http-client/Tests/HttpClientTestCase.php b/sites/all/libraries/vendor/symfony/http-client/Tests/HttpClientTestCase.php new file mode 100644 index 0000000000..9a1c177a53 --- /dev/null +++ b/sites/all/libraries/vendor/symfony/http-client/Tests/HttpClientTestCase.php @@ -0,0 +1,458 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient\Tests; + +use PHPUnit\Framework\SkippedTestSuiteError; +use Symfony\Component\HttpClient\Exception\ClientException; +use Symfony\Component\HttpClient\Exception\TransportException; +use Symfony\Component\HttpClient\Internal\ClientState; +use Symfony\Component\HttpClient\Response\StreamWrapper; +use Symfony\Component\Process\Exception\ProcessFailedException; +use Symfony\Component\Process\Process; +use Symfony\Contracts\HttpClient\Exception\RedirectionExceptionInterface; +use Symfony\Contracts\HttpClient\HttpClientInterface; +use Symfony\Contracts\HttpClient\Test\HttpClientTestCase as BaseHttpClientTestCase; + +/* +Tests for HTTP2 Push need a recent version of both PHP and curl. This docker command should run them: +docker run -it --rm -v $(pwd):/app -v /path/to/vulcain:/usr/local/bin/vulcain -w /app php:7.3-alpine ./phpunit src/Symfony/Component/HttpClient --filter Push +The vulcain binary can be found at https://github.com/symfony/binary-utils/releases/download/v0.1/vulcain_0.1.3_Linux_x86_64.tar.gz - see https://github.com/dunglas/vulcain for source +*/ + +abstract class HttpClientTestCase extends BaseHttpClientTestCase +{ + private static $vulcainStarted = false; + + public function testTimeoutOnDestruct() + { + if (!method_exists(parent::class, 'testTimeoutOnDestruct')) { + $this->markTestSkipped('BaseHttpClientTestCase doesn\'t have testTimeoutOnDestruct().'); + } + + parent::testTimeoutOnDestruct(); + } + + public function testAcceptHeader() + { + $client = $this->getHttpClient(__FUNCTION__); + + $response = $client->request('GET', 'http://localhost:8057'); + $requestHeaders = $response->toArray(); + + $this->assertSame('*/*', $requestHeaders['HTTP_ACCEPT']); + + $response = $client->request('GET', 'http://localhost:8057', [ + 'headers' => [ + 'Accept' => 'foo/bar', + ], + ]); + $requestHeaders = $response->toArray(); + + $this->assertSame('foo/bar', $requestHeaders['HTTP_ACCEPT']); + + $response = $client->request('GET', 'http://localhost:8057', [ + 'headers' => [ + 'Accept' => null, + ], + ]); + $requestHeaders = $response->toArray(); + + $this->assertArrayNotHasKey('HTTP_ACCEPT', $requestHeaders); + } + + public function testToStream() + { + $client = $this->getHttpClient(__FUNCTION__); + $response = $client->request('GET', 'http://localhost:8057'); + $stream = $response->toStream(); + + $this->assertSame("{\n \"SER", fread($stream, 10)); + $this->assertSame('VER_PROTOCOL', fread($stream, 12)); + $this->assertFalse(feof($stream)); + $this->assertTrue(rewind($stream)); + + $this->assertIsArray(json_decode(fread($stream, 1024), true)); + $this->assertSame('', fread($stream, 1)); + $this->assertTrue(feof($stream)); + } + + public function testStreamCopyToStream() + { + $client = $this->getHttpClient(__FUNCTION__); + $response = $client->request('GET', 'http://localhost:8057'); + $h = fopen('php://temp', 'w+'); + stream_copy_to_stream($response->toStream(), $h); + + $this->assertTrue(rewind($h)); + $this->assertSame("{\n \"SER", fread($h, 10)); + $this->assertSame('VER_PROTOCOL', fread($h, 12)); + $this->assertFalse(feof($h)); + } + + public function testToStream404() + { + $client = $this->getHttpClient(__FUNCTION__); + $response = $client->request('GET', 'http://localhost:8057/404'); + $stream = $response->toStream(false); + + $this->assertSame("{\n \"SER", fread($stream, 10)); + $this->assertSame('VER_PROTOCOL', fread($stream, 12)); + $this->assertSame($response, stream_get_meta_data($stream)['wrapper_data']->getResponse()); + $this->assertSame(404, $response->getStatusCode()); + + $response = $client->request('GET', 'http://localhost:8057/404'); + $this->expectException(ClientException::class); + $response->toStream(); + } + + public function testNonBlockingStream() + { + $client = $this->getHttpClient(__FUNCTION__); + $response = $client->request('GET', 'http://localhost:8057/timeout-body'); + $stream = $response->toStream(); + usleep(10000); + + $this->assertTrue(stream_set_blocking($stream, false)); + $this->assertSame('<1>', fread($stream, 8192)); + $this->assertFalse(feof($stream)); + + $this->assertTrue(stream_set_blocking($stream, true)); + $this->assertSame('<2>', fread($stream, 8192)); + $this->assertSame('', fread($stream, 8192)); + $this->assertTrue(feof($stream)); + } + + public function testSeekAsyncStream() + { + $client = $this->getHttpClient(__FUNCTION__); + $response = $client->request('GET', 'http://localhost:8057/timeout-body'); + $stream = $response->toStream(false); + + $this->assertSame(0, fseek($stream, 0, \SEEK_CUR)); + $this->assertSame('<1>', fread($stream, 8192)); + $this->assertFalse(feof($stream)); + $this->assertSame('<2>', stream_get_contents($stream)); + } + + public function testResponseStreamRewind() + { + $client = $this->getHttpClient(__FUNCTION__); + $response = $client->request('GET', 'http://localhost:8057/103'); + + $stream = $response->toStream(); + + $this->assertSame('Here the body', stream_get_contents($stream)); + rewind($stream); + $this->assertSame('Here the body', stream_get_contents($stream)); + } + + public function testStreamWrapperStreamRewind() + { + $client = $this->getHttpClient(__FUNCTION__); + $response = $client->request('GET', 'http://localhost:8057/103'); + + $stream = StreamWrapper::createResource($response); + + $this->assertSame('Here the body', stream_get_contents($stream)); + rewind($stream); + $this->assertSame('Here the body', stream_get_contents($stream)); + } + + public function testStreamWrapperWithClientStreamRewind() + { + $client = $this->getHttpClient(__FUNCTION__); + $response = $client->request('GET', 'http://localhost:8057/103'); + + $stream = StreamWrapper::createResource($response, $client); + + $this->assertSame('Here the body', stream_get_contents($stream)); + rewind($stream); + $this->assertSame('Here the body', stream_get_contents($stream)); + } + + public function testHttp2PushVulcain() + { + $client = $this->getHttpClient(__FUNCTION__); + self::startVulcain($client); + $logger = new TestLogger(); + $client->setLogger($logger); + + $responseAsArray = $client->request('GET', 'https://127.0.0.1:3000/json', [ + 'headers' => [ + 'Preload' => '/documents/*/id', + ], + ])->toArray(); + + foreach ($responseAsArray['documents'] as $document) { + $client->request('GET', 'https://127.0.0.1:3000'.$document['id'])->toArray(); + } + + $client->reset(); + + $expected = [ + 'Request: "GET https://127.0.0.1:3000/json"', + 'Queueing pushed response: "https://127.0.0.1:3000/json/1"', + 'Queueing pushed response: "https://127.0.0.1:3000/json/2"', + 'Queueing pushed response: "https://127.0.0.1:3000/json/3"', + 'Response: "200 https://127.0.0.1:3000/json"', + 'Accepting pushed response: "GET https://127.0.0.1:3000/json/1"', + 'Response: "200 https://127.0.0.1:3000/json/1"', + 'Accepting pushed response: "GET https://127.0.0.1:3000/json/2"', + 'Response: "200 https://127.0.0.1:3000/json/2"', + 'Accepting pushed response: "GET https://127.0.0.1:3000/json/3"', + 'Response: "200 https://127.0.0.1:3000/json/3"', + ]; + $this->assertSame($expected, $logger->logs); + } + + public function testPause() + { + $client = $this->getHttpClient(__FUNCTION__); + $response = $client->request('GET', 'http://localhost:8057/'); + + $time = microtime(true); + $response->getInfo('pause_handler')(0.5); + $this->assertSame(200, $response->getStatusCode()); + $this->assertTrue(0.5 <= microtime(true) - $time); + + $response = $client->request('GET', 'http://localhost:8057/'); + + $time = microtime(true); + $response->getInfo('pause_handler')(1); + + foreach ($client->stream($response, 0.5) as $chunk) { + $this->assertTrue($chunk->isTimeout()); + $response->cancel(); + } + $response = null; + $this->assertTrue(1.0 > microtime(true) - $time); + $this->assertTrue(0.5 <= microtime(true) - $time); + } + + public function testPauseReplace() + { + $client = $this->getHttpClient(__FUNCTION__); + $response = $client->request('GET', 'http://localhost:8057/'); + + $time = microtime(true); + $response->getInfo('pause_handler')(10); + $response->getInfo('pause_handler')(0.5); + $this->assertSame(200, $response->getStatusCode()); + $this->assertGreaterThanOrEqual(0.5, microtime(true) - $time); + $this->assertLessThanOrEqual(5, microtime(true) - $time); + } + + public function testPauseDuringBody() + { + $client = $this->getHttpClient(__FUNCTION__); + $response = $client->request('GET', 'http://localhost:8057/timeout-body'); + + $time = microtime(true); + $this->assertSame(200, $response->getStatusCode()); + $response->getInfo('pause_handler')(1); + $response->getContent(); + $this->assertGreaterThanOrEqual(1, microtime(true) - $time); + } + + public function testHttp2PushVulcainWithUnusedResponse() + { + $client = $this->getHttpClient(__FUNCTION__); + self::startVulcain($client); + $logger = new TestLogger(); + $client->setLogger($logger); + + $responseAsArray = $client->request('GET', 'https://127.0.0.1:3000/json', [ + 'headers' => [ + 'Preload' => '/documents/*/id', + ], + ])->toArray(); + + $i = 0; + foreach ($responseAsArray['documents'] as $document) { + $client->request('GET', 'https://127.0.0.1:3000'.$document['id'])->toArray(); + if (++$i >= 2) { + break; + } + } + + $client->reset(); + + $expected = [ + 'Request: "GET https://127.0.0.1:3000/json"', + 'Queueing pushed response: "https://127.0.0.1:3000/json/1"', + 'Queueing pushed response: "https://127.0.0.1:3000/json/2"', + 'Queueing pushed response: "https://127.0.0.1:3000/json/3"', + 'Response: "200 https://127.0.0.1:3000/json"', + 'Accepting pushed response: "GET https://127.0.0.1:3000/json/1"', + 'Response: "200 https://127.0.0.1:3000/json/1"', + 'Accepting pushed response: "GET https://127.0.0.1:3000/json/2"', + 'Response: "200 https://127.0.0.1:3000/json/2"', + 'Unused pushed response: "https://127.0.0.1:3000/json/3"', + ]; + $this->assertSame($expected, $logger->logs); + } + + public function testDnsFailure() + { + $client = $this->getHttpClient(__FUNCTION__); + $response = $client->request('GET', 'http://bad.host.test/'); + + $this->expectException(TransportException::class); + $response->getStatusCode(); + } + + private static function startVulcain(HttpClientInterface $client) + { + if (self::$vulcainStarted) { + return; + } + + if ('\\' === \DIRECTORY_SEPARATOR) { + throw new SkippedTestSuiteError('Testing with the "vulcain" is not supported on Windows.'); + } + + if (['application/json'] !== $client->request('GET', 'http://127.0.0.1:8057/json')->getHeaders()['content-type']) { + throw new SkippedTestSuiteError('symfony/http-client-contracts >= 2.0.1 required'); + } + + $process = new Process(['vulcain'], null, [ + 'DEBUG' => 1, + 'UPSTREAM' => 'http://127.0.0.1:8057', + 'ADDR' => ':3000', + 'KEY_FILE' => __DIR__.'/Fixtures/tls/server.key', + 'CERT_FILE' => __DIR__.'/Fixtures/tls/server.crt', + ]); + $process->start(); + + register_shutdown_function([$process, 'stop']); + sleep('\\' === \DIRECTORY_SEPARATOR ? 10 : 1); + + if (!$process->isRunning()) { + if ('\\' !== \DIRECTORY_SEPARATOR && 127 === $process->getExitCode()) { + throw new SkippedTestSuiteError('vulcain binary is missing'); + } + + if ('\\' !== \DIRECTORY_SEPARATOR && 126 === $process->getExitCode()) { + throw new SkippedTestSuiteError('vulcain binary is not executable'); + } + + throw new SkippedTestSuiteError((new ProcessFailedException($process))->getMessage()); + } + + self::$vulcainStarted = true; + } + + public function testHandleIsRemovedOnException() + { + $client = $this->getHttpClient(__FUNCTION__); + + try { + $client->request('GET', 'http://localhost:8057/304'); + $this->fail(RedirectionExceptionInterface::class.' expected'); + } catch (RedirectionExceptionInterface $e) { + // The response content-type mustn't be json as that calls getContent + // @see src/Symfony/Component/HttpClient/Exception/HttpExceptionTrait.php:58 + $this->assertStringNotContainsString('json', $e->getResponse()->getHeaders(false)['content-type'][0] ?? ''); + unset($e); + + $r = new \ReflectionProperty($client, 'multi'); + $r->setAccessible(true); + /** @var ClientState $clientState */ + $clientState = $r->getValue($client); + + $this->assertCount(0, $clientState->handlesActivity); + $this->assertCount(0, $clientState->openHandles); + } + } + + public function testDebugInfoOnDestruct() + { + $client = $this->getHttpClient(__FUNCTION__); + + $traceInfo = []; + $client->request('GET', 'http://localhost:8057', ['on_progress' => function (int $dlNow, int $dlSize, array $info) use (&$traceInfo) { + $traceInfo = $info; + }]); + + $this->assertNotEmpty($traceInfo['debug']); + } + + public function testFixContentLength() + { + $client = $this->getHttpClient(__FUNCTION__); + + $response = $client->request('POST', 'http://localhost:8057/post', [ + 'body' => 'abc=def', + 'headers' => ['Content-Length: 4'], + ]); + + $body = $response->toArray(); + + $this->assertSame(['abc' => 'def', 'REQUEST_METHOD' => 'POST'], $body); + } + + public function testDropContentRelatedHeadersWhenFollowingRequestIsUsingGet() + { + $client = $this->getHttpClient(__FUNCTION__); + + $response = $client->request('POST', 'http://localhost:8057/302', [ + 'body' => 'foo', + 'headers' => ['Content-Length: 3'], + ]); + + $this->assertSame(200, $response->getStatusCode()); + } + + public function testNegativeTimeout() + { + $client = $this->getHttpClient(__FUNCTION__); + + $this->assertSame(200, $client->request('GET', 'http://localhost:8057', [ + 'timeout' => -1, + ])->getStatusCode()); + } + + public function testRedirectAfterPost() + { + $client = $this->getHttpClient(__FUNCTION__); + + $response = $client->request('POST', 'http://localhost:8057/302/relative', [ + 'body' => '', + ]); + + $this->assertSame(200, $response->getStatusCode()); + $this->assertStringContainsStringIgnoringCase("\r\nContent-Length: 0", $response->getInfo('debug')); + } + + public function testEmptyPut() + { + $client = $this->getHttpClient(__FUNCTION__); + + $response = $client->request('PUT', 'http://localhost:8057/post', [ + 'headers' => ['Content-Length' => '0'], + ]); + + $this->assertSame(200, $response->getStatusCode()); + $this->assertStringContainsString("\r\nContent-Length: ", $response->getInfo('debug')); + } + + public function testNullBody() + { + $client = $this->getHttpClient(__FUNCTION__); + + $client->request('POST', 'http://localhost:8057/post', [ + 'body' => null, + ]); + + $this->expectNotToPerformAssertions(); + } +} diff --git a/sites/all/libraries/vendor/symfony/http-client/Tests/HttpClientTraitTest.php b/sites/all/libraries/vendor/symfony/http-client/Tests/HttpClientTraitTest.php new file mode 100644 index 0000000000..b811626c0c --- /dev/null +++ b/sites/all/libraries/vendor/symfony/http-client/Tests/HttpClientTraitTest.php @@ -0,0 +1,277 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient\Tests; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpClient\Exception\InvalidArgumentException; +use Symfony\Component\HttpClient\HttpClientTrait; +use Symfony\Contracts\HttpClient\HttpClientInterface; + +class HttpClientTraitTest extends TestCase +{ + use HttpClientTrait; + + private const RFC3986_BASE = 'http://a/b/c/d;p?q'; + + /** + * @dataProvider providePrepareRequestUrl + */ + public function testPrepareRequestUrl(string $expected, string $url, array $query = []) + { + $defaults = [ + 'base_uri' => 'http://example.com?c=c', + 'query' => ['a' => 1, 'b' => 'b'], + ]; + [, $defaults] = self::prepareRequest(null, null, $defaults); + + [$url] = self::prepareRequest(null, $url, ['query' => $query], $defaults); + $this->assertSame($expected, implode('', $url)); + } + + public function providePrepareRequestUrl(): iterable + { + yield ['http://example.com/', 'http://example.com/']; + yield ['http://example.com/?a=1&b=b', '.']; + yield ['http://example.com/?a=2&b=b', '.?a=2']; + yield ['http://example.com/?a=3&b=b', '.', ['a' => 3]]; + yield ['http://example.com/?a=3&b=b', '.?a=0', ['a' => 3]]; + yield ['http://example.com/', 'http://example.com/', ['a' => null]]; + yield ['http://example.com/?b=', 'http://example.com/', ['b' => '']]; + yield ['http://example.com/?b=', 'http://example.com/', ['a' => null, 'b' => '']]; + } + + /** + * @dataProvider provideResolveUrl + */ + public function testResolveUrl(string $base, string $url, string $expected) + { + $this->assertSame($expected, implode('', self::resolveUrl(self::parseUrl($url), self::parseUrl($base)))); + } + + /** + * From https://github.com/guzzle/psr7/blob/master/tests/UriResoverTest.php. + */ + public function provideResolveUrl(): array + { + return [ + [self::RFC3986_BASE, 'http:h', 'http:h'], + [self::RFC3986_BASE, 'g', 'http://a/b/c/g'], + [self::RFC3986_BASE, './g', 'http://a/b/c/g'], + [self::RFC3986_BASE, 'g/', 'http://a/b/c/g/'], + [self::RFC3986_BASE, '/g', 'http://a/g'], + [self::RFC3986_BASE, '//g', 'http://g/'], + [self::RFC3986_BASE, '?y', 'http://a/b/c/d;p?y'], + [self::RFC3986_BASE, 'g?y', 'http://a/b/c/g?y'], + [self::RFC3986_BASE, '#s', 'http://a/b/c/d;p?q#s'], + [self::RFC3986_BASE, 'g#s', 'http://a/b/c/g#s'], + [self::RFC3986_BASE, 'g?y#s', 'http://a/b/c/g?y#s'], + [self::RFC3986_BASE, ';x', 'http://a/b/c/;x'], + [self::RFC3986_BASE, 'g;x', 'http://a/b/c/g;x'], + [self::RFC3986_BASE, 'g;x?y#s', 'http://a/b/c/g;x?y#s'], + [self::RFC3986_BASE, '', self::RFC3986_BASE], + [self::RFC3986_BASE, '.', 'http://a/b/c/'], + [self::RFC3986_BASE, './', 'http://a/b/c/'], + [self::RFC3986_BASE, '..', 'http://a/b/'], + [self::RFC3986_BASE, '../', 'http://a/b/'], + [self::RFC3986_BASE, '../g', 'http://a/b/g'], + [self::RFC3986_BASE, '../..', 'http://a/'], + [self::RFC3986_BASE, '../../', 'http://a/'], + [self::RFC3986_BASE, '../../g', 'http://a/g'], + [self::RFC3986_BASE, '../../../g', 'http://a/g'], + [self::RFC3986_BASE, '../../../../g', 'http://a/g'], + [self::RFC3986_BASE, '/./g', 'http://a/g'], + [self::RFC3986_BASE, '/../g', 'http://a/g'], + [self::RFC3986_BASE, 'g.', 'http://a/b/c/g.'], + [self::RFC3986_BASE, '.g', 'http://a/b/c/.g'], + [self::RFC3986_BASE, 'g..', 'http://a/b/c/g..'], + [self::RFC3986_BASE, '..g', 'http://a/b/c/..g'], + [self::RFC3986_BASE, './../g', 'http://a/b/g'], + [self::RFC3986_BASE, 'foo////g', 'http://a/b/c/foo////g'], + [self::RFC3986_BASE, './g/.', 'http://a/b/c/g/'], + [self::RFC3986_BASE, 'g/./h', 'http://a/b/c/g/h'], + [self::RFC3986_BASE, 'g/../h', 'http://a/b/c/h'], + [self::RFC3986_BASE, 'g;x=1/./y', 'http://a/b/c/g;x=1/y'], + [self::RFC3986_BASE, 'g;x=1/../y', 'http://a/b/c/y'], + // dot-segments in the query or fragment + [self::RFC3986_BASE, 'g?y/./x', 'http://a/b/c/g?y/./x'], + [self::RFC3986_BASE, 'g?y/../x', 'http://a/b/c/g?y/../x'], + [self::RFC3986_BASE, 'g#s/./x', 'http://a/b/c/g#s/./x'], + [self::RFC3986_BASE, 'g#s/../x', 'http://a/b/c/g#s/../x'], + [self::RFC3986_BASE, 'g#s/../x', 'http://a/b/c/g#s/../x'], + [self::RFC3986_BASE, '?y#s', 'http://a/b/c/d;p?y#s'], + // base with fragment + ['http://a/b/c?q#s', '?y', 'http://a/b/c?y'], + // base with user info + ['http://u@a/b/c/d;p?q', '.', 'http://u@a/b/c/'], + ['http://u:p@a/b/c/d;p?q', '.', 'http://u:p@a/b/c/'], + // path ending with slash or no slash at all + ['http://a/b/c/d/', 'e', 'http://a/b/c/d/e'], + ['http:no-slash', 'e', 'http:e'], + // falsey relative parts + [self::RFC3986_BASE, '//0', 'http://0/'], + [self::RFC3986_BASE, '0', 'http://a/b/c/0'], + [self::RFC3986_BASE, '?0', 'http://a/b/c/d;p?0'], + [self::RFC3986_BASE, '#0', 'http://a/b/c/d;p?q#0'], + ]; + } + + public function testResolveUrlWithoutScheme() + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('Invalid URL: scheme is missing in "//localhost:8080". Did you forget to add "http(s)://"?'); + self::resolveUrl(self::parseUrl('localhost:8080'), null); + } + + public function testResolveBaseUrlWitoutScheme() + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('Invalid URL: scheme is missing in "//localhost:8081". Did you forget to add "http(s)://"?'); + self::resolveUrl(self::parseUrl('/foo'), self::parseUrl('localhost:8081')); + } + + /** + * @dataProvider provideParseUrl + */ + public function testParseUrl(array $expected, string $url, array $query = []) + { + $expected = array_combine(['scheme', 'authority', 'path', 'query', 'fragment'], $expected); + + $this->assertSame($expected, self::parseUrl($url, $query)); + } + + public function provideParseUrl(): iterable + { + yield [['http:', '//example.com', null, null, null], 'http://Example.coM:80']; + yield [['https:', '//xn--dj-kia8a.example.com:8000', '/', null, null], 'https://DÉjà.Example.com:8000/']; + yield [[null, null, '/f%20o.o', '?a=b', '#c'], '/f o%2Eo?a=b#c']; + yield [[null, '//a:b@foo', '/bar', null, null], '//a:b@foo/bar']; + yield [['http:', null, null, null, null], 'http:']; + yield [['http:', null, 'bar', null, null], 'http:bar']; + yield [[null, null, 'bar', '?a=1&c=c', null], 'bar?a=a&b=b', ['b' => null, 'c' => 'c', 'a' => 1]]; + yield [[null, null, 'bar', '?a=b+c&b=b', null], 'bar?a=b+c', ['b' => 'b']]; + yield [[null, null, 'bar', '?a=b%2B%20c', null], 'bar?a=b+c', ['a' => 'b+ c']]; + yield [[null, null, 'bar', '?a%5Bb%5D=c', null], 'bar', ['a' => ['b' => 'c']]]; + yield [[null, null, 'bar', '?a%5Bb%5Bc%5D=d', null], 'bar?a[b[c]=d', []]; + yield [[null, null, 'bar', '?a%5Bb%5D%5Bc%5D=dd', null], 'bar?a[b][c]=d&e[f]=g', ['a' => ['b' => ['c' => 'dd']], 'e[f]' => null]]; + yield [[null, null, 'bar', '?a=b&a%5Bb%20c%5D=d&e%3Df=%E2%9C%93', null], 'bar?a=b', ['a' => ['b c' => 'd'], 'e=f' => '✓']]; + // IDNA 2008 compliance + yield [['https:', '//xn--fuball-cta.test', null, null, null], 'https://fußball.test']; + } + + /** + * @dataProvider provideRemoveDotSegments + */ + public function testRemoveDotSegments($expected, $url) + { + $this->assertSame($expected, self::removeDotSegments($url)); + } + + public function provideRemoveDotSegments() + { + yield ['', '']; + yield ['', '.']; + yield ['', '..']; + yield ['a', './a']; + yield ['a', '../a']; + yield ['/a/b', '/a/./b']; + yield ['/b/', '/a/../b/.']; + yield ['/a//b/', '/a///../b/.']; + yield ['/a/', '/a/b/..']; + yield ['/a///b', '/a///b']; + } + + public function testAuthBearerOption() + { + [, $options] = self::prepareRequest('POST', 'http://example.com', ['auth_bearer' => 'foobar'], HttpClientInterface::OPTIONS_DEFAULTS); + $this->assertSame(['Accept: */*', 'Authorization: Bearer foobar'], $options['headers']); + $this->assertSame(['Authorization: Bearer foobar'], $options['normalized_headers']['authorization']); + } + + public function testInvalidAuthBearerOption() + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('Option "auth_bearer" must be a string, "stdClass" given.'); + self::prepareRequest('POST', 'http://example.com', ['auth_bearer' => new \stdClass()], HttpClientInterface::OPTIONS_DEFAULTS); + } + + public function testInvalidAuthBearerValue() + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('Invalid character found in option "auth_bearer": "a\nb".'); + self::prepareRequest('POST', 'http://example.com', ['auth_bearer' => "a\nb"], HttpClientInterface::OPTIONS_DEFAULTS); + } + + public function testSetAuthBasicAndBearerOptions() + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('Define either the "auth_basic" or the "auth_bearer" option, setting both is not supported.'); + self::prepareRequest('POST', 'http://example.com', ['auth_bearer' => 'foo', 'auth_basic' => 'foo:bar'], HttpClientInterface::OPTIONS_DEFAULTS); + } + + public function testSetJSONAndBodyOptions() + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('Define either the "json" or the "body" option, setting both is not supported'); + self::prepareRequest('POST', 'http://example.com', ['json' => ['foo' => 'bar'], 'body' => ''], HttpClientInterface::OPTIONS_DEFAULTS); + } + + public function providePrepareAuthBasic() + { + yield ['foo:bar', 'Zm9vOmJhcg==']; + yield [['foo', 'bar'], 'Zm9vOmJhcg==']; + yield ['foo', 'Zm9v']; + yield [['foo'], 'Zm9v']; + } + + /** + * @dataProvider providePrepareAuthBasic + */ + public function testPrepareAuthBasic($arg, $result) + { + [, $options] = $this->prepareRequest('POST', 'http://example.com', ['auth_basic' => $arg], HttpClientInterface::OPTIONS_DEFAULTS); + $this->assertSame('Authorization: Basic '.$result, $options['normalized_headers']['authorization'][0]); + } + + public function provideFingerprints() + { + foreach (['md5', 'sha1', 'sha256'] as $algo) { + $hash = hash($algo, $algo); + yield [$hash, [$algo => $hash]]; + } + + yield ['AAAA:BBBB:CCCC:DDDD:EEEE:FFFF:GGGG:HHHH:IIII:JJJJ:KKKK', ['pin-sha256' => ['AAAABBBBCCCCDDDDEEEEFFFFGGGGHHHHIIIIJJJJKKKK']]]; + } + + /** + * @dataProvider provideFingerprints + */ + public function testNormalizePeerFingerprint($fingerprint, $expected) + { + self::assertSame($expected, $this->normalizePeerFingerprint($fingerprint)); + } + + public function testNormalizePeerFingerprintException() + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('Cannot auto-detect fingerprint algorithm for "foo".'); + $this->normalizePeerFingerprint('foo'); + } + + public function testNormalizePeerFingerprintTypeException() + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('Option "peer_fingerprint" must be string or array, "stdClass" given.'); + $fingerprint = new \stdClass(); + + $this->normalizePeerFingerprint($fingerprint); + } +} diff --git a/sites/all/libraries/vendor/symfony/http-client/Tests/HttpOptionsTest.php b/sites/all/libraries/vendor/symfony/http-client/Tests/HttpOptionsTest.php new file mode 100644 index 0000000000..df5cb394df --- /dev/null +++ b/sites/all/libraries/vendor/symfony/http-client/Tests/HttpOptionsTest.php @@ -0,0 +1,42 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient\Tests; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpClient\HttpOptions; + +/** + * @author Kévin Dunglas + */ +class HttpOptionsTest extends TestCase +{ + public function provideSetAuthBasic(): iterable + { + yield ['user:password', 'user', 'password']; + yield ['user:password', 'user:password']; + yield ['user', 'user']; + yield ['user:0', 'user', '0']; + } + + /** + * @dataProvider provideSetAuthBasic + */ + public function testSetAuthBasic(string $expected, string $user, string $password = '') + { + $this->assertSame($expected, (new HttpOptions())->setAuthBasic($user, $password)->toArray()['auth_basic']); + } + + public function testSetAuthBearer() + { + $this->assertSame('foobar', (new HttpOptions())->setAuthBearer('foobar')->toArray()['auth_bearer']); + } +} diff --git a/sites/all/libraries/vendor/symfony/http-client/Tests/HttplugClientTest.php b/sites/all/libraries/vendor/symfony/http-client/Tests/HttplugClientTest.php new file mode 100644 index 0000000000..1f48be5c57 --- /dev/null +++ b/sites/all/libraries/vendor/symfony/http-client/Tests/HttplugClientTest.php @@ -0,0 +1,270 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient\Tests; + +use GuzzleHttp\Promise\FulfilledPromise as GuzzleFulfilledPromise; +use Http\Client\Exception\NetworkException; +use Http\Client\Exception\RequestException; +use Http\Promise\FulfilledPromise; +use Http\Promise\Promise; +use PHPUnit\Framework\TestCase; +use Psr\Http\Message\ResponseInterface; +use Symfony\Component\HttpClient\Exception\TransportException; +use Symfony\Component\HttpClient\HttplugClient; +use Symfony\Component\HttpClient\MockHttpClient; +use Symfony\Component\HttpClient\NativeHttpClient; +use Symfony\Component\HttpClient\Response\MockResponse; +use Symfony\Contracts\HttpClient\Test\TestHttpServer; + +class HttplugClientTest extends TestCase +{ + public static function setUpBeforeClass(): void + { + TestHttpServer::start(); + } + + public function testSendRequest() + { + $client = new HttplugClient(new NativeHttpClient()); + + $response = $client->sendRequest($client->createRequest('GET', 'http://localhost:8057')); + + $this->assertSame(200, $response->getStatusCode()); + $this->assertSame('application/json', $response->getHeaderLine('content-type')); + + $body = json_decode((string) $response->getBody(), true); + + $this->assertSame('HTTP/1.1', $body['SERVER_PROTOCOL']); + } + + public function testSendAsyncRequest() + { + $client = new HttplugClient(new NativeHttpClient()); + + $promise = $client->sendAsyncRequest($client->createRequest('GET', 'http://localhost:8057')); + $successCallableCalled = false; + $failureCallableCalled = false; + $promise->then(function (ResponseInterface $response) use (&$successCallableCalled) { + $successCallableCalled = true; + + return $response; + }, function (\Exception $exception) use (&$failureCallableCalled) { + $failureCallableCalled = true; + + throw $exception; + }); + + $this->assertEquals(Promise::PENDING, $promise->getState()); + + $response = $promise->wait(true); + $this->assertTrue($successCallableCalled, '$promise->then() was never called.'); + $this->assertFalse($failureCallableCalled, 'Failure callable should not be called when request is successful.'); + $this->assertEquals(Promise::FULFILLED, $promise->getState()); + + $this->assertSame(200, $response->getStatusCode()); + $this->assertSame('application/json', $response->getHeaderLine('content-type')); + + $body = json_decode((string) $response->getBody(), true); + + $this->assertSame('HTTP/1.1', $body['SERVER_PROTOCOL']); + } + + public function testWait() + { + $client = new HttplugClient(new NativeHttpClient()); + + $successCallableCalled = false; + $failureCallableCalled = false; + $client->sendAsyncRequest($client->createRequest('GET', 'http://localhost:8057/timeout-body')) + ->then(function (ResponseInterface $response) use (&$successCallableCalled) { + $successCallableCalled = true; + + return $response; + }, function (\Exception $exception) use (&$failureCallableCalled) { + $failureCallableCalled = true; + + throw $exception; + }); + + $client->wait(0); + $this->assertFalse($successCallableCalled, '$promise->then() should not be called yet.'); + + $client->wait(); + $this->assertTrue($successCallableCalled, '$promise->then() should have been called.'); + $this->assertFalse($failureCallableCalled, 'Failure callable should not be called when request is successful.'); + } + + public function testPostRequest() + { + $client = new HttplugClient(new NativeHttpClient()); + + $request = $client->createRequest('POST', 'http://localhost:8057/post') + ->withBody($client->createStream('foo=0123456789')); + + $response = $client->sendRequest($request); + $body = json_decode((string) $response->getBody(), true); + + $this->assertSame(['foo' => '0123456789', 'REQUEST_METHOD' => 'POST'], $body); + } + + public function testNetworkException() + { + $client = new HttplugClient(new NativeHttpClient()); + + $this->expectException(NetworkException::class); + $client->sendRequest($client->createRequest('GET', 'http://localhost:8058')); + } + + public function testAsyncNetworkException() + { + $client = new HttplugClient(new NativeHttpClient()); + + $promise = $client->sendAsyncRequest($client->createRequest('GET', 'http://localhost:8058')); + $successCallableCalled = false; + $failureCallableCalled = false; + $promise->then(function (ResponseInterface $response) use (&$successCallableCalled) { + $successCallableCalled = true; + + return $response; + }, function (\Exception $exception) use (&$failureCallableCalled) { + $failureCallableCalled = true; + + throw $exception; + }); + + $promise->wait(false); + $this->assertFalse($successCallableCalled, 'Success callable should not be called when request fails.'); + $this->assertTrue($failureCallableCalled, 'Failure callable was never called.'); + $this->assertEquals(Promise::REJECTED, $promise->getState()); + + $this->expectException(NetworkException::class); + $promise->wait(true); + } + + public function testRequestException() + { + $client = new HttplugClient(new NativeHttpClient()); + + $this->expectException(RequestException::class); + $client->sendRequest($client->createRequest('BAD.METHOD', 'http://localhost:8057')); + } + + public function testRetry404() + { + $client = new HttplugClient(new NativeHttpClient()); + + $successCallableCalled = false; + $failureCallableCalled = false; + + $promise = $client + ->sendAsyncRequest($client->createRequest('GET', 'http://localhost:8057/404')) + ->then( + function (ResponseInterface $response) use (&$successCallableCalled, $client) { + $this->assertSame(404, $response->getStatusCode()); + $successCallableCalled = true; + + return $client->sendAsyncRequest($client->createRequest('GET', 'http://localhost:8057')); + }, + function (\Exception $exception) use (&$failureCallableCalled) { + $failureCallableCalled = true; + + throw $exception; + } + ) + ; + + $response = $promise->wait(true); + + $this->assertTrue($successCallableCalled); + $this->assertFalse($failureCallableCalled); + $this->assertSame(200, $response->getStatusCode()); + } + + public function testRetryNetworkError() + { + $client = new HttplugClient(new NativeHttpClient()); + + $successCallableCalled = false; + $failureCallableCalled = false; + + $promise = $client + ->sendAsyncRequest($client->createRequest('GET', 'http://localhost:8057/chunked-broken')) + ->then(function (ResponseInterface $response) use (&$successCallableCalled) { + $successCallableCalled = true; + + return $response; + }, function (\Exception $exception) use (&$failureCallableCalled, $client) { + $this->assertSame(NetworkException::class, \get_class($exception)); + $this->assertSame(TransportException::class, \get_class($exception->getPrevious())); + $failureCallableCalled = true; + + return $client->sendAsyncRequest($client->createRequest('GET', 'http://localhost:8057')); + }) + ; + + $response = $promise->wait(true); + + $this->assertFalse($successCallableCalled); + $this->assertTrue($failureCallableCalled); + $this->assertSame(200, $response->getStatusCode()); + } + + public function testRetryEarlierError() + { + $isFirstRequest = true; + $errorMessage = 'Error occurred before making the actual request.'; + + $client = new HttplugClient(new MockHttpClient(function () use (&$isFirstRequest, $errorMessage) { + if ($isFirstRequest) { + $isFirstRequest = false; + throw new TransportException($errorMessage); + } + + return new MockResponse('OK', ['http_code' => 200]); + })); + + $request = $client->createRequest('GET', 'http://test'); + + $successCallableCalled = false; + $failureCallableCalled = false; + + $promise = $client + ->sendAsyncRequest($request) + ->then( + function (ResponseInterface $response) use (&$successCallableCalled) { + $successCallableCalled = true; + + return $response; + }, + function (\Exception $exception) use ($errorMessage, &$failureCallableCalled, $client, $request) { + $this->assertSame(NetworkException::class, \get_class($exception)); + $this->assertSame($errorMessage, $exception->getMessage()); + $failureCallableCalled = true; + + // Ensure arbitrary levels of promises work. + return (new FulfilledPromise(null))->then(function () use ($client, $request) { + return (new GuzzleFulfilledPromise(null))->then(function () use ($client, $request) { + return $client->sendAsyncRequest($request); + }); + }); + } + ) + ; + + $response = $promise->wait(true); + + $this->assertFalse($successCallableCalled); + $this->assertTrue($failureCallableCalled); + $this->assertSame(200, $response->getStatusCode()); + $this->assertSame('OK', (string) $response->getBody()); + } +} diff --git a/sites/all/libraries/vendor/symfony/http-client/Tests/MockHttpClientTest.php b/sites/all/libraries/vendor/symfony/http-client/Tests/MockHttpClientTest.php new file mode 100644 index 0000000000..e06575cfc7 --- /dev/null +++ b/sites/all/libraries/vendor/symfony/http-client/Tests/MockHttpClientTest.php @@ -0,0 +1,509 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient\Tests; + +use Symfony\Component\HttpClient\Chunk\DataChunk; +use Symfony\Component\HttpClient\Chunk\ErrorChunk; +use Symfony\Component\HttpClient\Chunk\FirstChunk; +use Symfony\Component\HttpClient\Exception\InvalidArgumentException; +use Symfony\Component\HttpClient\Exception\TransportException; +use Symfony\Component\HttpClient\MockHttpClient; +use Symfony\Component\HttpClient\NativeHttpClient; +use Symfony\Component\HttpClient\Response\MockResponse; +use Symfony\Component\HttpClient\Response\ResponseStream; +use Symfony\Contracts\HttpClient\ChunkInterface; +use Symfony\Contracts\HttpClient\HttpClientInterface; + +class MockHttpClientTest extends HttpClientTestCase +{ + /** + * @dataProvider mockingProvider + */ + public function testMocking($factory, array $expectedResponses) + { + $client = new MockHttpClient($factory); + $this->assertSame(0, $client->getRequestsCount()); + + $urls = ['/foo', '/bar']; + foreach ($urls as $i => $url) { + $response = $client->request('POST', $url, ['body' => 'payload']); + $this->assertEquals($expectedResponses[$i], $response->getContent()); + } + + $this->assertSame(2, $client->getRequestsCount()); + } + + public function mockingProvider(): iterable + { + yield 'callable' => [ + static function (string $method, string $url, array $options = []) { + return new MockResponse($method.': '.$url.' (body='.$options['body'].')'); + }, + [ + 'POST: https://example.com/foo (body=payload)', + 'POST: https://example.com/bar (body=payload)', + ], + ]; + + yield 'array of callable' => [ + [ + static function (string $method, string $url, array $options = []) { + return new MockResponse($method.': '.$url.' (body='.$options['body'].') [1]'); + }, + static function (string $method, string $url, array $options = []) { + return new MockResponse($method.': '.$url.' (body='.$options['body'].') [2]'); + }, + ], + [ + 'POST: https://example.com/foo (body=payload) [1]', + 'POST: https://example.com/bar (body=payload) [2]', + ], + ]; + + yield 'array of response objects' => [ + [ + new MockResponse('static response [1]'), + new MockResponse('static response [2]'), + ], + [ + 'static response [1]', + 'static response [2]', + ], + ]; + + yield 'iterator' => [ + new \ArrayIterator( + [ + new MockResponse('static response [1]'), + new MockResponse('static response [2]'), + ] + ), + [ + 'static response [1]', + 'static response [2]', + ], + ]; + + yield 'null' => [ + null, + [ + '', + '', + ], + ]; + } + + /** + * @dataProvider validResponseFactoryProvider + */ + public function testValidResponseFactory($responseFactory) + { + (new MockHttpClient($responseFactory))->request('GET', 'https://foo.bar'); + + $this->addToAssertionCount(1); + } + + public function validResponseFactoryProvider() + { + return [ + [static function (): MockResponse { return new MockResponse(); }], + [new MockResponse()], + [[new MockResponse()]], + [new \ArrayIterator([new MockResponse()])], + [null], + [(static function (): \Generator { yield new MockResponse(); })()], + ]; + } + + /** + * @dataProvider transportExceptionProvider + */ + public function testTransportExceptionThrowsIfPerformedMoreRequestsThanConfigured($factory) + { + $client = new MockHttpClient($factory); + + $client->request('POST', '/foo'); + $client->request('POST', '/foo'); + + $this->expectException(TransportException::class); + $client->request('POST', '/foo'); + } + + public function transportExceptionProvider(): iterable + { + yield 'array of callable' => [ + [ + static function (string $method, string $url, array $options = []) { + return new MockResponse(); + }, + static function (string $method, string $url, array $options = []) { + return new MockResponse(); + }, + ], + ]; + + yield 'array of response objects' => [ + [ + new MockResponse(), + new MockResponse(), + ], + ]; + + yield 'iterator' => [ + new \ArrayIterator( + [ + new MockResponse(), + new MockResponse(), + ] + ), + ]; + } + + /** + * @dataProvider invalidResponseFactoryProvider + */ + public function testInvalidResponseFactory($responseFactory, string $expectedExceptionMessage) + { + $this->expectException(TransportException::class); + $this->expectExceptionMessage($expectedExceptionMessage); + + (new MockHttpClient($responseFactory))->request('GET', 'https://foo.bar'); + } + + public function invalidResponseFactoryProvider() + { + return [ + [static function (): \Generator { yield new MockResponse(); }, 'The response factory passed to MockHttpClient must return/yield an instance of ResponseInterface, "Generator" given.'], + [static function (): array { return [new MockResponse()]; }, 'The response factory passed to MockHttpClient must return/yield an instance of ResponseInterface, "array" given.'], + [(static function (): \Generator { yield 'ccc'; })(), 'The response factory passed to MockHttpClient must return/yield an instance of ResponseInterface, "string" given.'], + ]; + } + + public function testZeroStatusCode() + { + $client = new MockHttpClient(new MockResponse('', ['response_headers' => ['HTTP/1.1 000 ']])); + $response = $client->request('GET', 'https://foo.bar'); + $this->assertSame(0, $response->getStatusCode()); + } + + public function testFixContentLength() + { + $client = new MockHttpClient(); + + $response = $client->request('POST', 'http://localhost:8057/post', [ + 'body' => 'abc=def', + 'headers' => ['Content-Length: 4'], + ]); + + $requestOptions = $response->getRequestOptions(); + $this->assertSame('Content-Length: 7', $requestOptions['headers'][0]); + $this->assertSame(['Content-Length: 7'], $requestOptions['normalized_headers']['content-length']); + + $response = $client->request('POST', 'http://localhost:8057/post', [ + 'body' => 'abc=def', + ]); + + $requestOptions = $response->getRequestOptions(); + $this->assertSame('Content-Length: 7', $requestOptions['headers'][1]); + $this->assertSame(['Content-Length: 7'], $requestOptions['normalized_headers']['content-length']); + + $response = $client->request('POST', 'http://localhost:8057/post', [ + 'body' => "8\r\nSymfony \r\n5\r\nis aw\r\n6\r\nesome!\r\n0\r\n\r\n", + 'headers' => ['Transfer-Encoding: chunked'], + ]); + + $requestOptions = $response->getRequestOptions(); + $this->assertSame(['Content-Length: 19'], $requestOptions['normalized_headers']['content-length']); + + $response = $client->request('POST', 'http://localhost:8057/post', [ + 'body' => '', + ]); + + $requestOptions = $response->getRequestOptions(); + $this->assertFalse(isset($requestOptions['normalized_headers']['content-length'])); + } + + public function testThrowExceptionInBodyGenerator() + { + $mockHttpClient = new MockHttpClient([ + new MockResponse((static function (): \Generator { + yield 'foo'; + throw new TransportException('foo ccc'); + })()), + new MockResponse((static function (): \Generator { + yield 'bar'; + throw new \RuntimeException('bar ccc'); + })()), + ]); + + try { + $mockHttpClient->request('GET', 'https://symfony.com', [])->getContent(); + $this->fail(); + } catch (TransportException $e) { + $this->assertEquals(new TransportException('foo ccc'), $e->getPrevious()); + $this->assertSame('foo ccc', $e->getMessage()); + } + + $chunks = []; + try { + foreach ($mockHttpClient->stream($mockHttpClient->request('GET', 'https://symfony.com', [])) as $chunk) { + $chunks[] = $chunk; + } + $this->fail(); + } catch (TransportException $e) { + $this->assertEquals(new \RuntimeException('bar ccc'), $e->getPrevious()); + $this->assertSame('bar ccc', $e->getMessage()); + } + + $this->assertCount(3, $chunks); + $this->assertEquals(new FirstChunk(0, ''), $chunks[0]); + $this->assertEquals(new DataChunk(0, 'bar'), $chunks[1]); + $this->assertInstanceOf(ErrorChunk::class, $chunks[2]); + $this->assertSame(3, $chunks[2]->getOffset()); + $this->assertSame('bar ccc', $chunks[2]->getError()); + } + + public function testMergeDefaultOptions() + { + $mockHttpClient = new MockHttpClient(null, 'https://example.com'); + + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('Invalid URL: scheme is missing'); + $mockHttpClient->request('GET', '/foo', ['base_uri' => null]); + } + + protected function getHttpClient(string $testCase): HttpClientInterface + { + $responses = []; + + $headers = [ + 'Host: localhost:8057', + 'Content-Type: application/json', + ]; + + $body = '{ + "SERVER_PROTOCOL": "HTTP/1.1", + "SERVER_NAME": "127.0.0.1", + "REQUEST_URI": "/", + "REQUEST_METHOD": "GET", + "HTTP_ACCEPT": "*/*", + "HTTP_FOO": "baR", + "HTTP_HOST": "localhost:8057" +}'; + + $client = new NativeHttpClient(); + + switch ($testCase) { + default: + return new MockHttpClient(function (string $method, string $url, array $options) use ($client) { + try { + // force the request to be completed so that we don't test side effects of the transport + $response = $client->request($method, $url, ['buffer' => false] + $options); + $content = $response->getContent(false); + + return new MockResponse($content, $response->getInfo()); + } catch (\Throwable $e) { + $this->fail($e->getMessage()); + } + }); + + case 'testUnsupportedOption': + $this->markTestSkipped('MockHttpClient accepts any options by default'); + break; + + case 'testChunkedEncoding': + $this->markTestSkipped("MockHttpClient doesn't dechunk"); + break; + + case 'testGzipBroken': + $this->markTestSkipped("MockHttpClient doesn't unzip"); + break; + + case 'testTimeoutWithActiveConcurrentStream': + $this->markTestSkipped('Real transport required'); + break; + + case 'testTimeoutOnInitialize': + case 'testTimeoutOnDestruct': + $this->markTestSkipped('Real transport required'); + break; + + case 'testDestruct': + $this->markTestSkipped("MockHttpClient doesn't timeout on destruct"); + break; + + case 'testHandleIsRemovedOnException': + $this->markTestSkipped("MockHttpClient doesn't cache handles"); + break; + + case 'testPause': + case 'testPauseReplace': + case 'testPauseDuringBody': + $this->markTestSkipped("MockHttpClient doesn't support pauses by default"); + break; + + case 'testDnsFailure': + $this->markTestSkipped("MockHttpClient doesn't use a DNS"); + break; + + case 'testGetRequest': + array_unshift($headers, 'HTTP/1.1 200 OK'); + $responses[] = new MockResponse($body, ['response_headers' => $headers]); + + $headers = [ + 'Host: localhost:8057', + 'Content-Length: 1000', + 'Content-Type: application/json', + ]; + + $responses[] = new MockResponse($body, ['response_headers' => $headers]); + break; + + case 'testDnsError': + $responses[] = $mockResponse = new MockResponse('', ['error' => 'DNS error']); + $responses[] = $mockResponse; + break; + + case 'testToStream': + case 'testBadRequestBody': + case 'testOnProgressCancel': + case 'testOnProgressError': + case 'testReentrantBufferCallback': + case 'testThrowingBufferCallback': + case 'testInfoOnCanceledResponse': + case 'testChangeResponseFactory': + $responses[] = new MockResponse($body, ['response_headers' => $headers]); + break; + + case 'testTimeoutOnAccess': + $responses[] = new MockResponse('', ['error' => 'Timeout']); + break; + + case 'testAcceptHeader': + $responses[] = new MockResponse($body, ['response_headers' => $headers]); + $responses[] = new MockResponse(str_replace('*/*', 'foo/bar', $body), ['response_headers' => $headers]); + $responses[] = new MockResponse(str_replace('"HTTP_ACCEPT": "*/*",', '', $body), ['response_headers' => $headers]); + break; + + case 'testResolve': + $responses[] = new MockResponse($body, ['response_headers' => $headers]); + $responses[] = new MockResponse($body, ['response_headers' => $headers]); + $responses[] = new MockResponse((function () { yield ''; })(), ['response_headers' => $headers]); + break; + + case 'testTimeoutOnStream': + case 'testUncheckedTimeoutThrows': + case 'testTimeoutIsNotAFatalError': + $body = ['<1>', '', '<2>']; + $responses[] = new MockResponse($body, ['response_headers' => $headers]); + break; + + case 'testInformationalResponseStream': + $client = $this->createMock(HttpClientInterface::class); + $response = new MockResponse('Here the body', ['response_headers' => [ + 'HTTP/1.1 103 ', + 'Link: ; rel=preload; as=style', + 'HTTP/1.1 200 ', + 'Date: foo', + 'Content-Length: 13', + ]]); + $client->method('request')->willReturn($response); + $client->method('stream')->willReturn(new ResponseStream((function () use ($response) { + $chunk = $this->createMock(ChunkInterface::class); + $chunk->method('getInformationalStatus') + ->willReturn([103, ['link' => ['; rel=preload; as=style', '; rel=preload; as=script']]]); + + yield $response => $chunk; + + $chunk = $this->createMock(ChunkInterface::class); + $chunk->method('isFirst')->willReturn(true); + + yield $response => $chunk; + + $chunk = $this->createMock(ChunkInterface::class); + $chunk->method('getContent')->willReturn('Here the body'); + + yield $response => $chunk; + + $chunk = $this->createMock(ChunkInterface::class); + $chunk->method('isLast')->willReturn(true); + + yield $response => $chunk; + })())); + + return $client; + + case 'testNonBlockingStream': + case 'testSeekAsyncStream': + $responses[] = new MockResponse((function () { yield '<1>'; yield ''; yield '<2>'; })(), ['response_headers' => $headers]); + break; + + case 'testMaxDuration': + $responses[] = new MockResponse('', ['error' => 'Max duration was reached.']); + break; + } + + return new MockHttpClient($responses); + } + + public function testHttp2PushVulcain() + { + $this->markTestSkipped('MockHttpClient doesn\'t support HTTP/2 PUSH.'); + } + + public function testHttp2PushVulcainWithUnusedResponse() + { + $this->markTestSkipped('MockHttpClient doesn\'t support HTTP/2 PUSH.'); + } + + public function testChangeResponseFactory() + { + /* @var MockHttpClient $client */ + $client = $this->getHttpClient(__METHOD__); + $expectedBody = '{"foo": "bar"}'; + $client->setResponseFactory(new MockResponse($expectedBody)); + + $response = $client->request('GET', 'http://localhost:8057'); + + $this->assertSame($expectedBody, $response->getContent()); + } + + public function testStringableBodyParam() + { + $client = new MockHttpClient(); + + $param = new class() { + public function __toString() + { + return 'bar'; + } + }; + + $response = $client->request('GET', 'https://example.com', [ + 'body' => ['foo' => $param], + ]); + + $this->assertSame('foo=bar', $response->getRequestOptions()['body']); + } + + public function testResetsRequestCount() + { + $client = new MockHttpClient([new MockResponse()]); + $this->assertSame(0, $client->getRequestsCount()); + + $client->request('POST', '/url', ['body' => 'payload']); + + $this->assertSame(1, $client->getRequestsCount()); + $client->reset(); + $this->assertSame(0, $client->getRequestsCount()); + } +} diff --git a/sites/all/libraries/vendor/symfony/http-client/Tests/NativeHttpClientTest.php b/sites/all/libraries/vendor/symfony/http-client/Tests/NativeHttpClientTest.php new file mode 100644 index 0000000000..3250b50137 --- /dev/null +++ b/sites/all/libraries/vendor/symfony/http-client/Tests/NativeHttpClientTest.php @@ -0,0 +1,48 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient\Tests; + +use Symfony\Component\HttpClient\NativeHttpClient; +use Symfony\Contracts\HttpClient\HttpClientInterface; + +class NativeHttpClientTest extends HttpClientTestCase +{ + protected function getHttpClient(string $testCase): HttpClientInterface + { + return new NativeHttpClient(); + } + + public function testInformationalResponseStream() + { + $this->markTestSkipped('NativeHttpClient doesn\'t support informational status codes.'); + } + + public function testTimeoutOnInitialize() + { + $this->markTestSkipped('NativeHttpClient doesn\'t support opening concurrent requests.'); + } + + public function testTimeoutOnDestruct() + { + $this->markTestSkipped('NativeHttpClient doesn\'t support opening concurrent requests.'); + } + + public function testHttp2PushVulcain() + { + $this->markTestSkipped('NativeHttpClient doesn\'t support HTTP/2.'); + } + + public function testHttp2PushVulcainWithUnusedResponse() + { + $this->markTestSkipped('NativeHttpClient doesn\'t support HTTP/2.'); + } +} diff --git a/sites/all/libraries/vendor/symfony/http-client/Tests/NoPrivateNetworkHttpClientTest.php b/sites/all/libraries/vendor/symfony/http-client/Tests/NoPrivateNetworkHttpClientTest.php new file mode 100755 index 0000000000..aabfe38c01 --- /dev/null +++ b/sites/all/libraries/vendor/symfony/http-client/Tests/NoPrivateNetworkHttpClientTest.php @@ -0,0 +1,164 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient\Tests; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpClient\Exception\InvalidArgumentException; +use Symfony\Component\HttpClient\Exception\TransportException; +use Symfony\Component\HttpClient\MockHttpClient; +use Symfony\Component\HttpClient\NoPrivateNetworkHttpClient; +use Symfony\Component\HttpClient\Response\MockResponse; +use Symfony\Contracts\HttpClient\HttpClientInterface; +use Symfony\Contracts\HttpClient\ResponseInterface; + +class NoPrivateNetworkHttpClientTest extends TestCase +{ + public function getExcludeData(): array + { + return [ + // private + ['0.0.0.1', null, true], + ['169.254.0.1', null, true], + ['127.0.0.1', null, true], + ['240.0.0.1', null, true], + ['10.0.0.1', null, true], + ['172.16.0.1', null, true], + ['192.168.0.1', null, true], + ['::1', null, true], + ['::ffff:0:1', null, true], + ['fe80::1', null, true], + ['fc00::1', null, true], + ['fd00::1', null, true], + ['10.0.0.1', '10.0.0.0/24', true], + ['10.0.0.1', '10.0.0.1', true], + ['fc00::1', 'fc00::1/120', true], + ['fc00::1', 'fc00::1', true], + + ['172.16.0.1', ['10.0.0.0/8', '192.168.0.0/16'], false], + ['fc00::1', ['fe80::/10', '::ffff:0:0/96'], false], + + // public + ['104.26.14.6', null, false], + ['104.26.14.6', '104.26.14.0/24', true], + ['2606:4700:20::681a:e06', null, false], + ['2606:4700:20::681a:e06', '2606:4700:20::/43', true], + + // no ipv4/ipv6 at all + ['2606:4700:20::681a:e06', '::/0', true], + ['104.26.14.6', '0.0.0.0/0', true], + + // weird scenarios (e.g.: when trying to match ipv4 address on ipv6 subnet) + ['10.0.0.1', 'fc00::/7', false], + ['fc00::1', '10.0.0.0/8', false], + ]; + } + + /** + * @dataProvider getExcludeData + */ + public function testExclude(string $ipAddr, $subnets, bool $mustThrow) + { + $content = 'foo'; + $url = sprintf('http://%s/', 0 < substr_count($ipAddr, ':') ? sprintf('[%s]', $ipAddr) : $ipAddr); + + if ($mustThrow) { + $this->expectException(TransportException::class); + $this->expectExceptionMessage(sprintf('IP "%s" is blocked for "%s".', $ipAddr, $url)); + } + + $previousHttpClient = $this->getHttpClientMock($url, $ipAddr, $content); + $client = new NoPrivateNetworkHttpClient($previousHttpClient, $subnets); + $response = $client->request('GET', $url); + + if (!$mustThrow) { + $this->assertEquals($content, $response->getContent()); + $this->assertEquals(200, $response->getStatusCode()); + } + } + + public function testCustomOnProgressCallback() + { + $ipAddr = '104.26.14.6'; + $url = sprintf('http://%s/', $ipAddr); + $content = 'foo'; + + $executionCount = 0; + $customCallback = function (int $dlNow, int $dlSize, array $info) use (&$executionCount): void { + ++$executionCount; + }; + + $previousHttpClient = $this->getHttpClientMock($url, $ipAddr, $content); + $client = new NoPrivateNetworkHttpClient($previousHttpClient); + $response = $client->request('GET', $url, ['on_progress' => $customCallback]); + + $this->assertEquals(1, $executionCount); + $this->assertEquals($content, $response->getContent()); + $this->assertEquals(200, $response->getStatusCode()); + } + + public function testNonCallableOnProgressCallback() + { + $ipAddr = '104.26.14.6'; + $url = sprintf('http://%s/', $ipAddr); + $content = 'bar'; + $customCallback = sprintf('cb_%s', microtime(true)); + + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('Option "on_progress" must be callable, "string" given.'); + + $client = new NoPrivateNetworkHttpClient(new MockHttpClient()); + $client->request('GET', $url, ['on_progress' => $customCallback]); + } + + public function testConstructor() + { + $this->expectException(\TypeError::class); + $this->expectExceptionMessage('Argument 2 passed to "Symfony\Component\HttpClient\NoPrivateNetworkHttpClient::__construct()" must be of the type array, string or null. "int" given.'); + + new NoPrivateNetworkHttpClient(new MockHttpClient(), 3); + } + + private function getHttpClientMock(string $url, string $ipAddr, string $content) + { + $previousHttpClient = $this + ->getMockBuilder(HttpClientInterface::class) + ->getMock(); + + $previousHttpClient + ->expects($this->once()) + ->method('request') + ->with( + 'GET', + $url, + $this->callback(function ($options) { + $this->assertArrayHasKey('on_progress', $options); + $onProgress = $options['on_progress']; + $this->assertIsCallable($onProgress); + + return true; + }) + ) + ->willReturnCallback(function ($method, $url, $options) use ($ipAddr, $content): ResponseInterface { + $info = [ + 'primary_ip' => $ipAddr, + 'url' => $url, + ]; + + $onProgress = $options['on_progress']; + $onProgress(0, 0, $info); + + return MockResponse::fromRequest($method, $url, [], new MockResponse($content)); + }); + + return $previousHttpClient; + } +} diff --git a/sites/all/libraries/vendor/symfony/http-client/Tests/Psr18ClientTest.php b/sites/all/libraries/vendor/symfony/http-client/Tests/Psr18ClientTest.php new file mode 100644 index 0000000000..366d555ae0 --- /dev/null +++ b/sites/all/libraries/vendor/symfony/http-client/Tests/Psr18ClientTest.php @@ -0,0 +1,104 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient\Tests; + +use Nyholm\Psr7\Factory\Psr17Factory; +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpClient\MockHttpClient; +use Symfony\Component\HttpClient\NativeHttpClient; +use Symfony\Component\HttpClient\Psr18Client; +use Symfony\Component\HttpClient\Psr18NetworkException; +use Symfony\Component\HttpClient\Psr18RequestException; +use Symfony\Component\HttpClient\Response\MockResponse; +use Symfony\Contracts\HttpClient\Test\TestHttpServer; + +class Psr18ClientTest extends TestCase +{ + public static function setUpBeforeClass(): void + { + TestHttpServer::start(); + } + + public function testSendRequest() + { + $factory = new Psr17Factory(); + $client = new Psr18Client(new NativeHttpClient(), $factory, $factory); + + $response = $client->sendRequest($factory->createRequest('GET', 'http://localhost:8057')); + + $this->assertSame(200, $response->getStatusCode()); + $this->assertSame('application/json', $response->getHeaderLine('content-type')); + + $body = json_decode((string) $response->getBody(), true); + + $this->assertSame('HTTP/1.1', $body['SERVER_PROTOCOL']); + } + + public function testPostRequest() + { + $factory = new Psr17Factory(); + $client = new Psr18Client(new NativeHttpClient(), $factory, $factory); + + $request = $factory->createRequest('POST', 'http://localhost:8057/post') + ->withBody($factory->createStream('foo=0123456789')); + + $response = $client->sendRequest($request); + $body = json_decode((string) $response->getBody(), true); + + $this->assertSame(['foo' => '0123456789', 'REQUEST_METHOD' => 'POST'], $body); + } + + public function testNetworkException() + { + $factory = new Psr17Factory(); + $client = new Psr18Client(new NativeHttpClient(), $factory, $factory); + + $this->expectException(Psr18NetworkException::class); + $client->sendRequest($factory->createRequest('GET', 'http://localhost:8058')); + } + + public function testRequestException() + { + $factory = new Psr17Factory(); + $client = new Psr18Client(new NativeHttpClient(), $factory, $factory); + + $this->expectException(Psr18RequestException::class); + $client->sendRequest($factory->createRequest('BAD.METHOD', 'http://localhost:8057')); + } + + public function test404() + { + $factory = new Psr17Factory(); + $client = new Psr18Client(new NativeHttpClient()); + + $response = $client->sendRequest($factory->createRequest('GET', 'http://localhost:8057/404')); + $this->assertSame(404, $response->getStatusCode()); + } + + public function testInvalidHeaderResponse() + { + $responseHeaders = [ + // space in header name not allowed in RFC 7230 + ' X-XSS-Protection' => '0', + 'Cache-Control' => 'no-cache', + ]; + $response = new MockResponse('body', ['response_headers' => $responseHeaders]); + $this->assertArrayHasKey(' x-xss-protection', $response->getHeaders()); + + $client = new Psr18Client(new MockHttpClient($response)); + $request = $client->createRequest('POST', 'http://localhost:8057/post') + ->withBody($client->createStream('foo=0123456789')); + + $resultResponse = $client->sendRequest($request); + $this->assertCount(1, $resultResponse->getHeaders()); + } +} diff --git a/sites/all/libraries/vendor/symfony/http-client/Tests/Response/HttplugPromiseTest.php b/sites/all/libraries/vendor/symfony/http-client/Tests/Response/HttplugPromiseTest.php new file mode 100644 index 0000000000..d781d4925b --- /dev/null +++ b/sites/all/libraries/vendor/symfony/http-client/Tests/Response/HttplugPromiseTest.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient\Tests\Response; + +use GuzzleHttp\Promise\Promise; +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpClient\Response\HttplugPromise; + +class HttplugPromiseTest extends TestCase +{ + public function testComplexNesting() + { + $mkPromise = function ($result): HttplugPromise { + $guzzlePromise = new Promise(function () use (&$guzzlePromise, $result) { + $guzzlePromise->resolve($result); + }); + + return new HttplugPromise($guzzlePromise); + }; + + $promise1 = $mkPromise('result'); + $promise2 = $promise1->then($mkPromise); + $promise3 = $promise2->then(function ($result) { return $result; }); + + $this->assertSame('result', $promise3->wait()); + } +} diff --git a/sites/all/libraries/vendor/symfony/http-client/Tests/Response/MockResponseTest.php b/sites/all/libraries/vendor/symfony/http-client/Tests/Response/MockResponseTest.php new file mode 100644 index 0000000000..d6839fbcfe --- /dev/null +++ b/sites/all/libraries/vendor/symfony/http-client/Tests/Response/MockResponseTest.php @@ -0,0 +1,119 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient\Tests\Response; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpClient\Exception\JsonException; +use Symfony\Component\HttpClient\Exception\TransportException; +use Symfony\Component\HttpClient\Response\MockResponse; + +/** + * Test methods from Symfony\Component\HttpClient\Response\*ResponseTrait. + */ +class MockResponseTest extends TestCase +{ + public function testTotalTimeShouldBeSimulatedWhenNotProvided() + { + $response = new MockResponse('body'); + $response = MockResponse::fromRequest('GET', 'https://example.com/file.txt', [], $response); + + $this->assertNotNull($response->getInfo('total_time')); + $this->assertGreaterThan(0.0, $response->getInfo('total_time')); + } + + public function testTotalTimeShouldNotBeSimulatedWhenProvided() + { + $totalTime = 4.2; + $response = new MockResponse('body', ['total_time' => $totalTime]); + $response = MockResponse::fromRequest('GET', 'https://example.com/file.txt', [], $response); + + $this->assertEquals($totalTime, $response->getInfo('total_time')); + } + + public function testToArray() + { + $data = ['color' => 'orange', 'size' => 42]; + $response = new MockResponse(json_encode($data)); + $response = MockResponse::fromRequest('GET', 'https://example.com/file.json', [], $response); + + $this->assertSame($data, $response->toArray()); + } + + /** + * @dataProvider toArrayErrors + */ + public function testToArrayError($content, $responseHeaders, $message) + { + $this->expectException(JsonException::class); + $this->expectExceptionMessage($message); + + $response = new MockResponse($content, ['response_headers' => $responseHeaders]); + $response = MockResponse::fromRequest('GET', 'https://example.com/file.json', [], $response); + $response->toArray(); + } + + public function testUrlHttpMethodMockResponse() + { + $responseMock = new MockResponse(json_encode(['foo' => 'bar'])); + $url = 'https://example.com/some-endpoint'; + $response = MockResponse::fromRequest('GET', $url, [], $responseMock); + + $this->assertSame('GET', $response->getInfo('http_method')); + $this->assertSame('GET', $responseMock->getRequestMethod()); + + $this->assertSame($url, $response->getInfo('url')); + $this->assertSame($url, $responseMock->getRequestUrl()); + } + + public function toArrayErrors() + { + yield [ + 'content' => '', + 'responseHeaders' => [], + 'message' => 'Response body is empty.', + ]; + + yield [ + 'content' => 'not json', + 'responseHeaders' => [], + 'message' => 'Syntax error for "https://example.com/file.json".', + ]; + + yield [ + 'content' => '[1,2}', + 'responseHeaders' => [], + 'message' => 'State mismatch (invalid or malformed JSON) for "https://example.com/file.json".', + ]; + + yield [ + 'content' => '"not an array"', + 'responseHeaders' => [], + 'message' => 'JSON content was expected to decode to an array, "string" returned for "https://example.com/file.json".', + ]; + + yield [ + 'content' => '8', + 'responseHeaders' => [], + 'message' => 'JSON content was expected to decode to an array, "int" returned for "https://example.com/file.json".', + ]; + } + + public function testErrorIsTakenIntoAccountInInitialization() + { + $this->expectException(TransportException::class); + $this->expectExceptionMessage('ccc error'); + + MockResponse::fromRequest('GET', 'https://symfony.com', [], new MockResponse('', [ + 'error' => 'ccc error', + ]))->getStatusCode(); + } +} diff --git a/sites/all/libraries/vendor/symfony/http-client/Tests/Retry/GenericRetryStrategyTest.php b/sites/all/libraries/vendor/symfony/http-client/Tests/Retry/GenericRetryStrategyTest.php new file mode 100644 index 0000000000..98b6578f0b --- /dev/null +++ b/sites/all/libraries/vendor/symfony/http-client/Tests/Retry/GenericRetryStrategyTest.php @@ -0,0 +1,121 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient\Tests\Retry; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpClient\Exception\TransportException; +use Symfony\Component\HttpClient\MockHttpClient; +use Symfony\Component\HttpClient\Response\AsyncContext; +use Symfony\Component\HttpClient\Response\MockResponse; +use Symfony\Component\HttpClient\Retry\GenericRetryStrategy; +use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface; + +class GenericRetryStrategyTest extends TestCase +{ + /** + * @dataProvider provideRetryable + */ + public function testShouldRetry(string $method, int $code, ?TransportExceptionInterface $exception) + { + $strategy = new GenericRetryStrategy(); + + self::assertTrue($strategy->shouldRetry($this->getContext(0, $method, 'http://example.com/', $code), null, $exception)); + } + + /** + * @dataProvider provideNotRetryable + */ + public function testShouldNotRetry(string $method, int $code, ?TransportExceptionInterface $exception) + { + $strategy = new GenericRetryStrategy(); + + self::assertFalse($strategy->shouldRetry($this->getContext(0, $method, 'http://example.com/', $code), null, $exception)); + } + + public function provideRetryable(): iterable + { + yield ['GET', 200, new TransportException()]; + yield ['GET', 500, null]; + yield ['POST', 429, null]; + } + + public function provideNotRetryable(): iterable + { + yield ['POST', 200, null]; + yield ['POST', 200, new TransportException()]; + yield ['POST', 500, null]; + } + + /** + * @dataProvider provideDelay + */ + public function testGetDelay(int $delay, int $multiplier, int $maxDelay, int $previousRetries, int $expectedDelay) + { + $strategy = new GenericRetryStrategy([], $delay, $multiplier, $maxDelay, 0); + + self::assertSame($expectedDelay, $strategy->getDelay($this->getContext($previousRetries, 'GET', 'http://example.com/', 200), null, null)); + } + + public function provideDelay(): iterable + { + // delay, multiplier, maxDelay, retries, expectedDelay + yield [1000, 1, 5000, 0, 1000]; + yield [1000, 1, 5000, 1, 1000]; + yield [1000, 1, 5000, 2, 1000]; + + yield [1000, 2, 10000, 0, 1000]; + yield [1000, 2, 10000, 1, 2000]; + yield [1000, 2, 10000, 2, 4000]; + yield [1000, 2, 10000, 3, 8000]; + yield [1000, 2, 10000, 4, 10000]; // max hit + yield [1000, 2, 0, 4, 16000]; // no max + + yield [1000, 3, 10000, 0, 1000]; + yield [1000, 3, 10000, 1, 3000]; + yield [1000, 3, 10000, 2, 9000]; + + yield [1000, 1, 500, 0, 500]; // max hit immediately + + // never a delay + yield [0, 2, 10000, 0, 0]; + yield [0, 2, 10000, 1, 0]; + } + + public function testJitter() + { + $strategy = new GenericRetryStrategy([], 1000, 1, 0, 1); + $min = 2000; + $max = 0; + for ($i = 0; $i < 50; ++$i) { + $delay = $strategy->getDelay($this->getContext(0, 'GET', 'http://example.com/', 200), null, null); + $min = min($min, $delay); + $max = max($max, $delay); + } + $this->assertGreaterThanOrEqual(1000, $max - $min); + $this->assertGreaterThanOrEqual(1000, $max); + $this->assertLessThanOrEqual(1000, $min); + } + + private function getContext($retryCount, $method, $url, $statusCode): AsyncContext + { + $passthru = null; + $info = [ + 'retry_count' => $retryCount, + 'http_method' => $method, + 'url' => $url, + 'http_code' => $statusCode, + ]; + $response = new MockResponse('', $info); + + return new AsyncContext($passthru, new MockHttpClient(), $response, $info, null, 0); + } +} diff --git a/sites/all/libraries/vendor/symfony/http-client/Tests/RetryableHttpClientTest.php b/sites/all/libraries/vendor/symfony/http-client/Tests/RetryableHttpClientTest.php new file mode 100644 index 0000000000..cf2af1560c --- /dev/null +++ b/sites/all/libraries/vendor/symfony/http-client/Tests/RetryableHttpClientTest.php @@ -0,0 +1,247 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient\Tests; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpClient\Exception\ServerException; +use Symfony\Component\HttpClient\HttpClient; +use Symfony\Component\HttpClient\MockHttpClient; +use Symfony\Component\HttpClient\NativeHttpClient; +use Symfony\Component\HttpClient\Response\AsyncContext; +use Symfony\Component\HttpClient\Response\MockResponse; +use Symfony\Component\HttpClient\Retry\GenericRetryStrategy; +use Symfony\Component\HttpClient\RetryableHttpClient; +use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface; + +class RetryableHttpClientTest extends TestCase +{ + public function testRetryOnError() + { + $client = new RetryableHttpClient( + new MockHttpClient([ + new MockResponse('', ['http_code' => 500]), + new MockResponse('', ['http_code' => 200]), + ]), + new GenericRetryStrategy([500], 0), + 1 + ); + + $response = $client->request('GET', 'http://example.com/foo-bar'); + + self::assertSame(200, $response->getStatusCode()); + } + + public function testRetryRespectStrategy() + { + $client = new RetryableHttpClient( + new MockHttpClient([ + new MockResponse('', ['http_code' => 500]), + new MockResponse('', ['http_code' => 500]), + new MockResponse('', ['http_code' => 200]), + ]), + new GenericRetryStrategy([500], 0), + 1 + ); + + $response = $client->request('GET', 'http://example.com/foo-bar'); + + $this->expectException(ServerException::class); + $response->getHeaders(); + } + + public function testRetryWithBody() + { + $client = new RetryableHttpClient( + new MockHttpClient([ + new MockResponse('abc', ['http_code' => 500]), + new MockResponse('def', ['http_code' => 200]), + ]), + new class(GenericRetryStrategy::DEFAULT_RETRY_STATUS_CODES, 0) extends GenericRetryStrategy { + public function shouldRetry(AsyncContext $context, ?string $responseContent, ?TransportExceptionInterface $exception): ?bool + { + return 500 === $context->getStatusCode() && null === $responseContent ? null : 200 !== $context->getStatusCode(); + } + }, + 2 + ); + + $response = $client->request('GET', 'http://example.com/foo-bar'); + + self::assertSame(200, $response->getStatusCode()); + self::assertSame('def', $response->getContent()); + } + + public function testRetryWithBodyKeepContent() + { + $client = new RetryableHttpClient( + new MockHttpClient([ + new MockResponse('my bad', ['http_code' => 400]), + ]), + new class([400], 0) extends GenericRetryStrategy { + public function shouldRetry(AsyncContext $context, ?string $responseContent, ?TransportExceptionInterface $exception): ?bool + { + if (null === $responseContent) { + return null; + } + + return 'my bad' !== $responseContent; + } + }, + 1 + ); + + $response = $client->request('GET', 'http://example.com/foo-bar'); + + self::assertSame(400, $response->getStatusCode()); + self::assertSame('my bad', $response->getContent(false)); + } + + public function testRetryWithBodyInvalid() + { + $client = new RetryableHttpClient( + new MockHttpClient([ + new MockResponse('', ['http_code' => 500]), + new MockResponse('', ['http_code' => 200]), + ]), + new class(GenericRetryStrategy::DEFAULT_RETRY_STATUS_CODES, 0) extends GenericRetryStrategy { + public function shouldRetry(AsyncContext $context, ?string $responseContent, ?TransportExceptionInterface $exception): ?bool + { + return null; + } + }, + 1 + ); + + $response = $client->request('GET', 'http://example.com/foo-bar'); + + $this->expectExceptionMessageMatches('/must not return null when called with a body/'); + $response->getHeaders(); + } + + public function testStreamNoRetry() + { + $client = new RetryableHttpClient( + new MockHttpClient([ + new MockResponse('', ['http_code' => 500]), + ]), + new GenericRetryStrategy([500], 0), + 0 + ); + + $response = $client->request('GET', 'http://example.com/foo-bar'); + + foreach ($client->stream($response) as $chunk) { + if ($chunk->isFirst()) { + self::assertSame(500, $response->getStatusCode()); + } + } + } + + public function testRetryWithDnsIssue() + { + $client = new RetryableHttpClient( + new NativeHttpClient(), + new class(GenericRetryStrategy::DEFAULT_RETRY_STATUS_CODES, 0) extends GenericRetryStrategy { + public function shouldRetry(AsyncContext $context, ?string $responseContent, ?TransportExceptionInterface $exception): ?bool + { + $this->fail('should not be called'); + } + }, + 2, + $logger = new TestLogger() + ); + + $response = $client->request('GET', 'http://does.not.exists/foo-bar'); + + try { + $response->getHeaders(); + } catch (TransportExceptionInterface $e) { + $this->assertSame('Could not resolve host "does.not.exists".', $e->getMessage()); + } + $this->assertCount(2, $logger->logs); + $this->assertSame('Try #{count} after {delay}ms: Could not resolve host "does.not.exists".', $logger->logs[0]); + } + + public function testCancelOnTimeout() + { + $client = HttpClient::create(); + + if ($client instanceof NativeHttpClient) { + $this->markTestSkipped('NativeHttpClient cannot timeout before receiving headers'); + } + + $client = new RetryableHttpClient($client); + + $response = $client->request('GET', 'https://example.com/'); + + foreach ($client->stream($response, 0) as $chunk) { + $this->assertTrue($chunk->isTimeout()); + $response->cancel(); + } + } + + public function testRetryWithDelay() + { + $retryAfter = '0.46'; + + $client = new RetryableHttpClient( + new MockHttpClient([ + new MockResponse('', [ + 'http_code' => 503, + 'response_headers' => [ + 'retry-after' => $retryAfter, + ], + ]), + new MockResponse('', [ + 'http_code' => 200, + ]), + ]), + new GenericRetryStrategy(), + 1, + $logger = new class() extends TestLogger { + public $context = []; + + public function log($level, $message, array $context = []): void + { + $this->context = $context; + parent::log($level, $message, $context); + } + } + ); + + $client->request('GET', 'http://example.com/foo-bar')->getContent(); + + $delay = $logger->context['delay'] ?? null; + + $this->assertArrayHasKey('delay', $logger->context); + $this->assertNotNull($delay); + $this->assertSame((int) ($retryAfter * 1000), $delay); + } + + public function testRetryOnErrorAssertContent() + { + $client = new RetryableHttpClient( + new MockHttpClient([ + new MockResponse('', ['http_code' => 500]), + new MockResponse('Test out content', ['http_code' => 200]), + ]), + new GenericRetryStrategy([500], 0), + 1 + ); + + $response = $client->request('GET', 'http://example.com/foo-bar'); + + self::assertSame(200, $response->getStatusCode()); + self::assertSame('Test out content', $response->getContent()); + self::assertSame('Test out content', $response->getContent(), 'Content should be buffered'); + } +} diff --git a/sites/all/libraries/vendor/symfony/http-client/Tests/ScopingHttpClientTest.php b/sites/all/libraries/vendor/symfony/http-client/Tests/ScopingHttpClientTest.php new file mode 100644 index 0000000000..078475bf10 --- /dev/null +++ b/sites/all/libraries/vendor/symfony/http-client/Tests/ScopingHttpClientTest.php @@ -0,0 +1,103 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient\Tests; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpClient\Exception\InvalidArgumentException; +use Symfony\Component\HttpClient\MockHttpClient; +use Symfony\Component\HttpClient\ScopingHttpClient; + +class ScopingHttpClientTest extends TestCase +{ + public function testRelativeUrl() + { + $mockClient = new MockHttpClient(); + $client = new ScopingHttpClient($mockClient, []); + + $this->expectException(InvalidArgumentException::class); + $client->request('GET', '/foo'); + } + + public function testRelativeUrlWithDefaultRegexp() + { + $mockClient = new MockHttpClient(); + $client = new ScopingHttpClient($mockClient, ['.*' => ['base_uri' => 'http://example.com', 'query' => ['a' => 'b']]], '.*'); + + $this->assertSame('http://example.com/foo?f=g&a=b', $client->request('GET', '/foo?f=g')->getInfo('url')); + } + + /** + * @dataProvider provideMatchingUrls + */ + public function testMatchingUrls(string $regexp, string $url, array $options) + { + $mockClient = new MockHttpClient(); + $client = new ScopingHttpClient($mockClient, $options); + + $response = $client->request('GET', $url); + $requestedOptions = $response->getRequestOptions(); + + $this->assertSame($options[$regexp]['case'], $requestedOptions['case']); + } + + public function provideMatchingUrls() + { + $defaultOptions = [ + '.*/foo-bar' => ['case' => 1], + '.*' => ['case' => 2], + ]; + + yield ['regexp' => '.*/foo-bar', 'url' => 'http://example.com/foo-bar', 'default_options' => $defaultOptions]; + yield ['regexp' => '.*', 'url' => 'http://example.com/bar-foo', 'default_options' => $defaultOptions]; + yield ['regexp' => '.*', 'url' => 'http://example.com/foobar', 'default_options' => $defaultOptions]; + } + + public function testMatchingUrlsAndOptions() + { + $defaultOptions = [ + '.*/foo-bar' => ['headers' => ['X-FooBar' => 'unit-test-foo-bar']], + '.*' => ['headers' => ['Content-Type' => 'text/html']], + ]; + + $mockClient = new MockHttpClient(); + $client = new ScopingHttpClient($mockClient, $defaultOptions); + + $response = $client->request('GET', 'http://example.com/foo-bar', ['json' => ['url' => 'http://example.com']]); + $requestOptions = $response->getRequestOptions(); + $this->assertSame('Content-Type: application/json', $requestOptions['headers'][1]); + $requestJson = json_decode($requestOptions['body'], true); + $this->assertSame('http://example.com', $requestJson['url']); + $this->assertSame('X-FooBar: '.$defaultOptions['.*/foo-bar']['headers']['X-FooBar'], $requestOptions['headers'][0]); + + $response = $client->request('GET', 'http://example.com/bar-foo', ['headers' => ['X-FooBar' => 'unit-test']]); + $requestOptions = $response->getRequestOptions(); + $this->assertSame('X-FooBar: unit-test', $requestOptions['headers'][0]); + $this->assertSame('Content-Type: text/html', $requestOptions['headers'][1]); + + $response = $client->request('GET', 'http://example.com/foobar-foo', ['headers' => ['X-FooBar' => 'unit-test']]); + $requestOptions = $response->getRequestOptions(); + $this->assertSame('X-FooBar: unit-test', $requestOptions['headers'][0]); + $this->assertSame('Content-Type: text/html', $requestOptions['headers'][1]); + } + + public function testForBaseUri() + { + $client = ScopingHttpClient::forBaseUri(new MockHttpClient(null, null), 'http://example.com/foo'); + + $response = $client->request('GET', '/bar'); + $this->assertSame('http://example.com/foo', implode('', $response->getRequestOptions()['base_uri'])); + $this->assertSame('http://example.com/bar', $response->getInfo('url')); + + $response = $client->request('GET', 'http://foo.bar/'); + $this->assertNull($response->getRequestOptions()['base_uri']); + } +} diff --git a/sites/all/libraries/vendor/symfony/http-client/Tests/TestLogger.php b/sites/all/libraries/vendor/symfony/http-client/Tests/TestLogger.php new file mode 100644 index 0000000000..83aa096889 --- /dev/null +++ b/sites/all/libraries/vendor/symfony/http-client/Tests/TestLogger.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient\Tests; + +use Psr\Log\AbstractLogger; + +class TestLogger extends AbstractLogger +{ + public $logs = []; + + public function log($level, $message, array $context = []): void + { + $this->logs[] = $message; + } +} diff --git a/sites/all/libraries/vendor/symfony/http-client/Tests/TraceableHttpClientTest.php b/sites/all/libraries/vendor/symfony/http-client/Tests/TraceableHttpClientTest.php new file mode 100755 index 0000000000..5f20e1989d --- /dev/null +++ b/sites/all/libraries/vendor/symfony/http-client/Tests/TraceableHttpClientTest.php @@ -0,0 +1,235 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient\Tests; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpClient\Exception\ClientException; +use Symfony\Component\HttpClient\MockHttpClient; +use Symfony\Component\HttpClient\NativeHttpClient; +use Symfony\Component\HttpClient\Response\MockResponse; +use Symfony\Component\HttpClient\TraceableHttpClient; +use Symfony\Component\Stopwatch\Stopwatch; +use Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface; +use Symfony\Contracts\HttpClient\HttpClientInterface; +use Symfony\Contracts\HttpClient\Test\TestHttpServer; + +class TraceableHttpClientTest extends TestCase +{ + public static function setUpBeforeClass(): void + { + TestHttpServer::start(); + } + + public function testItTracesRequest() + { + $httpClient = $this->createMock(HttpClientInterface::class); + $httpClient + ->expects($this->any()) + ->method('request') + ->with( + 'GET', + '/foo/bar', + $this->callback(function ($subject) { + $onprogress = $subject['on_progress']; + unset($subject['on_progress'], $subject['extra']); + $this->assertEquals(['options1' => 'foo'], $subject); + + return true; + }) + ) + ->willReturn(MockResponse::fromRequest('GET', '/foo/bar', ['options1' => 'foo'], new MockResponse('hello'))) + ; + + $sut = new TraceableHttpClient($httpClient); + + $sut->request('GET', '/foo/bar', ['options1' => 'foo'])->getContent(); + + $this->assertCount(1, $tracedRequests = $sut->getTracedRequests()); + $actualTracedRequest = $tracedRequests[0]; + $this->assertEquals([ + 'method' => 'GET', + 'url' => '/foo/bar', + 'options' => ['options1' => 'foo'], + 'info' => [], + 'content' => 'hello', + ], $actualTracedRequest); + + $sut->request('GET', '/foo/bar', ['options1' => 'foo', 'extra' => ['trace_content' => false]])->getContent(); + + $this->assertCount(2, $tracedRequests = $sut->getTracedRequests()); + $actualTracedRequest = $tracedRequests[1]; + $this->assertEquals([ + 'method' => 'GET', + 'url' => '/foo/bar', + 'options' => ['options1' => 'foo', 'extra' => ['trace_content' => false]], + 'info' => [], + 'content' => null, + ], $actualTracedRequest); + } + + public function testItCollectsInfoOnRealRequest() + { + $sut = new TraceableHttpClient(new MockHttpClient()); + $sut->request('GET', 'http://localhost:8057'); + $this->assertCount(1, $tracedRequests = $sut->getTracedRequests()); + $actualTracedRequest = $tracedRequests[0]; + $this->assertSame('GET', $actualTracedRequest['info']['http_method']); + $this->assertSame('http://localhost:8057/', $actualTracedRequest['info']['url']); + } + + public function testItExecutesOnProgressOption() + { + $sut = new TraceableHttpClient(new MockHttpClient()); + $foo = 0; + $sut->request('GET', 'http://localhost:8057', ['on_progress' => function (int $dlNow, int $dlSize, array $info) use (&$foo) { + ++$foo; + }]); + $this->assertCount(1, $tracedRequests = $sut->getTracedRequests()); + $actualTracedRequest = $tracedRequests[0]; + $this->assertGreaterThan(0, $foo); + } + + public function testItResetsTraces() + { + $sut = new TraceableHttpClient(new MockHttpClient()); + $sut->request('GET', 'https://example.com/foo/bar'); + $sut->reset(); + $this->assertCount(0, $sut->getTracedRequests()); + } + + public function testStream() + { + $sut = new TraceableHttpClient(new NativeHttpClient()); + $response = $sut->request('GET', 'http://localhost:8057/chunked'); + $chunks = []; + foreach ($sut->stream($response) as $r => $chunk) { + $chunks[] = $chunk->getContent(); + } + $this->assertSame($response, $r); + $this->assertGreaterThan(1, \count($chunks)); + $this->assertSame('Symfony is awesome!', implode('', $chunks)); + } + + public function testToArrayChecksStatusCodeBeforeDecoding() + { + $this->expectException(ClientExceptionInterface::class); + + $sut = new TraceableHttpClient(new MockHttpClient($responseFactory = function (): MockResponse { + return new MockResponse('Errored.', ['http_code' => 400]); + })); + + $response = $sut->request('GET', 'https://example.com/foo/bar'); + $response->toArray(); + } + + public function testStopwatch() + { + $sw = new Stopwatch(true); + $sut = new TraceableHttpClient(new NativeHttpClient(), $sw); + $response = $sut->request('GET', 'http://localhost:8057'); + + $response->getStatusCode(); + $response->getHeaders(); + $response->getContent(); + + $this->assertArrayHasKey('__root__', $sections = $sw->getSections()); + $this->assertCount(1, $events = $sections['__root__']->getEvents()); + $this->assertArrayHasKey('GET http://localhost:8057', $events); + $this->assertCount(3, $events['GET http://localhost:8057']->getPeriods()); + $this->assertGreaterThan(0.0, $events['GET http://localhost:8057']->getDuration()); + } + + public function testStopwatchError() + { + $sw = new Stopwatch(true); + $sut = new TraceableHttpClient(new NativeHttpClient(), $sw); + $response = $sut->request('GET', 'http://localhost:8057/404'); + + try { + $response->getContent(); + $this->fail('Response should have thrown an exception'); + } catch (ClientException $e) { + // no-op + } + + $this->assertArrayHasKey('__root__', $sections = $sw->getSections()); + $this->assertCount(1, $events = $sections['__root__']->getEvents()); + $this->assertArrayHasKey('GET http://localhost:8057/404', $events); + $this->assertCount(1, $events['GET http://localhost:8057/404']->getPeriods()); + } + + public function testStopwatchStream() + { + $sw = new Stopwatch(true); + $sut = new TraceableHttpClient(new NativeHttpClient(), $sw); + $response = $sut->request('GET', 'http://localhost:8057'); + + $chunkCount = 0; + foreach ($sut->stream([$response]) as $chunk) { + ++$chunkCount; + } + + $this->assertArrayHasKey('__root__', $sections = $sw->getSections()); + $this->assertCount(1, $events = $sections['__root__']->getEvents()); + $this->assertArrayHasKey('GET http://localhost:8057', $events); + $this->assertGreaterThanOrEqual($chunkCount, \count($events['GET http://localhost:8057']->getPeriods())); + } + + public function testStopwatchStreamError() + { + $sw = new Stopwatch(true); + $sut = new TraceableHttpClient(new NativeHttpClient(), $sw); + $response = $sut->request('GET', 'http://localhost:8057/404'); + + try { + $chunkCount = 0; + foreach ($sut->stream([$response]) as $chunk) { + ++$chunkCount; + } + $this->fail('Response should have thrown an exception'); + } catch (ClientException $e) { + // no-op + } + + $this->assertArrayHasKey('__root__', $sections = $sw->getSections()); + $this->assertCount(1, $events = $sections['__root__']->getEvents()); + $this->assertArrayHasKey('GET http://localhost:8057/404', $events); + $this->assertGreaterThanOrEqual($chunkCount, \count($events['GET http://localhost:8057/404']->getPeriods())); + } + + public function testStopwatchDestruct() + { + $sw = new Stopwatch(true); + $sut = new TraceableHttpClient(new NativeHttpClient(), $sw); + $sut->request('GET', 'http://localhost:8057'); + + $this->assertArrayHasKey('__root__', $sections = $sw->getSections()); + $this->assertCount(1, $events = $sections['__root__']->getEvents()); + $this->assertArrayHasKey('GET http://localhost:8057', $events); + $this->assertCount(1, $events['GET http://localhost:8057']->getPeriods()); + $this->assertGreaterThan(0.0, $events['GET http://localhost:8057']->getDuration()); + } + + public function testWithOptions() + { + $sut = new TraceableHttpClient(new NativeHttpClient()); + + $sut2 = $sut->withOptions(['base_uri' => 'http://localhost:8057']); + + $response = $sut2->request('GET', '/'); + + $this->assertSame(200, $response->getStatusCode()); + $this->assertSame('http://localhost:8057/', $response->getInfo('url')); + + $this->assertCount(1, $sut->getTracedRequests()); + } +} diff --git a/sites/all/libraries/vendor/symfony/http-client/TraceableHttpClient.php b/sites/all/libraries/vendor/symfony/http-client/TraceableHttpClient.php new file mode 100644 index 0000000000..76c9282243 --- /dev/null +++ b/sites/all/libraries/vendor/symfony/http-client/TraceableHttpClient.php @@ -0,0 +1,120 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient; + +use Psr\Log\LoggerAwareInterface; +use Psr\Log\LoggerInterface; +use Symfony\Component\HttpClient\Response\ResponseStream; +use Symfony\Component\HttpClient\Response\TraceableResponse; +use Symfony\Component\Stopwatch\Stopwatch; +use Symfony\Contracts\HttpClient\HttpClientInterface; +use Symfony\Contracts\HttpClient\ResponseInterface; +use Symfony\Contracts\HttpClient\ResponseStreamInterface; +use Symfony\Contracts\Service\ResetInterface; + +/** + * @author Jérémy Romey + */ +final class TraceableHttpClient implements HttpClientInterface, ResetInterface, LoggerAwareInterface +{ + private $client; + private $stopwatch; + private $tracedRequests; + + public function __construct(HttpClientInterface $client, Stopwatch $stopwatch = null) + { + $this->client = $client; + $this->stopwatch = $stopwatch; + $this->tracedRequests = new \ArrayObject(); + } + + /** + * {@inheritdoc} + */ + public function request(string $method, string $url, array $options = []): ResponseInterface + { + $content = null; + $traceInfo = []; + $this->tracedRequests[] = [ + 'method' => $method, + 'url' => $url, + 'options' => $options, + 'info' => &$traceInfo, + 'content' => &$content, + ]; + $onProgress = $options['on_progress'] ?? null; + + if (false === ($options['extra']['trace_content'] ?? true)) { + unset($content); + $content = false; + } + + $options['on_progress'] = function (int $dlNow, int $dlSize, array $info) use (&$traceInfo, $onProgress) { + $traceInfo = $info; + + if (null !== $onProgress) { + $onProgress($dlNow, $dlSize, $info); + } + }; + + return new TraceableResponse($this->client, $this->client->request($method, $url, $options), $content, null === $this->stopwatch ? null : $this->stopwatch->start("$method $url", 'http_client')); + } + + /** + * {@inheritdoc} + */ + public function stream($responses, float $timeout = null): ResponseStreamInterface + { + if ($responses instanceof TraceableResponse) { + $responses = [$responses]; + } elseif (!is_iterable($responses)) { + throw new \TypeError(sprintf('"%s()" expects parameter 1 to be an iterable of TraceableResponse objects, "%s" given.', __METHOD__, get_debug_type($responses))); + } + + return new ResponseStream(TraceableResponse::stream($this->client, $responses, $timeout)); + } + + public function getTracedRequests(): array + { + return $this->tracedRequests->getArrayCopy(); + } + + public function reset() + { + if ($this->client instanceof ResetInterface) { + $this->client->reset(); + } + + $this->tracedRequests->exchangeArray([]); + } + + /** + * {@inheritdoc} + */ + public function setLogger(LoggerInterface $logger): void + { + if ($this->client instanceof LoggerAwareInterface) { + $this->client->setLogger($logger); + } + } + + /** + * {@inheritdoc} + */ + public function withOptions(array $options): self + { + $clone = clone $this; + $clone->client = $this->client->withOptions($options); + + return $clone; + } +} diff --git a/sites/all/libraries/vendor/symfony/http-client/composer.json b/sites/all/libraries/vendor/symfony/http-client/composer.json new file mode 100644 index 0000000000..084c258121 --- /dev/null +++ b/sites/all/libraries/vendor/symfony/http-client/composer.json @@ -0,0 +1,53 @@ +{ + "name": "symfony/http-client", + "type": "library", + "description": "Provides powerful methods to fetch HTTP resources synchronously or asynchronously", + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "provide": { + "php-http/async-client-implementation": "*", + "php-http/client-implementation": "*", + "psr/http-client-implementation": "1.0", + "symfony/http-client-implementation": "2.4" + }, + "require": { + "php": ">=7.2.5", + "psr/log": "^1|^2|^3", + "symfony/deprecation-contracts": "^2.1|^3", + "symfony/http-client-contracts": "^2.4", + "symfony/polyfill-php73": "^1.11", + "symfony/polyfill-php80": "^1.16", + "symfony/service-contracts": "^1.0|^2|^3" + }, + "require-dev": { + "amphp/amp": "^2.5", + "amphp/http-client": "^4.2.1", + "amphp/http-tunnel": "^1.0", + "amphp/socket": "^1.1", + "guzzlehttp/promises": "^1.4", + "nyholm/psr7": "^1.0", + "php-http/httplug": "^1.0|^2.0", + "psr/http-client": "^1.0", + "symfony/dependency-injection": "^4.4|^5.0|^6.0", + "symfony/http-kernel": "^4.4.13|^5.1.5|^6.0", + "symfony/process": "^4.4|^5.0|^6.0", + "symfony/stopwatch": "^4.4|^5.0|^6.0" + }, + "autoload": { + "psr-4": { "Symfony\\Component\\HttpClient\\": "" }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "minimum-stability": "dev" +} diff --git a/sites/all/libraries/vendor/symfony/http-client/phpunit.xml.dist b/sites/all/libraries/vendor/symfony/http-client/phpunit.xml.dist new file mode 100644 index 0000000000..4a055dcf50 --- /dev/null +++ b/sites/all/libraries/vendor/symfony/http-client/phpunit.xml.dist @@ -0,0 +1,30 @@ + + + + + + + + + + ./Tests/ + + + + + + ./ + + ./Tests + ./vendor + + + + diff --git a/sites/all/libraries/vendor/symfony/polyfill-php73 b/sites/all/libraries/vendor/symfony/polyfill-php73 deleted file mode 160000 index 9e8ecb5f92..0000000000 --- a/sites/all/libraries/vendor/symfony/polyfill-php73 +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 9e8ecb5f92152187c4799efd3c96b78ccab18ff9 diff --git a/sites/all/libraries/vendor/symfony/polyfill-php73/LICENSE b/sites/all/libraries/vendor/symfony/polyfill-php73/LICENSE new file mode 100644 index 0000000000..3f853aaf35 --- /dev/null +++ b/sites/all/libraries/vendor/symfony/polyfill-php73/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2018-2019 Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/sites/all/libraries/vendor/symfony/polyfill-php73/Php73.php b/sites/all/libraries/vendor/symfony/polyfill-php73/Php73.php new file mode 100644 index 0000000000..65c35a6a11 --- /dev/null +++ b/sites/all/libraries/vendor/symfony/polyfill-php73/Php73.php @@ -0,0 +1,43 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Polyfill\Php73; + +/** + * @author Gabriel Caruso + * @author Ion Bazan + * + * @internal + */ +final class Php73 +{ + public static $startAt = 1533462603; + + /** + * @param bool $asNum + * + * @return array|float|int + */ + public static function hrtime($asNum = false) + { + $ns = microtime(false); + $s = substr($ns, 11) - self::$startAt; + $ns = 1E9 * (float) $ns; + + if ($asNum) { + $ns += $s * 1E9; + + return \PHP_INT_SIZE === 4 ? $ns : (int) $ns; + } + + return [$s, (int) $ns]; + } +} diff --git a/sites/all/libraries/vendor/symfony/polyfill-php73/README.md b/sites/all/libraries/vendor/symfony/polyfill-php73/README.md new file mode 100644 index 0000000000..032fafbda0 --- /dev/null +++ b/sites/all/libraries/vendor/symfony/polyfill-php73/README.md @@ -0,0 +1,18 @@ +Symfony Polyfill / Php73 +======================== + +This component provides functions added to PHP 7.3 core: + +- [`array_key_first`](https://php.net/array_key_first) +- [`array_key_last`](https://php.net/array_key_last) +- [`hrtime`](https://php.net/function.hrtime) +- [`is_countable`](https://php.net/is_countable) +- [`JsonException`](https://php.net/JsonException) + +More information can be found in the +[main Polyfill README](https://github.com/symfony/polyfill/blob/main/README.md). + +License +======= + +This library is released under the [MIT license](LICENSE). diff --git a/sites/all/libraries/vendor/symfony/polyfill-php73/Resources/stubs/JsonException.php b/sites/all/libraries/vendor/symfony/polyfill-php73/Resources/stubs/JsonException.php new file mode 100644 index 0000000000..f06d6c2694 --- /dev/null +++ b/sites/all/libraries/vendor/symfony/polyfill-php73/Resources/stubs/JsonException.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +if (\PHP_VERSION_ID < 70300) { + class JsonException extends Exception + { + } +} diff --git a/sites/all/libraries/vendor/symfony/polyfill-php73/bootstrap.php b/sites/all/libraries/vendor/symfony/polyfill-php73/bootstrap.php new file mode 100644 index 0000000000..d6b2153823 --- /dev/null +++ b/sites/all/libraries/vendor/symfony/polyfill-php73/bootstrap.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Polyfill\Php73 as p; + +if (\PHP_VERSION_ID >= 70300) { + return; +} + +if (!function_exists('is_countable')) { + function is_countable($value) { return is_array($value) || $value instanceof Countable || $value instanceof ResourceBundle || $value instanceof SimpleXmlElement; } +} +if (!function_exists('hrtime')) { + require_once __DIR__.'/Php73.php'; + p\Php73::$startAt = (int) microtime(true); + function hrtime($as_number = false) { return p\Php73::hrtime($as_number); } +} +if (!function_exists('array_key_first')) { + function array_key_first(array $array) { foreach ($array as $key => $value) { return $key; } } +} +if (!function_exists('array_key_last')) { + function array_key_last(array $array) { return key(array_slice($array, -1, 1, true)); } +} diff --git a/sites/all/libraries/vendor/symfony/polyfill-php73/composer.json b/sites/all/libraries/vendor/symfony/polyfill-php73/composer.json new file mode 100644 index 0000000000..b5c58ec193 --- /dev/null +++ b/sites/all/libraries/vendor/symfony/polyfill-php73/composer.json @@ -0,0 +1,36 @@ +{ + "name": "symfony/polyfill-php73", + "type": "library", + "description": "Symfony polyfill backporting some PHP 7.3+ features to lower PHP versions", + "keywords": ["polyfill", "shim", "compatibility", "portable"], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=7.1" + }, + "autoload": { + "psr-4": { "Symfony\\Polyfill\\Php73\\": "" }, + "files": [ "bootstrap.php" ], + "classmap": [ "Resources/stubs" ] + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-main": "1.27-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + } +} diff --git a/sites/all/libraries/vendor/symfony/polyfill-php80 b/sites/all/libraries/vendor/symfony/polyfill-php80 deleted file mode 160000 index 7a6ff3f195..0000000000 --- a/sites/all/libraries/vendor/symfony/polyfill-php80 +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936 diff --git a/sites/all/libraries/vendor/symfony/polyfill-php80/LICENSE b/sites/all/libraries/vendor/symfony/polyfill-php80/LICENSE new file mode 100644 index 0000000000..5593b1d84f --- /dev/null +++ b/sites/all/libraries/vendor/symfony/polyfill-php80/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2020 Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/sites/all/libraries/vendor/symfony/polyfill-php80/Php80.php b/sites/all/libraries/vendor/symfony/polyfill-php80/Php80.php new file mode 100644 index 0000000000..362dd1a959 --- /dev/null +++ b/sites/all/libraries/vendor/symfony/polyfill-php80/Php80.php @@ -0,0 +1,115 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Polyfill\Php80; + +/** + * @author Ion Bazan + * @author Nico Oelgart + * @author Nicolas Grekas + * + * @internal + */ +final class Php80 +{ + public static function fdiv(float $dividend, float $divisor): float + { + return @($dividend / $divisor); + } + + public static function get_debug_type($value): string + { + switch (true) { + case null === $value: return 'null'; + case \is_bool($value): return 'bool'; + case \is_string($value): return 'string'; + case \is_array($value): return 'array'; + case \is_int($value): return 'int'; + case \is_float($value): return 'float'; + case \is_object($value): break; + case $value instanceof \__PHP_Incomplete_Class: return '__PHP_Incomplete_Class'; + default: + if (null === $type = @get_resource_type($value)) { + return 'unknown'; + } + + if ('Unknown' === $type) { + $type = 'closed'; + } + + return "resource ($type)"; + } + + $class = \get_class($value); + + if (false === strpos($class, '@')) { + return $class; + } + + return (get_parent_class($class) ?: key(class_implements($class)) ?: 'class').'@anonymous'; + } + + public static function get_resource_id($res): int + { + if (!\is_resource($res) && null === @get_resource_type($res)) { + throw new \TypeError(sprintf('Argument 1 passed to get_resource_id() must be of the type resource, %s given', get_debug_type($res))); + } + + return (int) $res; + } + + public static function preg_last_error_msg(): string + { + switch (preg_last_error()) { + case \PREG_INTERNAL_ERROR: + return 'Internal error'; + case \PREG_BAD_UTF8_ERROR: + return 'Malformed UTF-8 characters, possibly incorrectly encoded'; + case \PREG_BAD_UTF8_OFFSET_ERROR: + return 'The offset did not correspond to the beginning of a valid UTF-8 code point'; + case \PREG_BACKTRACK_LIMIT_ERROR: + return 'Backtrack limit exhausted'; + case \PREG_RECURSION_LIMIT_ERROR: + return 'Recursion limit exhausted'; + case \PREG_JIT_STACKLIMIT_ERROR: + return 'JIT stack limit exhausted'; + case \PREG_NO_ERROR: + return 'No error'; + default: + return 'Unknown error'; + } + } + + public static function str_contains(string $haystack, string $needle): bool + { + return '' === $needle || false !== strpos($haystack, $needle); + } + + public static function str_starts_with(string $haystack, string $needle): bool + { + return 0 === strncmp($haystack, $needle, \strlen($needle)); + } + + public static function str_ends_with(string $haystack, string $needle): bool + { + if ('' === $needle || $needle === $haystack) { + return true; + } + + if ('' === $haystack) { + return false; + } + + $needleLength = \strlen($needle); + + return $needleLength <= \strlen($haystack) && 0 === substr_compare($haystack, $needle, -$needleLength); + } +} diff --git a/sites/all/libraries/vendor/symfony/polyfill-php80/PhpToken.php b/sites/all/libraries/vendor/symfony/polyfill-php80/PhpToken.php new file mode 100644 index 0000000000..fe6e691056 --- /dev/null +++ b/sites/all/libraries/vendor/symfony/polyfill-php80/PhpToken.php @@ -0,0 +1,103 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Polyfill\Php80; + +/** + * @author Fedonyuk Anton + * + * @internal + */ +class PhpToken implements \Stringable +{ + /** + * @var int + */ + public $id; + + /** + * @var string + */ + public $text; + + /** + * @var int + */ + public $line; + + /** + * @var int + */ + public $pos; + + public function __construct(int $id, string $text, int $line = -1, int $position = -1) + { + $this->id = $id; + $this->text = $text; + $this->line = $line; + $this->pos = $position; + } + + public function getTokenName(): ?string + { + if ('UNKNOWN' === $name = token_name($this->id)) { + $name = \strlen($this->text) > 1 || \ord($this->text) < 32 ? null : $this->text; + } + + return $name; + } + + /** + * @param int|string|array $kind + */ + public function is($kind): bool + { + foreach ((array) $kind as $value) { + if (\in_array($value, [$this->id, $this->text], true)) { + return true; + } + } + + return false; + } + + public function isIgnorable(): bool + { + return \in_array($this->id, [\T_WHITESPACE, \T_COMMENT, \T_DOC_COMMENT, \T_OPEN_TAG], true); + } + + public function __toString(): string + { + return (string) $this->text; + } + + /** + * @return static[] + */ + public static function tokenize(string $code, int $flags = 0): array + { + $line = 1; + $position = 0; + $tokens = token_get_all($code, $flags); + foreach ($tokens as $index => $token) { + if (\is_string($token)) { + $id = \ord($token); + $text = $token; + } else { + [$id, $text, $line] = $token; + } + $tokens[$index] = new static($id, $text, $line, $position); + $position += \strlen($text); + } + + return $tokens; + } +} diff --git a/sites/all/libraries/vendor/symfony/polyfill-php80/README.md b/sites/all/libraries/vendor/symfony/polyfill-php80/README.md new file mode 100644 index 0000000000..3816c559d5 --- /dev/null +++ b/sites/all/libraries/vendor/symfony/polyfill-php80/README.md @@ -0,0 +1,25 @@ +Symfony Polyfill / Php80 +======================== + +This component provides features added to PHP 8.0 core: + +- [`Stringable`](https://php.net/stringable) interface +- [`fdiv`](https://php.net/fdiv) +- [`ValueError`](https://php.net/valueerror) class +- [`UnhandledMatchError`](https://php.net/unhandledmatcherror) class +- `FILTER_VALIDATE_BOOL` constant +- [`get_debug_type`](https://php.net/get_debug_type) +- [`PhpToken`](https://php.net/phptoken) class +- [`preg_last_error_msg`](https://php.net/preg_last_error_msg) +- [`str_contains`](https://php.net/str_contains) +- [`str_starts_with`](https://php.net/str_starts_with) +- [`str_ends_with`](https://php.net/str_ends_with) +- [`get_resource_id`](https://php.net/get_resource_id) + +More information can be found in the +[main Polyfill README](https://github.com/symfony/polyfill/blob/main/README.md). + +License +======= + +This library is released under the [MIT license](LICENSE). diff --git a/sites/all/libraries/vendor/symfony/polyfill-php80/Resources/stubs/Attribute.php b/sites/all/libraries/vendor/symfony/polyfill-php80/Resources/stubs/Attribute.php new file mode 100644 index 0000000000..2b955423fc --- /dev/null +++ b/sites/all/libraries/vendor/symfony/polyfill-php80/Resources/stubs/Attribute.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +#[Attribute(Attribute::TARGET_CLASS)] +final class Attribute +{ + public const TARGET_CLASS = 1; + public const TARGET_FUNCTION = 2; + public const TARGET_METHOD = 4; + public const TARGET_PROPERTY = 8; + public const TARGET_CLASS_CONSTANT = 16; + public const TARGET_PARAMETER = 32; + public const TARGET_ALL = 63; + public const IS_REPEATABLE = 64; + + /** @var int */ + public $flags; + + public function __construct(int $flags = self::TARGET_ALL) + { + $this->flags = $flags; + } +} diff --git a/sites/all/libraries/vendor/symfony/polyfill-php80/Resources/stubs/PhpToken.php b/sites/all/libraries/vendor/symfony/polyfill-php80/Resources/stubs/PhpToken.php new file mode 100644 index 0000000000..bd1212f6e4 --- /dev/null +++ b/sites/all/libraries/vendor/symfony/polyfill-php80/Resources/stubs/PhpToken.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +if (\PHP_VERSION_ID < 80000 && extension_loaded('tokenizer')) { + class PhpToken extends Symfony\Polyfill\Php80\PhpToken + { + } +} diff --git a/sites/all/libraries/vendor/symfony/polyfill-php80/Resources/stubs/Stringable.php b/sites/all/libraries/vendor/symfony/polyfill-php80/Resources/stubs/Stringable.php new file mode 100644 index 0000000000..7c62d7508b --- /dev/null +++ b/sites/all/libraries/vendor/symfony/polyfill-php80/Resources/stubs/Stringable.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +if (\PHP_VERSION_ID < 80000) { + interface Stringable + { + /** + * @return string + */ + public function __toString(); + } +} diff --git a/sites/all/libraries/vendor/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php b/sites/all/libraries/vendor/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php new file mode 100644 index 0000000000..01c6c6c8ab --- /dev/null +++ b/sites/all/libraries/vendor/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +if (\PHP_VERSION_ID < 80000) { + class UnhandledMatchError extends Error + { + } +} diff --git a/sites/all/libraries/vendor/symfony/polyfill-php80/Resources/stubs/ValueError.php b/sites/all/libraries/vendor/symfony/polyfill-php80/Resources/stubs/ValueError.php new file mode 100644 index 0000000000..783dbc28c7 --- /dev/null +++ b/sites/all/libraries/vendor/symfony/polyfill-php80/Resources/stubs/ValueError.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +if (\PHP_VERSION_ID < 80000) { + class ValueError extends Error + { + } +} diff --git a/sites/all/libraries/vendor/symfony/polyfill-php80/bootstrap.php b/sites/all/libraries/vendor/symfony/polyfill-php80/bootstrap.php new file mode 100644 index 0000000000..e5f7dbc1a4 --- /dev/null +++ b/sites/all/libraries/vendor/symfony/polyfill-php80/bootstrap.php @@ -0,0 +1,42 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Polyfill\Php80 as p; + +if (\PHP_VERSION_ID >= 80000) { + return; +} + +if (!defined('FILTER_VALIDATE_BOOL') && defined('FILTER_VALIDATE_BOOLEAN')) { + define('FILTER_VALIDATE_BOOL', \FILTER_VALIDATE_BOOLEAN); +} + +if (!function_exists('fdiv')) { + function fdiv(float $num1, float $num2): float { return p\Php80::fdiv($num1, $num2); } +} +if (!function_exists('preg_last_error_msg')) { + function preg_last_error_msg(): string { return p\Php80::preg_last_error_msg(); } +} +if (!function_exists('str_contains')) { + function str_contains(?string $haystack, ?string $needle): bool { return p\Php80::str_contains($haystack ?? '', $needle ?? ''); } +} +if (!function_exists('str_starts_with')) { + function str_starts_with(?string $haystack, ?string $needle): bool { return p\Php80::str_starts_with($haystack ?? '', $needle ?? ''); } +} +if (!function_exists('str_ends_with')) { + function str_ends_with(?string $haystack, ?string $needle): bool { return p\Php80::str_ends_with($haystack ?? '', $needle ?? ''); } +} +if (!function_exists('get_debug_type')) { + function get_debug_type($value): string { return p\Php80::get_debug_type($value); } +} +if (!function_exists('get_resource_id')) { + function get_resource_id($resource): int { return p\Php80::get_resource_id($resource); } +} diff --git a/sites/all/libraries/vendor/symfony/polyfill-php80/composer.json b/sites/all/libraries/vendor/symfony/polyfill-php80/composer.json new file mode 100644 index 0000000000..bd9a3262a9 --- /dev/null +++ b/sites/all/libraries/vendor/symfony/polyfill-php80/composer.json @@ -0,0 +1,40 @@ +{ + "name": "symfony/polyfill-php80", + "type": "library", + "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", + "keywords": ["polyfill", "shim", "compatibility", "portable"], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Ion Bazan", + "email": "ion.bazan@gmail.com" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=7.1" + }, + "autoload": { + "psr-4": { "Symfony\\Polyfill\\Php80\\": "" }, + "files": [ "bootstrap.php" ], + "classmap": [ "Resources/stubs" ] + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-main": "1.27-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + } +} diff --git a/sites/all/libraries/vendor/symfony/service-contracts b/sites/all/libraries/vendor/symfony/service-contracts deleted file mode 160000 index 4b426aac47..0000000000 --- a/sites/all/libraries/vendor/symfony/service-contracts +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 4b426aac47d6427cc1a1d0f7e2ac724627f5966c diff --git a/sites/all/libraries/vendor/symfony/service-contracts/.gitignore b/sites/all/libraries/vendor/symfony/service-contracts/.gitignore new file mode 100644 index 0000000000..c49a5d8df5 --- /dev/null +++ b/sites/all/libraries/vendor/symfony/service-contracts/.gitignore @@ -0,0 +1,3 @@ +vendor/ +composer.lock +phpunit.xml diff --git a/sites/all/libraries/vendor/symfony/service-contracts/Attribute/Required.php b/sites/all/libraries/vendor/symfony/service-contracts/Attribute/Required.php new file mode 100644 index 0000000000..9df851189a --- /dev/null +++ b/sites/all/libraries/vendor/symfony/service-contracts/Attribute/Required.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\Service\Attribute; + +/** + * A required dependency. + * + * This attribute indicates that a property holds a required dependency. The annotated property or method should be + * considered during the instantiation process of the containing class. + * + * @author Alexander M. Turek + */ +#[\Attribute(\Attribute::TARGET_METHOD | \Attribute::TARGET_PROPERTY)] +final class Required +{ +} diff --git a/sites/all/libraries/vendor/symfony/service-contracts/Attribute/SubscribedService.php b/sites/all/libraries/vendor/symfony/service-contracts/Attribute/SubscribedService.php new file mode 100644 index 0000000000..10d1bc38e8 --- /dev/null +++ b/sites/all/libraries/vendor/symfony/service-contracts/Attribute/SubscribedService.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\Service\Attribute; + +use Symfony\Contracts\Service\ServiceSubscriberTrait; + +/** + * Use with {@see ServiceSubscriberTrait} to mark a method's return type + * as a subscribed service. + * + * @author Kevin Bond + */ +#[\Attribute(\Attribute::TARGET_METHOD)] +final class SubscribedService +{ + /** + * @param string|null $key The key to use for the service + * If null, use "ClassName::methodName" + */ + public function __construct( + public ?string $key = null + ) { + } +} diff --git a/sites/all/libraries/vendor/symfony/service-contracts/CHANGELOG.md b/sites/all/libraries/vendor/symfony/service-contracts/CHANGELOG.md new file mode 100644 index 0000000000..7932e26132 --- /dev/null +++ b/sites/all/libraries/vendor/symfony/service-contracts/CHANGELOG.md @@ -0,0 +1,5 @@ +CHANGELOG +========= + +The changelog is maintained for all Symfony contracts at the following URL: +https://github.com/symfony/contracts/blob/main/CHANGELOG.md diff --git a/sites/all/libraries/vendor/symfony/service-contracts/LICENSE b/sites/all/libraries/vendor/symfony/service-contracts/LICENSE new file mode 100644 index 0000000000..74cdc2dbf6 --- /dev/null +++ b/sites/all/libraries/vendor/symfony/service-contracts/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2018-2022 Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/sites/all/libraries/vendor/symfony/service-contracts/README.md b/sites/all/libraries/vendor/symfony/service-contracts/README.md new file mode 100644 index 0000000000..41e054a101 --- /dev/null +++ b/sites/all/libraries/vendor/symfony/service-contracts/README.md @@ -0,0 +1,9 @@ +Symfony Service Contracts +========================= + +A set of abstractions extracted out of the Symfony components. + +Can be used to build on semantics that the Symfony components proved useful - and +that already have battle tested implementations. + +See https://github.com/symfony/contracts/blob/main/README.md for more information. diff --git a/sites/all/libraries/vendor/symfony/service-contracts/ResetInterface.php b/sites/all/libraries/vendor/symfony/service-contracts/ResetInterface.php new file mode 100644 index 0000000000..1af1075eee --- /dev/null +++ b/sites/all/libraries/vendor/symfony/service-contracts/ResetInterface.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\Service; + +/** + * Provides a way to reset an object to its initial state. + * + * When calling the "reset()" method on an object, it should be put back to its + * initial state. This usually means clearing any internal buffers and forwarding + * the call to internal dependencies. All properties of the object should be put + * back to the same state it had when it was first ready to use. + * + * This method could be called, for example, to recycle objects that are used as + * services, so that they can be used to handle several requests in the same + * process loop (note that we advise making your services stateless instead of + * implementing this interface when possible.) + */ +interface ResetInterface +{ + public function reset(); +} diff --git a/sites/all/libraries/vendor/symfony/service-contracts/ServiceLocatorTrait.php b/sites/all/libraries/vendor/symfony/service-contracts/ServiceLocatorTrait.php new file mode 100644 index 0000000000..74dfa4362e --- /dev/null +++ b/sites/all/libraries/vendor/symfony/service-contracts/ServiceLocatorTrait.php @@ -0,0 +1,128 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\Service; + +use Psr\Container\ContainerExceptionInterface; +use Psr\Container\NotFoundExceptionInterface; + +// Help opcache.preload discover always-needed symbols +class_exists(ContainerExceptionInterface::class); +class_exists(NotFoundExceptionInterface::class); + +/** + * A trait to help implement ServiceProviderInterface. + * + * @author Robin Chalas + * @author Nicolas Grekas + */ +trait ServiceLocatorTrait +{ + private $factories; + private $loading = []; + private $providedTypes; + + /** + * @param callable[] $factories + */ + public function __construct(array $factories) + { + $this->factories = $factories; + } + + /** + * {@inheritdoc} + * + * @return bool + */ + public function has(string $id) + { + return isset($this->factories[$id]); + } + + /** + * {@inheritdoc} + * + * @return mixed + */ + public function get(string $id) + { + if (!isset($this->factories[$id])) { + throw $this->createNotFoundException($id); + } + + if (isset($this->loading[$id])) { + $ids = array_values($this->loading); + $ids = \array_slice($this->loading, array_search($id, $ids)); + $ids[] = $id; + + throw $this->createCircularReferenceException($id, $ids); + } + + $this->loading[$id] = $id; + try { + return $this->factories[$id]($this); + } finally { + unset($this->loading[$id]); + } + } + + /** + * {@inheritdoc} + */ + public function getProvidedServices(): array + { + if (null === $this->providedTypes) { + $this->providedTypes = []; + + foreach ($this->factories as $name => $factory) { + if (!\is_callable($factory)) { + $this->providedTypes[$name] = '?'; + } else { + $type = (new \ReflectionFunction($factory))->getReturnType(); + + $this->providedTypes[$name] = $type ? ($type->allowsNull() ? '?' : '').($type instanceof \ReflectionNamedType ? $type->getName() : $type) : '?'; + } + } + } + + return $this->providedTypes; + } + + private function createNotFoundException(string $id): NotFoundExceptionInterface + { + if (!$alternatives = array_keys($this->factories)) { + $message = 'is empty...'; + } else { + $last = array_pop($alternatives); + if ($alternatives) { + $message = sprintf('only knows about the "%s" and "%s" services.', implode('", "', $alternatives), $last); + } else { + $message = sprintf('only knows about the "%s" service.', $last); + } + } + + if ($this->loading) { + $message = sprintf('The service "%s" has a dependency on a non-existent service "%s". This locator %s', end($this->loading), $id, $message); + } else { + $message = sprintf('Service "%s" not found: the current service locator %s', $id, $message); + } + + return new class($message) extends \InvalidArgumentException implements NotFoundExceptionInterface { + }; + } + + private function createCircularReferenceException(string $id, array $path): ContainerExceptionInterface + { + return new class(sprintf('Circular reference detected for service "%s", path: "%s".', $id, implode(' -> ', $path))) extends \RuntimeException implements ContainerExceptionInterface { + }; + } +} diff --git a/sites/all/libraries/vendor/symfony/service-contracts/ServiceProviderInterface.php b/sites/all/libraries/vendor/symfony/service-contracts/ServiceProviderInterface.php new file mode 100644 index 0000000000..c60ad0bd4b --- /dev/null +++ b/sites/all/libraries/vendor/symfony/service-contracts/ServiceProviderInterface.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\Service; + +use Psr\Container\ContainerInterface; + +/** + * A ServiceProviderInterface exposes the identifiers and the types of services provided by a container. + * + * @author Nicolas Grekas + * @author Mateusz Sip + */ +interface ServiceProviderInterface extends ContainerInterface +{ + /** + * Returns an associative array of service types keyed by the identifiers provided by the current container. + * + * Examples: + * + * * ['logger' => 'Psr\Log\LoggerInterface'] means the object provides a service named "logger" that implements Psr\Log\LoggerInterface + * * ['foo' => '?'] means the container provides service name "foo" of unspecified type + * * ['bar' => '?Bar\Baz'] means the container provides a service "bar" of type Bar\Baz|null + * + * @return string[] The provided service types, keyed by service names + */ + public function getProvidedServices(): array; +} diff --git a/sites/all/libraries/vendor/symfony/service-contracts/ServiceSubscriberInterface.php b/sites/all/libraries/vendor/symfony/service-contracts/ServiceSubscriberInterface.php new file mode 100644 index 0000000000..098ab908cd --- /dev/null +++ b/sites/all/libraries/vendor/symfony/service-contracts/ServiceSubscriberInterface.php @@ -0,0 +1,53 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\Service; + +/** + * A ServiceSubscriber exposes its dependencies via the static {@link getSubscribedServices} method. + * + * The getSubscribedServices method returns an array of service types required by such instances, + * optionally keyed by the service names used internally. Service types that start with an interrogation + * mark "?" are optional, while the other ones are mandatory service dependencies. + * + * The injected service locators SHOULD NOT allow access to any other services not specified by the method. + * + * It is expected that ServiceSubscriber instances consume PSR-11-based service locators internally. + * This interface does not dictate any injection method for these service locators, although constructor + * injection is recommended. + * + * @author Nicolas Grekas + */ +interface ServiceSubscriberInterface +{ + /** + * Returns an array of service types required by such instances, optionally keyed by the service names used internally. + * + * For mandatory dependencies: + * + * * ['logger' => 'Psr\Log\LoggerInterface'] means the objects use the "logger" name + * internally to fetch a service which must implement Psr\Log\LoggerInterface. + * * ['loggers' => 'Psr\Log\LoggerInterface[]'] means the objects use the "loggers" name + * internally to fetch an iterable of Psr\Log\LoggerInterface instances. + * * ['Psr\Log\LoggerInterface'] is a shortcut for + * * ['Psr\Log\LoggerInterface' => 'Psr\Log\LoggerInterface'] + * + * otherwise: + * + * * ['logger' => '?Psr\Log\LoggerInterface'] denotes an optional dependency + * * ['loggers' => '?Psr\Log\LoggerInterface[]'] denotes an optional iterable dependency + * * ['?Psr\Log\LoggerInterface'] is a shortcut for + * * ['Psr\Log\LoggerInterface' => '?Psr\Log\LoggerInterface'] + * + * @return string[] The required service types, optionally keyed by service names + */ + public static function getSubscribedServices(); +} diff --git a/sites/all/libraries/vendor/symfony/service-contracts/ServiceSubscriberTrait.php b/sites/all/libraries/vendor/symfony/service-contracts/ServiceSubscriberTrait.php new file mode 100644 index 0000000000..16e3eb2c19 --- /dev/null +++ b/sites/all/libraries/vendor/symfony/service-contracts/ServiceSubscriberTrait.php @@ -0,0 +1,109 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\Service; + +use Psr\Container\ContainerInterface; +use Symfony\Contracts\Service\Attribute\SubscribedService; + +/** + * Implementation of ServiceSubscriberInterface that determines subscribed services from + * method return types. Service ids are available as "ClassName::methodName". + * + * @author Kevin Bond + */ +trait ServiceSubscriberTrait +{ + /** @var ContainerInterface */ + protected $container; + + /** + * {@inheritdoc} + */ + public static function getSubscribedServices(): array + { + $services = method_exists(get_parent_class(self::class) ?: '', __FUNCTION__) ? parent::getSubscribedServices() : []; + $attributeOptIn = false; + + if (\PHP_VERSION_ID >= 80000) { + foreach ((new \ReflectionClass(self::class))->getMethods() as $method) { + if (self::class !== $method->getDeclaringClass()->name) { + continue; + } + + if (!$attribute = $method->getAttributes(SubscribedService::class)[0] ?? null) { + continue; + } + + if ($method->isStatic() || $method->isAbstract() || $method->isGenerator() || $method->isInternal() || $method->getNumberOfRequiredParameters()) { + throw new \LogicException(sprintf('Cannot use "%s" on method "%s::%s()" (can only be used on non-static, non-abstract methods with no parameters).', SubscribedService::class, self::class, $method->name)); + } + + if (!$returnType = $method->getReturnType()) { + throw new \LogicException(sprintf('Cannot use "%s" on methods without a return type in "%s::%s()".', SubscribedService::class, $method->name, self::class)); + } + + $serviceId = $returnType instanceof \ReflectionNamedType ? $returnType->getName() : (string) $returnType; + + if ($returnType->allowsNull()) { + $serviceId = '?'.$serviceId; + } + + $services[$attribute->newInstance()->key ?? self::class.'::'.$method->name] = $serviceId; + $attributeOptIn = true; + } + } + + if (!$attributeOptIn) { + foreach ((new \ReflectionClass(self::class))->getMethods() as $method) { + if ($method->isStatic() || $method->isAbstract() || $method->isGenerator() || $method->isInternal() || $method->getNumberOfRequiredParameters()) { + continue; + } + + if (self::class !== $method->getDeclaringClass()->name) { + continue; + } + + if (!($returnType = $method->getReturnType()) instanceof \ReflectionNamedType) { + continue; + } + + if ($returnType->isBuiltin()) { + continue; + } + + if (\PHP_VERSION_ID >= 80000) { + trigger_deprecation('symfony/service-contracts', '2.5', 'Using "%s" in "%s" without using the "%s" attribute on any method is deprecated.', ServiceSubscriberTrait::class, self::class, SubscribedService::class); + } + + $services[self::class.'::'.$method->name] = '?'.($returnType instanceof \ReflectionNamedType ? $returnType->getName() : $returnType); + } + } + + return $services; + } + + /** + * @required + * + * @return ContainerInterface|null + */ + public function setContainer(ContainerInterface $container) + { + $this->container = $container; + + if (method_exists(get_parent_class(self::class) ?: '', __FUNCTION__)) { + return parent::setContainer($container); + } + + return null; + } +} diff --git a/sites/all/libraries/vendor/symfony/service-contracts/Test/ServiceLocatorTest.php b/sites/all/libraries/vendor/symfony/service-contracts/Test/ServiceLocatorTest.php new file mode 100644 index 0000000000..2a1b565f50 --- /dev/null +++ b/sites/all/libraries/vendor/symfony/service-contracts/Test/ServiceLocatorTest.php @@ -0,0 +1,95 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\Service\Test; + +use PHPUnit\Framework\TestCase; +use Psr\Container\ContainerInterface; +use Symfony\Contracts\Service\ServiceLocatorTrait; + +abstract class ServiceLocatorTest extends TestCase +{ + /** + * @return ContainerInterface + */ + protected function getServiceLocator(array $factories) + { + return new class($factories) implements ContainerInterface { + use ServiceLocatorTrait; + }; + } + + public function testHas() + { + $locator = $this->getServiceLocator([ + 'foo' => function () { return 'bar'; }, + 'bar' => function () { return 'baz'; }, + function () { return 'dummy'; }, + ]); + + $this->assertTrue($locator->has('foo')); + $this->assertTrue($locator->has('bar')); + $this->assertFalse($locator->has('dummy')); + } + + public function testGet() + { + $locator = $this->getServiceLocator([ + 'foo' => function () { return 'bar'; }, + 'bar' => function () { return 'baz'; }, + ]); + + $this->assertSame('bar', $locator->get('foo')); + $this->assertSame('baz', $locator->get('bar')); + } + + public function testGetDoesNotMemoize() + { + $i = 0; + $locator = $this->getServiceLocator([ + 'foo' => function () use (&$i) { + ++$i; + + return 'bar'; + }, + ]); + + $this->assertSame('bar', $locator->get('foo')); + $this->assertSame('bar', $locator->get('foo')); + $this->assertSame(2, $i); + } + + public function testThrowsOnUndefinedInternalService() + { + if (!$this->getExpectedException()) { + $this->expectException(\Psr\Container\NotFoundExceptionInterface::class); + $this->expectExceptionMessage('The service "foo" has a dependency on a non-existent service "bar". This locator only knows about the "foo" service.'); + } + $locator = $this->getServiceLocator([ + 'foo' => function () use (&$locator) { return $locator->get('bar'); }, + ]); + + $locator->get('foo'); + } + + public function testThrowsOnCircularReference() + { + $this->expectException(\Psr\Container\ContainerExceptionInterface::class); + $this->expectExceptionMessage('Circular reference detected for service "bar", path: "bar -> baz -> bar".'); + $locator = $this->getServiceLocator([ + 'foo' => function () use (&$locator) { return $locator->get('bar'); }, + 'bar' => function () use (&$locator) { return $locator->get('baz'); }, + 'baz' => function () use (&$locator) { return $locator->get('bar'); }, + ]); + + $locator->get('foo'); + } +} diff --git a/sites/all/libraries/vendor/symfony/service-contracts/composer.json b/sites/all/libraries/vendor/symfony/service-contracts/composer.json new file mode 100644 index 0000000000..f058637010 --- /dev/null +++ b/sites/all/libraries/vendor/symfony/service-contracts/composer.json @@ -0,0 +1,42 @@ +{ + "name": "symfony/service-contracts", + "type": "library", + "description": "Generic abstractions related to writing services", + "keywords": ["abstractions", "contracts", "decoupling", "interfaces", "interoperability", "standards"], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=7.2.5", + "psr/container": "^1.1", + "symfony/deprecation-contracts": "^2.1|^3" + }, + "conflict": { + "ext-psr": "<1.1|>=2" + }, + "suggest": { + "symfony/service-implementation": "" + }, + "autoload": { + "psr-4": { "Symfony\\Contracts\\Service\\": "" } + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-main": "2.5-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + } +} From aa6bbe53f11d2c3765c06487f5bcb39e18151f41 Mon Sep 17 00:00:00 2001 From: Rob Davis Date: Thu, 1 Dec 2022 13:08:33 +0000 Subject: [PATCH 14/22] fix syntax error in css https://github.com/NaturalHistoryMuseum/scratchpads2/issues/6564 --- .../modules/iucn/iucn.module | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/sites/all/modules/custom/scratchpads/scratchpads_species/modules/iucn/iucn.module b/sites/all/modules/custom/scratchpads/scratchpads_species/modules/iucn/iucn.module index 875d8bd0a9..da252ff34f 100644 --- a/sites/all/modules/custom/scratchpads/scratchpads_species/modules/iucn/iucn.module +++ b/sites/all/modules/custom/scratchpads/scratchpads_species/modules/iucn/iucn.module @@ -69,6 +69,7 @@ function iucn_block_view($delta = ''){ $contentMarkupToDisplay =""; + /* // credit: https://github.com/jbroutier/iucn-api-client#getting-started $contentMarkupToDisplay .= '

'.$species->getKingdom().'

'; // ANIMALIA $contentMarkupToDisplay .= '

'.$species->getPhylum().'

'; // CHORDATA @@ -77,6 +78,24 @@ function iucn_block_view($delta = ''){ $contentMarkupToDisplay .= '

'.$species->getFamily().'

'; // AILURIDAE $contentMarkupToDisplay .= '

'.$species->getGenus().'

'; // Ailurus $contentMarkupToDisplay .= '

'.$species->getMainCommonName().'

'; // Red Panda + */ + + $contentMarkupToDisplay + = ""; + + + foreach($iucnClient as $key => $value) { + $item = "
".$key."
\n"; + if (isset($value)) { + $item .= "
".$value."
\n"; + } + else { + $item .= "
-
\n"; + } + + $contentMarkupToDisplay .= $item; + } + $content['content']['#markup'] = $contentMarkupToDisplay; From f89daad79cc9b555e4dfc7a5644a7507caef578c Mon Sep 17 00:00:00 2001 From: Rob Davis Date: Thu, 1 Dec 2022 17:12:47 +0000 Subject: [PATCH 15/22] clarifying cache checking logic, https://github.com/NaturalHistoryMuseum/scratchpads2/issues/6564 --- .../modules/iucn/iucn.module | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/sites/all/modules/custom/scratchpads/scratchpads_species/modules/iucn/iucn.module b/sites/all/modules/custom/scratchpads/scratchpads_species/modules/iucn/iucn.module index da252ff34f..abbc3789c1 100644 --- a/sites/all/modules/custom/scratchpads/scratchpads_species/modules/iucn/iucn.module +++ b/sites/all/modules/custom/scratchpads/scratchpads_species/modules/iucn/iucn.module @@ -55,9 +55,20 @@ function iucn_block_view($delta = ''){ $iucnClient = new \IucnApi\Client($iucnV3APIKeyToken); - //$githubclient = new \Github\Client(); + $flagAjaxBlocks = false; //initial default + // was: + //if(!function_exists('ajaxblocks_in_ajax_handler') || (function_exists('ajaxblocks_in_ajax_handler') && ajaxblocks_in_ajax_handler())){ + // no comment about what this is exactly supposed to do, so breaking it down into several IFs - if(!function_exists('ajaxblocks_in_ajax_handler') || (function_exists('ajaxblocks_in_ajax_handler') && ajaxblocks_in_ajax_handler())){ + if (!function_exists('ajaxblocks_in_ajax_handler')) { + $flagAjaxBlocks = true; + } + + if (function_exists('ajaxblocks_in_ajax_handler') && ajaxblocks_in_ajax_handler()) { + $flagAjaxBlocks = true; + } + + if($flagAjaxBlocks){ $cache = cache_get($term->tid, 'cache_iucn'); if($cache->data){ $content['content']['#markup'] = $cache->data; @@ -83,8 +94,10 @@ function iucn_block_view($delta = ''){ $contentMarkupToDisplay = ""; + // credit: https://stackoverflow.com/questions/15700325/foreach-on-all-object-properties + - foreach($iucnClient as $key => $value) { + foreach($species as $key => $value) { $item = "
".$key."
\n"; if (isset($value)) { $item .= "
".$value."
\n"; From 85424dd05ada976272520479f7e03faf73973a19 Mon Sep 17 00:00:00 2001 From: Rob Davis Date: Fri, 2 Dec 2022 11:06:19 +0000 Subject: [PATCH 16/22] https://github.com/NaturalHistoryMuseum/scratchpads2/issues/6564#issuecomment-1335072987 --- .../modules/iucn/iucn.module | 30 +++++++++++++++---- 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/sites/all/modules/custom/scratchpads/scratchpads_species/modules/iucn/iucn.module b/sites/all/modules/custom/scratchpads/scratchpads_species/modules/iucn/iucn.module index abbc3789c1..b12e7d1059 100644 --- a/sites/all/modules/custom/scratchpads/scratchpads_species/modules/iucn/iucn.module +++ b/sites/all/modules/custom/scratchpads/scratchpads_species/modules/iucn/iucn.module @@ -91,16 +91,34 @@ function iucn_block_view($delta = ''){ $contentMarkupToDisplay .= '

'.$species->getMainCommonName().'

'; // Red Panda */ - $contentMarkupToDisplay - = ""; + // https://stackoverflow.com/questions/9744192/multi-line-strings-in-php + $contentMarkupToDisplay = << + .iucn-item.name { + background-color: #eeeeee; + font-weight: bold; + } + + .iucn-item.value { + background-color: white; + } - // credit: https://stackoverflow.com/questions/15700325/foreach-on-all-object-properties + .iucn-item { + padding: 5px; + } + +EOD; - foreach($species as $key => $value) { - $item = "
".$key."
\n"; + // bad advice here: https://stackoverflow.com/questions/15700325/ + // so I used this: https://stackoverflow.com/a/5970283/227926 + foreach((array)$species as $key => $value) { + // https://stackoverflow.com/q/10643167/227926 + // https://stackoverflow.com/a/41243650/227926 + $keyWithoutAsteriskPrefix = preg_replace('/[\*]+/', '', $key); + $item = "
".$keyWithoutAsteriskPrefix."
\n"; if (isset($value)) { - $item .= "
".$value."
\n"; + $item .= "
".$value."
\n"; } else { $item .= "
-
\n"; From be0e4d8db86dcb0256417e03d06d0ea8da632150 Mon Sep 17 00:00:00 2001 From: Rob Davis Date: Fri, 2 Dec 2022 11:09:50 +0000 Subject: [PATCH 17/22] clean up and fix iucn, output formatted, https://github.com/NaturalHistoryMuseum/scratchpads2/issues/6564#issuecomment-1335072987 --- .../modules/iucn/iucn.module | 56 ++----------------- 1 file changed, 6 insertions(+), 50 deletions(-) diff --git a/sites/all/modules/custom/scratchpads/scratchpads_species/modules/iucn/iucn.module b/sites/all/modules/custom/scratchpads/scratchpads_species/modules/iucn/iucn.module index b12e7d1059..4aea932c34 100644 --- a/sites/all/modules/custom/scratchpads/scratchpads_species/modules/iucn/iucn.module +++ b/sites/all/modules/custom/scratchpads/scratchpads_species/modules/iucn/iucn.module @@ -55,17 +55,17 @@ function iucn_block_view($delta = ''){ $iucnClient = new \IucnApi\Client($iucnV3APIKeyToken); + // simplifid logic: $flagAjaxBlocks = false; //initial default // was: //if(!function_exists('ajaxblocks_in_ajax_handler') || (function_exists('ajaxblocks_in_ajax_handler') && ajaxblocks_in_ajax_handler())){ // no comment about what this is exactly supposed to do, so breaking it down into several IFs - if (!function_exists('ajaxblocks_in_ajax_handler')) { $flagAjaxBlocks = true; - } - - if (function_exists('ajaxblocks_in_ajax_handler') && ajaxblocks_in_ajax_handler()) { - $flagAjaxBlocks = true; + } else { + if (function_exists('ajaxblocks_in_ajax_handler') && ajaxblocks_in_ajax_handler()) { + $flagAjaxBlocks = true; + } } if($flagAjaxBlocks){ @@ -80,17 +80,6 @@ function iucn_block_view($delta = ''){ $contentMarkupToDisplay =""; - /* - // credit: https://github.com/jbroutier/iucn-api-client#getting-started - $contentMarkupToDisplay .= '

'.$species->getKingdom().'

'; // ANIMALIA - $contentMarkupToDisplay .= '

'.$species->getPhylum().'

'; // CHORDATA - $contentMarkupToDisplay .= '

'.$species->getClass().'

'; // MAMMALIA - $contentMarkupToDisplay .= '

'.$species->getOrder().'

'; // CARNIVORA - $contentMarkupToDisplay .= '

'.$species->getFamily().'

'; // AILURIDAE - $contentMarkupToDisplay .= '

'.$species->getGenus().'

'; // Ailurus - $contentMarkupToDisplay .= '

'.$species->getMainCommonName().'

'; // Red Panda - */ - // https://stackoverflow.com/questions/9744192/multi-line-strings-in-php $contentMarkupToDisplay = << @@ -109,11 +98,10 @@ function iucn_block_view($delta = ''){ EOD; - // bad advice here: https://stackoverflow.com/questions/15700325/ // so I used this: https://stackoverflow.com/a/5970283/227926 foreach((array)$species as $key => $value) { - // https://stackoverflow.com/q/10643167/227926 + // https://stackoverflow.com/questions/41243543/what-does-an-array-with-a-mean/41243650#41243650 // https://stackoverflow.com/a/41243650/227926 $keyWithoutAsteriskPrefix = preg_replace('/[\*]+/', '', $key); $item = "
".$keyWithoutAsteriskPrefix."
\n"; @@ -138,38 +126,6 @@ EOD; '$term_name' => $term->name )); } - - - - /* - $request = drupal_http_request('http://api.iucnredlist.org/index/species/' . preg_replace('/[^A-Za-z\-]/','',str_replace(' ', '-', $term->name)) . '.js', array( - 'timeout' => '3.0' - )); - if($request->code == 200 && ($json = json_decode($request->data)) !== FALSE){ - if(count($json) && strtolower($json[0]->scientific_name) == strtolower($term->name)){ - $request = drupal_http_request('http://api.iucnredlist.org/details/' . $json[0]->species_id . '/0.js', array( - 'timeout' => '3.0' - )); - if($request->code == 200){ - $content['content']['#markup'] = str_replace('data); - $content['content']['#markup'] = str_replace('tid, $content['content']['#markup'], 'cache_iucn'); - }else{ - $content['content']['#markup'] = t('There was an error downloading the information for %term_name', array( - '$term_name' => $term->name - )); - } - }else{ - $content['content']['#markup'] = t('The IUCN does not hold any information for %term_name', array( - '%term_name' => $term->name - )); - cache_set($term->tid, $content['content']['#markup'], 'cache_iucn'); - } - } - - */ } } } From cd0f0473eb80a8166aedcd56b171a83c39b6f3ee Mon Sep 17 00:00:00 2001 From: Rob Davis Date: Fri, 2 Dec 2022 14:31:48 +0000 Subject: [PATCH 18/22] #6612 update Drupal 7 core to 7.92, before that last version on Scratchpads was 7.82 and between that and the latest there have been security updates --- .htaccess | 4 +- includes/authorize.inc | 5 - includes/batch.inc | 81 ++--- includes/bootstrap.inc | 78 ++++- includes/common.inc | 82 ++++- includes/database/database.inc | 12 +- includes/database/mysql/database.inc | 5 + includes/database/pgsql/database.inc | 3 +- includes/database/pgsql/query.inc | 4 +- includes/database/pgsql/schema.inc | 281 ++++++++++++++-- includes/database/prefetch.inc | 7 +- includes/database/query.inc | 30 +- includes/database/select.inc | 53 ++- includes/database/sqlite/schema.inc | 2 +- includes/entity.inc | 5 +- includes/errors.inc | 7 +- includes/file.inc | 96 +++++- includes/filetransfer/filetransfer.inc | 4 +- includes/form.inc | 22 +- includes/install.core.inc | 15 +- includes/install.inc | 22 +- includes/iso.inc | 2 +- includes/locale.inc | 4 +- includes/menu.inc | 6 +- includes/module.inc | 7 +- includes/pager.inc | 15 + includes/path.inc | 2 + includes/session.inc | 2 +- includes/stream_wrappers.inc | 31 +- includes/unicode.inc | 1 + misc/batch.js | 3 + misc/drupal.js | 8 +- misc/machine-name.js | 2 +- modules/aggregator/aggregator.info | 6 +- modules/aggregator/tests/aggregator_test.info | 6 +- modules/block/block.info | 6 +- modules/block/tests/block_test.info | 6 +- .../block_test_theme/block_test_theme.info | 6 +- modules/blog/blog.info | 6 +- modules/book/book.info | 6 +- modules/color/color.info | 6 +- modules/color/color.module | 4 +- modules/color/color.test | 50 +++ modules/comment/comment.info | 6 +- modules/comment/comment.module | 11 +- modules/comment/comment.test | 13 +- modules/contact/contact.info | 6 +- modules/contextual/contextual.info | 6 +- modules/dashboard/dashboard.info | 6 +- modules/dblog/dblog.admin.inc | 10 +- modules/dblog/dblog.info | 6 +- modules/dblog/dblog.test | 30 ++ modules/field/field.crud.inc | 1 + modules/field/field.info | 6 +- modules/field/field.info.class.inc | 2 +- .../field_sql_storage/field_sql_storage.info | 6 +- .../field_sql_storage.module | 12 + .../field_sql_storage/field_sql_storage.test | 23 ++ modules/field/modules/list/list.info | 6 +- modules/field/modules/list/list.module | 5 +- modules/field/modules/list/tests/list.test | 20 +- .../field/modules/list/tests/list_test.info | 6 +- modules/field/modules/number/number.info | 6 +- modules/field/modules/options/options.info | 6 +- modules/field/modules/options/options.test | 5 + modules/field/modules/text/text.info | 6 +- modules/field/modules/text/text.module | 5 + modules/field/modules/text/text.test | 8 + modules/field/tests/field.test | 12 + modules/field/tests/field_test.info | 6 +- modules/field_ui/field_ui.admin.inc | 9 + modules/field_ui/field_ui.info | 6 +- modules/file/file.field.inc | 6 +- modules/file/file.info | 6 +- modules/file/file.install | 28 +- modules/file/file.module | 35 +- modules/file/tests/file.test | 108 +++++- modules/file/tests/file_module_test.info | 6 +- modules/filter/filter.info | 6 +- modules/filter/filter.module | 4 +- modules/forum/forum.info | 6 +- modules/help/help.info | 6 +- modules/image/image.effects.inc | 2 +- modules/image/image.field.inc | 2 +- modules/image/image.info | 6 +- modules/image/image.module | 116 +++++-- modules/image/image.test | 58 +++- modules/image/tests/image_module_test.info | 6 +- modules/image/tests/image_module_test.module | 5 +- modules/locale/locale.info | 6 +- modules/locale/locale.module | 2 + modules/locale/locale.test | 30 ++ modules/locale/tests/locale_test.info | 6 +- modules/menu/menu.admin.inc | 1 + modules/menu/menu.info | 6 +- modules/menu/menu.module | 2 +- modules/node/node.api.php | 8 +- modules/node/node.info | 6 +- modules/node/node.test | 33 +- modules/node/node.tokens.inc | 6 +- modules/node/tests/node_access_test.info | 6 +- modules/node/tests/node_test.info | 6 +- modules/node/tests/node_test_exception.info | 6 +- modules/openid/openid.inc | 2 +- modules/openid/openid.info | 6 +- modules/openid/tests/openid_test.info | 6 +- modules/overlay/overlay.info | 6 +- modules/path/path.info | 6 +- modules/php/php.info | 6 +- modules/poll/poll.info | 6 +- modules/poll/poll.test | 2 +- modules/profile/profile.info | 6 +- modules/rdf/rdf.info | 6 +- modules/rdf/tests/rdf_test.info | 6 +- modules/search/search.info | 6 +- .../search/tests/search_embedded_form.info | 6 +- modules/search/tests/search_extra_type.info | 6 +- modules/search/tests/search_node_tags.info | 6 +- modules/shortcut/shortcut.info | 6 +- modules/simpletest/drupal_web_test_case.php | 2 +- modules/simpletest/simpletest.info | 6 +- modules/simpletest/simpletest.install | 13 - .../simpletest/tests/actions_loop_test.info | 6 +- modules/simpletest/tests/ajax_forms_test.info | 6 +- modules/simpletest/tests/ajax_test.info | 6 +- modules/simpletest/tests/batch_test.info | 6 +- modules/simpletest/tests/boot_test_1.info | 6 +- modules/simpletest/tests/boot_test_2.info | 6 +- modules/simpletest/tests/bootstrap.test | 25 ++ modules/simpletest/tests/common.test | 123 ++++++- modules/simpletest/tests/common_test.info | 6 +- modules/simpletest/tests/common_test.module | 36 ++ .../tests/common_test_cron_helper.info | 6 +- modules/simpletest/tests/database_test.info | 6 +- .../simpletest/tests/database_test.install | 40 +++ modules/simpletest/tests/database_test.test | 314 ++++++++++++++++-- .../drupal_autoload_test.info | 6 +- ...drupal_system_listing_compatible_test.info | 6 +- ...upal_system_listing_incompatible_test.info | 6 +- .../simpletest/tests/entity_cache_test.info | 6 +- .../tests/entity_cache_test_dependency.info | 6 +- modules/simpletest/tests/entity_crud.test | 12 + .../tests/entity_crud_hook_test.info | 6 +- .../tests/entity_query_access_test.info | 6 +- modules/simpletest/tests/error_test.info | 6 +- modules/simpletest/tests/file.test | 35 ++ modules/simpletest/tests/file_test.info | 6 +- modules/simpletest/tests/filetransfer.test | 2 +- modules/simpletest/tests/filter_test.info | 6 +- modules/simpletest/tests/form_test.info | 6 +- modules/simpletest/tests/image_test.info | 6 +- modules/simpletest/tests/menu.test | 38 +++ modules/simpletest/tests/menu_test.info | 6 +- modules/simpletest/tests/module.test | 31 ++ modules/simpletest/tests/module_test.info | 6 +- modules/simpletest/tests/pager.test | 2 +- modules/simpletest/tests/path.test | 11 + modules/simpletest/tests/path_test.info | 6 +- .../tests/psr_0_test/psr_0_test.info | 6 +- .../tests/psr_4_test/psr_4_test.info | 6 +- .../simpletest/tests/requirements1_test.info | 6 +- .../simpletest/tests/requirements2_test.info | 6 +- modules/simpletest/tests/session.test | 120 +++++++ modules/simpletest/tests/session_test.info | 6 +- .../tests/system_dependencies_test.info | 6 +- ...atible_core_version_dependencies_test.info | 6 +- ...system_incompatible_core_version_test.info | 6 +- ...ible_module_version_dependencies_test.info | 6 +- ...stem_incompatible_module_version_test.info | 6 +- .../tests/system_project_namespace_test.info | 6 +- modules/simpletest/tests/system_test.info | 6 +- modules/simpletest/tests/system_test.module | 53 ++- modules/simpletest/tests/taxonomy_test.info | 6 +- modules/simpletest/tests/taxonomy_test.module | 11 + modules/simpletest/tests/theme_test.info | 6 +- .../themes/test_basetheme/test_basetheme.info | 6 +- .../themes/test_subtheme/test_subtheme.info | 6 +- .../tests/themes/test_theme/test_theme.info | 6 +- .../test_theme_nyan_cat.info | 6 +- .../simpletest/tests/update_script_test.info | 6 +- modules/simpletest/tests/update_test_1.info | 6 +- modules/simpletest/tests/update_test_2.info | 6 +- modules/simpletest/tests/update_test_3.info | 6 +- .../tests/upgrade/update.aggregator.test | 1 + .../tests/upgrade/upgrade.comment.test | 3 + .../tests/upgrade/upgrade.filter.test | 3 + .../tests/upgrade/upgrade.forum.test | 3 + .../tests/upgrade/upgrade.locale.test | 12 + .../tests/upgrade/upgrade.menu.test | 3 + .../tests/upgrade/upgrade.node.test | 9 + .../tests/upgrade/upgrade.poll.test | 3 + .../tests/upgrade/upgrade.taxonomy.test | 3 + modules/simpletest/tests/upgrade/upgrade.test | 32 +- .../tests/upgrade/upgrade.translatable.test | 3 + .../tests/upgrade/upgrade.trigger.test | 3 + .../tests/upgrade/upgrade.upload.test | 3 + .../tests/upgrade/upgrade.user.test | 9 + modules/simpletest/tests/url_alter_test.info | 6 +- .../simpletest/tests/url_alter_test.module | 4 +- modules/simpletest/tests/xmlrpc_test.info | 6 +- modules/statistics/statistics.info | 6 +- modules/syslog/syslog.info | 6 +- modules/syslog/syslog.module | 4 +- modules/system/image.gd.inc | 4 +- modules/system/system.admin.inc | 17 +- modules/system/system.api.php | 2 +- modules/system/system.info | 6 +- modules/system/system.install | 46 ++- modules/system/system.module | 23 +- modules/system/system.tar.inc | 27 +- modules/system/system.test | 215 ++++++++++++ modules/system/tests/cron_queue_test.info | 6 +- modules/system/tests/system_cron_test.info | 6 +- modules/taxonomy/taxonomy.admin.inc | 4 +- modules/taxonomy/taxonomy.info | 6 +- modules/taxonomy/taxonomy.module | 31 +- modules/taxonomy/taxonomy.test | 149 +++++++++ modules/toolbar/toolbar.info | 6 +- modules/tracker/tracker.info | 6 +- .../translation/tests/translation_test.info | 6 +- modules/translation/translation.info | 6 +- modules/trigger/tests/trigger_test.info | 6 +- modules/trigger/trigger.info | 6 +- modules/update/tests/aaa_update_test.info | 6 +- modules/update/tests/bbb_update_test.info | 6 +- modules/update/tests/ccc_update_test.info | 6 +- .../update_test_admintheme.info | 6 +- .../update_test_basetheme.info | 6 +- .../update_test_subtheme.info | 6 +- modules/update/tests/update_test.info | 6 +- modules/update/update.info | 6 +- modules/update/update.module | 3 +- modules/user/tests/user_flood_test.info | 6 +- modules/user/tests/user_form_test.info | 6 +- modules/user/tests/user_session_test.info | 6 +- modules/user/user.admin.inc | 25 +- modules/user/user.info | 6 +- modules/user/user.install | 47 +++ modules/user/user.module | 8 +- modules/user/user.pages.inc | 22 +- modules/user/user.test | 53 ++- modules/user/user.tokens.inc | 22 +- profiles/minimal/minimal.info | 6 +- profiles/standard/standard.info | 6 +- profiles/standard/standard.install | 2 +- ...drupal_system_listing_compatible_test.info | 6 +- ...upal_system_listing_incompatible_test.info | 6 +- profiles/testing/testing.info | 6 +- scripts/run-tests.sh | 48 ++- sites/default/default.settings.php | 61 ++++ themes/bartik/bartik.info | 6 +- themes/garland/garland.info | 6 +- themes/seven/seven.info | 6 +- themes/stark/stark.info | 6 +- update.php | 4 - 255 files changed, 3328 insertions(+), 814 deletions(-) diff --git a/.htaccess b/.htaccess index 551b00eec9..c2aa6fc0dc 100644 --- a/.htaccess +++ b/.htaccess @@ -54,8 +54,8 @@ DirectoryIndex index.php index.html index.htm # Enable expirations. ExpiresActive On - # Cache all files for one month after access (A). - ExpiresDefault A2628000 + # Cache all files for 2 weeks after access (A). + ExpiresDefault A1209600 # Do not allow PHP scripts to be cached unless they explicitly send cache diff --git a/includes/authorize.inc b/includes/authorize.inc index 8360e132ce..1db3267832 100644 --- a/includes/authorize.inc +++ b/includes/authorize.inc @@ -104,11 +104,6 @@ function authorize_filetransfer_form($form, &$form_state) { // Start non-JS code. if (isset($form_state['values']['connection_settings']['authorize_filetransfer_default']) && $form_state['values']['connection_settings']['authorize_filetransfer_default'] == $name) { - // If the user switches from JS to non-JS, Drupal (and Batch API) will - // barf. This is a known bug: http://drupal.org/node/229825. - setcookie('has_js', '', time() - 3600, '/'); - unset($_COOKIE['has_js']); - // Change the submit button to the submit_process one. $form['submit_process']['#attributes'] = array(); unset($form['submit_connection']); diff --git a/includes/batch.inc b/includes/batch.inc index 4d4e504d5d..c98135bebf 100644 --- a/includes/batch.inc +++ b/includes/batch.inc @@ -72,7 +72,9 @@ function _batch_page() { $output = NULL; switch ($op) { case 'start': - $output = _batch_start(); + // Display the full progress page on startup and on each additional + // non-JavaScript iteration. + $output = _batch_progress_page(); break; case 'do': @@ -82,7 +84,7 @@ function _batch_page() { case 'do_nojs': // Non-JavaScript-based progress page. - $output = _batch_progress_page_nojs(); + $output = _batch_progress_page(); break; case 'finished': @@ -93,69 +95,12 @@ function _batch_page() { return $output; } -/** - * Initializes the batch processing. - * - * JavaScript-enabled clients are identified by the 'has_js' cookie set in - * drupal.js. If no JavaScript-enabled page has been visited during the current - * user's browser session, the non-JavaScript version is returned. - */ -function _batch_start() { - if (isset($_COOKIE['has_js']) && $_COOKIE['has_js']) { - return _batch_progress_page_js(); - } - else { - return _batch_progress_page_nojs(); - } -} - -/** - * Outputs a batch processing page with JavaScript support. - * - * This initializes the batch and error messages. Note that in JavaScript-based - * processing, the batch processing page is displayed only once and updated via - * AHAH requests, so only the first batch set gets to define the page title. - * Titles specified by subsequent batch sets are not displayed. - * - * @see batch_set() - * @see _batch_do() - */ -function _batch_progress_page_js() { - $batch = batch_get(); - - $current_set = _batch_current_set(); - drupal_set_title($current_set['title'], PASS_THROUGH); - - // Merge required query parameters for batch processing into those provided by - // batch_set() or hook_batch_alter(). - $batch['url_options']['query']['id'] = $batch['id']; - - $js_setting = array( - 'batch' => array( - 'errorMessage' => $current_set['error_message'] . '
' . $batch['error_message'], - 'initMessage' => $current_set['init_message'], - 'uri' => url($batch['url'], $batch['url_options']), - ), - ); - drupal_add_js($js_setting, 'setting'); - drupal_add_library('system', 'drupal.batch'); - - return '
'; -} - /** * Does one execution pass with JavaScript and returns progress to the browser. * - * @see _batch_progress_page_js() * @see _batch_process() */ function _batch_do() { - // HTTP POST required. - if ($_SERVER['REQUEST_METHOD'] != 'POST') { - drupal_set_message(t('HTTP POST is required.'), 'error'); - drupal_set_title(t('Error')); - return ''; - } // Perform actual processing. list($percentage, $message) = _batch_process(); @@ -164,11 +109,11 @@ function _batch_do() { } /** - * Outputs a batch processing page without JavaScript support. + * Outputs a batch processing page. * * @see _batch_process() */ -function _batch_progress_page_nojs() { +function _batch_progress_page() { $batch = &batch_get(); $current_set = _batch_current_set(); @@ -216,6 +161,9 @@ function _batch_progress_page_nojs() { $url = url($batch['url'], $batch['url_options']); $element = array( + // Redirect through a 'Refresh' meta tag if JavaScript is disabled. + '#prefix' => '', '#tag' => 'meta', '#attributes' => array( 'http-equiv' => 'Refresh', @@ -224,6 +172,17 @@ function _batch_progress_page_nojs() { ); drupal_add_html_head($element, 'batch_progress_meta_refresh'); + // Adds JavaScript code and settings for clients where JavaScript is enabled. + $js_setting = array( + 'batch' => array( + 'errorMessage' => $current_set['error_message'] . '
' . $batch['error_message'], + 'initMessage' => $current_set['init_message'], + 'uri' => $url, + ), + ); + drupal_add_js($js_setting, 'setting'); + drupal_add_library('system', 'drupal.batch'); + return theme('progress_bar', array('percent' => $percentage, 'message' => $message)); } diff --git a/includes/bootstrap.inc b/includes/bootstrap.inc index bd81e7e594..6bf5ec52ad 100644 --- a/includes/bootstrap.inc +++ b/includes/bootstrap.inc @@ -8,7 +8,7 @@ /** * The current system version. */ -define('VERSION', '7.82'); +define('VERSION', '7.92'); /** * Core API compatibility. @@ -359,6 +359,7 @@ abstract class DrupalCacheArray implements ArrayAccess { /** * Implements ArrayAccess::offsetExists(). */ + #[\ReturnTypeWillChange] public function offsetExists($offset) { return $this->offsetGet($offset) !== NULL; } @@ -366,6 +367,7 @@ abstract class DrupalCacheArray implements ArrayAccess { /** * Implements ArrayAccess::offsetGet(). */ + #[\ReturnTypeWillChange] public function offsetGet($offset) { if (isset($this->storage[$offset]) || array_key_exists($offset, $this->storage)) { return $this->storage[$offset]; @@ -378,6 +380,7 @@ abstract class DrupalCacheArray implements ArrayAccess { /** * Implements ArrayAccess::offsetSet(). */ + #[\ReturnTypeWillChange] public function offsetSet($offset, $value) { $this->storage[$offset] = $value; } @@ -385,6 +388,7 @@ abstract class DrupalCacheArray implements ArrayAccess { /** * Implements ArrayAccess::offsetUnset(). */ + #[\ReturnTypeWillChange] public function offsetUnset($offset) { unset($this->storage[$offset]); } @@ -803,14 +807,17 @@ function drupal_settings_initialize() { // HTTP_HOST can be modified by a visitor, but we already sanitized it // in drupal_settings_initialize(). if (!empty($_SERVER['HTTP_HOST'])) { - $cookie_domain = $_SERVER['HTTP_HOST']; - // Strip leading periods, www., and port numbers from cookie domain. - $cookie_domain = ltrim($cookie_domain, '.'); - if (strpos($cookie_domain, 'www.') === 0) { - $cookie_domain = substr($cookie_domain, 4); - } - $cookie_domain = explode(':', $cookie_domain); - $cookie_domain = '.' . $cookie_domain[0]; + $cookie_domain = _drupal_get_cookie_domain($_SERVER['HTTP_HOST']); + } + + // Drupal 7.83 included a security improvement whereby www. is no longer + // stripped from the cookie domain. However, this can cause problems with + // existing session cookies where some users are left unable to login. In + // order to avoid that, prepend a leading dot to the session_name that was + // derived from the base_url when a www. subdomain is in use. + // @see https://www.drupal.org/project/drupal/issues/2522002 + if (strpos($session_name, 'www.') === 0) { + $session_name = '.' . $session_name; } } // Per RFC 2109, cookie domains must contain at least one dot other than the @@ -831,6 +838,24 @@ function drupal_settings_initialize() { session_name($prefix . substr(hash('sha256', $session_name), 0, 32)); } +/** + * Derive the cookie domain to use for session cookies. + * + * @param $host + * The value of the HTTP host name. + * + * @return + * The string to use as a cookie domain. + */ +function _drupal_get_cookie_domain($host) { + $cookie_domain = $host; + // Strip leading periods and port numbers from cookie domain. + $cookie_domain = ltrim($cookie_domain, '.'); + $cookie_domain = explode(':', $cookie_domain); + $cookie_domain = '.' . $cookie_domain[0]; + return $cookie_domain; +} + /** * Returns and optionally sets the filename for a system resource. * @@ -1157,6 +1182,31 @@ function _drupal_trigger_error_with_delayed_logging($error_msg, $error_type = E_ $delay_logging = FALSE; } +/** + * Invoke trigger_error() using a fatal error that will terminate the request. + * + * Normally, Drupal's error handler does not terminate script execution on + * user-level errors, even if the error is of type E_USER_ERROR. This function + * triggers an error of type E_USER_ERROR that is explicitly forced to be a + * fatal error which terminates script execution. + * + * @param string $error_msg + * The error message to trigger. As with trigger_error() itself, this is + * limited to 1024 bytes; additional characters beyond that will be removed. + * + * @see _drupal_error_handler_real() + */ +function drupal_trigger_fatal_error($error_msg) { + $fatal_error = &drupal_static(__FUNCTION__, FALSE); + $fatal_error = TRUE; + trigger_error($error_msg, E_USER_ERROR); + $fatal_error = FALSE; + // The standard Drupal error handler should have treated this as a fatal + // error and already ended the page request. But in case another error + // handler is being used, terminate execution explicitly here also. + exit; +} + /** * Writes the file scan cache to the persistent cache. * @@ -1552,7 +1602,7 @@ function drupal_page_header() { */ function drupal_serve_page_from_cache(stdClass $cache) { // Negotiate whether to use compression. - $page_compression = !empty($cache->data['page_compressed']); + $page_compression = !empty($cache->data['page_compressed']) && !empty($cache->data['body']); $return_compressed = $page_compression && isset($_SERVER['HTTP_ACCEPT_ENCODING']) && strpos($_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip') !== FALSE; // Get headers set in hook_boot(). Keys are lower-case. @@ -1855,7 +1905,7 @@ function format_string($string, array $args = array()) { * @ingroup sanitization */ function check_plain($text) { - return htmlspecialchars($text, ENT_QUOTES, 'UTF-8'); + return htmlspecialchars((string) $text, ENT_QUOTES, 'UTF-8'); } /** @@ -1883,7 +1933,7 @@ function check_plain($text) { * TRUE if the text is valid UTF-8, FALSE if not. */ function drupal_validate_utf8($text) { - if (strlen($text) == 0) { + if (strlen((string) $text) == 0) { return TRUE; } // With the PCRE_UTF8 modifier 'u', preg_match() fails silently on strings @@ -2265,7 +2315,7 @@ function drupal_random_bytes($count) { // $random_state does not use drupal_static as it stores random bytes. static $random_state, $bytes, $has_openssl; - $missing_bytes = $count - strlen($bytes); + $missing_bytes = $count - strlen((string) $bytes); if ($missing_bytes > 0) { // PHP versions prior 5.3.4 experienced openssl_random_pseudo_bytes() @@ -2298,7 +2348,7 @@ function drupal_random_bytes($count) { // the microtime() - is prepended rather than appended. This is to avoid // directly leaking $random_state via the $output stream, which could // allow for trivial prediction of further "random" numbers. - if (strlen($bytes) < $count) { + if (strlen((string) $bytes) < $count) { // Initialize on the first call. The contents of $_SERVER includes a mix of // user-specific and system information that varies a little with each page. if (!isset($random_state)) { diff --git a/includes/common.inc b/includes/common.inc index 2ca4fa06fd..0f6ced800d 100644 --- a/includes/common.inc +++ b/includes/common.inc @@ -633,7 +633,7 @@ function drupal_parse_url($url) { * The Drupal path to encode. */ function drupal_encode_path($path) { - return str_replace('%2F', '/', rawurlencode($path)); + return str_replace('%2F', '/', rawurlencode((string) $path)); } /** @@ -943,7 +943,7 @@ function drupal_http_request($url, array $options = array()) { // or PUT request. Some non-standard servers get confused by Content-Length in // at least HEAD/GET requests, and Squid always requires Content-Length in // POST/PUT requests. - $content_length = strlen($options['data']); + $content_length = strlen((string) $options['data']); if ($content_length > 0 || $options['method'] == 'POST' || $options['method'] == 'PUT') { $options['headers']['Content-Length'] = $content_length; } @@ -1019,7 +1019,7 @@ function drupal_http_request($url, array $options = array()) { $result->headers = array(); // Parse the response headers. - while ($line = trim(array_shift($response))) { + while ($line = trim((string) array_shift($response))) { list($name, $value) = explode(':', $line, 2); $name = strtolower($name); if (isset($result->headers[$name]) && $name == 'set-cookie') { @@ -1104,6 +1104,14 @@ function drupal_http_request($url, array $options = array()) { // Redirect to the new location. $options['max_redirects']--; + // Check if we need to remove any potentially sensitive headers before + // following the redirect. + // @see https://www.rfc-editor.org/rfc/rfc9110.html#name-redirection-3xx + if (_drupal_should_strip_sensitive_headers_on_http_redirect($url, $location)) { + unset($options['headers']['Cookie']); + unset($options['headers']['Authorization']); + } + // We need to unset the 'Host' header // as we are redirecting to a new location. unset($options['headers']['Host']); @@ -1122,6 +1130,36 @@ function drupal_http_request($url, array $options = array()) { return $result; } +/** + * Determine whether to strip sensitive headers from a request when redirected. + * + * @param string $url + * The url from the original outbound http request. + * + * @param string $location + * The location to which the request has been redirected. + * + * @return boolean + * Whether sensitive headers should be stripped from the request before + * following the redirect. + */ +function _drupal_should_strip_sensitive_headers_on_http_redirect($url, $location) { + $url_parsed = parse_url($url); + $location_parsed = parse_url($location); + if (!isset($location_parsed['host'])) { + return FALSE; + } + $strip_on_host_change = variable_get('drupal_http_request_strip_sensitive_headers_on_host_change', TRUE); + $strip_on_https_downgrade = variable_get('drupal_http_request_strip_sensitive_headers_on_https_downgrade', TRUE); + if ($strip_on_host_change && strcasecmp($url_parsed['host'], $location_parsed['host']) !== 0) { + return TRUE; + } + if ($strip_on_https_downgrade && $url_parsed['scheme'] !== $location_parsed['scheme'] && 'https' !== $location_parsed['scheme']) { + return TRUE; + } + return FALSE; +} + /** * Splits an HTTP response status line into components. * @@ -1500,7 +1538,7 @@ function filter_xss($string, $allowed_tags = array('a', 'em', 'strong', 'cite', // Store the text format. _filter_xss_split($allowed_tags, TRUE); // Remove NULL characters (ignored by some browsers). - $string = str_replace(chr(0), '', $string); + $string = str_replace(chr(0), '', (string) $string); // Remove Netscape 4 JS entities. $string = preg_replace('%&\s*\{[^}]*(\}\s*;?|$)%', '', $string); @@ -1916,7 +1954,7 @@ function format_plural($count, $singular, $plural, array $args = array(), array */ function parse_size($size) { $unit = preg_replace('/[^bkmgtpezy]/i', '', $size); // Remove the non-unit characters from the size. - $size = preg_replace('/[^0-9\.]/', '', $size); // Remove the non-numeric characters from the size. + $size = (float) preg_replace('/[^0-9\.]/', '', $size); // Remove the non-numeric characters from the size. if ($unit) { // Find the position of the unit in the ordered string which is the power of magnitude to multiply a kilobyte by. return round($size * pow(DRUPAL_KILOBYTE, stripos('bkmgtpezy', $unit[0]))); @@ -1995,7 +2033,7 @@ function format_interval($interval, $granularity = 2, $langcode = NULL) { $key = explode('|', $key); if ($interval >= $value) { $output .= ($output ? ' ' : '') . format_plural(floor($interval / $value), $key[0], $key[1], array(), array('langcode' => $langcode)); - $interval %= $value; + $interval = (int) $interval % $value; $granularity--; } @@ -2308,7 +2346,7 @@ function url($path = NULL, array $options = array()) { // Strip leading slashes from internal paths to prevent them becoming external // URLs without protocol. /example.com should not be turned into // //example.com. - $path = ltrim($path, '/'); + $path = ltrim((string) $path, '/'); global $base_url, $base_secure_url, $base_insecure_url; @@ -2346,7 +2384,7 @@ function url($path = NULL, array $options = array()) { } $base = $options['absolute'] ? $options['base_url'] . '/' : base_path(); - $prefix = empty($path) ? rtrim($options['prefix'], '/') : $options['prefix']; + $prefix = empty($path) ? rtrim((string) $options['prefix'], '/') : $options['prefix']; // With Clean URLs. if (!empty($GLOBALS['conf']['clean_url'])) { @@ -2390,6 +2428,7 @@ function url($path = NULL, array $options = array()) { * Boolean TRUE or FALSE, where TRUE indicates an external path. */ function url_is_external($path) { + $path = (string) $path; $colonpos = strpos($path, ':'); // Some browsers treat \ as / so normalize to forward slashes. $path = str_replace('\\', '/', $path); @@ -2569,6 +2608,7 @@ function l($text, $path, array $options = array()) { $use_theme = FALSE; } } + $path = drupal_strip_dangerous_protocols((string) $path); if ($use_theme) { return theme('link', array('text' => $text, 'path' => $path, 'options' => $options)); } @@ -2695,6 +2735,7 @@ function drupal_deliver_html_page($page_callback_result) { if ($frame_options && is_null(drupal_get_http_header('X-Frame-Options'))) { drupal_add_http_header('X-Frame-Options', $frame_options); } + drupal_add_http_header('X-Content-Type-Options', 'nosniff'); if (variable_get('block_interest_cohort', TRUE)) { $permissions_policy = drupal_get_http_header('Permissions-Policy'); @@ -2944,12 +2985,12 @@ function base_path() { } /** - * Adds a LINK tag with a distinct 'rel' attribute to the page's HEAD. + * Adds a LINK tag with distinct attributes to the page's HEAD. * * This function can be called as long the HTML header hasn't been sent, which * on normal pages is up through the preprocess step of theme('html'). Adding - * a link will overwrite a prior link with the exact same 'rel' and 'href' - * attributes. + * a link will overwrite a prior link with the exact same 'rel', 'href' and + * 'hreflang' attributes. * * @param $attributes * Associative array of element attributes including 'href' and 'rel'. @@ -2965,12 +3006,12 @@ function drupal_add_html_head_link($attributes, $header = FALSE) { if ($header) { // Also add a HTTP header "Link:". - $href = '<' . check_plain($attributes['href']) . '>;'; + $href = '<' . $attributes['href'] . '>;'; unset($attributes['href']); $element['#attached']['drupal_add_http_header'][] = array('Link', $href . drupal_http_header_attributes($attributes), TRUE); } - drupal_add_html_head($element, 'drupal_add_html_head_link:' . $attributes['rel'] . ':' . $href); + drupal_add_html_head($element, 'drupal_add_html_head_link:' . $attributes['rel'] . ':' . (isset($attributes['hreflang']) ? "{$attributes['hreflang']}:" : '') . $href); } /** @@ -4342,6 +4383,7 @@ function drupal_add_js($data = NULL, $options = NULL) { 'data' => array( array('basePath' => base_path()), array('pathPrefix' => empty($prefix) ? '' : $prefix), + array('setHasJsCookie' => variable_get('set_has_js_cookie', FALSE) ? 1 : 0), ), 'type' => 'setting', 'scope' => 'header', @@ -6085,7 +6127,7 @@ function drupal_render_page($page) { */ function drupal_render(&$elements) { // Early-return nothing if user does not have access. - if (empty($elements) || (isset($elements['#access']) && !$elements['#access'])) { + if (empty($elements) || !is_array($elements) || (isset($elements['#access']) && !$elements['#access'])) { return ''; } @@ -6641,7 +6683,7 @@ function drupal_sort_title($a, $b) { * Checks if the key is a property. */ function element_property($key) { - return $key[0] == '#'; + return $key !== '' && is_string($key) && $key[0] == '#'; } /** @@ -8032,8 +8074,14 @@ function entity_extract_ids($entity_type, $entity) { $info = entity_get_info($entity_type); // Objects being created might not have id/vid yet. - $id = isset($entity->{$info['entity keys']['id']}) ? $entity->{$info['entity keys']['id']} : NULL; - $vid = ($info['entity keys']['revision'] && isset($entity->{$info['entity keys']['revision']})) ? $entity->{$info['entity keys']['revision']} : NULL; + if (!empty($info)) { + $id = isset($entity->{$info['entity keys']['id']}) ? $entity->{$info['entity keys']['id']} : NULL; + $vid = ($info['entity keys']['revision'] && isset($entity->{$info['entity keys']['revision']})) ? $entity->{$info['entity keys']['revision']} : NULL; + } + else { + $id = NULL; + $vid = NULL; + } if (!empty($info['entity keys']['bundle'])) { // Explicitly fail for malformed entities missing the bundle property. diff --git a/includes/database/database.inc b/includes/database/database.inc index 61ac44f783..36999e7e80 100644 --- a/includes/database/database.inc +++ b/includes/database/database.inc @@ -1924,6 +1924,11 @@ class DatabaseTransactionOutOfOrderException extends Exception { } */ class InvalidMergeQueryException extends Exception {} +/** + * Exception thrown if an invalid query condition is specified. + */ +class InvalidQueryConditionOperatorException extends Exception {} + /** * Exception thrown if an insert query specifies a field twice. * @@ -2257,6 +2262,7 @@ class DatabaseStatementBase extends PDOStatement implements DatabaseStatementInt $this->setFetchMode(PDO::FETCH_OBJ); } + #[\ReturnTypeWillChange] public function execute($args = array(), $options = array()) { if (isset($options['fetch'])) { if (is_string($options['fetch'])) { @@ -2394,23 +2400,27 @@ class DatabaseStatementEmpty implements Iterator, DatabaseStatementInterface { } /* Implementations of Iterator. */ - + #[\ReturnTypeWillChange] public function current() { return NULL; } + #[\ReturnTypeWillChange] public function key() { return NULL; } + #[\ReturnTypeWillChange] public function rewind() { // Nothing to do: our DatabaseStatement can't be rewound. } + #[\ReturnTypeWillChange] public function next() { // Do nothing, since this is an always-empty implementation. } + #[\ReturnTypeWillChange] public function valid() { return FALSE; } diff --git a/includes/database/mysql/database.inc b/includes/database/mysql/database.inc index b836111987..8bb88d24f3 100644 --- a/includes/database/mysql/database.inc +++ b/includes/database/mysql/database.inc @@ -332,6 +332,11 @@ class DatabaseConnection_mysql extends DatabaseConnection { PDO::MYSQL_ATTR_USE_BUFFERED_QUERY => TRUE, // Because MySQL's prepared statements skip the query cache, because it's dumb. PDO::ATTR_EMULATE_PREPARES => TRUE, + // Convert numeric values to strings when fetching. In PHP 8.1, + // PDO::ATTR_EMULATE_PREPARES now behaves the same way as non emulated + // prepares and returns integers. See https://externals.io/message/113294 + // for further discussion. + PDO::ATTR_STRINGIFY_FETCHES => TRUE, ); if (defined('PDO::MYSQL_ATTR_MULTI_STATEMENTS')) { // An added connection option in PHP 5.5.21+ to optionally limit SQL to a diff --git a/includes/database/pgsql/database.inc b/includes/database/pgsql/database.inc index 96ffc1d3e4..7aa69a92a4 100644 --- a/includes/database/pgsql/database.inc +++ b/includes/database/pgsql/database.inc @@ -117,7 +117,8 @@ class DatabaseConnection_pgsql extends DatabaseConnection { case Database::RETURN_AFFECTED: return $stmt->rowCount(); case Database::RETURN_INSERT_ID: - return $this->connection->lastInsertId($options['sequence_name']); + $sequence_name = isset($options['sequence_name']) ? $options['sequence_name'] : NULL; + return $this->connection->lastInsertId($sequence_name); case Database::RETURN_NULL: return; default: diff --git a/includes/database/pgsql/query.inc b/includes/database/pgsql/query.inc index 9902b1643a..e2d587d5b7 100644 --- a/includes/database/pgsql/query.inc +++ b/includes/database/pgsql/query.inc @@ -30,7 +30,7 @@ class InsertQuery_pgsql extends InsertQuery { foreach ($this->insertFields as $idx => $field) { if (isset($table_information->blob_fields[$field])) { $blobs[$blob_count] = fopen('php://memory', 'a'); - fwrite($blobs[$blob_count], $insert_values[$idx]); + fwrite($blobs[$blob_count], (string) $insert_values[$idx]); rewind($blobs[$blob_count]); $stmt->bindParam(':db_insert_placeholder_' . $max_placeholder++, $blobs[$blob_count], PDO::PARAM_LOB); @@ -182,7 +182,7 @@ class UpdateQuery_pgsql extends UpdateQuery { if (isset($table_information->blob_fields[$field])) { $blobs[$blob_count] = fopen('php://memory', 'a'); - fwrite($blobs[$blob_count], $value); + fwrite($blobs[$blob_count], (string) $value); rewind($blobs[$blob_count]); $stmt->bindParam($placeholder, $blobs[$blob_count], PDO::PARAM_LOB); ++$blob_count; diff --git a/includes/database/pgsql/schema.inc b/includes/database/pgsql/schema.inc index f5774de1c9..8d1b388c11 100644 --- a/includes/database/pgsql/schema.inc +++ b/includes/database/pgsql/schema.inc @@ -12,6 +12,13 @@ class DatabaseSchema_pgsql extends DatabaseSchema { + /** + * PostgreSQL's temporary namespace name. + * + * @var string + */ + protected $tempNamespaceName; + /** * A cache of information about blob columns and sequences of tables. * @@ -23,6 +30,64 @@ class DatabaseSchema_pgsql extends DatabaseSchema { */ protected $tableInformation = array(); + /** + * The maximum allowed length for index, primary key and constraint names. + * + * Value will usually be set to a 63 chars limit but PostgreSQL allows + * to higher this value before compiling, so we need to check for that. + * + * @var int + */ + protected $maxIdentifierLength; + + /** + * Make sure to limit identifiers according to PostgreSQL compiled in length. + * + * PostgreSQL allows in standard configuration identifiers no longer than 63 + * chars for table/relation names, indexes, primary keys, and constraints. So + * we map all identifiers that are too long to drupal_base64hash_tag, where + * tag is one of: + * - idx for indexes + * - key for constraints + * - pkey for primary keys + * - seq for sequences + * + * @param string $table_identifier_part + * The first argument used to build the identifier string. This usually + * refers to a table/relation name. + * @param string $column_identifier_part + * The second argument used to build the identifier string. This usually + * refers to one or more column names. + * @param string $tag + * The identifier tag. It can be one of 'idx', 'key', 'pkey' or 'seq'. + * + * @return string + * The index/constraint/pkey identifier. + */ + protected function ensureIdentifiersLength($table_identifier_part, $column_identifier_part, $tag) { + $info = $this->getPrefixInfo($table_identifier_part); + $table_identifier_part = $info['table']; + + // Filters out potentially empty $column_identifier_part to ensure + // compatibility with old naming convention (see prefixNonTable()). + $identifiers = array_filter(array($table_identifier_part, $column_identifier_part, $tag)); + $identifierName = implode('_', $identifiers); + + // Retrieve the max identifier length which is usually 63 characters + // but can be altered before PostgreSQL is compiled so we need to check. + if (empty($this->maxIdentifierLength)) { + $this->maxIdentifierLength = $this->connection->query("SHOW max_identifier_length")->fetchField(); + } + + if (strlen($identifierName) > $this->maxIdentifierLength) { + $saveIdentifier = 'drupal_' . $this->hashBase64($identifierName) . '_' . $tag; + } + else { + $saveIdentifier = $identifierName; + } + return $saveIdentifier; + } + /** * Fetch the list of blobs and sequences used on a table. * @@ -39,23 +104,47 @@ class DatabaseSchema_pgsql extends DatabaseSchema { public function queryTableInformation($table) { // Generate a key to reference this table's information on. $key = $this->connection->prefixTables('{' . $table . '}'); - if (!strpos($key, '.')) { + + // Take into account that temporary tables are stored in a different schema. + // \DatabaseConnection::generateTemporaryTableName() sets 'db_temporary_' + // prefix to all temporary tables. + if (strpos($key, '.') === FALSE && strpos($table, 'db_temporary_') === FALSE) { $key = 'public.' . $key; } + else { + $key = $this->getTempNamespaceName() . '.' . $key; + } if (!isset($this->tableInformation[$key])) { - // Split the key into schema and table for querying. - list($schema, $table_name) = explode('.', $key); $table_information = (object) array( 'blob_fields' => array(), 'sequences' => array(), ); - // Don't use {} around information_schema.columns table. - $result = $this->connection->query("SELECT column_name, data_type, column_default FROM information_schema.columns WHERE table_schema = :schema AND table_name = :table AND (data_type = 'bytea' OR (numeric_precision IS NOT NULL AND column_default LIKE :default))", array( - ':schema' => $schema, - ':table' => $table_name, - ':default' => '%nextval%', + + // The bytea columns and sequences for a table can be found in + // pg_attribute, which is significantly faster than querying the + // information_schema. The data type of a field can be found by lookup + // of the attribute ID, and the default value must be extracted from the + // node tree for the attribute definition instead of the historical + // human-readable column, adsrc. + $sql = <<<'EOD' +SELECT pg_attribute.attname AS column_name, format_type(pg_attribute.atttypid, pg_attribute.atttypmod) AS data_type, pg_get_expr(pg_attrdef.adbin, pg_attribute.attrelid) AS column_default +FROM pg_attribute +LEFT JOIN pg_attrdef ON pg_attrdef.adrelid = pg_attribute.attrelid AND pg_attrdef.adnum = pg_attribute.attnum +WHERE pg_attribute.attnum > 0 +AND NOT pg_attribute.attisdropped +AND pg_attribute.attrelid = :key::regclass +AND (format_type(pg_attribute.atttypid, pg_attribute.atttypmod) = 'bytea' +OR pg_get_expr(pg_attrdef.adbin, pg_attribute.attrelid) LIKE 'nextval%') +EOD; + $result = $this->connection->query($sql, array( + ':key' => $key, )); + + if (empty($result)) { + return $table_information; + } + foreach ($result as $column) { if ($column->data_type == 'bytea') { $table_information->blob_fields[$column->column_name] = TRUE; @@ -73,6 +162,19 @@ class DatabaseSchema_pgsql extends DatabaseSchema { return $this->tableInformation[$key]; } + /** + * Gets PostgreSQL's temporary namespace name. + * + * @return string + * PostgreSQL's temporary namespace anme. + */ + protected function getTempNamespaceName() { + if (!isset($this->tempNamespaceName)) { + $this->tempNamespaceName = $this->connection->query('SELECT nspname FROM pg_namespace WHERE oid = pg_my_temp_schema()')->fetchField(); + } + return $this->tempNamespaceName; + } + /** * Fetch the list of CHECK constraints used on a field. * @@ -124,11 +226,11 @@ class DatabaseSchema_pgsql extends DatabaseSchema { $sql_keys = array(); if (isset($table['primary key']) && is_array($table['primary key'])) { - $sql_keys[] = 'PRIMARY KEY (' . implode(', ', $table['primary key']) . ')'; + $sql_keys[] = 'CONSTRAINT ' . $this->ensureIdentifiersLength($name, '', 'pkey') . ' PRIMARY KEY (' . implode(', ', $table['primary key']) . ')'; } if (isset($table['unique keys']) && is_array($table['unique keys'])) { foreach ($table['unique keys'] as $key_name => $key) { - $sql_keys[] = 'CONSTRAINT ' . $this->prefixNonTable($name, $key_name, 'key') . ' UNIQUE (' . implode(', ', $key) . ')'; + $sql_keys[] = 'CONSTRAINT ' . $this->ensureIdentifiersLength($name, $key_name, 'key') . ' UNIQUE (' . implode(', ', $key) . ')'; } } @@ -312,6 +414,68 @@ class DatabaseSchema_pgsql extends DatabaseSchema { return implode(', ', $return); } + /** + * {@inheritdoc} + */ + public function tableExists($table) { + // In PostgreSQL "unquoted names are always folded to lower case." + // @see DatabaseSchema_pgsql::buildTableNameCondition(). + $prefixInfo = $this->getPrefixInfo(strtolower($table), TRUE); + + return (bool) $this->connection->query("SELECT 1 FROM pg_tables WHERE schemaname = :schema AND tablename = :table", array(':schema' => $prefixInfo['schema'], ':table' => $prefixInfo['table']))->fetchField(); + } + + /** + * {@inheritdoc} + */ + public function findTables($table_expression) { + $individually_prefixed_tables = $this->connection->getUnprefixedTablesMap(); + $default_prefix = $this->connection->tablePrefix(); + $default_prefix_length = strlen($default_prefix); + $tables = array(); + + // Load all the tables up front in order to take into account per-table + // prefixes. The actual matching is done at the bottom of the method. + $results = $this->connection->query("SELECT tablename FROM pg_tables WHERE schemaname = :schema", array(':schema' => $this->defaultSchema)); + foreach ($results as $table) { + // Take into account tables that have an individual prefix. + if (isset($individually_prefixed_tables[$table->tablename])) { + $prefix_length = strlen($this->connection->tablePrefix($individually_prefixed_tables[$table->tablename])); + } + elseif ($default_prefix && substr($table->tablename, 0, $default_prefix_length) !== $default_prefix) { + // This table name does not start the default prefix, which means that + // it is not managed by Drupal so it should be excluded from the result. + continue; + } + else { + $prefix_length = $default_prefix_length; + } + + // Remove the prefix from the returned tables. + $unprefixed_table_name = substr($table->tablename, $prefix_length); + + // The pattern can match a table which is the same as the prefix. That + // will become an empty string when we remove the prefix, which will + // probably surprise the caller, besides not being a prefixed table. So + // remove it. + if (!empty($unprefixed_table_name)) { + $tables[$unprefixed_table_name] = $unprefixed_table_name; + } + } + + // Need to use strtolower on the table name as it was used previously by + // DatabaseSchema_pgsql::buildTableNameCondition(). + // @see https://www.drupal.org/project/drupal/issues/3262341 + $table_expression = strtolower($table_expression); + + // Convert the table expression from its SQL LIKE syntax to a regular + // expression and escape the delimiter that will be used for matching. + $table_expression = str_replace(array('%', '_'), array('.*?', '.'), preg_quote($table_expression, '/')); + $tables = preg_grep('/^' . $table_expression . '$/i', $tables); + + return $tables; + } + function renameTable($table, $new_name) { if (!$this->tableExists($table)) { throw new DatabaseSchemaObjectDoesNotExistException(t("Cannot rename @table to @table_new: table @table doesn't exist.", array('@table' => $table, '@table_new' => $new_name))); @@ -328,10 +492,31 @@ class DatabaseSchema_pgsql extends DatabaseSchema { // rename them when renaming the table. $indexes = $this->connection->query('SELECT indexname FROM pg_indexes WHERE schemaname = :schema AND tablename = :table', array(':schema' => $old_schema, ':table' => $old_table_name)); foreach ($indexes as $index) { - if (preg_match('/^' . preg_quote($old_full_name) . '_(.*)$/', $index->indexname, $matches)) { + // Get the index type by suffix, e.g. idx/key/pkey + $index_type = substr($index->indexname, strrpos($index->indexname, '_') + 1); + + // If the index is already rewritten by ensureIdentifiersLength() to not + // exceed the 63 chars limit of PostgreSQL, we need to take care of that. + // Example (drupal_Gk7Su_T1jcBHVuvSPeP22_I3Ni4GrVEgTYlIYnBJkro_idx). + if (strpos($index->indexname, 'drupal_') !== FALSE) { + preg_match('/^drupal_(.*)_' . preg_quote($index_type) . '/', $index->indexname, $matches); $index_name = $matches[1]; - $this->connection->query('ALTER INDEX ' . $index->indexname . ' RENAME TO {' . $new_name . '}_' . $index_name); } + else { + if ($index_type == 'pkey') { + // Primary keys do not have a specific name in D7. + $index_name = ''; + } + else { + // Make sure to remove the suffix from index names, because + // ensureIdentifiersLength() will add the suffix again and thus + // would result in a wrong index name. + preg_match('/^' . preg_quote($old_full_name) . '_(.*)_' . preg_quote($index_type) . '/', $index->indexname, $matches); + $index_name = $matches[1]; + } + } + + $this->connection->query('ALTER INDEX ' . $index->indexname . ' RENAME TO ' . $this->ensureIdentifiersLength($new_name, $index_name, $index_type)); } // Now rename the table. @@ -414,9 +599,20 @@ class DatabaseSchema_pgsql extends DatabaseSchema { $this->connection->query('ALTER TABLE {' . $table . '} ALTER COLUMN "' . $field . '" DROP DEFAULT'); } + /** + * {@inheritdoc} + */ + public function fieldExists($table, $column) { + // In PostgreSQL "unquoted names are always folded to lower case." + // @see DatabaseSchema_pgsql::buildTableNameCondition(). + $prefixInfo = $this->getPrefixInfo(strtolower($table)); + + return (bool) $this->connection->query("SELECT 1 FROM pg_attribute WHERE attrelid = :key::regclass AND attname = :column AND NOT attisdropped AND attnum > 0", array(':key' => $prefixInfo['schema'] . '.' . $prefixInfo['table'], ':column' => $column))->fetchField(); + } + public function indexExists($table, $name) { - // Details http://www.postgresql.org/docs/8.3/interactive/view-pg-indexes.html - $index_name = '{' . $table . '}_' . $name . '_idx'; + // Details https://www.postgresql.org/docs/10/view-pg-indexes.html + $index_name = $this->ensureIdentifiersLength($table, $name, 'idx'); return (bool) $this->connection->query("SELECT 1 FROM pg_indexes WHERE indexname = '$index_name'")->fetchField(); } @@ -429,7 +625,18 @@ class DatabaseSchema_pgsql extends DatabaseSchema { * The name of the constraint (typically 'pkey' or '[constraint]_key'). */ protected function constraintExists($table, $name) { - $constraint_name = '{' . $table . '}_' . $name; + // ensureIdentifiersLength() expects three parameters, thus we split our + // constraint name in a proper name and a suffix. + if ($name == 'pkey') { + $suffix = $name; + $name = ''; + } + else { + $pos = strrpos($name, '_'); + $suffix = substr($name, $pos + 1); + $name = substr($name, 0, $pos); + } + $constraint_name = $this->ensureIdentifiersLength($table, $name, $suffix); return (bool) $this->connection->query("SELECT 1 FROM pg_constraint WHERE conname = '$constraint_name'")->fetchField(); } @@ -441,7 +648,7 @@ class DatabaseSchema_pgsql extends DatabaseSchema { throw new DatabaseSchemaObjectExistsException(t("Cannot add primary key to table @table: primary key already exists.", array('@table' => $table))); } - $this->connection->query('ALTER TABLE {' . $table . '} ADD PRIMARY KEY (' . implode(',', $fields) . ')'); + $this->connection->query('ALTER TABLE {' . $table . '} ADD CONSTRAINT ' . $this->ensureIdentifiersLength($table, '', 'pkey') . ' PRIMARY KEY (' . implode(',', $fields) . ')'); } public function dropPrimaryKey($table) { @@ -449,7 +656,7 @@ class DatabaseSchema_pgsql extends DatabaseSchema { return FALSE; } - $this->connection->query('ALTER TABLE {' . $table . '} DROP CONSTRAINT ' . $this->prefixNonTable($table, 'pkey')); + $this->connection->query('ALTER TABLE {' . $table . '} DROP CONSTRAINT ' . $this->ensureIdentifiersLength($table, '', 'pkey')); return TRUE; } @@ -461,7 +668,7 @@ class DatabaseSchema_pgsql extends DatabaseSchema { throw new DatabaseSchemaObjectExistsException(t("Cannot add unique key @name to table @table: unique key already exists.", array('@table' => $table, '@name' => $name))); } - $this->connection->query('ALTER TABLE {' . $table . '} ADD CONSTRAINT "' . $this->prefixNonTable($table, $name, 'key') . '" UNIQUE (' . implode(',', $fields) . ')'); + $this->connection->query('ALTER TABLE {' . $table . '} ADD CONSTRAINT "' . $this->ensureIdentifiersLength($table, $name, 'key') . '" UNIQUE (' . implode(',', $fields) . ')'); } public function dropUniqueKey($table, $name) { @@ -469,7 +676,7 @@ class DatabaseSchema_pgsql extends DatabaseSchema { return FALSE; } - $this->connection->query('ALTER TABLE {' . $table . '} DROP CONSTRAINT "' . $this->prefixNonTable($table, $name, 'key') . '"'); + $this->connection->query('ALTER TABLE {' . $table . '} DROP CONSTRAINT "' . $this->ensureIdentifiersLength($table, $name, 'key') . '"'); return TRUE; } @@ -489,7 +696,7 @@ class DatabaseSchema_pgsql extends DatabaseSchema { return FALSE; } - $this->connection->query('DROP INDEX ' . $this->prefixNonTable($table, $name, 'idx')); + $this->connection->query('DROP INDEX ' . $this->ensureIdentifiersLength($table, $name, 'idx')); return TRUE; } @@ -580,7 +787,7 @@ class DatabaseSchema_pgsql extends DatabaseSchema { } protected function _createIndexSql($table, $name, $fields) { - $query = 'CREATE INDEX "' . $this->prefixNonTable($table, $name, 'idx') . '" ON {' . $table . '} ('; + $query = 'CREATE INDEX "' . $this->ensureIdentifiersLength($table, $name, 'idx') . '" ON {' . $table . '} ('; $query .= $this->_createKeySql($fields) . ')'; return $query; } @@ -614,4 +821,36 @@ class DatabaseSchema_pgsql extends DatabaseSchema { return $this->connection->query('SELECT obj_description(oid, ?) FROM pg_class WHERE relname = ?', array('pg_class', $info['table']))->fetchField(); } } + + /** + * Calculates a base-64 encoded, PostgreSQL-safe sha-256 hash per PostgreSQL + * documentation: 4.1. Lexical Structure. + * + * @param $data + * String to be hashed. + * + * @return string + * A base-64 encoded sha-256 hash, with + and / replaced with _ and any = + * padding characters removed. + */ + protected function hashBase64($data) { + // Ensure lowercase as D7's pgsql driver does not quote identifiers + // consistently, and they are therefore folded to lowercase by PostgreSQL. + $hash = strtolower(base64_encode(hash('sha256', $data, TRUE))); + // Modify the hash so it's safe to use in PostgreSQL identifiers. + return strtr($hash, array('+' => '_', '/' => '_', '=' => '')); + } + + /** + * Build a condition to match a table name against a standard information_schema. + * + * In PostgreSQL "unquoted names are always folded to lower case." The pgsql + * driver does not quote table names, so they are therefore always lowercase. + * + * @see https://www.postgresql.org/docs/14/sql-syntax-lexical.html + */ + protected function buildTableNameCondition($table_name, $operator = '=', $add_prefix = TRUE) { + return parent::buildTableNameCondition(strtolower($table_name), $operator, $add_prefix); + } + } diff --git a/includes/database/prefetch.inc b/includes/database/prefetch.inc index 3b36a4e102..2a0bd55ce1 100644 --- a/includes/database/prefetch.inc +++ b/includes/database/prefetch.inc @@ -268,6 +268,7 @@ class DatabaseStatementPrefetch implements Iterator, DatabaseStatementInterface * @return * The current row formatted as requested. */ + #[\ReturnTypeWillChange] public function current() { if (isset($this->currentRow)) { switch ($this->fetchStyle) { @@ -285,7 +286,7 @@ class DatabaseStatementPrefetch implements Iterator, DatabaseStatementInterface case PDO::FETCH_OBJ: return (object) $this->currentRow; case PDO::FETCH_CLASS | PDO::FETCH_CLASSTYPE: - $class_name = array_unshift($this->currentRow); + $class_name = array_shift($this->currentRow); // Deliberate no break. case PDO::FETCH_CLASS: if (!isset($class_name)) { @@ -320,14 +321,17 @@ class DatabaseStatementPrefetch implements Iterator, DatabaseStatementInterface /* Implementations of Iterator. */ + #[\ReturnTypeWillChange] public function key() { return $this->currentKey; } + #[\ReturnTypeWillChange] public function rewind() { // Nothing to do: our DatabaseStatement can't be rewound. } + #[\ReturnTypeWillChange] public function next() { if (!empty($this->data)) { $this->currentRow = reset($this->data); @@ -339,6 +343,7 @@ class DatabaseStatementPrefetch implements Iterator, DatabaseStatementInterface } } + #[\ReturnTypeWillChange] public function valid() { return isset($this->currentRow); } diff --git a/includes/database/query.inc b/includes/database/query.inc index 048c8a2654..7346fbc0bd 100644 --- a/includes/database/query.inc +++ b/includes/database/query.inc @@ -871,8 +871,14 @@ class DeleteQuery extends Query implements QueryConditionInterface { $query = $comments . 'DELETE FROM {' . $this->connection->escapeTable($this->table) . '} '; if (count($this->condition)) { - - $this->condition->compile($this->connection, $this); + try { + $this->condition->compile($this->connection, $this); + } + // PHP does not allow exceptions to be thrown in __toString(), so trigger + // a fatal error instead. + catch (InvalidQueryConditionOperatorException $e) { + drupal_trigger_fatal_error($e->getMessage()); + } $query .= "\nWHERE " . $this->condition; } @@ -1204,7 +1210,14 @@ class UpdateQuery extends Query implements QueryConditionInterface { $query = $comments . 'UPDATE {' . $this->connection->escapeTable($this->table) . '} SET ' . implode(', ', $update_fields); if (count($this->condition)) { - $this->condition->compile($this->connection, $this); + try { + $this->condition->compile($this->connection, $this); + } + // PHP does not allow exceptions to be thrown in __toString(), so trigger + // a fatal error instead. + catch (InvalidQueryConditionOperatorException $e) { + drupal_trigger_fatal_error($e->getMessage()); + } // There is an implicit string cast on $this->condition. $query .= "\nWHERE " . $this->condition; } @@ -1697,6 +1710,7 @@ class DatabaseCondition implements QueryConditionInterface, Countable { * size of its conditional array minus one, because one element is the * conjunction. */ + #[\ReturnTypeWillChange] public function count() { return count($this->conditions) - 1; } @@ -1789,6 +1803,8 @@ class DatabaseCondition implements QueryConditionInterface, Countable { /** * Implements QueryConditionInterface::compile(). + * + * @throws InvalidQueryConditionOperatorException */ public function compile(DatabaseConnection $connection, QueryPlaceholderInterface $queryPlaceholder) { // Re-compile if this condition changed or if we are compiled against a @@ -1819,6 +1835,12 @@ class DatabaseCondition implements QueryConditionInterface, Countable { $arguments += $condition['field']->arguments(); } else { + // If the operator contains an invalid character, throw an + // exception to protect against SQL injection attempts. + if (stripos($condition['operator'], 'UNION') !== FALSE || strpbrk($condition['operator'], '[-\'"();') !== FALSE) { + throw new InvalidQueryConditionOperatorException('Invalid characters in query operator: ' . $condition['operator']); + } + // For simplicity, we treat all operators as the same data structure. // In the typical degenerate case, this won't get changed. $operator_defaults = array( @@ -1883,7 +1905,7 @@ class DatabaseCondition implements QueryConditionInterface, Countable { public function __toString() { // If the caller forgot to call compile() first, refuse to run. if ($this->changed) { - return NULL; + return ''; } return $this->stringVersion; } diff --git a/includes/database/select.inc b/includes/database/select.inc index 674c6b53ad..fa3745609a 100644 --- a/includes/database/select.inc +++ b/includes/database/select.inc @@ -883,7 +883,7 @@ class SelectQuery extends Query implements SelectQueryInterface { * 'type' => $join_type (one of INNER, LEFT OUTER, RIGHT OUTER), * 'table' => $table, * 'alias' => $alias_of_the_table, - * 'condition' => $condition_clause_on_which_to_join, + * 'condition' => $join_condition (string or Condition object), * 'arguments' => $array_of_arguments_for_placeholders_in_the condition. * 'all_fields' => TRUE to SELECT $alias.*, FALSE or NULL otherwise. * ) @@ -891,6 +891,10 @@ class SelectQuery extends Query implements SelectQueryInterface { * If $table is a string, it is taken as the name of a table. If it is * a SelectQuery object, it is taken as a subquery. * + * If $join_condition is a Condition object, any arguments should be + * incorporated into the object; a separate array of arguments does not need + * to be provided. + * * @var array */ protected $tables = array(); @@ -1028,6 +1032,10 @@ class SelectQuery extends Query implements SelectQueryInterface { if ($table['table'] instanceof SelectQueryInterface) { $args += $table['table']->arguments(); } + // If the join condition is an object, grab its arguments recursively. + if (!empty($table['condition']) && $table['condition'] instanceof QueryConditionInterface) { + $args += $table['condition']->arguments(); + } } foreach ($this->expressions as $expression) { @@ -1079,6 +1087,10 @@ class SelectQuery extends Query implements SelectQueryInterface { if ($table['table'] instanceof SelectQueryInterface) { $table['table']->compile($connection, $queryPlaceholder); } + // Make sure join conditions are also compiled. + if (!empty($table['condition']) && $table['condition'] instanceof QueryConditionInterface) { + $table['condition']->compile($connection, $queryPlaceholder); + } } // If there are any dependent queries to UNION, compile it recursively. @@ -1099,6 +1111,11 @@ class SelectQuery extends Query implements SelectQueryInterface { return FALSE; } } + if (!empty($table['condition']) && $table['condition'] instanceof QueryConditionInterface) { + if (!$table['condition']->compiled()) { + return FALSE; + } + } } foreach ($this->union as $union) { @@ -1504,7 +1521,14 @@ class SelectQuery extends Query implements SelectQueryInterface { // the query will be executed, it will be recompiled using the proper // placeholder generator anyway. if (!$this->compiled()) { - $this->compile($this->connection, $this); + try { + $this->compile($this->connection, $this); + } + // PHP does not allow exceptions to be thrown in __toString(), so trigger + // a fatal error instead. + catch (InvalidQueryConditionOperatorException $e) { + drupal_trigger_fatal_error($e->getMessage()); + } } // Create a sanitized comment string to prepend to the query. @@ -1561,7 +1585,7 @@ class SelectQuery extends Query implements SelectQueryInterface { $query .= $table_string . ' ' . $this->connection->escapeAlias($table['alias']); if (!empty($table['condition'])) { - $query .= ' ON ' . $table['condition']; + $query .= ' ON ' . (string) $table['condition']; } } @@ -1582,6 +1606,14 @@ class SelectQuery extends Query implements SelectQueryInterface { $query .= "\nHAVING " . $this->having; } + // UNION is a little odd, as the select queries to combine are passed into + // this query, but syntactically they all end up on the same level. + if ($this->union) { + foreach ($this->union as $union) { + $query .= ' ' . $union['type'] . ' ' . (string) $union['query']; + } + } + // ORDER BY if ($this->order) { $query .= "\nORDER BY "; @@ -1601,14 +1633,6 @@ class SelectQuery extends Query implements SelectQueryInterface { $query .= "\nLIMIT " . (int) $this->range['length'] . " OFFSET " . (int) $this->range['start']; } - // UNION is a little odd, as the select queries to combine are passed into - // this query, but syntactically they all end up on the same level. - if ($this->union) { - foreach ($this->union as $union) { - $query .= ' ' . $union['type'] . ' ' . (string) $union['query']; - } - } - if ($this->forUpdate) { $query .= ' FOR UPDATE'; } @@ -1617,6 +1641,8 @@ class SelectQuery extends Query implements SelectQueryInterface { } public function __clone() { + parent::__clone(); + // On cloning, also clone the dependent objects. However, we do not // want to clone the database connection object as that would duplicate the // connection itself. @@ -1626,6 +1652,11 @@ class SelectQuery extends Query implements SelectQueryInterface { foreach ($this->union as $key => $aggregate) { $this->union[$key]['query'] = clone($aggregate['query']); } + foreach ($this->tables as $alias => $table) { + if ($table['table'] instanceof SelectQueryInterface) { + $this->tables[$alias]['table'] = clone $table['table']; + } + } } } diff --git a/includes/database/sqlite/schema.inc b/includes/database/sqlite/schema.inc index 43ea6d61c5..9d9ec895eb 100644 --- a/includes/database/sqlite/schema.inc +++ b/includes/database/sqlite/schema.inc @@ -433,7 +433,7 @@ class DatabaseSchema_sqlite extends DatabaseSchema { 'type' => $type, 'size' => $size, 'not null' => !empty($row->notnull), - 'default' => trim($row->dflt_value, "'"), + 'default' => trim((string) $row->dflt_value, "'"), ); if ($length) { $schema['fields'][$row->name]['length'] = $length; diff --git a/includes/entity.inc b/includes/entity.inc index e80ce3b89f..2500e383cc 100644 --- a/includes/entity.inc +++ b/includes/entity.inc @@ -254,7 +254,10 @@ class DrupalDefaultEntityController implements DrupalEntityControllerInterface { * Callback for array_filter that removes non-integer IDs. */ protected function filterId($id) { - return is_numeric($id) && $id == (int) $id; + // ctype_digit() is used here instead of a strict comparison as sometimes + // the id is passed as a string containing '0' which may represent a bug + // elsewhere but would fail with a strict comparison. + return is_numeric($id) && $id == (int) $id && ctype_digit((string) $id); } /** diff --git a/includes/errors.inc b/includes/errors.inc index 4401ebe87e..500b7b9618 100644 --- a/includes/errors.inc +++ b/includes/errors.inc @@ -59,7 +59,8 @@ function _drupal_error_handler_real($error_level, $message, $filename, $line) { require_once DRUPAL_ROOT . '/includes/common.inc'; } - // We treat recoverable errors as fatal. + // We treat recoverable errors as fatal, and also allow fatal errors to be + // explicitly triggered by drupal_trigger_fatal_error(). _drupal_log_error(array( '%type' => isset($types[$error_level]) ? $severity_msg : 'Unknown error', // The standard PHP error handler considers that the error messages @@ -69,7 +70,7 @@ function _drupal_error_handler_real($error_level, $message, $filename, $line) { '%file' => $caller['file'], '%line' => $caller['line'], 'severity_level' => $severity_level, - ), $error_level == E_RECOVERABLE_ERROR); + ), $error_level == E_RECOVERABLE_ERROR || drupal_static('drupal_trigger_fatal_error')); } } @@ -266,7 +267,7 @@ function _drupal_log_error($error, $fatal = FALSE) { function _drupal_get_last_caller($backtrace) { // Errors that occur inside PHP internal functions do not generate // information about file and line. Ignore black listed functions. - $blacklist = array('debug', '_drupal_error_handler', '_drupal_exception_handler'); + $blacklist = array('debug', '_drupal_error_handler', '_drupal_exception_handler', 'drupal_trigger_fatal_error'); while (($backtrace && !isset($backtrace[0]['line'])) || (isset($backtrace[1]['function']) && in_array($backtrace[1]['function'], $blacklist))) { array_shift($backtrace); diff --git a/includes/file.inc b/includes/file.inc index 013c435cf3..b132b9c77c 100644 --- a/includes/file.inc +++ b/includes/file.inc @@ -197,7 +197,7 @@ function file_stream_wrapper_get_class($scheme) { * @see file_uri_target() */ function file_uri_scheme($uri) { - $position = strpos($uri, '://'); + $position = strpos((string) $uri, '://'); return $position ? substr($uri, 0, $position) : FALSE; } @@ -437,10 +437,10 @@ function file_prepare_directory(&$directory, $options = FILE_MODIFY_PERMISSIONS) // Check if directory exists. if (!is_dir($directory)) { - // Let mkdir() recursively create directories and use the default directory - // permissions. - if (($options & FILE_CREATE_DIRECTORY) && @drupal_mkdir($directory, NULL, TRUE)) { - return drupal_chmod($directory); + // Let drupal_mkdir() recursively create directories and use the default + // directory permissions. + if ($options & FILE_CREATE_DIRECTORY) { + return @drupal_mkdir($directory, NULL, TRUE); } return FALSE; } @@ -535,6 +535,10 @@ SetHandler Drupal_Security_Do_Not_Remove_See_SA_2006_006 php_flag engine off +# From PHP 8 there is no number in the module name. + + php_flag engine off + EOF; if ($private) { @@ -2481,19 +2485,21 @@ function drupal_basename($uri, $suffix = NULL) { } /** - * Creates a directory using Drupal's default mode. + * Creates a directory, optionally creating missing components in the path to + * the directory. * - * PHP's mkdir() does not respect Drupal's default permissions mode. If a mode - * is not provided, this function will make sure that Drupal's is used. - * - * Compatibility: normal paths and stream wrappers. + * When PHP's mkdir() creates a directory, the requested mode is affected by the + * process's umask. This function overrides the umask and sets the mode + * explicitly for all directory components created. * * @param $uri * A URI or pathname. * @param $mode - * By default the Drupal mode is used. + * Mode given to created directories. Defaults to the directory mode + * configured in the Drupal installation. It must have a leading zero. * @param $recursive - * Default to FALSE. + * Create directories recursively, defaults to FALSE. Cannot work with a mode + * which denies writing or execution to the owner of the process. * @param $context * Refer to http://php.net/manual/ref.stream.php * @@ -2509,7 +2515,71 @@ function drupal_mkdir($uri, $mode = NULL, $recursive = FALSE, $context = NULL) { $mode = variable_get('file_chmod_directory', 0775); } - if (!isset($context)) { + // If the URI has a scheme, don't override the umask - schemes can handle this + // issue in their own implementation. + if (file_uri_scheme($uri)) { + return _drupal_mkdir_call($uri, $mode, $recursive, $context); + } + + // If recursive, create each missing component of the parent directory + // individually and set the mode explicitly to override the umask. + if ($recursive) { + // Ensure the path is using DIRECTORY_SEPARATOR, and trim off any trailing + // slashes because they can throw off the loop when creating the parent + // directories. + $uri = rtrim(str_replace('/', DIRECTORY_SEPARATOR, $uri), DIRECTORY_SEPARATOR); + // Determine the components of the path. + $components = explode(DIRECTORY_SEPARATOR, $uri); + // If the filepath is absolute the first component will be empty as there + // will be nothing before the first slash. + if ($components[0] == '') { + $recursive_path = DIRECTORY_SEPARATOR; + // Get rid of the empty first component. + array_shift($components); + } + else { + $recursive_path = ''; + } + // Don't handle the top-level directory in this loop. + array_pop($components); + // Create each component if necessary. + foreach ($components as $component) { + $recursive_path .= $component; + + if (!file_exists($recursive_path)) { + $success = _drupal_mkdir_call($recursive_path, $mode, FALSE, $context); + // If the operation failed, check again if the directory was created + // by another process/server, only report a failure if not. + if (!$success && !file_exists($recursive_path)) { + return FALSE; + } + // Not necessary to use self::chmod() as there is no scheme. + if (!chmod($recursive_path, $mode)) { + return FALSE; + } + } + + $recursive_path .= DIRECTORY_SEPARATOR; + } + } + + // Do not check if the top-level directory already exists, as this condition + // must cause this function to fail. + if (!_drupal_mkdir_call($uri, $mode, FALSE, $context)) { + return FALSE; + } + // Not necessary to use drupal_chmod() as there is no scheme. + return chmod($uri, $mode); +} + +/** + * Helper function. Ensures we don't pass a NULL as a context resource to + * mkdir(). + * + * @see drupal_mkdir() + */ +function _drupal_mkdir_call($uri, $mode, $recursive, $context) { + if (is_null($context)) { return mkdir($uri, $mode, $recursive); } else { diff --git a/includes/filetransfer/filetransfer.inc b/includes/filetransfer/filetransfer.inc index cd420bd06a..2817b89d50 100644 --- a/includes/filetransfer/filetransfer.inc +++ b/includes/filetransfer/filetransfer.inc @@ -364,7 +364,7 @@ class FileTransferException extends Exception { public $arguments; function __construct($message, $code = 0, $arguments = array()) { - parent::__construct($message, $code); + parent::__construct($message, (int) $code); $this->arguments = $arguments; } } @@ -409,11 +409,13 @@ class SkipDotsRecursiveDirectoryIterator extends RecursiveDirectoryIterator { $this->skipdots(); } + #[\ReturnTypeWillChange] function rewind() { parent::rewind(); $this->skipdots(); } + #[\ReturnTypeWillChange] function next() { parent::next(); $this->skipdots(); diff --git a/includes/form.inc b/includes/form.inc index 3f6c7661ad..fe52b0afaa 100644 --- a/includes/form.inc +++ b/includes/form.inc @@ -1393,7 +1393,10 @@ function _form_validate(&$elements, &$form_state, $form_id = NULL) { // identical to the empty option's value, we reset the element's value // to NULL to trigger the regular #required handling below. // @see form_process_select() - elseif ($elements['#type'] == 'select' && !$elements['#multiple'] && $elements['#required'] && !isset($elements['#default_value']) && $elements['#value'] === $elements['#empty_value']) { + elseif ($elements['#type'] == 'select' && $elements['#required'] + && (!array_key_exists('#multiple', $elements) || !$elements['#multiple']) + && !isset($elements['#default_value']) + && $elements['#value'] === $elements['#empty_value']) { $elements['#value'] = NULL; form_set_value($elements, NULL, $form_state); } @@ -2084,7 +2087,7 @@ function _form_builder_handle_input_element($form_id, &$element, &$form_state) { // #access=FALSE on an element usually allow access for some users, so forms // submitted with drupal_form_submit() may bypass access restriction and be // treated as high-privilege users instead. - $process_input = empty($element['#disabled']) && (($form_state['programmed'] && $form_state['programmed_bypass_access_check']) || ($form_state['process_input'] && (!isset($element['#access']) || $element['#access']))); + $process_input = empty($element['#disabled']) && ($element['#type'] !== 'value') && (($form_state['programmed'] && $form_state['programmed_bypass_access_check']) || ($form_state['process_input'] && (!isset($element['#access']) || $element['#access']))); // Set the element's #value property. if (!isset($element['#value']) && !array_key_exists('#value', $element)) { @@ -4324,10 +4327,14 @@ function theme_form_required_marker($variables) { * required. That is especially important for screenreader users to know * which field is required. * + * To associate the label with a different field, set the #label_for property + * to the ID of the desired field. + * * @param $variables * An associative array containing: * - element: An associative array containing the properties of the element. - * Properties used: #required, #title, #id, #value, #description. + * Properties used: #required, #title, #id, #value, #description, + * #label_for. * * @ingroup themeable */ @@ -4356,7 +4363,14 @@ function theme_form_element_label($variables) { $attributes['class'] = 'element-invisible'; } - if (!empty($element['#id'])) { + // Use the element's ID as the default value of the "for" attribute (to + // associate the label with this form element), but allow this to be + // overridden in order to associate the label with a different form element + // instead. + if (!empty($element['#label_for'])) { + $attributes['for'] = $element['#label_for']; + } + elseif (!empty($element['#id'])) { $attributes['for'] = $element['#id']; } diff --git a/includes/install.core.inc b/includes/install.core.inc index b18d23d213..2e4c491a7f 100644 --- a/includes/install.core.inc +++ b/includes/install.core.inc @@ -1505,8 +1505,19 @@ function install_configure_form($form, &$form_state, &$install_state) { // especially out of place on the last page of the installer, where it would // distract from the message that the Drupal installation has completed // successfully.) - if (empty($_POST) && (!drupal_verify_install_file(DRUPAL_ROOT . '/' . $settings_file, FILE_EXIST|FILE_READABLE|FILE_NOT_WRITABLE) || !drupal_verify_install_file(DRUPAL_ROOT . '/' . $settings_dir, FILE_NOT_WRITABLE, 'dir'))) { - drupal_set_message(st('All necessary changes to %dir and %file have been made, so you should remove write permissions to them now in order to avoid security risks. If you are unsure how to do so, consult the online handbook.', array('%dir' => $settings_dir, '%file' => $settings_file, '@handbook_url' => 'http://drupal.org/server-permissions')), 'warning'); + + $skip_permissions_hardening = variable_get('skip_permissions_hardening', FALSE); + // Allow system administrators to ignore permissions hardening for the site + // directory. This allows additional files in the site directory to be + // updated when they are managed in a version control system. + if (!$skip_permissions_hardening) { + if (empty($_POST) && (!drupal_verify_install_file(DRUPAL_ROOT . '/' . $settings_file, FILE_EXIST | FILE_READABLE | FILE_NOT_WRITABLE) || !drupal_verify_install_file(DRUPAL_ROOT . '/' . $settings_dir, FILE_NOT_WRITABLE, 'dir'))) { + drupal_set_message(st('All necessary changes to %dir and %file have been made, so you should remove write permissions to them now in order to avoid security risks. If you are unsure how to do so, consult the online handbook.', array( + '%dir' => $settings_dir, + '%file' => $settings_file, + '@handbook_url' => 'http://drupal.org/server-permissions' + )), 'warning'); + } } drupal_add_js(drupal_get_path('module', 'system') . '/system.js'); diff --git a/includes/install.inc b/includes/install.inc index b7db783586..8f9137be30 100644 --- a/includes/install.inc +++ b/includes/install.inc @@ -830,11 +830,14 @@ function drupal_uninstall_modules($module_list = array(), $uninstall_dependents * An optional bitmask created from various FILE_* constants. * @param $type * The type of file. Can be file (default), dir, or link. + * @param bool $autofix + * (optional) Determines whether to attempt fixing the permissions according + * to the provided $mask. Defaults to TRUE. * * @return * TRUE on success or FALSE on failure. A message is set for the latter. */ -function drupal_verify_install_file($file, $mask = NULL, $type = 'file') { +function drupal_verify_install_file($file, $mask = NULL, $type = 'file', $autofix = TRUE) { $return = TRUE; // Check for files that shouldn't be there. if (isset($mask) && ($mask & FILE_NOT_EXIST) && file_exists($file)) { @@ -856,7 +859,7 @@ function drupal_verify_install_file($file, $mask = NULL, $type = 'file') { switch ($current_mask) { case FILE_EXIST: if (!file_exists($file)) { - if ($type == 'dir') { + if ($type == 'dir' && $autofix) { drupal_install_mkdir($file, $mask); } if (!file_exists($file)) { @@ -865,32 +868,32 @@ function drupal_verify_install_file($file, $mask = NULL, $type = 'file') { } break; case FILE_READABLE: - if (!is_readable($file) && !drupal_install_fix_file($file, $mask)) { + if (!is_readable($file)) { $return = FALSE; } break; case FILE_WRITABLE: - if (!is_writable($file) && !drupal_install_fix_file($file, $mask)) { + if (!is_writable($file)) { $return = FALSE; } break; case FILE_EXECUTABLE: - if (!is_executable($file) && !drupal_install_fix_file($file, $mask)) { + if (!is_executable($file)) { $return = FALSE; } break; case FILE_NOT_READABLE: - if (is_readable($file) && !drupal_install_fix_file($file, $mask)) { + if (is_readable($file)) { $return = FALSE; } break; case FILE_NOT_WRITABLE: - if (is_writable($file) && !drupal_install_fix_file($file, $mask)) { + if (is_writable($file)) { $return = FALSE; } break; case FILE_NOT_EXECUTABLE: - if (is_executable($file) && !drupal_install_fix_file($file, $mask)) { + if (is_executable($file)) { $return = FALSE; } break; @@ -898,6 +901,9 @@ function drupal_verify_install_file($file, $mask = NULL, $type = 'file') { } } } + if (!$return && $autofix) { + return drupal_install_fix_file($file, $mask); + } return $return; } diff --git a/includes/iso.inc b/includes/iso.inc index 5cad329d9d..e15c35731e 100644 --- a/includes/iso.inc +++ b/includes/iso.inc @@ -327,7 +327,7 @@ function _locale_get_predefined_list() { 'da' => array('Danish', 'Dansk'), 'de' => array('German', 'Deutsch'), 'dv' => array('Maldivian'), - 'dz' => array('Bhutani'), + 'dz' => array('Dzongkha', 'རྫོང་ཁ'), 'ee' => array('Ewe', 'Ɛʋɛ'), 'el' => array('Greek', 'Ελληνικά'), 'en' => array('English'), diff --git a/includes/locale.inc b/includes/locale.inc index 11f1413eec..48dbdce988 100644 --- a/includes/locale.inc +++ b/includes/locale.inc @@ -615,7 +615,7 @@ function locale_add_language($langcode, $name = NULL, $native = NULL, $direction 'direction' => $direction, 'domain' => $domain, 'prefix' => $prefix, - 'enabled' => $enabled, + 'enabled' => $enabled ? 1 : 0, )) ->execute(); @@ -1603,7 +1603,7 @@ function _locale_parse_js_file($filepath) { if ($source) { // We already have this source string and now have to add the location // to the location column, if this file is not yet present in there. - $locations = preg_split('~\s*;\s*~', $source->location); + $locations = preg_split('~\s*;\s*~', (string) $source->location); if (!in_array($filepath, $locations)) { $locations[] = $filepath; diff --git a/includes/menu.inc b/includes/menu.inc index b979275110..519ce8af57 100644 --- a/includes/menu.inc +++ b/includes/menu.inc @@ -3203,7 +3203,7 @@ function menu_link_save(&$item, $existing_item = array(), $parent_candidates = a 'external' => $item['external'], 'has_children' => $item['has_children'], 'expanded' => $item['expanded'], - 'weight' => $item['weight'], + 'weight' => (int) $item['weight'], 'module' => $item['module'], 'link_title' => $item['link_title'], 'options' => serialize($item['options']), @@ -3267,7 +3267,7 @@ function menu_link_save(&$item, $existing_item = array(), $parent_candidates = a 'external' => $item['external'], 'has_children' => $item['has_children'], 'expanded' => $item['expanded'], - 'weight' => $item['weight'], + 'weight' => (int) $item['weight'], 'depth' => $item['depth'], 'p1' => $item['p1'], 'p2' => $item['p2'], @@ -3906,7 +3906,7 @@ function _menu_router_save($menu, $masks) { 'type' => $item['type'], 'description' => $item['description'], 'position' => $item['position'], - 'weight' => $item['weight'], + 'weight' => (int) $item['weight'], 'include_file' => $item['include file'], )); diff --git a/includes/module.inc b/includes/module.inc index 4c2b3fbeeb..ab368b4b67 100644 --- a/includes/module.inc +++ b/includes/module.inc @@ -142,9 +142,10 @@ function system_list($type) { foreach ($bootstrap_list as $module) { drupal_get_filename('module', $module->name, $module->filename); } - // We only return the module names here since module_list() doesn't need - // the filename itself. - $lists['bootstrap'] = array_keys($bootstrap_list); + // Only return module names here since module_list() doesn't need the + // filename itself. Don't use drupal_map_assoc() as that requires common.inc. + $list = array_keys($bootstrap_list); + $lists['bootstrap'] = (!empty($list) ? array_combine($list, $list) : array()); } // Otherwise build the list for enabled modules and themes. elseif (!isset($lists['module_enabled'])) { diff --git a/includes/pager.inc b/includes/pager.inc index 316e17d5ad..48daa15e73 100644 --- a/includes/pager.inc +++ b/includes/pager.inc @@ -164,6 +164,21 @@ class PagerDefault extends SelectQueryExtender { } return $this; } + + /** + * Gets the element ID for this pager query. + * + * The element is used to differentiate different pager queries on the same + * page so that they may be operated independently. + * + * @return + * Element ID that is used to differentiate between different pager + * queries. + */ + public function getElement() { + $this->ensureElement(); + return $this->element; + } } /** diff --git a/includes/path.inc b/includes/path.inc index 28d1146a5e..5ed4a4ce0f 100644 --- a/includes/path.inc +++ b/includes/path.inc @@ -417,6 +417,8 @@ function path_load($conditions) { } return $select ->fields('url_alias') + ->orderBy('pid', 'DESC') + ->range(0, 1) ->execute() ->fetchAssoc(); } diff --git a/includes/session.inc b/includes/session.inc index a4ce54b7d0..1f3c1773ba 100644 --- a/includes/session.inc +++ b/includes/session.inc @@ -106,7 +106,7 @@ function _drupal_session_read($sid) { // active user. if ($user && $user->uid > 0 && $user->status == 1) { // This is done to unserialize the data member of $user. - $user->data = unserialize($user->data); + $user->data = unserialize((string) $user->data); // Add roles element to $user. $user->roles = array(); diff --git a/includes/stream_wrappers.inc b/includes/stream_wrappers.inc index 232ff1437c..285621893b 100644 --- a/includes/stream_wrappers.inc +++ b/includes/stream_wrappers.inc @@ -405,6 +405,12 @@ abstract class DrupalLocalStreamWrapper implements DrupalStreamWrapperInterface public function stream_open($uri, $mode, $options, &$opened_path) { $this->uri = $uri; $path = $this->getLocalPath(); + if ($path === FALSE) { + if ($options & STREAM_REPORT_ERRORS) { + trigger_error('stream_open() filename cannot be empty', E_USER_WARNING); + } + return FALSE; + } $this->handle = ($options & STREAM_REPORT_ERRORS) ? fopen($path, $mode) : @fopen($path, $mode); if ((bool) $this->handle && $options & STREAM_USE_PATH) { @@ -784,10 +790,10 @@ abstract class DrupalLocalStreamWrapper implements DrupalStreamWrapperInterface $localpath = $this->getLocalPath($uri); } if ($options & STREAM_REPORT_ERRORS) { - return mkdir($localpath, $mode, $recursive); + return drupal_mkdir($localpath, $mode, $recursive); } else { - return @mkdir($localpath, $mode, $recursive); + return @drupal_mkdir($localpath, $mode, $recursive); } } @@ -928,6 +934,27 @@ class DrupalPublicStreamWrapper extends DrupalLocalStreamWrapper { $path = str_replace('\\', '/', $this->getTarget()); return $GLOBALS['base_url'] . '/' . self::getDirectoryPath() . '/' . drupal_encode_path($path); } + + /** + * {@inheritdoc} + */ + protected function getLocalPath($uri = NULL) { + $path = parent::getLocalPath($uri); + + if (variable_get('sa_core_2022_012_override', FALSE)) { + return $path; + } + + $private_path = variable_get('file_private_path', FALSE); + if ($private_path) { + $private_path = realpath($private_path); + if ($private_path && strpos($path, $private_path) === 0) { + return FALSE; + } + } + + return $path; + } } diff --git a/includes/unicode.inc b/includes/unicode.inc index f9684a8973..4c61ce143e 100644 --- a/includes/unicode.inc +++ b/includes/unicode.inc @@ -589,6 +589,7 @@ function drupal_ucfirst($text) { */ function drupal_substr($text, $start, $length = NULL) { global $multibyte; + $text = (string) $text; if ($multibyte == UNICODE_MULTIBYTE) { return $length === NULL ? mb_substr($text, $start) : mb_substr($text, $start, $length); } diff --git a/misc/batch.js b/misc/batch.js index fee71a52fd..998879e4a5 100644 --- a/misc/batch.js +++ b/misc/batch.js @@ -7,6 +7,9 @@ Drupal.behaviors.batch = { attach: function (context, settings) { $('#progress', context).once('batch', function () { var holder = $(this); + // Remove HTML from no-js progress bar. The JS progress bar is created + // later on. + holder.empty(); // Success: redirect to the summary. var updateCallback = function (progress, status, pb) { diff --git a/misc/drupal.js b/misc/drupal.js index 7a3f5f5926..07692fcbc7 100644 --- a/misc/drupal.js +++ b/misc/drupal.js @@ -588,8 +588,12 @@ Drupal.ajaxError = function (xmlhttp, uri, customMessage) { // Class indicating that JS is enabled; used for styling purpose. $('html').addClass('js'); -// 'js enabled' cookie. -document.cookie = 'has_js=1; path=/'; +$(function () { + if (Drupal.settings.setHasJsCookie === 1) { + // 'js enabled' cookie. + document.cookie = 'has_js=1; path=/; SameSite=Lax'; + } +}); /** * Additions to jQuery.support. diff --git a/misc/machine-name.js b/misc/machine-name.js index 4678e0b534..45c146b4f4 100644 --- a/misc/machine-name.js +++ b/misc/machine-name.js @@ -27,7 +27,7 @@ Drupal.behaviors.machineName = { attach: function (context, settings) { var self = this; $.each(settings.machineName, function (source_id, options) { - var $source = $(source_id, context).addClass('machine-name-source'); + var $source = $(source_id, context).addClass('machine-name-source').once('machine-name'); var $target = $(options.target, context).addClass('machine-name-target'); var $suffix = $(options.suffix, context); var $wrapper = $target.closest('.form-item'); diff --git a/modules/aggregator/aggregator.info b/modules/aggregator/aggregator.info index cdf7864b4a..b696670c21 100644 --- a/modules/aggregator/aggregator.info +++ b/modules/aggregator/aggregator.info @@ -7,7 +7,7 @@ files[] = aggregator.test configure = admin/config/services/aggregator/settings stylesheets[all][] = aggregator.css -; Information added by Drupal.org packaging script on 2021-07-21 -version = "7.82" +; Information added by Drupal.org packaging script on 2022-09-07 +version = "7.92" project = "drupal" -datestamp = "1626883669" +datestamp = "1662554078" diff --git a/modules/aggregator/tests/aggregator_test.info b/modules/aggregator/tests/aggregator_test.info index 8569ca1aac..c63f62eac7 100644 --- a/modules/aggregator/tests/aggregator_test.info +++ b/modules/aggregator/tests/aggregator_test.info @@ -5,7 +5,7 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2021-07-21 -version = "7.82" +; Information added by Drupal.org packaging script on 2022-09-07 +version = "7.92" project = "drupal" -datestamp = "1626883669" +datestamp = "1662554078" diff --git a/modules/block/block.info b/modules/block/block.info index 346fb701f5..d043c4a441 100644 --- a/modules/block/block.info +++ b/modules/block/block.info @@ -6,7 +6,7 @@ core = 7.x files[] = block.test configure = admin/structure/block -; Information added by Drupal.org packaging script on 2021-07-21 -version = "7.82" +; Information added by Drupal.org packaging script on 2022-09-07 +version = "7.92" project = "drupal" -datestamp = "1626883669" +datestamp = "1662554078" diff --git a/modules/block/tests/block_test.info b/modules/block/tests/block_test.info index abb024f78b..a680bffc15 100644 --- a/modules/block/tests/block_test.info +++ b/modules/block/tests/block_test.info @@ -5,7 +5,7 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2021-07-21 -version = "7.82" +; Information added by Drupal.org packaging script on 2022-09-07 +version = "7.92" project = "drupal" -datestamp = "1626883669" +datestamp = "1662554078" diff --git a/modules/block/tests/themes/block_test_theme/block_test_theme.info b/modules/block/tests/themes/block_test_theme/block_test_theme.info index 3eaf9c065a..a3cdefbf4a 100644 --- a/modules/block/tests/themes/block_test_theme/block_test_theme.info +++ b/modules/block/tests/themes/block_test_theme/block_test_theme.info @@ -13,7 +13,7 @@ regions[footer] = Footer regions[highlighted] = Highlighted regions[help] = Help -; Information added by Drupal.org packaging script on 2021-07-21 -version = "7.82" +; Information added by Drupal.org packaging script on 2022-09-07 +version = "7.92" project = "drupal" -datestamp = "1626883669" +datestamp = "1662554078" diff --git a/modules/blog/blog.info b/modules/blog/blog.info index 775e032ad8..95bdd0c92a 100644 --- a/modules/blog/blog.info +++ b/modules/blog/blog.info @@ -5,7 +5,7 @@ version = VERSION core = 7.x files[] = blog.test -; Information added by Drupal.org packaging script on 2021-07-21 -version = "7.82" +; Information added by Drupal.org packaging script on 2022-09-07 +version = "7.92" project = "drupal" -datestamp = "1626883669" +datestamp = "1662554078" diff --git a/modules/book/book.info b/modules/book/book.info index 37a7b3d230..be6bf9b1ce 100644 --- a/modules/book/book.info +++ b/modules/book/book.info @@ -7,7 +7,7 @@ files[] = book.test configure = admin/content/book/settings stylesheets[all][] = book.css -; Information added by Drupal.org packaging script on 2021-07-21 -version = "7.82" +; Information added by Drupal.org packaging script on 2022-09-07 +version = "7.92" project = "drupal" -datestamp = "1626883669" +datestamp = "1662554078" diff --git a/modules/color/color.info b/modules/color/color.info index 0e6287e95d..2254f09768 100644 --- a/modules/color/color.info +++ b/modules/color/color.info @@ -5,7 +5,7 @@ version = VERSION core = 7.x files[] = color.test -; Information added by Drupal.org packaging script on 2021-07-21 -version = "7.82" +; Information added by Drupal.org packaging script on 2022-09-07 +version = "7.92" project = "drupal" -datestamp = "1626883669" +datestamp = "1662554078" diff --git a/modules/color/color.module b/modules/color/color.module index 45acd788ff..583a203228 100644 --- a/modules/color/color.module +++ b/modules/color/color.module @@ -724,7 +724,7 @@ function _color_blend($img, $hex1, $hex2, $alpha) { $in2 = _color_unpack($hex2); $out = array($img); for ($i = 0; $i < 3; ++$i) { - $out[] = $in1[$i] + ($in2[$i] - $in1[$i]) * $alpha; + $out[] = (int) ($in1[$i] + ($in2[$i] - $in1[$i]) * $alpha); } return call_user_func_array('imagecolorallocate', $out); @@ -752,7 +752,7 @@ function _color_unpack($hex, $normalize = FALSE) { function _color_pack($rgb, $normalize = FALSE) { $out = 0; foreach ($rgb as $k => $v) { - $out |= (($v * ($normalize ? 255 : 1)) << (16 - $k * 8)); + $out |= (((int) ($v * ($normalize ? 255 : 1))) << (16 - $k * 8)); } return '#' . str_pad(dechex($out), 6, 0, STR_PAD_LEFT); diff --git a/modules/color/color.test b/modules/color/color.test index f29c0c2679..bac17a8476 100644 --- a/modules/color/color.test +++ b/modules/color/color.test @@ -131,3 +131,53 @@ class ColorTestCase extends DrupalWebTestCase { } } } + +/** + * Unit tests for the color.module + */ +class ColorUnitTestCase extends DrupalUnitTestCase { + + protected $test_values; + + /** + * {@inheritdoc} + */ + public static function getInfo() { + return array( + 'name' => 'Color module unit tests', + 'description' => 'Test color.module functionality.', + 'group' => 'Color', + ); + } + + /** + * Set up the test environment. + */ + public function setUp() { + drupal_load('module', 'color'); + parent::setUp(); + + $this->test_values = array( + array(array(0.2, 0.4, 0.8), TRUE, '#3366cc'), + array(array(51, 102, 204), FALSE, '#3366cc'), + array(array(6, 120, 190), FALSE, '#0678be'), + array(array(192, 192, 192), FALSE, '#c0c0c0'), + array(array(255, 255, 0), FALSE, '#ffff00'), + array(array(128, 0, 128), FALSE, '#800080'), + array(array(0.6, 0.8, 1), TRUE, '#99ccff'), + array(array(221, 72, 20), FALSE, '#dd4814'), + ); + } + + public function testColorPack() { + foreach ($this->test_values as $test) { + $this->assertEqual(_color_pack($test[0], $test[1]), $test[2], __FUNCTION__ . ' hex: ' . $test[2] . ' normalize: ' . ($test[1] ? 'TRUE' : 'FALSE')); + } + } + + public function testColorUnpack() { + foreach ($this->test_values as $test) { + $this->assertEqual(_color_unpack($test[2], $test[1]), $test[0], __FUNCTION__ . ' hex: ' . $test[2] . ' normalize: ' . ($test[1] ? 'TRUE' : 'FALSE')); + } + } +} diff --git a/modules/comment/comment.info b/modules/comment/comment.info index 19a4bcd25c..77e8cfcd38 100644 --- a/modules/comment/comment.info +++ b/modules/comment/comment.info @@ -9,7 +9,7 @@ files[] = comment.test configure = admin/content/comment stylesheets[all][] = comment.css -; Information added by Drupal.org packaging script on 2021-07-21 -version = "7.82" +; Information added by Drupal.org packaging script on 2022-09-07 +version = "7.92" project = "drupal" -datestamp = "1626883669" +datestamp = "1662554078" diff --git a/modules/comment/comment.module b/modules/comment/comment.module index 6f0df6cae1..786be42de9 100644 --- a/modules/comment/comment.module +++ b/modules/comment/comment.module @@ -1515,7 +1515,7 @@ function comment_save($comment) { // by retrieving the maximum thread level. $max = db_query('SELECT MAX(thread) FROM {comment} WHERE nid = :nid', array(':nid' => $comment->nid))->fetchField(); // Strip the "/" from the end of the thread. - $max = rtrim($max, '/'); + $max = rtrim((string) $max, '/'); // We need to get the value at the correct depth. $parts = explode('.', $max); $firstsegment = $parts[0]; @@ -1918,7 +1918,6 @@ function comment_form($form, &$form_state, $comment) { if ($is_admin) { $author = (!$comment->uid && $comment->name ? $comment->name : $comment->registered_name); $status = (isset($comment->status) ? $comment->status : COMMENT_NOT_PUBLISHED); - $date = (!empty($comment->date) ? $comment->date : format_date($comment->created, 'custom', 'Y-m-d H:i O')); } else { if ($user->uid) { @@ -1928,7 +1927,11 @@ function comment_form($form, &$form_state, $comment) { $author = ($comment->name ? $comment->name : ''); } $status = (user_access('skip comment approval') ? COMMENT_PUBLISHED : COMMENT_NOT_PUBLISHED); - $date = ''; + } + + $date = ''; + if ($comment->cid) { + $date = !empty($comment->date) ? $comment->date : format_date($comment->created, 'custom', 'Y-m-d H:i:s O'); } // Add the author name field depending on the current user. @@ -2176,7 +2179,7 @@ function comment_submit($comment) { if (empty($comment->date)) { $comment->date = 'now'; } - $comment->created = strtotime($comment->date); + $comment->created = strtotime($comment->date, REQUEST_TIME); $comment->changed = REQUEST_TIME; // If the comment was posted by a registered user, assign the author's ID. diff --git a/modules/comment/comment.test b/modules/comment/comment.test index b70fa26c38..f87560bdf9 100644 --- a/modules/comment/comment.test +++ b/modules/comment/comment.test @@ -1003,7 +1003,7 @@ class CommentPreviewTest extends CommentHelperCase { */ function testCommentEditPreviewSave() { $langcode = LANGUAGE_NONE; - $web_user = $this->drupalCreateUser(array('access comments', 'post comments', 'skip comment approval')); + $web_user = $this->drupalCreateUser(array('access comments', 'post comments', 'skip comment approval', 'edit own comments')); $this->drupalLogin($this->admin_user); $this->setCommentPreview(DRUPAL_OPTIONAL); $this->setCommentForm(TRUE); @@ -1017,7 +1017,7 @@ class CommentPreviewTest extends CommentHelperCase { $edit['date'] = '2008-03-02 17:23 +0300'; $raw_date = strtotime($edit['date']); $expected_text_date = format_date($raw_date); - $expected_form_date = format_date($raw_date, 'custom', 'Y-m-d H:i O'); + $expected_form_date = format_date($raw_date, 'custom', 'Y-m-d H:i:s O'); $comment = $this->postComment($this->node, $edit['subject'], $edit['comment_body[' . $langcode . '][0][value]'], TRUE); $this->drupalPost('comment/' . $comment->id . '/edit', $edit, t('Preview')); @@ -1059,7 +1059,16 @@ class CommentPreviewTest extends CommentHelperCase { $this->assertEqual($comment_loaded->comment_body[$langcode][0]['value'], $edit['comment_body[' . $langcode . '][0][value]'], 'Comment body loaded.'); $this->assertEqual($comment_loaded->name, $edit['name'], 'Name loaded.'); $this->assertEqual($comment_loaded->created, $raw_date, 'Date loaded.'); + $this->drupalLogout(); + // Check that the date and time of the comment are correct when edited by + // non-admin users. + $user_edit = array(); + $expected_created_time = $comment_loaded->created; + $this->drupalLogin($web_user); + $this->drupalPost('comment/' . $comment->id . '/edit', $user_edit, t('Save')); + $comment_loaded = comment_load($comment->id, TRUE); + $this->assertEqual($comment_loaded->created, $expected_created_time, 'Expected date and time for comment edited.'); } } diff --git a/modules/contact/contact.info b/modules/contact/contact.info index 1d0e483e47..0a8c6b1c95 100644 --- a/modules/contact/contact.info +++ b/modules/contact/contact.info @@ -6,7 +6,7 @@ core = 7.x files[] = contact.test configure = admin/structure/contact -; Information added by Drupal.org packaging script on 2021-07-21 -version = "7.82" +; Information added by Drupal.org packaging script on 2022-09-07 +version = "7.92" project = "drupal" -datestamp = "1626883669" +datestamp = "1662554078" diff --git a/modules/contextual/contextual.info b/modules/contextual/contextual.info index 73c478e632..5d97a68913 100644 --- a/modules/contextual/contextual.info +++ b/modules/contextual/contextual.info @@ -5,7 +5,7 @@ version = VERSION core = 7.x files[] = contextual.test -; Information added by Drupal.org packaging script on 2021-07-21 -version = "7.82" +; Information added by Drupal.org packaging script on 2022-09-07 +version = "7.92" project = "drupal" -datestamp = "1626883669" +datestamp = "1662554078" diff --git a/modules/dashboard/dashboard.info b/modules/dashboard/dashboard.info index 983f918655..562059672d 100644 --- a/modules/dashboard/dashboard.info +++ b/modules/dashboard/dashboard.info @@ -7,7 +7,7 @@ files[] = dashboard.test dependencies[] = block configure = admin/dashboard/customize -; Information added by Drupal.org packaging script on 2021-07-21 -version = "7.82" +; Information added by Drupal.org packaging script on 2022-09-07 +version = "7.92" project = "drupal" -datestamp = "1626883669" +datestamp = "1662554078" diff --git a/modules/dblog/dblog.admin.inc b/modules/dblog/dblog.admin.inc index f8a00c26bb..df9f6a8719 100644 --- a/modules/dblog/dblog.admin.inc +++ b/modules/dblog/dblog.admin.inc @@ -286,13 +286,19 @@ function theme_dblog_message($variables) { $event = $variables['event']; // Check for required properties. if (isset($event->message) && isset($event->variables)) { + $event_variables = @unserialize($event->variables); // Messages without variables or user specified text. - if ($event->variables === 'N;') { + if ($event_variables === NULL) { $output = $event->message; } + elseif (!is_array($event_variables)) { + $output = t('Log data is corrupted and cannot be unserialized: @message', array( + '@message' => $event->message, + )); + } // Message to translate with injected variables. else { - $output = t($event->message, unserialize($event->variables)); + $output = t($event->message, $event_variables); } // If the output is expected to be a link, strip all the tags and // special characters by using filter_xss() without any allowed tags. diff --git a/modules/dblog/dblog.info b/modules/dblog/dblog.info index 6466d9bc01..1e043dea21 100644 --- a/modules/dblog/dblog.info +++ b/modules/dblog/dblog.info @@ -5,7 +5,7 @@ version = VERSION core = 7.x files[] = dblog.test -; Information added by Drupal.org packaging script on 2021-07-21 -version = "7.82" +; Information added by Drupal.org packaging script on 2022-09-07 +version = "7.92" project = "drupal" -datestamp = "1626883669" +datestamp = "1662554078" diff --git a/modules/dblog/dblog.test b/modules/dblog/dblog.test index b0a58ba454..9c266656fd 100644 --- a/modules/dblog/dblog.test +++ b/modules/dblog/dblog.test @@ -58,12 +58,42 @@ class DBLogTestCase extends DrupalWebTestCase { $this->verifyCron($row_limit); $this->verifyEvents(); $this->verifyReports(); + $this->testDBLogCorrupted(); // Login the regular user. $this->drupalLogin($this->any_user); $this->verifyReports(403); } + /** + * Tests corrupted log entries can still display available data. + */ + private function testDBLogCorrupted() { + global $base_root; + + // Prepare the fields to be logged + $log = array( + 'type' => 'custom', + 'message' => 'Log entry added to test the unserialize failure.', + 'variables' => 'BAD SERIALIZED DATA', + 'severity' => WATCHDOG_NOTICE, + 'link' => '', + 'user' => $this->big_user, + 'uid' => isset($this->big_user->uid) ? $this->big_user->uid : 0, + 'request_uri' => $base_root . request_uri(), + 'referer' => $_SERVER['HTTP_REFERER'], + 'ip' => ip_address(), + 'timestamp' => REQUEST_TIME, + ); + dblog_watchdog($log); + + // View the database log report page. + $this->drupalGet('admin/reports/dblog'); + $this->assertResponse(200); + $output = truncate_utf8(filter_xss(t('Log data is corrupted and cannot be unserialized: Log entry added to test unserialize failure.'), array()), 56, TRUE, TRUE); + $this->assertText($output, 'Log data is corrupted and cannot be unserialized.'); + } + /** * Verifies setting of the database log row limit. * diff --git a/modules/field/field.crud.inc b/modules/field/field.crud.inc index 7c0e3a154a..3673128d08 100644 --- a/modules/field/field.crud.inc +++ b/modules/field/field.crud.inc @@ -733,6 +733,7 @@ function field_read_instances($params = array(), $include_additional = array()) } $instances = array(); + $query->orderBy('fci.id'); $results = $query->execute(); foreach ($results as $record) { diff --git a/modules/field/field.info b/modules/field/field.info index 2b570ad58a..9cd7e0d76b 100644 --- a/modules/field/field.info +++ b/modules/field/field.info @@ -11,7 +11,7 @@ dependencies[] = field_sql_storage required = TRUE stylesheets[all][] = theme/field.css -; Information added by Drupal.org packaging script on 2021-07-21 -version = "7.82" +; Information added by Drupal.org packaging script on 2022-09-07 +version = "7.92" project = "drupal" -datestamp = "1626883669" +datestamp = "1662554078" diff --git a/modules/field/field.info.class.inc b/modules/field/field.info.class.inc index 772cd451f3..03dad67741 100644 --- a/modules/field/field.info.class.inc +++ b/modules/field/field.info.class.inc @@ -138,7 +138,7 @@ class FieldInfo { $map = array(); - $query = db_query('SELECT fc.type, fci.field_name, fci.entity_type, fci.bundle FROM {field_config_instance} fci INNER JOIN {field_config} fc ON fc.id = fci.field_id WHERE fc.active = 1 AND fc.storage_active = 1 AND fc.deleted = 0 AND fci.deleted = 0'); + $query = db_query('SELECT fc.type, fci.field_name, fci.entity_type, fci.bundle FROM {field_config_instance} fci INNER JOIN {field_config} fc ON fc.id = fci.field_id WHERE fc.active = 1 AND fc.storage_active = 1 AND fc.deleted = 0 AND fci.deleted = 0 ORDER BY bundle, entity_type'); foreach ($query as $row) { $map[$row->field_name]['bundles'][$row->entity_type][] = $row->bundle; $map[$row->field_name]['type'] = $row->type; diff --git a/modules/field/modules/field_sql_storage/field_sql_storage.info b/modules/field/modules/field_sql_storage/field_sql_storage.info index 3b80c0bf10..3390ada1e5 100644 --- a/modules/field/modules/field_sql_storage/field_sql_storage.info +++ b/modules/field/modules/field_sql_storage/field_sql_storage.info @@ -7,7 +7,7 @@ dependencies[] = field files[] = field_sql_storage.test required = TRUE -; Information added by Drupal.org packaging script on 2021-07-21 -version = "7.82" +; Information added by Drupal.org packaging script on 2022-09-07 +version = "7.92" project = "drupal" -datestamp = "1626883669" +datestamp = "1662554078" diff --git a/modules/field/modules/field_sql_storage/field_sql_storage.module b/modules/field/modules/field_sql_storage/field_sql_storage.module index deb08d0dac..63161ab7d1 100644 --- a/modules/field/modules/field_sql_storage/field_sql_storage.module +++ b/modules/field/modules/field_sql_storage/field_sql_storage.module @@ -212,6 +212,18 @@ function _field_sql_storage_schema($field) { ), ); + // If the target entity type uses a string for its entity ID then update + // the fields entity_id and revision_id columns from INT to VARCHAR. + if (!empty($field['entity_id_type']) && $field['entity_id_type'] === 'string') { + $current['fields']['entity_id']['type'] = 'varchar'; + $current['fields']['entity_id']['length'] = 128; + unset($current['fields']['entity_id']['unsigned']); + + $current['fields']['revision_id']['type'] = 'varchar'; + $current['fields']['revision_id']['length'] = 128; + unset($current['fields']['revision_id']['unsigned']); + } + $field += array('columns' => array(), 'indexes' => array(), 'foreign keys' => array()); // Add field columns. foreach ($field['columns'] as $column_name => $attributes) { diff --git a/modules/field/modules/field_sql_storage/field_sql_storage.test b/modules/field/modules/field_sql_storage/field_sql_storage.test index e46677be9c..ad8d74926b 100644 --- a/modules/field/modules/field_sql_storage/field_sql_storage.test +++ b/modules/field/modules/field_sql_storage/field_sql_storage.test @@ -104,6 +104,29 @@ class FieldSqlStorageTestCase extends DrupalWebTestCase { $this->assertFalse(array_key_exists($unavailable_language, $entity->{$this->field_name}), 'Field translation in an unavailable language ignored'); } + /** + * Tests adding a field with an entity ID type of string. + */ + function testFieldSqlSchemaForEntityWithStringIdentifier() { + // Test programmatically adding field with string ID. + $field_name = 'string_id_example'; + $field = array('field_name' => $field_name, 'type' => 'text', 'settings' => array('max_length' => 255), 'entity_id_type' => 'string'); + field_create_field($field); + $schema = drupal_get_schema('field_data_' . $field_name); + + $this->assertEqual($schema['fields']['entity_id']['type'], 'varchar'); + $this->assertEqual($schema['fields']['revision_id']['type'], 'varchar'); + + // Test programmatically adding field with default ID(int). + $field_name = 'default_id_example'; + $field = array('field_name' => $field_name, 'type' => 'text', 'settings' => array('max_length' => 255)); + field_create_field($field); + $schema = drupal_get_schema('field_data_' . $field_name); + + $this->assertEqual($schema['fields']['entity_id']['type'], 'int'); + $this->assertEqual($schema['fields']['revision_id']['type'], 'int'); + } + /** * Reads mysql to verify correct data is * written when using insert and update. diff --git a/modules/field/modules/list/list.info b/modules/field/modules/list/list.info index d79eb626dd..3ca656359f 100644 --- a/modules/field/modules/list/list.info +++ b/modules/field/modules/list/list.info @@ -7,7 +7,7 @@ dependencies[] = field dependencies[] = options files[] = tests/list.test -; Information added by Drupal.org packaging script on 2021-07-21 -version = "7.82" +; Information added by Drupal.org packaging script on 2022-09-07 +version = "7.92" project = "drupal" -datestamp = "1626883669" +datestamp = "1662554078" diff --git a/modules/field/modules/list/list.module b/modules/field/modules/list/list.module index 634c134886..47544be681 100644 --- a/modules/field/modules/list/list.module +++ b/modules/field/modules/list/list.module @@ -67,7 +67,7 @@ function list_field_settings_form($field, $instance, $has_data) { $form['allowed_values'] = array( '#type' => 'textarea', '#title' => t('Allowed values list'), - '#default_value' => list_allowed_values_string($settings['allowed_values']), + '#default_value' => empty($settings['allowed_values_function']) ? list_allowed_values_string($settings['allowed_values']) : array(), '#rows' => 10, '#element_validate' => array('list_allowed_values_setting_validate'), '#field_has_data' => $has_data, @@ -388,7 +388,8 @@ function _list_values_in_use($field, $values) { * - 'list_illegal_value': The value is not part of the list of allowed values. */ function list_field_validate($entity_type, $entity, $field, $instance, $langcode, $items, &$errors) { - $allowed_values = list_allowed_values($field, $instance, $entity_type, $entity); + // Flatten the array before validating to account for optgroups. + $allowed_values = options_array_flatten(list_allowed_values($field, $instance, $entity_type, $entity)); foreach ($items as $delta => $item) { if (!empty($item['value'])) { if (!empty($allowed_values) && !isset($allowed_values[$item['value']])) { diff --git a/modules/field/modules/list/tests/list.test b/modules/field/modules/list/tests/list.test index b476b5aad1..7c3aec0c4a 100644 --- a/modules/field/modules/list/tests/list.test +++ b/modules/field/modules/list/tests/list.test @@ -406,18 +406,36 @@ class ListFieldUITestCase extends FieldTestCase { $this->assertFalse(isset($field['settings']['off']), 'The off value is not saved into settings'); } + /** + * List (text) : test 'allowed values function' input. + */ + function testDynamicListAllowedValuesText() { + $this->field_name = 'field_list_text'; + $this->createListField('list_text', array( + 'allowed_values_function' => 'list_test_dynamic_values_callback', + 'allowed_values' => '', + )); + $this->drupalGet($this->admin_path); + } + /** * Helper function to create list field of a given type. * * @param string $type * 'list_integer', 'list_float', 'list_text' or 'list_boolean' + * @param array $settings + * + * @throws \FieldException */ - protected function createListField($type) { + protected function createListField($type, $settings = array()) { // Create a test field and instance. $field = array( 'field_name' => $this->field_name, 'type' => $type, ); + if (!empty($settings)) { + $field['settings'] = $settings; + } field_create_field($field); $instance = array( 'field_name' => $this->field_name, diff --git a/modules/field/modules/list/tests/list_test.info b/modules/field/modules/list/tests/list_test.info index e61332db98..f40208c070 100644 --- a/modules/field/modules/list/tests/list_test.info +++ b/modules/field/modules/list/tests/list_test.info @@ -5,7 +5,7 @@ package = Testing version = VERSION hidden = TRUE -; Information added by Drupal.org packaging script on 2021-07-21 -version = "7.82" +; Information added by Drupal.org packaging script on 2022-09-07 +version = "7.92" project = "drupal" -datestamp = "1626883669" +datestamp = "1662554078" diff --git a/modules/field/modules/number/number.info b/modules/field/modules/number/number.info index 064d07d130..7bd9574f0c 100644 --- a/modules/field/modules/number/number.info +++ b/modules/field/modules/number/number.info @@ -6,7 +6,7 @@ core = 7.x dependencies[] = field files[] = number.test -; Information added by Drupal.org packaging script on 2021-07-21 -version = "7.82" +; Information added by Drupal.org packaging script on 2022-09-07 +version = "7.92" project = "drupal" -datestamp = "1626883669" +datestamp = "1662554078" diff --git a/modules/field/modules/options/options.info b/modules/field/modules/options/options.info index 1c9c437eef..25ce8fcd09 100644 --- a/modules/field/modules/options/options.info +++ b/modules/field/modules/options/options.info @@ -6,7 +6,7 @@ core = 7.x dependencies[] = field files[] = options.test -; Information added by Drupal.org packaging script on 2021-07-21 -version = "7.82" +; Information added by Drupal.org packaging script on 2022-09-07 +version = "7.92" project = "drupal" -datestamp = "1626883669" +datestamp = "1662554078" diff --git a/modules/field/modules/options/options.test b/modules/field/modules/options/options.test index 321c2a4b5d..67110960ff 100644 --- a/modules/field/modules/options/options.test +++ b/modules/field/modules/options/options.test @@ -311,6 +311,11 @@ class OptionsWidgetsTestCase extends FieldTestCase { $edit = array("card_1[$langcode]" => '_none'); $this->drupalPost('test-entity/manage/' . $entity->ftid . '/edit', $edit, t('Save')); $this->assertFieldValues($entity_init, 'card_1', $langcode, array()); + + // Submit form: select the option from optgroup. + $edit = array("card_1[$langcode]" => 2); + $this->drupalPost(NULL, $edit, t('Save')); + $this->assertFieldValues($entity_init, 'card_1', $langcode, array(2)); } /** diff --git a/modules/field/modules/text/text.info b/modules/field/modules/text/text.info index 57c5cb74fb..07c2e3d301 100644 --- a/modules/field/modules/text/text.info +++ b/modules/field/modules/text/text.info @@ -7,7 +7,7 @@ dependencies[] = field files[] = text.test required = TRUE -; Information added by Drupal.org packaging script on 2021-07-21 -version = "7.82" +; Information added by Drupal.org packaging script on 2022-09-07 +version = "7.92" project = "drupal" -datestamp = "1626883669" +datestamp = "1662554078" diff --git a/modules/field/modules/text/text.module b/modules/field/modules/text/text.module index bf0d29d5a1..d64eef9a68 100644 --- a/modules/field/modules/text/text.module +++ b/modules/field/modules/text/text.module @@ -348,6 +348,11 @@ function _text_sanitize($instance, $langcode, $item, $column) { */ function text_summary($text, $format = NULL, $size = NULL) { + // If the input text is NULL, return unchanged. + if (is_null($text)) { + return NULL; + } + if (!isset($size)) { // What used to be called 'teaser' is now called 'summary', but // the variable 'teaser_length' is preserved for backwards compatibility. diff --git a/modules/field/modules/text/text.test b/modules/field/modules/text/text.test index ad803cf46d..0802c71194 100644 --- a/modules/field/modules/text/text.test +++ b/modules/field/modules/text/text.test @@ -378,6 +378,14 @@ class TextSummaryTestCase extends DrupalWebTestCase { } } + /** + * Test for the NULL value. + */ + function testNullSentence() { + $summary = text_summary(NULL); + $this->assertNull($summary, 'text_summary() casts returned null'); + } + /** * Calls text_summary() and asserts that the expected teaser is returned. */ diff --git a/modules/field/tests/field.test b/modules/field/tests/field.test index 5312f2d455..c334661aa1 100644 --- a/modules/field/tests/field.test +++ b/modules/field/tests/field.test @@ -3788,4 +3788,16 @@ class EntityPropertiesTestCase extends FieldTestCase { } } } + + /** + * Tests entity_extract_ids() with an empty entity info. + */ + function testEntityKeys(){ + $entity_type = 'test_entity2'; + $entity = field_test_create_stub_entity(); + list($id, $vid, $bundle) = entity_extract_ids($entity_type, $entity); + + $this->assertNull($id, 'Entity id for test_entity2 returned NULL.'); + $this->assertNull($vid, 'Entity vid for test_entity2 returned NULL.'); + } } diff --git a/modules/field/tests/field_test.info b/modules/field/tests/field_test.info index 1baa8717d7..a823922406 100644 --- a/modules/field/tests/field_test.info +++ b/modules/field/tests/field_test.info @@ -6,7 +6,7 @@ files[] = field_test.entity.inc version = VERSION hidden = TRUE -; Information added by Drupal.org packaging script on 2021-07-21 -version = "7.82" +; Information added by Drupal.org packaging script on 2022-09-07 +version = "7.92" project = "drupal" -datestamp = "1626883669" +datestamp = "1662554078" diff --git a/modules/field_ui/field_ui.admin.inc b/modules/field_ui/field_ui.admin.inc index 1bdaa45ddc..8ae489f303 100644 --- a/modules/field_ui/field_ui.admin.inc +++ b/modules/field_ui/field_ui.admin.inc @@ -795,6 +795,14 @@ function field_ui_field_overview_form_submit($form, &$form_state) { $destinations = array(); + // Check if the target entity uses a non numeric ID. + $entity_info = entity_get_info($entity_type); + if (!empty($entity_info['entity_id_type']) && $entity_info['entity_id_type'] === 'string') { + $entity_id_type = 'string'; + } else { + $entity_id_type = NULL; + } + // Create new field. $field = array(); if (!empty($form_values['_add_new_field']['field_name'])) { @@ -804,6 +812,7 @@ function field_ui_field_overview_form_submit($form, &$form_state) { 'field_name' => $values['field_name'], 'type' => $values['type'], 'translatable' => $values['translatable'], + 'entity_id_type' => $entity_id_type, ); $instance = array( 'field_name' => $field['field_name'], diff --git a/modules/field_ui/field_ui.info b/modules/field_ui/field_ui.info index 6dd468a060..d960343ddb 100644 --- a/modules/field_ui/field_ui.info +++ b/modules/field_ui/field_ui.info @@ -6,7 +6,7 @@ core = 7.x dependencies[] = field files[] = field_ui.test -; Information added by Drupal.org packaging script on 2021-07-21 -version = "7.82" +; Information added by Drupal.org packaging script on 2022-09-07 +version = "7.92" project = "drupal" -datestamp = "1626883669" +datestamp = "1662554078" diff --git a/modules/file/file.field.inc b/modules/file/file.field.inc index ddb4f841fd..706e01ceed 100644 --- a/modules/file/file.field.inc +++ b/modules/file/file.field.inc @@ -20,7 +20,7 @@ function file_field_info() { ), 'instance_settings' => array( 'file_extensions' => 'txt', - 'file_directory' => '', + 'file_directory' => '[date:custom:Y]-[date:custom:m]', 'max_filesize' => '', 'description_field' => 0, ), @@ -184,7 +184,7 @@ function file_field_load($entity_type, $entities, $field, $instances, $langcode, foreach ($items[$id] as $delta => $item) { // If the file does not exist, mark the entire item as empty. if (empty($item['fid']) || !isset($files[$item['fid']])) { - $items[$id][$delta] = NULL; + unset($items[$id][$delta]); } else { $items[$id][$delta] = array_merge((array) $files[$item['fid']], $item); @@ -943,7 +943,7 @@ function theme_file_upload_help($variables) { $descriptions = array(); - if (strlen($description)) { + if (!empty($description)) { $descriptions[] = $description; } if (isset($upload_validators['file_validate_size'])) { diff --git a/modules/file/file.info b/modules/file/file.info index ad5266c0cc..8a2810c393 100644 --- a/modules/file/file.info +++ b/modules/file/file.info @@ -6,7 +6,7 @@ core = 7.x dependencies[] = field files[] = tests/file.test -; Information added by Drupal.org packaging script on 2021-07-21 -version = "7.82" +; Information added by Drupal.org packaging script on 2022-09-07 +version = "7.92" project = "drupal" -datestamp = "1626883669" +datestamp = "1662554078" diff --git a/modules/file/file.install b/modules/file/file.install index 47ee4fd001..e0d33a04e2 100644 --- a/modules/file/file.install +++ b/modules/file/file.install @@ -53,18 +53,34 @@ function file_requirements($phase) { // Check the server's ability to indicate upload progress. if ($phase == 'runtime') { - $implementation = file_progress_implementation(); - $apache = strpos($_SERVER['SERVER_SOFTWARE'], 'Apache') !== FALSE; - $fastcgi = strpos($_SERVER['SERVER_SOFTWARE'], 'mod_fastcgi') !== FALSE || strpos($_SERVER["SERVER_SOFTWARE"], 'mod_fcgi') !== FALSE; $description = NULL; - if (!$apache) { + $implementation = file_progress_implementation(); + // Test the web server identity. + $server_software = $_SERVER['SERVER_SOFTWARE']; + if (preg_match("/Nginx/i", $server_software)) { + $is_nginx = TRUE; + $is_apache = FALSE; + $fastcgi = FALSE; + } + elseif (preg_match("/Apache/i", $server_software)) { + $is_nginx = FALSE; + $is_apache = TRUE; + $fastcgi = strpos($server_software, 'mod_fastcgi') !== FALSE || strpos($server_software, 'mod_fcgi') !== FALSE; + } + else { + $is_nginx = FALSE; + $is_apache = FALSE; + $fastcgi = FALSE; + } + + if (!$is_apache && !$is_nginx) { $value = t('Not enabled'); - $description = t('Your server is not capable of displaying file upload progress. File upload progress requires an Apache server running PHP with mod_php.'); + $description = t('Your server is not capable of displaying file upload progress. File upload progress requires an Apache server running PHP with mod_php or Nginx with PHP-FPM.'); $severity = REQUIREMENT_INFO; } elseif ($fastcgi) { $value = t('Not enabled'); - $description = t('Your server is not capable of displaying file upload progress. File upload progress requires PHP be run with mod_php and not as FastCGI.'); + $description = t('Your server is not capable of displaying file upload progress. File upload progress requires PHP be run with mod_php or PHP-FPM and not as FastCGI.'); $severity = REQUIREMENT_INFO; } elseif (!$implementation && extension_loaded('apc')) { diff --git a/modules/file/file.module b/modules/file/file.module index 1f1d594475..4db60669e5 100644 --- a/modules/file/file.module +++ b/modules/file/file.module @@ -246,7 +246,15 @@ function file_ajax_upload() { // Invalid request. drupal_set_message(t('An unrecoverable error occurred. The uploaded file likely exceeded the maximum file size (@size) that this server supports.', array('@size' => format_size(file_upload_max_size()))), 'error'); $commands = array(); - $commands[] = ajax_command_replace(NULL, theme('status_messages')); + $commands[] = ajax_command_prepend(NULL, theme('status_messages')); + + // Unset the problematic file from the input so that user can submit the + // form without reloading the page. + // @see https://www.drupal.org/project/drupal/issues/2749245 + $field_name = (string) reset($form_parents); + $wrapper_id = drupal_html_id('edit-' . $field_name); + $commands[] = ajax_command_invoke('#' . $wrapper_id . ' .form-type-managed-file input[type="file"]', 'val', array('')); + return array('#type' => 'ajax', '#commands' => $commands); } @@ -256,7 +264,7 @@ function file_ajax_upload() { // Invalid form_build_id. drupal_set_message(t('An unrecoverable error occurred. Use of this form has expired. Try reloading the page and submitting again.'), 'error'); $commands = array(); - $commands[] = ajax_command_replace(NULL, theme('status_messages')); + $commands[] = ajax_command_prepend(NULL, theme('status_messages')); return array('#type' => 'ajax', '#commands' => $commands); } @@ -363,10 +371,6 @@ function file_file_delete($file) { * support for a default value. */ function file_managed_file_process($element, &$form_state, $form) { - // Append the '-upload' to the #id so the field label's 'for' attribute - // corresponds with the file element. - $original_id = $element['#id']; - $element['#id'] .= '-upload'; $fid = isset($element['#value']['fid']) ? $element['#value']['fid'] : 0; // Set some default element properties. @@ -376,7 +380,7 @@ function file_managed_file_process($element, &$form_state, $form) { $ajax_settings = array( 'path' => 'file/ajax/' . implode('/', $element['#array_parents']) . '/' . $form['form_build_id']['#value'], - 'wrapper' => $original_id . '-ajax-wrapper', + 'wrapper' => $element['#id'] . '-ajax-wrapper', 'effect' => 'fade', 'progress' => array( 'type' => $element['#progress_indicator'], @@ -450,13 +454,26 @@ function file_managed_file_process($element, &$form_state, $form) { $element['upload'] = array( '#name' => 'files[' . implode('_', $element['#parents']) . ']', '#type' => 'file', + // This #title will not actually be used as the upload field's HTML label, + // since the theme function for upload fields never passes the element + // through theme('form_element'). Instead the parent element's #title is + // used as the label (see below). That is usually a more meaningful label + // anyway. '#title' => t('Choose a file'), '#title_display' => 'invisible', + // Set the ID manually so the desired field label can be associated with it + // below. Use the same method for setting the ID that the form API + // autogenerator does. + '#id' => drupal_html_id('edit-' . implode('-', array_merge($element['#parents'], array('upload')))), '#size' => $element['#size'], '#theme_wrappers' => array(), '#weight' => -10, ); + // Indicate that $element['#title'] should be used as the HTML label for the + // file upload field. + $element['#label_for'] = $element['upload']['#id']; + if ($fid && $element['#file']) { $element['filename'] = array( '#type' => 'markup', @@ -482,13 +499,13 @@ function file_managed_file_process($element, &$form_state, $form) { $element['upload']['#attached']['js'] = array( array( 'type' => 'setting', - 'data' => array('file' => array('elements' => array('#' . $element['#id'] => $extension_list))) + 'data' => array('file' => array('elements' => array('#' . $element['upload']['#id'] => $extension_list))) ) ); } // Prefix and suffix used for Ajax replacement. - $element['#prefix'] = '
'; + $element['#prefix'] = '
'; $element['#suffix'] = '
'; return $element; diff --git a/modules/file/tests/file.test b/modules/file/tests/file.test index c8264349d4..782d3a2fce 100644 --- a/modules/file/tests/file.test +++ b/modules/file/tests/file.test @@ -381,6 +381,20 @@ class FileManagedFileElementTestCase extends FileFieldTestCase { ); } + public function setUp() { + parent::setUp(); + + // Disable the displaying of errors, so that the AJAX responses are not + // contaminated with error messages about exceeding the maximum POST size. + $this->originalDisplayErrorsValue = ini_set('display_errors', '0'); + } + + public function tearDown() { + ini_set('display_errors', $this->originalDisplayErrorsValue); + + parent::tearDown(); + } + /** * Tests the managed_file element type. */ @@ -389,6 +403,9 @@ class FileManagedFileElementTestCase extends FileFieldTestCase { $this->drupalGet('file/test'); $this->assertFieldByXpath('//input[@name="files[nested_file]" and @size="13"]', NULL, 'The custom #size attribute is passed to the child upload element.'); + // Check that the file fields don't contain duplicate HTML IDs. + $this->assertNoDuplicateIds('There are no duplicate IDs'); + // Perform the tests with all permutations of $form['#tree'] and // $element['#extended']. foreach (array(0, 1) as $tree) { @@ -470,6 +487,43 @@ class FileManagedFileElementTestCase extends FileFieldTestCase { } } } + + /** + * Tests uploading a file that exceeds the maximum file size. + */ + function testManagedFileExceedMaximumFileSize() { + $path = 'file/test/0/0'; + $this->drupalGet($path); + + // Create a test file that exceeds the maximum POST size with 1 kilobyte. + $post_max_size = $this->_postMaxSizeToInteger(ini_get('post_max_size')); + $filename = 'text-exceeded'; + simpletest_generate_file($filename, ceil(($post_max_size + 1024) / 1024), 1024, 'text'); + $uri = 'public://' . $filename . '.txt'; + $input_base_name = 'file'; + $edit = array('files[' . $input_base_name . ']' => drupal_realpath($uri)); + $this->drupalPostAJAX(NULL, $edit, $input_base_name . '_upload_button'); + $this->assertFieldByXpath('//input[@type="submit"]', t('Upload'), 'After uploading a file that exceeds the maximum file size, the "Upload" button is displayed.'); + $this->drupalPost($path, array(), t('Save')); + $this->assertRaw(t('The file id is %fid.', array('%fid' => 0)), 'Submitted without a file.'); + } + + /** + * Converts php.ini post_max_size value to integer. + * + * @param $string + * The value from php.ini. + * + * @return int + * Converted value. + */ + protected function _postMaxSizeToInteger($string) { + sscanf($string, '%u%c', $number, $suffix); + if (isset($suffix)) { + $number = $number * pow(1024, strpos(' KMG', strtoupper($suffix))); + } + return $number; + } } /** @@ -749,6 +803,23 @@ class FileFieldWidgetTestCase extends FileFieldTestCase { foreach (array($field_name2, $field_name) as $each_field_name) { for ($delta = 0; $delta < 3; $delta++) { $edit = array('files[' . $each_field_name . '_' . LANGUAGE_NONE . '_' . $delta . ']' => drupal_realpath($test_file->uri)); + // drupalPost() takes a $submit parameter that is the value of the + // button whose click we want to emulate. Since we have multiple + // buttons with the value "Upload", and want to control which one we + // use, we change the value of the other ones to something else. + // Since non-clicked buttons aren't included in the submitted POST + // data, and since drupalPost() will result in $this being updated + // with a newly rebuilt form, this doesn't cause problems. Note that + // $buttons is an array of SimpleXMLElement objects passed by + // reference so modifications to each button will affect + // \DrupalWebTestCase::handleForm(). + $buttons = $this->xpath('//input[@type="submit" and @value="Upload"]'); + $button_name = $each_field_name . '_' . LANGUAGE_NONE . '_' . $delta . '_upload_button'; + foreach ($buttons as $button) { + if ($button['name'] != $button_name) { + $button['value'] = 'DUMMY'; + } + } // If the Upload button doesn't exist, drupalPost() will automatically // fail with an assertion message. $this->drupalPost(NULL, $edit, t('Upload')); @@ -786,13 +857,8 @@ class FileFieldWidgetTestCase extends FileFieldTestCase { $button_name = $current_field_name . '_' . LANGUAGE_NONE . '_' . $delta . '_remove_button'; switch ($type) { case 'nojs': - // drupalPost() takes a $submit parameter that is the value of the - // button whose click we want to emulate. Since we have multiple - // buttons with the value "Remove", and want to control which one we - // use, we change the value of the other ones to something else. - // Since non-clicked buttons aren't included in the submitted POST - // data, and since drupalPost() will result in $this being updated - // with a newly rebuilt form, this doesn't cause problems. + // Same workaround for multiple buttons with the value "Remove" as + // we did for the "Upload" buttons above. foreach ($buttons as $button) { if ($button['name'] != $button_name) { $button['value'] = 'DUMMY'; @@ -1354,10 +1420,11 @@ class FileFieldPathTestCase extends FileFieldTestCase { // Create a new node. $nid = $this->uploadNodeFile($test_file, $field_name, $type_name); - // Check that the file was uploaded to the file root. + // Check that the file was uploaded to the correct location. $node = node_load($nid, NULL, TRUE); $node_file = (object) $node->{$field_name}[LANGUAGE_NONE][0]; - $this->assertPathMatch('public://' . $test_file->filename, $node_file->uri, format_string('The file %file was uploaded to the correct path.', array('%file' => $node_file->uri))); + $expected_path = 'public://' . date('Y', REQUEST_TIME) . '-' . date('m', REQUEST_TIME) . '/' . $test_file->filename; + $this->assertPathMatch($expected_path, $node_file->uri, format_string('The file %file was uploaded to the correct path.', array('%file' => $node_file->uri))); // Change the path to contain multiple subdirectories. $field = $this->updateFileField($field_name, $type_name, array('file_directory' => 'foo/bar/baz')); @@ -1932,3 +1999,26 @@ class FileScanDirectory extends FileFieldTestCase { } } + +/** + * Test theme implementations declared in file_theme(). + */ +class FileThemeImplementationsTestCase extends DrupalUnitTestCase { + + public static function getInfo() { + return array( + 'name' => 'Theme implementations declared in file_theme()', + 'description' => 'Unit tests theme functions in the file module.', + 'group' => 'File', + ); + } + + function testThemeFileUploadHelp() { + $variables = array( + 'description' => NULL, + 'upload_validators' => NULL, + ); + $this->assertEqual('', theme_file_upload_help($variables), 'Empty string returned by theme_file_upload_help() with NULL inputs.'); + } + +} diff --git a/modules/file/tests/file_module_test.info b/modules/file/tests/file_module_test.info index 7fc368f295..2fe8034b66 100644 --- a/modules/file/tests/file_module_test.info +++ b/modules/file/tests/file_module_test.info @@ -5,7 +5,7 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2021-07-21 -version = "7.82" +; Information added by Drupal.org packaging script on 2022-09-07 +version = "7.92" project = "drupal" -datestamp = "1626883669" +datestamp = "1662554078" diff --git a/modules/filter/filter.info b/modules/filter/filter.info index 7ac8efbbd9..afefe2c3fd 100644 --- a/modules/filter/filter.info +++ b/modules/filter/filter.info @@ -7,7 +7,7 @@ files[] = filter.test required = TRUE configure = admin/config/content/formats -; Information added by Drupal.org packaging script on 2021-07-21 -version = "7.82" +; Information added by Drupal.org packaging script on 2022-09-07 +version = "7.92" project = "drupal" -datestamp = "1626883669" +datestamp = "1662554078" diff --git a/modules/filter/filter.module b/modules/filter/filter.module index e9fd01d388..6b911cb8a0 100644 --- a/modules/filter/filter.module +++ b/modules/filter/filter.module @@ -785,7 +785,7 @@ function check_markup($text, $format_id = NULL, $langcode = '', $cache = FALSE) // Convert all Windows and Mac newlines to a single newline, so filters only // need to deal with one possibility. - $text = str_replace(array("\r\n", "\r"), "\n", $text); + $text = str_replace(array("\r\n", "\r"), "\n", (string) $text); // Get a complete list of filters, ordered properly. $filters = filter_list_format($format->format); @@ -1661,7 +1661,7 @@ function _filter_url_trim($text, $length = NULL) { } // Use +3 for '...' string length. - if ($_length && strlen($text) > $_length + 3) { + if ($_length && strlen((string) $text) > $_length + 3) { $text = substr($text, 0, $_length) . '...'; } diff --git a/modules/forum/forum.info b/modules/forum/forum.info index 213e59ecd6..258baee2fa 100644 --- a/modules/forum/forum.info +++ b/modules/forum/forum.info @@ -9,7 +9,7 @@ files[] = forum.test configure = admin/structure/forum stylesheets[all][] = forum.css -; Information added by Drupal.org packaging script on 2021-07-21 -version = "7.82" +; Information added by Drupal.org packaging script on 2022-09-07 +version = "7.92" project = "drupal" -datestamp = "1626883669" +datestamp = "1662554078" diff --git a/modules/help/help.info b/modules/help/help.info index 96c2f05b07..367b0f19c7 100644 --- a/modules/help/help.info +++ b/modules/help/help.info @@ -5,7 +5,7 @@ version = VERSION core = 7.x files[] = help.test -; Information added by Drupal.org packaging script on 2021-07-21 -version = "7.82" +; Information added by Drupal.org packaging script on 2022-09-07 +version = "7.92" project = "drupal" -datestamp = "1626883669" +datestamp = "1662554078" diff --git a/modules/image/image.effects.inc b/modules/image/image.effects.inc index 35a6a74c7a..cbdb13609b 100644 --- a/modules/image/image.effects.inc +++ b/modules/image/image.effects.inc @@ -259,7 +259,7 @@ function image_rotate_effect(&$image, $data) { ); // Convert short #FFF syntax to full #FFFFFF syntax. - if (strlen($data['bgcolor']) == 4) { + if (strlen((string) $data['bgcolor']) == 4) { $c = $data['bgcolor']; $data['bgcolor'] = $c[0] . $c[1] . $c[1] . $c[2] . $c[2] . $c[3] . $c[3]; } diff --git a/modules/image/image.field.inc b/modules/image/image.field.inc index 6d1867cb02..f0dbf27184 100644 --- a/modules/image/image.field.inc +++ b/modules/image/image.field.inc @@ -19,7 +19,7 @@ function image_field_info() { ), 'instance_settings' => array( 'file_extensions' => 'png gif jpg jpeg', - 'file_directory' => '', + 'file_directory' => '[date:custom:Y]-[date:custom:m]', 'max_filesize' => '', 'alt_field' => 0, 'title_field' => 0, diff --git a/modules/image/image.info b/modules/image/image.info index d9c0820a73..b56252d5e8 100644 --- a/modules/image/image.info +++ b/modules/image/image.info @@ -7,7 +7,7 @@ dependencies[] = file files[] = image.test configure = admin/config/media/image-styles -; Information added by Drupal.org packaging script on 2021-07-21 -version = "7.82" +; Information added by Drupal.org packaging script on 2022-09-07 +version = "7.92" project = "drupal" -datestamp = "1626883669" +datestamp = "1662554078" diff --git a/modules/image/image.module b/modules/image/image.module index dab88361a2..cfb25713a9 100644 --- a/modules/image/image.module +++ b/modules/image/image.module @@ -583,8 +583,18 @@ function image_styles() { $style['storage'] = IMAGE_STORAGE_DEFAULT; foreach ($style['effects'] as $key => $effect) { $definition = image_effect_definition_load($effect['name']); - $effect = array_merge($definition, $effect); - $style['effects'][$key] = $effect; + if ($definition) { + $effect = array_merge($definition, $effect); + $style['effects'][$key] = $effect; + } + else { + watchdog('image', 'Image style %style_name has an effect %effect_name with no definition.', + array( + '%style_name' => $style_name, + '%effect_name' => $effect['name'], + ), + WATCHDOG_ERROR); + } } $styles[$style_name] = $style; } @@ -804,22 +814,25 @@ function image_style_options($include_empty = TRUE, $output = CHECK_PLAIN) { * @param $scheme * The file scheme, for example 'public' for public files. */ -function image_style_deliver($style, $scheme) { +function image_style_deliver($style, $scheme = NULL) { $args = func_get_args(); array_shift($args); array_shift($args); $target = implode('/', $args); - // Check that the style is defined, the scheme is valid, and the image - // derivative token is valid. (Sites which require image derivatives to be - // generated without a token can set the 'image_allow_insecure_derivatives' + // Check that the style is defined, the scheme is valid. + $valid = !empty($style) && !empty($scheme) && file_stream_wrapper_valid_scheme($scheme); + + // Also validate the derivative token. Sites which require image derivatives + // to be generated without a token can set the 'image_allow_insecure_derivatives' // variable to TRUE to bypass the latter check, but this will increase the // site's vulnerability to denial-of-service attacks. To prevent this // variable from leaving the site vulnerable to the most serious attacks, a // token is always required when a derivative of a derivative is requested.) - $valid = !empty($style) && file_stream_wrapper_valid_scheme($scheme); + $token = isset($_GET[IMAGE_DERIVATIVE_TOKEN]) ? $_GET[IMAGE_DERIVATIVE_TOKEN] : ''; + $token_is_valid = $token === image_style_path_token($style['name'], $scheme . '://' . $target); if (!variable_get('image_allow_insecure_derivatives', FALSE) || strpos(ltrim($target, '\/'), 'styles/') === 0) { - $valid = $valid && isset($_GET[IMAGE_DERIVATIVE_TOKEN]) && $_GET[IMAGE_DERIVATIVE_TOKEN] === image_style_path_token($style['name'], $scheme . '://' . $target); + $valid = $valid && $token_is_valid; } if (!$valid) { return MENU_ACCESS_DENIED; @@ -827,28 +840,33 @@ function image_style_deliver($style, $scheme) { $image_uri = $scheme . '://' . $target; $derivative_uri = image_style_path($style['name'], $image_uri); + $derivative_scheme = file_uri_scheme($derivative_uri); - // If using the private scheme, let other modules provide headers and - // control access to the file. - if ($scheme == 'private') { - if (file_exists($derivative_uri)) { - file_download($scheme, file_uri_target($derivative_uri)); - } - else { - $headers = file_download_headers($image_uri); - if (empty($headers)) { - return MENU_ACCESS_DENIED; - } - if (count($headers)) { - foreach ($headers as $name => $value) { - drupal_add_http_header($name, $value); - } - } + if ($token_is_valid) { + $is_public = ($scheme !== 'private'); + } + else { + $core_schemes = array('public', 'private', 'temporary'); + $additional_public_schemes = array_diff(variable_get('file_additional_public_schemes', array()), $core_schemes); + $public_schemes = array_merge(array('public'), $additional_public_schemes); + $is_public = in_array($derivative_scheme, $public_schemes, TRUE); + } + + if ($scheme == 'private' && file_exists($derivative_uri)) { + file_download($scheme, file_uri_target($derivative_uri)); + } + + $headers = array(); + + if (!$is_public) { + $headers = file_download_headers($image_uri); + if (empty($headers)) { + return MENU_ACCESS_DENIED; } } // Confirm that the original source image exists before trying to process it. - if (!is_file($image_uri)) { + if (!_image_source_image_exists($image_uri, $token_is_valid)) { watchdog('image', 'Source image at %source_image_path not found while trying to generate derivative image at %derivative_path.', array('%source_image_path' => $image_uri, '%derivative_path' => $derivative_uri)); return MENU_NOT_FOUND; } @@ -879,7 +897,11 @@ function image_style_deliver($style, $scheme) { if ($success) { $image = image_load($derivative_uri); - file_transfer($image->source, array('Content-Type' => $image->info['mime_type'], 'Content-Length' => $image->info['file_size'])); + $headers += array( + 'Content-Type' => $image->info['mime_type'], + 'Content-Length' => $image->info['file_size'] + ); + file_transfer($image->source, $headers); } else { watchdog('image', 'Unable to generate the derived image located at %path.', array('%path' => $derivative_uri)); @@ -890,6 +912,48 @@ function image_style_deliver($style, $scheme) { } } +/** + * Checks whether the provided source image exists. + * + * When a valid token is provided for the image URI, this function is + * equivalent to calling file_exists($image_uri). + * + * @param string $image_uri + * The URI for the source image. + * @param bool $token_is_valid + * Whether a valid image token was supplied. + * + * @return bool + * Whether the source image exists. + */ +function _image_source_image_exists($image_uri, $token_is_valid) { + $exists = file_exists($image_uri); + + // If the file doesn't exist, we can stop here. + if (!$exists) { + return FALSE; + } + + if ($token_is_valid) { + return TRUE; + } + + if (file_uri_scheme($image_uri) !== 'public') { + return TRUE; + } + + $image_path = drupal_realpath($image_uri); + $private_path = variable_get('file_private_path', FALSE); + if ($private_path) { + $private_path = realpath($private_path); + if ($private_path && strpos($image_path, $private_path) === 0) { + return FALSE; + } + } + + return TRUE; +} + /** * Creates a new image derivative based on an image style. * diff --git a/modules/image/image.test b/modules/image/image.test index 22edcaa064..f569bec028 100644 --- a/modules/image/image.test +++ b/modules/image/image.test @@ -31,7 +31,12 @@ class ImageFieldTestCase extends DrupalWebTestCase { protected $admin_user; function setUp() { - parent::setUp('image'); + $modules = func_get_args(); + if (isset($modules[0]) && is_array($modules[0])) { + $modules = $modules[0]; + } + $modules[] = 'image'; + parent::setUp($modules); $this->admin_user = $this->drupalCreateUser(array('access content', 'access administration pages', 'administer site configuration', 'administer content types', 'administer nodes', 'create article content', 'edit any article content', 'delete any article content', 'administer image styles', 'administer fields')); $this->drupalLogin($this->admin_user); } @@ -378,6 +383,11 @@ class ImageStylesPathAndUrlTestCase extends DrupalWebTestCase { $directory = $scheme . '://styles/' . $this->style_name . '/' . $scheme . '/' . $this->randomName(); $this->drupalGet(file_create_url($directory . '/' . $this->randomName())); $this->assertFalse(file_exists($directory), 'New directory was not created in the filesystem when requesting an unauthorized image.'); + + // Check that requesting a partial image style path returns access denied. + $partial_url = $scheme . '://styles/' . $this->style_name . '/'; + $this->drupalGet(file_create_url($partial_url) . '/'); + $this->assertResponse(403, 'Access was denied to a partial image style path.'); } } @@ -568,6 +578,10 @@ class ImageAdminStylesUnitTest extends ImageFieldTestCase { ); } + function setUp() { + parent::setUp('image_module_test', 'image_module_styles_test'); + } + /** * Given an image style, generate an image. */ @@ -888,6 +902,18 @@ class ImageAdminStylesUnitTest extends ImageFieldTestCase { $this->drupalGet('node/' . $nid); $this->assertRaw(check_plain(image_style_url('thumbnail', $node->{$field_name}[LANGUAGE_NONE][0]['uri'])), format_string('Image displayed using style replacement style.')); } + + /** + * Test disabling a module providing an effect in use by an image style. + */ + function testOrphanedEffect() { + // This will not check whether anything depends on the module. + module_disable(array('image_module_test'), FALSE); + $this->drupalGet('admin/config/media/image-styles'); + $this->assertText('Test Image Style', 'Image style with an orphaned effect displayed in the list of styles.'); + $image_log = db_query_range('SELECT message FROM {watchdog} WHERE type = :type ORDER BY wid DESC', 0, 1, array(':type' => 'image'))->fetchField(); + $this->assertEqual('Image style %style_name has an effect %effect_name with no definition.', $image_log, 'A watchdog message was logged for the broken image style effect'); + } } /** @@ -1165,6 +1191,36 @@ class ImageFieldDisplayTestCase extends ImageFieldTestCase { $this->drupalGet('node/' . $node->nid); $this->assertRaw($default_output, 'Default private image displayed when no user supplied image is present.'); } + + /** + * Tests the display of image field with the missing FID. + */ + function testMissingImageFieldDisplay() { + $field_name = strtolower($this->randomName()); + $type_name = 'article'; + $field_settings = array( + 'display_field' => '1', + 'display_default' => '1', + ); + $instance_settings = array( + 'description_field' => '1', + ); + $widget_settings = array(); + $this->createImageField($field_name, $type_name, $field_settings, $instance_settings, $widget_settings); + $images = $this->drupalGetTestFiles('image'); + + // Create a new node with the uploaded file. + $nid = $this->uploadNodeImage($images[1], $field_name, 'article'); + // Delete uploaded file from file_managed table. + $max_fid_after = db_query('SELECT MAX(fid) AS fid FROM {file_managed}')->fetchField(); + $uploaded_file = file_load($max_fid_after); + file_delete($uploaded_file, TRUE); + // Clear field cache. + field_cache_clear(); + // Check the node detail if the file is loaded. + $this->drupalGet('node/' . $nid); + $this->assertResponse(200); + } } /** diff --git a/modules/image/tests/image_module_test.info b/modules/image/tests/image_module_test.info index 4daeb380cc..460cdc5669 100644 --- a/modules/image/tests/image_module_test.info +++ b/modules/image/tests/image_module_test.info @@ -6,7 +6,7 @@ core = 7.x files[] = image_module_test.module hidden = TRUE -; Information added by Drupal.org packaging script on 2021-07-21 -version = "7.82" +; Information added by Drupal.org packaging script on 2022-09-07 +version = "7.92" project = "drupal" -datestamp = "1626883669" +datestamp = "1662554078" diff --git a/modules/image/tests/image_module_test.module b/modules/image/tests/image_module_test.module index fc66d9b8b7..e7ae716e7a 100644 --- a/modules/image/tests/image_module_test.module +++ b/modules/image/tests/image_module_test.module @@ -20,7 +20,8 @@ function image_module_test_file_download($uri) { function image_module_test_image_effect_info() { $effects = array( 'image_module_test_null' => array( - 'effect callback' => 'image_module_test_null_effect', + 'label' => 'image_module_test_null', + 'effect callback' => 'image_module_test_null_effect', ), ); @@ -38,7 +39,7 @@ function image_module_test_image_effect_info() { * @return * TRUE */ -function image_module_test_null_effect(array &$image, array $data) { +function image_module_test_null_effect(&$image, array $data) { return TRUE; } diff --git a/modules/locale/locale.info b/modules/locale/locale.info index ed5488a66e..f3c6336632 100644 --- a/modules/locale/locale.info +++ b/modules/locale/locale.info @@ -6,7 +6,7 @@ core = 7.x files[] = locale.test configure = admin/config/regional/language -; Information added by Drupal.org packaging script on 2021-07-21 -version = "7.82" +; Information added by Drupal.org packaging script on 2022-09-07 +version = "7.92" project = "drupal" -datestamp = "1626883669" +datestamp = "1662554078" diff --git a/modules/locale/locale.module b/modules/locale/locale.module index 93a4657f0c..0d7e080d57 100644 --- a/modules/locale/locale.module +++ b/modules/locale/locale.module @@ -555,6 +555,8 @@ function locale_language_types_info() { 'fixed' => array(LOCALE_LANGUAGE_NEGOTIATION_INTERFACE), ), LANGUAGE_TYPE_URL => array( + 'name' => t('URL'), + 'description' => t('Order of language detection methods for URLs. The detected language will be used as the default when generating URLs for internal links on the site.'), 'fixed' => array(LOCALE_LANGUAGE_NEGOTIATION_URL, LOCALE_LANGUAGE_NEGOTIATION_URL_FALLBACK), ), ); diff --git a/modules/locale/locale.test b/modules/locale/locale.test index b890b06147..1709996d80 100644 --- a/modules/locale/locale.test +++ b/modules/locale/locale.test @@ -297,6 +297,28 @@ class LocaleJavascriptTranslationTest extends DrupalWebTestCase { $this->assertEqual(count($source_strings), count($test_strings), 'Found correct number of source strings.'); } + + /** + * Test handling of null values in JS parsing for PHP8.0+ deprecations. + */ + function testNullValuesLocalesSource() { + db_insert('locales_source') + ->fields(array( + 'location' => NULL, + 'source' => 'Standard Call t', + 'context' => '', + 'textgroup' => 'default', + )) + ->execute(); + + $filename = drupal_get_path('module', 'locale_test') . '/locale_test.js'; + + // Parse the file to look for source strings. + _locale_parse_js_file($filename); + + $num_records = db_select('locales_source')->fields(NULL, array('lid'))->countQuery()->execute()->fetchField(); + $this->assertEqual($num_records, 32, 'Correct number of strings parsed from JS file'); + } } /** * Functional test for string translation and validation. @@ -1642,6 +1664,14 @@ class LocaleLanguageSwitchingFunctionalTest extends DrupalWebTestCase { $this->assertIdentical($links, array('active' => array('en'), 'inactive' => array('fr')), 'Only the current language list item is marked as active on the language switcher block.'); $this->assertIdentical($anchors, array('active' => array('en'), 'inactive' => array('fr')), 'Only the current language anchor is marked as active on the language switcher block.'); } + + /** + * Tests that languages can be added as disabled. + */ + function testNewDisabledLanguage() { + // Add new language which will be disabled. + locale_add_language('de', 'German', 'Deutsch', LANGUAGE_LTR, '', '', FALSE, FALSE); + } } /** diff --git a/modules/locale/tests/locale_test.info b/modules/locale/tests/locale_test.info index 916403fd96..e4dee0eda3 100644 --- a/modules/locale/tests/locale_test.info +++ b/modules/locale/tests/locale_test.info @@ -5,7 +5,7 @@ package = Testing version = VERSION hidden = TRUE -; Information added by Drupal.org packaging script on 2021-07-21 -version = "7.82" +; Information added by Drupal.org packaging script on 2022-09-07 +version = "7.92" project = "drupal" -datestamp = "1626883669" +datestamp = "1662554078" diff --git a/modules/menu/menu.admin.inc b/modules/menu/menu.admin.inc index 4d0a792dda..4be6be84ed 100644 --- a/modules/menu/menu.admin.inc +++ b/modules/menu/menu.admin.inc @@ -481,6 +481,7 @@ function menu_edit_menu($form, &$form_state, $type, $menu = array()) { '#default_value' => $menu['menu_name'], '#maxlength' => MENU_MAX_MENU_NAME_LENGTH_UI, '#description' => t('A unique name to construct the URL for the menu. It must only contain lowercase letters, numbers and hyphens.'), + '#field_prefix' => empty($menu['old_name']) ? 'menu-' : '', '#machine_name' => array( 'exists' => 'menu_edit_menu_name_exists', 'source' => array('title'), diff --git a/modules/menu/menu.info b/modules/menu/menu.info index 339fd3fc26..77ac90b98b 100644 --- a/modules/menu/menu.info +++ b/modules/menu/menu.info @@ -6,7 +6,7 @@ core = 7.x files[] = menu.test configure = admin/structure/menu -; Information added by Drupal.org packaging script on 2021-07-21 -version = "7.82" +; Information added by Drupal.org packaging script on 2022-09-07 +version = "7.92" project = "drupal" -datestamp = "1626883669" +datestamp = "1662554078" diff --git a/modules/menu/menu.module b/modules/menu/menu.module index 9578f59271..c6cb548b1e 100644 --- a/modules/menu/menu.module +++ b/modules/menu/menu.module @@ -659,7 +659,7 @@ function _menu_get_menu_weight_delta($menu_names, $max_delta = NULL) { $weight_info = db_query("SELECT MAX(weight) AS max_weight, MIN(weight) as min_weight FROM {menu_links} WHERE menu_name IN (:menu_names)", array(':menu_names' => $menu_names))->fetchObject(); - $delta = max(abs($weight_info->min_weight), abs($weight_info->max_weight)) + 1; + $delta = max(abs((int) $weight_info->min_weight), abs((int) $weight_info->max_weight)) + 1; // Honor max param, if given. if (!is_null($max_delta) && $delta > $max_delta) { diff --git a/modules/node/node.api.php b/modules/node/node.api.php index c8176a7d32..1a0c3a99fd 100644 --- a/modules/node/node.api.php +++ b/modules/node/node.api.php @@ -747,10 +747,10 @@ function hook_node_update_index($node) { /** * Perform node validation before a node is created or updated. * - * This hook is invoked from node_validate(), after a user has has finished - * editing the node and is previewing or submitting it. It is invoked at the - * end of all the standard validation steps, and after the type-specific - * hook_validate() is invoked. + * This hook is invoked from node_validate(), after a user has finished editing + * the node and is previewing or submitting it. It is invoked at the end of all + * the standard validation steps, and after the type-specific hook_validate() is + * invoked. * * To indicate a validation error, use form_set_error(). * diff --git a/modules/node/node.info b/modules/node/node.info index 8fda3d748b..3a780271f0 100644 --- a/modules/node/node.info +++ b/modules/node/node.info @@ -9,7 +9,7 @@ required = TRUE configure = admin/structure/types stylesheets[all][] = node.css -; Information added by Drupal.org packaging script on 2021-07-21 -version = "7.82" +; Information added by Drupal.org packaging script on 2022-09-07 +version = "7.92" project = "drupal" -datestamp = "1626883669" +datestamp = "1662554078" diff --git a/modules/node/node.test b/modules/node/node.test index f0ff8e7d6e..96c927b28f 100644 --- a/modules/node/node.test +++ b/modules/node/node.test @@ -666,8 +666,17 @@ class NodeCreationTestCase extends DrupalWebTestCase { } // Check that the rollback error was logged. - $records = db_query("SELECT wid FROM {watchdog} WHERE variables LIKE '%Test exception for rollback.%'")->fetchAll(); - $this->assertTrue(count($records) > 0, 'Rollback explanatory error logged to watchdog.'); + // PostgreSQL doesn't support bytea LIKE queries, so we need to unserialize + // first to check for the rollback exception message. + $matches = array(); + $records = db_query("SELECT wid, variables FROM {watchdog}")->fetchAll(); + foreach ($records as $record) { + $variables = (array) unserialize($record->variables); + if (isset($variables['!message']) && $variables['!message'] === 'Test exception for rollback.') { + $matches[] = $record->wid; + } + } + $this->assertTrue(count($matches) > 0, 'Rollback explanatory error logged to watchdog.'); } /** @@ -2545,6 +2554,26 @@ class NodeTokenReplaceTestCase extends DrupalWebTestCase { $this->assertEqual($output, $expected, format_string('Sanitized node token %token replaced.', array('%token' => $input))); } + // Test if the node without nid gets correct tokens (e.g. unsaved node). + $new_node_without_nid = clone $node; + unset($new_node_without_nid->nid); + + // Update tokens values which should be empty + $tests['[node:nid]'] = ''; + $tests['[node:url]'] = ''; + $tests['[node:edit-url]'] = ''; + + // Generate and test sanitized tokens. + foreach ($tests as $input => $expected) { + $output = token_replace($input, array('node' => $new_node_without_nid), array('language' => $language)); + $this->assertEqual($output, $expected, format_string('Sanitized node token %token replaced.', array('%token' => $input))); + } + + // Revert tokens values + $tests['[node:nid]'] = $node->nid; + $tests['[node:url]'] = url('node/' . $node->nid, $url_options); + $tests['[node:edit-url]'] = url('node/' . $node->nid . '/edit', $url_options); + // Generate and test unsanitized tokens. $tests['[node:title]'] = $node->title; $tests['[node:body]'] = $node->body[$langcode][0]['value']; diff --git a/modules/node/node.tokens.inc b/modules/node/node.tokens.inc index e63c751d6c..63b3273359 100644 --- a/modules/node/node.tokens.inc +++ b/modules/node/node.tokens.inc @@ -109,7 +109,7 @@ function node_tokens($type, $tokens, array $data = array(), array $options = arr switch ($name) { // Simple key values on the node. case 'nid': - $replacements[$original] = $node->nid; + $replacements[$original] = isset($node->nid) ? $node->nid : ''; break; case 'vid': @@ -168,11 +168,11 @@ function node_tokens($type, $tokens, array $data = array(), array $options = arr break; case 'url': - $replacements[$original] = url('node/' . $node->nid, $url_options); + $replacements[$original] = isset($node->nid) ? url('node/' . $node->nid, $url_options) : ''; break; case 'edit-url': - $replacements[$original] = url('node/' . $node->nid . '/edit', $url_options); + $replacements[$original] = isset($node->nid) ? url('node/' . $node->nid . '/edit', $url_options) : ''; break; // Default values for the chained tokens handled below. diff --git a/modules/node/tests/node_access_test.info b/modules/node/tests/node_access_test.info index ab458804df..6a53bb99f8 100644 --- a/modules/node/tests/node_access_test.info +++ b/modules/node/tests/node_access_test.info @@ -5,7 +5,7 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2021-07-21 -version = "7.82" +; Information added by Drupal.org packaging script on 2022-09-07 +version = "7.92" project = "drupal" -datestamp = "1626883669" +datestamp = "1662554078" diff --git a/modules/node/tests/node_test.info b/modules/node/tests/node_test.info index bf4e8261c6..c93b1853f0 100644 --- a/modules/node/tests/node_test.info +++ b/modules/node/tests/node_test.info @@ -5,7 +5,7 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2021-07-21 -version = "7.82" +; Information added by Drupal.org packaging script on 2022-09-07 +version = "7.92" project = "drupal" -datestamp = "1626883669" +datestamp = "1662554078" diff --git a/modules/node/tests/node_test_exception.info b/modules/node/tests/node_test_exception.info index cf5a939a7b..547d4fcf46 100644 --- a/modules/node/tests/node_test_exception.info +++ b/modules/node/tests/node_test_exception.info @@ -5,7 +5,7 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2021-07-21 -version = "7.82" +; Information added by Drupal.org packaging script on 2022-09-07 +version = "7.92" project = "drupal" -datestamp = "1626883669" +datestamp = "1662554078" diff --git a/modules/openid/openid.inc b/modules/openid/openid.inc index 4ca7471645..87a0d4d75a 100644 --- a/modules/openid/openid.inc +++ b/modules/openid/openid.inc @@ -445,7 +445,7 @@ function _openid_signature($association, $message_array, $keys_to_sign) { } $message = _openid_create_message($sign_data); - $secret = base64_decode($association->mac_key); + $secret = base64_decode((string) $association->mac_key); $signature = _openid_hmac($secret, $message); return base64_encode($signature); diff --git a/modules/openid/openid.info b/modules/openid/openid.info index 2908a9a4e8..702ad5bc78 100644 --- a/modules/openid/openid.info +++ b/modules/openid/openid.info @@ -5,7 +5,7 @@ package = Core core = 7.x files[] = openid.test -; Information added by Drupal.org packaging script on 2021-07-21 -version = "7.82" +; Information added by Drupal.org packaging script on 2022-09-07 +version = "7.92" project = "drupal" -datestamp = "1626883669" +datestamp = "1662554078" diff --git a/modules/openid/tests/openid_test.info b/modules/openid/tests/openid_test.info index 7935fac905..6e3c149342 100644 --- a/modules/openid/tests/openid_test.info +++ b/modules/openid/tests/openid_test.info @@ -6,7 +6,7 @@ core = 7.x dependencies[] = openid hidden = TRUE -; Information added by Drupal.org packaging script on 2021-07-21 -version = "7.82" +; Information added by Drupal.org packaging script on 2022-09-07 +version = "7.92" project = "drupal" -datestamp = "1626883669" +datestamp = "1662554078" diff --git a/modules/overlay/overlay.info b/modules/overlay/overlay.info index 0feb6d9ee9..82e87b9f61 100644 --- a/modules/overlay/overlay.info +++ b/modules/overlay/overlay.info @@ -4,7 +4,7 @@ package = Core version = VERSION core = 7.x -; Information added by Drupal.org packaging script on 2021-07-21 -version = "7.82" +; Information added by Drupal.org packaging script on 2022-09-07 +version = "7.92" project = "drupal" -datestamp = "1626883669" +datestamp = "1662554078" diff --git a/modules/path/path.info b/modules/path/path.info index 60c1b6a62e..6158000b4e 100644 --- a/modules/path/path.info +++ b/modules/path/path.info @@ -6,7 +6,7 @@ core = 7.x files[] = path.test configure = admin/config/search/path -; Information added by Drupal.org packaging script on 2021-07-21 -version = "7.82" +; Information added by Drupal.org packaging script on 2022-09-07 +version = "7.92" project = "drupal" -datestamp = "1626883669" +datestamp = "1662554078" diff --git a/modules/php/php.info b/modules/php/php.info index 0a6c967da7..0fe0aa9fe1 100644 --- a/modules/php/php.info +++ b/modules/php/php.info @@ -5,7 +5,7 @@ version = VERSION core = 7.x files[] = php.test -; Information added by Drupal.org packaging script on 2021-07-21 -version = "7.82" +; Information added by Drupal.org packaging script on 2022-09-07 +version = "7.92" project = "drupal" -datestamp = "1626883669" +datestamp = "1662554078" diff --git a/modules/poll/poll.info b/modules/poll/poll.info index 10d53de1f5..5dbe7df803 100644 --- a/modules/poll/poll.info +++ b/modules/poll/poll.info @@ -6,7 +6,7 @@ core = 7.x files[] = poll.test stylesheets[all][] = poll.css -; Information added by Drupal.org packaging script on 2021-07-21 -version = "7.82" +; Information added by Drupal.org packaging script on 2022-09-07 +version = "7.92" project = "drupal" -datestamp = "1626883669" +datestamp = "1662554078" diff --git a/modules/poll/poll.test b/modules/poll/poll.test index e24032d5a7..1e15848bc9 100644 --- a/modules/poll/poll.test +++ b/modules/poll/poll.test @@ -216,7 +216,7 @@ class PollCreateTestCase extends PollTestCase { $vote_count = '2000'; $node->choice[] = array( - 'chid' => '', + 'chid' => NULL, 'chtext' => $new_option, 'chvotes' => (int) $vote_count, 'weight' => 1000, diff --git a/modules/profile/profile.info b/modules/profile/profile.info index 12b56ca89f..4ef85a3c3f 100644 --- a/modules/profile/profile.info +++ b/modules/profile/profile.info @@ -11,7 +11,7 @@ configure = admin/config/people/profile ; See user_system_info_alter(). hidden = TRUE -; Information added by Drupal.org packaging script on 2021-07-21 -version = "7.82" +; Information added by Drupal.org packaging script on 2022-09-07 +version = "7.92" project = "drupal" -datestamp = "1626883669" +datestamp = "1662554078" diff --git a/modules/rdf/rdf.info b/modules/rdf/rdf.info index 04fd0616c1..9236080cb4 100644 --- a/modules/rdf/rdf.info +++ b/modules/rdf/rdf.info @@ -5,7 +5,7 @@ version = VERSION core = 7.x files[] = rdf.test -; Information added by Drupal.org packaging script on 2021-07-21 -version = "7.82" +; Information added by Drupal.org packaging script on 2022-09-07 +version = "7.92" project = "drupal" -datestamp = "1626883669" +datestamp = "1662554078" diff --git a/modules/rdf/tests/rdf_test.info b/modules/rdf/tests/rdf_test.info index ca0be33df4..66c17f9d53 100644 --- a/modules/rdf/tests/rdf_test.info +++ b/modules/rdf/tests/rdf_test.info @@ -6,7 +6,7 @@ core = 7.x hidden = TRUE dependencies[] = blog -; Information added by Drupal.org packaging script on 2021-07-21 -version = "7.82" +; Information added by Drupal.org packaging script on 2022-09-07 +version = "7.92" project = "drupal" -datestamp = "1626883669" +datestamp = "1662554078" diff --git a/modules/search/search.info b/modules/search/search.info index b7e6532df3..ac35612a9e 100644 --- a/modules/search/search.info +++ b/modules/search/search.info @@ -8,7 +8,7 @@ files[] = search.test configure = admin/config/search/settings stylesheets[all][] = search.css -; Information added by Drupal.org packaging script on 2021-07-21 -version = "7.82" +; Information added by Drupal.org packaging script on 2022-09-07 +version = "7.92" project = "drupal" -datestamp = "1626883669" +datestamp = "1662554078" diff --git a/modules/search/tests/search_embedded_form.info b/modules/search/tests/search_embedded_form.info index f342d817d4..d007729388 100644 --- a/modules/search/tests/search_embedded_form.info +++ b/modules/search/tests/search_embedded_form.info @@ -5,7 +5,7 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2021-07-21 -version = "7.82" +; Information added by Drupal.org packaging script on 2022-09-07 +version = "7.92" project = "drupal" -datestamp = "1626883669" +datestamp = "1662554078" diff --git a/modules/search/tests/search_extra_type.info b/modules/search/tests/search_extra_type.info index 89cc171e08..36f449def4 100644 --- a/modules/search/tests/search_extra_type.info +++ b/modules/search/tests/search_extra_type.info @@ -5,7 +5,7 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2021-07-21 -version = "7.82" +; Information added by Drupal.org packaging script on 2022-09-07 +version = "7.92" project = "drupal" -datestamp = "1626883669" +datestamp = "1662554078" diff --git a/modules/search/tests/search_node_tags.info b/modules/search/tests/search_node_tags.info index fd009deb38..b0f696a958 100644 --- a/modules/search/tests/search_node_tags.info +++ b/modules/search/tests/search_node_tags.info @@ -5,7 +5,7 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2021-07-21 -version = "7.82" +; Information added by Drupal.org packaging script on 2022-09-07 +version = "7.92" project = "drupal" -datestamp = "1626883669" +datestamp = "1662554078" diff --git a/modules/shortcut/shortcut.info b/modules/shortcut/shortcut.info index 8f10d16312..d6cec7a20a 100644 --- a/modules/shortcut/shortcut.info +++ b/modules/shortcut/shortcut.info @@ -6,7 +6,7 @@ core = 7.x files[] = shortcut.test configure = admin/config/user-interface/shortcut -; Information added by Drupal.org packaging script on 2021-07-21 -version = "7.82" +; Information added by Drupal.org packaging script on 2022-09-07 +version = "7.92" project = "drupal" -datestamp = "1626883669" +datestamp = "1662554078" diff --git a/modules/simpletest/drupal_web_test_case.php b/modules/simpletest/drupal_web_test_case.php index e2dc2322de..a3bd3730cc 100644 --- a/modules/simpletest/drupal_web_test_case.php +++ b/modules/simpletest/drupal_web_test_case.php @@ -1536,7 +1536,7 @@ protected function storeSetupCache($cache_key_prefix = '') { // Inform others that this cache is usable now. $cache_file = $this->originalFileDirectory . '/simpletest/' . $cache_key . '/simpletest-cache-setup'; - file_put_contents($cache_file, time(NULL)); + file_put_contents($cache_file, time()); lock_release($lock_key); return TRUE; diff --git a/modules/simpletest/simpletest.info b/modules/simpletest/simpletest.info index da1e2d056a..3c6ded7b44 100644 --- a/modules/simpletest/simpletest.info +++ b/modules/simpletest/simpletest.info @@ -58,7 +58,7 @@ files[] = tests/upgrade/update.trigger.test files[] = tests/upgrade/update.field.test files[] = tests/upgrade/update.user.test -; Information added by Drupal.org packaging script on 2021-07-21 -version = "7.82" +; Information added by Drupal.org packaging script on 2022-09-07 +version = "7.92" project = "drupal" -datestamp = "1626883669" +datestamp = "1662554078" diff --git a/modules/simpletest/simpletest.install b/modules/simpletest/simpletest.install index 6c6f5694de..d92f70d17f 100644 --- a/modules/simpletest/simpletest.install +++ b/modules/simpletest/simpletest.install @@ -20,7 +20,6 @@ function simpletest_requirements($phase) { $has_curl = function_exists('curl_init'); $has_hash = function_exists('hash_hmac'); $has_domdocument = method_exists('DOMDocument', 'loadHTML'); - $open_basedir = ini_get('open_basedir'); $requirements['curl'] = array( 'title' => $t('cURL'), @@ -48,18 +47,6 @@ function simpletest_requirements($phase) { $requirements['php_domdocument']['description'] = $t('The testing framework requires the DOMDocument class to be available. Check the configure command at the PHP info page.', array('@link-phpinfo' => url('admin/reports/status/php'))); } - // SimpleTest currently needs 2 cURL options which are incompatible with - // having PHP's open_basedir restriction set. - // See http://drupal.org/node/674304. - $requirements['php_open_basedir'] = array( - 'title' => $t('PHP open_basedir restriction'), - 'value' => $open_basedir ? $t('Enabled') : $t('Disabled'), - ); - if ($open_basedir) { - $requirements['php_open_basedir']['severity'] = REQUIREMENT_ERROR; - $requirements['php_open_basedir']['description'] = $t('The testing framework requires the PHP open_basedir restriction to be disabled. Check your webserver configuration or contact your web host.', array('@open_basedir-url' => 'http://php.net/manual/en/ini.core.php#ini.open-basedir')); - } - // Check the current memory limit. If it is set too low, SimpleTest will fail // to load all tests and throw a fatal error. $memory_limit = ini_get('memory_limit'); diff --git a/modules/simpletest/tests/actions_loop_test.info b/modules/simpletest/tests/actions_loop_test.info index c879eb92ae..2624231216 100644 --- a/modules/simpletest/tests/actions_loop_test.info +++ b/modules/simpletest/tests/actions_loop_test.info @@ -5,7 +5,7 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2021-07-21 -version = "7.82" +; Information added by Drupal.org packaging script on 2022-09-07 +version = "7.92" project = "drupal" -datestamp = "1626883669" +datestamp = "1662554078" diff --git a/modules/simpletest/tests/ajax_forms_test.info b/modules/simpletest/tests/ajax_forms_test.info index 7a1151833b..da452a4868 100644 --- a/modules/simpletest/tests/ajax_forms_test.info +++ b/modules/simpletest/tests/ajax_forms_test.info @@ -5,7 +5,7 @@ package = Testing version = VERSION hidden = TRUE -; Information added by Drupal.org packaging script on 2021-07-21 -version = "7.82" +; Information added by Drupal.org packaging script on 2022-09-07 +version = "7.92" project = "drupal" -datestamp = "1626883669" +datestamp = "1662554078" diff --git a/modules/simpletest/tests/ajax_test.info b/modules/simpletest/tests/ajax_test.info index ccf249bdd9..de20a4149e 100644 --- a/modules/simpletest/tests/ajax_test.info +++ b/modules/simpletest/tests/ajax_test.info @@ -5,7 +5,7 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2021-07-21 -version = "7.82" +; Information added by Drupal.org packaging script on 2022-09-07 +version = "7.92" project = "drupal" -datestamp = "1626883669" +datestamp = "1662554078" diff --git a/modules/simpletest/tests/batch_test.info b/modules/simpletest/tests/batch_test.info index 599081c816..819c7c674d 100644 --- a/modules/simpletest/tests/batch_test.info +++ b/modules/simpletest/tests/batch_test.info @@ -5,7 +5,7 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2021-07-21 -version = "7.82" +; Information added by Drupal.org packaging script on 2022-09-07 +version = "7.92" project = "drupal" -datestamp = "1626883669" +datestamp = "1662554078" diff --git a/modules/simpletest/tests/boot_test_1.info b/modules/simpletest/tests/boot_test_1.info index bbd2619f04..43d9427187 100644 --- a/modules/simpletest/tests/boot_test_1.info +++ b/modules/simpletest/tests/boot_test_1.info @@ -5,7 +5,7 @@ package = Testing version = VERSION hidden = TRUE -; Information added by Drupal.org packaging script on 2021-07-21 -version = "7.82" +; Information added by Drupal.org packaging script on 2022-09-07 +version = "7.92" project = "drupal" -datestamp = "1626883669" +datestamp = "1662554078" diff --git a/modules/simpletest/tests/boot_test_2.info b/modules/simpletest/tests/boot_test_2.info index 9624ba945f..f1b72fd0bb 100644 --- a/modules/simpletest/tests/boot_test_2.info +++ b/modules/simpletest/tests/boot_test_2.info @@ -5,7 +5,7 @@ package = Testing version = VERSION hidden = TRUE -; Information added by Drupal.org packaging script on 2021-07-21 -version = "7.82" +; Information added by Drupal.org packaging script on 2022-09-07 +version = "7.92" project = "drupal" -datestamp = "1626883669" +datestamp = "1662554078" diff --git a/modules/simpletest/tests/bootstrap.test b/modules/simpletest/tests/bootstrap.test index 61caf53cae..6bee03e12f 100644 --- a/modules/simpletest/tests/bootstrap.test +++ b/modules/simpletest/tests/bootstrap.test @@ -187,6 +187,7 @@ class BootstrapPageCacheTestCase extends DrupalWebTestCase { $this->assertEqual($this->drupalGetHeader('Cache-Control'), 'public, max-age=0', 'Cache-Control header was sent.'); $this->assertEqual($this->drupalGetHeader('Expires'), 'Sun, 19 Nov 1978 05:00:00 GMT', 'Expires header was sent.'); $this->assertEqual($this->drupalGetHeader('Foo'), 'bar', 'Custom header was sent.'); + $this->assertEqual($this->drupalGetHeader('X-Content-Type-Options'), 'nosniff', 'X-Content-Type-Options header was sent.'); // Check replacing default headers. $this->drupalGet('system-test/set-header', array('query' => array('name' => 'Expires', 'value' => 'Fri, 19 Nov 2008 05:00:00 GMT'))); @@ -236,6 +237,9 @@ class BootstrapPageCacheTestCase extends DrupalWebTestCase { $this->assertTitle(t('Welcome to @site-name | @site-name', array('@site-name' => variable_get('site_name', 'Drupal'))), 'Site title matches.'); $this->assertRaw('', 'Page was not compressed.'); + // Verify that an empty page doesn't throw an error when being decompressed. + $this->drupalGet('system-test/empty-page'); + // Disable compression mode. variable_set('page_compression', FALSE); @@ -248,6 +252,27 @@ class BootstrapPageCacheTestCase extends DrupalWebTestCase { $this->drupalGet(''); $this->assertRaw('', 'Page was delivered after compression mode is changed (compression support disabled).'); } + + /** + * Test page cache headers. + */ + function testPageCacheHeaders() { + variable_set('cache', 1); + // First request should store a response in the page cache. + $this->drupalGet('system-test/page-cache-headers'); + + // The test callback should remove the query string leaving the same path + // as the previous request, which we'll try to retrieve from cache_page. + $this->drupalGet('system-test/page-cache-headers', array('query' => array('return_headers' => 'TRUE'))); + + $headers = json_decode($this->drupalGetHeader('Page-Cache-Headers'), TRUE); + if (is_null($headers)) { + $this->fail('No headers were retrieved from the page cache.'); + } + else { + $this->assertEqual($headers['X-Content-Type-Options'], 'nosniff', 'X-Content-Type-Options header retrieved from response in the page cache.'); + } + } } class BootstrapVariableTestCase extends DrupalWebTestCase { diff --git a/modules/simpletest/tests/common.test b/modules/simpletest/tests/common.test index f5b85bad0b..fde70b1385 100644 --- a/modules/simpletest/tests/common.test +++ b/modules/simpletest/tests/common.test @@ -91,6 +91,12 @@ class CommonURLUnitTest extends DrupalWebTestCase { $link = l($text, $path); $sanitized_path = check_url(url($path)); $this->assertTrue(strpos($link, $sanitized_path) !== FALSE, format_string('XSS attack @path was filtered', array('@path' => $path))); + + // Verify that a dangerous protocol is sanitized. + $text = $this->randomName(); + $path = "javascript:alert('XSS')"; + $link = l($text, $path, array('external' => TRUE)); + $this->assertTrue(strpos($link, 'javascript:') === FALSE, 'Dangerous protocol javascript: was sanitized.'); } /* @@ -480,6 +486,15 @@ class CommonXssUnitTest extends DrupalUnitTestCase { * Check that invalid multi-byte sequences are rejected. */ function testInvalidMultiByte() { + // Ignore PHP 8.0+ null deprecations. + $text = check_plain(NULL); + $this->assertEqual($text, '', 'check_plain() casts null to string'); + $text = check_plain(FALSE); + $this->assertEqual($text, '', 'check_plain() casts boolean to string'); + $text = filter_xss(NULL); + $this->assertEqual($text, '', 'filter_xss() casts null to string'); + $text = filter_xss(FALSE); + $this->assertEqual($text, '', 'filter_xss() casts boolean to string'); // Ignore PHP 5.3+ invalid multibyte sequence warning. $text = @check_plain("Foo\xC0barbaz"); $this->assertEqual($text, '', 'check_plain() rejects invalid sequence "Foo\xC0barbaz"'); @@ -1182,6 +1197,10 @@ class DrupalHTTPRequestTestCase extends DrupalWebTestCase { $redirect_301 = drupal_http_request(url('system-test/redirect/301', array('absolute' => TRUE)), array('max_redirects' => 0)); $this->assertFalse(isset($redirect_301->redirect_code), 'drupal_http_request does not follow 301 redirect if max_redirects = 0.'); + $redirect_invalid = drupal_http_request(url('system-test/redirect-protocol-relative', array('absolute' => TRUE)), array('max_redirects' => 1)); + $this->assertEqual($redirect_invalid->code, -1002, format_string('301 redirect to protocol-relative URL returned with error code !error.', array('!error' => $redirect_invalid->error))); + $this->assertEqual($redirect_invalid->error, 'missing schema', format_string('301 redirect to protocol-relative URL returned with error message "!error".', array('!error' => $redirect_invalid->error))); + $redirect_invalid = drupal_http_request(url('system-test/redirect-noscheme', array('absolute' => TRUE)), array('max_redirects' => 1)); $this->assertEqual($redirect_invalid->code, -1002, format_string('301 redirect to invalid URL returned with error code !error.', array('!error' => $redirect_invalid->error))); $this->assertEqual($redirect_invalid->error, 'missing schema', format_string('301 redirect to invalid URL returned with error message "!error".', array('!error' => $redirect_invalid->error))); @@ -1231,6 +1250,44 @@ class DrupalHTTPRequestTestCase extends DrupalWebTestCase { } } +/** + * Unit tests for processing of http redirects. + */ +class DrupalHTTPRedirectTest extends DrupalUnitTestCase { + + public static function getInfo() { + return array( + 'name' => 'Drupal HTTP redirect processing', + 'description' => 'Perform unit tests on processing of http redirects.', + 'group' => 'System', + ); + } + + public function testHttpsDowngrade() { + $url = 'https://example.com/foo'; + $location = 'http://example.com/bar'; + $this->assertTrue(_drupal_should_strip_sensitive_headers_on_http_redirect($url, $location), 'Sensitive headers are stripped on HTTPS downgrade.'); + } + + public function testNoHttpsDowngrade() { + $url = 'https://example.com/foo'; + $location = 'https://example.com/bar'; + $this->assertFalse(_drupal_should_strip_sensitive_headers_on_http_redirect($url, $location), 'Sensitive headers are not stripped without HTTPS downgrade.'); + } + + public function testHostChange() { + $url = 'https://example.com/foo'; + $location = 'https://www.example.com/bar'; + $this->assertTrue(_drupal_should_strip_sensitive_headers_on_http_redirect($url, $location), 'Sensitive headers are stripped on change of host.'); + } + + public function testNoHostChange() { + $url = 'http://example.com/foo'; + $location = 'http://example.com/bar'; + $this->assertFalse(_drupal_should_strip_sensitive_headers_on_http_redirect($url, $location), 'Sensitive headers are not stripped without change of host.'); + } +} + /** * Tests parsing of the HTTP response status line. */ @@ -1484,8 +1541,8 @@ class JavaScriptTestCase extends DrupalWebTestCase { */ function testAddSetting() { $javascript = drupal_add_js(array('drupal' => 'rocks', 'dries' => 280342800), 'setting'); - $this->assertEqual(280342800, $javascript['settings']['data'][2]['dries'], 'JavaScript setting is set correctly.'); - $this->assertEqual('rocks', $javascript['settings']['data'][2]['drupal'], 'The other JavaScript setting is set correctly.'); + $this->assertEqual(280342800, $javascript['settings']['data'][3]['dries'], 'JavaScript setting is set correctly.'); + $this->assertEqual('rocks', $javascript['settings']['data'][3]['drupal'], 'The other JavaScript setting is set correctly.'); } /** @@ -1923,6 +1980,18 @@ class JavaScriptTestCase extends DrupalWebTestCase { $this->assertRaw(drupal_get_path('module', 'node') . '/node.js?' . $query_string, 'Query string was appended correctly to js.'); } + /** + * Tests that the set_has_js_cookie variable is reflected in Drupal.settings. + */ + function testSetHasJsCookie() { + $this->drupalGet(''); + $this->assertRaw('"setHasJsCookie":0', 'setHasJsCookie set to 0 by default.'); + + variable_set('set_has_js_cookie', TRUE); + $this->drupalGet(''); + $this->assertRaw('"setHasJsCookie":1', 'setHasJsCookie set to 1 when set_has_js_cookie is set to TRUE.'); + } + /** * Resets static variables related to adding JavaScript to a page. */ @@ -1968,6 +2037,11 @@ class DrupalRenderTestCase extends DrupalWebTestCase { 'value' => '', 'expected' => '', ), + array( + 'name' => 'not a render array', + 'value' => 'this is not an array', + 'expected' => '', + ), array( 'name' => 'no access', 'value' => array( @@ -3297,3 +3371,48 @@ class BlockInterestCohortTest extends DrupalWebTestCase { } } + +/** + * Test for drupal_add_html_head_link(). + */ +class DrupalAddHtmlHeadLinkTest extends DrupalWebTestCase { + + /** + * {@inheritdoc} + */ + public static function getInfo() { + return array( + 'name' => 'Add HTML head link', + 'description' => 'Test for drupal_add_html_head_link().', + 'group' => 'System', + ); + } + + /** + * {@inheritdoc} + */ + function setUp() { + parent::setUp('common_test'); + } + + /** + * Tests drupal_add_html_head_link(). + */ + function testDrupalAddHtmlHeadLink() { + $this->drupalGet('common-test/html_head_link'); + $expected_link_header = implode(',', array( + '; rel="alternate"', + '; hreflang="nl"; rel="alternate"', + '; hreflang="de"; rel="alternate"', + '; hreflang="en"; rel="alternate"', + )); + $this->assertEqual($this->drupalGetHeader('Link'), $expected_link_header); + + // Check that duplicate alternate URLs with different hreflangs are allowed. + $test_link = $this->xpath('//head/link[@rel="alternate"][@href="/foo/bar"]'); + $this->assertEqual(count($test_link), 2, 'Duplicate alternate URLs are allowed.'); + // Check that link element attributes are HTML-encoded. + $this->assertRaw(''); + } + +} diff --git a/modules/simpletest/tests/common_test.info b/modules/simpletest/tests/common_test.info index f1fc039563..e9c6bcccdf 100644 --- a/modules/simpletest/tests/common_test.info +++ b/modules/simpletest/tests/common_test.info @@ -7,7 +7,7 @@ stylesheets[all][] = common_test.css stylesheets[print][] = common_test.print.css hidden = TRUE -; Information added by Drupal.org packaging script on 2021-07-21 -version = "7.82" +; Information added by Drupal.org packaging script on 2022-09-07 +version = "7.92" project = "drupal" -datestamp = "1626883669" +datestamp = "1662554078" diff --git a/modules/simpletest/tests/common_test.module b/modules/simpletest/tests/common_test.module index d7b97bc892..863d6b02f5 100644 --- a/modules/simpletest/tests/common_test.module +++ b/modules/simpletest/tests/common_test.module @@ -64,6 +64,12 @@ function common_test_menu() { 'access arguments' => array('access content'), 'type' => MENU_CALLBACK, ); + $items['common-test/html_head_link'] = array( + 'title' => 'Test HTML head link', + 'page callback' => 'common_test_html_head_link', + 'access arguments' => array('access content'), + 'type' => MENU_CALLBACK, + ); return $items; } @@ -314,3 +320,33 @@ function existing_permissions_policy_header() { drupal_add_http_header('Permissions-Policy', 'geolocation=()'); print __FUNCTION__; } + +/** + * Page callback. + */ +function common_test_html_head_link() { + drupal_add_html_head_link(array( + 'href' => '/foo?bar=baz', + 'rel' => 'alternate', + ), TRUE); + drupal_add_html_head_link(array( + 'href' => '/not-added-to-http-headers', + 'rel' => 'alternate', + ), FALSE); + drupal_add_html_head_link(array( + 'href' => '/foo/bar', + 'hreflang' => 'nl', + 'rel' => 'alternate', + ), TRUE); + drupal_add_html_head_link(array( + 'href' => '/foo/bar', + 'hreflang' => 'de', + 'rel' => 'alternate', + ), TRUE); + drupal_add_html_head_link(array( + 'href' => '/foo?bar=baz&baz=false', + 'hreflang' => 'en', + 'rel' => 'alternate', + ), TRUE); + return ''; +} diff --git a/modules/simpletest/tests/common_test_cron_helper.info b/modules/simpletest/tests/common_test_cron_helper.info index c012d8879f..f9cff27bc7 100644 --- a/modules/simpletest/tests/common_test_cron_helper.info +++ b/modules/simpletest/tests/common_test_cron_helper.info @@ -5,7 +5,7 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2021-07-21 -version = "7.82" +; Information added by Drupal.org packaging script on 2022-09-07 +version = "7.92" project = "drupal" -datestamp = "1626883669" +datestamp = "1662554078" diff --git a/modules/simpletest/tests/database_test.info b/modules/simpletest/tests/database_test.info index 8df294b023..a9dac596b8 100644 --- a/modules/simpletest/tests/database_test.info +++ b/modules/simpletest/tests/database_test.info @@ -5,7 +5,7 @@ package = Testing version = VERSION hidden = TRUE -; Information added by Drupal.org packaging script on 2021-07-21 -version = "7.82" +; Information added by Drupal.org packaging script on 2022-09-07 +version = "7.92" project = "drupal" -datestamp = "1626883669" +datestamp = "1662554078" diff --git a/modules/simpletest/tests/database_test.install b/modules/simpletest/tests/database_test.install index 44ed5ee0a4..ad19430d8b 100644 --- a/modules/simpletest/tests/database_test.install +++ b/modules/simpletest/tests/database_test.install @@ -54,6 +54,44 @@ function database_test_schema() { ), ); + $schema['test_classtype'] = array( + 'description' => 'A duplicate version of the test table, used for fetch_style PDO::FETCH_CLASSTYPE tests.', + 'fields' => array( + 'classname' => array( + 'description' => "A custom class name", + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + 'default' => '', + ), + 'name' => array( + 'description' => "A person's name", + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + 'default' => '', + ), + 'age' => array( + 'description' => "The person's age", + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + 'default' => 0, + ), + 'job' => array( + 'description' => "The person's job", + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + 'default' => '', + ), + ), + 'primary key' => array('job'), + 'indexes' => array( + 'ages' => array('age'), + ), + ); + // This is an alternate version of the same table that is structured the same // but has a non-serial Primary Key. $schema['test_people'] = array( @@ -236,5 +274,7 @@ function database_test_schema() { 'primary key' => array('id'), ); + $schema['TEST_UPPERCASE'] = $schema['test']; + return $schema; } diff --git a/modules/simpletest/tests/database_test.test b/modules/simpletest/tests/database_test.test index 0f0d2c3209..310178e1bb 100644 --- a/modules/simpletest/tests/database_test.test +++ b/modules/simpletest/tests/database_test.test @@ -22,11 +22,13 @@ class DatabaseTestCase extends DrupalWebTestCase { parent::setUp('database_test'); $schema['test'] = drupal_get_schema('test'); + $schema['test_classtype'] = drupal_get_schema('test_classtype'); $schema['test_people'] = drupal_get_schema('test_people'); $schema['test_people_copy'] = drupal_get_schema('test_people_copy'); $schema['test_one_blob'] = drupal_get_schema('test_one_blob'); $schema['test_two_blobs'] = drupal_get_schema('test_two_blobs'); $schema['test_task'] = drupal_get_schema('test_task'); + $schema['TEST_UPPERCASE'] = drupal_get_schema('TEST_UPPERCASE'); $this->installTables($schema); @@ -117,6 +119,15 @@ class DatabaseTestCase extends DrupalWebTestCase { )) ->execute(); + db_insert('test_classtype') + ->fields(array( + 'classname' => 'FakeRecord', + 'name' => 'Kay', + 'age' => 26, + 'job' => 'Web Developer', + )) + ->execute(); + db_insert('test_people') ->fields(array( 'name' => 'Meredith', @@ -314,6 +325,10 @@ class DatabaseSelectCloneTest extends DatabaseTestCase { $query->condition('id', $subquery, 'IN'); $clone = clone $query; + + // Cloned query should have a different unique identifier. + $this->assertNotEqual($query->uniqueIdentifier(), $clone->uniqueIdentifier()); + // Cloned query should not be altered by the following modification // happening on original query. $subquery->condition('age', 25, '>'); @@ -325,6 +340,33 @@ class DatabaseSelectCloneTest extends DatabaseTestCase { $this->assertEqual(3, $clone_result, 'The cloned query returns the expected number of rows'); $this->assertEqual(2, $query_result, 'The query returns the expected number of rows'); } + + /** + * Tests that nested SELECT queries are cloned properly. + */ + public function testNestedQueryCloning() { + $sub_query = db_select('test', 't'); + $sub_query->addField('t', 'id', 'id'); + $sub_query->condition('age', 28, '<'); + + $query = db_select($sub_query, 't'); + + $clone = clone $query; + + // Cloned query should have a different unique identifier. + $this->assertNotEqual($query->uniqueIdentifier(), $clone->uniqueIdentifier()); + + // Cloned query should not be altered by the following modification + // happening on original query. + $sub_query->condition('age', 25, '>'); + + $clone_result = $clone->countQuery()->execute()->fetchField(); + $query_result = $query->countQuery()->execute()->fetchField(); + + // Make sure the cloned query has not been modified. + $this->assertEqual(3, $clone_result, 'The cloned query returns the expected number of rows'); + $this->assertEqual(2, $query_result, 'The query returns the expected number of rows'); + } } /** @@ -406,6 +448,27 @@ class DatabaseFetchTestCase extends DatabaseTestCase { $this->assertIdentical(count($records), 1, 'There is only one record.'); } + + /** + * Confirms that we can fetch a record into a new instance of a custom class. + * The name of the class is determined from a value of the first column. + * + * @see FakeRecord + */ + function testQueryFetchClasstype() { + $records = array(); + $result = db_query('SELECT classname, name, job FROM {test_classtype} WHERE age = :age', array(':age' => 26), array('fetch' => PDO::FETCH_CLASS | PDO::FETCH_CLASSTYPE)); + foreach ($result as $record) { + $records[] = $record; + if ($this->assertTrue($record instanceof FakeRecord, 'Record is an object of class FakeRecord.')) { + $this->assertIdentical($record->name, 'Kay', 'Kay is found.'); + $this->assertIdentical($record->job, 'Web Developer', 'A 26 year old Web Developer.'); + } + $this->assertFalse(isset($record->classname), 'Classname field not found, as intended.'); + } + + $this->assertIdentical(count($records), 1, 'There is only one record.'); + } } /** @@ -867,28 +930,30 @@ class DatabaseUpdateTestCase extends DatabaseTestCase { ->fields(array('age' => 1)) ->execute(); - // Ensure that expressions are handled properly. This should set every - // record's age to a square of itself, which will change only three of the - // four records in the table since 1*1 = 1. That means only three records - // are modified, so we should get back 3, not 4, from execute(). + // Ensure that expressions are handled properly. This should set every + // record's age to a square of itself. The number of affected rows returned + // by db_update() will vary across different database engines and versions: + // - MySQL / InnoDB: changed rows only + // - MySQL / MyISAM: changed rows only + // - PostgreSQL: all rows matched by the query + // - SQLite: changed rows only (see workaround in UpdateQuery_sqlite) + // All database engines will change only three of the four records in the + // table since 1*1 = 1. However, the pgsql driver will return the number of + // rows matched rather than the number changed. + // @see https://www.drupal.org/project/drupal/issues/805858 + // @see https://www.drupal.org/project/drupal/issues/3264471 + $connection = Database::getConnection()->getConnectionOptions(); + $expected_rows = 3; + if ($connection['driver'] === 'pgsql') { + $expected_rows ++; + } $num_rows = db_update('test') ->expression('age', 'age * age') ->execute(); - $this->assertIdentical($num_rows, 3, 'Number of affected rows are returned.'); - } - - /** - * Confirm that we can update the primary key of a record successfully. - */ - function testPrimaryKeyUpdate() { - $num_updated = db_update('test') - ->fields(array('id' => 42, 'name' => 'John')) - ->condition('id', 1) - ->execute(); - $this->assertIdentical($num_updated, 1, 'Updated 1 record.'); + $this->assertIdentical($num_rows, $expected_rows, 'Number of affected rows are returned.'); - $saved_name= db_query('SELECT name FROM {test} WHERE id = :id', array(':id' => 42))->fetchField(); - $this->assertIdentical($saved_name, 'John', 'Updated primary key successfully.'); + $saved_name = db_query('SELECT name FROM {test} WHERE age = :age', array(':age' => pow(26, 2)))->fetchField(); + $this->assertIdentical('Paul', $saved_name, 'Successfully updated values using an algebraic expression.'); } } @@ -1605,7 +1670,7 @@ class DatabaseSelectTestCase extends DatabaseTestCase { /** * Test that we can UNION multiple Select queries together. This is - * semantically equal to UNION DISTINCT, so we don't explicity test that. + * semantically equal to UNION DISTINCT, so we don't explicitly test that. */ function testUnion() { $query_1 = db_select('test', 't') @@ -1614,7 +1679,8 @@ class DatabaseSelectTestCase extends DatabaseTestCase { $query_2 = db_select('test', 't') ->fields('t', array('name')) - ->condition('age', 28); + ->condition('age', 28) + ->orderBy('name'); $query_1->union($query_2); @@ -1651,6 +1717,64 @@ class DatabaseSelectTestCase extends DatabaseTestCase { $this->assertEqual($names[2], 'Ringo', 'Third query returned correct name.'); } + /** + * Tests that we can UNION multiple Select queries together and set the ORDER. + */ + function testUnionOrder() { + // This gives George and Ringo. + $query_1 = db_select('test', 't') + ->fields('t', array('name')) + ->condition('age', array(27, 28), 'IN'); + + // This gives Paul. + $query_2 = db_select('test', 't') + ->fields('t', array('name')) + ->condition('age', 26); + + $query_1->union($query_2); + $query_1->orderBy('name', 'DESC'); + + $names = $query_1->execute()->fetchCol(); + + // Ensure we get all 3 records. + $this->assertEqual(count($names), 3, 'UNION returned rows from both queries.'); + + // Ensure that the names are in the correct reverse alphabetical order, + // regardless of which query they came from. + $this->assertEqual($names[0], 'Ringo', 'First query returned correct name.'); + $this->assertEqual($names[1], 'Paul', 'Second query returned correct name.'); + $this->assertEqual($names[2], 'George', 'Third query returned correct name.'); + } + + /** + * Tests that we can UNION multiple Select queries together with a LIMIT. + */ + function testUnionOrderLimit() { + // This gives George and Ringo. + $query_1 = db_select('test', 't') + ->fields('t', array('name')) + ->condition('age', array(27, 28), 'IN'); + + // This gives Paul. + $query_2 = db_select('test', 't') + ->fields('t', array('name')) + ->condition('age', 26); + + $query_1->union($query_2); + $query_1->orderBy('name', 'DESC'); + $query_1->range(0, 2); + + $names = $query_1->execute()->fetchCol(); + + // Ensure we get only 2 of the 3 records. + $this->assertEqual(count($names), 2, 'UNION with a limit returned rows from both queries.'); + + // Ensure that the names are in the correct reverse alphabetical order, + // regardless of which query they came from. + $this->assertEqual($names[0], 'Ringo', 'First query returned correct name.'); + $this->assertEqual($names[1], 'Paul', 'Second query returned correct name.'); + } + /** * Test that random ordering of queries works. * @@ -2316,6 +2440,51 @@ class DatabaseSelectComplexTestCase extends DatabaseTestCase { $this->assertNotEqual($crowded_job->name, $crowded_job->othername, 'Correctly joined same table twice.'); } + /** + * Test that join conditions can use Condition objects. + */ + function testJoinConditionObject() { + // Same test as testDefaultJoin, but with a Condition object. + $query = db_select('test_task', 't'); + $join_cond = db_and()->where('t.pid = p.id'); + $people_alias = $query->join('test', 'p', $join_cond); + $name_field = $query->addField($people_alias, 'name', 'name'); + $query->addField('t', 'task', 'task'); + $priority_field = $query->addField('t', 'priority', 'priority'); + + $query->orderBy($priority_field); + $result = $query->execute(); + + $num_records = 0; + $last_priority = 0; + foreach ($result as $record) { + $num_records++; + $this->assertTrue($record->$priority_field >= $last_priority, 'Results returned in correct order.'); + $this->assertNotEqual($record->$name_field, 'Ringo', 'Taskless person not selected.'); + $last_priority = $record->$priority_field; + } + + $this->assertEqual($num_records, 7, 'Returned the correct number of rows.'); + + // Test a condition object that creates placeholders. + $t1_name = 'John'; + $t2_name = 'George'; + $join_cond = db_and() + ->condition('t1.name', $t1_name) + ->condition('t2.name', $t2_name); + $query = db_select('test', 't1'); + $query->innerJoin('test', 't2', $join_cond); + $query->addField('t1', 'name', 't1_name'); + $query->addField('t2', 'name', 't2_name'); + + $num_records = $query->countQuery()->execute()->fetchField(); + $this->assertEqual($num_records, 1, 'Query expected to return 1 row. Actual: ' . $num_records); + if ($num_records == 1) { + $record = $query->execute()->fetchObject(); + $this->assertEqual($record->t1_name, $t1_name, 'Query expected to retrieve name ' . $t1_name . ' from table t1. Actual: ' . $record->t1_name); + $this->assertEqual($record->t2_name, $t2_name, 'Query expected to retrieve name ' . $t2_name . ' from table t2. Actual: ' . $record->t2_name); + } + } } /** @@ -2507,32 +2676,32 @@ class DatabaseSelectPagerDefaultTestCase extends DatabaseTestCase { function testElementNumbers() { $_GET['page'] = '3, 2, 1, 0'; - $name = db_select('test', 't')->extend('PagerDefault') - ->element(2) + $query = db_select('test', 't')->extend('PagerDefault'); + $query->element(2) ->fields('t', array('name')) ->orderBy('age') - ->limit(1) - ->execute() - ->fetchField(); + ->limit(1); + $this->assertEqual(2, $query->getElement()); + $name = $query->execute()->fetchField(); $this->assertEqual($name, 'Paul', 'Pager query #1 with a specified element ID returned the correct results.'); // Setting an element smaller than the previous one // should not overwrite the pager $maxElement with a smaller value. - $name = db_select('test', 't')->extend('PagerDefault') - ->element(1) + $query = db_select('test', 't')->extend('PagerDefault'); + $query->element(1) ->fields('t', array('name')) ->orderBy('age') - ->limit(1) - ->execute() - ->fetchField(); + ->limit(1); + $this->assertEqual(1, $query->getElement()); + $name = $query->execute()->fetchField(); $this->assertEqual($name, 'George', 'Pager query #2 with a specified element ID returned the correct results.'); - $name = db_select('test', 't')->extend('PagerDefault') - ->fields('t', array('name')) + $query = db_select('test', 't')->extend('PagerDefault'); + $query->fields('t', array('name')) ->orderBy('age') - ->limit(1) - ->execute() - ->fetchField(); + ->limit(1); + $this->assertEqual(3, $query->getElement()); + $name = $query->execute()->fetchField(); $this->assertEqual($name, 'John', 'Pager query #3 with a generated element ID returned the correct results.'); unset($_GET['page']); @@ -3463,6 +3632,67 @@ class DatabaseQueryTestCase extends DatabaseTestCase { ->fetchField(); $this->assertFalse($result, 'SQL injection attempt did not result in a row being inserted in the database table.'); } + + /** + * Tests SQL injection via condition operator. + */ + public function testConditionOperatorArgumentsSQLInjection() { + + $injection = "IS NOT NULL); INSERT INTO {test} (name) VALUES ('test12345678'); -- "; + try { + $result = db_select('test', 't') + ->fields('t') + ->condition('name', 1, $injection) + ->execute(); + $this->fail('Should not be able to attempt SQL injection via condition operator.'); + } + catch (InvalidQueryConditionOperatorException $e) { + $this->pass('SQL injection attempt via condition arguments should result in a database exception.'); + } + + // Test that the insert query that was used in the SQL injection attempt did + // not result in a row being inserted in the database. + $result = db_select('test') + ->condition('name', 'test12345678') + ->countQuery() + ->execute() + ->fetchField(); + $this->assertFalse($result, 'SQL injection attempt did not result in a row being inserted in the database table.'); + + // Attempt SQLi via union query with no unsafe characters. + db_insert('test') + ->fields(array('name' => '123456')) + ->execute(); + + $injection = "= 1 UNION ALL SELECT password FROM user WHERE uid ="; + try { + $result = db_select('test', 't') + ->fields('t', array('name', 'name')) + ->condition('name', 1, $injection) + ->execute(); + $this->fail('Should not be able to attempt SQL injection via operator.'); + } + catch (InvalidQueryConditionOperatorException $e) { + $this->pass('SQL injection attempt via condition arguments should result in a database exception.'); + } + + // Attempt SQLi via union query - uppercase tablename. + db_insert('TEST_UPPERCASE') + ->fields(array('name' => 'secrets')) + ->execute(); + + $injection = "IS NOT NULL) UNION ALL SELECT name FROM {TEST_UPPERCASE} -- "; + try { + $result = db_select('test', 't') + ->fields('t', array('name')) + ->condition('name', 1, $injection) + ->execute(); + $this->fail('Should not be able to attempt SQL injection via operator.'); + } + catch (InvalidQueryConditionOperatorException $e) { + $this->pass('SQL injection attempt via condition arguments should result in a database exception.'); + } + } } /** @@ -4293,6 +4523,13 @@ class DatabaseReservedKeywordTestCase extends DatabaseTestCase { } else { $database = $connection['database']; + + if ($connection['driver'] === 'pgsql') { + // database.scheme.table for PostgreSQL + // @see https://www.postgresql.org/docs/14/ddl-schemas.html + $database .= '.public'; + } + // Test db_query with schema.{table} pattern db_query('SELECT * FROM ' . $database . '.{system} LIMIT 1')->fetchObject(); $this->assertTrue(isset($record->filename), 'Successfully queried the schema.{system} table.'); @@ -4416,6 +4653,13 @@ class DatabaseTablePrefixTestCase extends DatabaseTestCase { } $db_name = $connection_options['database']; + + if ($connection_options['driver'] === 'pgsql') { + // database.scheme.table for PostgreSQL + // @see https://www.postgresql.org/docs/14/ddl-schemas.html + $db_name .= '.public'; + } + // This prefix is usually something like simpletest12345 $test_prefix = $connection_options['prefix']['default']; diff --git a/modules/simpletest/tests/drupal_autoload_test/drupal_autoload_test.info b/modules/simpletest/tests/drupal_autoload_test/drupal_autoload_test.info index 0b077fc068..1db5a80b5f 100644 --- a/modules/simpletest/tests/drupal_autoload_test/drupal_autoload_test.info +++ b/modules/simpletest/tests/drupal_autoload_test/drupal_autoload_test.info @@ -7,7 +7,7 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2021-07-21 -version = "7.82" +; Information added by Drupal.org packaging script on 2022-09-07 +version = "7.92" project = "drupal" -datestamp = "1626883669" +datestamp = "1662554078" diff --git a/modules/simpletest/tests/drupal_system_listing_compatible_test/drupal_system_listing_compatible_test.info b/modules/simpletest/tests/drupal_system_listing_compatible_test/drupal_system_listing_compatible_test.info index 8eb87b206e..0c9ca69460 100644 --- a/modules/simpletest/tests/drupal_system_listing_compatible_test/drupal_system_listing_compatible_test.info +++ b/modules/simpletest/tests/drupal_system_listing_compatible_test/drupal_system_listing_compatible_test.info @@ -5,7 +5,7 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2021-07-21 -version = "7.82" +; Information added by Drupal.org packaging script on 2022-09-07 +version = "7.92" project = "drupal" -datestamp = "1626883669" +datestamp = "1662554078" diff --git a/modules/simpletest/tests/drupal_system_listing_incompatible_test/drupal_system_listing_incompatible_test.info b/modules/simpletest/tests/drupal_system_listing_incompatible_test/drupal_system_listing_incompatible_test.info index 5939450452..eb38a64f96 100644 --- a/modules/simpletest/tests/drupal_system_listing_incompatible_test/drupal_system_listing_incompatible_test.info +++ b/modules/simpletest/tests/drupal_system_listing_incompatible_test/drupal_system_listing_incompatible_test.info @@ -5,7 +5,7 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2021-07-21 -version = "7.82" +; Information added by Drupal.org packaging script on 2022-09-07 +version = "7.92" project = "drupal" -datestamp = "1626883669" +datestamp = "1662554078" diff --git a/modules/simpletest/tests/entity_cache_test.info b/modules/simpletest/tests/entity_cache_test.info index 1688292117..05fe08084d 100644 --- a/modules/simpletest/tests/entity_cache_test.info +++ b/modules/simpletest/tests/entity_cache_test.info @@ -6,7 +6,7 @@ core = 7.x dependencies[] = entity_cache_test_dependency hidden = TRUE -; Information added by Drupal.org packaging script on 2021-07-21 -version = "7.82" +; Information added by Drupal.org packaging script on 2022-09-07 +version = "7.92" project = "drupal" -datestamp = "1626883669" +datestamp = "1662554078" diff --git a/modules/simpletest/tests/entity_cache_test_dependency.info b/modules/simpletest/tests/entity_cache_test_dependency.info index 32234c4a62..b9feaf393e 100644 --- a/modules/simpletest/tests/entity_cache_test_dependency.info +++ b/modules/simpletest/tests/entity_cache_test_dependency.info @@ -5,7 +5,7 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2021-07-21 -version = "7.82" +; Information added by Drupal.org packaging script on 2022-09-07 +version = "7.92" project = "drupal" -datestamp = "1626883669" +datestamp = "1662554078" diff --git a/modules/simpletest/tests/entity_crud.test b/modules/simpletest/tests/entity_crud.test index be15977902..c7d66509a2 100644 --- a/modules/simpletest/tests/entity_crud.test +++ b/modules/simpletest/tests/entity_crud.test @@ -46,4 +46,16 @@ class EntityLoadTestCase extends DrupalWebTestCase { $this->assertIdentical($nodes_loaded[$node_2->nid], $all_nodes[$node_2->nid], 'Loaded node 2 is identical to cached node.'); $this->assertIdentical($nodes_loaded[$node_3->nid], $all_nodes[$node_3->nid], 'Loaded node 3 is identical to cached node.'); } + + public function testEntityLoadIds() { + $this->drupalCreateNode(array('title' => 'Node 1')); + $this->drupalCreateNode(array('title' => 'Node 2')); + + $nodes_loaded = entity_load('node', array('1', '2')); + $this->assertEqual(count($nodes_loaded), 2); + + // Ensure that an id with a trailing decimal place is ignored. + $nodes_loaded = entity_load('node', array('1.', '2')); + $this->assertEqual(count($nodes_loaded), 1); + } } diff --git a/modules/simpletest/tests/entity_crud_hook_test.info b/modules/simpletest/tests/entity_crud_hook_test.info index ea7a78a5f8..9b95541e70 100644 --- a/modules/simpletest/tests/entity_crud_hook_test.info +++ b/modules/simpletest/tests/entity_crud_hook_test.info @@ -5,7 +5,7 @@ package = Testing version = VERSION hidden = TRUE -; Information added by Drupal.org packaging script on 2021-07-21 -version = "7.82" +; Information added by Drupal.org packaging script on 2022-09-07 +version = "7.92" project = "drupal" -datestamp = "1626883669" +datestamp = "1662554078" diff --git a/modules/simpletest/tests/entity_query_access_test.info b/modules/simpletest/tests/entity_query_access_test.info index 1444a0b3d8..baa7aedd2d 100644 --- a/modules/simpletest/tests/entity_query_access_test.info +++ b/modules/simpletest/tests/entity_query_access_test.info @@ -5,7 +5,7 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2021-07-21 -version = "7.82" +; Information added by Drupal.org packaging script on 2022-09-07 +version = "7.92" project = "drupal" -datestamp = "1626883669" +datestamp = "1662554078" diff --git a/modules/simpletest/tests/error_test.info b/modules/simpletest/tests/error_test.info index 5620431ce4..5b6af5bcf9 100644 --- a/modules/simpletest/tests/error_test.info +++ b/modules/simpletest/tests/error_test.info @@ -5,7 +5,7 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2021-07-21 -version = "7.82" +; Information added by Drupal.org packaging script on 2022-09-07 +version = "7.92" project = "drupal" -datestamp = "1626883669" +datestamp = "1662554078" diff --git a/modules/simpletest/tests/file.test b/modules/simpletest/tests/file.test index 57b315fb0e..ff8cb2f7a6 100644 --- a/modules/simpletest/tests/file.test +++ b/modules/simpletest/tests/file.test @@ -108,6 +108,7 @@ class FileTestCase extends DrupalWebTestCase { // Mask out all but the last three octets. $actual_mode = fileperms($filepath) & 0777; + $expected_mode = $expected_mode & 0777; // PHP on Windows has limited support for file permissions. Usually each of // "user", "group" and "other" use one octal digit (3 bits) to represent the @@ -143,6 +144,7 @@ class FileTestCase extends DrupalWebTestCase { // Mask out all but the last three octets. $actual_mode = fileperms($directory) & 0777; + $expected_mode = $expected_mode & 0777; // PHP on Windows has limited support for file permissions. Usually each of // "user", "group" and "other" use one octal digit (3 bits) to represent the @@ -1055,6 +1057,39 @@ class FileDirectoryTest extends FileTestCase { ); } + /** + * Test local directory handling functions. + */ + function testFileCheckLocalDirectoryHandling() { + $directory = file_default_scheme() . '://'; + + // Check a new recursively created local directory for correct file system + // permissions. + $parent = $this->randomName(); + $child = $this->randomName(); + + // Files directory already exists. + $this->assertTrue(is_dir($directory), 'Files directory already exists.', 'File'); + // Make files directory writable only. + $old_mode = fileperms($directory); + + // Create the directories. + $parent_path = $directory . DIRECTORY_SEPARATOR . $parent; + $child_path = $parent_path . DIRECTORY_SEPARATOR . $child; + $this->assertTrue(drupal_mkdir($child_path, 0775, TRUE), 'No error reported when creating new local directories.', 'File'); + + // Ensure new directories also exist. + $this->assertTrue(is_dir($parent_path), 'New parent directory actually exists.', 'File'); + $this->assertTrue(is_dir($child_path), 'New child directory actually exists.', 'File'); + + // Check that new directory permissions were set properly. + $this->assertDirectoryPermissions($parent_path, 0775); + $this->assertDirectoryPermissions($child_path, 0775); + + // Check that existing directory permissions were not modified. + $this->assertDirectoryPermissions($directory, $old_mode); + } + /** * Test directory handling functions. */ diff --git a/modules/simpletest/tests/file_test.info b/modules/simpletest/tests/file_test.info index ad2dc10187..51d51cecff 100644 --- a/modules/simpletest/tests/file_test.info +++ b/modules/simpletest/tests/file_test.info @@ -6,7 +6,7 @@ core = 7.x files[] = file_test.module hidden = TRUE -; Information added by Drupal.org packaging script on 2021-07-21 -version = "7.82" +; Information added by Drupal.org packaging script on 2022-09-07 +version = "7.92" project = "drupal" -datestamp = "1626883669" +datestamp = "1662554078" diff --git a/modules/simpletest/tests/filetransfer.test b/modules/simpletest/tests/filetransfer.test index 905d23cab7..617e6cc021 100644 --- a/modules/simpletest/tests/filetransfer.test +++ b/modules/simpletest/tests/filetransfer.test @@ -114,7 +114,7 @@ class TestFileTransfer extends FileTransfer { $parts = explode(':', $this->hostname); $port = (count($parts) == 2) ? $parts[1] : $this->port; $this->connection = new MockTestConnection(); - $this->connection->connectionString = 'test://' . urlencode($this->username) . ':' . urlencode($this->password) . "@$this->host:$this->port/"; + $this->connection->connectionString = 'test://' . urlencode((string) $this->username) . ':' . urlencode((string) $this->password) . "@$this->host:$this->port/"; } function copyFileJailed($source, $destination) { diff --git a/modules/simpletest/tests/filter_test.info b/modules/simpletest/tests/filter_test.info index 5e54e1d2fe..3eee6e431a 100644 --- a/modules/simpletest/tests/filter_test.info +++ b/modules/simpletest/tests/filter_test.info @@ -5,7 +5,7 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2021-07-21 -version = "7.82" +; Information added by Drupal.org packaging script on 2022-09-07 +version = "7.92" project = "drupal" -datestamp = "1626883669" +datestamp = "1662554078" diff --git a/modules/simpletest/tests/form_test.info b/modules/simpletest/tests/form_test.info index b871932c7c..99a4bd0db2 100644 --- a/modules/simpletest/tests/form_test.info +++ b/modules/simpletest/tests/form_test.info @@ -5,7 +5,7 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2021-07-21 -version = "7.82" +; Information added by Drupal.org packaging script on 2022-09-07 +version = "7.92" project = "drupal" -datestamp = "1626883669" +datestamp = "1662554078" diff --git a/modules/simpletest/tests/image_test.info b/modules/simpletest/tests/image_test.info index 4e3f8acde7..f2d612da8c 100644 --- a/modules/simpletest/tests/image_test.info +++ b/modules/simpletest/tests/image_test.info @@ -5,7 +5,7 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2021-07-21 -version = "7.82" +; Information added by Drupal.org packaging script on 2022-09-07 +version = "7.92" project = "drupal" -datestamp = "1626883669" +datestamp = "1662554078" diff --git a/modules/simpletest/tests/menu.test b/modules/simpletest/tests/menu.test index f5d7d290fc..daea9f09ec 100644 --- a/modules/simpletest/tests/menu.test +++ b/modules/simpletest/tests/menu.test @@ -1738,3 +1738,41 @@ class MenuTrailTestCase extends MenuWebTestCase { } } } + +/** + * Tests value integrity. + */ +class MenuDataIntegrityTestCase extends MenuWebTestCase { + + public static function getInfo() { + return array( + 'name' => 'Menu item array integrity', + 'description' => 'Tests the values passed on to menu items array.', + 'group' => 'Menu', + ); + } + + /** + * Tests for null/casting weight parameter. + */ + public function testNullMenuWeight() { + $base_options = array( + 'link_title' => 'Menu link test', + 'module' => 'menu_test', + 'menu_name' => 'menu_test', + 'weight' => NULL, + 'link_path' => 'menu-test/parent', + ); + + try { + menu_link_save($base_options); + } + catch (PDOException $exception) { + $this->fail('Menu weight is not being cast properly.'); + return; + } + + $this->pass('Menu weight is being cast properly.'); + } + +} diff --git a/modules/simpletest/tests/menu_test.info b/modules/simpletest/tests/menu_test.info index 8576ac8368..4f0c3c1de9 100644 --- a/modules/simpletest/tests/menu_test.info +++ b/modules/simpletest/tests/menu_test.info @@ -5,7 +5,7 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2021-07-21 -version = "7.82" +; Information added by Drupal.org packaging script on 2022-09-07 +version = "7.92" project = "drupal" -datestamp = "1626883669" +datestamp = "1662554078" diff --git a/modules/simpletest/tests/module.test b/modules/simpletest/tests/module.test index eea3b51e9c..617c8f3f38 100644 --- a/modules/simpletest/tests/module.test +++ b/modules/simpletest/tests/module.test @@ -66,6 +66,12 @@ class ModuleUnitTest extends DrupalWebTestCase { // Reset the module list. module_list(TRUE); $this->assertModuleList($module_list, t('After reset')); + + // Verify that the module_list() returns correct bootstrap modules. + $bootstrap_module_list = module_list(TRUE, TRUE); + $expected_bootstrap_modules = db_query("SELECT name, filename FROM {system} WHERE status = 1 AND bootstrap = 1 AND type = 'module' ORDER BY weight ASC, name ASC")->fetchAllAssoc('name'); + $expected_bootstrap_module_list = array_combine(array_keys($expected_bootstrap_modules), array_keys($expected_bootstrap_modules)); + $this->assertIdentical($expected_bootstrap_module_list, $bootstrap_module_list, 'module_list() returns correct bootstrap modules.'); } /** @@ -110,6 +116,31 @@ class ModuleUnitTest extends DrupalWebTestCase { $this->assertEqual($static['test_hook']['module_test'], 'file', 'Include file detected.'); } + /** + * Test system_modules() with a module with a dependency with a null version. + */ + function testSystemModulesNullVersion() { + module_enable(array('system_requires_null_version_test'), FALSE); + $this->resetAll(); + $admin = $this->drupalCreateUser(array('administer modules')); + $this->drupalLogin($admin); + $this->drupalGet('admin/modules'); + $this->assertText('System null version test', 'Module admin UI listed dependency with null version successfully.'); + } + + /** + * Test system_modules() with a module with a broken configure path. + */ + function testSystemModulesBrokenConfigure() { + module_enable(array('system_admin_test')); + $this->resetAll(); + $admin = $this->drupalCreateUser(array('administer modules')); + $this->drupalLogin($admin); + $this->drupalGet('admin/modules'); + $module_log = db_query_range('SELECT message FROM {watchdog} WHERE type = :type ORDER BY wid DESC', 0, 1, array(':type' => 'system'))->fetchField(); + $this->assertEqual('Module %module specifies an invalid path for configuration: %configure', $module_log, 'An error was logged for the module\'s broken configure path.'); + } + /** * Test that module_invoke() can load a hook defined in hook_hook_info(). */ diff --git a/modules/simpletest/tests/module_test.info b/modules/simpletest/tests/module_test.info index 301fdd20a2..600dbe9f4e 100644 --- a/modules/simpletest/tests/module_test.info +++ b/modules/simpletest/tests/module_test.info @@ -5,7 +5,7 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2021-07-21 -version = "7.82" +; Information added by Drupal.org packaging script on 2022-09-07 +version = "7.92" project = "drupal" -datestamp = "1626883669" +datestamp = "1662554078" diff --git a/modules/simpletest/tests/pager.test b/modules/simpletest/tests/pager.test index 432edbf456..1a00472166 100644 --- a/modules/simpletest/tests/pager.test +++ b/modules/simpletest/tests/pager.test @@ -169,6 +169,6 @@ class PagerFunctionalWebTestCase extends DrupalWebTestCase { if (!isset($message)) { $message = "Class .$class not found."; } - $this->assertTrue(strpos($element['class'], $class) === FALSE, $message); + $this->assertTrue(strpos((string) $element['class'], $class) === FALSE, $message); } } diff --git a/modules/simpletest/tests/path.test b/modules/simpletest/tests/path.test index b8b3c93c81..5473bab7c9 100644 --- a/modules/simpletest/tests/path.test +++ b/modules/simpletest/tests/path.test @@ -331,6 +331,17 @@ class PathLookupTest extends DrupalWebTestCase { ); path_save($path); $this->assertEqual(drupal_lookup_path('source', $path['alias']), $path['source'], 'Newer alias record is returned when comparing two LANGUAGE_NONE paths with the same alias.'); + + // Test if the alias returned by drupal_lookup_path() is the same as the + // alias returned by path_load(). + $path = array( + 'source' => 'user/' . $account2->uid, + 'alias' => 'bar2', + ); + path_save($path); + $this->assertEqual(drupal_lookup_path('alias', $path['source']), 'bar2', 'Newer alias record is returned when using drupal_lookup_path() on paths with multiple aliases.'); + $loaded_path = path_load(array('source' => $path['source'])); + $this->assertEqual($loaded_path['alias'], 'bar2', 'Newer alias record is returned when using path_load() on paths with multiple aliases.'); } } diff --git a/modules/simpletest/tests/path_test.info b/modules/simpletest/tests/path_test.info index d9d2ff8f6f..fcfd79f1f0 100644 --- a/modules/simpletest/tests/path_test.info +++ b/modules/simpletest/tests/path_test.info @@ -5,7 +5,7 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2021-07-21 -version = "7.82" +; Information added by Drupal.org packaging script on 2022-09-07 +version = "7.92" project = "drupal" -datestamp = "1626883669" +datestamp = "1662554078" diff --git a/modules/simpletest/tests/psr_0_test/psr_0_test.info b/modules/simpletest/tests/psr_0_test/psr_0_test.info index af851e1190..637b2f2020 100644 --- a/modules/simpletest/tests/psr_0_test/psr_0_test.info +++ b/modules/simpletest/tests/psr_0_test/psr_0_test.info @@ -5,7 +5,7 @@ core = 7.x hidden = TRUE package = Testing -; Information added by Drupal.org packaging script on 2021-07-21 -version = "7.82" +; Information added by Drupal.org packaging script on 2022-09-07 +version = "7.92" project = "drupal" -datestamp = "1626883669" +datestamp = "1662554078" diff --git a/modules/simpletest/tests/psr_4_test/psr_4_test.info b/modules/simpletest/tests/psr_4_test/psr_4_test.info index 274fe3a006..2283f3a727 100644 --- a/modules/simpletest/tests/psr_4_test/psr_4_test.info +++ b/modules/simpletest/tests/psr_4_test/psr_4_test.info @@ -5,7 +5,7 @@ core = 7.x hidden = TRUE package = Testing -; Information added by Drupal.org packaging script on 2021-07-21 -version = "7.82" +; Information added by Drupal.org packaging script on 2022-09-07 +version = "7.92" project = "drupal" -datestamp = "1626883669" +datestamp = "1662554078" diff --git a/modules/simpletest/tests/requirements1_test.info b/modules/simpletest/tests/requirements1_test.info index 4ed711c706..b980e5325f 100644 --- a/modules/simpletest/tests/requirements1_test.info +++ b/modules/simpletest/tests/requirements1_test.info @@ -5,7 +5,7 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2021-07-21 -version = "7.82" +; Information added by Drupal.org packaging script on 2022-09-07 +version = "7.92" project = "drupal" -datestamp = "1626883669" +datestamp = "1662554078" diff --git a/modules/simpletest/tests/requirements2_test.info b/modules/simpletest/tests/requirements2_test.info index 721caeb6a2..a036bbf0c0 100644 --- a/modules/simpletest/tests/requirements2_test.info +++ b/modules/simpletest/tests/requirements2_test.info @@ -7,7 +7,7 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2021-07-21 -version = "7.82" +; Information added by Drupal.org packaging script on 2022-09-07 +version = "7.92" project = "drupal" -datestamp = "1626883669" +datestamp = "1662554078" diff --git a/modules/simpletest/tests/session.test b/modules/simpletest/tests/session.test index 5632d44e4d..b309546f14 100644 --- a/modules/simpletest/tests/session.test +++ b/modules/simpletest/tests/session.test @@ -773,3 +773,123 @@ class SessionHttpsTestCase extends DrupalWebTestCase { } } +/** + * Unit tests for session handling. + */ +class SessionUnitTestCase extends DrupalUnitTestCase { + + /** + * {@inheritdoc} + */ + public static function getInfo() { + return array( + 'name' => 'Session unit tests', + 'description' => 'Test session handling functionality.', + 'group' => 'Session', + ); + } + + function testCookieDomain() { + $tests = array( + array('example.com', '.example.com'), + array('www.example.com', '.www.example.com'), + array('subdomain.example.com', '.subdomain.example.com'), + ); + + foreach ($tests as $test) { + $this->assertEqual(_drupal_get_cookie_domain($test[0]), $test[1], 'Correct $cookie_domain for host ' . $test[0]); + } + } + + /** + * Unit test drupal_settings_initialize(). + */ + function testSessionInitialization() { + global $base_url, $cookie_domain; + + $tests = array( + array( + 'http_host' => 'example.com', + 'script_name' => '/index.php', + 'session_name' => 'SESSa379a6f6eeafb9a55e378c118034e275', + 'base_url' => 'http://example.com', + 'cookie_domain' => '.example.com', + ), + array( + 'http_host' => 'example.com', + 'script_name' => '/foo/index.php', + 'session_name' => 'SESS76f699faa11e8fbe9a70d933651df90f', + 'base_url' => 'http://example.com/foo', + 'cookie_domain' => '.example.com', + ), + array( + 'http_host' => 'one.two.example.com', + 'script_name' => '/index.php', + 'session_name' => 'SESSc0cc73483118b575693f5c255faeb739', + 'base_url' => 'http://one.two.example.com', + 'cookie_domain' => '.one.two.example.com', + ), + array( + 'http_host' => 'www.sub.example.com', + 'script_name' => '/index.php', + 'session_name' => 'SESSdc70980beaa5d571d3c9785cf9246c96', + 'base_url' => 'http://www.sub.example.com', + 'cookie_domain' => '.www.sub.example.com', + ), + array( + 'http_host' => 'example.com', + 'script_name' => '/foo/bar/index.php', + 'session_name' => 'SESSdbf7edcf4f2656b247021ceb4752f7f9', + 'base_url' => 'http://example.com/foo/bar', + 'cookie_domain' => '.example.com', + ), + array( + 'http_host' => 'www.sub.example.com', + 'script_name' => '/baz/index.php', + 'session_name' => 'SESS63543f965940db9e8a79801aa6d52423', + 'base_url' => 'http://www.sub.example.com/baz', + 'cookie_domain' => '.www.sub.example.com', + ), + array( + 'http_host' => 'sub.example.com', + 'script_name' => '/index.php', + 'session_name' => 'SESS005c4a974d8b94af421206f9ef34efeb', + 'base_url' => 'http://sub.example.com', + 'cookie_domain' => '.sub.example.com', + ), + array( + 'http_host' => 'www.example.com', + 'script_name' => '/index.php', + 'session_name' => 'SESS0e9f0e2600e4d8f26827597c5e324261', + 'base_url' => 'http://www.example.com', + 'cookie_domain' => '.www.example.com', + ), + array( + 'http_host' => 'www.example.com', + 'script_name' => '/foo/index.php', + 'session_name' => 'SESS0ddd0afc0bece848c840cdcd7b8ed9c9', + 'base_url' => 'http://www.example.com/foo', + 'cookie_domain' => '.www.example.com', + ), + array( + 'http_host' => 'www.example.com', + 'script_name' => '/bar/index.php', + 'session_name' => 'SESS22f2cd08599536cb4363fcd09e29d264', + 'base_url' => 'http://www.example.com/bar', + 'cookie_domain' => '.www.example.com', + ), + ); + + foreach ($tests as $test) { + $_SERVER['HTTP_HOST'] = $test['http_host']; + $_SERVER['SCRIPT_NAME'] = $test['script_name']; + $cookie_domain = NULL; + $base_url = NULL; + drupal_settings_initialize(); + $this->assertEqual(session_name(), $test['session_name'], 'Correct session_name for ' . $test['http_host'] . $test['script_name']); + $this->assertEqual($base_url, $test['base_url'], 'Correct base_url for ' . $test['http_host'] . $test['script_name']); + $this->assertEqual($cookie_domain, $test['cookie_domain'], 'Correct cookie_domain for ' . $test['http_host'] . $test['script_name']); + } + + } +} diff --git a/modules/simpletest/tests/session_test.info b/modules/simpletest/tests/session_test.info index aaaf84cff3..28b4019d4d 100644 --- a/modules/simpletest/tests/session_test.info +++ b/modules/simpletest/tests/session_test.info @@ -5,7 +5,7 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2021-07-21 -version = "7.82" +; Information added by Drupal.org packaging script on 2022-09-07 +version = "7.92" project = "drupal" -datestamp = "1626883669" +datestamp = "1662554078" diff --git a/modules/simpletest/tests/system_dependencies_test.info b/modules/simpletest/tests/system_dependencies_test.info index a6fe68431e..e22415eb64 100644 --- a/modules/simpletest/tests/system_dependencies_test.info +++ b/modules/simpletest/tests/system_dependencies_test.info @@ -6,7 +6,7 @@ core = 7.x hidden = TRUE dependencies[] = _missing_dependency -; Information added by Drupal.org packaging script on 2021-07-21 -version = "7.82" +; Information added by Drupal.org packaging script on 2022-09-07 +version = "7.92" project = "drupal" -datestamp = "1626883669" +datestamp = "1662554078" diff --git a/modules/simpletest/tests/system_incompatible_core_version_dependencies_test.info b/modules/simpletest/tests/system_incompatible_core_version_dependencies_test.info index d04df64d09..b0cfb27426 100644 --- a/modules/simpletest/tests/system_incompatible_core_version_dependencies_test.info +++ b/modules/simpletest/tests/system_incompatible_core_version_dependencies_test.info @@ -6,7 +6,7 @@ core = 7.x hidden = TRUE dependencies[] = system_incompatible_core_version_test -; Information added by Drupal.org packaging script on 2021-07-21 -version = "7.82" +; Information added by Drupal.org packaging script on 2022-09-07 +version = "7.92" project = "drupal" -datestamp = "1626883669" +datestamp = "1662554078" diff --git a/modules/simpletest/tests/system_incompatible_core_version_test.info b/modules/simpletest/tests/system_incompatible_core_version_test.info index 25110ab9e0..e02d1eb595 100644 --- a/modules/simpletest/tests/system_incompatible_core_version_test.info +++ b/modules/simpletest/tests/system_incompatible_core_version_test.info @@ -5,7 +5,7 @@ version = VERSION core = 5.x hidden = TRUE -; Information added by Drupal.org packaging script on 2021-07-21 -version = "7.82" +; Information added by Drupal.org packaging script on 2022-09-07 +version = "7.92" project = "drupal" -datestamp = "1626883669" +datestamp = "1662554078" diff --git a/modules/simpletest/tests/system_incompatible_module_version_dependencies_test.info b/modules/simpletest/tests/system_incompatible_module_version_dependencies_test.info index 1c071914aa..aec5b1bcfb 100644 --- a/modules/simpletest/tests/system_incompatible_module_version_dependencies_test.info +++ b/modules/simpletest/tests/system_incompatible_module_version_dependencies_test.info @@ -7,7 +7,7 @@ hidden = TRUE ; system_incompatible_module_version_test declares version 1.0 dependencies[] = system_incompatible_module_version_test (>2.0) -; Information added by Drupal.org packaging script on 2021-07-21 -version = "7.82" +; Information added by Drupal.org packaging script on 2022-09-07 +version = "7.92" project = "drupal" -datestamp = "1626883669" +datestamp = "1662554078" diff --git a/modules/simpletest/tests/system_incompatible_module_version_test.info b/modules/simpletest/tests/system_incompatible_module_version_test.info index 27a7d791ac..827c96c897 100644 --- a/modules/simpletest/tests/system_incompatible_module_version_test.info +++ b/modules/simpletest/tests/system_incompatible_module_version_test.info @@ -5,7 +5,7 @@ version = 1.0 core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2021-07-21 -version = "7.82" +; Information added by Drupal.org packaging script on 2022-09-07 +version = "7.92" project = "drupal" -datestamp = "1626883669" +datestamp = "1662554078" diff --git a/modules/simpletest/tests/system_project_namespace_test.info b/modules/simpletest/tests/system_project_namespace_test.info index 398989ccc8..256a1b1f35 100644 --- a/modules/simpletest/tests/system_project_namespace_test.info +++ b/modules/simpletest/tests/system_project_namespace_test.info @@ -6,7 +6,7 @@ core = 7.x hidden = TRUE dependencies[] = drupal:filter -; Information added by Drupal.org packaging script on 2021-07-21 -version = "7.82" +; Information added by Drupal.org packaging script on 2022-09-07 +version = "7.92" project = "drupal" -datestamp = "1626883669" +datestamp = "1662554078" diff --git a/modules/simpletest/tests/system_test.info b/modules/simpletest/tests/system_test.info index ac7f8e8a2f..3d18dc276d 100644 --- a/modules/simpletest/tests/system_test.info +++ b/modules/simpletest/tests/system_test.info @@ -6,7 +6,7 @@ core = 7.x files[] = system_test.module hidden = TRUE -; Information added by Drupal.org packaging script on 2021-07-21 -version = "7.82" +; Information added by Drupal.org packaging script on 2022-09-07 +version = "7.92" project = "drupal" -datestamp = "1626883669" +datestamp = "1662554078" diff --git a/modules/simpletest/tests/system_test.module b/modules/simpletest/tests/system_test.module index fef539adb3..7844ef0ee9 100644 --- a/modules/simpletest/tests/system_test.module +++ b/modules/simpletest/tests/system_test.module @@ -45,6 +45,11 @@ function system_test_menu() { 'access arguments' => array('access content'), 'type' => MENU_CALLBACK, ); + $items['system-test/redirect-protocol-relative'] = array( + 'page callback' => 'system_test_redirect_protocol_relative', + 'access arguments' => array('access content'), + 'type' => MENU_CALLBACK, + ); $items['system-test/redirect-noparse'] = array( 'page callback' => 'system_test_redirect_noparse', 'access arguments' => array('access content'), @@ -141,6 +146,19 @@ function system_test_menu() { 'type' => MENU_CALLBACK, ); + $items['system-test/empty-page'] = array( + 'title' => 'Test page cache with empty page.', + 'page callback' => 'system_test_empty_page', + 'access callback' => TRUE, + 'type' => MENU_CALLBACK, + ); + + $items['system-test/page-cache-headers'] = array( + 'page callback' => 'system_test_page_cache_headers', + 'access arguments' => array('access content'), + 'type' => MENU_CALLBACK, + ); + return $items; } @@ -213,6 +231,11 @@ function system_test_redirect_noscheme() { exit; } +function system_test_redirect_protocol_relative() { + header("Location: //example.com/path", TRUE, 301); + exit; +} + function system_test_redirect_noparse() { header("Location: http:///path", TRUE, 301); exit; @@ -223,6 +246,28 @@ function system_test_redirect_invalid_scheme() { exit; } +/** + * Menu callback to test headers stored in the page cache. + */ +function system_test_page_cache_headers() { + if (!isset($_GET['return_headers'])) { + return t('Content to store in the page cache if it is enabled.'); + } + global $base_root; + // Remove the test query param but try to preserve any remaining query string. + $url = parse_url($base_root . request_uri()); + $query_parts = explode('&', $url['query']); + $query_string = implode('&', array_diff($query_parts, array('return_headers=TRUE'))); + $request_uri = $url['path'] . '?' . $query_string; + $cache = cache_get($base_root . $request_uri, 'cache_page'); + // If there are any headers stored in the cache, output them. + if (isset($cache->data['headers'])) { + drupal_add_http_header('Page-Cache-Headers', json_encode($cache->data['headers'])); + return 'Headers from cache_page returned in the Page-Cache-Headers http response header.'; + } + return 'No headers retrieved from cache_page.'; +} + /** * Implements hook_modules_installed(). */ @@ -532,6 +577,12 @@ function system_test_drupal_get_filename_with_schema_rebuild() { return ''; } +/** + * Page callback to output an empty page. + */ +function system_test_empty_page() { +} + /** * Implements hook_watchdog(). */ @@ -566,6 +617,6 @@ function system_test_module_implements_alter(&$implementations, $hook) { $group = $implementations['system_test']; unset($implementations['system_test']); $count = count($implementations); - $implementations = array_merge(array_slice($implementations, 0, $count / 2, TRUE), array('system_test' => $group), array_slice($implementations, $count / 2, NULL, TRUE)); + $implementations = array_merge(array_slice($implementations, 0, (int) ($count / 2), TRUE), array('system_test' => $group), array_slice($implementations, (int) ($count / 2), NULL, TRUE)); } } diff --git a/modules/simpletest/tests/taxonomy_test.info b/modules/simpletest/tests/taxonomy_test.info index 9d7234c08e..6a9e4e6f5d 100644 --- a/modules/simpletest/tests/taxonomy_test.info +++ b/modules/simpletest/tests/taxonomy_test.info @@ -6,7 +6,7 @@ core = 7.x hidden = TRUE dependencies[] = taxonomy -; Information added by Drupal.org packaging script on 2021-07-21 -version = "7.82" +; Information added by Drupal.org packaging script on 2022-09-07 +version = "7.92" project = "drupal" -datestamp = "1626883669" +datestamp = "1662554078" diff --git a/modules/simpletest/tests/taxonomy_test.module b/modules/simpletest/tests/taxonomy_test.module index f414438013..16b571ecdc 100644 --- a/modules/simpletest/tests/taxonomy_test.module +++ b/modules/simpletest/tests/taxonomy_test.module @@ -139,3 +139,14 @@ function taxonomy_test_query_taxonomy_term_access_alter(QueryAlterableInterface variable_set(__FUNCTION__, ++$value); } } + +/** + * Test controller class for taxonomy terms. + * + * The main purpose is to make cacheGet() method available for testing. + */ +class TestTaxonomyTermController extends TaxonomyTermController { + public function loadFromCache($ids, $conditions = array()) { + return parent::cacheGet($ids, $conditions); + } +} diff --git a/modules/simpletest/tests/theme_test.info b/modules/simpletest/tests/theme_test.info index bd2eb34af7..b3ea77cd55 100644 --- a/modules/simpletest/tests/theme_test.info +++ b/modules/simpletest/tests/theme_test.info @@ -5,7 +5,7 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2021-07-21 -version = "7.82" +; Information added by Drupal.org packaging script on 2022-09-07 +version = "7.92" project = "drupal" -datestamp = "1626883669" +datestamp = "1662554078" diff --git a/modules/simpletest/tests/themes/test_basetheme/test_basetheme.info b/modules/simpletest/tests/themes/test_basetheme/test_basetheme.info index c535122ec4..26fc997547 100644 --- a/modules/simpletest/tests/themes/test_basetheme/test_basetheme.info +++ b/modules/simpletest/tests/themes/test_basetheme/test_basetheme.info @@ -6,7 +6,7 @@ hidden = TRUE settings[basetheme_only] = base theme value settings[subtheme_override] = base theme value -; Information added by Drupal.org packaging script on 2021-07-21 -version = "7.82" +; Information added by Drupal.org packaging script on 2022-09-07 +version = "7.92" project = "drupal" -datestamp = "1626883669" +datestamp = "1662554078" diff --git a/modules/simpletest/tests/themes/test_subtheme/test_subtheme.info b/modules/simpletest/tests/themes/test_subtheme/test_subtheme.info index 4cb573a2cd..1a629be1c6 100644 --- a/modules/simpletest/tests/themes/test_subtheme/test_subtheme.info +++ b/modules/simpletest/tests/themes/test_subtheme/test_subtheme.info @@ -6,7 +6,7 @@ hidden = TRUE settings[subtheme_override] = subtheme value -; Information added by Drupal.org packaging script on 2021-07-21 -version = "7.82" +; Information added by Drupal.org packaging script on 2022-09-07 +version = "7.92" project = "drupal" -datestamp = "1626883669" +datestamp = "1662554078" diff --git a/modules/simpletest/tests/themes/test_theme/test_theme.info b/modules/simpletest/tests/themes/test_theme/test_theme.info index dfe3943042..bfd1ee6e32 100644 --- a/modules/simpletest/tests/themes/test_theme/test_theme.info +++ b/modules/simpletest/tests/themes/test_theme/test_theme.info @@ -17,7 +17,7 @@ stylesheets[all][] = system.base.css settings[theme_test_setting] = default value -; Information added by Drupal.org packaging script on 2021-07-21 -version = "7.82" +; Information added by Drupal.org packaging script on 2022-09-07 +version = "7.92" project = "drupal" -datestamp = "1626883669" +datestamp = "1662554078" diff --git a/modules/simpletest/tests/themes/test_theme_nyan_cat/test_theme_nyan_cat.info b/modules/simpletest/tests/themes/test_theme_nyan_cat/test_theme_nyan_cat.info index eb57839a15..4f121ee1d0 100644 --- a/modules/simpletest/tests/themes/test_theme_nyan_cat/test_theme_nyan_cat.info +++ b/modules/simpletest/tests/themes/test_theme_nyan_cat/test_theme_nyan_cat.info @@ -4,7 +4,7 @@ core = 7.x hidden = TRUE engine = nyan_cat -; Information added by Drupal.org packaging script on 2021-07-21 -version = "7.82" +; Information added by Drupal.org packaging script on 2022-09-07 +version = "7.92" project = "drupal" -datestamp = "1626883669" +datestamp = "1662554078" diff --git a/modules/simpletest/tests/update_script_test.info b/modules/simpletest/tests/update_script_test.info index 279f899541..10b6596128 100644 --- a/modules/simpletest/tests/update_script_test.info +++ b/modules/simpletest/tests/update_script_test.info @@ -5,7 +5,7 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2021-07-21 -version = "7.82" +; Information added by Drupal.org packaging script on 2022-09-07 +version = "7.92" project = "drupal" -datestamp = "1626883669" +datestamp = "1662554078" diff --git a/modules/simpletest/tests/update_test_1.info b/modules/simpletest/tests/update_test_1.info index afb06f08b5..4c38b10f77 100644 --- a/modules/simpletest/tests/update_test_1.info +++ b/modules/simpletest/tests/update_test_1.info @@ -5,7 +5,7 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2021-07-21 -version = "7.82" +; Information added by Drupal.org packaging script on 2022-09-07 +version = "7.92" project = "drupal" -datestamp = "1626883669" +datestamp = "1662554078" diff --git a/modules/simpletest/tests/update_test_2.info b/modules/simpletest/tests/update_test_2.info index afb06f08b5..4c38b10f77 100644 --- a/modules/simpletest/tests/update_test_2.info +++ b/modules/simpletest/tests/update_test_2.info @@ -5,7 +5,7 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2021-07-21 -version = "7.82" +; Information added by Drupal.org packaging script on 2022-09-07 +version = "7.92" project = "drupal" -datestamp = "1626883669" +datestamp = "1662554078" diff --git a/modules/simpletest/tests/update_test_3.info b/modules/simpletest/tests/update_test_3.info index afb06f08b5..4c38b10f77 100644 --- a/modules/simpletest/tests/update_test_3.info +++ b/modules/simpletest/tests/update_test_3.info @@ -5,7 +5,7 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2021-07-21 -version = "7.82" +; Information added by Drupal.org packaging script on 2022-09-07 +version = "7.92" project = "drupal" -datestamp = "1626883669" +datestamp = "1662554078" diff --git a/modules/simpletest/tests/upgrade/update.aggregator.test b/modules/simpletest/tests/upgrade/update.aggregator.test index d4135774e7..9f5bcaa254 100644 --- a/modules/simpletest/tests/upgrade/update.aggregator.test +++ b/modules/simpletest/tests/upgrade/update.aggregator.test @@ -36,6 +36,7 @@ class AggregatorUpdatePathTestCase extends UpdatePathTestCase { $query ->fields('af', array('url', 'link')) ->fields('ai', array('link', 'guid')); + $query->orderBy('ai.iid'); $pre_update_data = $query->execute()->fetchAll(); $this->assertTrue($this->performUpgrade(), 'The update was completed successfully.'); diff --git a/modules/simpletest/tests/upgrade/upgrade.comment.test b/modules/simpletest/tests/upgrade/upgrade.comment.test index 40d893a845..84551c3c0e 100644 --- a/modules/simpletest/tests/upgrade/upgrade.comment.test +++ b/modules/simpletest/tests/upgrade/upgrade.comment.test @@ -27,6 +27,9 @@ class CommentUpgradePathTestCase extends UpgradePathTestCase { * Test a successful upgrade. */ public function testCommentUpgrade() { + if ($this->skipUpgradeTest) { + return; + } $this->assertTrue($this->performUpgrade(), 'The upgrade was completed successfully.'); } } diff --git a/modules/simpletest/tests/upgrade/upgrade.filter.test b/modules/simpletest/tests/upgrade/upgrade.filter.test index 584f4d9461..6d805122f2 100644 --- a/modules/simpletest/tests/upgrade/upgrade.filter.test +++ b/modules/simpletest/tests/upgrade/upgrade.filter.test @@ -28,6 +28,9 @@ class FilterFormatUpgradePathTestCase extends UpgradePathTestCase { * Test a successful upgrade. */ function testFilterFormatUpgrade() { + if ($this->skipUpgradeTest) { + return; + } $this->assertTrue($this->performUpgrade(), 'The upgrade was completed successfully.'); $format = filter_format_load('1'); diff --git a/modules/simpletest/tests/upgrade/upgrade.forum.test b/modules/simpletest/tests/upgrade/upgrade.forum.test index b0bbd4ed78..7551445368 100644 --- a/modules/simpletest/tests/upgrade/upgrade.forum.test +++ b/modules/simpletest/tests/upgrade/upgrade.forum.test @@ -27,6 +27,9 @@ class ForumUpgradePathTestCase extends UpgradePathTestCase { * Test a successful upgrade (no negotiation). */ public function testForumUpgrade() { + if ($this->skipUpgradeTest) { + return; + } $this->assertTrue($this->performUpgrade(), 'The upgrade was completed successfully.'); // Work around http://drupal.org/node/931512 diff --git a/modules/simpletest/tests/upgrade/upgrade.locale.test b/modules/simpletest/tests/upgrade/upgrade.locale.test index f7b0038d64..a13499d015 100644 --- a/modules/simpletest/tests/upgrade/upgrade.locale.test +++ b/modules/simpletest/tests/upgrade/upgrade.locale.test @@ -27,6 +27,9 @@ class LocaleUpgradePathTestCase extends UpgradePathTestCase { * Test a successful upgrade (no negotiation). */ public function testLocaleUpgrade() { + if ($this->skipUpgradeTest) { + return; + } $this->assertTrue($this->performUpgrade(), 'The upgrade was completed successfully.'); // The home page should be in French. @@ -43,6 +46,9 @@ class LocaleUpgradePathTestCase extends UpgradePathTestCase { * Test an upgrade with path-based negotiation. */ public function testLocaleUpgradePathDefault() { + if ($this->skipUpgradeTest) { + return; + } // LANGUAGE_NEGOTIATION_PATH_DEFAULT. $this->variable_set('language_negotiation', 1); @@ -66,6 +72,9 @@ class LocaleUpgradePathTestCase extends UpgradePathTestCase { * Test an upgrade with path-based (with fallback) negotiation. */ public function testLocaleUpgradePathFallback() { + if ($this->skipUpgradeTest) { + return; + } // LANGUAGE_NEGOTIATION_PATH. $this->variable_set('language_negotiation', 2); @@ -92,6 +101,9 @@ class LocaleUpgradePathTestCase extends UpgradePathTestCase { * Test an upgrade with domain-based negotiation. */ public function testLocaleUpgradeDomain() { + if ($this->skipUpgradeTest) { + return; + } // LANGUAGE_NEGOTIATION_DOMAIN. $this->variable_set('language_negotiation', 3); diff --git a/modules/simpletest/tests/upgrade/upgrade.menu.test b/modules/simpletest/tests/upgrade/upgrade.menu.test index d9ae6c1b77..a9dfd4fa4b 100644 --- a/modules/simpletest/tests/upgrade/upgrade.menu.test +++ b/modules/simpletest/tests/upgrade/upgrade.menu.test @@ -27,6 +27,9 @@ class MenuUpgradePathTestCase extends UpgradePathTestCase { * Test a successful upgrade. */ public function testMenuUpgrade() { + if ($this->skipUpgradeTest) { + return; + } $this->assertTrue($this->performUpgrade(), 'The upgrade was completed successfully.'); // Test the migration of "Default menu for content" setting to individual diff --git a/modules/simpletest/tests/upgrade/upgrade.node.test b/modules/simpletest/tests/upgrade/upgrade.node.test index 4ac14c5745..7986bab69c 100644 --- a/modules/simpletest/tests/upgrade/upgrade.node.test +++ b/modules/simpletest/tests/upgrade/upgrade.node.test @@ -26,6 +26,9 @@ class NodeBodyUpgradePathTestCase extends UpgradePathTestCase { * Test a successful upgrade. */ public function testNodeBodyUpgrade() { + if ($this->skipUpgradeTest) { + return; + } $this->assertTrue($this->performUpgrade(), 'The upgrade was completed successfully.'); $instance = field_info_instance('node', 'body', 'story'); @@ -77,6 +80,9 @@ class DisabledNodeTypeTestCase extends UpgradePathTestCase { * Tests a successful upgrade. */ public function testDisabledNodeTypeUpgrade() { + if ($this->skipUpgradeTest) { + return; + } $this->assertTrue($this->performUpgrade(), 'The upgrade was completed successfully.'); $this->assertTrue(field_info_instance('comment', 'comment_body', 'comment_node_broken'), 'Comment body field instance was created for comments attached to the disabled broken node type'); } @@ -114,6 +120,9 @@ class PollUpgradePathTestCase extends UpgradePathTestCase { * Test a successful upgrade. */ public function testPollUpgrade() { + if ($this->skipUpgradeTest) { + return; + } $this->assertTrue($this->performUpgrade(), 'The upgrade was completed successfully.'); // Check modules page for poll diff --git a/modules/simpletest/tests/upgrade/upgrade.poll.test b/modules/simpletest/tests/upgrade/upgrade.poll.test index ac4ea4365d..85c4ffd757 100644 --- a/modules/simpletest/tests/upgrade/upgrade.poll.test +++ b/modules/simpletest/tests/upgrade/upgrade.poll.test @@ -32,6 +32,9 @@ class PollUpgradePathTestCase extends UpgradePathTestCase { * Test a successful upgrade. */ public function testPollUpgrade() { + if ($this->skipUpgradeTest) { + return; + } $this->assertTrue($this->performUpgrade(), 'The upgrade was completed successfully.'); // Check modules page for poll diff --git a/modules/simpletest/tests/upgrade/upgrade.taxonomy.test b/modules/simpletest/tests/upgrade/upgrade.taxonomy.test index 51402ed760..bde2691fbe 100644 --- a/modules/simpletest/tests/upgrade/upgrade.taxonomy.test +++ b/modules/simpletest/tests/upgrade/upgrade.taxonomy.test @@ -46,6 +46,9 @@ class UpgradePathTaxonomyTestCase extends UpgradePathTestCase { * Basic tests for the taxonomy upgrade. */ public function testTaxonomyUpgrade() { + if ($this->skipUpgradeTest) { + return; + } $this->assertTrue($this->performUpgrade(), 'The upgrade was completed successfully.'); // Visit the front page to assert for PHP warning and errors. diff --git a/modules/simpletest/tests/upgrade/upgrade.test b/modules/simpletest/tests/upgrade/upgrade.test index 784a091787..68eec60cef 100644 --- a/modules/simpletest/tests/upgrade/upgrade.test +++ b/modules/simpletest/tests/upgrade/upgrade.test @@ -37,6 +37,11 @@ abstract class UpgradePathTestCase extends DrupalWebTestCase { */ var $pendingUpdates = TRUE; + /** + * Flag to indicate whether to skip upgrade tests. + */ + var $skipUpgradeTest = FALSE; + /** * Constructs an UpgradePathTestCase object. * @@ -46,6 +51,8 @@ abstract class UpgradePathTestCase extends DrupalWebTestCase { */ function __construct($test_id = NULL) { parent::__construct($test_id); + $connection_info = Database::getConnectionInfo(); + $this->skipUpgradeTest = (bool) ($connection_info['default']['driver'] == 'pgsql'); $this->zlibInstalled = function_exists('gzopen'); } @@ -355,6 +362,9 @@ class BasicUpgradePath extends UpgradePathTestCase { * Test a failed upgrade, and verify that the failure is reported. */ public function testFailedUpgrade() { + if ($this->skipUpgradeTest) { + return; + } // Destroy a table that the upgrade process needs. db_drop_table('access'); // Assert that the upgrade fails. @@ -365,6 +375,9 @@ class BasicUpgradePath extends UpgradePathTestCase { * Test a successful upgrade. */ public function testBasicUpgrade() { + if ($this->skipUpgradeTest) { + return; + } $this->assertTrue($this->performUpgrade(), 'The upgrade was completed successfully.'); // Hit the frontpage. @@ -579,7 +592,24 @@ class BasicMinimalUpdatePath extends UpdatePathTestCase { $this->assertText(t('Custom date format added.')); // Verify that the unique key on {date_formats}.format still exists. - $this->assertTrue(db_index_exists('date_formats', 'formats'), 'Unique key on {date_formats} exists'); + try { + // The PostgreSQL driver appends suffixes to keys/indexes according to + // their types. A unique key is a key (not an index), so it has a _key + // suffix. It is not possible to call db_index_exists() as this function + // will check the index name with _idx suffix, so it will never succeed on + // PostgreSQL. An attempt to create a new unique key when it exists should + // fail and the thrown exception will confirm that the unique key exists. + // MySQL and SQLite are not affected by this change, as they do not append + // suffixes to keys/indexes names. + db_add_unique_key('date_formats', 'formats', array('formats')); + // This is executed if no exception has been thrown and the unique key + // did not already exist. + $this->fail('Unique key on {date_formats} does not exist'); + } + catch (DatabaseSchemaObjectExistsException $e) { + // Exception confirms that the unique key already existed. + $this->pass('Unique key on {date_formats} exists'); + } } } diff --git a/modules/simpletest/tests/upgrade/upgrade.translatable.test b/modules/simpletest/tests/upgrade/upgrade.translatable.test index 6fefb0ff07..c4f7161a67 100644 --- a/modules/simpletest/tests/upgrade/upgrade.translatable.test +++ b/modules/simpletest/tests/upgrade/upgrade.translatable.test @@ -28,6 +28,9 @@ class TranslatableUpgradePathTestCase extends UpgradePathTestCase { * Test a successful upgrade (no negotiation). */ public function testTranslatableUpgrade() { + if ($this->skipUpgradeTest) { + return; + } $this->assertTrue($this->performUpgrade(), 'The upgrade was completed successfully.'); // The D6 database contains the english node "First translatable page" with diff --git a/modules/simpletest/tests/upgrade/upgrade.trigger.test b/modules/simpletest/tests/upgrade/upgrade.trigger.test index ef2394142d..79cb63e3b9 100644 --- a/modules/simpletest/tests/upgrade/upgrade.trigger.test +++ b/modules/simpletest/tests/upgrade/upgrade.trigger.test @@ -29,6 +29,9 @@ class UpgradePathTriggerTestCase extends UpgradePathTestCase { * Basic tests for the trigger upgrade. */ public function testTaxonomyUpgrade() { + if ($this->skipUpgradeTest) { + return; + } $this->assertTrue($this->performUpgrade(), 'The upgrade was completed successfully.'); $this->drupalGet('admin/structure/trigger/node'); $this->assertRaw(''. t('Make post sticky') .''); diff --git a/modules/simpletest/tests/upgrade/upgrade.upload.test b/modules/simpletest/tests/upgrade/upgrade.upload.test index dfa94a008b..6e0668cc3b 100644 --- a/modules/simpletest/tests/upgrade/upgrade.upload.test +++ b/modules/simpletest/tests/upgrade/upgrade.upload.test @@ -29,6 +29,9 @@ class UploadUpgradePathTestCase extends UpgradePathTestCase { * Test a successful upgrade. */ public function testUploadUpgrade() { + if ($this->skipUpgradeTest) { + return; + } $this->assertTrue($this->performUpgrade(), 'The upgrade was completed successfully.'); $query = new EntityFieldQuery(); $query->entityCondition('entity_type', 'node'); diff --git a/modules/simpletest/tests/upgrade/upgrade.user.test b/modules/simpletest/tests/upgrade/upgrade.user.test index d33234e431..605a43dcfb 100644 --- a/modules/simpletest/tests/upgrade/upgrade.user.test +++ b/modules/simpletest/tests/upgrade/upgrade.user.test @@ -24,6 +24,9 @@ class UserUpgradePathPasswordTokenTestCase extends UpgradePathTestCase { * Test a successful upgrade. */ public function testUserUpgrade() { + if ($this->skipUpgradeTest) { + return; + } $this->assertTrue($this->performUpgrade(), 'The upgrade was completed successfully.'); $this->assertEqual(variable_get('user_mail_register_no_approval_required_body'), ', [user:name], [site:name], [site:url], [site:url-brief], [user:mail], [date:medium], [site:login-url], [user:edit-url], [user:one-time-login-url].', 'Existing email templates have been modified (password token involved).'); // Check that a non-md5 hash was untouched. @@ -57,6 +60,9 @@ class UserUpgradePathNoPasswordTokenTestCase extends UpgradePathTestCase { * Test a successful upgrade. */ public function testUserUpgrade() { + if ($this->skipUpgradeTest) { + return; + } $this->assertTrue($this->performUpgrade(), 'The upgrade was completed successfully.'); $this->assertEqual(variable_get('user_mail_register_no_approval_required_body'), '[user:name], [site:name], [site:url], [site:url-brief], [user:mail], [date:medium], [site:login-url], [user:edit-url], [user:one-time-login-url].', 'Existing email templates have been modified (password token not involved).'); } @@ -87,6 +93,9 @@ class UserUpgradePathDuplicatedPermissionTestCase extends UpgradePathTestCase { * Test a successful upgrade. */ public function testUserUpgrade() { + if ($this->skipUpgradeTest) { + return; + } $this->assertTrue($this->performUpgrade(), 'The upgrade was completed successfully.'); } } diff --git a/modules/simpletest/tests/url_alter_test.info b/modules/simpletest/tests/url_alter_test.info index dfdd63b707..b827b85598 100644 --- a/modules/simpletest/tests/url_alter_test.info +++ b/modules/simpletest/tests/url_alter_test.info @@ -5,7 +5,7 @@ package = Testing version = VERSION hidden = TRUE -; Information added by Drupal.org packaging script on 2021-07-21 -version = "7.82" +; Information added by Drupal.org packaging script on 2022-09-07 +version = "7.92" project = "drupal" -datestamp = "1626883669" +datestamp = "1662554078" diff --git a/modules/simpletest/tests/url_alter_test.module b/modules/simpletest/tests/url_alter_test.module index 9287ff5230..bdc3167a81 100644 --- a/modules/simpletest/tests/url_alter_test.module +++ b/modules/simpletest/tests/url_alter_test.module @@ -57,7 +57,7 @@ function url_alter_test_url_inbound_alter(&$path, $original_path, $path_language */ function url_alter_test_url_outbound_alter(&$path, &$options, $original_path) { // Rewrite user/uid to user/username. - if (preg_match('!^user/([0-9]+)(/.*)?!', $path, $matches)) { + if (preg_match('!^user/([0-9]+)(/.*)?!', (string) $path, $matches)) { if ($account = user_load($matches[1])) { $matches += array(2 => ''); $path = 'user/' . $account->name . $matches[2]; @@ -65,7 +65,7 @@ function url_alter_test_url_outbound_alter(&$path, &$options, $original_path) { } // Rewrite forum/ to community/. - if ($path == 'forum' || strpos($path, 'forum/') === 0) { + if ($path == 'forum' || strpos((string) $path, 'forum/') === 0) { $path = 'community' . substr($path, 5); } } diff --git a/modules/simpletest/tests/xmlrpc_test.info b/modules/simpletest/tests/xmlrpc_test.info index b8201c78bf..4408a46c0b 100644 --- a/modules/simpletest/tests/xmlrpc_test.info +++ b/modules/simpletest/tests/xmlrpc_test.info @@ -5,7 +5,7 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2021-07-21 -version = "7.82" +; Information added by Drupal.org packaging script on 2022-09-07 +version = "7.92" project = "drupal" -datestamp = "1626883669" +datestamp = "1662554078" diff --git a/modules/statistics/statistics.info b/modules/statistics/statistics.info index 9e6e78dc5c..d14bd216ff 100644 --- a/modules/statistics/statistics.info +++ b/modules/statistics/statistics.info @@ -6,7 +6,7 @@ core = 7.x files[] = statistics.test configure = admin/config/system/statistics -; Information added by Drupal.org packaging script on 2021-07-21 -version = "7.82" +; Information added by Drupal.org packaging script on 2022-09-07 +version = "7.92" project = "drupal" -datestamp = "1626883669" +datestamp = "1662554078" diff --git a/modules/syslog/syslog.info b/modules/syslog/syslog.info index e60284a523..d240d2327a 100644 --- a/modules/syslog/syslog.info +++ b/modules/syslog/syslog.info @@ -6,7 +6,7 @@ core = 7.x files[] = syslog.test configure = admin/config/development/logging -; Information added by Drupal.org packaging script on 2021-07-21 -version = "7.82" +; Information added by Drupal.org packaging script on 2022-09-07 +version = "7.92" project = "drupal" -datestamp = "1626883669" +datestamp = "1662554078" diff --git a/modules/syslog/syslog.module b/modules/syslog/syslog.module index af3eb0a0f0..32193a18dd 100644 --- a/modules/syslog/syslog.module +++ b/modules/syslog/syslog.module @@ -111,8 +111,8 @@ function syslog_watchdog(array $log_entry) { '!request_uri' => $log_entry['request_uri'], '!referer' => $log_entry['referer'], '!uid' => $log_entry['uid'], - '!link' => strip_tags($log_entry['link']), - '!message' => strip_tags(!isset($log_entry['variables']) ? $log_entry['message'] : strtr($log_entry['message'], $log_entry['variables'])), + '!link' => strip_tags((string) $log_entry['link']), + '!message' => strip_tags((string) (!isset($log_entry['variables']) ? $log_entry['message'] : strtr($log_entry['message'], $log_entry['variables']))), )); syslog($log_entry['severity'], $message); diff --git a/modules/system/image.gd.inc b/modules/system/image.gd.inc index 3d0797e424..bcc02cf06b 100644 --- a/modules/system/image.gd.inc +++ b/modules/system/image.gd.inc @@ -204,9 +204,11 @@ function image_gd_rotate(stdClass $image, $degrees, $background = NULL) { * @see image_crop() */ function image_gd_crop(stdClass $image, $x, $y, $width, $height) { + $width = (int) $width; + $height = (int) $height; $res = image_gd_create_tmp($image, $width, $height); - if (!imagecopyresampled($res, $image->resource, 0, 0, $x, $y, $width, $height, $width, $height)) { + if (!imagecopyresampled($res, $image->resource, 0, 0, (int) $x, (int) $y, $width, $height, $width, $height)) { return FALSE; } diff --git a/modules/system/system.admin.inc b/modules/system/system.admin.inc index b7e6fc9e70..bf3abb67d6 100644 --- a/modules/system/system.admin.inc +++ b/modules/system/system.admin.inc @@ -839,7 +839,7 @@ function system_modules($form, $form_state = array()) { elseif (isset($visible_files[$requires])) { $requires_name = $files[$requires]->info['name']; // Disable this module if it is incompatible with the dependency's version. - if ($incompatible_version = drupal_check_incompatibility($v, str_replace(DRUPAL_CORE_COMPATIBILITY . '-', '', $files[$requires]->info['version']))) { + if ($incompatible_version = drupal_check_incompatibility($v, str_replace(DRUPAL_CORE_COMPATIBILITY . '-', '', (string) $files[$requires]->info['version']))) { $extra['requires'][$requires] = t('@module (incompatible with version @version)', array( '@module' => $requires_name . $incompatible_version, '@version' => $files[$requires]->info['version'], @@ -886,7 +886,13 @@ function system_modules($form, $form_state = array()) { // one. if ($module->status && isset($module->info['configure'])) { $configure_link = menu_get_item($module->info['configure']); - if ($configure_link['access']) { + if ($configure_link === FALSE) { + watchdog('system', 'Module %module specifies an invalid path for configuration: %configure', array( + '%module' => $module->info['name'], + '%configure' => $module->info['configure'], + )); + } + else if ($configure_link['access']) { $extra['links']['configure'] = array( '#type' => 'link', '#title' => t('Configure'), @@ -929,6 +935,8 @@ function system_modules($form, $form_state = array()) { ), // Ensure that the "Core" package fieldset comes first. '#weight' => $package == 'Core' ? -10 : NULL, + // Hide this package unless we're running a test. + '#access' => !($package == 'Only For Testing' && !drupal_valid_test_ua()), ); } @@ -2358,6 +2366,10 @@ function system_status($check = FALSE) { * Menu callback: run cron manually. */ function system_run_cron() { + if (!isset($_GET['token']) || !drupal_valid_token($_GET['token'], 'run-cron')) { + return MENU_ACCESS_DENIED; + } + // Run cron manually if (drupal_cron_run()) { drupal_set_message(t('Cron ran successfully.')); @@ -2386,6 +2398,7 @@ function system_batch_page() { if ($output === FALSE) { drupal_access_denied(); + drupal_exit(); } elseif (isset($output)) { // Force a page without blocks or messages to diff --git a/modules/system/system.api.php b/modules/system/system.api.php index d5de102405..3a14738906 100644 --- a/modules/system/system.api.php +++ b/modules/system/system.api.php @@ -3175,7 +3175,7 @@ function hook_requirements($phase) { ); } - $requirements['cron']['description'] .= ' ' . $t('You can run cron manually.', array('@cron' => url('admin/reports/status/run-cron'))); + $requirements['cron']['description'] .= ' ' . $t('You can run cron manually.', array('@cron' => url('admin/reports/status/run-cron', array('query' => array('token' => drupal_get_token('run-cron')))))); $requirements['cron']['title'] = $t('Cron maintenance tasks'); } diff --git a/modules/system/system.info b/modules/system/system.info index 5f8b0388a6..b904ea725a 100644 --- a/modules/system/system.info +++ b/modules/system/system.info @@ -12,7 +12,7 @@ files[] = system.test required = TRUE configure = admin/config/system -; Information added by Drupal.org packaging script on 2021-07-21 -version = "7.82" +; Information added by Drupal.org packaging script on 2022-09-07 +version = "7.92" project = "drupal" -datestamp = "1626883669" +datestamp = "1662554078" diff --git a/modules/system/system.install b/modules/system/system.install index 01b0af474d..147f61c3ae 100644 --- a/modules/system/system.install +++ b/modules/system/system.install @@ -236,20 +236,36 @@ function system_requirements($phase) { // Test settings.php file writability if ($phase == 'runtime') { - $conf_dir = drupal_verify_install_file(conf_path(), FILE_NOT_WRITABLE, 'dir'); - $conf_file = drupal_verify_install_file(conf_path() . '/settings.php', FILE_EXIST|FILE_READABLE|FILE_NOT_WRITABLE); - if (!$conf_dir || !$conf_file) { + $conf_errors = array(); + // Allow system administrators to ignore permissions hardening for the site + // directory. This allows additional files in the site directory to be + // updated when they are managed in a version control system. + $skip_permissions_hardening = variable_get('skip_permissions_hardening', FALSE); + + if ($skip_permissions_hardening) { + $error_value = t('Protection disabled'); + // If permissions hardening is disabled, then only show a warning for a + // writable file, as a reminder, rather than an error. + $file_protection_severity = REQUIREMENT_WARNING; + } + else { + $error_value = t('Not protected'); + // In normal operation, writable files or directories are an error. + $file_protection_severity = REQUIREMENT_ERROR; + if (!drupal_verify_install_file(conf_path(), FILE_NOT_WRITABLE, 'dir')) { + $conf_errors[] = $t('The directory %file is not protected from modifications and poses a security risk. You must change the directory\'s permissions to be non-writable. ', array('%file' => conf_path())); + } + } + if (!drupal_verify_install_file(conf_path() . '/settings.php', FILE_EXIST | FILE_READABLE | FILE_NOT_WRITABLE, 'file', !$skip_permissions_hardening)) { + $conf_errors[] = $t('The file %file is not protected from modifications and poses a security risk. You must change the file\'s permissions to be non-writable.', array('%file' => conf_path() . '/settings.php')); + } + + if (!empty($conf_errors)) { $requirements['settings.php'] = array( - 'value' => $t('Not protected'), - 'severity' => REQUIREMENT_ERROR, - 'description' => '', + 'value' => $error_value, + 'severity' => $file_protection_severity, + 'description' => implode('
', $conf_errors), ); - if (!$conf_dir) { - $requirements['settings.php']['description'] .= $t('The directory %file is not protected from modifications and poses a security risk. You must change the directory\'s permissions to be non-writable. ', array('%file' => conf_path())); - } - if (!$conf_file) { - $requirements['settings.php']['description'] .= $t('The file %file is not protected from modifications and poses a security risk. You must change the file\'s permissions to be non-writable.', array('%file' => conf_path() . '/settings.php')); - } } else { $requirements['settings.php'] = array( @@ -323,7 +339,7 @@ function system_requirements($phase) { $description = $t('Cron has not run recently.') . ' ' . $help; } - $description .= ' ' . $t('You can run cron manually.', array('@cron' => url('admin/reports/status/run-cron'))); + $description .= ' ' . $t('You can run cron manually.', array('@cron' => url('admin/reports/status/run-cron', array('query' => array('token' => drupal_get_token('run-cron')))))); $description .= '
' . $t('To run cron from outside the site, go to !cron', array('!cron' => url($base_url . '/cron.php', array('external' => TRUE, 'query' => array('cron_key' => variable_get('cron_key', 'drupal')))))); $requirements['cron'] = array( @@ -473,7 +489,7 @@ function system_requirements($phase) { // Check for an incompatible version. $required_file = $files[$required_module]; $required_name = $required_file->info['name']; - $version = str_replace(DRUPAL_CORE_COMPATIBILITY . '-', '', $required_file->info['version']); + $version = str_replace(DRUPAL_CORE_COMPATIBILITY . '-', '', (string) $required_file->info['version']); $compatibility = drupal_check_incompatibility($requirement, $version); if ($compatibility) { $compatibility = rtrim(substr($compatibility, 2), ')'); @@ -522,7 +538,7 @@ function system_requirements($phase) { // Warning for httpoxy on IIS with affected PHP versions. // @see https://www.drupal.org/node/2783079 - if (strpos($software, 'Microsoft-IIS') !== FALSE && (version_compare(PHP_VERSION, '5.5.38', '<') + if (strpos((string) $software, 'Microsoft-IIS') !== FALSE && (version_compare(PHP_VERSION, '5.5.38', '<') || (version_compare(PHP_VERSION, '5.6.0', '>=') && version_compare(PHP_VERSION, '5.6.24', '<')) || (version_compare(PHP_VERSION, '7.0.0', '>=') && version_compare(PHP_VERSION, '7.0.9', '<')) )) { diff --git a/modules/system/system.module b/modules/system/system.module index 02785ef575..ba5da73f7a 100644 --- a/modules/system/system.module +++ b/modules/system/system.module @@ -1328,6 +1328,7 @@ function system_library() { 'version' => '1.8.7', 'js' => array( 'misc/ui/jquery.ui.datepicker.min.js' => array(), + 'misc/ui/jquery.ui.datepicker-1.13.0-backport.js' => array(), ), 'css' => array( 'misc/ui/jquery.ui.datepicker.css' => array(), @@ -1341,7 +1342,8 @@ function system_library() { 'website' => 'http://jqueryui.com/demos/dialog/', 'version' => '1.8.7', 'js' => array( - 'misc/ui/jquery.ui.dialog.min.js' => array(), + 'misc/ui/jquery.ui.dialog.min.js' => array(), + 'misc/ui/jquery.ui.dialog-1.13.0-backport.js' => array(), ), 'css' => array( 'misc/ui/jquery.ui.dialog.css' => array(), @@ -1397,6 +1399,7 @@ function system_library() { 'version' => '1.8.7', 'js' => array( 'misc/ui/jquery.ui.position.min.js' => array(), + 'misc/ui/jquery.ui.position-1.13.0-backport.js' => array(), ), ); $libraries['ui.progressbar'] = array( @@ -4085,3 +4088,21 @@ function system_admin_paths() { ); return $paths; } + +/** + * Implements hook_file_download(). + */ +function system_file_download($uri) { + $core_schemes = array('public', 'private', 'temporary'); + $additional_public_schemes = array_diff(variable_get('file_additional_public_schemes', array()), $core_schemes); + if ($additional_public_schemes) { + $scheme = file_uri_scheme($uri); + if (in_array($scheme, $additional_public_schemes, TRUE)) { + return array( + // Returning any header grants access, and setting the 'Cache-Control' + // header is appropriate for public files. + 'Cache-Control' => 'public', + ); + } + } +} diff --git a/modules/system/system.tar.inc b/modules/system/system.tar.inc index 505f2c0ba7..0b26f9cb76 100644 --- a/modules/system/system.tar.inc +++ b/modules/system/system.tar.inc @@ -41,7 +41,7 @@ /** * Note on Drupal 7 porting. - * This file origin is Tar.php, release 1.4.9 (stable) with some code + * This file origin is Tar.php, release 1.4.14 (stable) with some code * from PEAR.php, release 1.10.10 (stable) both at http://pear.php.net. * To simplify future porting from pear of this file, you should not * do cosmetic or other non significant changes to this file. @@ -792,7 +792,7 @@ class Archive_Tar */ public function setIgnoreList($list) { - $regexp = str_replace(array('#', '.', '^', '$'), array('\#', '\.', '\^', '\$'), $list); + $list = str_replace(array('#', '.', '^', '$'), array('\#', '\.', '\^', '\$'), $list); $regexp = '#/' . join('$|/', $list) . '#'; $this->setIgnoreRegexp($regexp); } @@ -1336,7 +1336,7 @@ class Archive_Tar while (($v_buffer = fread($v_file, $this->buffer_length)) != '') { $buffer_length = strlen("$v_buffer"); if ($buffer_length != $this->buffer_length) { - $pack_size = ((int)($buffer_length / 512) + 1) * 512; + $pack_size = ((int)($buffer_length / 512) + ($buffer_length % 512 !== 0 ? 1 : 0)) * 512; $pack_format = sprintf('a%d', $pack_size); } else { $pack_format = sprintf('a%d', $this->buffer_length); @@ -1465,11 +1465,13 @@ class Archive_Tar $userinfo = posix_getpwuid($v_info[4]); $groupinfo = posix_getgrgid($v_info[5]); - $v_uname = $userinfo['name']; - $v_gname = $groupinfo['name']; - } else { - $v_uname = ''; - $v_gname = ''; + if (isset($userinfo['name'])) { + $v_uname = $userinfo['name']; + } + + if (isset($groupinfo['name'])) { + $v_gname = $groupinfo['name']; + } } $v_devmajor = ''; @@ -1578,8 +1580,13 @@ class Archive_Tar $userinfo = posix_getpwuid($p_uid); $groupinfo = posix_getgrgid($p_gid); - $v_uname = $userinfo['name']; - $v_gname = $groupinfo['name']; + if ($userinfo === false || $groupinfo === false) { + $v_uname = ''; + $v_gname = ''; + } else { + $v_uname = $userinfo['name']; + $v_gname = $groupinfo['name']; + } } else { $v_uname = ''; $v_gname = ''; diff --git a/modules/system/system.test b/modules/system/system.test index 45c6648c43..0d6a9e76aa 100644 --- a/modules/system/system.test +++ b/modules/system/system.test @@ -925,6 +925,22 @@ class CronRunTestCase extends DrupalWebTestCase { $this->assertEqual($result, 'success', 'Cron correctly handles exceptions thrown during hook_cron() invocations.'); } + /** + * Ensure that the manual cron run is working. + */ + function testManualCron() { + $admin_user = $this->drupalCreateUser(array('administer site configuration')); + $this->drupalLogin($admin_user); + + $this->drupalGet('admin/reports/status/run-cron'); + $this->assertResponse(403); + + $this->drupalGet('admin/reports/status'); + $this->clickLink(t('run cron manually')); + $this->assertResponse(200); + $this->assertText(t('Cron ran successfully.')); + } + /** * Tests that hook_flush_caches() is not invoked on every single cron run. * @@ -1101,6 +1117,16 @@ class AccessDeniedTestCase extends DrupalWebTestCase { // Check that we're still on the same page. $this->assertText(t('Site information')); + + // Check batch page response. + $query_parameters = array( + ':type' => 'php', + ':severity' => WATCHDOG_WARNING, + ); + $this->assertEqual(db_query('SELECT COUNT(*) FROM {watchdog} WHERE type = :type AND severity = :severity', $query_parameters)->fetchField(), 0, 'No warning message appears in the logs before accessing the batch page.'); + $this->drupalGet('batch'); + $this->assertResponse(403); + $this->assertEqual(db_query('SELECT COUNT(*) FROM {watchdog} WHERE type = :type AND severity = :severity', $query_parameters)->fetchField(), 0, 'No warning message appears in the logs after accessing the batch page.'); } } @@ -3096,3 +3122,192 @@ class ConfirmFormTest extends DrupalWebTestCase { return $this->assertTrue(isset($links[0]), $message, $group); } } + +/** + * Test case for Archiver classes. + * + * Cannot be a DrupalUnitTestCase as it requires access to the db and files. + */ +class SystemArchiverTest extends DrupalWebTestCase +{ + + public static function getInfo() + { + return array( + 'name' => 'Archiver tests', + 'description' => 'Test the Archiver classes.', + 'group' => 'System', + ); + } + + /** + * Tests interacting with a tarball archive. + */ + public function testArchiverTarball() { + $src_tarball = DRUPAL_ROOT . '/' . drupal_get_path('module', 'system') . '/tests/system_test_archive.tar.gz'; + $tmp = file_directory_temp(); + $tarball = $tmp . '/' . basename($src_tarball); + file_unmanaged_copy($src_tarball, $tarball); + try { + $archiver = archiver_get_archiver($tarball); + } + catch (Exception $e) { + // The file's not there (this is not part of the test). + $this->assertTrue(FALSE, $e); + return; + } + + // Test \ArchiverTar::listContents + $listing = $archiver->listContents(); + $this->assertEqual(count($listing), 4, 'Tarball listing has 4 entries.'); + $this->assertTrue((strpos(implode(',', $listing), 'tarball.module') !== FALSE), 'Tarball listing includes tarball.module'); + + // Test \ArchiverTar::extract + $extract_dir = file_directory_temp() . '/testArchiverTarball'; + $archiver->extract($extract_dir); + $this->assertTrue(file_exists($extract_dir . '/system_test_archive/test.txt'), 'test.txt extracted from tarball'); + $this->assertEqual(count(glob($extract_dir . '/system_test_archive/*')), 3, '3 files extracted from tarball'); + + // Test \ArchiverTar::add + $extra_file = DRUPAL_ROOT . '/misc/druplicon.png'; + $archiver->add($extra_file); + $new_listing = $archiver->listContents(); + // \ArchiverTar::add probably should not add the new file with its absolute + // path. However that's how \Archive_Tar::add works. If we wanted to modify + // the file path within the archive, we could call \Archive_Tar::addModify + // directly and use its additional parameters. That could be done using + // \ArchiverTar::getArchive like in _testArchiverOutOfPath() which calls + // \Archive_Tar::extract directly. + $this->assertTrue(in_array($extra_file, $new_listing), 'Druplicon added to tarball'); + } + + /** + * Tests out-of-path extraction protection. + */ + public function testArchiverOutOfPath() { + $this->_testArchiverOutOfPath('system_test_archive_rel.tar', 'Relative out-of-path extraction caught'); + $this->_testArchiverOutOfPath('system_test_archive_abs.tgz', 'Absolute out-of-path extraction caught'); + } + + /** + * Helper to test out-of-path extraction protection. + */ + public function _testArchiverOutOfPath($archive, $message) { + $src_tarball = DRUPAL_ROOT . '/modules/system/tests/' . $archive; + $tarball = file_directory_temp() . '/' . $archive; + file_unmanaged_copy($src_tarball, $tarball); + try { + $archiver = archiver_get_archiver($tarball); + } catch (Exception $e) { + // The file's not there (this is not part of the test). + $this->assertTrue(FALSE, $e); + return; + } + + $extract_dir = file_directory_temp() . '/testArchiverTarball'; + $caught_exception = FALSE; + try { + // Drupal's \ArchiverTar::extract() doesn't support symlinks, so we have + // to access the underlying Archive_Tar object. + $archiver->getArchive()->extract($extract_dir, FALSE, TRUE); + } + catch (Exception $e) { + $caught_exception = (strpos($e->getMessage(), 'Out-of-path file extraction') !== FALSE); + } + $this->assertTrue($caught_exception, $message); + } +} + +/** + * Tests .htaccess is working correctly. + */ +class HtaccessTest extends DrupalWebTestCase { + public static function getInfo() { + return array( + 'name' => '.htaccess tests', + 'description' => 'Tests .htaccess is working correctly.', + 'group' => 'System', + ); + } + + /** + * Get an array of file paths for access testing. + */ + protected function getProtectedFiles() { + $path = drupal_get_path('module', 'system') . '/tests/fixtures/HtaccessTest'; + + // Tests the FilesMatch directive which denies access to certain file + // extensions. + $file_exts_to_deny = array( + 'engine', + 'inc', + 'info', + 'install', + 'make', + 'module', + 'module~', + 'module.bak', + 'module.orig', + 'module.save', + 'module.swo', + 'module.swp', + 'php~', + 'php.bak', + 'php.orig', + 'php.save', + 'php.swo', + 'php.swp', + 'profile', + 'po', + 'sh', + 'sql', + 'test', + 'theme', + 'tpl.php', + 'xtmpl', + ); + + foreach ($file_exts_to_deny as $file_ext) { + $file_paths["$path/access_test.$file_ext"] = 403; + } + + // Test extensions that should be permitted. + $file_exts_to_allow = array( + 'php-info.txt', + ); + + foreach ($file_exts_to_allow as $file_ext) { + $file_paths["$path/access_test.$file_ext"] = 200; + } + + // Ensure web server configuration files cannot be accessed. + $file_paths["$path/.htaccess"] = 403; + $file_paths["$path/web.config"] = 403; + + return $file_paths; + } + + /** + * Iterates over protected files and calls assertNoFileAccess(). + */ + function testFileAccess() { + foreach ($this->getProtectedFiles() as $file => $response_code) { + $this->assertFileAccess($file, $response_code); + } + } + + /** + * Asserts that a file exists and requesting it returns a specific response. + * + * @param string $path + * Path to file. Without leading slash. + * @param int $response_code + * The expected response code. For example: 200, 403 or 404. + */ + protected function assertFileAccess($path, $response_code) { + global $base_url; + $this->assertTrue(file_exists(DRUPAL_ROOT . '/' . $path), format_string('@filename exists.', array('@filename' => $path))); + $this->drupalGet($base_url . '/' . $path, array('external' => TRUE)); + $this->assertResponse($response_code, "Response code to $path should be $response_code"); + } +} diff --git a/modules/system/tests/cron_queue_test.info b/modules/system/tests/cron_queue_test.info index c231dc1609..f8618a1fbc 100644 --- a/modules/system/tests/cron_queue_test.info +++ b/modules/system/tests/cron_queue_test.info @@ -5,7 +5,7 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2021-07-21 -version = "7.82" +; Information added by Drupal.org packaging script on 2022-09-07 +version = "7.92" project = "drupal" -datestamp = "1626883669" +datestamp = "1662554078" diff --git a/modules/system/tests/system_cron_test.info b/modules/system/tests/system_cron_test.info index 7136292ab8..1d9572b9b7 100644 --- a/modules/system/tests/system_cron_test.info +++ b/modules/system/tests/system_cron_test.info @@ -5,7 +5,7 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2021-07-21 -version = "7.82" +; Information added by Drupal.org packaging script on 2022-09-07 +version = "7.92" project = "drupal" -datestamp = "1626883669" +datestamp = "1662554078" diff --git a/modules/taxonomy/taxonomy.admin.inc b/modules/taxonomy/taxonomy.admin.inc index 828fde0ab2..b44128ed81 100644 --- a/modules/taxonomy/taxonomy.admin.inc +++ b/modules/taxonomy/taxonomy.admin.inc @@ -826,8 +826,8 @@ function taxonomy_form_term_submit($form, &$form_state) { break; } - $current_parent_count = count($form_state['values']['parent']); - $previous_parent_count = count($form['#term']['parent']); + $current_parent_count = empty($form_state['values']['parent']) ? 0 : count((array) $form_state['values']['parent']); + $previous_parent_count = empty($form['#term']['parent']) ? 0 : count((array) $form['#term']['parent']); // Root doesn't count if it's the only parent. if ($current_parent_count == 1 && isset($form_state['values']['parent'][0])) { $current_parent_count = 0; diff --git a/modules/taxonomy/taxonomy.info b/modules/taxonomy/taxonomy.info index d2d0cace78..cbd5c8e1ab 100644 --- a/modules/taxonomy/taxonomy.info +++ b/modules/taxonomy/taxonomy.info @@ -8,7 +8,7 @@ files[] = taxonomy.module files[] = taxonomy.test configure = admin/structure/taxonomy -; Information added by Drupal.org packaging script on 2021-07-21 -version = "7.82" +; Information added by Drupal.org packaging script on 2022-09-07 +version = "7.92" project = "drupal" -datestamp = "1626883669" +datestamp = "1662554078" diff --git a/modules/taxonomy/taxonomy.module b/modules/taxonomy/taxonomy.module index e3ee48e0cf..d94e165ad6 100644 --- a/modules/taxonomy/taxonomy.module +++ b/modules/taxonomy/taxonomy.module @@ -207,7 +207,7 @@ function taxonomy_select_nodes($tid, $pager = TRUE, $limit = FALSE, $order = arr } $query = db_select('taxonomy_index', 't'); $query->addTag('node_access'); - $query->condition('tid', $tid); + $query->condition('t.tid', $tid); if ($pager) { $count_query = clone $query; $count_query->addExpression('COUNT(t.nid)'); @@ -1274,7 +1274,7 @@ class TaxonomyTermController extends DrupalDefaultEntityController { // LOWER() and drupal_strtolower() may return different results. foreach ($terms as $term) { $term_values = (array) $term; - if (isset($conditions['name']) && drupal_strtolower($conditions['name'] != drupal_strtolower($term_values['name']))) { + if (isset($conditions['name']) && drupal_strtolower($conditions['name']) != drupal_strtolower($term_values['name'])) { unset($terms[$term->tid]); } } @@ -1513,7 +1513,7 @@ function taxonomy_field_validate($entity_type, $entity, $field, $instance, $lang // Build an array of existing term IDs so they can be loaded with // taxonomy_term_load_multiple(); foreach ($items as $delta => $item) { - if (!empty($item['tid']) && $item['tid'] != 'autocreate') { + if (!empty($item['tid']) && $item['tid'] !== 'autocreate') { $tids[] = $item['tid']; } } @@ -1524,7 +1524,7 @@ function taxonomy_field_validate($entity_type, $entity, $field, $instance, $lang // allowed values for this field. foreach ($items as $delta => $item) { $validate = TRUE; - if (!empty($item['tid']) && $item['tid'] != 'autocreate') { + if (!empty($item['tid']) && $item['tid'] !== 'autocreate') { $validate = FALSE; foreach ($field['settings']['allowed_values'] as $settings) { // If no parent is specified, check if the term is in the vocabulary. @@ -1600,7 +1600,7 @@ function taxonomy_field_formatter_view($entity_type, $entity, $field, $instance, switch ($display['type']) { case 'taxonomy_term_reference_link': foreach ($items as $delta => $item) { - if ($item['tid'] == 'autocreate') { + if ($item['tid'] === 'autocreate') { $element[$delta] = array( '#markup' => check_plain($item['name']), ); @@ -1620,7 +1620,7 @@ function taxonomy_field_formatter_view($entity_type, $entity, $field, $instance, case 'taxonomy_term_reference_plain': foreach ($items as $delta => $item) { - $name = ($item['tid'] != 'autocreate' ? $item['taxonomy_term']->name : $item['name']); + $name = ($item['tid'] !== 'autocreate' ? $item['taxonomy_term']->name : $item['name']); $element[$delta] = array( '#markup' => check_plain($name), ); @@ -1631,9 +1631,9 @@ function taxonomy_field_formatter_view($entity_type, $entity, $field, $instance, foreach ($items as $delta => $item) { $entity->rss_elements[] = array( 'key' => 'category', - 'value' => $item['tid'] != 'autocreate' ? $item['taxonomy_term']->name : $item['name'], + 'value' => $item['tid'] !== 'autocreate' ? $item['taxonomy_term']->name : $item['name'], 'attributes' => array( - 'domain' => $item['tid'] != 'autocreate' ? url('taxonomy/term/' . $item['tid'], array('absolute' => TRUE)) : '', + 'domain' => $item['tid'] !== 'autocreate' ? url('taxonomy/term/' . $item['tid'], array('absolute' => TRUE)) : '', ), ); } @@ -1678,7 +1678,7 @@ function taxonomy_field_formatter_prepare_view($entity_type, $entities, $field, foreach ($entities as $id => $entity) { foreach ($items[$id] as $delta => $item) { // Force the array key to prevent duplicates. - if ($item['tid'] != 'autocreate') { + if ($item['tid'] !== 'autocreate') { $tids[$item['tid']] = $item['tid']; } } @@ -1697,7 +1697,7 @@ function taxonomy_field_formatter_prepare_view($entity_type, $entities, $field, $items[$id][$delta]['taxonomy_term'] = $terms[$item['tid']]; } // Terms to be created are not in $terms, but are still legitimate. - elseif ($item['tid'] == 'autocreate') { + elseif ($item['tid'] === 'autocreate') { // Leave the item in place. } // Otherwise, unset the instance value, since the term does not exist. @@ -1901,7 +1901,7 @@ function taxonomy_rdf_mapping() { */ function taxonomy_field_presave($entity_type, $entity, $field, $instance, $langcode, &$items) { foreach ($items as $delta => $item) { - if ($item['tid'] == 'autocreate') { + if ($item['tid'] === 'autocreate') { $term = (object) $item; unset($term->tid); taxonomy_term_save($term); @@ -2061,3 +2061,12 @@ function taxonomy_entity_query_alter($query) { unset($conditions['bundle']); } } + +/** + * Implements hook_file_download_access(). + */ +function taxonomy_file_download_access($field, $entity_type, $entity) { + if ($entity_type == 'taxonomy_term') { + return user_access('access content'); + } +} diff --git a/modules/taxonomy/taxonomy.test b/modules/taxonomy/taxonomy.test index a4b7ee833e..6188d8be5c 100644 --- a/modules/taxonomy/taxonomy.test +++ b/modules/taxonomy/taxonomy.test @@ -1008,6 +1008,29 @@ class TaxonomyTermTestCase extends TaxonomyWebTestCase { $this->assertEqual(count($terms), 0, 'No terms loaded when restricted by a non-existing vocabulary.'); } + /** + * Tests that taxonomy term detail page is working even after the default + * taxonomy_select_nodes() query is altered. + */ + public function testTaxonomySelectNodesAlter() { + // Create a new term. + $term = $this->createTerm($this->vocabulary); + + // Create an article. + $settings = array( + 'type' => 'article', + $this->instance['field_name'] => array(LANGUAGE_NONE => array(array('tid' => $term->tid))), + ); + $this->drupalCreateNode($settings); + + // Check if the taxonomy term detail page is working. + module_enable(array('taxonomy_nodes_test')); + variable_set('taxonomy_nodes_test_query_node_access_alter', TRUE); + $this->drupalGet('taxonomy/term/' . $term->tid); + $this->assertResponse(200, 'The taxonomy term page is working.'); + variable_set('taxonomy_nodes_test_query_node_access_alter', FALSE); + } + } /** @@ -1610,6 +1633,21 @@ class TaxonomyTermFieldTestCase extends TaxonomyWebTestCase { $this->assertEqual($allowed_values[2]['vocabulary'], 'foo', 'Index 2: Machine name was left untouched.'); } + /** + * Test empty taxonomy term reference field. + */ + function testEmptyTaxonomyTermReferenceField() { + // Test if an empty value in the taxonomy reference field would trigger + // autocreate or if the value would be saved correctly. + $langcode = LANGUAGE_NONE; + $entity = field_test_create_stub_entity(NULL, NULL); + $entity->{$this->field_name}[$langcode][0]['tid'] = 0; + field_test_entity_save($entity); + $entity = field_test_entity_test_load($entity->ftid); + field_test_entity_save($entity); + $this->pass('Empty term ID does not trigger autocreate.'); + } + } /** @@ -2093,3 +2131,114 @@ class TaxonomyQueryAlterTestCase extends TaxonomyWebTestCase { } } + +/** + * Tests for taxonomy terms cache usage. + */ +class TaxonomyTermCacheUsageTestCase extends TaxonomyWebTestCase { + public static function getInfo() { + return array( + 'name' => 'Taxonomy term cache usage', + 'description' => 'Tests for taxonomy term cache usage.', + 'group' => 'Taxonomy', + ); + } + + function setUp() { + parent::setUp('taxonomy', 'taxonomy_test'); + } + + /** + * Test taxonomy_get_term_by_name() cache usage. + */ + function testTaxonomyGetTermByNameCacheUsage() { + // Create vocabulary and term. + $new_vocabulary = $this->createVocabulary(); + $new_term = new stdClass(); + $new_term->name = 'MixedCaseTerm'; + $new_term->vid = $new_vocabulary->vid; + taxonomy_term_save($new_term); + + // Try to load term with mixed case letters from the cache. + $taxonomy_controller = new TestTaxonomyTermController('taxonomy_term'); + // First load to warm the cache. + $terms = $taxonomy_controller->load(array(), array('name' => $new_term->name)); + $this->assertTrue(isset($terms[$new_term->tid]), 'Term loaded using exact name and vocabulary machine name.'); + // Second load should load the $new_term from the cache. + $terms = $taxonomy_controller->loadFromCache(array(), array('name' => $new_term->name)); + $this->assertTrue(isset($terms[$new_term->tid]), 'Term loaded using the cache.'); + } + +} + +/** + * Tests appropriate access control to private file fields on a term. + */ +class TaxonomyPrivateFileTestCase extends TaxonomyWebTestCase { + public static function getInfo() { + return array( + 'name' => 'Taxonomy term private file access', + 'description' => 'Verifies private files on terms have appropriate access control.', + 'group' => 'Taxonomy', + ); + } + + public function setUp() { + parent::setUp('taxonomy_test'); + + // Remove access content permission from registered users. + user_role_revoke_permissions(DRUPAL_AUTHENTICATED_RID, array('access content')); + + $this->vocabulary = $this->createVocabulary(); + // Add a field instance to the vocabulary. + $field = array( + 'field_name' => 'field_test', + 'type' => 'image', + 'settings' => array( + 'uri_scheme' => 'private' + ), + ); + field_create_field($field); + $instance = array( + 'field_name' => 'field_test', + 'entity_type' => 'taxonomy_term', + 'label' => 'test', + 'bundle' => $this->vocabulary->machine_name, + 'widget' => array( + 'type' => 'image_image', + 'settings' => array(), + ), + ); + field_create_instance($instance); + } + + /** + * Tests access to a private file on a taxonomy term entity. + */ + public function testTaxonomyImageAccess() { + $user = $this->drupalCreateUser(array('administer site configuration', 'administer taxonomy', 'access user profiles')); + $this->drupalLogin($user); + + // Create a term and upload the image. + $term = $this->createTerm($this->vocabulary); + $files = $this->drupalGetTestFiles('image'); + $image = array_pop($files); + $edit['files[field_test_' . LANGUAGE_NONE . '_0]'] = drupal_realpath($image->uri); + $this->drupalPost('taxonomy/term/' . $term->tid . '/edit', $edit, t('Save')); + $term = taxonomy_term_load($term->tid); + $this->assertText(t('Updated term @name.', array('@name' => $term->name))); + + // Create a user that should have access to the file and one that doesn't. + $access_user = $this->drupalCreateUser(array('access content')); + $no_access_user = $this->drupalCreateUser(); + $image = file_load($term->field_test[LANGUAGE_NONE][0]['fid']); + $image_url = file_create_url($image->uri); + $this->drupalLogin($access_user); + $this->drupalGet($image_url); + $this->assertResponse(200, 'Private image on term is accessible with right permission'); + + $this->drupalLogin($no_access_user); + $this->drupalGet($image_url); + $this->assertResponse(403, 'Private image on term not accessible without right permission'); + } +} diff --git a/modules/toolbar/toolbar.info b/modules/toolbar/toolbar.info index ebb5cd8dac..b02a0abe1e 100644 --- a/modules/toolbar/toolbar.info +++ b/modules/toolbar/toolbar.info @@ -4,7 +4,7 @@ core = 7.x package = Core version = VERSION -; Information added by Drupal.org packaging script on 2021-07-21 -version = "7.82" +; Information added by Drupal.org packaging script on 2022-09-07 +version = "7.92" project = "drupal" -datestamp = "1626883669" +datestamp = "1662554078" diff --git a/modules/tracker/tracker.info b/modules/tracker/tracker.info index 4fc73a4fd7..e671674548 100644 --- a/modules/tracker/tracker.info +++ b/modules/tracker/tracker.info @@ -6,7 +6,7 @@ version = VERSION core = 7.x files[] = tracker.test -; Information added by Drupal.org packaging script on 2021-07-21 -version = "7.82" +; Information added by Drupal.org packaging script on 2022-09-07 +version = "7.92" project = "drupal" -datestamp = "1626883669" +datestamp = "1662554078" diff --git a/modules/translation/tests/translation_test.info b/modules/translation/tests/translation_test.info index 08492c01ac..71ebebfcfb 100644 --- a/modules/translation/tests/translation_test.info +++ b/modules/translation/tests/translation_test.info @@ -5,7 +5,7 @@ package = Testing version = VERSION hidden = TRUE -; Information added by Drupal.org packaging script on 2021-07-21 -version = "7.82" +; Information added by Drupal.org packaging script on 2022-09-07 +version = "7.92" project = "drupal" -datestamp = "1626883669" +datestamp = "1662554078" diff --git a/modules/translation/translation.info b/modules/translation/translation.info index 60990920ee..600668d5b6 100644 --- a/modules/translation/translation.info +++ b/modules/translation/translation.info @@ -6,7 +6,7 @@ version = VERSION core = 7.x files[] = translation.test -; Information added by Drupal.org packaging script on 2021-07-21 -version = "7.82" +; Information added by Drupal.org packaging script on 2022-09-07 +version = "7.92" project = "drupal" -datestamp = "1626883669" +datestamp = "1662554078" diff --git a/modules/trigger/tests/trigger_test.info b/modules/trigger/tests/trigger_test.info index cf85753142..a5351b80d2 100644 --- a/modules/trigger/tests/trigger_test.info +++ b/modules/trigger/tests/trigger_test.info @@ -4,7 +4,7 @@ package = Testing core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2021-07-21 -version = "7.82" +; Information added by Drupal.org packaging script on 2022-09-07 +version = "7.92" project = "drupal" -datestamp = "1626883669" +datestamp = "1662554078" diff --git a/modules/trigger/trigger.info b/modules/trigger/trigger.info index 03b43f15ef..c62794b351 100644 --- a/modules/trigger/trigger.info +++ b/modules/trigger/trigger.info @@ -6,7 +6,7 @@ core = 7.x files[] = trigger.test configure = admin/structure/trigger -; Information added by Drupal.org packaging script on 2021-07-21 -version = "7.82" +; Information added by Drupal.org packaging script on 2022-09-07 +version = "7.92" project = "drupal" -datestamp = "1626883669" +datestamp = "1662554078" diff --git a/modules/update/tests/aaa_update_test.info b/modules/update/tests/aaa_update_test.info index 92df754514..d0c897cc2c 100644 --- a/modules/update/tests/aaa_update_test.info +++ b/modules/update/tests/aaa_update_test.info @@ -4,7 +4,7 @@ package = Testing core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2021-07-21 -version = "7.82" +; Information added by Drupal.org packaging script on 2022-09-07 +version = "7.92" project = "drupal" -datestamp = "1626883669" +datestamp = "1662554078" diff --git a/modules/update/tests/bbb_update_test.info b/modules/update/tests/bbb_update_test.info index 9c5d5f18e5..22eb3384ff 100644 --- a/modules/update/tests/bbb_update_test.info +++ b/modules/update/tests/bbb_update_test.info @@ -4,7 +4,7 @@ package = Testing core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2021-07-21 -version = "7.82" +; Information added by Drupal.org packaging script on 2022-09-07 +version = "7.92" project = "drupal" -datestamp = "1626883669" +datestamp = "1662554078" diff --git a/modules/update/tests/ccc_update_test.info b/modules/update/tests/ccc_update_test.info index ec3badc4bc..35a601c9bc 100644 --- a/modules/update/tests/ccc_update_test.info +++ b/modules/update/tests/ccc_update_test.info @@ -4,7 +4,7 @@ package = Testing core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2021-07-21 -version = "7.82" +; Information added by Drupal.org packaging script on 2022-09-07 +version = "7.92" project = "drupal" -datestamp = "1626883669" +datestamp = "1662554078" diff --git a/modules/update/tests/themes/update_test_admintheme/update_test_admintheme.info b/modules/update/tests/themes/update_test_admintheme/update_test_admintheme.info index c7bcefc7b6..f3799feb71 100644 --- a/modules/update/tests/themes/update_test_admintheme/update_test_admintheme.info +++ b/modules/update/tests/themes/update_test_admintheme/update_test_admintheme.info @@ -3,7 +3,7 @@ description = Test theme which is used as admin theme. core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2021-07-21 -version = "7.82" +; Information added by Drupal.org packaging script on 2022-09-07 +version = "7.92" project = "drupal" -datestamp = "1626883669" +datestamp = "1662554078" diff --git a/modules/update/tests/themes/update_test_basetheme/update_test_basetheme.info b/modules/update/tests/themes/update_test_basetheme/update_test_basetheme.info index 6d5ab97dc6..a84e02b1a2 100644 --- a/modules/update/tests/themes/update_test_basetheme/update_test_basetheme.info +++ b/modules/update/tests/themes/update_test_basetheme/update_test_basetheme.info @@ -3,7 +3,7 @@ description = Test theme which acts as a base theme for other test subthemes. core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2021-07-21 -version = "7.82" +; Information added by Drupal.org packaging script on 2022-09-07 +version = "7.92" project = "drupal" -datestamp = "1626883669" +datestamp = "1662554078" diff --git a/modules/update/tests/themes/update_test_subtheme/update_test_subtheme.info b/modules/update/tests/themes/update_test_subtheme/update_test_subtheme.info index 2efff4d327..3259dcff3f 100644 --- a/modules/update/tests/themes/update_test_subtheme/update_test_subtheme.info +++ b/modules/update/tests/themes/update_test_subtheme/update_test_subtheme.info @@ -4,7 +4,7 @@ core = 7.x base theme = update_test_basetheme hidden = TRUE -; Information added by Drupal.org packaging script on 2021-07-21 -version = "7.82" +; Information added by Drupal.org packaging script on 2022-09-07 +version = "7.92" project = "drupal" -datestamp = "1626883669" +datestamp = "1662554078" diff --git a/modules/update/tests/update_test.info b/modules/update/tests/update_test.info index f0bec43f09..b829dd6bab 100644 --- a/modules/update/tests/update_test.info +++ b/modules/update/tests/update_test.info @@ -5,7 +5,7 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2021-07-21 -version = "7.82" +; Information added by Drupal.org packaging script on 2022-09-07 +version = "7.92" project = "drupal" -datestamp = "1626883669" +datestamp = "1662554078" diff --git a/modules/update/update.info b/modules/update/update.info index 9c6ab9ba53..0b28d0ee0c 100644 --- a/modules/update/update.info +++ b/modules/update/update.info @@ -6,7 +6,7 @@ core = 7.x files[] = update.test configure = admin/reports/updates/settings -; Information added by Drupal.org packaging script on 2021-07-21 -version = "7.82" +; Information added by Drupal.org packaging script on 2022-09-07 +version = "7.92" project = "drupal" -datestamp = "1626883669" +datestamp = "1662554078" diff --git a/modules/update/update.module b/modules/update/update.module index a59c7d7f91..67f832cbc5 100644 --- a/modules/update/update.module +++ b/modules/update/update.module @@ -367,8 +367,9 @@ function update_cache_clear_submit($form, &$form_state) { */ function _update_no_data() { $destination = drupal_get_destination(); + $cron_token = array('token' => drupal_get_token('run-cron')); return t('No update information available. Run cron or check manually.', array( - '@run_cron' => url('admin/reports/status/run-cron', array('query' => $destination)), + '@run_cron' => url('admin/reports/status/run-cron', array('query' => $cron_token + $destination)), '@check_manually' => url('admin/reports/updates/check', array('query' => $destination)), )); } diff --git a/modules/user/tests/user_flood_test.info b/modules/user/tests/user_flood_test.info index 895cfff5f4..be30b610f7 100644 --- a/modules/user/tests/user_flood_test.info +++ b/modules/user/tests/user_flood_test.info @@ -5,7 +5,7 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2021-07-21 -version = "7.82" +; Information added by Drupal.org packaging script on 2022-09-07 +version = "7.92" project = "drupal" -datestamp = "1626883669" +datestamp = "1662554078" diff --git a/modules/user/tests/user_form_test.info b/modules/user/tests/user_form_test.info index e15a70b353..7b48bab23f 100644 --- a/modules/user/tests/user_form_test.info +++ b/modules/user/tests/user_form_test.info @@ -5,7 +5,7 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2021-07-21 -version = "7.82" +; Information added by Drupal.org packaging script on 2022-09-07 +version = "7.92" project = "drupal" -datestamp = "1626883669" +datestamp = "1662554078" diff --git a/modules/user/tests/user_session_test.info b/modules/user/tests/user_session_test.info index df6510db6d..00221d1978 100644 --- a/modules/user/tests/user_session_test.info +++ b/modules/user/tests/user_session_test.info @@ -5,7 +5,7 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2021-07-21 -version = "7.82" +; Information added by Drupal.org packaging script on 2022-09-07 +version = "7.92" project = "drupal" -datestamp = "1626883669" +datestamp = "1662554078" diff --git a/modules/user/user.admin.inc b/modules/user/user.admin.inc index 0db7efb1a1..7a6adf2f2e 100644 --- a/modules/user/user.admin.inc +++ b/modules/user/user.admin.inc @@ -167,6 +167,7 @@ function user_admin_account() { 'status' => array('data' => t('Status'), 'field' => 'u.status'), 'roles' => array('data' => t('Roles')), 'member_for' => array('data' => t('Member for'), 'field' => 'u.created', 'sort' => 'desc'), + 'changed' => array('data' => t('Last changed'), 'field' => 'u.changed', 'sort' => 'desc'), 'access' => array('data' => t('Last access'), 'field' => 'u.access'), 'operations' => array('data' => t('Operations')), ); @@ -180,7 +181,7 @@ function user_admin_account() { $query = $query->extend('PagerDefault')->extend('TableSort'); $query - ->fields('u', array('uid', 'name', 'status', 'created', 'access')) + ->fields('u', array('uid', 'name', 'status', 'created', 'changed', 'access')) ->limit(50) ->orderByHeader($header) ->setCountQuery($count_query); @@ -226,6 +227,7 @@ function user_admin_account() { 'status' => $status[$account->status], 'roles' => theme('item_list', array('items' => $users_roles)), 'member_for' => format_interval(REQUEST_TIME - $account->created), + 'changed' => t('@time ago', array('@time' => format_interval(REQUEST_TIME - $account->changed))), 'access' => $account->access ? t('@time ago', array('@time' => format_interval(REQUEST_TIME - $account->access))) : t('never'), 'operations' => array('data' => array('#type' => 'link', '#title' => t('edit'), '#href' => "user/$account->uid/edit", '#options' => array('query' => $destination))), ); @@ -437,6 +439,18 @@ function user_admin_settings() { '#description' => t("This text is displayed at the picture upload form in addition to the default guidelines. It's useful for helping or instructing your users."), ); + $form['privacy'] = array( + '#type' => 'fieldset', + '#title' => t('Privacy'), + ); + $form['privacy']['user_password_reset_text'] = array( + '#type' => 'textarea', + '#title' => t('Password reset text'), + '#default_value' => variable_get('user_password_reset_text', t('If %identifier is a valid account, an email will be sent with instructions to reset your password.')), + '#description' => t('The text that appears when a user successfully submits the password reset form. Due to privacy concerns, it should not contain any information about previously registered users. %identifier will be replaced with what the user entered into the form field.'), + '#required' => TRUE, + ); + $form['email_title'] = array( '#type' => 'item', '#title' => t('E-mails'), @@ -843,27 +857,26 @@ function theme_user_permission_description($variables) { */ function user_admin_roles($form, $form_state) { $roles = user_roles(); + $role_weights = db_query('SELECT r.rid, r.weight FROM {role} r')->fetchAllKeyed(); $form['roles'] = array( '#tree' => TRUE, ); - $order = 0; foreach ($roles as $rid => $name) { $form['roles'][$rid]['#role'] = (object) array( 'rid' => $rid, 'name' => $name, - 'weight' => $order, + 'weight' => $role_weights[$rid], ); - $form['roles'][$rid]['#weight'] = $order; + $form['roles'][$rid]['#weight'] = $role_weights[$rid]; $form['roles'][$rid]['weight'] = array( '#type' => 'textfield', '#title' => t('Weight for @title', array('@title' => $name)), '#title_display' => 'invisible', '#size' => 4, - '#default_value' => $order, + '#default_value' => $role_weights[$rid], '#attributes' => array('class' => array('role-weight')), ); - $order++; } $form['name'] = array( diff --git a/modules/user/user.info b/modules/user/user.info index 9e206e7090..5412df8183 100644 --- a/modules/user/user.info +++ b/modules/user/user.info @@ -9,7 +9,7 @@ required = TRUE configure = admin/config/people stylesheets[all][] = user.css -; Information added by Drupal.org packaging script on 2021-07-21 -version = "7.82" +; Information added by Drupal.org packaging script on 2022-09-07 +version = "7.92" project = "drupal" -datestamp = "1626883669" +datestamp = "1662554078" diff --git a/modules/user/user.install b/modules/user/user.install index 7a74766a1e..43afb19a42 100644 --- a/modules/user/user.install +++ b/modules/user/user.install @@ -182,6 +182,12 @@ function user_schema() { 'default' => 0, 'description' => 'Timestamp for when user was created.', ), + 'changed' => array( + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + 'description' => 'Timestamp for when user was changed.', + ), 'access' => array( 'type' => 'int', 'not null' => TRUE, @@ -238,6 +244,7 @@ function user_schema() { 'indexes' => array( 'access' => array('access'), 'created' => array('created'), + 'changed' => array('changed'), 'mail' => array('mail'), 'picture' => array('picture'), ), @@ -312,6 +319,7 @@ function user_install() { 'name' => 'placeholder-for-uid-1', 'mail' => 'placeholder-for-uid-1', 'created' => REQUEST_TIME, + 'changed' => REQUEST_TIME, 'status' => 1, 'data' => NULL, )) @@ -922,6 +930,45 @@ function user_update_7019() { db_add_index('authmap', 'uid_module', array('uid', 'module')); } } + +/** + * Add changed field to users table. + */ +function user_update_7020() { + // The "changed" column was renamed to "access" in system_update_136(), and + // the "changed" index on "access" column may persist on old MySQL databases. + if (db_index_exists('users', 'changed')) { + if (!db_index_exists('users', 'access')) { + db_add_index('users', 'access', array('access')); + } + db_drop_index('users', 'changed'); + } + + $spec = array( + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + 'description' => 'Timestamp for when user was changed.', + ); + $keys = array( + 'indexes' => array( + 'changed' => array('changed'), + ), + ); + // In some cases sites have added the changed field themselves e.g. via a + // contrib or custom module. Ensure the field uses core's new schema. + if (db_field_exists('users', 'changed')) { + db_change_field('users', 'changed', 'changed', $spec, $keys); + } + else { + db_add_field('users', 'changed', $spec, $keys); + // Set the initial value for existing users. + db_update('users') + ->expression('changed', 'created') + ->execute(); + } + +} /** * @} End of "addtogroup updates-7.x-extra". */ diff --git a/modules/user/user.module b/modules/user/user.module index dfa05978cb..9f99980c55 100644 --- a/modules/user/user.module +++ b/modules/user/user.module @@ -304,7 +304,7 @@ class UserController extends DrupalDefaultEntityController { $picture_fids = array(); foreach ($queried_users as $key => $record) { $picture_fids[] = $record->picture; - $queried_users[$key]->data = unserialize($record->data); + $queried_users[$key]->data = unserialize((string) $record->data); $queried_users[$key]->roles = array(); if ($record->uid) { $queried_users[$record->uid]->roles[DRUPAL_AUTHENTICATED_RID] = 'authenticated user'; @@ -322,7 +322,7 @@ class UserController extends DrupalDefaultEntityController { // Add the full file objects for user pictures if enabled. if (!empty($picture_fids) && variable_get('user_pictures', 0)) { - $pictures = file_load_multiple($picture_fids); + $pictures = file_load_multiple(array_filter($picture_fids)); foreach ($queried_users as $account) { if (!empty($account->picture) && isset($pictures[$account->picture])) { $account->picture = $pictures[$account->picture]; @@ -506,6 +506,8 @@ function user_save($account, $edit = array(), $category = 'account') { // Do not allow 'uid' to be changed. $account->uid = $account->original->uid; + // Save current time as last changed time. + $account->changed = REQUEST_TIME; // Save changes to the user table. $success = drupal_write_record('users', $account, 'uid'); // Restore the picture object. @@ -577,6 +579,8 @@ function user_save($account, $edit = array(), $category = 'account') { if (!isset($account->created)) { $account->created = REQUEST_TIME; } + // Save current time as last changed time. + $account->changed = REQUEST_TIME; $success = drupal_write_record('users', $account); if ($success === FALSE) { // On a failed INSERT some other existing user's uid may be returned. diff --git a/modules/user/user.pages.inc b/modules/user/user.pages.inc index 937f7a31e4..1266bbfb59 100644 --- a/modules/user/user.pages.inc +++ b/modules/user/user.pages.inc @@ -107,9 +107,6 @@ function user_pass_validate($form, &$form_state) { flood_register_event('pass_reset_user', $user_pass_reset_user_window, $identifier); form_set_value(array('#parents' => array('account')), $account, $form_state); } - else { - form_set_error('name', t('Sorry, %name is not recognized as a user name or an e-mail address.', array('%name' => $name))); - } } /** @@ -120,14 +117,21 @@ function user_pass_validate($form, &$form_state) { function user_pass_submit($form, &$form_state) { global $language; - $account = $form_state['values']['account']; - // Mail one time login URL and instructions using current language. - $mail = _user_mail_notify('password_reset', $account, $language); - if (!empty($mail)) { - watchdog('user', 'Password reset instructions mailed to %name at %email.', array('%name' => $account->name, '%email' => $account->mail)); - drupal_set_message(t('Further instructions have been sent to your e-mail address.')); + $name = $form_state['values']['name']; + if (isset($form_state['values']['account'])) { + $account = $form_state['values']['account']; + // Mail one time login URL and instructions using current language. + $mail = _user_mail_notify('password_reset', $account, $language); + if (!empty($mail)) { + watchdog('user', 'Password reset instructions mailed to %name at %email.', array('%name' => $account->name, '%email' => $account->mail)); + } + } + else { + watchdog('user', 'Password reset form was submitted with an unknown or inactive account: %name.', array('%name' => $name)); } + $password_reset_text = variable_get('user_password_reset_text', t('If %identifier is a valid account, an email will be sent with instructions to reset your password.')); + drupal_set_message(format_string($password_reset_text, array('%identifier' => $name))); $form_state['redirect'] = 'user'; return; } diff --git a/modules/user/user.test b/modules/user/user.test index ec2f90d6f1..4cfb5162e6 100644 --- a/modules/user/user.test +++ b/modules/user/user.test @@ -164,6 +164,7 @@ class UserRegistrationTestCase extends DrupalWebTestCase { $this->assertEqual($new_user->theme, '', 'Correct theme field.'); $this->assertEqual($new_user->signature, '', 'Correct signature field.'); $this->assertTrue(($new_user->created > REQUEST_TIME - 20 ), 'Correct creation time.'); + $this->assertEqual($new_user->changed, $new_user->created, 'Correct changed time.'); $this->assertEqual($new_user->status, variable_get('user_register', USER_REGISTER_VISITORS_ADMINISTRATIVE_APPROVAL) == USER_REGISTER_VISITORS ? 1 : 0, 'Correct status field.'); $this->assertEqual($new_user->timezone, variable_get('date_default_timezone'), 'Correct time zone field.'); $this->assertEqual($new_user->language, '', 'Correct language field.'); @@ -534,11 +535,20 @@ class UserPasswordResetTestCase extends DrupalWebTestCase { // Attempt to reset password. $edit = array('name' => $account->name); $this->drupalPost('user/password', $edit, t('E-mail new password')); - // Confirm the password reset. - $this->assertText(t('Further instructions have been sent to your e-mail address.'), 'Password reset instructions mailed message displayed.'); + // Ensure the correct message is shown for a valid user name. + $password_reset_text = variable_get('user_password_reset_text', t('If %identifier is a valid account, an email will be sent with instructions to reset your password.')); + $this->assertRaw(format_string($password_reset_text, array('%identifier' => $account->name)), 'Password reset instructions mailed message displayed for a valid user.'); + // Ensure that flood control was not triggered. $this->assertNoText(t('is temporarily blocked. Try again later'), 'Flood control was not triggered by single password reset.'); + // Ensure the correct message is shown for a non-existent user name. + $name = $this->randomName(); + $edit = array('name' => $name); + $this->drupalPost('user/password', $edit, t('E-mail new password')); + $password_reset_text = variable_get('user_password_reset_text', t('If %identifier is a valid account, an email will be sent with instructions to reset your password.')); + $this->assertRaw(format_string($password_reset_text, array('%identifier' => $name)), 'Password reset instructions mailed message displayed for a non-existent user.'); + // Create an image field to enable an Ajax request on the user profile page. $field = array( 'field_name' => 'field_avatar', @@ -619,7 +629,8 @@ class UserPasswordResetTestCase extends DrupalWebTestCase { for ($i = 0; $i < 2; $i++) { $this->drupalPost('user/password', $edit, t('E-mail new password')); // Confirm the password reset. - $this->assertText(t('Further instructions have been sent to your e-mail address.'), 'Password reset instructions mailed message displayed.'); + $password_reset_text = variable_get('user_password_reset_text', t('If %identifier is a valid account, an email will be sent with instructions to reset your password.')); + $this->assertRaw(format_string($password_reset_text, array('%identifier' => $account->name)), 'Password reset instructions mailed message displayed.'); // Ensure that flood control was not triggered. $this->assertNoText(t('is temporarily blocked. Try again later'), 'Flood control was not triggered by password reset.'); } @@ -636,7 +647,8 @@ class UserPasswordResetTestCase extends DrupalWebTestCase { for ($i = 0; $i < 2; $i++) { $this->drupalPost('user/password', $edit, t('E-mail new password')); // Confirm the password reset. - $this->assertText(t('Further instructions have been sent to your e-mail address.'), 'Password reset instructions mailed message displayed.'); + $password_reset_text = variable_get('user_password_reset_text', t('If %identifier is a valid account, an email will be sent with instructions to reset your password.')); + $this->assertRaw(format_string($password_reset_text, array('%identifier' => $account->name)), 'Password reset instructions mailed message displayed.'); // Ensure that flood control was not triggered. $this->assertNoText(t('is temporarily blocked. Try again later'), 'Flood control was not triggered by password reset.'); } @@ -661,9 +673,9 @@ class UserPasswordResetTestCase extends DrupalWebTestCase { $name = $this->randomName(); $edit = array('name' => $name); $this->drupalPost('user/password', $edit, t('E-mail new password')); - // Confirm the password reset was not blocked. Note that @name is used - // instead of %name as assertText() works with plain text not HTML. - $this->assertText(t('Sorry, @name is not recognized as a user name or an e-mail address.', array('@name' => $name)), 'User name not recognized message displayed.'); + // Confirm the password reset was not blocked. + $password_reset_text = variable_get('user_password_reset_text', t('If %identifier is a valid account, an email will be sent with instructions to reset your password.')); + $this->assertRaw(format_string($password_reset_text, array('%identifier' => $name)), 'Password reset instructions mailed message displayed.'); // Ensure that flood control was not triggered. $this->assertNoText(t('is temporarily blocked. Try again later'), 'Flood control was not triggered by password reset.'); } @@ -1358,7 +1370,7 @@ class UserPictureTestCase extends DrupalWebTestCase { $this->assertRaw($text, 'File size cited as reason for failure.'); // Check if file is not uploaded. - $this->assertFalse(is_file($pic_path), 'File was not uploaded.'); + $this->assertFalse(is_file((string) $pic_path), 'File was not uploaded.'); } } @@ -2486,6 +2498,10 @@ class UserRoleAdminTestCase extends DrupalWebTestCase { $role = user_role_load($rid); $new_weight = $role->weight; $this->assertTrue(($old_weight + 1) == $new_weight, 'Role weight updated successfully.'); + + // Check if the updated weight is displayed on the roles settings page. + $this->drupalGet('admin/people/permissions/roles'); + $this->assertFieldByXPath("//input[@name='roles[$rid][weight]']", $new_weight, 'The role weight is displayed correctly.'); } } @@ -2532,6 +2548,8 @@ class UserTokenReplaceTestCase extends DrupalWebTestCase { $tests['[user:last-login:short]'] = format_date($account->login, 'short', '', NULL, $language->language); $tests['[user:created]'] = format_date($account->created, 'medium', '', NULL, $language->language); $tests['[user:created:short]'] = format_date($account->created, 'short', '', NULL, $language->language); + $tests['[user:changed]'] = format_date($account->changed, 'medium', '', NULL, $language->language); + $tests['[user:changed:short]'] = format_date($account->changed, 'short', '', NULL, $language->language); $tests['[current-user:name]'] = check_plain(format_username($global_account)); // Test to make sure that we generated something for each token. @@ -2552,6 +2570,25 @@ class UserTokenReplaceTestCase extends DrupalWebTestCase { $this->assertEqual($output, $expected, format_string('Unsanitized user token %token replaced.', array('%token' => $input))); } } + + /** + * Uses an anonymous user, then tests the tokens generated from it. + */ + function testAnonymousUserTokenReplacement() { + global $language; + + // Load anonymous user data. + $account = drupal_anonymous_user(); + + // Generate and test sanitized tokens. + $tests = array(); + $tests['[user:mail]'] = ''; + + foreach ($tests as $input => $expected) { + $output = token_replace($input, array('user' => $account), array('language' => $language)); + $this->assertEqual($output, $expected, format_string('Sanitized user token %token replaced.', array('%token' => $input))); + } + } } /** diff --git a/modules/user/user.tokens.inc b/modules/user/user.tokens.inc index 8dcea4b597..a372696da4 100644 --- a/modules/user/user.tokens.inc +++ b/modules/user/user.tokens.inc @@ -52,6 +52,12 @@ function user_token_info() { 'type' => 'date', ); + $user['changed'] = array( + 'name' => t("Changed"), + 'description' => t("The date the user account was changed."), + 'type' => 'date', + ); + return array( 'types' => $types, 'tokens' => array('user' => $user), @@ -90,7 +96,12 @@ function user_tokens($type, $tokens, array $data = array(), array $options = arr break; case 'mail': - $replacements[$original] = $sanitize ? check_plain($account->mail) : $account->mail; + if (empty($account->mail)) { + $replacements[$original] = ''; + } + else { + $replacements[$original] = $sanitize ? check_plain($account->mail) : $account->mail; + } break; case 'url': @@ -110,6 +121,11 @@ function user_tokens($type, $tokens, array $data = array(), array $options = arr // In the case of user_presave the created date may not yet be set. $replacements[$original] = !empty($account->created) ? format_date($account->created, 'medium', '', NULL, $language_code) : t('not yet created'); break; + + case 'changed': + // In the case of user_presave the created date may not yet be set. + $replacements[$original] = !empty($account->changed) ? format_date($account->changed, 'medium', '', NULL, $language_code) : t('not yet created'); + break; } } @@ -120,6 +136,10 @@ function user_tokens($type, $tokens, array $data = array(), array $options = arr if ($registered_tokens = token_find_with_prefix($tokens, 'created')) { $replacements += token_generate('date', $registered_tokens, array('date' => $account->created), $options); } + + if ($changed_tokens = token_find_with_prefix($tokens, 'changed')) { + $replacements += token_generate('date', $changed_tokens, array('date' => $account->changed), $options); + } } if ($type == 'current-user') { diff --git a/profiles/minimal/minimal.info b/profiles/minimal/minimal.info index d3b5d57599..3e6913a791 100644 --- a/profiles/minimal/minimal.info +++ b/profiles/minimal/minimal.info @@ -5,7 +5,7 @@ core = 7.x dependencies[] = block dependencies[] = dblog -; Information added by Drupal.org packaging script on 2021-07-21 -version = "7.82" +; Information added by Drupal.org packaging script on 2022-09-07 +version = "7.92" project = "drupal" -datestamp = "1626883669" +datestamp = "1662554078" diff --git a/profiles/standard/standard.info b/profiles/standard/standard.info index 917c293abc..887ac6ab78 100644 --- a/profiles/standard/standard.info +++ b/profiles/standard/standard.info @@ -24,7 +24,7 @@ dependencies[] = field_ui dependencies[] = file dependencies[] = rdf -; Information added by Drupal.org packaging script on 2021-07-21 -version = "7.82" +; Information added by Drupal.org packaging script on 2022-09-07 +version = "7.92" project = "drupal" -datestamp = "1626883669" +datestamp = "1662554078" diff --git a/profiles/standard/standard.install b/profiles/standard/standard.install index ae34eafafe..158c4eaaa0 100644 --- a/profiles/standard/standard.install +++ b/profiles/standard/standard.install @@ -356,7 +356,7 @@ function standard_install() { 'required' => FALSE, 'settings' => array( - 'file_directory' => 'field/image', + 'file_directory' => '[date:custom:Y]-[date:custom:m]', 'file_extensions' => 'png gif jpg jpeg', 'max_filesize' => '', 'max_resolution' => '', diff --git a/profiles/testing/modules/drupal_system_listing_compatible_test/drupal_system_listing_compatible_test.info b/profiles/testing/modules/drupal_system_listing_compatible_test/drupal_system_listing_compatible_test.info index ce74c6086f..9bebfb5ca1 100644 --- a/profiles/testing/modules/drupal_system_listing_compatible_test/drupal_system_listing_compatible_test.info +++ b/profiles/testing/modules/drupal_system_listing_compatible_test/drupal_system_listing_compatible_test.info @@ -6,7 +6,7 @@ core = 7.x hidden = TRUE files[] = drupal_system_listing_compatible_test.test -; Information added by Drupal.org packaging script on 2021-07-21 -version = "7.82" +; Information added by Drupal.org packaging script on 2022-09-07 +version = "7.92" project = "drupal" -datestamp = "1626883669" +datestamp = "1662554078" diff --git a/profiles/testing/modules/drupal_system_listing_incompatible_test/drupal_system_listing_incompatible_test.info b/profiles/testing/modules/drupal_system_listing_incompatible_test/drupal_system_listing_incompatible_test.info index 1d735e2773..eaf1fdd30e 100644 --- a/profiles/testing/modules/drupal_system_listing_incompatible_test/drupal_system_listing_incompatible_test.info +++ b/profiles/testing/modules/drupal_system_listing_incompatible_test/drupal_system_listing_incompatible_test.info @@ -8,7 +8,7 @@ version = VERSION core = 6.x hidden = TRUE -; Information added by Drupal.org packaging script on 2021-07-21 -version = "7.82" +; Information added by Drupal.org packaging script on 2022-09-07 +version = "7.92" project = "drupal" -datestamp = "1626883669" +datestamp = "1662554078" diff --git a/profiles/testing/testing.info b/profiles/testing/testing.info index ccc3d731df..f8730a516c 100644 --- a/profiles/testing/testing.info +++ b/profiles/testing/testing.info @@ -4,7 +4,7 @@ version = VERSION core = 7.x hidden = TRUE -; Information added by Drupal.org packaging script on 2021-07-21 -version = "7.82" +; Information added by Drupal.org packaging script on 2022-09-07 +version = "7.92" project = "drupal" -datestamp = "1626883669" +datestamp = "1662554078" diff --git a/scripts/run-tests.sh b/scripts/run-tests.sh index b18fd142ab..bc90233915 100755 --- a/scripts/run-tests.sh +++ b/scripts/run-tests.sh @@ -142,6 +142,8 @@ All arguments are long options. --all Run all available tests. --class Run tests identified by specific class names, instead of group names. + A specific test method can be added, for example, + 'UserAccountLinksUnitTests::testDisabledAccountLink'. --file Run tests identified by specific file names, instead of group names. Specify the path and the extension (i.e. 'modules/user/user.test'). @@ -326,7 +328,11 @@ function simpletest_script_init($server_software) { if (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on') { // Ensure that any and all environment variables are changed to https://. foreach ($_SERVER as $key => $value) { - $_SERVER[$key] = str_replace('http://', 'https://', $_SERVER[$key]); + // The first time this script runs $_SERVER['SERVER_SOFTWARE'] will be + // NULL, so avoid errors from str_replace(). + if (!empty($_SERVER[$key])) { + $_SERVER[$key] = str_replace('http://', 'https://', $_SERVER[$key]); + } } } @@ -408,10 +414,19 @@ function simpletest_script_run_one_test($test_id, $test_class) { simpletest_classloader_register(); - $test = new $test_class($test_id); + if (strpos($test_class, '::') > 0) { + list($class_name, $method) = explode('::', $test_class, 2); + $methods = array($method); + } + else { + $class_name = $test_class; + // Use empty array to run all the test methods. + $methods = array(); + } + $test = new $class_name($test_id); $test->useSetupInstallationCache = !empty($args['cache']); $test->useSetupModulesCache = !empty($args['cache-modules']); - $test->run(); + $test->run($methods); $info = $test->getInfo(); $had_fails = (isset($test->results['#fail']) && $test->results['#fail'] > 0); @@ -477,8 +492,16 @@ function simpletest_script_get_test_list() { // Check for valid class names. $test_list = array(); foreach ($args['test_names'] as $test_class) { - if (class_exists($test_class)) { - $test_list[] = $test_class; + list($class_name, $method) = explode('::', $test_class, 2); + if (class_exists($class_name)) { + if (empty($method) || method_exists($class_name, $method)) { + $test_list[] = $test_class; + } else { + $all_methods = get_class_methods($class_name); + simpletest_script_print_error('Test method not found: ' . $test_class); + simpletest_script_print_alternatives($method, $all_methods, 6); + exit(1); + } } else { $groups = simpletest_test_get_all(); @@ -486,8 +509,8 @@ function simpletest_script_get_test_list() { foreach ($groups as $group) { $all_classes = array_merge($all_classes, array_keys($group)); } - simpletest_script_print_error('Test class not found: ' . $test_class); - simpletest_script_print_alternatives($test_class, $all_classes, 6); + simpletest_script_print_error('Test class not found: ' . $class_name); + simpletest_script_print_alternatives($class_name, $all_classes, 6); exit(SIMPLETEST_SCRIPT_EXIT_FAILURE); } } @@ -597,9 +620,14 @@ function simpletest_script_reporter_init() { } else { echo "Tests to be run:\n"; - foreach ($test_list as $class_name) { - $info = call_user_func(array($class_name, 'getInfo')); - echo " - " . $info['name'] . ' (' . $class_name . ')' . "\n"; + foreach ($test_list as $test_name) { + if (strpos($test_name, '::') > 0) { + list($test_class, $method) = explode('::', $test_name, 2); + $info = call_user_func(array($test_class, 'getInfo')); + } else { + $info = call_user_func(array($test_name, 'getInfo')); + } + echo " - " . $info['name'] . ' (' . $test_name . ')' . "\n"; } echo "\n"; } diff --git a/sites/default/default.settings.php b/sites/default/default.settings.php index f8941f94c5..c521bc3b3a 100644 --- a/sites/default/default.settings.php +++ b/sites/default/default.settings.php @@ -760,3 +760,64 @@ * to FALSE. */ # $conf['block_interest_cohort'] = TRUE; + +/** + * Retain legacy has_js cookie. + * + * Older releases of Drupal set a has_js cookie with a boolean value which + * server-side code can use to determine whether JavaScript is available. + * + * This functionality can be re-enabled by setting this variable to TRUE. + */ +# $conf['set_has_js_cookie'] = FALSE; + +/** + * Skip file system permissions hardening. + * + * The system module will periodically check the permissions of your site's + * site directory to ensure that it is not writable by the website user. For + * sites that are managed with a version control system, this can cause problems + * when files in that directory such as settings.php are updated, because the + * user pulling in the changes won't have permissions to modify files in the + * directory. + */ +# $conf['skip_permissions_hardening'] = TRUE; + +/** + * Additional public file schemes: + * + * Public schemes are URI schemes that allow download access to all users for + * all files within that scheme. + * + * The "public" scheme is always public, and the "private" scheme is always + * private, but other schemes, such as "https", "s3", "example", or others, + * can be either public or private depending on the site. By default, they're + * private, and access to individual files is controlled via + * hook_file_download(). + * + * Typically, if a scheme should be public, a module makes it public by + * implementing hook_file_download(), and granting access to all users for all + * files. This could be either the same module that provides the stream wrapper + * for the scheme, or a different module that decides to make the scheme + * public. However, in cases where a site needs to make a scheme public, but + * is unable to add code in a module to do so, the scheme may be added to this + * variable, the result of which is that system_file_download() grants public + * access to all files within that scheme. + */ +# $conf['file_additional_public_schemes'] = array('example'); + +/** + * Sensitive request headers in drupal_http_request() when following a redirect. + * + * By default drupal_http_request() will strip sensitive request headers when + * following a redirect if the redirect location has a different http host to + * the original request, or if the scheme downgrades from https to http. + * + * These variables allow opting out of this behaviour. Careful consideration of + * the security implications of opting out is recommended. + * + * @see _drupal_should_strip_sensitive_headers_on_http_redirect() + * @see drupal_http_request() + */ +# $conf['drupal_http_request_strip_sensitive_headers_on_host_change'] = TRUE; +# $conf['drupal_http_request_strip_sensitive_headers_on_https_downgrade'] = TRUE; diff --git a/themes/bartik/bartik.info b/themes/bartik/bartik.info index f78f52be2d..9ebf98ec80 100644 --- a/themes/bartik/bartik.info +++ b/themes/bartik/bartik.info @@ -34,7 +34,7 @@ regions[footer] = Footer settings[shortcut_module_link] = 0 -; Information added by Drupal.org packaging script on 2021-07-21 -version = "7.82" +; Information added by Drupal.org packaging script on 2022-09-07 +version = "7.92" project = "drupal" -datestamp = "1626883669" +datestamp = "1662554078" diff --git a/themes/garland/garland.info b/themes/garland/garland.info index cee7d96361..0495d6a641 100644 --- a/themes/garland/garland.info +++ b/themes/garland/garland.info @@ -7,7 +7,7 @@ stylesheets[all][] = style.css stylesheets[print][] = print.css settings[garland_width] = fluid -; Information added by Drupal.org packaging script on 2021-07-21 -version = "7.82" +; Information added by Drupal.org packaging script on 2022-09-07 +version = "7.92" project = "drupal" -datestamp = "1626883669" +datestamp = "1662554078" diff --git a/themes/seven/seven.info b/themes/seven/seven.info index fad75ebf6e..3042cf886f 100644 --- a/themes/seven/seven.info +++ b/themes/seven/seven.info @@ -13,7 +13,7 @@ regions[page_bottom] = Page bottom regions[sidebar_first] = First sidebar regions_hidden[] = sidebar_first -; Information added by Drupal.org packaging script on 2021-07-21 -version = "7.82" +; Information added by Drupal.org packaging script on 2022-09-07 +version = "7.92" project = "drupal" -datestamp = "1626883669" +datestamp = "1662554078" diff --git a/themes/stark/stark.info b/themes/stark/stark.info index 34a4a6efc5..c9e082ae2d 100644 --- a/themes/stark/stark.info +++ b/themes/stark/stark.info @@ -5,7 +5,7 @@ version = VERSION core = 7.x stylesheets[all][] = layout.css -; Information added by Drupal.org packaging script on 2021-07-21 -version = "7.82" +; Information added by Drupal.org packaging script on 2022-09-07 +version = "7.92" project = "drupal" -datestamp = "1626883669" +datestamp = "1662554078" diff --git a/update.php b/update.php index d79270305a..b05d47e228 100644 --- a/update.php +++ b/update.php @@ -134,10 +134,6 @@ function update_script_selection_form($form, &$form_state) { else { $form['start']['#title'] = format_plural($count, '1 pending update', '@count pending updates'); } - $form['has_js'] = array( - '#type' => 'hidden', - '#default_value' => FALSE, - ); $form['actions'] = array('#type' => 'actions'); $form['actions']['submit'] = array( '#type' => 'submit', From c4df5571ef7192f9687bed6242cc2677a3fe4755 Mon Sep 17 00:00:00 2001 From: Rob Davis Date: Fri, 2 Dec 2022 15:02:32 +0000 Subject: [PATCH 19/22] #6519 restored module back to original version in master branch --- .../contrib/advagg/{README.md => README.txt} | 786 ++-- .../modules/contrib/advagg/advagg.admin.inc | 1126 +---- .../modules/contrib/advagg/advagg.admin.js | 72 +- .../modules/contrib/advagg/advagg.advagg.inc | 332 +- .../all/modules/contrib/advagg/advagg.api.php | 46 +- .../modules/contrib/advagg/advagg.cache.inc | 392 +- .../advagg/advagg.developer-documentation.php | 41 - .../modules/contrib/advagg/advagg.drush.inc | 84 +- sites/all/modules/contrib/advagg/advagg.inc | 910 +--- sites/all/modules/contrib/advagg/advagg.info | 9 +- .../all/modules/contrib/advagg/advagg.install | 2169 ++------- .../contrib/advagg/advagg.make.example | 50 - .../modules/contrib/advagg/advagg.missing.inc | 906 +--- .../all/modules/contrib/advagg/advagg.module | 4090 +++-------------- .../advagg_bundler/advagg_bundler.admin.inc | 158 +- .../advagg_bundler/advagg_bundler.advagg.inc | 50 +- .../advagg/advagg_bundler/advagg_bundler.info | 7 +- .../advagg_bundler/advagg_bundler.install | 37 - .../advagg_bundler/advagg_bundler.module | 329 +- .../advagg_critical_css.admin.inc | 381 -- .../advagg_critical_css.info | 14 - .../advagg_critical_css.install | 84 - .../advagg_critical_css.module | 239 - .../advagg/advagg_css_cdn/advagg_css_cdn.info | 7 +- .../advagg_css_cdn/advagg_css_cdn.install | 9 - .../advagg_css_cdn/advagg_css_cdn.module | 80 +- .../advagg_css_compress.admin.inc | 52 +- .../advagg_css_compress.advagg.inc | 71 +- .../advagg_css_compress.info | 8 +- .../advagg_css_compress.install | 42 +- .../advagg_css_compress.module | 145 +- .../advagg/advagg_css_compress/yui/CSSMin.inc | 1144 ++--- .../advagg_ext_compress.admin.inc | 83 - .../advagg_ext_compress.info | 13 - .../advagg_ext_compress.module | 241 - .../advagg/advagg_font/advagg_font.admin.inc | 209 - .../advagg/advagg_font/advagg_font.advagg.inc | 63 - .../advagg/advagg_font/advagg_font.info | 14 - .../advagg/advagg_font/advagg_font.inline.js | 39 - .../advagg/advagg_font/advagg_font.install | 61 - .../contrib/advagg/advagg_font/advagg_font.js | 110 - .../advagg/advagg_font/advagg_font.module | 542 --- .../advagg/advagg_js_cdn/advagg_js_cdn.info | 7 +- .../advagg_js_cdn/advagg_js_cdn.install | 11 +- .../advagg/advagg_js_cdn/advagg_js_cdn.module | 28 +- .../advagg/advagg_js_cdn/js/jquery-ui.js | 41 +- .../advagg_js_compress.admin.inc | 135 +- .../advagg_js_compress.advagg.inc | 626 +-- .../advagg_js_compress.drush.inc | 111 - .../advagg_js_compress.info | 10 +- .../advagg_js_compress.install | 38 +- .../advagg_js_compress.module | 581 +-- .../advagg_js_compress.php53.inc | 138 - .../advagg/advagg_js_compress/jshrink.inc | 93 +- .../advagg/advagg_js_compress/jsminplus.inc | 76 +- .../advagg/advagg_js_compress/jspacker.inc | 4 +- .../advagg/advagg_js_compress/jsqueeze.inc | 1010 ++-- .../advagg/advagg_mod/advagg_mod.admin.inc | 572 +-- .../advagg/advagg_mod/advagg_mod.advagg.inc | 83 +- .../contrib/advagg/advagg_mod/advagg_mod.info | 8 +- .../advagg/advagg_mod/advagg_mod.install | 87 - .../advagg/advagg_mod/advagg_mod.module | 3484 +++----------- .../advagg/advagg_mod/advagg_mod_css_defer.js | 5 +- .../advagg/advagg_mod/cssrelpreload.js | 112 - .../advagg/advagg_mod/cssrelpreload.min.js | 3 - .../contrib/advagg/advagg_mod/loadCSS.js | 121 +- .../contrib/advagg/advagg_mod/loadCSS.min.js | 5 +- .../contrib/advagg/advagg_mod/onloadCSS.js | 37 - .../advagg/advagg_mod/onloadCSS.min.js | 3 - .../advagg_relocate/advagg_relocate.admin.inc | 532 --- .../advagg_relocate.advagg.inc | 1104 ----- .../advagg_relocate/advagg_relocate.info | 13 - .../advagg_relocate/advagg_relocate.install | 23 - .../advagg_relocate/advagg_relocate.module | 1350 ------ .../advagg/advagg_sri/advagg_sri.admin.inc | 55 - .../advagg/advagg_sri/advagg_sri.advagg.inc | 240 - .../contrib/advagg/advagg_sri/advagg_sri.info | 13 - .../advagg/advagg_sri/advagg_sri.install | 75 - .../advagg/advagg_sri/advagg_sri.module | 80 - .../advagg_validator.admin.inc | 101 +- .../advagg_validator.advagg.inc | 31 - .../advagg_validator/advagg_validator.inc | 14 +- .../advagg_validator/advagg_validator.info | 10 +- .../advagg_validator/advagg_validator.install | 102 +- .../advagg_validator/advagg_validator.js | 73 +- .../advagg_validator/advagg_validator.module | 154 - .../all/modules/contrib/advagg/composer.json | 19 - .../modules/contrib/advagg/tests/advagg.test | 534 +-- .../advagg/tests/css_test_files/advagg.css | 48 - .../css_test_files/advagg.css.optimized.css | 13 +- .../advagg/tests/css_test_files/charset.css | 6 - .../css_test_files/charset.css.optimized.css | 3 +- .../tests/css_test_files/charset_newline.css | 1 - .../charset_newline.css.optimized.css | 1 - .../tests/css_test_files/charset_sameline.css | 1 - .../charset_sameline.css.optimized.css | 1 - .../tests/css_test_files/comment_hacks.css | 7 - .../comment_hacks.css.optimized.css | 3 +- .../comment_hacks.css.unoptimized.css | 7 - .../css_test_files/css_input_with_import.css | 3 +- .../css_input_with_import.css.optimized.css | 3 +- .../css_input_with_import.css.unoptimized.css | 10 +- .../css_input_without_import.css | 2 +- ...css_input_without_import.css.optimized.css | 1 - ...s_input_without_import.css.unoptimized.css | 2 +- .../css_subfolder/css_input_with_import.css | 2 +- .../css_input_with_import.css.optimized.css | 3 +- .../css_input_with_import.css.unoptimized.css | 9 +- .../advagg/tests/css_test_files/import1.css | 3 +- .../advagg/tests/css_test_files/import2.css | 4 +- .../contrib/advagg/tpl/imce-page.tpl.php | 41 - 111 files changed, 4100 insertions(+), 23271 deletions(-) rename sites/all/modules/contrib/advagg/{README.md => README.txt} (53%) delete mode 100644 sites/all/modules/contrib/advagg/advagg.developer-documentation.php delete mode 100644 sites/all/modules/contrib/advagg/advagg.make.example delete mode 100644 sites/all/modules/contrib/advagg/advagg_bundler/advagg_bundler.install delete mode 100644 sites/all/modules/contrib/advagg/advagg_critical_css/advagg_critical_css.admin.inc delete mode 100644 sites/all/modules/contrib/advagg/advagg_critical_css/advagg_critical_css.info delete mode 100644 sites/all/modules/contrib/advagg/advagg_critical_css/advagg_critical_css.install delete mode 100644 sites/all/modules/contrib/advagg/advagg_critical_css/advagg_critical_css.module delete mode 100644 sites/all/modules/contrib/advagg/advagg_ext_compress/advagg_ext_compress.admin.inc delete mode 100644 sites/all/modules/contrib/advagg/advagg_ext_compress/advagg_ext_compress.info delete mode 100644 sites/all/modules/contrib/advagg/advagg_ext_compress/advagg_ext_compress.module delete mode 100644 sites/all/modules/contrib/advagg/advagg_font/advagg_font.admin.inc delete mode 100644 sites/all/modules/contrib/advagg/advagg_font/advagg_font.advagg.inc delete mode 100644 sites/all/modules/contrib/advagg/advagg_font/advagg_font.info delete mode 100644 sites/all/modules/contrib/advagg/advagg_font/advagg_font.inline.js delete mode 100644 sites/all/modules/contrib/advagg/advagg_font/advagg_font.install delete mode 100644 sites/all/modules/contrib/advagg/advagg_font/advagg_font.js delete mode 100644 sites/all/modules/contrib/advagg/advagg_font/advagg_font.module delete mode 100644 sites/all/modules/contrib/advagg/advagg_js_compress/advagg_js_compress.drush.inc delete mode 100644 sites/all/modules/contrib/advagg/advagg_js_compress/advagg_js_compress.php53.inc delete mode 100644 sites/all/modules/contrib/advagg/advagg_mod/advagg_mod.install delete mode 100644 sites/all/modules/contrib/advagg/advagg_mod/cssrelpreload.js delete mode 100644 sites/all/modules/contrib/advagg/advagg_mod/cssrelpreload.min.js delete mode 100644 sites/all/modules/contrib/advagg/advagg_mod/onloadCSS.js delete mode 100644 sites/all/modules/contrib/advagg/advagg_mod/onloadCSS.min.js delete mode 100644 sites/all/modules/contrib/advagg/advagg_relocate/advagg_relocate.admin.inc delete mode 100644 sites/all/modules/contrib/advagg/advagg_relocate/advagg_relocate.advagg.inc delete mode 100644 sites/all/modules/contrib/advagg/advagg_relocate/advagg_relocate.info delete mode 100644 sites/all/modules/contrib/advagg/advagg_relocate/advagg_relocate.install delete mode 100644 sites/all/modules/contrib/advagg/advagg_relocate/advagg_relocate.module delete mode 100644 sites/all/modules/contrib/advagg/advagg_sri/advagg_sri.admin.inc delete mode 100644 sites/all/modules/contrib/advagg/advagg_sri/advagg_sri.advagg.inc delete mode 100644 sites/all/modules/contrib/advagg/advagg_sri/advagg_sri.info delete mode 100644 sites/all/modules/contrib/advagg/advagg_sri/advagg_sri.install delete mode 100644 sites/all/modules/contrib/advagg/advagg_sri/advagg_sri.module delete mode 100644 sites/all/modules/contrib/advagg/advagg_validator/advagg_validator.advagg.inc delete mode 100644 sites/all/modules/contrib/advagg/composer.json delete mode 100644 sites/all/modules/contrib/advagg/tpl/imce-page.tpl.php diff --git a/sites/all/modules/contrib/advagg/README.md b/sites/all/modules/contrib/advagg/README.txt similarity index 53% rename from sites/all/modules/contrib/advagg/README.md rename to sites/all/modules/contrib/advagg/README.txt index 8740c676cb..40a9627592 100644 --- a/sites/all/modules/contrib/advagg/README.md +++ b/sites/all/modules/contrib/advagg/README.txt @@ -1,356 +1,20 @@ +---------------------------------- ADVANCED CSS/JS AGGREGATION MODULE -================================== +---------------------------------- CONTENTS OF THIS FILE --------------------- - - Introduction - - Requirements - - Recommended modules - - Installation - - How to get a high PageSpeed score - - JSMin PHP Extension - - Brotli PHP Extension - - Zopfli PHP Extension - - nginx Configuration - - JavaScript Bookmarklet - - Troubleshooting - Features & benefits - Configuration - - Additional options for `drupal_add_css/js` functions + - JSMin PHP Extension + - JavaScript Bookmarklet - Technical Details & Hooks - - -INTRODUCTION ------------- - -The Advanced CSS/JS Aggregation allows you to improve the frontend performance -of your site. Be sure to do a before and after comparison by using Google's -PageSpeed Insights and WebPagetest.org. -https://developers.google.com/speed/pagespeed/insights/ -http://www.webpagetest.org/easy - - -REQUIREMENTS ------------- - -No special requirements. - - -RECOMMENDED MODULES -------------------- - - - Libraries (https://www.drupal.org/project/libraries) - Allows for 3rd party code for minification to be used by AdvAgg. - - -INSTALLATION ------------- - - - Install as you would normally install a contributed Drupal module. Visit: - https://drupal.org/documentation/install/modules-themes/modules-7 - for further information. - - -HOW TO GET A HIGH PAGESPEED SCORE ---------------------------------- - -Be sure to check the site after every section to make sure the change didn't -mess up your site. The changes under AdvAgg Modifier are usually the most -problematic but they offer the biggest improvements. - -#### Advanced CSS/JS Aggregation #### -Go to `admin/config/development/performance/advagg` - -Select "Use recommended (optimized) settings" - -#### AdvAgg Compress Javascript #### -Install AdvAgg Compress Javascript if not enabled and go to -`admin/config/development/performance/advagg/js-compress` - - - Select JSMin if available; otherwise select JSMin+ - - Select Strip everything (smallest files) - - Save configuration - - Click the batch compress link to process these files at the top. - -#### AdvAgg Async Font Loader #### -Install AdvAgg Async Font Loader if not enabled and go to -`admin/config/development/performance/advagg/font` - - - Select Local file included in aggregate (version: X.X.X). If this option is - not available follow the directions right below the options on how to install - it. - -Keep the 2 checkboxes checked. - -#### AdvAgg Bundler #### -Install AdvAgg Bundler if not enabled and go to -`admin/config/development/performance/advagg/bundler` - -If your server supports HTTP 2 then select "Use HTTP 2.0 settings"; otherwise -leave it at the "Use HTTP 1.1 settings". - -#### AdvAgg Relocate #### -Install AdvAgg Relocate if not enabled and go to -`admin/config/development/performance/advagg/relocate` - -Select "Use recommended (optimized) settings" - -#### AdvAgg Modifier #### -Install AdvAgg Modifier if not enabled and go to -`admin/config/development/performance/advagg/mod` - -Select "Use recommended (optimized) settings" - -#### AdvAgg Critical CSS module #### -Install AdvAgg Critical CSS if not enabled and go to -`admin/config/development/performance/advagg/critical-css` - -These are the directions for the front page of your site. - -Under Add Critical CSS -- Select the theme that is your front page; usually the default is correct. -- User type should be set to 'anonymous' under most circumstances. -- Type of lookup, select URL -- Value to lookup, type in `` -- Critical CSS, paste in the generated CSS from running your homepage url -through https://www.sitelocity.com/critical-path-css-generator which is inside -of the 'Critical Path CSS' textarea on the sitelocity page. -- Click Save Configuration. - -Other landing pages should have their critical CSS added as well. If you have -Google Analytics this will show you how to find your top landing pages -https://developers.google.com/analytics/devguides/reporting/core/v3/common-queries#top-landing-pages -or for Piwik https://piwik.org/faq/how-to/faq_160/. You can also use this -chrome browser plugin to generate critical CSS -https://chrome.google.com/webstore/detail/critical-style-snapshot/gkoeffcejdhhojognlonafnijfkcepob?hl=en - - -JSMIN PHP EXTENSION -------------------- - -The AdvAgg JS Compress module can take advantage of jsmin.c. JavaScript parsing -and minimizing will be done in C instead of PHP dramatically speeding up the -process. If using PHP 5.3.10 or higher https://github.com/sqmk/pecl-jsmin is -recommended. If using PHP 5.3.9 or lower -http://www.ypass.net/software/php_jsmin/ is recommended. - - -BROTLI PHP EXTENSION --------------------- - -The AdvAgg module can take advantage of Brotli compression. Install this -extension to take advantage of it. Should reduce CSS/JS files by 20%. -https://github.com/kjdev/php-ext-brotli - - -ZOPFLI PHP EXTENSION --------------------- - -The AdvAgg module can take advantage of the Zopfli compression algorithm. -Install this extension to take advantage of it. This gives higher gzip -compression ratios compared to stock PHP. -https://github.com/kjdev/php-ext-zopfli - - -NGINX CONFIGURATION -------------------- - -https://drupal.org/node/1116618 -Note that @drupal (last line of code below) might be @rewrite or @rewrites -depending on your servers configuration. If there are image style rules in your -Nginx configuration add this right below that. If you want to have brotli -support https://github.com/google/ngx_brotli is how to install that; add -`brotli_static on;` right above `gzip_static on;` in the configuration below. - - ### - ### 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 $uri/ @drupal; - } - -Also noted that some ready made nginx configurations add in a Last-Modified -header inside the advagg directories. These should be removed. - - -JAVASCRIPT BOOKMARKLET ----------------------- - -You can use this JS code as a bookmarklet for toggling the AdvAgg URL parameter. -See http://en.wikipedia.org/wiki/Bookmarklet for more details. - - javascript:(function(){var loc = document.location.href,qs = document.location.search,regex_off = /\&?advagg=-1/,goto = loc;if(qs.match(regex_off)) {goto = loc.replace(regex_off, '');} else {qs = qs ? qs + '&advagg=-1' : '?advagg=-1';goto = document.location.pathname + qs;}window.location = goto;})(); - - -TROUBLESHOOTING ---------------- - -If the core Fast 404 Pages functionality is enabled via settings.php, the -settings must be changed in order for the on-demand file compilation to work. -Change this: - - $conf['404_fast_paths_exclude'] = '/\/(?:styles)\//'; - -to this: - - $conf['404_fast_paths_exclude'] = '/\/(?:styles|advagg_(cs|j)s)\//'; - -Similarly, if the Fast_404 module is enabled, the 'fast_404_string_whitelisting' -variable must be set inside of settings.php. Add this to your settings.php file: - - $conf['fast_404_string_whitelisting'][] = '/advagg_'; - - -Modules like the Central Authentication Services https://drupal.org/project/cas -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. - - services/* - sites/default/files/advagg_css/* - sites/default/files/advagg_js/* - httprl_async_function_callback - -In the example of CAS this setting can be found on the `admin/config/people/cas` -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). - - - # No mod_headers. Apache module headers is not enabled. - - # No mod_expires. Apache module expires is not enabled. - - # Use ETags. - FileETag MTime Size - - - - # Use Expires Directive if apache module expires is enabled. - - # Do not use ETags. - FileETag None - # Enable expirations. - ExpiresActive On - # Cache all aggregated css/js files for 52 weeks after access (A). - ExpiresDefault A31449600 - - - # Use Headers Directive if apache module headers is enabled. - - # Do not use etags for cache validation. - Header unset ETag - - # Set a far future Cache-Control header to 52 weeks. - Header set Cache-Control "max-age=31449600, no-transform, public" - - - Header append Cache-Control "no-transform, public" - - - - # Force advagg .js file to have the type of application/javascript. - - ForceType application/javascript - - - -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 - -something as simple as an errant unfinished comment in one file may cause entire -aggregates of files to be ignored. - - -If AdvAgg was installed via drush sometimes directory permissions need to be -fixed. Using `chown -R` on the advagg directories usually solves this issue. - - -If hosting on Pantheon, you might need to add this to your settings.php file if -you get Numerous login prompts after enabling Adv Agg module on Pantheon Test -and Live instances. - - if (isset($_SERVER['PANTHEON_ENVIRONMENT'])) { - // NO trailing slash when setting the $base_url variable. - switch ($_SERVER['PANTHEON_ENVIRONMENT']) { - case 'dev': - $base_url = 'http://dev-sitename.gotpantheon.com'; - break; - - case 'test': - $base_url = 'http://test-sitename.gotpantheon.com'; - break; - - case 'live': - $base_url = 'http://www.domain.tld'; - break; - } - // Remove a trailing slash if one was added. - if (!empty($base_url)) { - $base_url = rtrim($base_url, '/'); - } - } - - -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 -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] - - -If brotli compression is not working 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 right above this line `# Rules to -correctly serve gzip compressed CSS and JS files.` - - - # Serve brotli compressed CSS if they exist and the client accepts br. - RewriteCond %{HTTP:Accept-encoding} br - RewriteCond %{REQUEST_FILENAME}\.br -s - RewriteRule ^(.*)\.css $1\.css\.br [QSA] - RewriteRule \.css\.br$ - [T=text/css,E=no-gzip:1] - - # Serve brotli compressed JS if they exist and the client accepts br. - RewriteCond %{HTTP:Accept-encoding} br - RewriteCond %{REQUEST_FILENAME}\.br -s - RewriteRule ^(.*)\.js $1\.js\.br [QSA] - RewriteRule \.js\.br$ - [T=application/javascript,E=no-gzip:1] - - - # Serve correct encoding type. - Header set Content-Encoding br - # Force proxies to cache compressed and non-compressed css/js files - # separately. - Header append Vary Accept-Encoding - - - - -If you are having 5 minute or longer timeouts on the admin/reports/status page -then you might need to use an alternative to drupal_httP_request(). The cURL -HTTP Request module https://www.drupal.org/project/chr might fix this issue. + - How to get a high PageSpeed score + - nginx Configuration + - Troubleshooting FEATURES & BENEFITS @@ -370,10 +34,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. @@ -382,29 +46,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. - - Defer loading of CSS. - 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. @@ -419,7 +82,11 @@ 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. + Enabled by default. - Use Cores Grouping Logic: Leave this checkbox enabled until you are ready to begin exploring the AdvAgg Bundler sub-module which overrides Core's functionality. This groups files just like Core does so should just work. @@ -438,49 +105,6 @@ Settings page is located at: cache hit ratio will be low; if that is the case consider using Drupal.settings for a better cache hit ratio. -**Resource Hints** - -Preemptively get resources (CSS/JS & sub requests). This will set tags in the -document head telling the browser to open up connections before they are needed. - - - DNS Prefetch: Start the DNS lookup for external CSS and JavaScript files as - soon as possible. - - Preconnect: Start the connection to external resources before an HTTP request - is actually sent to the server. On HTTPS this can have a dramatic effect. - - Location of resource hints: This only needs to be changed if the above - settings are not working. - - Preload link http headers: If your server supports HTTP/2 push then this - allows for resources to be sent before the browser knows it needs it. - -**Cron Options** - -Adjusting the frequency of how often something happens on cron. - -**Obscure Options** - - - 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. - Enabled by default. - - Create .br files: Check this by default as it will improve your performance. - For every Aggregated file generated, this will create a brotli version of - file and then only serve it out if the browser accepts gzip files - compression. Enabled by default IF the Brotli Extension for PHP is installed. - See https://github.com/kjdev/php-ext-brotli - - Run advagg_ajax_render_alter(): Turn this off if you're having issues with - ajax. Also keep in mind that the max_input_vars setting can cause issues if - you are submitting a lot of data. - - Include the base_url variable in the hooks hash array: Enabled only if you - know you need it. - - Convert absolute paths to be self references: Turn on unless pages are used - inside of an iframe. - - Convert absolute paths to be protocol relative paths: Safe to use unless you - need to support IE6. - - Convert http:// to https://: Usually not needed, but here in case you do. - - Do not run CSS url() values through file_create_url(): Usually not needed, - but here in case you do. - - **CSS Options & JS Options** - Combine CSS files by using media queries: "Use cores grouping logic" needs to @@ -488,9 +112,9 @@ Adjusting the frequency of how often something happens on cron. 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 @@ -508,10 +132,7 @@ Adjusting the frequency of how often something happens on cron. 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 Render Info: Displays the scripts and styles render callbakcs. 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 @@ -530,7 +151,6 @@ 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 @@ -539,38 +159,23 @@ 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 - - Clear 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. - - Delete Orphaned Aggregates: Scan CSS/JS advagg dir and remove file if - there is no associated db record. + - 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. - Delete Unused Aggregates from Database: Delete aggregates that have not been accessed in the last 6 weeks. - Delete orphaned/expired advagg locks from the semaphore database table. - - Delete leftover temporary files: Delete old temporary files from the - filesystem. - Drastic Measures - Clear All Caches: Remove all data stored in the advagg cache bins. - - Clear All Files: Remove All Generated Files. Remove all files in the - `advagg_(css|js)` directories. - - Force New Aggregates: Increment Global Counter. Force the creation of all - new aggregates by incrementing a global counter. - - Rescan All Files: Force rescan all files and clear the cache. Useful if a - css/js change from a deployment did not work. - - Remove deleted files in the advagg_files table: Remove entries in the - advagg_files table that have a filesize of 0 and delete the - javascript_parsed variable. This gets around the grace period that the - cron cleanup does. - - Reset the AdvAgg Files table: Truncate the advagg_files table and delete - the javascript_parsed variable. This may cause some 404s for CSS/JS assets - for a short amount of time (seconds). Useful if you really want to reset - some stuff. Might be best to put the site into maintenance mode before - doing this. + - 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. **Hidden Settings** @@ -581,9 +186,6 @@ 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; @@ -641,55 +243,24 @@ current defaults are shown. // Value for the compression ratio test. $conf['advagg_js_compress_ratio'] = 0.1; - // Skip far future check on status page. - $conf['advagg_skip_far_future_check'] = FALSE; - - // 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; - - // If FALSE mtime of files will only trigger a change if they are in the - // future. - $conf['advagg_strict_mtime_check'] = TRUE; - - // Skip 304 check on status page. - $conf['advagg_skip_304_check'] = FALSE; - - // Control how many bytes can be inlined. - $conf['advagg_mod_css_defer_inline_size_limit'] = 12288; - - // Control how many bytes the preload header can use. - $conf['advagg_resource_hints_preload_max_size'] = 3072; - // If TRUE, only verify 1st hash instead of all 3 of the filename. - $conf['advagg_weak_file_verification'] = FALSE; - - -ADDITIONAL OPTIONS FOR DRUPAL_ADD_CSS/JS FUNCTIONS --------------------------------------------------- +JSMIN PHP EXTENSION +------------------- -AdvAgg extends the available options inside of `drupal_add_css` and -`drupal_add_js`. +The AdvAgg JS Compress module can take advantage of jsmin.c. JavaScript parsing +and minimizing will be done in C instead of PHP dramatically speeding up the +process. If using PHP 5.3.10 or higher https://github.com/sqmk/pecl-jsmin is +recommended. If using PHP 5.3.9 or lower +http://www.ypass.net/software/php_jsmin/ is recommended. -`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. +JAVASCRIPT BOOKMARKLET +---------------------- -Both `drupal_add_js` + `drupal_add_css` - additional keys for $options. +You can use this JS code as a bookmarklet for toggling the AdvAgg URL parameter. +See http://en.wikipedia.org/wiki/Bookmarklet for more details. - - `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. - - `preprocess_lock`: TRUE - Make sure the preprocess key will not ever change. + javascript:(function(){var loc = document.location.href,qs = document.location.search,regex_off = /\&?advagg=-1/,goto = loc;if(qs.match(regex_off)) {goto = loc.replace(regex_off, '');} else {qs = qs ? qs + '&advagg=-1' : '?advagg=-1';goto = document.location.pathname + qs;}window.location = goto;})(); TECHNICAL DETAILS & HOOKS @@ -698,10 +269,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. @@ -717,28 +288,23 @@ 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: - // CSS. - $hooks_hash = advagg_get_current_hooks_hash(); - $css_cache_id_full = - 'advagg:css:full:' . $hooks_hash . ':' . - drupal_hash_base64(serialize($full_css)); - // JS. - $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. @@ -750,57 +316,235 @@ 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 - before it is processed. - - `advagg_js_groups_alter`. Allow other modules to modify `$js_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. 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. + + +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()" + - 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** + +On the `admin/config/development/performance/advagg/mod` page there is the +setting "Remove unused JavaScript tags if possible". This is a backport of D8 +where it will not add any JS to the page if it is not being used. +https://drupal.org/node/1279226 + +The AdvAgg Bundler module on the +`admin/config/development/performance/advagg/bundler` page. The bundler provides +intelligent bundling of CSS and JS files by grouping files that belong together. +This does what core tried to do; group CSS & JS files together that get used +together. Using this will make your pagespeed score go down as there will be +more css/js files to download but if different css/js files are used on +different pages of your site this will be a net win as a new full aggregate will +not have to be downloaded, instead a smaller aggregate can be downloaded, +ideally with only the css/js that is different on that page. You can select how +many bundles to create and the bundler will do it's best to meet that goal; if +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. + + +NGINX CONFIGURATION +------------------- + +http://drupal.org/node/1116618 +Note that @drupal (last line of code below) might be @rewrite or @rewrites +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; + add_header ETag ""; + add_header Cache-Control "max-age=31449600, no-transform, public"; + try_files $uri @drupal; + } + + +TROUBLESHOOTING +--------------- + +If the core Fast 404 Pages functionality is enabled via settings.php, the +settings must be changed in order for the on-demand file compilation to work. +Change this: + + $conf['404_fast_paths_exclude'] = '/\/(?:styles)\//'; + +to this: + + $conf['404_fast_paths_exclude'] = '/\/(?:styles|advagg_(cs|j)s)\//'; + +Similarly, if the Fast_404 module is enabled, the 'fast_404_string_whitelisting' +variable must be set inside of settings.php. Add this to your settings.php file: + + $conf['fast_404_string_whitelisting'][] = '/advagg_'; + + +Modules like the Central Authentication Services https://drupal.org/project/cas +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. + + services/* + sites/default/files/advagg_css/* + sites/default/files/advagg_js/* + httprl_async_function_callback + +In the example of CAS this setting can be found on the `admin/config/people/cas` +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). + + + # No mod_headers + + # No mod_expires + + # Use ETags. + FileETag MTime Size + + + + # Use Expires Directive. + + # Do not use ETags. + FileETag None + # Enable expirations. + ExpiresActive On + # Cache all aggregated css/js files for 52 weeks after access (A). + ExpiresDefault A31449600 + + + + # Do not use etags for cache validation. + Header unset ETag + + # Set a far future Cache-Control header to 52 weeks. + Header set Cache-Control "max-age=31449600, no-transform, public" + + + Header append Cache-Control "no-transform, public" + + + + # Force advagg .js file to have the type of application/javascript. + + ForceType application/javascript + + + +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 - +something as simple as an errant unfinished comment in one file may cause entire +aggregates of files to be ignored. + + +If AdvAgg was installed via drush sometimes directory permissions need to be +fixed. Using `chown -R` on the advagg directories usually solves this issue. + + +If hosting on Pantheon, you might need to add this to your settings.php file if +you get Numerous login prompts after enabling Adv Agg module on Pantheon Test +and Live instances. + + if (isset($_SERVER['PANTHEON_ENVIRONMENT'])) { + // NO trailing slash when setting the $base_url variable. + switch ($_SERVER['PANTHEON_ENVIRONMENT']) { + case 'dev': + $base_url = 'http://dev-sitename.gotpantheon.com'; + break; + + case 'test': + $base_url = 'http://test-sitename.gotpantheon.com'; + break; + + case 'live': + $base_url = 'http://www.domain.tld'; + break; + } + // Remove a trailing slash if one was added. + if (!empty($base_url)) { + $base_url = rtrim($base_url, '/'); + } + } + + +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 +production and not production environments. diff --git a/sites/all/modules/contrib/advagg/advagg.admin.inc b/sites/all/modules/contrib/advagg/advagg.admin.inc index 80601001e0..5eff78b5e6 100644 --- a/sites/all/modules/contrib/advagg/advagg.admin.inc +++ b/sites/all/modules/contrib/advagg/advagg.admin.inc @@ -5,69 +5,33 @@ * Admin page callbacks for the advanced CSS/JS aggregation module. */ -/** - * @defgroup advagg_forms Advanced Aggregates Forms - * @{ - * Advanced Aggregates administration forms. - */ - /** * Form builder; Configure advagg settings. * + * @ingroup forms + * * @see system_settings_form() */ function advagg_admin_settings_form($form, $form_state) { drupal_set_title(t('AdvAgg: Configuration')); - advagg_display_message_if_requirements_not_met(); $config_path = advagg_admin_config_root_path(); - $options = array( - 0 => t('Use default (safe) settings'), - 2 => t('Use recommended (optimized) settings'), - 4 => t('Use customized settings'), - ); - $form['advagg_admin_mode'] = array( - '#type' => 'radios', - '#title' => t('AdvAgg Settings'), - '#default_value' => variable_get('advagg_admin_mode', ADVAGG_ADMIN_MODE), - '#options' => $options, - '#description' => t("Default settings will mirror core as closely as possible.
Recommended settings are optimized for speed."), - ); - - $form['global_container'] = array( - '#type' => 'container', - '#hidden' => TRUE, - '#states' => array( - 'visible' => array( - ':input[name="advagg_admin_mode"]' => array('value' => '4'), - ), - ), - ); - // Simple checkbox settings. - $form['global_container']['global'] = array( + $form['global'] = array( '#type' => 'fieldset', '#title' => t('Global Options'), ); - $form['global_container']['global']['advagg_enabled'] = array( + $form['global']['advagg_enabled'] = array( '#type' => 'checkbox', - '#title' => t('Enable advanced aggregation (recommended)'), + '#title' => t('Enable advanced aggregation'), '#default_value' => variable_get('advagg_enabled', ADVAGG_ENABLED), '#description' => t('Uncheck this box to completely disable AdvAgg functionality.'), - '#recommended_value' => TRUE, ); - $advagg_core_groups_default = variable_get('advagg_core_groups', ADVAGG_CORE_GROUPS); - if (variable_get('advagg_combine_css_media', ADVAGG_COMBINE_CSS_MEDIA) - || variable_get('advagg_ie_css_selector_limiter', ADVAGG_IE_CSS_SELECTOR_LIMITER) - || (module_exists('advagg_bundler') && variable_get('advagg_bundler_active', ADVAGG_BUNDLER_ACTIVE))) { - $advagg_core_groups_default = FALSE; - } - $form['global_container']['global']['advagg_core_groups'] = array( + $form['global']['advagg_core_groups'] = array( '#type' => 'checkbox', '#title' => t('Use cores grouping logic'), - '#default_value' => $advagg_core_groups_default, + '#default_value' => variable_get('advagg_combine_css_media', ADVAGG_COMBINE_CSS_MEDIA) || variable_get('advagg_ie_css_selector_limiter', ADVAGG_IE_CSS_SELECTOR_LIMITER) ? FALSE : variable_get('advagg_core_groups', ADVAGG_CORE_GROUPS), '#description' => t('Will group files just like core does.'), - '#recommended_value' => FALSE, '#states' => array( 'enabled' => array( '#edit-advagg-combine-css-media' => array('checked' => FALSE), @@ -75,13 +39,12 @@ function advagg_admin_settings_form($form, $form_state) { ), ), ); - $form['global_container']['global']['advagg_use_httprl'] = array( + $form['global']['advagg_use_httprl'] = array( '#type' => 'checkbox', - '#title' => t('Use HTTPRL to generate aggregates (recommended)'), + '#title' => t('Use HTTPRL to generate aggregates.'), '#default_value' => module_exists('httprl') ? variable_get('advagg_use_httprl', ADVAGG_USE_HTTPRL) : FALSE, '#disabled' => module_exists('httprl') ? FALSE : TRUE, '#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')), - '#recommended_value' => TRUE, ); $aggressive_cache_conflicts = advagg_aggressive_cache_conflicts(); @@ -93,50 +56,22 @@ function advagg_admin_settings_form($form, $form_state) { } $options = array( -1 => t('Development ~ 300ms'), - 1 => t('Normal ~ 60ms'), - 3 => t('Render Cache ~ 30ms (recommended)'), - 5 => t('Aggressive Render Cache ~ 10ms'), + 3 => t('Normal ~ 30ms'), + 5 => t('Aggressive ~ 10ms'), ); - $form['global_container']['global']['advagg_cache_level'] = array( + $form['global']['advagg_cache_level'] = array( '#type' => 'radios', '#title' => t('AdvAgg Cache Settings'), '#default_value' => variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL), '#options' => $options, - '#recommended_value' => 3, - '#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( + '#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 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.", array( '@information' => url($config_path . '/advagg/info', array( 'fragment' => 'edit-hooks-implemented', )), - '!description' => $description, - )), + )) . ' ' . $description, ); - $stream_wrappers = file_get_stream_wrappers(); - $prefix = variable_get('advagg_root_dir_prefix', ADVAGG_ROOT_DIR_PREFIX); - if ($prefix !== 'public://' - || $stream_wrappers['public']['class'] !== 'DrupalPublicStreamWrapper' - || count($stream_wrappers) > 2 - || file_default_scheme() !== 'public' - ) { - $stream_wrappers_string = array(); - foreach ($stream_wrappers as $key => $values) { - $stream_wrappers_string[] = t("@key:// uses @class; @description", array( - '@key' => $key, - '@class' => $values['class'], - '@description' => $values['description'], - )); - } - $stream_wrappers_string = implode("
\n", $stream_wrappers_string); - $form['global_container']['global']['advagg_root_dir_prefix'] = array( - '#type' => 'textfield', - '#title' => t('Stream wrapper for AdvAgg.'), - '#default_value' => $prefix, - '#description' => t("Options:
\n!stream_wrappers", array( - '!stream_wrappers' => $stream_wrappers_string, - )), - ); - } - $form['global_container']['global']['dev_container'] = array( + $form['global']['dev_container'] = array( '#type' => 'container', '#states' => array( 'visible' => array( @@ -145,14 +80,14 @@ function advagg_admin_settings_form($form, $form_state) { ), ); // Show msg about advagg css compress. - if (module_exists('advagg_css_compress') && (variable_get('advagg_css_compressor', ADVAGG_CSS_COMPRESSOR) > 0 || variable_get('advagg_css_compress_inline', ADVAGG_CSS_COMPRESS_INLINE))) { - $form['global_container']['global']['dev_container']['advagg_css_compress_msg'] = array( + if (module_exists('advagg_css_compress') && (variable_get('advagg_css_compressor', ADVAGG_CSS_COMPRESSOR) || variable_get('advagg_css_compress_inline', ADVAGG_CSS_COMPRESS_INLINE))) { + $form['global']['dev_container']['advagg_css_compress_msg'] = array( '#markup' => '

' . t('The AdvAgg CSS Compression module is disabled when in development mode.', array('@css' => url($config_path . '/advagg/css-compress'))) . '

', ); } // Show msg about advagg js compress. if (module_exists('advagg_js_compress') && (variable_get('advagg_js_compressor', ADVAGG_JS_COMPRESSOR) || variable_get('advagg_js_compress_inline', ADVAGG_JS_COMPRESS_INLINE))) { - $form['global_container']['global']['dev_container']['advagg_js_compress_msg'] = array( + $form['global']['dev_container']['advagg_js_compress_msg'] = array( '#markup' => '

' . t('The AdvAgg JS Compression module is disabled when in development mode.', array('@js' => url($config_path . '/advagg/js-compress'))) . '

', ); } @@ -160,14 +95,14 @@ function advagg_admin_settings_form($form, $form_state) { // Show msg about the jquery update compression setting. if (module_exists('jquery_update')) { if (variable_get('jquery_update_compression_type', 'min') === 'min') { - $form['global_container']['global']['dev_container']['advagg_jquery_update_development'] = array( + $form['global']['dev_container']['advagg_jquery_update_development'] = array( '#markup' => '

' . t('You might want to change the jQuery update compression level to "Development" as well.', array( '!url' => url('admin/config/development/jquery_update'), )) . '

', ); } else { - $form['global_container']['global']['prod_container'] = array( + $form['global']['prod_container'] = array( '#type' => 'container', '#states' => array( 'visible' => array( @@ -175,7 +110,7 @@ function advagg_admin_settings_form($form, $form_state) { ), ), ); - $form['global_container']['global']['prod_container']['advagg_jquery_update_development'] = array( + $form['global']['prod_container']['advagg_jquery_update_development'] = array( '#markup' => '

' . t('You might want to change the jQuery update compression level to "Production" as well.', array( '!url' => url('admin/config/development/jquery_update'), )) . '

', @@ -183,205 +118,14 @@ function advagg_admin_settings_form($form, $form_state) { } } - $advagg_resource_hints_dns_prefetch = variable_get('advagg_resource_hints_dns_prefetch', ADVAGG_RESOURCE_HINTS_DNS_PREFETCH); - $advagg_resource_hints_preconnect = variable_get('advagg_resource_hints_preconnect', ADVAGG_RESOURCE_HINTS_PRECONNECT); - $advagg_resource_hints_preload = variable_get('advagg_resource_hints_preload', ADVAGG_RESOURCE_HINTS_PRELOAD); - $form['global_container']['global']['resource_hints'] = array( - '#type' => 'fieldset', - '#title' => t('Resource Hints'), - '#collapsible' => TRUE, - '#collapsed' => ($advagg_resource_hints_dns_prefetch || $advagg_resource_hints_preconnect || $advagg_resource_hints_preload) ? FALSE : TRUE, - '#description' => t('Preemptively get resources (CSS/JS & sub requests).'), - ); - $form['global_container']['global']['resource_hints']['advagg_resource_hints_dns_prefetch'] = array( - '#type' => 'checkbox', - '#title' => t('DNS Prefetch (recommended).'), - '#default_value' => $advagg_resource_hints_dns_prefetch, - '#disabled' => '', - '#description' => t('Start the DNS lookup for external CSS and JavaScript files as soon as possible.'), - '#recommended_value' => TRUE, - ); - $form['global_container']['global']['resource_hints']['advagg_resource_hints_preconnect'] = array( - '#type' => 'checkbox', - '#title' => t('Preconnect (recommended).'), - '#default_value' => $advagg_resource_hints_preconnect, - '#disabled' => '', - '#description' => t('Start the connection to external resources before an HTTP request is actually sent to the server.'), - '#recommended_value' => TRUE, - ); - $form['global_container']['global']['resource_hints']['advagg_resource_hints_location'] = array( - '#type' => 'radios', - '#title' => t('Location of resource hints.'), - '#default_value' => variable_get('advagg_resource_hints_location', ADVAGG_RESOURCE_HINTS_LOCATION), - '#description' => t('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 some of the resource hints to work; Optimizely is an example that would need this other option. Link headers are not supported in every browser.'), - '#recommended_value' => 1, - '#options' => array( - 1 => t('Below charset=utf-8 (recommended)'), - 3 => t('Above charset=utf-8'), - ), - '#states' => array( - 'disabled' => array( - '#edit-advagg-resource-hints-dns-prefetch' => array('checked' => FALSE), - '#edit-advagg-resource-hints-preconnect' => array('checked' => FALSE), - ), - ), - ); - - // Preload Section. - $form['global_container']['global']['resource_hints']['preload']['advagg_resource_hints_preload'] = array( - '#type' => 'checkbox', - '#title' => t('Preload link http headers.'), - '#default_value' => $advagg_resource_hints_preload, - '#disabled' => '', - '#description' => t('Use link http headers to send out preload hints for local and external resources.'), - '#recommended_value' => FALSE, - ); - $advagg_resource_hints_preload_settings = advagg_get_resource_hints_preload_settings(); - $form['global_container']['global']['resource_hints']['preload']['advagg_resource_hints_preload_settings'] = array( - '#type' => 'fieldset', - '#title' => t('Preload Ordering and Settings'), - '#collapsible' => TRUE, - '#collapsed' => TRUE, - '#description' => t('Change the order of what preload items get sent first. Can also control what will be sent and if HTTP/2 push should be used (if your server supports it). If your webserver supports HTTP/2 Push, and it is enabled, the server will send every asset flagged to be pushed to the browser on every request, ignoring the browser cache (not a good thing for repeat visits). More complex server configuration will be needed to make this efficient; no solutions are currently available.'), - '#states' => array( - 'invisible' => array( - '#edit-advagg-resource-hints-preload' => array('checked' => FALSE), - ), - ), - ); - // Build the rows. - foreach ($advagg_resource_hints_preload_settings as $id => $entry) { - // Build the table rows. - $rows[$id] = array( - 'data' => array( - // Cell for the cross drag and drop element. - array('class' => array('entry-cross')), - // Weight item for the tabledrag. - array( - 'data' => array( - '#type' => 'weight', - '#title' => t('Weight'), - '#title_display' => 'invisible', - '#default_value' => $entry['#weight'], - '#parents' => array( - 'advagg_resource_hints_preload_settings', - $id, - 'weight', - ), - '#attributes' => array( - 'class' => array('entry-order-weight'), - ), - ), - ), - check_plain($entry['title']), - array( - 'data' => array( - '#type' => 'checkbox', - '#title' => t('Enable'), - '#title_display' => 'invisible', - '#default_value' => $entry['enabled'], - '#parents' => array( - 'advagg_resource_hints_preload_settings', - $id, - 'enabled', - ), - ), - ), - array( - 'data' => array( - '#type' => 'checkbox', - '#title' => t('HTTP/2 Push'), - '#title_display' => 'invisible', - '#default_value' => $entry['push'], - '#parents' => array( - 'advagg_resource_hints_preload_settings', - $id, - 'push', - ), - ), - ), - array( - 'data' => array( - '#type' => 'checkbox', - '#title' => t('Local'), - '#title_display' => 'invisible', - '#default_value' => $entry['local'], - '#parents' => array( - 'advagg_resource_hints_preload_settings', - $id, - 'local', - ), - ), - ), - array( - 'data' => array( - '#type' => 'checkbox', - '#title' => t('External'), - '#title_display' => 'invisible', - '#default_value' => $entry['external'], - '#parents' => array( - 'advagg_resource_hints_preload_settings', - $id, - 'external', - ), - ), - ), - ), - 'class' => array('draggable'), - ); - // Build rows of the form elements in the table. - $row_elements[$id] = array( - 'weight' => &$rows[$id]['data'][1]['data'], - 'enabled' => &$rows[$id]['data'][3]['data'], - 'push' => &$rows[$id]['data'][4]['data'], - 'local' => &$rows[$id]['data'][5]['data'], - 'external' => &$rows[$id]['data'][6]['data'], - ); - } - - // Add the table to the form. - $form['global_container']['global']['resource_hints']['preload']['advagg_resource_hints_preload_settings']['advagg_table'] = array( - '#theme' => 'table', - // The row form elements need to be processed and build, - // therefore pass them as element children. - 'elements' => $row_elements, - '#header' => array( - // We need two empty columns for the weight field and the cross. - array('data' => NULL, 'colspan' => 2), - t('Name'), - t('Enabled'), - t('HTTP/2 Push'), - t('Local'), - t('External'), - ), - '#rows' => $rows, - '#attributes' => array('id' => 'entry-order'), - ); - drupal_add_tabledrag('entry-order', 'order', 'sibling', 'entry-order-weight'); - $form['global_container']['global']['resource_hints']['preload']['advagg_resource_hints_preload_settings']['advagg_resource_hints_preload_reset'] = array( - '#type' => 'submit', - '#value' => t('Reset'), - '#submit' => array('advagg_admin_resource_hints_preload_reset'), - ); - $form['global_container']['global']['resource_hints']['advagg_resource_hints_use_immutable'] = array( - '#type' => 'checkbox', - '#title' => t('Send immutable header for all aggregated files (recommended).'), - '#default_value' => variable_get('advagg_resource_hints_use_immutable', ADVAGG_RESOURCE_HINTS_USE_IMMUTABLE), - '#description' => t('Have Apache send Cache-Control: immutable for all aggregated files. Current browser support', array( - '@url1' => 'http://bitsup.blogspot.de/2016/05/cache-control-immutable.html', - '@url2' => 'https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control#Browser_compatibility', - )), - '#recommended_value' => TRUE, - ); - - $form['global_container']['global']['cron'] = array( + $form['global']['cron'] = array( '#type' => 'fieldset', '#title' => t('Cron Options'), '#collapsible' => TRUE, '#collapsed' => TRUE, '#description' => t('Unless you have a good reason to adjust these values you should leave them alone.'), ); - // @codingStandardsIgnoreStart + // @ignore sniffer_squiz_commenting_poststatementcomment_found:27 $short_times = drupal_map_assoc(array( 60 * 15, // 15 min. 60 * 30, // 30 min. @@ -394,7 +138,6 @@ function advagg_admin_settings_form($form, $form_state) { 60 * 60 * 10, // 10 hours. 60 * 60 * 12, // 12 hours. 60 * 60 * 18, // 18 hours. - 60 * 60 * 23, // 23 hours. 60 * 60 * 24, // 1 day. 60 * 60 * 24 * 2, // 2 days. ), 'format_interval'); @@ -411,7 +154,6 @@ function advagg_admin_settings_form($form, $form_state) { 60 * 60 * 24 * 45, // 1 month 2 weeks. 60 * 60 * 24 * 60, // 2 months. ), 'format_interval'); - // @codingStandardsIgnoreEnd $last_ran = variable_get('advagg_cron_timestamp', 0); if (!empty($last_ran)) { $last_ran = t('@time ago', array('@time' => format_interval(REQUEST_TIME - $last_ran))); @@ -419,8 +161,7 @@ function advagg_admin_settings_form($form, $form_state) { else { $last_ran = t('never'); } - - $form['global_container']['global']['cron']['advagg_cron_frequency'] = array( + $form['global']['cron']['advagg_cron_frequency'] = array( '#type' => 'select', '#options' => $short_times, '#title' => 'Minimum amount of time between advagg_cron() runs.', @@ -430,22 +171,21 @@ function advagg_admin_settings_form($form, $form_state) { '%time' => $last_ran, )), ); - $form['global_container']['global']['cron']['drupal_stale_file_threshold'] = array( + $form['global']['cron']['drupal_stale_file_threshold'] = array( '#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))), ); - $form['global_container']['global']['cron']['advagg_remove_missing_files_from_db_time'] = array( + $form['global']['cron']['advagg_remove_missing_files_from_db_time'] = array( '#type' => 'select', '#options' => $long_times, '#title' => 'How long to wait until unaccessed aggregates with missing files are removed from the database.', '#default_value' => variable_get('advagg_remove_missing_files_from_db_time', ADVAGG_REMOVE_MISSING_FILES_FROM_DB_TIME), '#description' => t('The default value for this is %value.', array('%value' => format_interval(ADVAGG_REMOVE_MISSING_FILES_FROM_DB_TIME))), ); - $form['global_container']['global']['cron']['advagg_remove_old_unused_aggregates_time'] = array( + $form['global']['cron']['advagg_remove_old_unused_aggregates_time'] = array( '#type' => 'select', '#options' => $long_times, '#title' => 'How long to wait until unaccessed aggregates are removed from the database.', @@ -453,45 +193,27 @@ function advagg_admin_settings_form($form, $form_state) { '#description' => t('The default value for this is %value.', array('%value' => format_interval(ADVAGG_REMOVE_OLD_UNUSED_AGGREGATES_TIME))), ); - $form['global_container']['global']['obscure'] = array( + $form['global']['obscure'] = array( '#type' => 'fieldset', '#title' => t('Obscure Options'), '#collapsible' => TRUE, '#collapsed' => TRUE, '#description' => t('Some of the more obscure AdvAgg settings. Odds are you do not need to change anything in here.'), ); - $form['global_container']['global']['obscure']['advagg_gzip'] = array( + $form['global']['obscure']['advagg_gzip'] = array( '#type' => 'checkbox', '#title' => t('Create .gz files'), '#default_value' => variable_get('advagg_gzip', ADVAGG_GZIP), '#description' => t('All aggregated files can be pre-compressed into a .gz file and served from Apache. This is faster then gzipping the file on each request.'), ); - $form['global_container']['global']['obscure']['advagg_brotli'] = array( - '#type' => 'checkbox', - '#title' => t('Create .br files'), - '#default_value' => variable_get('advagg_brotli', ADVAGG_BROTLI), - '#description' => t('All aggregated files can be pre-compressed into a .br file and - served from Apache. This is faster then brotli compressing the file on each request.'), - ); - if (!function_exists('brotli_compress') || !defined('BROTLI_TEXT')) { - $form['global_container']['global']['obscure']['advagg_brotli']['#default_value'] = FALSE; - $form['global_container']['global']['obscure']['advagg_brotli']['#disabled'] = TRUE; - $form['global_container']['global']['obscure']['advagg_brotli']['#description'] .= ' ' . t('The PHP brotli extension is needed in order to enable this feature.', array('@url' => 'https://github.com/kjdev/php-ext-brotli')); - if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') { - $form['global_container']['global']['obscure']['advagg_brotli']['#description'] .= ' ' . t('If running on windows this is a helpful guide on how to compile PHP extensions. Also noted that IIS has a Brotli extension,', array( - '@url' => 'https://wiki.php.net/internals/windows/stepbystepbuild', - '@iis' => 'https://www.iis.net/downloads/community/2016/03/iis-brotli', - )); - } - } - $form['global_container']['global']['obscure']['advagg_ajax_render_alter'] = array( + $form['global']['obscure']['advagg_ajax_render_alter'] = array( '#type' => 'checkbox', '#title' => t('Run advagg_ajax_render_alter()'), '#default_value' => variable_get('advagg_ajax_render_alter', ADVAGG_AJAX_RENDER_ALTER), '#description' => t('If disabled, AdvAgg will not alter the aggregates returned by ajax requests.'), ); - $form['global_container']['global']['obscure']['advagg_include_base_url'] = array( + $form['global']['obscure']['advagg_include_base_url'] = array( '#type' => 'checkbox', '#title' => t('Include the base_url variable in the hooks hash array.'), '#default_value' => variable_get('advagg_include_base_url', ADVAGG_INCLUDE_BASE_URL), @@ -500,17 +222,17 @@ function advagg_admin_settings_form($form, $form_state) { '@issue' => 'https://www.drupal.org/node/2353811', )), ); - $form['global_container']['global']['obscure']['advagg_convert_absolute_to_relative_path'] = array( + $form['global']['obscure']['advagg_convert_absolute_to_relative_path'] = array( '#type' => 'checkbox', '#title' => t('Convert absolute paths to be self references.'), '#default_value' => variable_get('advagg_convert_absolute_to_relative_path', ADVAGG_CONVERT_ABSOLUTE_TO_RELATIVE_PATH), '#description' => t('If the src to a CSS/JS file points to this domain, convert the absolute path to a relative path. Will also convert url() references inside of css files. This is generally safe unless the drupal page will be embedded, like it is when used as an iframe.'), ); - $form['global_container']['global']['obscure']['advagg_convert_absolute_to_protocol_relative_path'] = array( + $form['global']['obscure']['advagg_convert_absolute_to_protocol_relative_path'] = array( '#type' => 'checkbox', '#title' => t('Convert absolute paths to be protocol relative paths.'), '#default_value' => variable_get('advagg_convert_absolute_to_protocol_relative_path', ADVAGG_CONVERT_ABSOLUTE_TO_PROTOCOL_RELATIVE_PATH), - '#description' => t('If the src to a CSS/JS file points starts with http:// or https://, convert it to use a protocol relative path //. Will also convert url() references inside of css files. This is incompatible with IE6 and might cause extra bandwidth usage in IE7 and IE8 in regards to url() statements inside css files. According to current web browser market share IE 6 is less than 0.2% of the total number of browsers in use and IE 7+8 make up under 5% of browsers in use.', array( + '#description' => t('If the src to a CSS/JS file points starts with http:// or https://, convert it to use a protocol relative path //. Will also convert url() references inside of css files. This is incompatible with IE6 and might cause extra bandwidth usage in IE7 & IE8 in regards to url() statements inside css files. According to current web browser market share IE 6 is less than 0.2% of the total number of browsers in use & IE 7+8 make up under 5% of browsers in use.', array( '@url' => 'http://www.w3counter.com/trends', )), '#states' => array( @@ -519,7 +241,7 @@ function advagg_admin_settings_form($form, $form_state) { ), ), ); - $form['global_container']['global']['obscure']['advagg_force_https_path'] = array( + $form['global']['obscure']['advagg_force_https_path'] = array( '#type' => 'checkbox', '#title' => t('Convert http:// to https://.'), '#default_value' => variable_get('advagg_force_https_path', ADVAGG_FORCE_HTTPS_PATH), @@ -530,88 +252,14 @@ function advagg_admin_settings_form($form, $form_state) { ), ), ); - $form['global_container']['global']['obscure']['advagg_css_absolute_path'] = array( - '#type' => 'checkbox', - '#title' => t('Convert CSS url() to absolute paths if file is outside of the public:// dir.'), - '#default_value' => variable_get('advagg_css_absolute_path', ADVAGG_CSS_ABSOLUTE_PATH), - '#description' => t('Useful for CSS files hosted on S3 where the referenced files inside of url() is not in the same location as the aggregated CSS file.'), - ); - $form['global_container']['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 will not convert relative paths in your source CSS to absolute paths in the aggregated CSS.'), - ); - $form['global_container']['global']['obscure']['advagg_htaccess_symlinksifownermatch'] = array( - '#type' => 'checkbox', - '#title' => t('Use "Options +SymLinksIfOwnerMatch"'), - '#default_value' => variable_get('advagg_htaccess_symlinksifownermatch', ADVAGG_HTACCESS_SYMLINKSIFOWNERMATCH), - '#description' => t('By default the custom .htaccess files are configured to use "Options +FollowSymLinks". Some hosting companies do not support this so "Options +SymLinksIfOwnerMatch" must be used instead.'), - ); - $form['global_container']['global']['obscure']['advagg_disable_on_admin'] = array( - '#type' => 'checkbox', - '#title' => t('Do not use AdvAgg or any aggregation in the admin section.'), - '#default_value' => variable_get('advagg_disable_on_admin', ADVAGG_DISABLE_ON_ADMIN), - '#description' => t('If checked, AdvAgg will not be used in the /admin section as well as any place the admin theme is used.'), - ); - $form['global_container']['global']['obscure']['advagg_disable_on_listed_pages'] = array( - '#type' => 'textarea', - '#title' => t('Do not use AdvAgg or any aggregation in the following pages'), - '#default_value' => variable_get('advagg_disable_on_listed_pages', ADVAGG_DISABLE_ON_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.", array( - '%blog' => 'blog', - '%blog-wildcard' => 'blog/*', - )), - ); - // Get core htaccess files. - $core_htaccess = advagg_htaccess_rewritebase(); - $advagg_htaccess_rewritebase = variable_get('advagg_htaccess_rewritebase', ADVAGG_HTACCESS_REWRITEBASE); - if (!empty($core_htaccess)) { - // Get advagg htaccess files. - list($css_path, $js_path) = advagg_get_root_files_dir(); - $advagg_css_htaccess = advagg_htaccess_rewritebase($css_path[1]); - $advagg_js_htaccess = advagg_htaccess_rewritebase($js_path[1]); - // Build best guess dir name. - $core_htaccess_directive = trim(str_ireplace('RewriteBase ', '', $core_htaccess)); - $new_dir = str_replace('//', '/', $core_htaccess_directive . '/' . dirname($css_path[1])); - $form['global_container']['global']['obscure']['advagg_htaccess_rewritebase'] = array( - '#type' => 'textfield', - '#title' => t('AdvAgg RewriteBase Directive in .htaccess files.'), - '#default_value' => $advagg_htaccess_rewritebase, - '#description' => t('If gzip and/or brotli files are getting a 307 instead of a 200, this might fix the issue. This Drupal install is using "@core_htaccess" in the webroot .htaccess file. Based off of this and the current location of the advagg css/js directories (@css_path, @js_path) this is the recommended value for this field:

@best_guess

Current advagg htaccess values:

@advagg_css_htaccess
@advagg_js_htaccess

Note that the advagg CSS/JS directories are automatically added onto the end of the given input value.', array( - '@core_htaccess' => $core_htaccess, - '@best_guess' => $new_dir, - '@css_path' => $css_path[1], - '@js_path' => $js_path[1], - '@advagg_css_htaccess' => $advagg_css_htaccess, - '@advagg_js_htaccess' => $advagg_js_htaccess, - )), - ); - } - elseif (!empty($advagg_htaccess_rewritebase)) { - list($css_path, $js_path) = advagg_get_root_files_dir(); - $advagg_css_htaccess = advagg_htaccess_rewritebase($css_path[1]); - $advagg_js_htaccess = advagg_htaccess_rewritebase($js_path[1]); - // Offer advice if this setting should be disabled. - $form['global_container']['global']['obscure']['advagg_htaccess_rewritebase'] = array( - '#type' => 'textfield', - '#title' => t('AdvAgg RewriteBase Directive in .htaccess files.'), - '#default_value' => $advagg_htaccess_rewritebase, - '#description' => t('This Drupal install is no longer using RewriteBase in the webroot .htaccess file. The recommended value for this field is a blank string (empty)
Current advagg htaccess values:

@advagg_css_htaccess
@advagg_js_htaccess

', array( - '@advagg_css_htaccess' => $advagg_css_htaccess, - '@advagg_js_htaccess' => $advagg_js_htaccess, - )), - ); - } - - $form['global_container']['css'] = array( + $form['css'] = array( '#type' => 'fieldset', '#title' => t('CSS Options'), ); - $form['global_container']['css']['advagg_combine_css_media'] = array( + $form['css']['advagg_combine_css_media'] = array( '#type' => 'checkbox', - '#title' => t('Combine CSS files by using media queries (recommended)'), + '#title' => t('Combine CSS files by using media queries'), '#default_value' => variable_get('advagg_combine_css_media', ADVAGG_COMBINE_CSS_MEDIA), '#description' => t('Will combine more CSS files together because different CSS media types can be used in the same file by using media queries. Use cores grouping logic needs to be unchecked in order for this to work. Also noted is that due to an issue with IE9, compatibility mode is forced off if this is enabled.'), '#states' => array( @@ -619,25 +267,23 @@ function advagg_admin_settings_form($form, $form_state) { '#edit-advagg-core-groups' => array('checked' => TRUE), ), ), - '#recommended_value' => TRUE, ); - $form['global_container']['css']['advagg_ie_css_selector_limiter'] = array( + $form['css']['advagg_ie_css_selector_limiter'] = array( '#type' => 'checkbox', '#title' => t('Prevent more than %limit CSS selectors in an aggregated CSS file', array('%limit' => variable_get('advagg_ie_css_selector_limiter_value', ADVAGG_IE_CSS_SELECTOR_LIMITER_VALUE))), '#default_value' => variable_get('advagg_ie_css_selector_limiter', ADVAGG_IE_CSS_SELECTOR_LIMITER), - '#description' => t('Internet Explorer before version 10; IE9, IE8, IE7, and IE6 all have 4095 as the limit for the maximum number of css selectors that can be in a file. Enabling this will prevent CSS aggregates from being created that exceed this limit. More info. Use cores grouping logic needs to be unchecked in order for this to work.', array('@link' => 'http://blogs.msdn.com/b/ieinternals/archive/2011/05/14/10164546.aspx')), + '#description' => t('Internet Explorer before version 10; IE9, IE8, IE7, & IE6 all have 4095 as the limit for the maximum number of css selectors that can be in a file. Enabling this will prevent CSS aggregates from being created that exceed this limit. More info. Use cores grouping logic needs to be unchecked in order for this to work.', array('@link' => 'http://blogs.msdn.com/b/ieinternals/archive/2011/05/14/10164546.aspx')), '#states' => array( 'disabled' => array( '#edit-advagg-core-groups' => array('checked' => TRUE), ), ), - '#recommended_value' => FALSE, ); - $form['global_container']['css']['advagg_ie_css_selector_limiter_value'] = array( + $form['css']['advagg_ie_css_selector_limiter_value'] = array( '#type' => 'textfield', '#title' => t('The selector count the IE CSS limiter should use'), '#default_value' => variable_get('advagg_ie_css_selector_limiter_value', ADVAGG_IE_CSS_SELECTOR_LIMITER_VALUE), - '#description' => t('Internet Explorer before version 10; IE9, IE8, IE7, and IE6 all have 4095 as the limit for the maximum number of css selectors that can be in a file. Use this field to modify the value used; 4095 sometimes may be still be too many with media queries.'), + '#description' => t('Internet Explorer before version 10; IE9, IE8, IE7, & IE6 all have 4095 as the limit for the maximum number of css selectors that can be in a file. Use this field to modify the value used; 4095 sometimes may be still be too many with media queries.'), '#states' => array( 'visible' => array( '#edit-advagg-ie-css-selector-limiter' => array('checked' => TRUE), @@ -647,52 +293,26 @@ function advagg_admin_settings_form($form, $form_state) { ), ), ); - $form['global_container']['css']['advagg_css_fix_type'] = array( + $form['css']['advagg_css_fix_type'] = array( '#type' => 'checkbox', - '#title' => t('Fix improperly set type (recommended)'), + '#title' => t('Fix improperly set type'), '#default_value' => variable_get('advagg_css_fix_type', ADVAGG_CSS_FIX_TYPE), '#description' => t('If type is external but does not start with http, https, or // change it to be type file. If type is file but it starts with http, https, or // change type to be external. Note that if this is causing issues, odds are you have a double slash when there should be a single; see this issue', array( '@link' => 'https://www.drupal.org/node/2336217', )), - '#recommended_value' => TRUE, - ); - $form['global_container']['css']['advagg_css_remove_empty_files'] = array( - '#type' => 'checkbox', - '#title' => t('Remove empty CSS files from aggregates (recommended)'), - '#default_value' => variable_get('advagg_css_remove_empty_files', ADVAGG_CSS_REMOVE_EMPTY_FILES), - '#description' => t('If an empty CSS file ends up being in its own aggregate, that aggregate can end up as a 404.'), - '#recommended_value' => TRUE, ); - $form['global_container']['js'] = array( + $form['js'] = array( '#type' => 'fieldset', '#title' => t('JS Options'), ); - $form['global_container']['js']['advagg_js_fix_type'] = array( + $form['js']['advagg_js_fix_type'] = array( '#type' => 'checkbox', - '#title' => t('Fix improperly set type (recommended)'), + '#title' => t('Fix improperly set type'), '#default_value' => variable_get('advagg_js_fix_type', ADVAGG_JS_FIX_TYPE), '#description' => t('If type is external but does not start with http, https, or // change it to be type file. If type is file but it starts with http, https, or // change type to be external. Note that if this is causing issues, odds are you have a double slash when there should be a single; see this issue', array( '@link' => 'https://www.drupal.org/node/2336217', )), - '#recommended_value' => TRUE, - ); - $form['global_container']['js']['advagg_js_remove_empty_files'] = array( - '#type' => 'checkbox', - '#title' => t('Remove empty JS files from aggregates (recommended)'), - '#default_value' => variable_get('advagg_js_remove_empty_files', ADVAGG_JS_REMOVE_EMPTY_FILES), - '#description' => t('If an empty JS file ends up being in its own aggregate, that aggregate can end up as a 404.'), - '#recommended_value' => TRUE, - ); - $form['global_container']['js']['advagg_scripts_scope_anywhere'] = array( - '#type' => 'checkbox', - '#title' => t('Allow for JS to be added to blocks and views'), - '#default_value' => variable_get('advagg_scripts_scope_anywhere', ADVAGG_SCRIPTS_SCOPE_ANYWHERE), - '#description' => t('The scope key when using drupal_add_js and friends can be used to insert script tags throughout the html body. To see possible insertion points set @theme_debug in your settings.php file and view the page source while looking for @comment html comments. Some example scopes:

!example

', array( - '@theme_debug' => '$conf[\'theme_debug\'] = TRUE;', - '@comment' => '', - '!example' => "page_top:prefix
block:prefix:system:powered-by
view:suffix:frontpage:page", - )), ); // Clear the cache bins on submit. @@ -703,10 +323,11 @@ function advagg_admin_settings_form($form, $form_state) { /** * Form builder; Do advagg operations. + * + * @ingroup forms */ function advagg_admin_operations_form($form, $form_state) { drupal_set_title(t('AdvAgg: Operations')); - advagg_display_message_if_requirements_not_met(); // Explain what can be done on this page. $form['tip'] = array( @@ -739,7 +360,6 @@ function advagg_admin_operations_form($form, $form_state) { 60 * 60 * 24 * 2, 60 * 60 * 24 * 7, 60 * 60 * 24 * 30, - 60 * 60 * 24 * 365, ), 'format_interval'); $form['bypass']['timespan'] = array( '#type' => 'select', @@ -754,38 +374,16 @@ function advagg_admin_operations_form($form, $form_state) { ); // Add in aggregation bypass cookie javascript. $form['#attached']['js'][] = array( - 'data' => array( - 'advagg' => array( - 'key' => drupal_hmac_base64('advagg_cookie', drupal_get_private_key() . drupal_get_hash_salt() . variable_get('cron_key', 'drupal')), - ), - ), + 'data' => array('advagg' => array('key' => drupal_hash_base64(drupal_get_private_key()))), 'type' => 'setting', ); $form['#attached']['js'][] = drupal_get_path('module', 'advagg') . '/advagg.admin.js'; - // Regenerate .htaccess files. - if (variable_get('advagg_htaccess_check_generate', ADVAGG_HTACCESS_CHECK_GENERATE)) { - list($css_path, $js_path) = advagg_get_root_files_dir(); - $form['htaccess'] = array( - '#type' => 'fieldset', - '#title' => t('Regenerate .htaccess files'), - '#description' => t('@advagg_css_htaccess
@advagg_js_htaccess', array( - '@advagg_css_htaccess' => $css_path[1] . '/.htaccess', - '@advagg_js_htaccess' => $js_path[1] . '/.htaccess', - )), - ); - $form['htaccess']['advagg_regenerate_htaccess'] = array( - '#type' => 'submit', - '#value' => t('Recreate htaccess files'), - '#submit' => array('advagg_admin_regenerate_htaccess_button'), - ); - } - // Tasks run by cron. $form['cron'] = array( '#type' => 'fieldset', '#title' => t('Cron Maintenance Tasks'), - '#description' => t('The following 7 operations are ran on cron but you can run them manually here.'), + '#description' => t('The following 4 operations are ran on cron but you can run them manually here.'), ); $form['cron']['smart_file_flush'] = array( '#type' => 'fieldset', @@ -799,33 +397,6 @@ 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_empty_aggregates'] = array( - '#type' => 'fieldset', - '#collapsible' => TRUE, - '#collapsed' => TRUE, - '#title' => t('Delete empty aggregates'), - '#description' => t('Delete all aggregates that have no content so that they can be regenerated.'), - ); - $form['cron']['delete_empty_aggregates']['advagg_delete_empty_aggregates'] = array( - '#type' => 'submit', - '#value' => t('Delete empty aggregates'), - '#submit' => array('advagg_delete_empty_aggregates_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, @@ -838,7 +409,6 @@ 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, @@ -851,7 +421,6 @@ 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, @@ -861,38 +430,10 @@ function advagg_admin_operations_form($form, $form_state) { ); $form['cron']['cleanup_semaphore_table']['advagg_cleanup_semaphore_table'] = array( '#type' => 'submit', - '#value' => t('Delete Orphaned Semaphore Locks'), + '#value' => t('Delete Unused Aggregates From Database'), '#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', @@ -937,98 +478,6 @@ function advagg_admin_operations_form($form, $form_state) { '#value' => t('Increment Global Counter'), '#submit' => array('advagg_admin_increment_global_counter'), ); - $form['drastic_measures']['rescan_files'] = array( - '#type' => 'fieldset', - '#collapsible' => TRUE, - '#collapsed' => TRUE, - '#title' => t('Rescan all files'), - '#description' => t('Force rescan all files and clear the cache. Useful if a css/js change from a deployment did not work.'), - ); - $form['drastic_measures']['rescan_files']['reset_mtime'] = array( - '#type' => 'submit', - '#value' => t('Reset All mtime Values'), - '#submit' => array('advagg_admin_reset_mtime'), - ); - $form['drastic_measures']['remove_empty_advagg_files'] = array( - '#type' => 'fieldset', - '#collapsible' => TRUE, - '#collapsed' => TRUE, - '#title' => t('Remove deleted files in the advagg_files table'), - '#description' => t('Remove entries in the advagg_files table that have a filesize of 0 and delete the javascript_parsed variable. This gets around the grace period that the cron cleanup does.'), - ); - $form['drastic_measures']['remove_empty_advagg_files']['advagg_admin_remove_empty_advagg_files'] = array( - '#type' => 'submit', - '#value' => t('Remove deleted files from advagg_files'), - '#submit' => array('advagg_admin_remove_empty_advagg_files'), - ); - $form['drastic_measures']['reset_advagg_files'] = array( - '#type' => 'fieldset', - '#collapsible' => TRUE, - '#collapsed' => TRUE, - '#title' => t('Reset the AdvAgg Files table'), - '#description' => t('Truncate the advagg_files table and delete the javascript_parsed variable. This may cause some 404s for CSS/JS assets for a short amount of time (seconds). Useful if you really want to reset some stuff. Might be best to put the site into maintenance mode before doing this.'), - ); - $form['drastic_measures']['reset_advagg_files']['truncate_advagg_files'] = array( - '#type' => 'submit', - '#value' => t('Truncate advagg_files'), - '#submit' => array('advagg_admin_truncate_advagg_files'), - ); - - $form['drastic_measures']['clear_base_file'] = array( - '#type' => 'fieldset', - '#collapsible' => TRUE, - '#collapsed' => TRUE, - '#title' => t('Delete all aggregates containing this base file.'), - '#description' => t('Pinpoint clearing of all aggregates that contain a file.'), - ); - $form['drastic_measures']['clear_base_file']['filename'] = array( - '#type' => 'textfield', - '#size' => 170, - '#maxlength' => 256, - '#default_value' => '', - '#title' => t('Filename'), - ); - $form['drastic_measures']['clear_base_file']['submit'] = array( - '#type' => 'submit', - '#value' => t('Delete Select Aggregates'), - '#validate' => array('advagg_admin_clear_file_aggregate_validate'), - '#submit' => array('advagg_admin_clear_file_aggregate_submit'), - '#ajax' => array( - 'callback' => 'advagg_admin_clear_file_aggregate_callback', - 'wrapper' => 'advagg-clear-file-aggregate-ajax', - 'effect' => 'fade', - ), - ); - $types = array('css', 'js'); - $css_file = ''; - $js_file = ''; - foreach ($types as $type) { - // Get valid filename. - $results = db_select('advagg_files', 'af') - ->fields('af', array('filename')) - ->condition('filetype', $type) - ->orderBy('filename', 'ASC') - ->execute(); - while ($row = $results->fetchAssoc()) { - if (empty($css_file) && $type === 'css') { - $css_file = $row['filename']; - break; - } - if (empty($js_file) && $type === 'js') { - $js_file = $row['filename']; - break; - } - } - } - $form['drastic_measures']['clear_base_file']['tip'] = array( - '#markup' => '

' . t('Takes input like "@css_file" or "@advagg_js"', array( - '@css_file' => $css_file, - '@advagg_js' => $js_file, - )) . '

', - ); - $form['drastic_measures']['clear_base_file']['wrapper'] = array( - '#markup' => "
", - ); return $form; } @@ -1036,66 +485,36 @@ function advagg_admin_operations_form($form, $form_state) { /** * Form builder; Show info about advagg and advagg settings. * + * @ingroup forms + * * @see system_settings_form() */ function advagg_admin_info_form($form, $form_state) { drupal_set_title(t('AdvAgg: Information')); - advagg_display_message_if_requirements_not_met(); // Explain what can be done on this page. $form['tip'] = array( '#markup' => '

' . t('This page provides debugging information. There are no configuration options here.') . '

', ); - // Get all hooks and variables. + // Get all hooks & variables. drupal_theme_initialize(); $core_hooks = theme_get_registry(); $advagg_hooks = advagg_hooks_implemented(); list(, $js_path) = advagg_get_root_files_dir(); // Output html process functions hooks. - $form['theme_info'] = array( + $form['info'] = array( '#type' => 'fieldset', '#collapsible' => TRUE, '#collapsed' => TRUE, '#title' => t('Hook Theme Info'), ); $data = implode("\n", $core_hooks['html']['process functions']); - $form['theme_info']['advagg_theme_info'] = array( + $form['info']['advagg_debug_info'] = array( '#markup' => '
' . $data . '
', ); - $form['render_info'] = array( - '#type' => 'fieldset', - '#collapsible' => TRUE, - '#collapsed' => TRUE, - '#title' => t('Hook Render Info'), - ); - $element_info = array(); - $element_info['scripts'] = element_info('scripts'); - $element_info['styles'] = element_info('styles'); - $output = ''; - foreach ($element_info as $type => &$values) { - $output .= $type . ":\n"; - foreach ($values as $key => &$value) { - if ($key === '#items' || $key === '#type') { - continue; - } - if (empty($value)) { - $output .= ' ' . $key . ' - ' . str_replace(array("\r", "\n"), '', (string) var_export($value, TRUE)) . "\n"; - } - elseif (is_array($value)) { - $output .= ' ' . $key . ' - ' . implode(', ', $value) . "\n"; - } - else { - $output .= ' ' . $key . ' - ' . print_r($value, TRUE) . "\n"; - } - } - } - $form['render_info']['advagg_render_info'] = array( - '#markup' => '
' . $output . '
', - ); - // Get all parent css and js files. $types = array('css', 'js'); $css_file = ''; @@ -1110,7 +529,6 @@ function advagg_admin_info_form($form, $form_state) { $results = db_select('advagg_files', 'af') ->fields('af', array('filename', 'filename_hash', 'changes')) ->condition('filetype', $type) - ->orderBy('filename', 'ASC') ->execute(); while ($row = $results->fetchAssoc()) { if (empty($css_file) && $type === 'css') { @@ -1225,11 +643,10 @@ function advagg_admin_info_form($form, $form_state) { ), ); module_load_include('install', 'advagg', 'advagg'); - $first_file_path = (advagg_s3fs_evaluate_no_rewrite_cssjs(FALSE)) ? $js_path[0] : $js_path[1]; $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($first_file_path, 'js'), + '@advagg_js' => advagg_install_get_first_advagg_file($js_path[1]), )) . '

', ); $form['get_info_about_agg']['wrapper'] = array( @@ -1239,144 +656,24 @@ 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'), - )); - $msg .= ' '; - 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', - )); - } - if (is_callable('omega_extension_enabled') - && is_callable('omega_theme_get_setting') - && omega_extension_enabled('development') - && omega_theme_get_setting('omega_livereload', TRUE) - ) { - $msg .= ' ' . t('The omega theme is in development mode and livereload is also enabled. This setting combination disables CSS aggregation. If you wish to have CSS aggregation turned on, go to the theme settings page, select the Omega theme/subtheme and turn off LiveReload and/or turn off the Development extension.', array('@url' => url('admin/appearance/settings'))); - } - - $form['bandwidth_optimization']['advagg_note'] = array( - '#markup' => $msg, - ); -} - -/** - * @} End of "defgroup advagg_forms". - */ - -/** - * @defgroup advagg_forms_callback AdvAgg forms callbacks and validation. - * @{ - * Functions that handle user input from forms. - */ - +// Submit callback. /** * Clear out the advagg cache bin when the save configuration button is pressed. */ function advagg_admin_settings_form_submit($form, &$form_state) { - // Remove non variables. - if (isset($form_state['values']['advagg_resource_hints_preload_reset'])) { - unset($form_state['values']['advagg_resource_hints_preload_reset']); + $cache_bins = advagg_flush_caches(); + foreach ($cache_bins as $bin) { + cache_clear_all('*', $bin, TRUE); } - - // Reset this form to defaults or recommended values; also show what changed. - advagg_set_admin_form_defaults_recommended($form_state, 'advagg_admin_mode'); - - // Sort and fix values before saving to the db. - foreach ($form_state['values']['advagg_resource_hints_preload_settings'] as &$entry) { - if (isset($entry['weight'])) { - $entry['#weight'] = $entry['weight']; - unset($entry['weight']); - } - ksort($entry); - } - uasort($form_state['values']['advagg_resource_hints_preload_settings'], 'element_sort'); - - // Make sure .htaccess file exists in the advagg dir. - if (isset($form_state['values']['advagg_resource_hints_use_immutable']) - && variable_get('advagg_resource_hints_use_immutable', ADVAGG_RESOURCE_HINTS_USE_IMMUTABLE) != $form_state['values']['advagg_resource_hints_use_immutable'] - && variable_get('advagg_htaccess_check_generate', ADVAGG_HTACCESS_CHECK_GENERATE) - ) { - // Set variable. - variable_set('advagg_resource_hints_use_immutable', $form_state['values']['advagg_resource_hints_use_immutable']); - unset($form_state['values']['advagg_resource_hints_use_immutable']); - - // Get paths to .htaccess file. - list($css_path, $js_path) = advagg_get_root_files_dir(); - $files['css'] = $css_path[0] . '/.htaccess'; - $files['js'] = $js_path[0] . '/.htaccess'; - - // Make the advagg_htaccess_check_generate() function available. - module_load_include('inc', 'advagg', 'advagg.missing'); - - // Generate new .htaccess files in advagg dirs. - foreach ($files as $type => $uri) { - advagg_htaccess_check_generate(array($uri => $type), $type, TRUE); - } - } - - // Alter htaccess if needed. - if (isset($form_state['values']['advagg_htaccess_rewritebase']) - && variable_get('advagg_htaccess_rewritebase', ADVAGG_HTACCESS_REWRITEBASE) != $form_state['values']['advagg_htaccess_rewritebase'] - && variable_get('advagg_htaccess_check_generate', ADVAGG_HTACCESS_CHECK_GENERATE) - ) { - // Set variable. - variable_set('advagg_htaccess_rewritebase', trim($form_state['values']['advagg_htaccess_rewritebase'])); - unset($form_state['values']['advagg_htaccess_rewritebase']); - - // Get paths to .htaccess file. - list($css_path, $js_path) = advagg_get_root_files_dir(); - $files['css'] = $css_path[0] . '/.htaccess'; - $files['js'] = $js_path[0] . '/.htaccess'; - - // Make the advagg_htaccess_check_generate() function available. - module_load_include('inc', 'advagg', 'advagg.missing'); - - // Generate new .htaccess files in advagg dirs. - foreach ($files as $type => $uri) { - advagg_htaccess_check_generate(array($uri => $type), $type, TRUE); - } - } - - if (variable_get('advagg_htaccess_symlinksifownermatch', ADVAGG_HTACCESS_SYMLINKSIFOWNERMATCH) != $form_state['values']['advagg_htaccess_symlinksifownermatch'] - && variable_get('advagg_htaccess_check_generate', ADVAGG_HTACCESS_CHECK_GENERATE) - ) { - list($css_path, $js_path) = advagg_get_root_files_dir(); - $files['css'] = $css_path[0] . '/.htaccess'; - $files['js'] = $js_path[0] . '/.htaccess'; - - // Make the advagg_htaccess_check_generate() function available. - module_load_include('inc', 'advagg', 'advagg.missing'); - - // Push out new setting. - variable_set('advagg_htaccess_symlinksifownermatch', $form_state['values']['advagg_htaccess_symlinksifownermatch']); - unset($form_state['values']['advagg_htaccess_symlinksifownermatch']); - foreach ($files as $type => $uri) { - advagg_htaccess_check_generate(array($uri => $type), $type, TRUE); - } - } - - // Clear caches. - advagg_cache_clear_admin_submit(); } +// Callbacks for buttons. /** * Set or remove the AdvAggDisabled cookie. */ function advagg_admin_toggle_bypass_cookie($form, &$form_state) { $cookie_name = 'AdvAggDisabled'; - $key = drupal_hmac_base64('advagg_cookie', drupal_get_private_key() . drupal_get_hash_salt() . variable_get('cron_key', 'drupal')); + $key = drupal_hash_base64(drupal_get_private_key()); // If the cookie does exist then remove it. if (!empty($_COOKIE[$cookie_name]) && $_COOKIE[$cookie_name] == $key) { @@ -1397,7 +694,6 @@ 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; } @@ -1529,50 +825,10 @@ function advagg_admin_get_file_info($filename) { return $output; } -/** - * Reset the advagg_resource_hints_preload_settings variable. - */ -function advagg_admin_resource_hints_preload_reset() { - variable_del('advagg_resource_hints_preload_settings'); -} - -/** - * Recreate the advagg htaccess files. - */ -function advagg_admin_regenerate_htaccess_button() { - // Get paths to .htaccess file. - list($css_path, $js_path) = advagg_get_root_files_dir(); - $files['css'] = $css_path[0] . '/.htaccess'; - $files['js'] = $js_path[0] . '/.htaccess'; - - // Make the advagg_htaccess_check_generate() function available. - module_load_include('inc', 'advagg', 'advagg.missing'); - - // Generate new .htaccess files in advagg dirs. - $errors = array(); - foreach ($files as $type => $uri) { - $errors += advagg_htaccess_check_generate(array($uri => $type), $type, TRUE); - } - if (empty($errors)) { - drupal_set_message(t('The .htaccess files have been regenerated.')); - } - else { - drupal_set_message(t('The .htaccess files failed to be regenerated. @errors', array('@errors' => implode(', ', $errors)))); - } -} - /** * 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(); @@ -1593,11 +849,10 @@ function advagg_admin_flush_cache_button() { 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. Trigger: @changes', array( + 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' => $data[0], '%db_count' => $data[1], - '@changes' => print_r($data[2], TRUE), '%type' => $ext, ))); } @@ -1613,14 +868,6 @@ 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(); @@ -1633,9 +880,12 @@ function advagg_admin_clear_all_caches_button() { } /** - * Clear out all advagg aggregated files. + * Clear out all advagg cache bins and clear out all advagg aggregated files. */ function advagg_admin_clear_all_files_button() { + // Clear out the cache. + advagg_admin_clear_all_caches_button(); + // Run the command. module_load_include('inc', 'advagg', 'advagg.cache'); list($css_files, $js_files) = advagg_remove_all_aggregated_files(); @@ -1667,26 +917,6 @@ function advagg_admin_flush_stale_files_button() { } } -/** - * Delete all empty advagg aggregated files. - */ -function advagg_delete_empty_aggregates_button() { - // Run the command. - module_load_include('inc', 'advagg', 'advagg.cache'); - list($css_files, $js_files) = advagg_delete_empty_aggregates(); - - // Report back the results. - if (count($css_files) > 0 || count($js_files) > 0) { - drupal_set_message(t('All empty aggregates have been deleted. %css_count CSS files and %js_count JS files have been removed.', array( - '%css_count' => count($css_files), - '%js_count' => count($js_files), - ))); - } - else { - drupal_set_message(t('No empty aggregates found. Nothing was deleted.')); - } -} - /** * Clear out all advagg cache bins and increment the counter. */ @@ -1700,69 +930,6 @@ function advagg_admin_increment_global_counter() { drupal_set_message(t('Global counter is now set to %new_value', array('%new_value' => $new_value))); } -/** - * Clear out all advagg cache bins and increment the counter. - */ -function advagg_admin_reset_mtime() { - // Reset mtime. - db_update('advagg_files') - ->fields(array( - 'mtime' => 0, - )) - ->execute(); - drupal_set_message(t('All files have been rescanned.')); - - // Smart cache clear. - advagg_admin_flush_cache_button(); - - // Clear out the cache. - advagg_admin_clear_all_caches_button(); -} - -/** - * Remove filesize zero files from the advagg_files table and clear caches. - */ -function advagg_admin_remove_empty_advagg_files() { - // Remove dead files from the advagg_files table. - db_delete('advagg_files') - ->condition('filesize', 0) - ->execute(); - variable_del('javascript_parsed'); - drupal_set_message(t('All empty files in the advagg_files table have been removed.')); - - // Clear out the cache. - advagg_admin_clear_all_caches_button(); -} - -/** - * Truncate the advagg_files table and clear caches. - */ -function advagg_admin_truncate_advagg_files() { - // Truncate advagg_files table. - db_truncate('advagg_files')->execute(); - variable_del('javascript_parsed'); - drupal_set_message(t('All files from the advagg_files table have been removed.')); - - // Clear out the cache. - advagg_admin_clear_all_caches_button(); -} - -/** - * 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. */ @@ -1807,159 +974,6 @@ 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(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)))); + 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))); } } - -/** - * 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.')); - } -} - -/** - * Verify that the filename is correct. - */ -function advagg_admin_clear_file_aggregate_validate($form, &$form_state) { - if (empty($form_state['values']['filename'])) { - form_set_error('filename', t('Please input a filename.')); - $form_state['values']['error'] = TRUE; - } -} - -/** - * Display what files where deleted in a drupal message. - */ -function advagg_admin_clear_file_aggregate_submit($form, &$form_state) { - // @codingStandardsIgnoreLine - if (!empty($form_state['input']['ajax_page_state'])) { - return; - } - $info = advagg_admin_clear_file_aggregates($form_state['values']['filename']); - if (module_exists('httprl')) { - $output = httprl_pr($info); - } - else { - $output = '
' . check_plain(print_r($info, TRUE)) . '
'; - } - // @ignore security_dsm - drupal_set_message($output); -} - -/** - * Display what files where deleted via ajax callback. - */ -function advagg_admin_clear_file_aggregate_callback($form, &$form_state) { - if (!empty($form_state['values']['error'])) { - return '
'; - } - $info = advagg_admin_clear_file_aggregates($form_state['values']['filename']); - if (empty($info)) { - form_set_error('filename', t('Please input a valid filename.')); - return '
'; - } - else { - if (module_exists('httprl')) { - $output = httprl_pr($info); - } - else { - $output = '
' . print_r($info, TRUE) . '
'; - } - return '
' . $output . '
'; - } -} - -/** - * Remove the aggregates that contain the given filename. - * - * @param string $filename - * Name of file to lookup. Can be a comma separated list. - * @param bool $dry_run - * If TRUE, return the regex search string. - * - * @return array - * Returns an array of the parent file and what children where deleted. - */ -function advagg_admin_clear_file_aggregates($filename, $dry_run = FALSE) { - module_load_include('inc', 'advagg', 'advagg.cache'); - list($css_path, $js_path) = advagg_get_root_files_dir(); - $space = ADVAGG_SPACE; - $output = array(); - $options = array('callback' => 'file_unmanaged_delete'); - if ($dry_run) { - $options = array(); - } - - // Strip quotes and trim. - $filenames = array_map('trim', explode(',', trim(str_replace(array('"', "'"), '', $filename)))); - - foreach ($filenames as $filename) { - $results = db_select('advagg_files', 'af') - ->fields('af') - ->condition('filename', $filename) - ->execute(); - while ($row = $results->fetchAssoc()) { - // Get aggregates that use this file. - $row['aggregates_using_file'] = advagg_get_aggregates_using_file($row['filename_hash']); - // Get dir and other info from file. - if ($row['filetype'] === 'css') { - $dirname = $css_path[0]; - $basename_prefix = "{$row['filetype']}"; - } - if ($row['filetype'] === 'js') { - $dirname = $js_path[0]; - $basename_prefix = "{$row['filetype']}"; - } - - // Build regex search string for file_scan_directory(). - $regex_search = array(); - foreach ($row['aggregates_using_file'] as $values) { - $regex_search[] = preg_quote("{$basename_prefix}{$space}{$values['aggregate_filenames_hash']}{$space}") . '.*'; - } - $regex_search = array_unique($regex_search); - $regex_search_string = '/(' . implode('|', $regex_search) . ')/'; - $files = file_scan_directory($dirname, $regex_search_string, $options); - - // List what files were deleted. - $row['aggregates_deleted'] = array(); - $files_deleted = array_keys($files); - if (!empty($files_deleted)) { - $row['aggregates_deleted'][] = $files_deleted; - } - - $output[$filename] = $row['aggregates_deleted']; - } - } - - return $output; -} - -/** - * @} End of "defgroup advagg_forms_callback". - */ diff --git a/sites/all/modules/contrib/advagg/advagg.admin.js b/sites/all/modules/contrib/advagg/advagg.admin.js index 5a4e29ec2e..8fb0fe7826 100644 --- a/sites/all/modules/contrib/advagg/advagg.admin.js +++ b/sites/all/modules/contrib/advagg/advagg.admin.js @@ -1,83 +1,80 @@ +/** global Drupal:false */ + /** * @file * Used to toggle the AdvAgg Bypass Cookie client side. */ -/* global Drupal:false */ -/* eslint-disable no-unused-vars */ - /** * 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 {bool} + * @return * true if string contains non ASCII characters. * false if string only contains ASCII characters. */ -Drupal.formatInterval = function (interval, granularity, langcode) { - 'use strict'; +Drupal.formatInterval = function(interval, granularity, langcode) { + "use strict"; granularity = typeof granularity !== 'undefined' ? granularity : 2; langcode = typeof langcode !== 'undefined' ? langcode : null; var output = ''; - /* eslint-disable key-spacing */ while (granularity > 0) { var value = 0; if (interval >= 31536000) { value = 31536000; - output += (output.length ? ' ' : '') + Drupal.formatPlural(Math.floor(interval / value), '1 year', '@count years', {langcode : langcode}); + output += (output.length ? ' ' : '') + Drupal.formatPlural(Math.floor(interval / value), '1 year', '@count years', { langcode : langcode }); } else if (interval >= 2592000) { value = 2592000; - output += (output.length ? ' ' : '') + Drupal.formatPlural(Math.floor(interval / value), '1 month', '@count months', {langcode : langcode}); + output += (output.length ? ' ' : '') + Drupal.formatPlural(Math.floor(interval / value), '1 month', '@count months', { langcode : langcode }); } else if (interval >= 604800) { value = 604800; - output += (output.length ? ' ' : '') + Drupal.formatPlural(Math.floor(interval / value), '1 week', '@count weeks', {langcode : langcode}); + output += (output.length ? ' ' : '') + Drupal.formatPlural(Math.floor(interval / value), '1 week', '@count weeks', { langcode : langcode }); } else if (interval >= 86400) { value = 86400; - output += (output.length ? ' ' : '') + Drupal.formatPlural(Math.floor(interval / value), '1 day', '@count days', {langcode : langcode}); + output += (output.length ? ' ' : '') + Drupal.formatPlural(Math.floor(interval / value), '1 day', '@count days', { langcode : langcode }); } else if (interval >= 3600) { value = 3600; - output += (output.length ? ' ' : '') + Drupal.formatPlural(Math.floor(interval / value), '1 hour', '@count hours', {langcode : langcode}); + output += (output.length ? ' ' : '') + Drupal.formatPlural(Math.floor(interval / value), '1 hour', '@count hours', { langcode : langcode }); } else if (interval >= 60) { value = 60; - output += (output.length ? ' ' : '') + Drupal.formatPlural(Math.floor(interval / value), '1 min', '@count min', {langcode : langcode}); + output += (output.length ? ' ' : '') + Drupal.formatPlural(Math.floor(interval / value), '1 min', '@count min', { langcode : langcode }); } else if (interval >= 1) { value = 1; - output += (output.length ? ' ' : '') + Drupal.formatPlural(Math.floor(interval / value), '1 sec', '@count sec', {langcode : langcode}); + output += (output.length ? ' ' : '') + Drupal.formatPlural(Math.floor(interval / value), '1 sec', '@count sec', { langcode : langcode }); } interval %= value; granularity--; } - return output.length ? output : Drupal.t('0 sec', {}, {langcode : langcode}); - /* eslint-enable key-spacing */ -}; + return output.length ? output : Drupal.t('0 sec', {}, { langcode : langcode }); +} /** * Test to see if the given string contains unicode. * - * @param {string} str + * @param str * String to test. * - * @return {bool} + * @return * true if string contains non ASCII characters. * false if string only contains ASCII characters. */ -function advagg_is_unicode(str) { - 'use strict'; +function advagg_is_unicode(str){ + "use strict"; for (var i = 0, n = str.length; i < n; i++) { if (str.charCodeAt(i) > 255) { return true; @@ -89,12 +86,12 @@ function advagg_is_unicode(str) { /** * Toggle the advagg cookie. * - * @return {bool} + * @return * true if hostname contains unicode. * false so the form does not get submitted. */ function advagg_toggle_cookie() { - 'use strict'; + "use strict"; // Fallback to submitting the form for Unicode domains like ".рф". if (advagg_is_unicode(document.location.hostname)) { return true; @@ -107,24 +104,21 @@ function advagg_toggle_cookie() { // If the cookie does exist then remove it. if (cookie_pos !== -1) { - document.cookie = - cookie_name + '=' - + '; expires=Thu, 01 Jan 1970 00:00:00 GMT' - + '; path=' + Drupal.settings.basePath - + '; domain=.' + document.location.hostname + ';'; + document.cookie = cookie_name + '=;' + + 'expires=Thu, 01 Jan 1970 00:00:00 GMT;' + + ' path=' + Drupal.settings.basePath + ';' + + ' domain=.' + document.location.hostname + ';'; alert(Drupal.t('AdvAgg Bypass Cookie Removed')); } // If the cookie does not exist then set it. else { - var bypass_length = document.getElementById('edit-timespan').value; - var expire_date = new Date(new Date().getTime() + bypass_length * 1000); + var bypass_length = document.getElementById('edit-timespan').value, expire_date = new Date(new Date().getTime() + bypass_length * 1000); - document.cookie = - cookie_name + '=' + Drupal.settings.advagg.key - + '; expires=' + expire_date.toGMTString() - + '; path=' + Drupal.settings.basePath - + '; domain=.' + document.location.hostname + ';'; - alert(Drupal.t('AdvAgg Bypass Cookie Set for @time.', {'@time': Drupal.formatInterval(bypass_length)})); + document.cookie = cookie_name + '=' + Drupal.settings.advagg.key + ';' + + ' expires=' + expire_date.toGMTString() + ';' + + ' path=' + Drupal.settings.basePath + ';' + + ' domain=.' + document.location.hostname + ';'; + alert(Drupal.t('AdvAgg Bypass Cookie Set for @time.', {'@time' : Drupal.formatInterval(bypass_length)})); } // Must return false, if returning true then form gets submitted. diff --git a/sites/all/modules/contrib/advagg/advagg.advagg.inc b/sites/all/modules/contrib/advagg/advagg.advagg.inc index bd6aba3802..feab39ac72 100644 --- a/sites/all/modules/contrib/advagg/advagg.advagg.inc +++ b/sites/all/modules/contrib/advagg/advagg.advagg.inc @@ -7,122 +7,65 @@ * File used to store hook_advagg_* hooks. */ -/** - * @addtogroup advagg_hooks - * @{ - */ - +// @ignore sniffer_commenting_functioncomment_hookparamdoc:11 /** * Implements hook_advagg_save_aggregate_alter(). * * Used to add in a .gz file if none exits. + * + * @param array $files_to_save + * Array($uri => $contents). + * @param array $aggregate_settings + * Array of settings. + * @param array $other_parameters + * Array of containing $files & $type. */ function advagg_advagg_save_aggregate_alter(array &$files_to_save, array $aggregate_settings, array $other_parameters) { - // * @param array $files_to_save - // * Array($uri => $contents). - // * @param array $aggregate_settings - // * Array of settings. - // * @param array $other_parameters - // * Array of containing $files and $type. - $file_types = array(); - // Handle gzip. - if (!empty($aggregate_settings['variables']['advagg_gzip'])) { - // Use zopfli_encode if it exists. - // See https://github.com/kjdev/php-ext-zopfli - if (function_exists('zopfli_encode') - && defined('ZOPFLI_GZIP') - && empty($aggregate_settings['variables']['advagg_no_zopfli']) - ) { - $file_types['.gz'] = array('zopfli_encode', 15, constant('ZOPFLI_GZIP')); - } - else { - $file_types['.gz'] = array('gzencode', 9, FORCE_GZIP); - } - } - // Handle brotli. - // See https://github.com/kjdev/php-ext-brotli - if (!empty($aggregate_settings['variables']['advagg_brotli']) - && defined('BROTLI_TEXT') - && function_exists('brotli_compress') - ) { - $file_types['.br'] = array('brotli_compress', 11, constant('BROTLI_TEXT')); - } - - if (empty($file_types)) { + // Return if gzip is disabled. + if (empty($aggregate_settings['variables']['advagg_gzip'])) { return; } - // Special S3 handling. - $s3fs = FALSE; - $files_to_save_keys = array_keys($files_to_save); - foreach ($files_to_save_keys as $uri) { - $wrapper = file_stream_wrapper_get_instance_by_uri($uri); - if ($wrapper && get_class($wrapper) === 'S3fsStreamWrapper') { - $s3fs = TRUE; - break; - } - } - - foreach ($file_types as $ext => $settings) { - // See if a file already exists with this extension. - $ext_exists = FALSE; - foreach ($files_to_save as $uri => $contents) { - // See if this uri contains $ext near the end of it. - if (strlen($uri) > 91 + strlen(ADVAGG_SPACE) * 3) { - $pos = strripos($uri, $ext, 91 + strlen(ADVAGG_SPACE) * 3); - } - else { - $pos = strripos($uri, $ext); - } - if (!empty($pos)) { - $len = strlen($uri); - // $ext file exists, exit loop. - if ($pos == $len - 3) { - $ext_exists = TRUE; - break; - } + // See if a .gz file already exists. + $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 (!empty($pos)) { + $len = strlen($uri); + // .gz file exists, exit loop. + if ($pos == $len - 3) { + $gzip_exists = TRUE; + break; } } + } - // If a $ext file does not exist, create one. - if (!$ext_exists) { - // Use the first file in the array. - $data = reset($files_to_save); - $uri = key($files_to_save); - // Compress it and add it to the $files_to_save array. - $callback = $settings[0]; - $settings[0] = $data; - $compressed = call_user_func_array($callback, $settings); - if (!empty($compressed)) { - if ($s3fs && $ext === '.gz') { - // Only serve gzip files from S3. - $files_to_save[$uri] = $compressed; - } - elseif ($s3fs && $ext === '.br' && !isset($file_types['.gz'])) { - // Only serve br files from S3. - $files_to_save[$uri] = $compressed; - } - else { - $files_to_save[$uri . $ext] = $compressed; - } - } - } + // If a .gz file does not exist, create one. + if (!$gzip_exists) { + // Use the first file in the array. + $data = reset($files_to_save); + $uri = key($files_to_save); + // Compress it and add it to the $files_to_save array. + $compressed = gzencode($data, 9, FORCE_GZIP); + $files_to_save[$uri . '.gz'] = $compressed; } } +// @ignore sniffer_commenting_functioncomment_hookparamdoc:10 /** * Implements hook_advagg_build_aggregate_plans_alter(). * * Used to alter the plan so it has the same grouping as cores. + * + * @param array $files + * List of files in the aggregate as well as the aggregate name. + * @param bool $modified + * Change this to TRUE if $files has been changed. + * @param string $type + * String containing css or js. */ function advagg_advagg_build_aggregate_plans_alter(array &$files, &$modified, $type) { - // * @param array $files - // * List of files in the aggregate as well as the aggregate name. - // * @param bool $modified - // * Change this to TRUE if $files has been changed. - // * @param string $type - // * String containing css or js. - // // Do nothing if core grouping is disabled. if (!variable_get('advagg_core_groups', ADVAGG_CORE_GROUPS)) { return; @@ -134,7 +77,7 @@ function advagg_advagg_build_aggregate_plans_alter(array &$files, &$modified, $t $group = NULL; $every_page = NULL; foreach ($data['files'] as $fileinfo) { - // Grouped by group and every_page variables. + // Grouped by group & every_page variables. if (is_null($group)) { $group = $fileinfo['group']; } @@ -158,6 +101,7 @@ function advagg_advagg_build_aggregate_plans_alter(array &$files, &$modified, $t $files = advagg_generate_filenames(array($temp_new_files), $type); } + /** * Implements hook_advagg_context_alter(). */ @@ -173,12 +117,13 @@ function advagg_advagg_context_alter(&$original, $aggregate_settings, $mode) { $GLOBALS['is_https'] = $aggregate_settings['variables']['is_https']; if ($aggregate_settings['variables']['is_https']) { $GLOBALS['base_root'] = str_replace('http://', 'https://', $GLOBALS['base_root']); + $GLOBALS['base_url'] = str_replace('http://', 'https://', $GLOBALS['base_url']); } else { $GLOBALS['base_root'] = str_replace('https://', 'http://', $GLOBALS['base_root']); + $GLOBALS['base_url'] = str_replace('https://', 'http://', $GLOBALS['base_url']); } $GLOBALS['base_path'] = $aggregate_settings['variables']['base_path']; - $GLOBALS['base_url'] = rtrim($GLOBALS['base_root'] . $GLOBALS['base_path'], '/'); if (isset($aggregate_settings['variables']['language'])) { $languages = language_list(); @@ -269,103 +214,32 @@ function advagg_advagg_context_alter(&$original, $aggregate_settings, $mode) { * Used to make sure the info is up to date in the cache. */ function advagg_advagg_get_info_on_files_alter(&$return, $cached_data, $bypass_cache) { - // Check for the ie_css_selector_limiter. - if (variable_get('advagg_ie_css_selector_limiter', ADVAGG_IE_CSS_SELECTOR_LIMITER)) { - $limit_value = variable_get('advagg_ie_css_selector_limiter_value', ADVAGG_IE_CSS_SELECTOR_LIMITER_VALUE); - - // Get the css path. - list($css_path) = advagg_get_root_files_dir(); - $css_parts_path = (advagg_s3fs_evaluate_no_rewrite_cssjs(FALSE)) ? $css_path[0] : $css_path[1]; - $parts_path = $css_parts_path . '/parts/'; - - foreach ($return as &$info) { - // Skip if not a css file. - if (empty($info['fileext']) || $info['fileext'] !== 'css') { - continue; - } - - // Check if this is a split css file. - if (strpos($info['data'], $parts_path) !== FALSE) { - $info['split'] = TRUE; - } - // Break large file into multiple small files if needed. - elseif ($info['linecount'] > $limit_value) { - advagg_split_css_file($info); - } - } - unset($info); + // Do nothing if the ie_css_selector_limiter is disabled. + if (!variable_get('advagg_ie_css_selector_limiter', ADVAGG_IE_CSS_SELECTOR_LIMITER)) { + return; } + $limit_value = variable_get('advagg_ie_css_selector_limiter_value', ADVAGG_IE_CSS_SELECTOR_LIMITER_VALUE); - // Capture resource_hints. - if (variable_get('advagg_resource_hints_dns_prefetch', ADVAGG_RESOURCE_HINTS_DNS_PREFETCH) - || variable_get('advagg_resource_hints_preconnect', ADVAGG_RESOURCE_HINTS_PRECONNECT) - || variable_get('advagg_resource_hints_preload', ADVAGG_RESOURCE_HINTS_PRELOAD) - ) { - $aggregate_settings = advagg_current_hooks_hash_array(); - foreach ($return as &$info) { - // Skip if not a css file. - if (empty($info['fileext']) || $info['fileext'] !== 'css') { - continue; - } - // Get the file contents. - $file_contents = (string) @advagg_file_get_contents($info['data']); - $file_contents = advagg_load_css_stylesheet($info['data'], FALSE, $aggregate_settings, $file_contents); - - // Get domain names and external assets in this css file. - $hosts = array(); - $urls = array(); - $matches = array(); - $pattern = '%url\(\s*+[\'"]?+(http:\/\/|https:\/\/|\/\/)([^\'"()\s]++)[\'"]?+\s*+\)%i'; - preg_match_all($pattern, $file_contents, $matches); - if (!empty($matches[1])) { - foreach ($matches[1] as $key => $match) { - $url = $match . $matches[2][$key]; - $parse = @parse_url($url); - if (!empty($parse['host'])) { - $extra = ''; - $ext = strtolower(pathinfo($url, PATHINFO_EXTENSION)); - $supported = array( - 'eot', - 'woff2', - 'woff', - 'ttf', - 'otf', - ); - if (in_array($ext, $supported)) { - $extra .= '#crossorigin'; - } - $hosts[$parse['host'] . $extra] = $match . $parse['host'] . '/' . $extra; - $urls[$url] = $url; - } - } - } - if (!empty($hosts) && (variable_get('advagg_resource_hints_dns_prefetch', ADVAGG_RESOURCE_HINTS_DNS_PREFETCH) - || variable_get('advagg_resource_hints_preconnect', ADVAGG_RESOURCE_HINTS_PRECONNECT) - )) { - $info['dns_prefetch'] = array_values($hosts); - } + // Get the css path. + list($css_path) = advagg_get_root_files_dir(); + $parts_path = $css_path[1] . '/parts'; - // Get local files to preload in this css file. - if (variable_get('advagg_resource_hints_preload', ADVAGG_RESOURCE_HINTS_PRELOAD)) { - $matches = array(); - $pattern = '/url\(\s*+[\'"]?(.*?)[\'"\s]?+\)/i'; - preg_match_all($pattern, $file_contents, $matches); - if (!empty($matches[1])) { - foreach ($matches[1] as $key => $match) { - $url = advagg_convert_abs_to_rel($match); - $parse = @parse_url($url); - if (empty($parse['host'])) { - $urls[$url] = $url; - } - } - } - if (!empty($urls)) { - $info['preload'] = array_values($urls); - } - } + foreach ($return as &$info) { + // Skip if not a css file. + if (empty($info['fileext']) || $info['fileext'] !== 'css') { + continue; + } + + // Check if this is a split css file. + if (strpos($info['data'], $parts_path) === 0) { + $info['split'] = TRUE; + } + // Break large file into multiple small files if needed. + elseif ($info['linecount'] > $limit_value) { + advagg_split_css_file($info); } - unset($info); } + unset($info); } /** @@ -373,16 +247,13 @@ function advagg_advagg_get_info_on_files_alter(&$return, $cached_data, $bypass_c * * If the color module is enabled regenerate color module css when it changes. * If a responsive file inside an adaptive theme has changed, regenerate it. + * + * @param array $files + * List of files that have changed. + * @param array $types + * Array with the css and or the js key. */ function advagg_advagg_changed_files(array $files, array $types) { - // * @param array $files - // * List of files that have changed. - // * @param array $types - // * Array with the css and or the js key. - if (module_exists('locale')) { - _advagg_locale_changed_files($files, $types); - } - // Keep track of what themes have been done. static $themes_done; if (!isset($themes_done)) { @@ -394,16 +265,17 @@ function advagg_advagg_changed_files(array $files, array $types) { return; } + $return = array(); foreach ($files as $filename => $meta_data) { // Only care about css files. - $ext = strtolower(pathinfo($filename, PATHINFO_EXTENSION)); - if ($ext !== 'css') { + $ext = pathinfo($filename, PATHINFO_EXTENSION); + if ($ext != 'css') { continue; } - advagg_advagg_scan_for_changes($filename, TRUE); + $return[$filename] = advagg_advagg_scan_for_changes($filename, TRUE); } - // Save error states and clear them. + // Save error states & clear them. $errors_before = drupal_static('form_set_error', array()); form_clear_error(); @@ -422,7 +294,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 ((!empty($theme_path)) && strpos($css_file, $theme_path) !== 0) { + if (strpos($css_file, $theme_path) !== 0) { continue; } $files_in_theme[] = $css_file; @@ -524,7 +396,7 @@ function advagg_advagg_changed_files(array $files, array $types) { */ function advagg_advagg_scan_for_changes($filename, $save_changes = FALSE) { // Skip if this file is not a css file. - $ext = strtolower(pathinfo($filename, PATHINFO_EXTENSION)); + $ext = pathinfo($filename, PATHINFO_EXTENSION); if ($ext !== 'css') { return FALSE; } @@ -542,7 +414,7 @@ function advagg_advagg_scan_for_changes($filename, $save_changes = FALSE) { return; } - $file_changed = array(); + $this_file_changed = FALSE; foreach ($adaptivethemes as $theme_name => $path) { // Set up some paths we use to get and save files. $path_to_responsive_css = drupal_get_path('theme', $theme_name) . '/css/'; @@ -569,58 +441,12 @@ function advagg_advagg_scan_for_changes($filename, $save_changes = FALSE) { } // See if anything has changed. - $changes = advagg_detect_subfile_changes($filename, $file_map[$filename], 'adaptivetheme', $save_changes); - if (!empty($changes)) { - $file_changed[$path] = $changes; + if (advagg_detect_subfile_changes($filename, $file_map[$filename], 'adaptivetheme', $save_changes)) { + $this_file_changed = TRUE; } } - return $file_changed; -} - -/** - * @} End of "addtogroup advagg_hooks". - */ - -/** - * 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 _advagg_locale_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 = strtolower(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; - _advagg_locale_js_alter($javascript); - } - } - $GLOBALS['language'] = $language_before; - } + return $this_file_changed; } /** diff --git a/sites/all/modules/contrib/advagg/advagg.api.php b/sites/all/modules/contrib/advagg/advagg.api.php index b0e68d0d91..f081ffe213 100644 --- a/sites/all/modules/contrib/advagg/advagg.api.php +++ b/sites/all/modules/contrib/advagg/advagg.api.php @@ -6,14 +6,8 @@ */ /** - * @defgroup advagg_hooks Advanced Aggregates Hooks - * + * @addtogroup hooks * @{ - * Hooks for modules to implement to extend or modify Advanced Aggregates. - * - * For more examples of use see most of the Advanced Agregrates sub modules. - * - * @see https://api.drupal.org/api/drupal/includes%21module.inc/group/hooks/7.x */ /** @@ -39,13 +33,10 @@ function hook_advagg_build_aggregate_plans_alter(array &$files, &$modified, $typ $temp_new_files = array(); $counter = 0; foreach ($files as $filename => $data) { - if ($filename) { - // This is the filename. - } $group = NULL; $every_page = NULL; foreach ($data['files'] as $fileinfo) { - // Grouped by group and every_page variables. + // Grouped by group & every_page variables. if (is_null($group)) { $group = $fileinfo['group']; } @@ -92,7 +83,7 @@ function hook_advagg_changed_files(array $files, array $types) { $return = array(); foreach ($files as $filename => $meta_data) { // Only care about js files. - $ext = strtolower(pathinfo($filename, PATHINFO_EXTENSION)); + $ext = pathinfo($filename, PATHINFO_EXTENSION); if ($ext !== 'js') { continue; } @@ -125,7 +116,7 @@ function hook_advagg_current_hooks_hash_array_alter(array &$aggregate_settings) * @param array $aggregate_settings * Array of settings. * @param array $other_parameters - * Array of containing $files and $type. + * Array of containing $files & $type. * * @see advagg_save_aggregate() * @see advagg_advagg_save_aggregate_alter() @@ -349,7 +340,8 @@ function hook_advagg_css_groups_alter(array &$css_groups, $preprocess_css) { } else { $diff = array_merge(array_diff_assoc($group['browsers'], $target['browsers']), array_diff_assoc($target['browsers'], $group['browsers'])); - if ($group['type'] != $target['type'] + // @ignore sniffer_whitespace_openbracketspacing_openingwhitespace + if ( $group['type'] != $target['type'] || $group['group'] != $target['group'] || $group['every_page'] != $target['every_page'] || $group['media'] != $target['media'] @@ -359,7 +351,8 @@ function hook_advagg_css_groups_alter(array &$css_groups, $preprocess_css) { ) { if (!empty($last_group)) { $diff = array_merge(array_diff_assoc($last_group['browsers'], $target['browsers']), array_diff_assoc($target['browsers'], $last_group['browsers'])); - if ($last_group['type'] != $target['type'] + // @ignore sniffer_whitespace_openbracketspacing_openingwhitespace + if ( $last_group['type'] != $target['type'] || $last_group['group'] != $target['group'] || $last_group['every_page'] != $target['every_page'] || $last_group['media'] != $target['media'] @@ -434,7 +427,7 @@ function hook_advagg_js_groups_alter(array &$js_groups, $preprocess_js) { } /** - * Allow other modules to modify $children and $elements before rendering. + * Allow other modules to modify $children & $elements before they are rendered. * * @param array $children * An array of children elements. @@ -469,7 +462,7 @@ function hook_advagg_modify_css_pre_render_alter(array &$children, array &$eleme module_load_include('inc', 'advagg_css_compress', 'advagg_css_compress.advagg'); if ($compressor == 2) { // Compress any inline CSS with YUI. - foreach ($children as &$values) { + foreach ($children as $key => &$values) { if (!empty($values['#value'])) { advagg_css_compress_yui_cssmin($values['#value']); } @@ -479,7 +472,7 @@ function hook_advagg_modify_css_pre_render_alter(array &$children, array &$eleme } /** - * Allow other modules to modify $children and $elements before rendering. + * Allow other modules to modify $children & $elements before they are rendered. * * @param array $children * An array of children elements. @@ -514,7 +507,7 @@ function hook_advagg_modify_js_pre_render_alter(array &$children, array &$elemen // Compress any inline JS. module_load_include('inc', 'advagg_js_compress', 'advagg_js_compress.advagg'); - foreach ($children as &$values) { + foreach ($children as $key => &$values) { if (!empty($values['#value'])) { $contents = $values['#value']; $filename = drupal_hash_base64($contents); @@ -584,9 +577,6 @@ function hook_advagg_context_alter(array &$original, array $aggregate_settings, */ function hook_advagg_removed_aggregates(array $kill_list) { foreach ($kill_list as $uri) { - if ($uri) { - // This is the uri. - } // Do something else. } } @@ -631,15 +621,9 @@ function hook_advagg_get_info_on_files_alter(array &$return, array $cached_data, } $limit_value = variable_get('advagg_ie_css_selector_limiter_value', ADVAGG_IE_CSS_SELECTOR_LIMITER_VALUE); list($css_path, $js_path) = advagg_get_root_files_dir(); - if ($js_path) { - // This is the js_path array. - } $parts_path = $css_path[1] . '/parts'; foreach ($return as $filename => &$info) { - if ($filename) { - // This is the filename. - } if (empty($info['fileext']) || $info['fileext'] !== 'css') { continue; } @@ -682,10 +666,6 @@ function hook_advagg_hooks_implemented_alter(array &$hooks, $all) { */ function hook_advagg_bundler_analysis_alter(array &$analysis) { foreach ($analysis as $filename => &$data) { - if ($filename) { - // This is the filename. - } - // This changes often; 604800 is 1 week. if ($data['changes'] > 10 && $data['mtime'] >= REQUEST_TIME - 604800) { // Modify the group hash so this doesn't end up in a big aggregate. @@ -696,5 +676,5 @@ function hook_advagg_bundler_analysis_alter(array &$analysis) { } /** - * @} End of "defgroup advagg_hooks". + * @} End of "addtogroup hooks". */ diff --git a/sites/all/modules/contrib/advagg/advagg.cache.inc b/sites/all/modules/contrib/advagg/advagg.cache.inc index 8d1d1586c0..90827684c6 100644 --- a/sites/all/modules/contrib/advagg/advagg.cache.inc +++ b/sites/all/modules/contrib/advagg/advagg.cache.inc @@ -7,6 +7,7 @@ * Functions used for clearing caches and killing files. */ +// Cache and file flushing. /** * Uses the database to scan CSS/JS files for changes. * @@ -43,30 +44,25 @@ function advagg_scan_for_changes() { 'content_hash', 'linecount', ); - $changed = array(); + $changed = FALSE; foreach ($keys_to_compare as $key) { if ($row[$key] != $info[$key]) { - $changed[] = $key . ' db:' . $row[$key] . ' file:' . $info[$key]; + $changed = TRUE; break; } } // Compare mtime if it is not zero. - if (empty($info['split']) && !empty($info['mtime'])) { - if (variable_get('advagg_strict_mtime_check', ADVAGG_STRICT_MTIME_CHECK) && $row['mtime'] != $info['mtime']) { - $changed[] = 'mtime db:' . $row['mtime'] . ' file:' . $info['mtime']; - } - elseif ($row['mtime'] < $info['mtime']) { - $changed[] = 'mtime db:' . $row['mtime'] . ' file:' . $info['mtime']; - } + if (!$changed && empty($info['split']) && !empty($info['mtime']) && $row['mtime'] != $info['mtime']) { + $changed = TRUE; } - if (empty($changed)) { + if (!$changed) { // Call hook_advagg_scan_for_changes(). $changes_array = module_invoke_all('advagg_scan_for_changes', $row['filename']); if (is_array($changes_array)) { foreach ($changes_array as $value) { if (!empty($value)) { - $changed[] = $value; + $changed = TRUE; break; } } @@ -74,8 +70,7 @@ function advagg_scan_for_changes() { } // If file has changed, add it to the array. - if (!empty($changed)) { - $info['changes'] = $changed; + if ($changed) { $files_that_have_changed[$row['filename']] = $info; } } @@ -90,27 +85,16 @@ function advagg_scan_for_changes() { * @return array * Array of files that have changed and caches flushed. */ -function advagg_push_new_changes(array $files = array()) { - $results = array(); - // Scan the file system for changes to CSS/JS files. - if (empty($files)) { - $files = advagg_scan_for_changes(); - if (variable_get('advagg_debug', ADVAGG_DEBUG) >= 2) { - watchdog('advagg-debug', 'Changes detected in
@files
.', array('@files' => print_r($files, TRUE)), WATCHDOG_DEBUG); - } - } - +function advagg_push_new_changes() { // Clear some static caches. drupal_static_reset('advagg_get_info_on_file'); drupal_static_reset('advagg_drupal_hash_base64'); drupal_static_reset('advagg_current_hooks_hash_array'); drupal_static_reset('advagg_get_current_hooks_hash'); - if (variable_get('advagg_debug', ADVAGG_DEBUG) >= 2) { - // Exception used to get a compact stack trace. - $e = new Exception(); - watchdog('advagg-debug', 'New changes called by:
@changes
', array('@changes' => print_r($e->getTraceAsString(), TRUE)), WATCHDOG_DEBUG); - } + // Scan the file system for changes to CSS/JS files. + $files = advagg_scan_for_changes(); + $results = array(); // If something changed, flush the correct caches so that change goes out. if (!empty($files)) { @@ -120,31 +104,17 @@ function advagg_push_new_changes(array $files = array()) { // Lookup the aggregates/cache ids that use this file. $cache_ids = advagg_get_aggregates_using_file($meta_data['filename_hash'], TRUE); $cache_hits = array(); - $ext = strtolower(pathinfo($filename, PATHINFO_EXTENSION)); + $ext = pathinfo($filename, PATHINFO_EXTENSION); $types[$ext] = TRUE; if (!empty($cache_ids)) { $cache_hits = cache_get_multiple($cache_ids, 'cache_advagg_info'); foreach ($cache_hits as $cid => $data) { - if (variable_get('advagg_debug', ADVAGG_DEBUG) >= 2) { - watchdog('advagg-debug', 'Clearing cache @cid.', array('@cid' => $cid), WATCHDOG_DEBUG); - } cache_clear_all($cid, 'cache_advagg_info', FALSE); } } - $changes = array(); - if (!empty($meta_data['changes'])) { - $changes = $meta_data['changes']; - unset($meta_data['changes']); - } - - $results[$filename] = array( - count($cache_ids), - count($cache_hits), - $changes, - ); - + $results[$filename] = array(count($cache_ids), count($cache_hits)); // Update database. advagg_insert_update_files(array($filename => $meta_data), $ext); } @@ -159,9 +129,6 @@ function advagg_push_new_changes(array $files = array()) { // Clear out the full aggregates cache. foreach ($types as $ext => $bool) { - if (variable_get('advagg_debug', ADVAGG_DEBUG) >= 2) { - watchdog('advagg-debug', 'Clearing cache advagg:@ext: in cache_advagg_aggregates.', array('@ext' => print_r($ext, TRUE)), WATCHDOG_DEBUG); - } cache_clear_all('advagg:' . $ext . ':', 'cache_advagg_aggregates', TRUE); } } @@ -217,7 +184,7 @@ function advagg_get_aggregates_using_file($filename_hash, $cid_only = FALSE) { */ function advagg_get_all_files(array $options = array()) { list($css_path, $js_path) = advagg_get_root_files_dir(); - $options += array('nomask' => '/(\.\.?|CVS|\.gz|\.br)$/'); + $options += array('nomask' => '/(\.\.?|CVS|\.gz)$/'); // Get a list of files. $css_files = file_scan_directory($css_path[0], '/.*/', $options); @@ -226,7 +193,7 @@ function advagg_get_all_files(array $options = array()) { } /** - * Scan CSS/JS advagg dir and remove that file if atime is grater than 30 days. + * Scan CSS/JS advagg dir & remove that file if atime is grater than 30 days. * * @return array * Array of files that got removed. @@ -263,65 +230,32 @@ function advagg_delete_files_if_stale(array $files) { } else { // Can not get data on file, remove it. - $kill_list[] = advagg_delete_file_by_uri($uri); + file_unmanaged_delete($uri); + if (file_exists($uri . '.gz')) { + file_unmanaged_delete($uri . '.gz'); + } + $kill_list[] = $uri; continue; } // Get atime of file. $atime = advagg_get_atime($aggregate_filenames_hash, $aggregate_contents_hash, $uri); if (empty($atime)) { - $kill_list[] = advagg_delete_file_by_uri($uri); + file_unmanaged_delete($uri); + if (file_exists($uri . '.gz')) { + file_unmanaged_delete($uri . '.gz'); + } + $kill_list[] = $uri; continue; } // Default stale file threshold is 30 days. if (REQUEST_TIME - $atime > variable_get('drupal_stale_file_threshold', 2592000)) { - $kill_list[] = advagg_delete_file_by_uri($uri); - continue; - } - - } - // Let other modules know about the removed files. - // Call hook_advagg_removed_aggregates(). - module_invoke_all('advagg_removed_aggregates', $kill_list); - return $kill_list; -} - -/** - * Scan CSS/JS advagg dir and remove that file if it is empty. - * - * @return array - * Array of files that got removed. - */ -function advagg_delete_empty_aggregates() { - list($css_files, $js_files) = advagg_get_all_files(); - $css_files = advagg_delete_files_if_empty($css_files); - $js_files = advagg_delete_files_if_empty($js_files); - return array($css_files, $js_files); -} - -/** - * Given an array of files remove that file if it is empty. - * - * @param array $files - * Array of files returned by file_scan_directory. - * - * @return array - * Array of files that got removed. - */ -function advagg_delete_files_if_empty(array $files) { - // Array used to record what files were deleted. - $kill_list = array(); - - foreach ($files as $uri => $file) { - // Ignore temp files. There's a separate process for cleaning those up. - if (strpos($uri, '/advagg_file_') !== FALSE) { - continue; - } - $size = filesize($uri); - if ($size === 0) { - $kill_list[] = advagg_delete_file_by_uri($uri); - continue; + file_unmanaged_delete($uri); + if (file_exists($uri . '.gz')) { + file_unmanaged_delete($uri . '.gz'); + } + $kill_list[] = $uri; } } // Let other modules know about the removed files. @@ -330,28 +264,6 @@ function advagg_delete_files_if_empty(array $files) { return $kill_list; } -/** - * Delete a file, and any compressed versions. - * - * @param string $uri - * URI of the file to delete. - * - * @return string - * The given URI. - */ -function advagg_delete_file_by_uri($uri) { - if (file_exists($uri)) { - file_unmanaged_delete($uri); - } - if (file_exists($uri . '.gz')) { - file_unmanaged_delete($uri . '.gz'); - } - if (file_exists($uri . '.br')) { - file_unmanaged_delete($uri . '.br'); - } - return $uri; -} - /** * Perform a cache_clear_all on all bins returned by advagg_flush_caches(TRUE). * @@ -380,10 +292,6 @@ 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) { @@ -453,7 +361,7 @@ function advagg_remove_missing_files_from_db() { $types = array(); foreach ($missing_files as $filename => $data) { // Setup this run. - $ext = strtolower(pathinfo($filename, PATHINFO_EXTENSION)); + $ext = pathinfo($filename, PATHINFO_EXTENSION); $advagg_files_del = 0; $advagg_aggregates_del = 0; $advagg_aggregates_versions_del = 0; @@ -494,17 +402,12 @@ function advagg_remove_missing_files_from_db() { ->execute(); // Add info to array. - if (!empty($advagg_files_del) - || !empty($advagg_aggregates_versions_del) - || !empty($advagg_aggregates_del) - ) { - $types[$ext] = TRUE; - $deleted[$filename] = array( - 'advagg_files' => $advagg_files_del, - 'advagg_aggregates_versions' => $advagg_aggregates_versions_del, - 'advagg_aggregates' => $advagg_aggregates_del, - ); - } + $types[$ext] = TRUE; + $deleted[$filename] = array( + 'advagg_files' => $advagg_files_del, + 'advagg_aggregates_versions' => $advagg_aggregates_versions_del, + 'advagg_aggregates' => $advagg_aggregates_del, + ); } } @@ -520,7 +423,7 @@ function advagg_remove_missing_files_from_db() { } /** - * Scan CSS/JS advagg dir and remove file if there is no associated db record. + * Scan CSS/JS advagg dir & remove file if there is no associated db record. * * @return array * Array of files that got removed. @@ -547,7 +450,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(); @@ -566,7 +469,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)); + $original_file = substr($file->uri, $start + strlen($parts_uri) + 1); $original_file = preg_replace('/(.\\d+\\.css)$/i', '.css', $original_file); if (file_exists($original_file)) { // Original file exists, do not delete. @@ -602,13 +505,14 @@ function advagg_delete_files_if_orphaned(array $files) { if (!empty($kill_list)) { foreach ($kill_list as $uri) { - advagg_delete_file_by_uri($uri); + if (file_exists($uri)) { + file_unmanaged_delete($uri); + if (file_exists($uri . '.gz')) { + file_unmanaged_delete($uri . '.gz'); + } + } } } - - // Let other modules know about the removed files. - // Call hook_advagg_removed_aggregates(). - module_invoke_all('advagg_removed_aggregates', $kill_list); return $kill_list; } @@ -627,7 +531,7 @@ function advagg_remove_old_unused_aggregates() { $query = db_select('advagg_aggregates_versions', 'aav') ->fields('aav', array('aggregate_filenames_hash')) ->groupBy('aav.aggregate_filenames_hash'); - // Create join and add in query comment. + // Create join & add in query comment. $query->leftjoin('advagg_aggregates', 'aa', 'aa.aggregate_filenames_hash=aav.aggregate_filenames_hash'); $query->isNull('aa.aggregate_filenames_hash'); $query->comment('Query called from ' . __FUNCTION__ . '()'); @@ -651,7 +555,7 @@ function advagg_remove_old_unused_aggregates() { $query = db_select('advagg_aggregates', 'aa') ->fields('aa', array('aggregate_filenames_hash')) ->groupBy('aa.aggregate_filenames_hash'); - // Create join and add in query comment. + // Create join & add in query comment. $query->leftjoin('advagg_aggregates_versions', 'aav', 'aa.aggregate_filenames_hash=aav.aggregate_filenames_hash'); $query->isNull('aav.aggregate_filenames_hash'); $query->comment('Query called from ' . __FUNCTION__ . '()'); @@ -686,197 +590,11 @@ 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') - ->condition('af.filesize', 0, '>') - ->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. * * @param string $filename - * Name of the file that is related to the subfiles. + * Name of the file that is related to teh subfiles. * @param array $subfiles * An array of files to check for changes. * @param string $keyname @@ -897,7 +615,7 @@ function advagg_detect_subfile_changes($filename, array $subfiles, $keyname, $sa $info[$keyname] = advagg_get_hash_settings($hash_id); } - $subfile_changed = array(); + $subfile_changed = FALSE; // Check every subfile seeing if they have changed. foreach ($subfiles as $subfile) { $current_file_info = $defaults = array( @@ -913,22 +631,22 @@ function advagg_detect_subfile_changes($filename, array $subfiles, $keyname, $sa // Get the current info on the file. if (file_exists($subfile)) { $current_file_info = array( - 'hash' => drupal_hash_base64((string) @advagg_file_get_contents($subfile)), + 'hash' => drupal_hash_base64(file_get_contents($subfile)), 'size' => filesize($subfile), 'mtime' => filemtime($subfile), ); } - // Set the info in case a save happens. + // Set the info incase a save happens. $info[$keyname][$subfile] = $current_file_info; // Check for any differences. $diff = array_diff_assoc($saved_file_info, $current_file_info); if (!empty($diff)) { - $subfile_changed[$subfile] = $diff; + $subfile_changed = TRUE; } } - if (!empty($subfile_changed) && $save_changes) { + if ($subfile_changed && $save_changes) { $cache_id = 'advagg:file:' . $info['filename_hash']; // Set static cache. diff --git a/sites/all/modules/contrib/advagg/advagg.developer-documentation.php b/sites/all/modules/contrib/advagg/advagg.developer-documentation.php deleted file mode 100644 index f49d8ac0fb..0000000000 --- a/sites/all/modules/contrib/advagg/advagg.developer-documentation.php +++ /dev/null @@ -1,41 +0,0 @@ - advagg_get_global_counter())); + return dt('Force the creation of all new aggregates by incrementing a global counter. Current value of counter: %value. This is useful if a CDN has cached an aggregate incorrectly as it will force new ones to be used even if nothing else has changed.', array('%value' => advagg_get_global_counter())); } } @@ -73,10 +68,6 @@ function advagg_drush_command() { return $items; } -/** - * @} End of "addtogroup 3rd_party_hooks". - */ - /** * Callback function for drush advagg-force-new-aggregates. * @@ -146,65 +137,29 @@ function drush_advagg_cron() { // Output results from running advagg_delete_stale_aggregates(). list($css_files, $js_files) = $output[0]; if (count($css_files) > 0 || count($js_files) > 0) { - drush_log(dt('All stale aggregates have been deleted. @css_count CSS files and @js_count JS files have been removed.', array( - '@css_count' => count($css_files), - '@js_count' => count($js_files), + drush_log(dt('All stale aggregates have been deleted. %css_count CSS files and %js_count JS files have been removed.', array( + '%css_count' => count($css_files), + '%js_count' => count($js_files), )), 'ok'); } else { 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[2])) { - drupal_set_message(dt('All source files where found, no database entries where pruned.'), 'ok'); + if (empty($output[1])) { + drush_log(dt('No missing files found and/or could be safely cleared out of the database.'), 'ok'); } else { - // 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'); + 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'); } // Output results from running advagg_remove_old_unused_aggregates(). - if (empty($output[3])) { - drupal_set_message(dt('No old and unused aggregates found. Nothing was deleted.'), 'ok'); - } - else { - // 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'); + if (empty($output[2])) { + drupal_set_message(t('No old and unused aggregates found. Nothing was deleted.'), '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'); + drupal_set_message(t('Some old and unused aggregates were found. A total of %count database entries were removed.', array('%count' => $output[2])), 'ok'); } } @@ -212,14 +167,6 @@ 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(); @@ -240,12 +187,11 @@ function drush_advagg_smart_cache_flush() { continue; } $ext = pathinfo($filename, PATHINFO_EXTENSION); - drush_log(dt('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. Trigger: @changes', array( - '@filename' => $filename, - '@db_usage' => $data[0], - '@db_count' => $data[1], - '@changes' => print_r($data[2], TRUE), - '@type' => $ext, + drush_log(dt('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' => $data[0], + '%db_count' => $data[1], + '%type' => $ext, )), 'ok'); } diff --git a/sites/all/modules/contrib/advagg/advagg.inc b/sites/all/modules/contrib/advagg/advagg.inc index 7db438b363..1707d2a5f7 100644 --- a/sites/all/modules/contrib/advagg/advagg.inc +++ b/sites/all/modules/contrib/advagg/advagg.inc @@ -7,10 +7,11 @@ * These functions are needed for cache misses. */ +// Database operations. /** * Insert/Update data in advagg tables. * - * Tables: advagg_files, advagg_aggregates, advagg_aggregates_versions. + * Tables: advagg_files, advagg_aggregates, & advagg_aggregates_versions. * * @param array $files * List of files in the aggregate as well as the aggregate name. @@ -74,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; @@ -132,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(); @@ -174,8 +175,6 @@ 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( @@ -187,246 +186,50 @@ function advagg_insert_update_files(array $files, $type) { 'mtime' => $file_meta_data['mtime'], 'linecount' => $file_meta_data['linecount'], ); - try { - // Check the file in the database. - if (empty($files_in_db[$filename])) { - // 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; + // Check the file in the database. + if (empty($files_in_db[$filename])) { + // Insert into database. + $record['changes'] = 1; - $return = db_merge('advagg_files') - ->key(array( - 'filename_hash' => $record['filename_hash'], + $return = db_merge('advagg_files') + ->key(array( + 'filename_hash' => $record['filename_hash'], )) - ->insertFields($record) - ->execute(); - if ($return) { - if (variable_get('advagg_debug', ADVAGG_DEBUG) >= 2) { - watchdog('advagg-debug', 'Inserting into db
@record
.', array('@record' => print_r($record, TRUE)), WATCHDOG_DEBUG); - } - $write_done = TRUE; - } - } - else { - // Take changes counter out of the diff equation. - $changes = $files_in_db[$filename]['changes']; - unset($files_in_db[$filename]['changes']); - // If not in strict mode, only use mtime if newer than the existing one. - if (!variable_get('advagg_strict_mtime_check', ADVAGG_STRICT_MTIME_CHECK)) { - // Make sure mtime only moves forward. - if ($record['mtime'] <= $files_in_db[$filename]['mtime']) { - $record['mtime'] = $files_in_db[$filename]['mtime']; - } - } - - // If something is different, update. - $diff = array_diff_assoc($record, $files_in_db[$filename]); - if (!empty($diff)) { - $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; - } - } - } + ->insertFields($record) + ->execute(); - $return = db_merge('advagg_files') - ->key(array( - 'filename_hash' => $diff['filename_hash'], - )) - ->fields($diff) - ->execute(); - if ($return) { - if (variable_get('advagg_debug', ADVAGG_DEBUG) >= 2) { - watchdog('advagg-debug', 'Updating db
@diff
.', array('@diff' => print_r($diff, TRUE)), WATCHDOG_DEBUG); - } - $write_done = TRUE; - } - } + if ($return) { + $write_done = TRUE; } } - 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)); - } - } - } - 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)) { - $files[$type][$filename] = strlen(gzencode($contents, 9, FORCE_GZIP)); - } 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(); + // Take changes counter out of the diff equation. + $changes = $files_in_db[$filename]['changes']; + unset($files_in_db[$filename]['changes']); - $file_aggregate = array($filename => array()); - list($contents) = advagg_get_js_aggregate_contents($file_aggregate, $aggregate_settings); + // If something is different, update. + $diff = array_diff_assoc($record, $files_in_db[$filename]); + if (!empty($diff)) { + $diff['changes'] = $changes + 1; + $diff['filename_hash'] = $record['filename_hash']; - // See if the js file starts with "use strict";. - // Trim the JS down to 24kb. - $length = variable_get('advagg_js_header_length', ADVAGG_JS_HEADER_LENGTH); - $header = advagg_get_js_header($contents, $length); - - // Look for the string. - $use_strict = stripos($header, '"use strict";'); - $strict_js = FALSE; - if ($use_strict === FALSE) { - $use_strict = stripos($header, "'use strict';"); - } - if ($use_strict !== FALSE) { - if ($use_strict == 0) { - $strict_js = TRUE; - } - else { - // Get all text before "use strict";. - $substr = substr($header, 0, $use_strict); - // Check if there are any comments. - $single_line_comment = strpos($substr, '//'); - $multi_line_comment = strpos($substr, '/*'); - $in_function = strpos($substr, '{'); - if ($single_line_comment !== FALSE || $multi_line_comment !== FALSE) { - // Remove js comments and try again. - advagg_remove_js_comments($header); - - // Look for the string. - $use_strict = stripos($header, '"use strict";'); - if ($use_strict === FALSE) { - $use_strict = stripos($header, "'use strict';"); - } - // Get all text before "use strict"; with comments removed. - $substr = substr($header, 0, $use_strict); - // Check if there is a function before use strict. - $in_function = strpos($substr, '{'); - } - if ($in_function === FALSE) { - $strict_js = TRUE; + $return = db_merge('advagg_files') + ->key(array( + 'filename_hash' => $diff['filename_hash'], + )) + ->updateFields($diff) + ->execute(); + if ($return) { + $write_done = TRUE; } } } - - $files[$filename] = $strict_js; - } - return $files[$filename]; -} - -/** - * Read only the first 8192 bytes to get the file header. - * - * @param string $content - * JS string to cut. - * @param int $length - * The number of bytes to grab. See advagg_js_header_length variable. - * - * @return string - * The shortened JS string. - */ -function advagg_get_js_header($content, $length) { - $content = trim($content); - // Only grab the first X bytes. - if (function_exists('mb_strcut')) { - $header = mb_strcut($content, 0, $length); - } - else { - $header = substr($content, 0, $length); } - - return $header; -} - -/** - * Remove comments from JavaScript. - * - * @param string $content - * JS string to minify. - */ -function advagg_remove_js_comments(&$content) { - // Remove comments. - $content = preg_replace('/(?:(?:\/\*(?:[^*]|(?:\*+[^*\/]))*\*+\/)|(?:(?@finfo', array( - '@finfo' => var_export($info, TRUE), - )); - } + $files_info_filenames[] = $info['data']; } // Get filesystem data. $files_info = advagg_get_info_on_files($files_info_filenames); foreach ($files_with_meta_data as $info) { - // Skip if not a string or key doesn't exist. - if (!is_string($info['data']) || !array_key_exists($info['data'], $files_info)) { - continue; - } - $filename = $info['data']; $info += $files_info[$filename]; // Skip if file doesn't exist. @@ -542,11 +333,12 @@ function &advagg_load_files_info_into_static_cache(array $files) { // Get the static cache of this data. $cached_data = &drupal_static('advagg_get_info_on_file'); - // Get the statically cached data for all the given files. + // Get that staticly cached data for all the given files. $cache_ids = array(); foreach ($files as $file) { $cache_id = 'advagg:file:' . advagg_drupal_hash_base64($file); - if (!empty($cached_data) + // @ignore sniffer_whitespace_openbracketspacing_openingwhitespace + if ( !empty($cached_data) && !empty($cached_data[$cache_id]) ) { // Make sure the cache_id is included. @@ -585,8 +377,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', array()); - if (!array_key_exists($file, $cached_data)) { + $cached_data = &drupal_static('advagg_drupal_hash_base64'); + if (!isset($cached_data[$file])) { $cached_data[$file] = drupal_hash_base64($file); } return $cached_data[$file]; @@ -623,7 +415,8 @@ function advagg_get_info_on_files(array $files, $bypass_cache = FALSE, $run_alte $filename_hash = advagg_drupal_hash_base64($file); $cache_id = 'advagg:file:' . $filename_hash; // If we are not bypassing the cache add cached data. - if ($bypass_cache == FALSE + // @ignore sniffer_whitespace_openbracketspacing_openingwhitespace + if ( $bypass_cache == FALSE && is_array($cached_data) && array_key_exists($cache_id, $cached_data) ) { @@ -635,11 +428,11 @@ 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) || is_dir($file)) { + if (!file_exists($file)) { if (isset($cached_data[$cache_id])) { cache_clear_all($cache_id, 'cache_advagg_info', FALSE); } - // Return filename_hash and data. Empty values for the other keys. + // Return filename_hash & data. Empty values for the other keys. $return[$file] = array( 'filesize' => 0, 'mtime' => 0, @@ -654,11 +447,11 @@ function advagg_get_info_on_files(array $files, $bypass_cache = FALSE, $run_alte } // Get the file contents. - $file_contents = (string) @advagg_file_get_contents($file); + $file_contents = file_get_contents($file); - $ext = strtolower(pathinfo($file, PATHINFO_EXTENSION)); + $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) @@ -673,7 +466,8 @@ function advagg_get_info_on_files(array $files, $bypass_cache = FALSE, $run_alte if ($ext === 'css') { // Get the number of selectors. - $linecount = advagg_count_css_selectors($file_contents); + // http://stackoverflow.com/a/12567381/125684 + $linecount = preg_match_all('/\{.+?\}|,/s', $file_contents, $matched); } else { // Get the number of lines. @@ -682,7 +476,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' => (int) @filesize($file), + 'filesize' => filesize($file), 'mtime' => @filemtime($file), 'filename_hash' => $filename_hash, 'content_hash' => drupal_hash_base64($file_contents), @@ -705,7 +499,8 @@ function advagg_get_info_on_files(array $files, $bypass_cache = FALSE, $run_alte foreach ($return as $info) { // If no cache is empty add/update the cached entry. // Update the cache if it is new or something changed. - if (empty($info['#no_cache']) + // @ignore sniffer_whitespace_openbracketspacing_openingwhitespace + if ( empty($info['#no_cache']) && !empty($info['cache_id']) && (empty($cached_data[$info['cache_id']]) || $info !== $cached_data[$info['cache_id']]) ) { @@ -798,18 +593,17 @@ function advagg_clearstatcache($filename = NULL) { } } +// Modify CSS/JS arrays. /** * Group the CSS/JS into the biggest buckets possible. * * @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, $type) { +function advagg_generate_groups(array $files_to_aggregate) { $groups = array(); $count = 0; $location = 0; @@ -819,30 +613,16 @@ function advagg_generate_groups(array $files_to_aggregate, $type) { $async = ''; $cache = ''; $scope = ''; - $use_strict = 0; $browsers = array(); $selector_count = 0; // Get CSS limit value. $limit_value = variable_get('advagg_ie_css_selector_limiter_value', ADVAGG_IE_CSS_SELECTOR_LIMITER_VALUE); - if (variable_get('advagg_ie_css_selector_limiter', ADVAGG_IE_CSS_SELECTOR_LIMITER) - || variable_get('advagg_resource_hints_dns_prefetch', ADVAGG_RESOURCE_HINTS_DNS_PREFETCH) - || variable_get('advagg_resource_hints_preconnect', ADVAGG_RESOURCE_HINTS_PRECONNECT) - || variable_get('advagg_resource_hints_preload', ADVAGG_RESOURCE_HINTS_PRELOAD) - ) { - $filenames = array(); + if (variable_get('advagg_ie_css_selector_limiter', ADVAGG_IE_CSS_SELECTOR_LIMITER)) { foreach ($files_to_aggregate as $data) { foreach ($data as $values) { foreach ($values['items'] as $file_info) { - if (!empty($file_info['data']) && is_string($file_info['data'])) { - $filenames[] = $file_info['data']; - } - else { - watchdog('advagg', 'Bad data key. File info: @finfo Group info: @ginfo', array( - '@finfo' => var_export($file_info, TRUE), - '@ginfo' => var_export($values, TRUE), - )); - } + $filenames[] = $file_info['data']; } } } @@ -851,21 +631,6 @@ function advagg_generate_groups(array $files_to_aggregate, $type) { $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) { @@ -877,10 +642,9 @@ function advagg_generate_groups(array $files_to_aggregate, $type) { // changed from the previous run of this loop. $changed = FALSE; $ext = isset($file_info['fileext']) ? $file_info['fileext'] : pathinfo($file_info['data'], PATHINFO_EXTENSION); - $ext = strtolower($ext); 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']) @@ -909,6 +673,7 @@ function advagg_generate_groups(array $files_to_aggregate, $type) { $media = ''; } } + if (isset($file_info['browsers'])) { // Browsers changed. $diff = array_merge(array_diff_assoc($file_info['browsers'], $browsers), array_diff_assoc($browsers, $file_info['browsers'])); @@ -923,16 +688,6 @@ function advagg_generate_groups(array $files_to_aggregate, $type) { $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; @@ -974,11 +729,7 @@ function advagg_generate_groups(array $files_to_aggregate, $type) { $scope = ''; } - if (variable_get('advagg_ie_css_selector_limiter', ADVAGG_IE_CSS_SELECTOR_LIMITER) - && array_key_exists('data', $file_info) - && is_string($file_info['data']) - && array_key_exists($file_info['data'], $files_info) - ) { + if (variable_get('advagg_ie_css_selector_limiter', ADVAGG_IE_CSS_SELECTOR_LIMITER)) { $file_info += $files_info[$file_info['data']]; // Prevent CSS rules exceeding 4095 due to limits with IE9 and below. if ($ext === 'css') { @@ -995,37 +746,6 @@ function advagg_generate_groups(array $files_to_aggregate, $type) { } } - // Merge in dns_prefetch. - if ((variable_get('advagg_resource_hints_dns_prefetch', ADVAGG_RESOURCE_HINTS_DNS_PREFETCH) - || variable_get('advagg_resource_hints_preconnect', ADVAGG_RESOURCE_HINTS_PRECONNECT)) - && isset($files_info[$file_info['data']]['dns_prefetch']) - ) { - if (!isset($file_info['dns_prefetch'])) { - $file_info['dns_prefetch'] = array(); - } - if (!empty($file_info['dns_prefetch']) && is_string($file_info['dns_prefetch'])) { - $temp = $file_info['dns_prefetch']; - unset($file_info['dns_prefetch']); - $file_info['dns_prefetch'] = array($temp); - } - $file_info['dns_prefetch'] = array_filter(array_unique(array_merge($file_info['dns_prefetch'], $files_info[$file_info['data']]['dns_prefetch']))); - } - - // Merge in preload. - if (variable_get('advagg_resource_hints_preload', ADVAGG_RESOURCE_HINTS_PRELOAD) - && isset($files_info[$file_info['data']]['preload']) - ) { - if (!isset($file_info['preload'])) { - $file_info['preload'] = array(); - } - if (!empty($file_info['preload']) && is_string($file_info['preload'])) { - $temp = $file_info['preload']; - unset($file_info['preload']); - $file_info['preload'] = array($temp); - } - $file_info['preload'] = array_filter(array_unique(array_merge($file_info['preload'], $files_info[$file_info['data']]['preload']))); - } - // If one of the above options changed, it needs to be in a different // aggregate. if (!empty($parts)) { @@ -1054,278 +774,193 @@ function advagg_generate_groups(array $files_to_aggregate, $type) { * * @param array $file_info * File info array from advagg_get_info_on_file(). - * @param string $file_contents - * CSS file contents. * * @return array - * Array with advagg_get_info_on_file data and split data. + * array array with advagg_get_info_on_file data & split data. */ -function advagg_split_css_file(array $file_info, $file_contents = '') { +function advagg_split_css_file(array $file_info) { // Make advagg_parse_media_blocks() available. module_load_include('inc', 'advagg', 'advagg.missing'); // Get the CSS file and break up by media queries. - if (empty($file_contents)) { - $file_contents = (string) @advagg_file_get_contents($file_info['data']); - } + $file_contents = file_get_contents($file_info['data']); $media_blocks = advagg_parse_media_blocks($file_contents); - // Get the advagg_ie_css_selector_limiter_value. - $selector_limit = (int) max(variable_get('advagg_ie_css_selector_limiter_value', ADVAGG_IE_CSS_SELECTOR_LIMITER_VALUE), 100); - - // Group media queries together. + // Get 98% of the advagg_ie_css_selector_limiter_value; usually 4013. + $selector_split_value = (int) max(floor(variable_get('advagg_ie_css_selector_limiter_value', ADVAGG_IE_CSS_SELECTOR_LIMITER_VALUE) * 0.98), 100); $part_selector_count = 0; + $major_chunks = array(); $counter = 0; - $values = array(); + // Group media queries together. foreach ($media_blocks as $media_block) { + $matched = array(); // Get the number of selectors. - $selector_count = advagg_count_css_selectors($media_block); - - // This chunk is bigger than $selector_limit. It needs to be split. - if ($selector_count > $selector_limit) { - $inner_selector_count = 0; - // Split css string. - list($media_query, $split_css_strings) = advagg_split_css_string($media_block, $selector_limit); - foreach ($split_css_strings as $split_css_strings) { - $counter_changed = FALSE; - if (empty($split_css_strings)) { - continue; - } - - // Make sure selector count doesn't go over selector limit. - $inner_selector_count = advagg_count_css_selectors($split_css_strings); - $part_selector_count += $inner_selector_count; - if ($part_selector_count > $selector_limit) { - if (!empty($values[$counter])) { - ++$counter; - } - $counter_changed = TRUE; - $part_selector_count = $inner_selector_count; - } - - // Add to output array. - if (isset($values[$counter])) { - if (!empty($media_query)) { - $values[$counter] .= "\n$media_query { $split_css_strings } "; - } - else { - $values[$counter] .= "$split_css_strings"; - } - } - else { - if (!empty($media_query)) { - $values[$counter] = "$media_query { $split_css_strings } "; - } - else { - $values[$counter] = $split_css_strings; - } - } - } - // Add to current selector counter and go to the next value. - if (!$counter_changed) { - $part_selector_count += $inner_selector_count; - } - continue; - } - + // http://stackoverflow.com/a/12567381/125684 + $selector_count = preg_match_all('/\{.+?\}|,/s', $media_block, $matched); $part_selector_count += $selector_count; - if ($part_selector_count > $selector_limit) { - if (!empty($values[$counter])) { + + if ($part_selector_count > $selector_split_value) { + if (isset($major_chunks[$counter])) { ++$counter; + $major_chunks[$counter] = $media_block; + } + else { + $major_chunks[$counter] = $media_block; } - $values[$counter] = $media_block; - $part_selector_count = $selector_count; + ++$counter; + $part_selector_count = 0; } else { - if (isset($values[$counter])) { - $values[$counter] .= "\n$media_block"; + if (isset($major_chunks[$counter])) { + $major_chunks[$counter] .= "\n" . $media_block; } else { - $values[$counter] = $media_block; + $major_chunks[$counter] = $media_block; } } } - // Save data. $parts = array(); - $overall_counter = 0; - foreach ($values as $key => $value) { + $overall_split = 0; + $split_at = $selector_split_value; + $chunk_split_value = (int) variable_get('advagg_ie_css_selector_limiter_value', ADVAGG_IE_CSS_SELECTOR_LIMITER_VALUE) - $selector_split_value - 1; + foreach ($major_chunks as $chunk_key => $chunks) { $last_chunk = FALSE; $file_info['split_last_part'] = FALSE; - if (count($values) - 1 == $key) { + if (count($major_chunks) - 1 == $chunk_key) { $last_chunk = TRUE; } - if ($last_chunk) { - $file_info['split_last_part'] = TRUE; - } // Get the number of selectors. - $selector_count = advagg_count_css_selectors($value); - $overall_counter += $selector_count; - - // Save file. - $subfile = advagg_create_subfile($value, $overall_counter, $file_info); - if (empty($subfile)) { - // Something 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(); + $matches = array(); + $selector_count = preg_match_all('/\{.+?\}|,/s', $chunks, $matches); + + // Pass through if selector count is low. + if ($selector_count < $selector_split_value) { + $overall_split += $selector_count; + if ($last_chunk) { + $file_info['split_last_part'] = TRUE; + } + $parts[] = advagg_create_subfile($chunks, $overall_split, $file_info); + continue; } - $parts[] = $subfile; - } - return $parts; -} -/** - * Count the number of selectors inside of a CSS string. - * - * @param string $css_string - * CSS string. - * - * @return int - * The number of CSS selectors. - */ -function advagg_count_css_selectors($css_string) { - return substr_count($css_string, ',') + substr_count($css_string, '{') - substr_count($css_string, '@media'); -} - -/** - * Given a css string it will split it if it's over the selector limit. - * - * @param string $css_string - * CSS string. - * @param int $selector_limit - * How many selectors can be grouped together. - * - * @return array - * Array that contains the $media_query and the $css_array. - */ -function advagg_split_css_string($css_string, $selector_limit) { - // See if this css string is wrapped in a @media statement. - $media_query = ''; - $media_query_pos = strpos($css_string, '@media'); - if ($media_query_pos !== FALSE) { - // Get the opening bracket. - $open_bracket_pos = strpos($css_string, "{", $media_query_pos); - // Skip if there is a syntax error. - if ($open_bracket_pos === FALSE) { - return array(); + $media_query = ''; + if (strpos($chunks, '@media') !== FALSE) { + $media_query_pos = strpos($chunks, '{'); + $media_query = substr($chunks, 0, $media_query_pos); + $chunks = substr($chunks, $media_query_pos + 1); } - $media_query = substr($css_string, $media_query_pos, $open_bracket_pos - $media_query_pos); - $css_string_inside = substr($css_string, $open_bracket_pos + 1); - } - else { - $css_string_inside = $css_string; - } - // Split CSS into selector chunks. - $split = preg_split('/(\{.+?\}|,)/si', $css_string_inside, -1, PREG_SPLIT_DELIM_CAPTURE); + // Split CSS into selector chunks. + $split = preg_split('/(\{.+?\}|,)/si', $chunks, -1, PREG_SPLIT_DELIM_CAPTURE); - $new_css_chunk = array(0 => ''); - $selector_chunk_counter = 0; - $counter = 0; - // Have the key value be the running selector count and put split array semi - // back together. - foreach ($split as $value) { - $new_css_chunk[$counter] .= $value; - if (strpos($value, '}') === FALSE) { + // Setup and handle media queries. + $new_css_chunk = array(0 => ''); + $selector_chunk_counter = 0; + $counter = 0; + if (!empty($media_query)) { + $new_css_chunk[0] = $media_query . '{'; + $new_css_chunk[1] = ''; ++$selector_chunk_counter; + ++$counter; } - else { - if ($counter + 1 < $selector_chunk_counter) { - $selector_chunk_counter += ($counter - $selector_chunk_counter + 1) / 2; + // Have the key value be the running selector count and put split array semi + // back together. + foreach ($split as $value) { + $new_css_chunk[$counter] .= $value; + if (strpos($value, '}') === FALSE) { + ++$selector_chunk_counter; } - $counter = $selector_chunk_counter; - if (!isset($new_css_chunk[$counter])) { - $new_css_chunk[$counter] = ''; + else { + if ($counter + 1 < $selector_chunk_counter) { + $selector_chunk_counter += ($counter - $selector_chunk_counter + 1) / 2; + } + $counter = $selector_chunk_counter; + if (!isset($new_css_chunk[$counter])) { + $new_css_chunk[$counter] = ''; + } } } - } - // Generate output array in this function. - $css_array = array(); - $keys = array_keys($new_css_chunk); - $counter = 0; - $chunk_counter = 0; - foreach (array_keys($keys) as $key) { - // Get out of loop if at the end of the array. - if (!isset($keys[$key + 1])) { - break; - } + // Group selectors. + $first = TRUE; + while (!empty($new_css_chunk)) { + // Find where to split the array. + $string_to_write = ''; + while (array_key_exists($split_at, $new_css_chunk) === FALSE) { + --$split_at; + } - // Get values, keys and counts. - $this_value = $new_css_chunk[$keys[$key]]; - $this_key = $keys[$key]; - $next_key = $keys[$key + 1]; - $this_selector_count = $next_key - $this_key; - - // Single rule is bigger than the selector limit. - if ($this_selector_count > $selector_limit) { - // Get css rules for these selectors. - $open_bracket_pos = strpos($this_value, "{"); - $css_rule = ' ' . substr($this_value, $open_bracket_pos); - - // Split on selectors. - $split = preg_split('/(\,)/si', $this_value, NULL, PREG_SPLIT_OFFSET_CAPTURE); - $index = 0; - $counter = 0; - while (isset($split[$index][1])) { - // Get starting and ending positions of the selectors given the selector - // limit. - $next_index = $index + $selector_limit - 1; - $start = $split[$index][1]; - if (isset($split[$next_index][1])) { - $end = $split[$next_index][1]; + // Combine parts of the css so that it can be saved to disk. + foreach ($new_css_chunk as $key => $value) { + if ($key !== $split_at) { + // Move this css row to the $string_to_write variable. + $string_to_write .= $value; + unset($new_css_chunk[$key]); } + // We are at the split point. else { - // Last one. - $temp = end($split); - $split_key = key($split); - $counter = $split_key % $selector_limit; - $end_open_bracket_pos = (int) strpos($temp[0], "{"); - $end = $temp[1] + $end_open_bracket_pos; + // Get the number of selectors in this chunk. + $matched = array(); + $chunk_selector_count = preg_match_all('/\{.+?\}|,/s', $new_css_chunk[$key], $matched); + if ($chunk_selector_count < $chunk_split_value) { + // The number of selectors at this point is below the threshold; + // move this chunk to the write variable and break out of the loop. + $string_to_write .= $value; + unset($new_css_chunk[$key]); + $overall_split = $split_at; + $split_at += $selector_split_value; + } + else { + // The number of selectors with this chunk included is over the + // threshold; do not move it. Change split position so the next + // iteration of the while loop ends at the correct spot. Because we + // skip unset here, this chunk will start the next part file. + $overall_split = $split_at; + $split_at += $selector_split_value - $chunk_selector_count; + } + break; } + } - // Extract substr. - $sub_this_value = substr($this_value, $start, $end - $start - 1) . $css_rule; - - // Save substr. - ++$chunk_counter; - $key_output = $selector_limit; - if (!empty($counter)) { - $key_output = $selector_limit - $counter; + // Handle media queries. + if (!empty($media_query)) { + // See if brackets need a new line. + if (strpos($string_to_write, "\n") === 0) { + $open_bracket = '{'; } - $css_array["$chunk_counter $key_output"] = ''; - - if (!isset($css_array[$chunk_counter])) { - $css_array[$chunk_counter] = $sub_this_value; + else { + $open_bracket = "{\n"; + } + if (strrpos($string_to_write, "\n") === strlen($string_to_write)) { + $close_bracket = '}'; } else { - $css_array[$chunk_counter] .= $sub_this_value; + $close_bracket = "\n}"; } - // Move counter. - $index = $next_index; + // Fix syntax around media queries. + if ($first) { + $string_to_write .= $close_bracket; + } + elseif (empty($new_css_chunk)) { + $string_to_write = $media_query . $open_bracket . $string_to_write; + } + else { + $string_to_write = $media_query . $open_bracket . $string_to_write . $close_bracket; + } } - continue; - } - - $counter += $this_selector_count; - if ($counter > $selector_limit) { - $key_output = $counter - $this_selector_count; - $css_array["$chunk_counter $key_output"] = ''; - $counter = $next_key - $this_key; - ++$chunk_counter; - } - if (!isset($css_array[$chunk_counter])) { - $css_array[$chunk_counter] = $this_value; - } - else { - $css_array[$chunk_counter] .= $this_value; + // Handle the last split part. + if (empty($new_css_chunk) && $last_chunk) { + $file_info['split_last_part'] = TRUE; + } + // Write the data. + $parts[] = advagg_create_subfile($string_to_write, $overall_split, $file_info); + $first = FALSE; } } - - // Group into sets smaller than $selector_limit. - return array($media_query, $css_array); + return $parts; } /** @@ -1339,7 +974,7 @@ function advagg_split_css_string($css_string, $selector_limit) { * File info array from advagg_get_info_on_file(). * * @return array - * Array with advagg_get_info_on_file data and split data; FALSE on failure. + * array with advagg_get_info_on_file data & split data. */ function advagg_create_subfile($css, $overall_split, array $file_info) { static $parts_uri; @@ -1358,9 +993,6 @@ 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); @@ -1369,7 +1001,7 @@ function advagg_create_subfile($css, $overall_split, array $file_info) { $scheme = file_uri_scheme($new_filename); if ($scheme) { $wrapper = file_stream_wrapper_get_instance_by_scheme($scheme); - if ($wrapper && method_exists($wrapper, 'getDirectoryPath')) { + if ($wrapper) { // Use the wrappers directory path. $new_filename = $wrapper->getDirectoryPath() . '/' . file_uri_target($new_filename); } @@ -1382,19 +1014,18 @@ function advagg_create_subfile($css, $overall_split, array $file_info) { $part_uri = $parts_uri . '/' . $new_filename; $dirname = drupal_dirname($part_uri); file_prepare_directory($dirname, FILE_CREATE_DIRECTORY); - $filename_path = (advagg_s3fs_evaluate_no_rewrite_cssjs(FALSE)) ? $parts_uri : $parts_path; // Get info on the file that was just created. - $part = advagg_get_info_on_file($filename_path . '/' . $new_filename, TRUE) + $file_info; + $part = advagg_get_info_on_file($parts_path . '/' . $new_filename) + $file_info; $part['split'] = TRUE; $part['split_location'] = $overall_split; $part['split_original'] = $file_info['data']; // Overwrite/create file if hash doesn't match. $hash = drupal_hash_base64($css); - if ($part['content_hash'] !== $hash) { + if ($part['content_hash'] != $hash) { advagg_save_data($part_uri, $css, TRUE); - $part = advagg_get_info_on_file($filename_path . '/' . $new_filename, TRUE) + $file_info; + $part = advagg_get_info_on_file($parts_path . '/' . $new_filename, TRUE) + $file_info; $part['split'] = TRUE; $part['split_location'] = $overall_split; $part['split_original'] = $file_info['data']; @@ -1420,7 +1051,7 @@ function advagg_build_aggregate_plans(array $files_to_aggregate, $type) { } // Place into biggest grouping possible. - $groups = advagg_generate_groups($files_to_aggregate, $type); + $groups = advagg_generate_groups($files_to_aggregate); // Get filenames. $files = advagg_generate_filenames($groups, $type); @@ -1466,46 +1097,23 @@ function advagg_build_aggregate_plans(array $files_to_aggregate, $type) { } } + // Get onload. $onload = array(); - $onerror = array(); - $attributes = array(); - $onloadcss = array(); - foreach ($values['files'] as &$items) { - // Get onload. + foreach ($values['files'] as $items) { if (!empty($items['onload'])) { $onload[] = $items['onload']; } - // Get attributes onload. - if (!empty($items['attributes']['onload'])) { - $onload[] = $items['attributes']['onload']; - unset($items['attributes']['onload']); - } - // Get onerror. + } + $onload = implode(';', array_unique(array_filter($onload))); + + // Get onerror. + $onerror = array(); + foreach ($values['files'] as $items) { if (!empty($items['onerror'])) { $onload[] = $items['onerror']; } - // Get attributes onerror. - if (!empty($items['attributes']['onerror'])) { - $onload[] = $items['attributes']['onerror']; - unset($items['attributes']['onerror']); - } - // Get attributes onloadCSS. - if (!empty($items['onloadCSS'])) { - $onloadcss[] = $items['onloadCSS']; - } - // Get attributes onloadCSS. - if (!empty($items['attributes']['onloadCSS'])) { - $onloadcss[] = $items['attributes']['onloadCSS']; - unset($items['attributes']['onloadCSS']); - } - // Get attributes. - if (!empty($items['attributes'])) { - $attributes += $items['attributes']; - } } - $onload = implode(';', array_unique(array_filter($onload))); $onerror = implode(';', array_unique(array_filter($onerror))); - $onloadcss = implode(';', array_unique(array_filter($onloadcss))); $first = reset($values['files']); if (!empty($mixed_media)) { @@ -1513,7 +1121,7 @@ function advagg_build_aggregate_plans(array $files_to_aggregate, $type) { } $url = ($type === 'css') ? $css_path[0] : $js_path[0]; $path = ($type === 'css') ? $css_path[1] : $js_path[1]; - $plans[$agg_filename] = array( + $plans[] = array( 'data' => $url . '/' . $agg_filename, 'media' => isset($first['media']) ? $first['media'] : '', 'defer' => isset($first['defer']) ? $first['defer'] : '', @@ -1526,23 +1134,14 @@ function advagg_build_aggregate_plans(array $files_to_aggregate, $type) { 'items' => $values, 'filepath' => $path . '/' . $agg_filename, 'filename' => $agg_filename, - 'attributes' => $attributes, ); - if (!empty($onloadcss)) { - $plans[$agg_filename]['attributes']['onloadCSS'] = $onloadcss; - } } - $plans = array_values($plans); // Create the aggregate files. if (variable_get('advagg_pregenerate_aggregate_files', ADVAGG_PREGENERATE_AGGREGATE_FILES)) { 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, $type); - return $plans; } @@ -1565,9 +1164,10 @@ function advagg_create_aggregate_files(array $plans, $type) { } // If the httprl module exists and we want to use it. - if (module_exists('httprl') + // @ignore sniffer_whitespace_openbracketspacing_openingwhitespace + if ( module_exists('httprl') && variable_get('advagg_use_httprl', ADVAGG_USE_HTTPRL) - && (is_callable('httprl_is_background_callback_capable') + && ( is_callable('httprl_is_background_callback_capable') && httprl_is_background_callback_capable() || !is_callable('httprl_is_background_callback_capable') ) @@ -1627,64 +1227,27 @@ 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 = TRUE, array $aggregate_settings = array(), $contents = '') { - $old_base_path = $GLOBALS['base_path']; - +function advagg_load_css_stylesheet($file, $optimize, array $aggregate_settings = array()) { // 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, TRUE, $contents); - - // Resolve public:// if needed. - if (!advagg_is_external($file) && file_uri_scheme($file)) { - $file = advagg_get_relative_path($file); - } + $contents = advagg_load_stylesheet($file, $optimize); // Get the parent directory of this file, relative to the Drupal root. $css_base_url = substr($file, 0, strrpos($file, '/')); - - // Handle split css files. - list($css_path) = advagg_get_root_files_dir(); - $parts_path = ((advagg_s3fs_evaluate_no_rewrite_cssjs(FALSE)) ? $css_path[0] : $css_path[1]) . '/parts/'; - $url_parts = strpos($css_base_url, $parts_path); + $url_parts = explode('advagg_css/parts/', $css_base_url); // 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 ($url_parts !== FALSE) { - $css_base_url = substr($css_base_url, $url_parts + strlen($parts_path)); + // 'url( ... )' bits. + if (count($url_parts) === 2) { + $css_base_url = $url_parts[1]; } - - // Replace the old base path with the one that was passed in. - if (advagg_is_external($css_base_url) || variable_get('advagg_skip_file_create_url_inside_css', ADVAGG_SKIP_FILE_CREATE_URL_INSIDE_CSS)) { - $pos = strpos($css_base_url, $old_base_path); - if ($pos !== FALSE) { - $parsed_url = parse_url($css_base_url); - if (!empty($parsed_url['path'])) { - // Remove any double slash in path. - $parsed_url['path'] = str_replace('//', '/', $parsed_url['path']); - - // Get newly recalculated position. - $pos = strpos($parsed_url['path'], $old_base_path); - - // Replace. - if (strpos($parsed_url['path'], '/') !== 0 && $old_base_path === '/') { - // Special case if going to a subdir. - $parsed_url['path'] = $GLOBALS['base_path'] . $parsed_url['path']; - } - else { - $parsed_url['path'] = substr_replace($parsed_url['path'], $GLOBALS['base_path'], $pos, strlen($old_base_path)); - } - - $css_base_url = advagg_glue_url($parsed_url); - } - } - } - _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*+\)%i', '_advagg_build_css_path', $contents); + $contents = preg_replace_callback('%url\(\s*+[\'"]?+(?![a-z]++:|/|\#|\%23+)([^\'"()\s]++)[\'"]?+\s*+\)%i', '_advagg_build_css_path', $contents); // Change context back. advagg_context_switch($aggregate_settings, 1); @@ -1746,31 +1309,8 @@ function _advagg_build_css_path(array $matches, $base = '', array $aggregate_set return ''; } - // Prefix with base. + // Prefix with base and remove '../' segments where possible. $url = $_base . $matches[1]; - - // If advagg_file_create_url() is not being used and the $url is local, redo - // the $url taking the base_path into account. - if (!advagg_is_external($url) && variable_get('advagg_skip_file_create_url_inside_css', ADVAGG_SKIP_FILE_CREATE_URL_INSIDE_CSS)) { - $new_base_path = $GLOBALS['base_path']; - if (isset($_aggregate_settings['variables']['base_path'])) { - $new_base_path = $_aggregate_settings['variables']['base_path']; - } - // Remove first /. - $new_base_path = ltrim($new_base_path, '/'); - $pos = FALSE; - // See if base_path is in the passed in $_base. - if (!empty($new_base_path)) { - $pos = strpos($_base, $new_base_path); - } - if ($pos !== FALSE) { - $url = substr($_base, $pos) . $matches[1]; - } - else { - $url = $new_base_path . $_base . $matches[1]; - } - } - // Remove '../' segments where possible. $last = ''; while ($url != $last) { $last = $url; @@ -1779,7 +1319,7 @@ function _advagg_build_css_path(array $matches, $base = '', array $aggregate_set // Parse and build back the url without the query and fragment parts. $parsed_url = parse_url($url); - $base_url = advagg_glue_url($parsed_url, TRUE); + $base_url = advagg_glue_url($parsed_url, TRUE); $query = isset($parsed_url['query']) ? $parsed_url['query'] : ''; // In the case of certain URLs, we may have simply a '?' character without @@ -1792,14 +1332,6 @@ function _advagg_build_css_path(array $matches, $base = '', array $aggregate_set } $fragment = isset($parsed_url['fragment']) ? '#' . $parsed_url['fragment'] : ''; - $run_file_create_url = FALSE; - if (!variable_get('advagg_skip_file_create_url_inside_css', ADVAGG_SKIP_FILE_CREATE_URL_INSIDE_CSS)) { - $run_file_create_url = TRUE; - } - if (empty($parsed_url['host'])) { - $base_url = ltrim($base_url, '/'); - } - $base_url = advagg_file_create_url($base_url, $_aggregate_settings, $run_file_create_url, 'css'); - - return 'url(' . $base_url . $query . $fragment . ')'; + $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 f2ece07431..cfeccf296c 100644 --- a/sites/all/modules/contrib/advagg/advagg.info +++ b/sites/all/modules/contrib/advagg/advagg.info @@ -1,4 +1,4 @@ -name = Advanced CSS/JS Aggregation (AdvAgg) +name = Advanced CSS/JS Aggregation description = Aggregates multiple CSS/JS files in a way that prevents 404 from happening when accessing a CSS or JS file. package = Advanced CSS/JS Aggregation core = 7.x @@ -6,8 +6,9 @@ files[] = tests/advagg.test configure = admin/config/development/performance/advagg -; Information added by Drupal.org packaging script on 2020-11-19 -version = "7.x-2.35" +; Information added by Drupal.org packaging script on 2015-04-14 +version = "7.x-2.8" core = "7.x" project = "advagg" -datestamp = "1605792717" +datestamp = "1429049283" + diff --git a/sites/all/modules/contrib/advagg/advagg.install b/sites/all/modules/contrib/advagg/advagg.install index cad60a2e88..4137e77b22 100644 --- a/sites/all/modules/contrib/advagg/advagg.install +++ b/sites/all/modules/contrib/advagg/advagg.install @@ -5,39 +5,6 @@ * Handles Advanced Aggregation installation and upgrade tasks. */ -/** - * @addtogroup hooks - * @{ - */ - -/** - * Implements hook_install(). - */ -function advagg_install() { - $tables = array( - 'advagg_aggregates' => array( - 'aggregate_filenames_hash', - 'filename_hash', - ), - 'advagg_aggregates_versions' => array( - 'aggregate_filenames_hash', - 'aggregate_contents_hash', - ), - 'advagg_files' => array( - 'filename_hash', - 'content_hash', - ), - ); - - $schema = advagg_schema(); - foreach ($tables as $table => $fields) { - // Change utf8_bin to ascii_bin. - advagg_install_change_table_collation($table, $fields, 'ascii_bin', $schema[$table]['fields']); - } - // New install gets a locked admin section. - variable_set('advagg_admin_mode', 0); -} - /** * Implements hook_enable(). */ @@ -66,22 +33,6 @@ function advagg_enable() { @chgrp($stat_js[0], $stat_public['gid']); } } - if (drupal_is_cli()) { - // Remove advagg and public dirs if empty and running from command line. - list($css_path, $js_path) = advagg_get_root_files_dir(); - $files = file_scan_directory($css_path[1], '/.*/'); - if (empty($files)) { - rmdir($css_path[1]); - } - $files = file_scan_directory($js_path[1], '/.*/'); - if (empty($files)) { - rmdir($js_path[1]); - } - $files = file_scan_directory('public://', '/.*/'); - if (empty($files)) { - rmdir('public://'); - } - } // Make sure the advagg_flush_all_cache_bins() function is available. module_load_include('inc', 'advagg', 'advagg'); @@ -115,13 +66,6 @@ function advagg_disable() { // Flush caches. advagg_flush_all_cache_bins(); - _drupal_flush_css_js(); - drupal_clear_css_cache(); - drupal_clear_js_cache(); - cache_clear_all('*', 'cache_page', TRUE); - - // Make sure the theme_registry: cid is cleared. - register_shutdown_function('cache_clear_all', 'theme_registry:', 'cache', TRUE); } /** @@ -130,10 +74,13 @@ function advagg_disable() { function advagg_uninstall() { // Make sure the advagg_get_root_files_dir() function is available. drupal_load('module', 'advagg'); - list($css_path, $js_path) = advagg_get_root_files_dir(); - // Make sure the advagg_flush_all_cache_bins() function is available. + // Make sure the advagg_remove_all_aggregated_files() function is available. + module_load_include('inc', 'advagg', 'advagg'); module_load_include('inc', 'advagg', 'advagg.cache'); + + // Remove files. + advagg_remove_all_aggregated_files(TRUE); // Flush caches. advagg_flush_all_cache_bins(); @@ -142,12 +89,10 @@ function advagg_uninstall() { ->condition('name', 'advagg%', 'LIKE') ->execute(); - // Remove all files and directories. - file_unmanaged_delete_recursive($css_path[0]); - file_unmanaged_delete_recursive($js_path[0]); - - // Make sure the theme_registry: cid is cleared. - register_shutdown_function('cache_clear_all', 'theme_registry:', 'cache', TRUE); + // Remove Directories. + list($css_path, $js_path) = advagg_get_root_files_dir(); + drupal_rmdir($css_path[0]); + drupal_rmdir($js_path[0]); } /** @@ -175,25 +120,19 @@ function advagg_schema() { ), 'filename_hash' => array( 'description' => 'Hash of path and filename. Used to join tables.', - 'type' => 'char', + 'type' => 'varchar', 'length' => 43, 'not null' => TRUE, 'default' => '', 'binary' => TRUE, - 'collation' => 'ascii_bin', - 'charset' => 'ascii', - 'mysql_character_set' => 'ascii', ), 'content_hash' => array( 'description' => 'Hash of the file content. Used to see if the file has changed.', - 'type' => 'char', + 'type' => 'varchar', 'length' => 43, - 'not null' => FALSE, + 'not null' => TRUE, 'default' => '', 'binary' => TRUE, - 'collation' => 'ascii_bin', - 'charset' => 'ascii', - 'mysql_character_set' => 'ascii', ), 'filetype' => array( 'description' => 'Filetype.', @@ -227,24 +166,11 @@ 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'), ); @@ -254,25 +180,19 @@ function advagg_schema() { 'fields' => array( 'aggregate_filenames_hash' => array( 'description' => 'Hash of the aggregates list of files. Keep track of what files are in the aggregate.', - 'type' => 'char', + 'type' => 'varchar', 'length' => 43, 'not null' => TRUE, 'default' => '', 'binary' => TRUE, - 'collation' => 'ascii_bin', - 'charset' => 'ascii', - 'mysql_character_set' => 'ascii', ), 'filename_hash' => array( 'description' => 'Hash of path and filename.', - 'type' => 'char', + 'type' => 'varchar', 'length' => 43, 'not null' => TRUE, 'default' => '', 'binary' => TRUE, - 'collation' => 'ascii_bin', - 'charset' => 'ascii', - 'mysql_character_set' => 'ascii', ), 'porder' => array( 'description' => 'Processing order.', @@ -291,8 +211,6 @@ function advagg_schema() { ), 'indexes' => array( 'porder' => array('porder'), - 'filename_hash' => array('filename_hash'), - 'aggregate_filenames_hash_porder' => array('aggregate_filenames_hash', 'porder'), ), 'primary key' => array('aggregate_filenames_hash', 'filename_hash'), ); @@ -302,25 +220,19 @@ function advagg_schema() { 'fields' => array( 'aggregate_filenames_hash' => array( 'description' => 'Hash of the aggregates list of files. Keep track of what files are in the aggregate.', - 'type' => 'char', + 'type' => 'varchar', 'length' => 43, 'not null' => TRUE, 'default' => '', 'binary' => TRUE, - 'collation' => 'ascii_bin', - 'charset' => 'ascii', - 'mysql_character_set' => 'ascii', ), 'aggregate_contents_hash' => array( 'description' => 'Hash of all content_hashes in this aggregate. Simple Version control of the aggregate.', - 'type' => 'char', + 'type' => 'varchar', 'length' => 43, 'not null' => TRUE, 'default' => '', 'binary' => TRUE, - 'collation' => 'ascii_bin', - 'charset' => 'ascii', - 'mysql_character_set' => 'ascii', ), 'atime' => array( 'description' => 'Last access time for this version of the aggregate. Updated every 12 hours.', @@ -346,7 +258,7 @@ function advagg_schema() { 'primary key' => array('aggregate_filenames_hash', 'aggregate_contents_hash'), ); - // Copy the variable table and change a couple of things. + // Copy the variable table & change a couple of things. $schema['advagg_aggregates_hashes'] = drupal_get_schema_unprocessed('system', 'variable'); $schema['advagg_aggregates_hashes']['fields']['hash'] = $schema['advagg_aggregates_hashes']['fields']['name']; $schema['advagg_aggregates_hashes']['fields']['hash']['length'] = 255; @@ -362,7 +274,9 @@ function advagg_schema() { } /** - * Upgrade AdvAgg previous versions (6.x-1.x and 7.x-1.x) to 7.x-2.x. + * Upgrade AdvAgg previous versions (6.x-1.x & 7.x-1.x) to 7.x-2.x. + * + * Implements hook_update_N(). */ function advagg_update_7200(&$sandbox) { // Check and see if new tables exist. @@ -404,27 +318,38 @@ function advagg_update_7200(&$sandbox) { } /** - * Remove Last-Modified Header from .htaccess to fix far future 304's. + * Implements hook_update_N(). + * + * Update the .htaccess file in the advagg directories by removing the + * Last-Modified Header so far future 304's work correctly. */ function advagg_update_7201(&$sandbox) { return advagg_install_update_htaccess('Header set Last-Modified'); } /** - * Remove the 480 week Far-Future code from .htaccess (violates RFC 2616 14.21). + * Implements hook_update_N(). + * + * Update the .htaccess file in the advagg directories by removing the 480 week + * Far-Future code as it does not follow RFC 2616 14.21. */ function advagg_update_7202(&$sandbox) { return advagg_install_update_htaccess('290304000'); } /** - * Add forcing of .js files to be application/javascript to follow RFC 4329 7.1. + * Implements hook_update_N(). + * + * Update the .htaccess file in the advagg directories by forcing .js files to + * be application/javascript so to follow RFC 4329 7.1 */ function advagg_update_7203(&$sandbox) { return advagg_install_update_htaccess('', 'ForceType'); } /** + * Implements hook_update_N(). + * * Remove empty temporary files left behind by AdvAgg. */ function advagg_update_7204(&$sandbox) { @@ -463,16 +388,20 @@ function advagg_update_7204(&$sandbox) { } /** - * Fix incorrect usage of ForceType in .htaccess from update 7203. + * Implements hook_update_N(). + * + * Update the .htaccess file in the advagg directories in order to fix incorrect + * usage of ForceType from update 7203. */ function advagg_update_7205(&$sandbox) { - 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'), - )); + $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; } /** + * Implements hook_update_N(). + * * Update the schema making the varchar columns utf8_bin in MySQL. */ function advagg_update_7206(&$sandbox) { @@ -502,356 +431,88 @@ function advagg_update_7206(&$sandbox) { } /** - * Update schema making the varchar columns char. Change utf8_bin to ascii_bin. - */ -function advagg_update_7207(&$sandbox) { - $tables = array( - 'advagg_aggregates' => array( - 'aggregate_filenames_hash', - 'filename_hash', - ), - 'advagg_aggregates_versions' => array( - 'aggregate_filenames_hash', - 'aggregate_contents_hash', - ), - 'advagg_files' => array( - 'filename_hash', - 'content_hash', - ), - ); - - $schema = advagg_schema(); - foreach ($tables as $table => $fields) { - foreach ($fields as $field) { - // Change varchar to char. - db_change_field($table, $field, $field, $schema[$table]['fields'][$field]); - } - // Change utf8_bin to ascii_bin. - advagg_install_change_table_collation($table, $fields, 'ascii_bin', $schema[$table]['fields']); - } - return t('AdvAgg Tables converted from varchar to char and utf8_bin to ascii_bin.'); -} - -/** - * Add an index to the filename_hash column in the advagg_aggregates table. - */ -function advagg_update_7208(&$sandbox) { - if (!db_index_exists('advagg_aggregates', 'filename_hash')) { - db_add_index('advagg_aggregates', 'filename_hash', array('filename_hash')); - return t('Database index added to the filename_hash column of the advagg_aggregates table.'); - } - return t('Nothing needed to be done.'); -} - -/** - * Update schema making it match the definition. - */ -function advagg_update_7209(&$sandbox) { - $tables = array( - 'advagg_aggregates' => array( - 'aggregate_filenames_hash', - 'filename_hash', - ), - 'advagg_aggregates_versions' => array( - 'aggregate_filenames_hash', - 'aggregate_contents_hash', - ), - 'advagg_files' => array( - 'filename_hash', - 'content_hash', - ), - ); - - $schema = advagg_schema(); - foreach ($tables as $table => $fields) { - foreach ($fields as $field) { - // Change varchar to char. - db_change_field($table, $field, $field, $schema[$table]['fields'][$field]); - } - // Change utf8_bin to ascii_bin. - advagg_install_change_table_collation($table, $fields, 'ascii_bin', $schema[$table]['fields']); - } - return t('Database schema was adjusted to match what is listed in advagg_schema.'); -} - -/** - * Add filesize_processed field to advagg_files table. + * Callback to delete files if size == 0 & modified more than 60 seconds ago. + * + * @param string $uri + * Location of the file to check. */ -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); +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) { + file_unmanaged_delete($uri); } - return t('The filesize_processed field has been added to the advagg_files table.'); } /** - * Populate the filesize_processed field in the advagg_files table. + * Callback to delete files if size == 0 & modified more than 60 seconds ago. + * + * @param string $has_string + * If the .htaccess file contains this string it will be removed & recreated. + * @param string $does_not_have_string + * If the .htaccess file does not contains this string it will be removed & + * recreated. + * + * @return string + * Translated string indicating what was done. */ -function advagg_update_7211(&$sandbox) { +function advagg_install_update_htaccess($has_string = '', $does_not_have_string = '') { + // Make sure the advagg_get_root_files_dir() function is available. drupal_load('module', 'advagg'); - module_load_include('inc', 'advagg', 'advagg'); - $types = array('css', 'js'); - - // If first run of this update function then set progress variables. - if (!isset($sandbox['progress'])) { - $count = db_select('advagg_files', 'af') - ->fields('af') - ->condition('filesize_processed', 0) - ->countQuery() - ->execute() - ->fetchField(); - $sandbox['progress'] = 0; - $sandbox['max'] = $count; - } - // How many items should be processed per pass. - $limit = 20; + // Get paths to .htaccess file. + list($css_path, $js_path) = advagg_get_root_files_dir(); + $files['css'] = $css_path[0] . '/.htaccess'; + $files['js'] = $js_path[0] . '/.htaccess'; - foreach ($types as $type) { - $query = db_select('advagg_files', 'af') - ->fields('af') - ->condition('filesize_processed', 0) - ->condition('filetype', $type) - ->range($sandbox['progress'], $limit) - ->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(); - } + // Check for old .htaccess files. + $something_done = FALSE; + foreach ($files as $type => $uri) { + if (!file_exists($uri)) { + unset($files[$type]); + continue; } - } - - // Update our progress information. - $sandbox['progress'] += $limit; - // Set the value for finished. - $sandbox['#finished'] = ($sandbox['progress'] >= $sandbox['max']) ? TRUE : ($sandbox['progress'] / $sandbox['max']); - - if ($sandbox['#finished']) { - return t('The filesize_processed field has been populated inside the advagg_files table.'); - } -} - -/** - * 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.'); -} - -/** - * Populate the use_strict field in the advagg_files table. - */ -function advagg_update_7213(&$sandbox) { - drupal_load('module', 'advagg'); - module_load_include('inc', 'advagg', 'advagg'); - - // If first run of this update function then set progress variables. - if (!isset($sandbox['progress'])) { - $count = db_select('advagg_files', 'af') - ->fields('af') - ->condition('filetype', 'js') - ->countQuery() - ->execute() - ->fetchField(); - $sandbox['progress'] = 0; - $sandbox['max'] = $count; - } - - // How many items should be processed per pass. - $limit = 10; - - $query = db_select('advagg_files', 'af') - ->fields('af') - ->condition('filetype', 'js') - ->range($sandbox['progress'], $limit) - ->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(); + $contents = file_get_contents($uri); + // Remove old .htaccess file if it has this string. + if (!empty($has_string) && strpos($contents, $has_string) !== FALSE) { + drupal_unlink($uri); + $something_done = TRUE; + } + // Remove old .htaccess file if it does not have this string. + if (!empty($does_not_have_string) && strpos($contents, $does_not_have_string) === FALSE) { + drupal_unlink($uri); + $something_done = TRUE; } } - // Update our progress information. - $sandbox['progress'] += $limit; - // Set the value for finished. - $sandbox['#finished'] = ($sandbox['progress'] >= $sandbox['max']) ? TRUE : ($sandbox['progress'] / $sandbox['max']); - - if ($sandbox['#finished']) { - return t('The use_strict field has been populated inside the advagg_files table.'); - } -} - -/** - * Update .htaccess to support brotli compression (br). - */ -function advagg_update_7214(&$sandbox) { - return advagg_install_update_htaccess('', 'brotli'); -} - -/** - * Update .htaccess to support brotli compression (br). - */ -function advagg_update_7215(&$sandbox) { - return advagg_install_update_htaccess('', '%{HTTP:Accept-encoding} gzip'); -} - -/** - * Update .htaccess to support brotli compression (br). - */ -function advagg_update_7216(&$sandbox) { - if ($GLOBALS['base_path'] !== '/') { - return advagg_install_update_htaccess('', 'ErrorDocument 404'); - } -} - -/** - * Update migrate the advagg_browser_dns_prefetch variable. - */ -function advagg_update_7217(&$sandbox) { - $advagg_browser_dns_prefetch = variable_get('advagg_browser_dns_prefetch', NULL); - $advagg_resource_hints_dns_prefetch = variable_get('advagg_resource_hints_dns_prefetch', NULL); - $advagg_resource_hints_location = variable_get('advagg_resource_hints_location', NULL); - variable_del('advagg_browser_dns_prefetch'); - if (empty($advagg_browser_dns_prefetch) - || !is_null($advagg_resource_hints_dns_prefetch) - || !is_null($advagg_resource_hints_location) - ) { - return t('Nothing needed to be done.'); - } - drupal_load('module', 'advagg'); - $config_path = advagg_admin_config_root_path(); - - if ($advagg_browser_dns_prefetch == 1) { - variable_set('advagg_resource_hints_location', 1); - variable_set('advagg_resource_hints_dns_prefetch', TRUE); - } - elseif ($advagg_browser_dns_prefetch == 2) { - variable_set('advagg_resource_hints_location', 3); - variable_set('advagg_resource_hints_dns_prefetch', TRUE); - } - else { - return t('Nothing happened.'); - } - return t('Old DNS Prefetch variable transferred to the new variable. Other options are under Resource Hints on the configuration page', array('@url' => url($config_path . '/advagg', array('fragment' => 'edit-resource-hints')))); -} - -/** - * Update the advagg .htaccess file fixing edge cases with the new rules. - */ -function advagg_update_7218(&$sandbox) { - return advagg_install_update_htaccess('', 'Options +FollowSymLinks'); -} - -/** - * Update the .htaccess file in the advagg directories adding immutable header. - */ -function advagg_update_7219(&$sandbox) { - return advagg_install_update_htaccess('', 'immutable'); -} - -/** - * Update the advagg_files table; use_strict column might have been incorrect. - */ -function advagg_update_7220() { - // Get all files that have use_strict marked. - $filenames = array(); - $query = db_select('advagg_files', 'af') - ->fields('af', array('filename', 'use_strict')) - ->condition('use_strict', 1) - ->execute(); - foreach ($query as $row) { - $filenames[] = $row->filename; - } - if (empty($filenames)) { - return t('Nothing needed to happen. Good to go!'); - } - - drupal_load('module', 'advagg'); - module_load_include('inc', 'advagg', 'advagg'); - - // Force change. - $info = advagg_get_info_on_files($filenames); - foreach ($info as &$value) { - $value['mtime']++; - } - advagg_insert_update_files($info, 'js'); - - // Fix changed record. - advagg_get_info_on_files($filenames); - advagg_insert_update_files($info, 'js'); - - // Detect changes. - $filenames_new = array(); - $query = db_select('advagg_files', 'af') - ->fields('af', array('filename', 'use_strict')) - ->condition('use_strict', 1) - ->execute(); - foreach ($query as $row) { - $filenames_new[] = $row->filename; + // Create the new .htaccess file. + $new_htaccess = FALSE; + if (!empty($files) && $something_done && variable_get('advagg_htaccess_check_generate', ADVAGG_HTACCESS_CHECK_GENERATE)) { + // Make the advagg_htaccess_check_generate() function available. + module_load_include('inc', 'advagg', 'advagg.missing'); + foreach ($files as $type => $uri) { + advagg_htaccess_check_generate(array($uri => $type), $type, TRUE); + $new_htaccess = TRUE; + } } - // Output results. - if (count($filenames_new) == count($filenames_new)) { - return t('Nothing needed to happen. Good to go!'); + // Output info. + if ($something_done) { + if ($new_htaccess) { + return t('Removed the old .htaccess file and put in a new one.'); + } + else { + return t('Removed the old .htaccess file.'); + } } else { - return t('The advagg_files table has been updated; use_strict column has been updated.'); - } -} - -/** - * Add index to aggregate_filenames_hash and porder in advagg_aggregates table. - */ -function advagg_update_7221(&$sandbox) { - if (!db_index_exists('advagg_aggregates', 'aggregate_filenames_hash_porder')) { - db_add_index('advagg_aggregates', 'aggregate_filenames_hash_porder', array('aggregate_filenames_hash', 'porder')); - return t('Database index added to the aggregate_filenames_hash and porder column of the advagg_aggregates table.'); + return t('Nothing needed to be done.'); } - return t('Nothing needed to be done.'); } /** - * Run various checks that are fast. - * - * @param string $phase - * Can be install, update, or runtime. - * - * @return array - * An associative array. + * Implements hook_requirements(). */ -function advagg_install_fast_checks($phase = 'runtime') { +function advagg_requirements($phase) { $requirements = array(); // Ensure translations don't break at install time. $t = get_t(); @@ -904,169 +565,71 @@ function advagg_install_fast_checks($phase = 'runtime') { if ($phase !== 'runtime') { return $requirements; } - // Make sure the advagg default values for variable_get are available. - drupal_load('module', 'advagg'); // Do the following checks only at runtime. list($css_path, $js_path) = advagg_get_root_files_dir(); $config_path = advagg_admin_config_root_path(); // Make sure directories are writable. - if (!file_prepare_directory($css_path[0], FILE_CREATE_DIRECTORY + FILE_MODIFY_PERMISSIONS)) { - $requirements['advagg_css_path_0_prepare_dir'] = array( + if (!file_prepare_directory($css_path[0], FILE_CREATE_DIRECTORY)) { + $requirements['advagg_css_path'] = array( 'title' => $t('Adv CSS/JS Agg - CSS Path'), 'severity' => REQUIREMENT_ERROR, 'value' => $t('CSS directory is not created or writable.'), 'description' => $t('%path is not setup correctly.', array('%path' => $css_path[0])), ); } - if (!is_writable($css_path[0])) { - $requirements['advagg_css_path_0_write'] = 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])), - ); - } - if (!is_readable($css_path[0])) { - $requirements['advagg_css_path_0_read'] = 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])), - ); - } - $css_wrapper = file_stream_wrapper_get_instance_by_uri($css_path[0]); - if ($css_wrapper instanceof DrupalLocalStreamWrapper) { - if (!is_writable($css_path[1])) { - $requirements['advagg_css_path_1_write'] = 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])), - ); - } - if (!is_readable($css_path[1])) { - $requirements['advagg_css_path_1_read'] = 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_0_prepare_dir'] = array( + if (!file_prepare_directory($js_path[0], FILE_CREATE_DIRECTORY)) { + $requirements['advagg_js_path'] = array( 'title' => $t('Adv CSS/JS Agg - JS Path'), 'severity' => REQUIREMENT_ERROR, 'value' => $t('JS directory is not created or writable.'), 'description' => $t('%path is not setup correctly.', array('%path' => $js_path[0])), ); } - if (!is_writable($js_path[0])) { - $requirements['advagg_js_path_0_write'] = 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])), + + // Make sure variables are set correctly. + if (variable_get('advagg_enabled', ADVAGG_ENABLED) == FALSE) { + $requirements['advagg_not_on'] = array( + 'title' => $t('Adv CSS/JS Agg - Enabled'), + 'severity' => REQUIREMENT_WARNING, + 'value' => $t('Advanced CSS/JS aggregation is disabled.'), + 'description' => $t('Go to the Advanced CSS/JS aggregation settings page and enable it.', array('@settings' => url($config_path . '/advagg'))), ); } - if (!is_readable($js_path[0])) { - $requirements['advagg_js_path_0_read'] = array( - 'title' => $t('Adv CSS/JS Agg - JS Path'), + elseif (!variable_get('preprocess_css', FALSE) || !variable_get('preprocess_js', FALSE)) { + $requirements['advagg_core_off'] = array( + 'title' => $t('Adv CSS/JS Agg - Core Variables'), 'severity' => REQUIREMENT_ERROR, - 'value' => $t('JS directory is not readable.'), - 'description' => $t('%path is not setup correctly.', array('%path' => $js_path[0])), + 'value' => $t('Cores 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'))), ); } - $js_wrapper = file_stream_wrapper_get_instance_by_uri($js_path[0]); - if ($js_wrapper instanceof DrupalLocalStreamWrapper) { - if (!is_writable($js_path[1])) { - $requirements['advagg_js_path_1_write'] = 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])), - ); - } - if (!is_readable($js_path[1])) { - $requirements['advagg_js_path_1_read'] = 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. - if (!variable_get('advagg_enabled', ADVAGG_ENABLED)) { - $requirements['advagg_not_on'] = array( - 'title' => $t('Adv CSS/JS Agg - Enabled'), - 'severity' => REQUIREMENT_WARNING, - 'value' => $t('Advanced CSS/JS aggregation is disabled.'), - 'description' => $t('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)) { - $requirements['advagg_core_off'] = array( - 'title' => $t('Adv CSS/JS Agg - Core Variables'), - 'severity' => REQUIREMENT_ERROR, - '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', array('fragment' => 'edit-bandwidth-optimization')))), - ); - } - } // Check that the menu router handler is working. - // Paths will vary based on s3fs no_rewrite_cssjs setting. - if (advagg_s3fs_evaluate_no_rewrite_cssjs(FALSE)) { - // If using s3fs and no_rewrite_cssjs is not set, external paths are needed. - // Use $css_path[0] and $js_path[0] since they contain the scheme. - $menu_path_key = 0; - $menu_css_path = trim(parse_url(file_create_url($css_path[0] . '/test.css'), PHP_URL_PATH)); - if (strpos($menu_css_path, $GLOBALS['base_path']) === 0) { - $menu_css_path = substr($menu_css_path, strlen($GLOBALS['base_path'])); - } - $menu_js_path = trim(parse_url(file_create_url($js_path[0] . '/test.js'), PHP_URL_PATH)); - if (strpos($menu_js_path, $GLOBALS['base_path']) === 0) { - $menu_js_path = substr($menu_js_path, strlen($GLOBALS['base_path'])); - } - } - else { - // Determine paths if not using s3fs, or no_rewrite_cssjs is set. - // Use $css_path[1] and $js_path[1] since they are without schemes. - $menu_path_key = 1; - $menu_css_path = $css_path[1] . '/test.css'; - $menu_js_path = $js_path[1] . '/test.js'; - } - - // Use the paths set above to check menu router handler. - $advagg_async_generation_menu_issue = FALSE; - if (!file_uri_scheme($css_path[$menu_path_key])) { - $item_css = menu_get_item($menu_css_path); - if (empty($item_css['page_callback']) - || strpos($item_css['page_callback'], 'advagg') === FALSE - ) { - $advagg_async_generation_menu_issue = TRUE; - } - } - if (!file_uri_scheme($js_path[$menu_path_key])) { - $item_js = menu_get_item($menu_js_path); - if (empty($item_js['page_callback']) - || strpos($item_js['page_callback'], 'advagg') === FALSE - ) { - $advagg_async_generation_menu_issue = TRUE; - } + $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)))); + $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, + )), + ); } - if ($advagg_async_generation_menu_issue) { - $requirements['advagg_async_generation_menu_issue'] = array( + $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_ERROR, + '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; under "Clear cache" press the "Clear all caches" button.', array( + '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( '@performance' => url('admin/config/development/performance'), + '!info' => $item, )), ); } @@ -1074,36 +637,22 @@ function advagg_install_fast_checks($phase = 'runtime') { // Make hook_element_info_alter worked. $styles_info = element_info('styles'); $scripts_info = element_info('scripts'); - if (empty($styles_info['#pre_render']) + // @ignore sniffer_whitespace_openbracketspacing_openingwhitespace + if ( empty($styles_info['#pre_render']) || !is_array($styles_info['#pre_render']) || !in_array('advagg_modify_css_pre_render', $styles_info['#pre_render']) || empty($scripts_info['#pre_render']) || !is_array($scripts_info['#pre_render']) || !in_array('advagg_modify_js_pre_render', $scripts_info['#pre_render']) ) { - if (!empty($scripts_info['#group_callback']) && $scripts_info['#group_callback'] === 'omega_group_js') { - $requirements['advagg_hook_element_info_alter_omega'] = array( - 'title' => $t('Adv CSS/JS Agg - omega theme patch'), - 'severity' => REQUIREMENT_WARNING, - 'value' => $t('Omega theme needs a patch.'), - 'description' => $t('The patch can be found in this issue', array( - '@patch' => 'https://www.drupal.org/files/issues/omega-2492461-1-smarter-element-info-alter.patch', - '@issue' => 'https://www.drupal.org/node/2492461', - )), - ); - } - else { - $requirements['advagg_hook_element_info_alter'] = array( - 'title' => $t('Adv CSS/JS Agg - element_info'), - 'severity' => REQUIREMENT_WARNING, - 'value' => $t('Flush your caches.'), - 'description' => $t('You need to flush your cache_bootstrap cache bin as advagg_hook_element_info_alter() is not working correctly. This can be done near the top of the performance page under Clear cache.
Styles:

@styles

Scripts:

@scripts

', array( - '@performance' => url('admin/config/development/performance'), - '@styles' => print_r($styles_info, TRUE), - '@scripts' => print_r($scripts_info, TRUE), - )), - ); - } + $requirements['advagg_hook_element_info_alter'] = array( + 'title' => $t('Adv CSS/JS Agg - element_info'), + 'severity' => REQUIREMENT_WARNING, + 'value' => $t('Flush your caches.'), + 'description' => $t('You need to flush your cache_bootstrap cache bin as advagg_hook_element_info_alter() is not working correctly. This can be done near the top of the performance page under Clear cache.', array( + '@performance' => url('admin/config/development/performance'), + )), + ); } // Make sure some modules have the correct patches installed. @@ -1130,9 +679,10 @@ function advagg_install_fast_checks($phase = 'runtime') { } // Adjust some modules settings. + // @ignore sniffer_whitespace_openbracketspacing_openingwhitespace $search404_ignore_query = variable_get('search404_ignore_query', 'gif jpg jpeg bmp png'); - if (module_exists('search404') && - (strpos($search404_ignore_query, 'css') === FALSE + if ( module_exists('search404') && + ( strpos($search404_ignore_query, 'css') === FALSE || strpos($search404_ignore_query, 'js') === FALSE ) ) { @@ -1156,44 +706,10 @@ function advagg_install_fast_checks($phase = 'runtime') { ); } - if (module_exists('securepages') && variable_get('securepages_enable', 0) && function_exists('securepages_match')) { - $test_css = securepages_match($css_path[1] . '/test.css'); - $test_js = securepages_match($js_path[1] . '/test.js'); - if ($test_css === 0 || $test_js === 0) { - $added_paths = array(); - $securepages_ignore = variable_get('securepages_ignore', ''); - if (strpos($securepages_ignore, $css_path[1]) === FALSE) { - $added_paths[] = $css_path[1] . '/*'; - } - if (strpos($securepages_ignore, $js_path[1]) === FALSE) { - $added_paths[] = $js_path[1] . '/*'; - } - if (!empty($added_paths)) { - $requirements['advagg_securepages_module'] = array( - 'title' => $t('Adv CSS/JS Agg - Secure Pages Settings'), - 'severity' => REQUIREMENT_ERROR, - 'value' => $t('Requests to advagg for css/js files may be getting redirected to http on a https page.'), - ); - if (!empty($securepages_ignore)) { - $requirements['advagg_securepages_module']['description'] = $t('The Secure Pages module is enabled. You need to change the securepages_ignore setting, also known as "Ignore pages" so advagg will work. Go to the Secure Pages settings page and under the "Ignore pages" setting add

!code

to the string that looks like this:

@old

so it will then look like this:

@old
!code

', array( - '@config' => url('admin/config/system/securepages', array('fragment' => 'edit-securepages-ignore')), - '!code' => implode("\n
", $added_paths), - '@old' => trim($securepages_ignore), - )); - } - else { - $requirements['advagg_securepages_module']['description'] = $t('The Secure Pages module is enabled. You need to change the securepages_ignore setting, also known as "Ignore pages" so advagg will work. Go to the Secure Pages settings page and under the "Ignore pages" setting add

!code

to that section.', array( - '@config' => url('admin/config/system/securepages', array('fragment' => 'edit-securepages-ignore')), - '!code' => implode("\n
", $added_paths), - )); - } - } - } - } - // Check that https is correct. - if (empty($GLOBALS['is_https']) && - ((isset($_SERVER['HTTPS']) && strtolower($_SERVER['HTTPS']) === 'on') + // @ignore sniffer_whitespace_openbracketspacing_openingwhitespace:2 + if ( empty($GLOBALS['is_https']) && + ( (isset($_SERVER['HTTPS']) && strtolower($_SERVER['HTTPS']) === 'on') || (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https') || (isset($_SERVER['HTTP_HTTPS']) && $_SERVER['HTTP_HTTPS'] === 'on') ) @@ -1203,91 +719,33 @@ function advagg_install_fast_checks($phase = 'runtime') { 'severity' => REQUIREMENT_WARNING, 'value' => $t('The core global $is_https is not TRUE.'), 'description' => $t('You need to add in this logic near the top your settings.php file:
@code
', array( - '@code' => 'if ((isset($_SERVER[\'HTTPS\']) && strtolower($_SERVER[\'HTTPS\']) == \'on\') + '@code' => 'if ( (isset($_SERVER[\'HTTPS\']) && strtolower($_SERVER[\'HTTPS\']) == \'on\') || (isset($_SERVER[\'HTTP_X_FORWARDED_PROTO\']) && $_SERVER[\'HTTP_X_FORWARDED_PROTO\'] == \'https\') || (isset($_SERVER[\'HTTP_HTTPS\']) && $_SERVER[\'HTTP_HTTPS\'] == \'on\') ) { $_SERVER[\'HTTPS\'] = \'on\'; -}', - )), - ); +}'))); } // Make sure $base_url is correct. - // Site is https but $base_url starts with http://. - if (!empty($GLOBALS['is_https']) - && strpos($GLOBALS['base_url'], 'http://') === 0 - ) { + if (!empty($GLOBALS['is_https']) && strpos($GLOBALS['base_url'], 'https://') !== 0) { $requirements['advagg_is_https_check'] = array( - 'title' => $t('Adv CSS/JS Agg - $base_url'), + 'title' => $t('Adv CSS/JS Agg - HTTPS'), 'severity' => REQUIREMENT_WARNING, 'value' => $t('The core global $base_url\'s scheme is incorrect.'), - 'description' => $t('You need to add in this logic near the bottom of your settings.php file:

@code

', array( + 'description' => $t('You need to add in this logic near the bottom of your settings.php file:
@code
', array( '@code' => 'if (isset($_SERVER["HTTPS"]) && strtolower($_SERVER["HTTPS"]) == "on" && isset($base_url)) { - $base_url = str_replace("http://", "https://", $base_url); -}', - )), - ); - } - - return $requirements; +$base_url = str_replace("http://", "https://", $base_url); } - -/** - * Implements hook_requirements(). - */ -function advagg_requirements($phase) { - $t = get_t(); - $requirements = advagg_install_fast_checks($phase); - // If not at runtime, return here. - if ($phase !== 'runtime') { - return $requirements; - } - - // Make sure outbound http requests will work. - $request = drupal_http_request('https://www.google.com/robots.txt', array('timeout' => 8)); - if (empty($request->data) || $request->code != 200) { - $requirements['advagg_drupal_http_request_failure'] = array( - 'title' => $t('Adv CSS/JS Agg - drupal_http_request test'), - 'severity' => REQUIREMENT_WARNING, - 'value' => $t('An external request for https://www.google.com/robots.txt could not be fulfilled.'), - 'description' => $t('If drupal_http_request does not work, the tests that AdvAgg performs may not be accurate.'), - ); +'))); } - // Make sure http requests to advagg will work. + // 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 = ''; - if (variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL) < 0) { $description .= ' ' . $t('Currently running in development mode.'); } @@ -1313,10 +771,6 @@ function advagg_requirements($phase) { return $requirements; } -/** - * @} End of "addtogroup hooks". - */ - /** * Make sure http requests to css/js files work correctly. * @@ -1335,47 +789,21 @@ function advagg_install_check_via_http(array &$requirements) { // Setup some variables. list($css_path, $js_path) = advagg_get_root_files_dir(); $types = array('css', 'js'); - $config_path = advagg_admin_config_root_path(); - - // Get s3fs no_rewrite_cssjs setting. - $s3fs_no_rewrite_cssjs = advagg_get_s3fs_config('no_rewrite_cssjs'); // Make sure we get an advagg fast 404. $mod_url = FALSE; if (!variable_get('maintenance_mode', FALSE) && !variable_get('advagg_skip_404_check', FALSE)) { foreach ($types as $type) { if ($type === 'css') { - $url_path = $css_path[0]; - $file_path = $css_path[1]; + $path = $css_path[0]; } elseif ($type === 'js') { - $url_path = $js_path[0]; - $file_path = $js_path[1]; + $path = $js_path[0]; } - // Set arguments for drupal_http_request(). // Make a 404 request to the advagg menu callback. - $url = file_create_url($url_path . '/' . $type . ADVAGG_SPACE . REQUEST_TIME . '.' . $type); - $options = array('timeout' => 8); - - if (empty($url)) { - $filename_path = (!is_null($s3fs_no_rewrite_cssjs) && empty($s3fs_no_rewrite_cssjs)) ? $url_path : $file_path; - $filename = advagg_install_get_first_advagg_file($filename_path, $type); - $url = file_create_url($url_path . '/' . $filename); - $end = strpos($url, $filename); - if ($end !== FALSE) { - $url = substr($url, 0, $end) . $type . ADVAGG_SPACE . REQUEST_TIME . '.' . $type; - } - else { - $requirements['advagg_self_request'] = array( - 'title' => $t('Adv CSS/JS Agg - Self Request Failure'), - 'severity' => REQUIREMENT_ERROR, - 'value' => $t('The uri: %url can not be converted to a url.', array('%url' => $url_path . '/' . $type . ADVAGG_SPACE . REQUEST_TIME . '.' . $type)), - 'description' => $t('If you are using a non default stream wrapper this might be the issue.'), - ); - continue; - } - } + $url = file_create_url($path . '/' . $type . ADVAGG_SPACE . REQUEST_TIME . '.' . $type); + $options = array(); // Send request. advagg_install_url_mod($url, $options, $mod_url); @@ -1390,13 +818,13 @@ function advagg_install_check_via_http(array &$requirements) { if ($new_request->code < 0) { $description = ''; if (!module_exists('httprl')) { - $description = t('Enabling the HTTP Parallel Request and Threading Library module might be able to fix this as AdvAgg will use HTTPRL to build the URL if it is enabled.', array('!httprl' => 'https://drupal.org/project/httprl')); + $description = t('Enabling the HTTP Parallel Request & Threading Library module might be able to fix this as AdvAgg will use HTTPRL to build the URL if it is enabled.', array('!httprl' => 'https://drupal.org/project/httprl')); } $requirements['advagg_self_request'] = array( 'title' => $t('Adv CSS/JS Agg - Self Request Failure'), 'severity' => REQUIREMENT_ERROR, 'value' => $t('HTTP loopback requests to this server are returning a non positive response code of %code', array('%code' => $new_request->code)), - 'description' => $t('If you have manually verified that AdvAgg is working correctly you can set the advagg_skip_404_check variable to TRUE in your settings.php. Editing the servers hosts file so the host name points to the localhost might also fix this (127.0.0.1 !hostname). To manually check go to @url, view the source (press ctrl+u on your keyboard) and check for this string @string. If that string is in the source, you can safely add this to your settings.php file @code', array( + 'description' => $t('If you have manually verified that AdvAgg is working correctly you can set the advagg_skip_404_check variable to TRUE in your settings.php. Editing the servers hosts file so the host name points to the localhost might also fix this (127.0.0.1 !hostname). To manually check go to @url, view the source and check for this string @string. If that string is in the source, you can safely add this to your settings.php file @code', array( '!hostname' => $_SERVER['HTTP_HOST'], '@url' => $url, '@string' => '', @@ -1413,32 +841,23 @@ function advagg_install_check_via_http(array &$requirements) { } // Try request without https. - if ($request->code == 0 && stripos($request->error, 'Error opening socket ssl://') !== FALSE) { + if ($request->code == 0 && stripos($request->error, 'Error opening socket ssl://')) { $url = advagg_force_http_path($url); $request = drupal_http_request($url, $options); } - // 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); - } - } - + // @ignore sniffer_commenting_inlinecomment_spacingbefore:5 // 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". - if ($request->code != 404 + // 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']) + || ( empty($request->headers['x-advagg']) && strpos($request->data, '') === FALSE - ) + ) ) { // Fast 404 check. $url_path_404 = parse_url($url, PHP_URL_PATH); @@ -1446,34 +865,16 @@ function advagg_install_check_via_http(array &$requirements) { $fast_404_html = variable_get('404_fast_html', '404 Not Found

Not Found

The requested URL "@path" was not found on this server.

'); // Replace @path in the variable with the page path. $fast_404_html = trim(strtr($fast_404_html, array('@path' => check_plain($url_path_404)))); - if (!empty($request->data) + if ( !empty($request->data) && $fast_404_html == trim($request->data) && !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 %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), - )); + $description = $t('Change it from /\/(?:styles)\// to /\/(?:styles|advagg_(cs|j)s)\// Current value: %value', array('%value' => $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'), @@ -1482,7 +883,8 @@ function advagg_install_check_via_http(array &$requirements) { 'description' => $t('If you have fast 404 enabled in your settings.php file, you need to change the 404_fast_paths_exclude setting so advagg will work.') . ' ' . $description, ); } - elseif (module_exists('fast_404') + // @ignore sniffer_whitespace_openbracketspacing_openingwhitespace + elseif ( module_exists('fast_404') && defined('FAST_404_EXT_CHECKED') && !in_array('/advagg_', variable_get('fast_404_string_whitelisting', array())) && strpos(variable_get('fast_404_exts', '/^(?!robots).*\.(txt|png|gif|jpe?g|css|js|ico|swf|flv|cgi|bat|pl|dll|exe|asp)$/i'), $type) !== FALSE @@ -1495,9 +897,10 @@ function advagg_install_check_via_http(array &$requirements) { array('@code' => '$conf[\'fast_404_string_whitelisting\'][] = \'/advagg_\';')), ); } - elseif (module_exists('stage_file_proxy') + // @ignore sniffer_whitespace_openbracketspacing_openingwhitespace + elseif ( module_exists('stage_file_proxy') && variable_get('stage_file_proxy_origin', NULL) - && strpos(advagg_file_get_contents(drupal_get_path('module', 'stage_file_proxy') . '/stage_file_proxy.module'), 'advagg') === FALSE + && strpos(file_get_contents(drupal_get_path('module', 'stage_file_proxy') . '/stage_file_proxy.module'), 'advagg') === FALSE ) { // Stage File Proxy patch is missing. $requirements['advagg_stage_file_proxy_' . $type . '_generation'] = array( @@ -1532,100 +935,13 @@ 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. If there are image style rules in your Nginx configuration add this right below that. !config_location Raw request info:
@request
', array( - '@request' => var_export($request, TRUE), - '@code' => ' -### -### 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 $uri/ @drupal; -}', - '!config_location' => $config_location, - )), - ); - } - elseif (!advagg_install_htaccess_errordocument($type)) { - $parsed_base_url = parse_url($GLOBALS['base_url']); - if (isset($parsed_base_url['scheme'])) { - unset($parsed_base_url['scheme']); - } - if ($type === 'css') { - $location = $css_path[1] . '/.htaccess'; - } - if ($type === 'js') { - $location = $js_path[1] . '/.htaccess'; - } - $requirements['advagg_' . $type . '_errordoc_404'] = array( - 'title' => $t('Adv CSS/JS Agg - HTTP Request'), - 'severity' => REQUIREMENT_WARNING, - 'value' => $t('HTTP requests to advagg for ' . $type . ' files are not getting through. The .htaccess needs to be rebuilt.'), - 'description' => $t('The .htaccess file generated by AdvAgg has the incorrect errordoc location. This can happen if Drush is used incorrectly or if the site has been moved to a different directory structure. If you are currently using drush this is how to access it correctly:

@drush

Odds are you will need to fix the errordoc location. Go to the AdvAgg: Operations page and under Regenerate .htaccess files press the Recreate htaccess files button. If you wish to manually edit the file go to the @htaccess_loc file and make sure the following line is in there near the top and any other ErrorDocument 404 statements have been removed.

@code

', array( - '@drush' => 'drush --root=' . DRUPAL_ROOT . '/ --uri=' . advagg_glue_url($parsed_base_url) . ' ', - '@url' => url($config_path . '/advagg/operations', array('fragment' => 'edit-htaccess')), - '@htaccess_loc' => $location, - '@code' => "ErrorDocument 404 {$GLOBALS['base_path']}index.php", - )), - ); - } - elseif (!is_null($s3fs_no_rewrite_cssjs) - && !empty($s3fs_no_rewrite_cssjs) - && !empty($request->headers['server']) - && $request->headers['server'] === 'AmazonS3' - ) { - $severity = REQUIREMENT_WARNING; - if (module_exists('httprl') && variable_get('advagg_use_httprl', ADVAGG_USE_HTTPRL)) { - $severity = REQUIREMENT_ERROR; - } - // S3 doesn't do origin pull. - $requirements['advagg_' . $type . '_generation'] = array( - 'title' => $t('Adv CSS/JS Agg - HTTP Request'), - 'severity' => $severity, - '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. In this case the s3fs Advanced Configuration Option "Don\'t render proxied CSS/JS file paths" should be disabled. Raw request info:
@request
', array( - '@request' => var_export($request, TRUE), - '@url' => url('admin/config/media/s3fs', array('fragment' => 'edit-s3fs-no-rewrite-cssjs')), - )), - ); - } 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. In some cases this can sometimes be a false report; go here: @url and check if the source (press ctrl+u on your keyboard) has an html comment that says "advagg_missing_fast404"; if it does, this is a false report, add this $conf[\'advagg_skip_404_check\'] = TRUE; to your settings.php file. Raw request info:
@request
', array( - '@request' => var_export($request, TRUE), - '@url' => $url, - )), + '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))), ); } } @@ -1651,8 +967,7 @@ location ~* files/advagg_(?:css|js)/ { $file_path = $js_path[1]; } // Get filename. - $filename_path = (!is_null($s3fs_no_rewrite_cssjs) && empty($s3fs_no_rewrite_cssjs)) ? $url_path : $file_path; - $filename = advagg_install_get_first_advagg_file($filename_path, $type); + $filename = advagg_install_get_first_advagg_file($file_path); // Skip if filename is empty. if (empty($filename)) { @@ -1660,11 +975,7 @@ location ~* files/advagg_(?:css|js)/ { } $urls = array(); - $url = file_create_url($url_path . '/' . $filename); - if (empty($url)) { - continue; - } - $urls[] = $url; + $urls[] = file_create_url($url_path . '/' . $filename); if (module_exists('cdn')) { // Get CDN defaults. $blacklist = variable_get(CDN_EXCEPTION_DRUPAL_PATH_BLACKLIST_VARIABLE, CDN_EXCEPTION_DRUPAL_PATH_BLACKLIST_DEFAULT); @@ -1686,11 +997,8 @@ location ~* files/advagg_(?:css|js)/ { 'Accept-Encoding' => 'gzip, deflate', ), 'version' => '1.0', - '#advagg_path' => "{$file_path}/{$filename}", - 'timeout' => 8, ); - // Test http 1.0. - $old_requirements = $requirements; + // Test http 1.0 advagg_install_chk_urls($requirements, $urls, $options, $mod_url, $type, $url_path, $file_path, $filename); // Test http 1.1 @@ -1699,46 +1007,11 @@ location ~* files/advagg_(?:css|js)/ { $old = variable_get('drupal_http_request_function', FALSE); $GLOBALS['conf']['drupal_http_request_function'] = 'httprl_override_core'; - // Only test 1.1; 1.0 is rarely used these days. - $requirements = $old_requirements; - $options['version'] = '1.1'; advagg_install_chk_urls($requirements, $urls, $options, $mod_url, $type, $url_path, $file_path, $filename); $GLOBALS['conf']['drupal_http_request_function'] = $old; } - - if (function_exists('brotli_compress') - && defined('BROTLI_TEXT') - && variable_get('advagg_brotli', ADVAGG_BROTLI) - ) { - // Set arguments for drupal_http_request(). - $options = array( - 'headers' => array( - 'Accept-Encoding' => 'br', - ), - 'version' => '1.0', - 'timeout' => 8, - ); - // Test http 1.0. - $old_requirements = $requirements; - advagg_install_chk_urls($requirements, $urls, $options, $mod_url, $type, $url_path, $file_path, $filename); - - // Test http 1.1 - // If Drupal version is >= 7.22 and httprl_override_core exists. - if (defined('VERSION') && floatval(VERSION) >= 7.22 && is_callable('httprl_override_core')) { - $old = variable_get('drupal_http_request_function', FALSE); - $GLOBALS['conf']['drupal_http_request_function'] = 'httprl_override_core'; - - // Only test 1.1; 1.0 is rarely used these days. - $requirements = $old_requirements; - - $options['version'] = '1.1'; - advagg_install_chk_urls($requirements, $urls, $options, $mod_url, $type, $url_path, $file_path, $filename); - - $GLOBALS['conf']['drupal_http_request_function'] = $old; - } - } } } @@ -1766,22 +1039,7 @@ function advagg_install_chk_urls(array &$requirements, array $urls, array $optio // Ensure translations don't break at install time. $t = get_t(); - list($css_path, $js_path) = advagg_get_root_files_dir(); - $config_path = advagg_admin_config_root_path(); - - $options += array( - 'timeout' => 8, - ); - - $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 = strtolower(pathinfo($url, PATHINFO_EXTENSION)); + foreach ($urls as $key => $url) { // Make sure the URL contains a schema. if (strpos($url, 'http') !== 0) { if ($GLOBALS['is_https']) { @@ -1792,319 +1050,77 @@ function advagg_install_chk_urls(array &$requirements, array $urls, array $optio } } - // Before sending the request when using s3fs, check if the file exists. - if (module_exists('s3fs') && !file_exists($url_path . '/' . $filename)) { - if (module_exists('httprl') && variable_get('advagg_use_httprl', ADVAGG_USE_HTTPRL)) { - $httprl_message = 'This may be due to an issue with the HTTPRL module or its configuration. '; - } - else { - $httprl_message = ''; - } - $requirements['advagg_' . $type . '_missing' . $key] = array( - 'title' => $t('Adv CSS/JS Agg - file does not exist'), - 'severity' => REQUIREMENT_WARNING, - 'value' => $t('Unable to find %type files.', array('%type' => $type)), - 'description' => $t('The AdvAgg database records and S3 files are not in sync. The file referenced in the database to perform a test cannot be found in the S3 file system. @httprl_messageFile URL:
@file
', array( - '@httprl_message' => $httprl_message, - '@file' => $url, - )), - ); - continue; - } - // Send request. advagg_install_url_mod($url, $options, $mod_url); $request = drupal_http_request($url, $options); - $encoding_type = 'gzip'; - if (!empty($request->options['headers']['Accept-Encoding']) && strpos($request->options['headers']['Accept-Encoding'], 'br') !== FALSE) { - $encoding_type = 'br'; - } - if (!variable_get('advagg_skip_gzip_check', ADVAGG_SKIP_GZIP_CHECK)) { - // Check response. Report an error if - // - Not a 200. - // - Headers do not contain "content-encoding". - // - content-encoding is not gzip, deflate or br. - if ($request->code != 200 - || empty($request->headers['content-encoding']) - || ($request->headers['content-encoding'] !== 'gzip' - && $request->headers['content-encoding'] !== 'deflate' - && $request->headers['content-encoding'] !== 'br' - ) - ) { - // Gzip failed. - if (!variable_get('advagg_gzip', ADVAGG_GZIP) - && $encoding_type === '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, - )), - ); - } - elseif (function_exists('brotli_compress') - && defined('BROTLI_TEXT') - && !variable_get('advagg_brotli', ADVAGG_BROTLI) - && $encoding_type === 'br' - ) { - // Recommend that br be turned on. - $requirements['advagg_' . $type . '_br' . $key] = array( - 'title' => $t('Adv CSS/JS Agg - brotli'), - 'severity' => REQUIREMENT_WARNING, - 'value' => $t('Brotli is failing for %type files.', array('%type' => $type)), - 'description' => $t('Try enabling on the "Create .br 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 . '_' . $encoding_type] = 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 @encoding to fail.', array( - '!link' => 'http://httpd.apache.org/docs/current/mod/mod_headers.html', - '@encoding' => $encoding_type, - )), - 'severity' => REQUIREMENT_WARNING, - 'value' => $t('Apache module "mod_headers" is not installed.'), - ); - } - if ($mod_rewrite === FALSE) { - $requirements['advagg_mod_rewrite' . $key . '_' . $encoding_type] = 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 @encoding to fail.', array( - '!link' => 'http://httpd.apache.org/docs/current/mod/mod_rewrite.html', - '@encoding' => $encoding_type, - )), - 'severity' => REQUIREMENT_ERROR, - 'value' => $t('Apache module "mod_rewrite" is not installed.'), - ); - } - } - } - if (!$apache_module_missing) { - // Check via external service. - $ext_url = 'http://checkgzipcompression.com/?url=' . urlencode($url); - if ($encoding_type === 'br') { - $ext_url = 'https://tools.keycdn.com/brotli-query.php?url=' . urlencode($url) . '&public=0'; - } - $external_compression_request = drupal_http_request($ext_url, array( - 'timeout' => 7, - 'headers' => array('Connection' => 'close'), - )); - if (!empty($external_compression_request->data)) { - if ($encoding_type === 'br') { - if (stripos($external_compression_request->data, '') !== FALSE) { - preg_match("/(.*)<\/strong>/siU", $external_compression_request->data, $title_matches); - if (stripos($title_matches[1], 'Negative') === FALSE) { - $external_test_results = 1; - } - else { - $external_test_results = -1; - } - } - else { - $external_test_results = 0; - } - } - elseif (stripos($external_compression_request->data, '') !== FALSE) { - preg_match("/<title>(.*)<\/title>/siU", $external_compression_request->data, $title_matches); - if (stripos($title_matches[1], 'gzip') === FALSE) { - $external_test_results = 0; - } - elseif (stripos($title_matches[1], 'not gzip') === FALSE) { - $external_test_results = 1; - } - else { - $external_test_results = -1; - } - } + // @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 <a href="@advagg">Advanced CSS/JS Aggregation Configuration page</a>', 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 <a href="!link">mod_headers</a> 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 (!isset($external_test_results) || $external_test_results !== 1) { - if ($request->code != 200) { - $rewritebase = advagg_htaccess_rewritebase(); - if (!empty($rewritebase)) { - if ($type === 'css') { - $rewritebase_advagg = advagg_htaccess_rewritebase($css_path[1]); - } - if ($type === 'js') { - $rewritebase_advagg = advagg_htaccess_rewritebase($js_path[1]); - } - } - $advagg_htaccess_rewritebase = variable_get('advagg_htaccess_rewritebase', ADVAGG_HTACCESS_REWRITEBASE); - if ($request->code == 307 && !empty($rewritebase) && empty($rewritebase_advagg) && empty($advagg_htaccess_rewritebase)) { - $requirements['advagg_' . $type . $encoding_type . $key . $options['version']] = array( - 'title' => $t('Adv CSS/JS Agg - @encoding', array('@encoding' => $encoding_type)), - 'severity' => REQUIREMENT_WARNING, - 'value' => $t('@encoding is failing for %type files.', array( - '%type' => $type, - '@encoding' => $encoding_type, - )), - 'description' => $t('The web server is not returning a 200, instead a @code is being returned. The RewriteBase option should be set on the <a href="@url">configuration page</a> under "Obscure Options" look for "AdvAgg RewriteBase Directive in .htaccess files". Raw request info: <pre>@request</pre>', array( - '@code' => $request->code, - '@encoding' => $encoding_type, - '@request' => print_r($request, TRUE), - '@url' => url($config_path . '/advagg', array('fragment' => 'edit-advagg-htaccess-rewritebase')), - )), - ); - } - else { - if (module_exists('s3fs') && ($request->code == 307 || $request->code == -2)) { - $requirements['advagg_' . $type . $encoding_type . $key . $options['version']] = array( - 'title' => $t('Adv CSS/JS Agg - Redirect Loop'), - 'severity' => REQUIREMENT_WARNING, - 'value' => $t('S3fs issue. Proxy is not setup correctly for %type.', array( - '%type' => $type, - )), - 'description' => $t('The web server is not returning a 200, instead a @code is being returned. Apache proxy settings for httpd.conf: <p><code>!httpd</code></p>. Raw request info: <pre>@request</pre>', array( - '!httpd' => nl2br(str_replace(' ', '  ', htmlentities(advagg_install_s3fs_proxy_settings($type)))), - '@code' => $request->code, - '@encoding' => $encoding_type, - '@request' => print_r($request, TRUE), - )), - ); - } - else { - $requirements['advagg_' . $type . $encoding_type . $key . $options['version']] = array( - 'title' => $t('Adv CSS/JS Agg - @encoding', array('@encoding' => $encoding_type)), - 'severity' => REQUIREMENT_WARNING, - 'value' => $t('@encoding is failing for %type files.', array( - '%type' => $type, - '@encoding' => $encoding_type, - )), - 'description' => $t('The web server is not returning a 200, instead a @code is being returned. @encoding can not be tested. Raw request info: <pre>@request</pre>', array( - '@code' => $request->code, - '@encoding' => $encoding_type, - '@request' => print_r($request, TRUE), - )), - ); - } - } - } - elseif (empty($request->data)) { - $url = 'http://checkgzipcompression.com/?url=' . urlencode($url); - if ($encoding_type === 'br') { - $url = 'https://tools.keycdn.com/brotli-query.php?url=' . urlencode($url) . '&public=0'; - } - $requirements['advagg_' . $type . $encoding_type . $key . $options['version']] = array( - 'title' => $t('Adv CSS/JS Agg - @encoding', array('@encoding' => $encoding_type)), - 'severity' => REQUIREMENT_WARNING, - 'value' => $t('@encoding is failing for %type files.', array( - '%type' => $type, - '@encoding' => $encoding_type, - )), - 'description' => $t('No data was returned from your server; this @encoding test can not be done locally. Error: @error - @message You can manually test if @encoding is working by <a href="@urlGZ">going here</a> and seeing if @encoding is enabled. You can turn this warning off by adding this to your settings.php file: <code>@skipcode</code>', array( - '@error' => $request->code, - '@message' => isset($request->error) ? $request->error : '', - '@url' => $url, - '@skipcode' => '$conf[\'advagg_skip_gzip_check\'] = TRUE;', - '@encoding' => $encoding_type, - )), - ); - } - else { - // Recommend servers configuration be adjusted. - $request->data = '...'; - $requirements['advagg_' . $type . $encoding_type . $key . $options['version']] = array( - 'title' => $t('Adv CSS/JS Agg - @encoding', array('@encoding' => $encoding_type)), - 'severity' => REQUIREMENT_WARNING, - 'value' => $t('@encoding is failing for %type files.', array( - '%type' => $type, - '@encoding' => $encoding_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". Also check in the <a href="@readme">readme</a>, under Troubleshooting. Certain default web server configurations (<a href="!nginx">nginx</a>) do not gzip HTTP/1.0 requests. If you are using cloudfront you will have to <a href="!cloudfront">add metadata</a> to the .gz files. There are some other options if using <a href="!so">cloudfront</a>. Raw request info: <pre>@request</pre>', 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', - '@readme' => url(drupal_get_path('module', 'advagg') . '/README.txt'), - )), - ); - } + 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 <a href="!link">mod_rewrite</a> 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.'), + ); } } } - } - elseif ($request->code == 200 - && !empty($request->headers['content-encoding']) - && !empty($request->data) - && ($request->headers['content-encoding'] === 'gzip' - || $request->headers['content-encoding'] === 'deflate' - || $request->headers['content-encoding'] === 'br' - )) { - // Do the first level of decoding if not already done. - if (!isset($request->chunk_size)) { - if ($request->headers['content-encoding'] === 'gzip') { - $request->data = @gzinflate(substr($request->data, 10)); - } - elseif ($request->headers['content-encoding'] === 'deflate') { - $request->data = @gzinflate($request->data); - } - elseif ($request->headers['content-encoding'] === 'br' && is_callable('brotli_uncompress')) { - $request->data = @brotli_uncompress($request->data); - } - } - // Check for double gzip compression. - $contents = @file_get_contents($options['#advagg_path']); - if ($contents !== $request->data && (@gzinflate(substr($request->data, 10)) !== FALSE || @gzinflate($request->data) !== FALSE)) { - $config_path = advagg_admin_config_root_path(); - $description = ''; - if (variable_get('advagg_gzip', ADVAGG_GZIP)) { - $description .= $t('Go to the Advanced CSS/JS aggregation <a href="@settings">settings page</a>, under Obsucre Options uncheck the Create .gz files setting.', array( - '@settings' => url($config_path . '/advagg', array('fragment' => 'edit-obscure')), - )); - } - else { - $description .= $t('Your webserver configuration needs to be changed so that %type files are not being double compressed.', array( - '%type' => $type, - )); - if (isset($request->headers['content-type'])) { - $description .= ' ' . $t('The content type is: %type.', array( - '%type' => $request->headers['content-type'], - )); - } - } + if (!$apache_module_missing) { + // Recommend servers configuration be adjusted. + $request->data = '...'; $requirements['advagg_' . $type . '_gzip' . $key . $options['version']] = array( 'title' => $t('Adv CSS/JS Agg - gzip'), - 'severity' => REQUIREMENT_ERROR, - 'value' => $t('Double gzip encoding detected for %type files.', array('%type' => $type)), - 'description' => $description, - ); - } - if ($contents !== $request->data && (is_callable('brotli_uncompress') && @brotli_uncompress($request->data) !== FALSE)) { - $config_path = advagg_admin_config_root_path(); - $description = ''; - if (variable_get('advagg_brotli', ADVAGG_BROTLI)) { - $description .= $t('Go to the Advanced CSS/JS aggregation <a href="@settings">settings page</a>, under Obsucre Options uncheck the Create .br files setting.', array( - '@settings' => url($config_path . '/advagg', array('fragment' => 'edit-obscure')), - )); - } - else { - $description .= $t('Your webserver configuration needs to be changed so that %type files are not being double compressed.', array( - '%type' => $type, - )); - if (isset($request->headers['content-type'])) { - $description .= ' ' . $t('The content type is: %type.', array( - '%type' => $request->headers['content-type'], - )); - } - } - $requirements['advagg_' . $type . '_br' . $key . $options['version']] = array( - 'title' => $t('Adv CSS/JS Agg - br'), - 'severity' => REQUIREMENT_ERROR, - 'value' => $t('Double br encoding detected for %type files.', array('%type' => $type)), - 'description' => $description, + '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 (<a href="!nginx">nginx</a>) do not gzip HTTP/1.0 requests. If you are using cloudfront you will have to <a href="!cloudfront">add metadata</a> to the .gz files. There are some other options if using <a href="!so">cloudfront</a>. Raw request info: <pre>@request</pre>', 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', + )), ); } } @@ -2113,35 +1129,31 @@ function advagg_install_chk_urls(array &$requirements, array $urls, array $optio if ($content_type === 'js') { $content_type = 'javascript'; } - if ($request->code == 200) { + $modules = array(); $matches = array(); - if (!empty($request->headers['x-advagg']) + // @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 ) { - 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 <a href="@readme">readme</a>, under Troubleshooting try the Far-Future recommendations.', array('@readme' => url(drupal_get_path('module', 'advagg') . '/README.txt'))), - ); - } + $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, & If-Modified-Since might be incorrect. In the <a href="@readme">readme</a>, 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 ($type === 'css' - && (empty($request->headers['content-type']) + if ( $type === 'css' + && ( empty($request->headers['content-type']) || strpos($request->headers['content-type'], 'text/' . $content_type) === FALSE - ) + ) ) { // Recommend servers configuration be adjusted. $requirements['advagg_' . $type . '_type' . $key] = array( @@ -2155,12 +1167,12 @@ function advagg_install_chk_urls(array &$requirements, array $urls, array $optio ); } - if ($type === 'js' - && (empty($request->headers['content-type']) - || (strpos($request->headers['content-type'], 'application/' . $content_type) === FALSE + if ( $type === 'js' + && ( empty($request->headers['content-type']) + || ( strpos($request->headers['content-type'], 'application/' . $content_type) === FALSE && strpos($request->headers['content-type'], 'application/x-' . $content_type) === FALSE + ) ) - ) ) { // Recommend servers configuration be adjusted. $requirements['advagg_' . $type . '_type' . $key] = array( @@ -2177,191 +1189,115 @@ function advagg_install_chk_urls(array &$requirements, array $urls, array $optio // Test far future headers. $apache_module_missing = FALSE; - if (!variable_get('advagg_skip_far_future_check', ADVAGG_SKIP_FAR_FUTURE_CHECK) - && empty($_SERVER['PANTHEON_ENVIRONMENT']) - && empty($_SERVER['PANTHEON_SITE_NAME']) + // Make sure the expires header is at least set to 1 month + // (2628000 seconds) in the future. + if ( !empty($request->headers['expires']) + && strtotime($request->headers['expires']) < time() + 2628000 ) { - // Make sure the expires header is at least set to 1 month - // (2628000 seconds) in the future. - if (!empty($request->headers['expires']) - && strtotime($request->headers['expires']) < time() + 2628000 - ) { - // Recommend servers configuration be adjusted. - if ($is_apache && $mod_headers === FALSE) { - $apache_module_missing = TRUE; - } - else { - $requirements['advagg_' . $type . '_expires' . $key] = array( - '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.'), - '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 <code>@received</code> (@expires). You can turn this warning off if you can not adjust this value (Pantheon) by adding this to your settings.php file: <code>@skipcode</code>', 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;', - )), - ); - } + // Recommend servers configuration be adjusted. + if (function_exists('apache_get_modules') && !in_array('mod_headers', $modules)) { + $apache_module_missing = TRUE; } - // Make sure the cache-control header max age value is at least set to - // 1 month (2628000 seconds) in the future. - $matches = array(); - if (empty($request->headers['cache-control']) - || !preg_match('/\s*max-age\s*=\s*(\\d+)\s*/is', $request->headers['cache-control'], $matches) - || $matches[1] < 2628000 - ) { - // Recommend servers configuration be adjusted. - if ($is_apache && $mod_headers === FALSE) { - $apache_module_missing = TRUE; - } - else { - $requirements['advagg_' . $type . '_cache_control' . $key] = array( - 'title' => $t('Adv CSS/JS Agg - Cache-Control'), - 'severity' => REQUIREMENT_WARNING, - 'value' => $t("The cache-control's max-age header being sent by your web server is not at least 1 month in the future."), - '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 <code>@received</code>. You can turn this warning off if you can not adjust this value (Pantheon) by adding this to your settings.php file: <code>@skipcode</code>', array( - '@received' => isset($request->headers['cache-control']) ? $request->headers['cache-control'] : 'NULL', - '@skipcode' => '$conf[\'advagg_skip_far_future_check\'] = TRUE;', - )), - ); - } + else { + $requirements['advagg_' . $type . '_expires' . $key] = array( + '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 <code>@received</code>.', array( + '@received' => isset($request->headers['expires']) ? number_format(strtotime($request->headers['expires'])) : 'NULL', + )), + ); + } + } + // Make sure the cache-control header max age value is at least set to + // 1 month (2628000 seconds) in the future. + $matches = array(); + if ( empty($request->headers['cache-control']) + || !preg_match('/max-age=(\\d+)/is', $request->headers['cache-control'], $matches) + || $matches[1] < 2628000 + ) { + // Recommend servers configuration be adjusted. + if (function_exists('apache_get_modules') && (!in_array('mod_headers', $modules))) { + $apache_module_missing = TRUE; + } + else { + $requirements['advagg_' . $type . '_cache_control' . $key] = array( + 'title' => $t('Adv CSS/JS Agg - Cache-Control'), + 'severity' => REQUIREMENT_WARNING, + '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 <code>@received</code>.', array( + '@received' => isset($request->headers['cache-control']) ? $request->headers['cache-control'] : 'NULL', + )), + ); } } - // Handle missing apache modules. - if ($apache_module_missing && $is_apache && $mod_headers === FALSE) { - $requirements['advagg_mod_headers_far_future_headers_' . $key] = array( + if ($apache_module_missing && !in_array('mod_headers', $modules)) { + $requirements['advagg_mod_headers_far_future' . $key] = array( 'title' => $t('Adv CSS/JS Agg - Apache'), 'description' => $t('The Apache module "mod_headers" is not available. Enable <a href="!link">mod_headers</a> 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 <a href="!link">mod_expires</a> 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. - if (!variable_get('advagg_skip_304_check', ADVAGG_SKIP_304_CHECK) - && empty($_SERVER['PANTHEON_ENVIRONMENT']) - && empty($_SERVER['PANTHEON_SITE_NAME']) - ) { - $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); - $if_modified_options = $options; - $if_modified_options['headers'] = array( - 'Accept-Encoding' => 'gzip, deflate, br', - 'If-None-Match' => $request->headers['etag'], - ); - if (!empty($request->options['headers']['Accept-Encoding'])) { - $if_modified_options['headers']['Accept-Encoding'] = $request->options['headers']['Accept-Encoding']; - } + if (isset($request->headers['last-modified'])) { + // Send a If-Modified-Since header and see if the web server returns + // 304. + $url = file_create_url($url_path . '/' . $filename); + $if_modified_options = $options; + $if_modified_options['headers'] = array( + 'Accept-Encoding' => 'gzip, deflate', + 'If-Modified-Since' => $request->headers['last-modified'], + ); - // Send request. - advagg_install_url_mod($url, $if_modified_options, $mod_url); - $request_304 = drupal_http_request($url, $if_modified_options); - if ($request_304->code != 304) { - // Recommend servers configuration be adjusted. - $requirements['advagg_' . $type . '_304' . $key . '_etag'] = array( - 'title' => $t('Adv CSS/JS Agg - Etag'), - 'severity' => REQUIREMENT_WARNING, - 'value' => $t('The If-None-Match (Etag) header is being ignored by your web server.'), - 'description' => $t('The web servers configuration will need to be adjusted. The server should have responded with a 304, instead a @code was returned.', array( - '@code' => $request_304->code, - )), - ); - } - else { - $etag_works = TRUE; - } - } - if (isset($request->headers['last-modified'])) { - // Send a If-Modified-Since header and see if the web server returns a - // 304. - $url = file_create_url($url_path . '/' . $filename); - $if_modified_options = $options; - $if_modified_options['headers'] = array( - 'Accept-Encoding' => 'gzip, deflate, br', - 'If-Modified-Since' => $request->headers['last-modified'], + // Send request. + advagg_install_url_mod($url, $if_modified_options, $mod_url); + $request = drupal_http_request($url, $if_modified_options); + if ($request->code != 304) { + // Recommend servers configuration be adjusted. + $requirements['advagg_' . $type . '_304' . $key] = array( + 'title' => $t('Adv CSS/JS Agg - If-Modified-Since'), + 'severity' => REQUIREMENT_WARNING, + 'value' => $t('The If-Modified-Since header is being ignored by your web server.'), + 'description' => $t('The web servers configuration will need to be adjusted. The server should have responded with a 304, instead a @code was returned.', array('@code' => $request->code)), ); - if (!empty($request->options['headers']['Accept-Encoding'])) { - $if_modified_options['headers']['Accept-Encoding'] = $request->options['headers']['Accept-Encoding']; - } - - // Send request. - advagg_install_url_mod($url, $if_modified_options, $mod_url); - $request_304 = drupal_http_request($url, $if_modified_options); - if ($request_304->code != 304) { - // Recommend servers configuration be adjusted. - $requirements['advagg_' . $type . '_304' . $key . '_last_modified'] = array( - 'title' => $t('Adv CSS/JS Agg - If-Modified-Since'), - 'severity' => REQUIREMENT_WARNING, - 'value' => $t('The If-Modified-Since (Last-Modified) header is being ignored by your web server.'), - 'description' => $t('The web servers configuration will need to be adjusted. The server should have responded with a 304, instead a @code was returned.', array( - '@code' => $request_304->code, - )), - ); - } - elseif (isset($requirements['advagg_' . $type . '_304' . $key . '_etag'])) { - // Last-Modified works, Etag is broken. 304s are working. Don't warn - // user. - 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'])) { - // Get path to advagg .htaccess file. - $files = array( - $file_path . '/.htaccess', - DRUPAL_ROOT . '/.htaccess', - ); - - // Check for bad .htaccess files. - $bad_config_found = FALSE; - foreach ($files as $count => $file) { - if (!file_exists($file)) { - continue; - } - $contents = advagg_file_get_contents($file); - if (strpos($contents, 'Header unset Last-Modified') !== FALSE) { - $bad_config_found = TRUE; - $requirements['advagg_' . $type . '_last-modified_' . $key . $count] = array( - 'title' => $t('Adv CSS/JS Agg - Last-Modified'), - 'severity' => REQUIREMENT_WARNING, - 'value' => $t('The Last-Modified header is not being sent out by your web server.'), - 'description' => $t('The web servers configuration will need to be adjusted. The server should have sent out a Last-Modified header. Remove "Header unset Last-Modified" inside this file to fix the issue: @file', array('@file' => $file)), - ); - } + } + else { + // Get path to advagg .htaccess file. + $files = array(); + $files[] = $file_path . '/.htaccess'; + $files[] = DRUPAL_ROOT . '/.htaccess'; + + // Check for bad .htaccess files. + $bad_config_found = FALSE; + foreach ($files as $file) { + if (!file_exists($file)) { + continue; } - - // Recommend servers configuration be adjusted. - if (!$bad_config_found) { + $contents = file_get_contents($file); + if (strpos($contents, 'Header unset Last-Modified') !== FALSE) { + $bad_config_found = TRUE; $requirements['advagg_' . $type . '_last-modified_' . $key] = array( 'title' => $t('Adv CSS/JS Agg - Last-Modified'), 'severity' => REQUIREMENT_WARNING, 'value' => $t('The Last-Modified header is not being sent out by your web server.'), - 'description' => $t('The web servers configuration will need to be adjusted. The server should have sent out a Last-Modified header and/or an Etag header. If you can not get the Last-Modified header to work, you can try using ETags. Inside <code>@htaccess</code> right before <code>@line1</code> at the bottom of the file add in <code>@line2</code>. You will also need to remove the <code>@line3</code> line further up.', array( - '@htaccess' => $file_path . '/.htaccess', - '@line1' => '</FilesMatch>', - '@line2' => 'FileETag MTime Size', - '@line3' => 'Header unset ETag', - )), + 'description' => $t('The web servers configuration will need to be adjusted. The server should have sent out a Last-Modified header. Remove "Header unset Last-Modified" inside this file to fix the issue: @file', array('@file' => $file)), ); } } + + // Recommend servers configuration be adjusted. + if (!$bad_config_found) { + $requirements['advagg_' . $type . '_last-modified_' . $key] = array( + 'title' => $t('Adv CSS/JS Agg - Last-Modified'), + 'severity' => REQUIREMENT_WARNING, + 'value' => $t('The Last-Modified header is not being sent out by your web server.'), + 'description' => $t('The web servers configuration will need to be adjusted. The server should have sent out a Last-Modified header.'), + ); + } } } } @@ -2372,13 +1308,11 @@ 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, $type) { +function advagg_install_get_first_advagg_file($directory) { module_load_include('inc', 'advagg', 'advagg.missing'); $filename = ''; @@ -2386,8 +1320,7 @@ function advagg_install_get_first_advagg_file($directory, $type) { $scanned_directory = @scandir($directory); // Bailout here if the advagg directory is empty. if (empty($scanned_directory)) { - // Get a file that will generate from the database. - return advagg_generate_advagg_filename_from_db($type); + return $filename; } // Filter list. $blacklist = array('..', '.', '.htaccess', 'parts'); @@ -2397,7 +1330,7 @@ function advagg_install_get_first_advagg_file($directory, $type) { $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; @@ -2412,42 +1345,10 @@ function advagg_install_get_first_advagg_file($directory, $type) { } } - $gzip_filename = $scanned_directory[$key + 1]; - $br_filename = $scanned_directory[$key + 1]; - if (function_exists('brotli_compress') - && defined('BROTLI_TEXT') - && variable_get('advagg_brotli', ADVAGG_BROTLI) - ) { - $gzip_filename = $scanned_directory[$key + 2]; - // Skip if the next file does not have a .br extension. - // This can occur if: - // - File is not .br compressed, or, - // - Using s3fs module and only .br compression is set. In - // this case, the advagg_advadgg_save_aggregate_alter() - // function will not add a file extension. - if (strcmp($filename . '.br', $br_filename) !== 0 - && (!module_exists('s3fs') - || (module_exists('s3fs') && variable_get('advagg_gzip', ADVAGG_GZIP)))) { - continue; - } - } - else { - // Skip if the next file is a .br file. - if (strcmp($filename . '.br', $br_filename) === 0) { - continue; - } - } - + $gzip_filename = $scanned_directory[$key+1]; if (variable_get('advagg_gzip', ADVAGG_GZIP)) { - // Skip if the next file does not have a .gz extension. - // This can occur if: - // - File is not .gz compressed, or, - // - Using s3fs module and either: - // - Only .gz compression option is set or, - // - Both .gz and .br compression options are set. In - // this case, the advagg_advagg_save_aggregate_alter() - // function creates a .gz file by default. - if (strcmp($filename . '.gz', $gzip_filename) !== 0 && !module_exists('s3fs')) { + // Skip if the next file is not a .gz file. + if (strcmp($filename . '.gz', $gzip_filename) !== 0) { continue; } } @@ -2472,9 +1373,6 @@ function advagg_install_get_first_advagg_file($directory, $type) { } } - if (empty($filename)) { - return advagg_generate_advagg_filename_from_db($type); - } return $filename; } @@ -2497,6 +1395,12 @@ function advagg_install_url_mod(&$url, array &$options, $mod_url = FALSE) { 'connect_timeout' => 8, 'ttfb_timeout' => 8, ); + if (!empty($_SERVER['HTTP_HOST'])) { + $options['headers']['Host'] = $_SERVER['HTTP_HOST']; + } + elseif (!empty($_SERVER['SERVER_NAME'])) { + $options['headers']['Host'] = $_SERVER['SERVER_NAME']; + } // Set connection to closed to prevent keep-alive from causing a timeout. $options['headers']['Connection'] = 'close'; // Set referrer to current page. @@ -2507,39 +1411,16 @@ 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); @@ -2552,20 +1433,14 @@ function advagg_install_url_mod(&$url, array &$options, $mod_url = FALSE) { $new_url = httprl_build_url_self($path); } else { - if (!empty($_SERVER['HTTP_HOST'])) { - if ($parts['host'] != $_SERVER['HTTP_HOST']) { - $parts['host'] = $_SERVER['HTTP_HOST']; - } + if (!empty($_SERVER['HTTP_HOST']) && $parts['host'] != $_SERVER['HTTP_HOST']) { + $parts['host'] = $_SERVER['HTTP_HOST']; } - elseif (!empty($_SERVER['SERVER_NAME'])) { - if ($parts['host'] != $_SERVER['SERVER_NAME']) { - $parts['host'] = $_SERVER['SERVER_NAME']; - } + elseif (!empty($_SERVER['SERVER_NAME']) && $parts['host'] != $_SERVER['SERVER_NAME']) { + $parts['host'] = $_SERVER['SERVER_NAME']; } - elseif (!empty($_SERVER['SERVER_ADDR'])) { - if ($parts['host'] != $_SERVER['SERVER_ADDR']) { - $parts['host'] = $_SERVER['SERVER_ADDR']; - } + elseif (!empty($_SERVER['SERVER_ADDR']) && $parts['host'] != $_SERVER['SERVER_ADDR']) { + $parts['host'] = $_SERVER['SERVER_ADDR']; } else { $parts['host'] = '127.0.0.1'; @@ -2578,291 +1453,3 @@ function advagg_install_url_mod(&$url, array &$options, $mod_url = FALSE) { } $url = $new_url; } - -/** - * 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 multiple 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; -} - -/** - * Convert the table to the specified collation. - * - * @param string $table_name - * Perform the operation on this table. - * @param array $fields - * An array of field names. - * @param string $collation - * The db collation to change to table columns to. - * @param array $schema_fields - * An array of field definitions. - * - * @return array - * Returns an array of tables and column names. - */ -function advagg_install_change_table_collation($table_name, array $fields, $collation, array $schema_fields) { - $db_type = Database::getConnection()->databaseType(); - // Skip if not MySQL. - if ($db_type !== 'mysql') { - return FALSE; - } - $table_name = Database::getConnection()->prefixTables('{' . db_escape_table($table_name) . '}'); - $results = db_query("SHOW FULL FIELDS FROM $table_name")->fetchAllAssoc('Field'); - $db_schema = Database::getConnection()->schema(); - foreach ($results as $row) { - if (!in_array($row->Field, $fields)) { - continue; - } - - $charset = strtolower(substr($collation, 0, strpos($collation, '_'))); - $query = "ALTER TABLE $table_name CHANGE `{$row->Field}` `{$row->Field}` {$row->Type}"; - $query .= " CHARACTER SET $charset COLLATE $collation"; - - if (isset($schema_fields[$row->Field]['not null'])) { - if ($schema_fields[$row->Field]['not null']) { - $query .= ' NOT NULL'; - } - else { - $query .= ' NULL'; - } - } - - // $schema_fields[$row->Field]['default'] can be NULL, so we explicitly - // check for the key here. - 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 . "'"; - } - elseif (!isset($default)) { - $default = 'NULL'; - } - $query .= ' DEFAULT ' . $default; - } - - if (empty($schema_fields[$row->Field]['not null']) && !isset($schema_fields[$row->Field]['default'])) { - $query .= ' DEFAULT NULL'; - } - - // Add column comment. - if (!empty($schema_fields[$row->Field]['description'])) { - $query .= ' COMMENT ' . $db_schema->prepareComment($schema_fields[$row->Field]['description'], 255); - } - - db_query($query); - } -} - -/** - * Callback to delete files if size == 0 and modified more than 60 seconds ago. - * - * @param string $uri - * Location of the file to check. - */ -function advagg_install_delete_empty_file_if_stale($uri) { - // Set stale file threshold to 60 seconds. - if (filesize($uri) < 3 && REQUEST_TIME - filemtime($uri) > 60) { - file_unmanaged_delete($uri); - } -} - -/** - * Callback to delete files if size == 0 and modified more than 60 seconds ago. - * - * @param string $has_string - * If the .htaccess file contains this string it will be removed and - * recreated. - * @param string $does_not_have_string - * If the .htaccess file does not contains this string it will be removed & - * recreated. - * - * @return string - * Translated string indicating what was done. - */ -function advagg_install_update_htaccess($has_string = '', $does_not_have_string = '') { - // Make sure the advagg_get_root_files_dir() function is available. - drupal_load('module', 'advagg'); - - // Get paths to .htaccess file. - list($css_path, $js_path) = advagg_get_root_files_dir(); - $files['css'] = $css_path[0] . '/.htaccess'; - $files['js'] = $js_path[0] . '/.htaccess'; - - // Check for old .htaccess files. - $something_done = FALSE; - foreach ($files as $type => $uri) { - if (!file_exists($uri)) { - unset($files[$type]); - continue; - } - $contents = advagg_file_get_contents($uri); - // Remove old .htaccess file if it has this string. - if (!empty($has_string) && strpos($contents, $has_string) !== FALSE) { - drupal_unlink($uri); - $something_done = TRUE; - } - // Remove old .htaccess file if it does not have this string. - if (!empty($does_not_have_string) && strpos($contents, $does_not_have_string) === FALSE) { - drupal_unlink($uri); - $something_done = TRUE; - } - } - - // Create the new .htaccess file. - $new_htaccess = FALSE; - if (!empty($files) && $something_done && variable_get('advagg_htaccess_check_generate', ADVAGG_HTACCESS_CHECK_GENERATE)) { - // Make the advagg_htaccess_check_generate() function available. - module_load_include('inc', 'advagg', 'advagg.missing'); - foreach ($files as $type => $uri) { - advagg_htaccess_check_generate(array($uri => $type), $type, TRUE); - $new_htaccess = TRUE; - } - } - - // Output info. - if ($something_done) { - if ($new_htaccess) { - return t('Removed the old .htaccess file and put in a new one.'); - } - else { - return t('Removed the old .htaccess file.'); - } - } - else { - return t('Nothing needed to be done.'); - } -} - -/** - * See if the .htaccess file uses the RewriteBase directive. - * - * @param string $type - * Either css or js. - * - * @return bool - * FALSE if the ErrorDocument 404 statement is incorrect. - */ -function advagg_install_htaccess_errordocument($type) { - list($css_path, $js_path) = advagg_get_root_files_dir(); - if ($type === 'css') { - $location = $css_path[1] . '/.htaccess'; - } - if ($type === 'js') { - $location = $js_path[1] . '/.htaccess'; - } - $good = TRUE; - - // Get the location of the 404 error doc. - if (is_readable($location)) { - $htaccess = advagg_file_get_contents($location); - $matches = array(); - $found = preg_match_all('/\\n\s*ErrorDocument\s*404\s*(.*)/i', $htaccess, $matches); - if ($found && !empty($matches[0])) { - $matches[1] = array_map('trim', $matches[1]); - $location = array_pop($matches[1]); - } - } - else { - return $good; - } - // If it's pointing to the wrong place or doesn't exist return FALSE. - if (!empty($location) && $location !== "{$GLOBALS['base_path']}index.php") { - $good = FALSE; - } - if (empty($location) && $GLOBALS['base_path'] !== '/') { - $good = FALSE; - } - - return $good; -} - -/** - * Create proxy settings. - * - * @return string - * Apache httpd.conf settings for the s3fs module. - */ -function advagg_install_s3fs_proxy_settings() { - list($css_path, $js_path) = advagg_get_root_files_dir(); - - $position = strpos($css_path[0], '://'); - $dir = ''; - if ($position !== FALSE) { - $dir = substr($css_path[0], $position + 3); - } - $css_target = str_replace($dir, '', file_create_url($css_path[0])); - $position = strpos($js_path[0], '://'); - $dir = ''; - if ($position !== FALSE) { - $dir = substr($js_path[0], $position + 3); - } - $js_target = str_replace($dir, '', file_create_url($js_path[0])); - $scheme = parse_url($js_target, PHP_URL_SCHEME); - - $config = ''; - $extra = ''; - if ($scheme === 'http') { - $port = '80'; - } - elseif ($scheme === 'https') { - $port = '443'; - $extra = " SSLProxyEngine on\n"; - } - $config .= "<VirtualHost *:$port>\n"; - $config .= " ProxyRequests Off\n"; - $config .= $extra; - $config .= " <Proxy *>\n"; - $config .= " Order deny,allow\n"; - $config .= " Allow from all\n"; - $config .= " </Proxy>\n"; - $config .= " ProxyTimeout 4\n"; - $config .= " ProxyPass {$GLOBALS['base_path']}s3fs-css/ $css_target\n"; - $config .= " ProxyPassReverse {$GLOBALS['base_path']}s3fs-css/ $css_target\n"; - $config .= " ProxyPass {$GLOBALS['base_path']}s3fs-js/ $js_target\n"; - $config .= " ProxyPassReverse {$GLOBALS['base_path']}s3fs-js/ $js_target\n"; - $config .= " ProxyErrorOverride On\n"; - $config .= " ErrorDocument 404 {$GLOBALS['base_path']}index.php\n"; - $config .= "</VirtualHost>\n"; - - return $config; -} diff --git a/sites/all/modules/contrib/advagg/advagg.make.example b/sites/all/modules/contrib/advagg/advagg.make.example deleted file mode 100644 index 1971310e1e..0000000000 --- a/sites/all/modules/contrib/advagg/advagg.make.example +++ /dev/null @@ -1,50 +0,0 @@ -; Rename this file to advagg.make to have it included within a drush make -; command. - -api = 2 -core = 7.x - -; Contrib module: libraries -; --------------------------------------- -projects[libraries][version] = "2" - -; JavaScript libraries -; --------------------------------------- - -; Library: CSSLint. -libraries[csslint][download][type] = git -libraries[csslint][download][url] = https://github.com/CSSLint/csslint - -; Library: fontfaceobserver. -libraries[fontfaceobserver][download][type] = git -libraries[fontfaceobserver][download][url] = https://github.com/bramstein/fontfaceobserver.git - -; Library: jshint. -libraries[jshint][download][type] = git -libraries[jshint][download][url] = https://github.com/jshint/jshint - -; Library: JShrink. -libraries[JShrink][download][type] = git -libraries[JShrink][download][url] = https://github.com/tedious/JShrink - -; Library: JSMinPlus. -libraries[jsminplus][download][type] = git -libraries[jsminplus][download][url] = https://github.com/JSMinPlus/JSMinPlus -libraries[jsminplus][directory_name] = "jsminplus" - -; Library: jspacker. -libraries[jspacker][download][type] = git -libraries[jspacker][download][url] = https://github.com/tholu/php-packer -libraries[jspacker][directory_name] = "jspacker" - -; Library: jsqueeze. -libraries[jsqueeze][download][type] = git -libraries[jsqueeze][download][url] = https://github.com/tchwork/jsqueeze - -; Library: loadCSS. -libraries[loadCSS][download][type] = git -libraries[loadCSS][download][url] = https://github.com/filamentgroup/loadCSS - -; Library: YUI-CSS-compressor-PHP-port. -libraries[YUI-CSS-compressor-PHP-port][download][type] = git -libraries[YUI-CSS-compressor-PHP-port][download][url] = https://github.com/tubalmartin/YUI-CSS-compressor-PHP-port diff --git a/sites/all/modules/contrib/advagg/advagg.missing.inc b/sites/all/modules/contrib/advagg/advagg.missing.inc index 2bd1dddbdf..4ab239eb49 100644 --- a/sites/all/modules/contrib/advagg/advagg.missing.inc +++ b/sites/all/modules/contrib/advagg/advagg.missing.inc @@ -17,40 +17,6 @@ function advagg_missing_aggregate($input = '') { // Generate missing file. $msg = advagg_missing_generate($input); - if (module_exists('jquery_update')) { - $arg = arg(); - $filename = array_pop($arg); - $filename = explode('?', $filename); - $filename = array_shift($filename); - if (strpos($filename, 'min.map') !== FALSE && strpos($filename, 'jquery') !== FALSE) { - // Get filename from request. - $wrong_pattern = t('Wrong pattern.'); - if ($msg === $wrong_pattern) { - $version = variable_get('jquery_update_jquery_version', '1.10'); - $trueversion = '1.9.1'; - switch ($version) { - case '1.9': - $trueversion = '1.9.1'; - break; - - case '1.10': - $trueversion = '1.10.2'; - break; - - case '1.11': - $trueversion = '1.11.2'; - break; - - case '2.1': - $trueversion = '2.1.4'; - break; - } - $url = "https://cdn.jsdelivr.net/gh/jquery/jquery@$trueversion/jquery.min.map"; - drupal_goto($url, array('external' => TRUE), 301); - } - } - } - // If here send out fast 404. advagg_missing_fast404($msg); } @@ -80,12 +46,9 @@ function advagg_missing_generate($input = '') { if (is_string($data) || !is_array($data)) { // Try again with the function input. $filename = $input; - $data1 = advagg_get_hashes_from_filename($filename); + $data = advagg_get_hashes_from_filename($filename); if (is_string($data) || !is_array($data)) { - return "$data $data1"; - } - else { - $data = $data1; + return $data; } } @@ -112,17 +75,7 @@ function advagg_missing_generate($input = '') { $uri = $GLOBALS['base_path'] . $_GET['q']; $created = FALSE; $files_to_save = array(); - if (variable_get('advagg_no_locks', ADVAGG_NO_LOCKS)) { - $return = advagg_missing_create_file($filename, FALSE, $data); - if (!is_array($return)) { - return $return; - } - else { - list($files_to_save, $type) = $return; - $created = TRUE; - } - } - elseif (lock_acquire($lock_name, 10) || $redirect_counter > 4) { + if (lock_acquire($lock_name, 10) || $redirect_counter > 4) { if ($redirect_counter > 4) { $return = advagg_missing_create_file($filename, TRUE, $data); } @@ -168,7 +121,7 @@ function advagg_missing_generate($input = '') { } /** - * Given the filename, type, and settings, create absolute URL for 307 redirect. + * Given the filename, type, & settings, create absolute URL for 307 redirect. * * Due to url inbound alter we can not trust that the redirect will work if * using $GLOBALS['base_path'] . $_GET['q']. Generate the uri as if it was @@ -193,10 +146,6 @@ function advagg_generate_location_uri($filename, $type, array $aggregate_setting $uri_307 = $js_path[0] . '/' . $filename; } - if (empty($aggregate_settings)) { - $aggregate_settings = advagg_current_hooks_hash_array(); - } - // 307s need to be absolute. RFC 2616 14.30. $aggregate_settings['variables']['advagg_convert_absolute_to_relative_path'] = FALSE; $aggregate_settings['variables']['advagg_convert_absolute_to_protocol_relative_path'] = FALSE; @@ -227,35 +176,16 @@ 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(); - } - - $return_compressed_br = FALSE; - $return_compressed_gz = FALSE; // Negotiate whether to use gzip compression. - if (!empty($_SERVER['HTTP_ACCEPT_ENCODING'])) { - if (strpos($_SERVER['HTTP_ACCEPT_ENCODING'], 'br') !== FALSE) { - $return_compressed_br = TRUE; - } - if (strpos($_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip') !== FALSE) { - $return_compressed_gz = TRUE; - } - } + $return_compressed = isset($_SERVER['HTTP_ACCEPT_ENCODING']) && strpos($_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip') !== FALSE; header('Vary: Accept-Encoding', FALSE); if (!empty($created)) { - if ($return_compressed_br && file_exists($uri . '.br') && filesize($uri . '.br') > 0) { - $uri .= '.br'; - } - elseif ($return_compressed_gz && file_exists($uri . '.gz') && filesize($uri . '.gz') > 0) { + if ($return_compressed && file_exists($uri . '.gz') && filesize($uri . '.gz') > 0) { $uri .= '.gz'; } if (!isset($files_to_save[$uri]) && file_exists($uri) && filesize($uri)) { - // Do not use advagg_file_get_contents here. - $files_to_save[$uri] = (string) @file_get_contents($uri); + $files_to_save[$uri] = file_get_contents($uri); } } @@ -267,23 +197,10 @@ function advagg_missing_send_saved_file(array $files_to_save, $uri, $created, $f ob_end_clean(); } // Set generic far future headers. - if (variable_get('advagg_farfuture_php', ADVAGG_FARFUTURE_PHP)) { - advagg_missing_set_farfuture_headers(); - } + advagg_missing_set_farfuture_headers(); // Return compressed content if we can. - if ($return_compressed_br || $return_compressed_gz) { + if ($return_compressed) { foreach ($files_to_save as $uri => $data) { - // See if this uri contains .br near the end of it. - $pos = strripos($uri, '.br', 91 + strlen(ADVAGG_SPACE) * 3); - if (!empty($pos)) { - $len = strlen($uri); - if ($pos == $len - 3) { - // .br file exists, send it out. - header('Content-Encoding: br'); - break; - } - } - // See if this uri contains .gz near the end of it. $pos = strripos($uri, '.gz', 91 + strlen(ADVAGG_SPACE) * 3); if (!empty($pos)) { @@ -302,49 +219,14 @@ 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. - $default = ADVAGG_HTTP_200_CODE; - if (module_exists('httprl') - && variable_get('advagg_use_httprl', ADVAGG_USE_HTTPRL) - && (is_callable('httprl_is_background_callback_capable') - && httprl_is_background_callback_capable() - || !is_callable('httprl_is_background_callback_capable') - ) - ) { - // Use 203 instead of 200 if HTTPRL is being used. - $default = 203; - } - $number = variable_get('advagg_http_200_code', $default); - header("{$_SERVER['SERVER_PROTOCOL']} $number OK"); + header($_SERVER['SERVER_PROTOCOL'] . " 200 OK"); // Insure the Last-Modified header is set so 304's work correctly. + // @ignore druplart_andor_assignment if (file_exists($uri) && $filemtime = @filemtime($uri)) { header('Last-Modified: ' . gmdate('D, d M Y H:i:s \G\M\T', $filemtime)); - // 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. - if (variable_get('advagg_farfuture_php', ADVAGG_FARFUTURE_PHP)) { - 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"); @@ -376,7 +258,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, @@ -398,12 +280,7 @@ function advagg_missing_set_farfuture_headers() { // Set a far future Cache-Control header (52 weeks), which prevents // intermediate caches from transforming the data and allows any // intermediate cache to cache it, since it's marked as a public resource. - if (variable_get('advagg_resource_hints_use_immutable', ADVAGG_RESOURCE_HINTS_USE_IMMUTABLE)) { - header('Cache-Control: max-age=31449600, no-transform, public, immutable'); - } - else { - header('Cache-Control: max-age=31449600, no-transform, public'); - } + header('Cache-Control: max-age=31449600, no-transform, public'); } /** @@ -413,7 +290,7 @@ function advagg_missing_set_farfuture_headers() { * Just the filename no path information. * @param bool $no_alters * (optional) Set to TRUE to do the bare amount of processing on the file. - * @param mixed $data + * @param array $data * (optional) Output from advagg_get_hashes_from_filename(). * * @return mixed @@ -421,9 +298,6 @@ function advagg_missing_set_farfuture_headers() { * On success the $files_to_save array. */ function advagg_missing_create_file($filename, $no_alters = FALSE, $data = array()) { - // Option to still delever the file if fatal error. - register_shutdown_function("advagg_missing_fatal_handler", $filename); - if (empty($data)) { $data = advagg_get_hashes_from_filename($filename); } @@ -434,10 +308,6 @@ function advagg_missing_create_file($filename, $no_alters = FALSE, $data = array return $data; } - if (empty($aggregate_settings)) { - $aggregate_settings = advagg_current_hooks_hash_array(); - } - // Set no alters if this is the last chance of generating the aggregate. if ($no_alters) { $aggregate_settings['settings']['no_alters'] = TRUE; @@ -450,14 +320,12 @@ function advagg_missing_create_file($filename, $no_alters = FALSE, $data = array } // Save aggregate file. - list($files_to_save, $errors) = advagg_save_aggregate($filename, $files, $type, $aggregate_settings); + $files_to_save = advagg_save_aggregate($filename, $files, $type, $aggregate_settings); // Update atime. - advagg_multi_update_atime(array( - array( - 'aggregate_filenames_hash' => $aggregate_filenames_hash, - 'aggregate_contents_hash' => $aggregate_contents_hash, - ), - )); + advagg_multi_update_atime(array(array( + 'aggregate_filenames_hash' => $aggregate_filenames_hash, + 'aggregate_contents_hash' => $aggregate_contents_hash, + ))); // Make sure .htaccess file exists in the advagg dir. if (variable_get('advagg_htaccess_check_generate', ADVAGG_HTACCESS_CHECK_GENERATE)) { advagg_htaccess_check_generate($files_to_save, $type); @@ -471,7 +339,6 @@ function advagg_missing_create_file($filename, $no_alters = FALSE, $data = array $aggregate_contents_hash, $aggregate_settings, $files, - $errors, ); } @@ -484,96 +351,26 @@ 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) { - list($css_path, $js_path) = advagg_get_root_files_dir(); - $content_type = $type; if ($content_type === 'js') { $content_type = 'javascript'; - $advagg_dir = basename($js_path[1]); } - elseif ($content_type === 'css') { - $advagg_dir = basename($css_path[1]); - } - $type_upper = strtoupper($type); - $data = "\n"; - // Some hosting companies do not allow "FollowSymLinks" but will support - // "SymLinksIfOwnerMatch". - if (variable_get('advagg_htaccess_symlinksifownermatch', ADVAGG_HTACCESS_SYMLINKSIFOWNERMATCH)) { - $data .= "Options +SymLinksIfOwnerMatch\n"; - } - else { - $data .= "Options +FollowSymLinks\n"; - } - if ($GLOBALS['base_path'] !== '/') { - $data .= "ErrorDocument 404 {$GLOBALS['base_path']}index.php\n"; - } - // See if RewriteBase is needed. - $advagg_htaccess_rewritebase = trim(variable_get('advagg_htaccess_rewritebase', ADVAGG_HTACCESS_REWRITEBASE)); - if (!empty($advagg_htaccess_rewritebase) && !empty($advagg_dir)) { - $rewrite_base_rule = str_replace('//', '/', $advagg_htaccess_rewritebase . '/' . $advagg_dir); - $data .= "RewriteBase $rewrite_base_rule\n"; - } - - $data .= "\n"; - $data .= "<IfModule mod_rewrite.c>\n"; - $data .= " RewriteEngine on\n"; - $data .= " <IfModule mod_headers.c>\n"; - $data .= " # Serve brotli compressed ${type_upper} files if they exist and the client accepts br.\n"; - $data .= " RewriteCond %{HTTP:Accept-encoding} br\n"; - $data .= " RewriteCond %{REQUEST_FILENAME}\.br -s\n"; - $data .= " RewriteRule ^(.*)\.${type} " . '$1' . "\.${type}\.br [QSA]\n"; - if ($type === 'css') { - $data .= " RewriteRule \.${type}\.br$ - [T=text/${content_type},E=no-gzip:1]\n"; - } - else { - $data .= " RewriteRule \.${type}\.br$ - [T=application/${content_type},E=no-gzip:1]\n"; - } - $data .= "\n"; - $data .= " <FilesMatch \"\.${type}\.br$\">\n"; - $data .= " # Serve correct encoding type.\n"; - $data .= " Header set Content-Encoding br\n"; - $data .= " # Force proxies to cache gzipped & non-gzipped css/js files separately.\n"; - $data .= " Header append Vary Accept-Encoding\n"; - $data .= " </FilesMatch>\n"; - $data .= "\n"; - $data .= " # Serve gzip compressed ${type_upper} files if they exist and the client accepts gzip.\n"; - $data .= " RewriteCond %{HTTP:Accept-encoding} gzip\n"; - $data .= " RewriteCond %{REQUEST_FILENAME}\.gz -s\n"; - $data .= " RewriteRule ^(.*)\.${type} " . '$1' . "\.${type}\.gz [QSA]\n"; - if ($type === 'css') { - $data .= " RewriteRule \.${type}\.gz$ - [T=text/${content_type},E=no-gzip:1]\n"; - } - else { - $data .= " RewriteRule \.${type}\.gz$ - [T=application/${content_type},E=no-gzip:1]\n"; - } - $data .= "\n"; - $data .= " <FilesMatch \"\.${type}\.gz$\">\n"; - $data .= " # Serve correct encoding type.\n"; - $data .= " Header set Content-Encoding gzip\n"; - $data .= " # Force proxies to cache gzipped & non-gzipped css/js files separately.\n"; - $data .= " Header append Vary Accept-Encoding\n"; - $data .= " </FilesMatch>\n"; - $data .= " </IfModule>\n"; - $data .= "</IfModule>\n"; - $data .= "\n"; - $data .= "<FilesMatch \"^${type}" . ADVAGG_SPACE . "[A-Za-z0-9-_]{43}" . ADVAGG_SPACE . "[A-Za-z0-9-_]{43}" . ADVAGG_SPACE . "[A-Za-z0-9-_]{43}.${type}(\.gz|\.br)?\">\n"; - $data .= " # No mod_headers. Apache module headers is not enabled.\n"; + // @ignore style_lowercase_html:45 + $data = "\n"; + $data .= "<FilesMatch \"^${type}" . ADVAGG_SPACE . "[A-Za-z0-9-_]{43}" . ADVAGG_SPACE . "[A-Za-z0-9-_]{43}" . ADVAGG_SPACE . "[A-Za-z0-9-_]{43}.${type}(\.gz)?\">\n"; + $data .= " # No mod_headers\n"; $data .= " <IfModule !mod_headers.c>\n"; - $data .= " # No mod_expires. Apache module expires is not enabled.\n"; + $data .= " # No mod_expires\n"; $data .= " <IfModule !mod_expires.c>\n"; $data .= " # Use ETags.\n"; $data .= " FileETag MTime Size\n"; $data .= " </IfModule>\n"; $data .= " </IfModule>\n"; $data .= "\n"; - $data .= " # Use Expires Directive if apache module expires is enabled.\n"; + $data .= " # Use Expires Directive.\n"; $data .= " <IfModule mod_expires.c>\n"; $data .= " # Do not use ETags.\n"; $data .= " FileETag None\n"; @@ -583,7 +380,6 @@ function advagg_htaccess_check_generate(array $files_to_save, $type, $force = FA $data .= " ExpiresDefault A31449600\n"; $data .= " </IfModule>\n"; $data .= "\n"; - $data .= " # Use Headers Directive if apache module headers is enabled.\n"; $data .= " <IfModule mod_headers.c>\n"; $data .= " # Do not use etags for cache validation.\n"; $data .= " Header unset ETag\n"; @@ -596,20 +392,10 @@ function advagg_htaccess_check_generate(array $files_to_save, $type, $force = FA } $data .= " <IfModule !mod_expires.c>\n"; $data .= " # Set a far future Cache-Control header to 52 weeks.\n"; - if (variable_get('advagg_resource_hints_use_immutable', ADVAGG_RESOURCE_HINTS_USE_IMMUTABLE)) { - $data .= " Header set Cache-Control \"max-age=31449600, no-transform, public, immutable\"\n"; - } - else { - $data .= " Header set Cache-Control \"max-age=31449600, no-transform, public\"\n"; - } + $data .= " Header set Cache-Control \"max-age=31449600, no-transform, public\"\n"; $data .= " </IfModule>\n"; $data .= " <IfModule mod_expires.c>\n"; - if (variable_get('advagg_resource_hints_use_immutable', ADVAGG_RESOURCE_HINTS_USE_IMMUTABLE)) { - $data .= " Header append Cache-Control \"no-transform, public, immutable\"\n"; - } - else { - $data .= " Header append Cache-Control \"no-transform, public\"\n"; - } + $data .= " Header append Cache-Control \"no-transform, public\"\n"; $data .= " </IfModule>\n"; $data .= " </IfModule>\n"; if ($type === 'css') { @@ -620,7 +406,6 @@ function advagg_htaccess_check_generate(array $files_to_save, $type, $force = FA } $data .= "</FilesMatch>\n"; - $errors = array(); foreach (array_keys($files_to_save) as $uri) { $dir = dirname($uri); $htaccess_file = $dir . '/.htaccess'; @@ -628,11 +413,11 @@ function advagg_htaccess_check_generate(array $files_to_save, $type, $force = FA continue; } - $errors = advagg_save_data($htaccess_file, $data, $force); + advagg_save_data($htaccess_file, $data, $force); } - return $errors; } +// Lookup functions. /** * Given a filename return the type and 2 hashes. * @@ -679,12 +464,7 @@ function advagg_get_hashes_from_filename($filename, $skip_hash_settings = FALSE) // Verify that the hooks hashes is valid. $aggregate_settings = advagg_get_hash_settings($hooks_hashes_value); if (empty($aggregate_settings)) { - if (!variable_get('advagg_weak_file_verification', ADVAGG_WEAK_FILE_VERIFICATION)) { - return t('Bad hooks hashes value.'); - } - elseif (variable_get('advagg_debug', ADVAGG_DEBUG) >= 2) { - watchdog('advagg-debug', 'File @filename has an empty aggregate_settings variable; the 3rd hash is incorrect.', array('@filename' => $filename), WATCHDOG_DEBUG); - } + return t('Bad hooks hashes value.'); } } @@ -720,7 +500,7 @@ function advagg_get_files_from_hashes($type, $aggregate_filenames_hash, $aggrega array(':aggregate_filenames_hash' => $aggregate_filenames_hash)); // Create join query for the advagg_files table. $query->join('advagg_files', 'af', 'af.filename_hash = aa.filename_hash AND - af.filetype = :type AND af.filesize > 0', array(':type' => $type)); + af.filetype = :type', array(':type' => $type)); // Select fields and ordering of the query; add in query comment as well. $query = $query->fields('af', array('filename')) ->fields($subquery_aggregates, array('settings')) @@ -733,42 +513,10 @@ function advagg_get_files_from_hashes($type, $aggregate_filenames_hash, $aggrega foreach ($results as $value) { $files[$value->filename] = unserialize($value->settings); } - - // Try again with weak file verification. - if (empty($files) && variable_get('advagg_weak_file_verification', ADVAGG_WEAK_FILE_VERIFICATION)) { - if (variable_get('advagg_debug', ADVAGG_DEBUG) >= 2) { - watchdog('advagg-debug', 'Filehash @filename of type @type has an aggregate_contents_hash variable; the 2rd hash is incorrect.', array( - '@filename' => $aggregate_filenames_hash, - '@type' => $type, - ), WATCHDOG_DEBUG); - } - - // Create main query for the advagg_aggregates_versions table. - $query = db_select('advagg_aggregates_versions', 'aav') - ->condition('aav.aggregate_filenames_hash', $aggregate_filenames_hash); - // Create join query for the advagg_aggregates table. - $subquery_aggregates = $query->join('advagg_aggregates', 'aa', 'aa.aggregate_filenames_hash = - aav.aggregate_filenames_hash AND aa.aggregate_filenames_hash = :aggregate_filenames_hash', - array(':aggregate_filenames_hash' => $aggregate_filenames_hash)); - // Create join query for the advagg_files table. - $query->join('advagg_files', 'af', 'af.filename_hash = aa.filename_hash AND - af.filetype = :type AND af.filesize > 0', array(':type' => $type)); - // Select fields and ordering of the query; add in query comment as well. - $query = $query->fields('af', array('filename')) - ->fields($subquery_aggregates, array('settings')) - ->orderBy('porder', 'ASC'); - $query->comment('Query called from ' . __FUNCTION__ . '()'); - $results = $query->execute(); - - // Add in files that are included in this aggregate. - $files = array(); - foreach ($results as $value) { - $files[$value->filename] = unserialize($value->settings); - } - } return $files; } +// Read CSS/JS files. /** * Given a list of files, grab their contents and glue it into one big string. * @@ -776,14 +524,11 @@ 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, $aggregate_filename = '') { - $write_aggregate = TRUE; +function advagg_get_css_aggregate_contents(array $files, array $aggregate_settings) { // Check if CSS compression is enabled. $optimize = TRUE; if (!empty($aggregate_settings['settings']['no_alters'])) { @@ -793,9 +538,6 @@ 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; @@ -808,77 +550,16 @@ function advagg_get_css_aggregate_contents(array $files, array $aggregate_settin $last_media = $settings['media']; continue; } - if ($settings['media'] !== $last_media) { + if ($settings['media'] != $last_media) { $media_changes = TRUE; break; } } - if ($media_changes) { - $global_file_media = 'all'; - } - else { - $global_file_media = $last_media; - } - - // https://developer.mozilla.org/en-US/docs/Web/Guide/CSS/Media_queries - $media_types = array( - 'all', - 'aural', - 'braille', - 'handheld', - 'print', - 'projection', - 'screen', - 'tty', - 'tv', - 'embossed', - ); - + $last_media = NULL; $import_statements = array(); module_load_include('inc', 'advagg', 'advagg'); - $original_settings = array($optimize, $aggregate_settings); foreach ($files as $file => $settings) { - $media_changes = FALSE; - if (!isset($settings['media'])) { - $settings['media'] = ''; - } - - if ($settings['media'] !== $global_file_media) { - $media_changes = TRUE; - } - - list($optimize, $aggregate_settings) = $original_settings; - // Allow other modules to modify aggregate_settings optimize. - // Call hook_advagg_get_css_file_contents_pre_alter(). - if (empty($aggregate_settings['settings']['no_alters'])) { - drupal_alter('advagg_get_css_file_contents_pre', $file, $optimize, $aggregate_settings); - } - if (is_readable($file)) { - // Get the files contents. - $file_contents = (string) @advagg_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 (isset($info_on_files[$cid]) == FALSE || $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 { - // File is not readable. - $write_aggregate = advagg_missing_file_not_readable($file, $aggregate_filename, TRUE); - } + $contents = advagg_load_css_stylesheet($file, $optimize, $aggregate_settings); // Allow other modules to modify this files contents. // Call hook_advagg_get_css_file_contents_alter(). @@ -890,16 +571,6 @@ function advagg_get_css_aggregate_contents(array $files, array $aggregate_settin $media_blocks = advagg_parse_media_blocks($contents); $contents = ''; - $file_has_type = FALSE; - if (!empty($settings['media'])) { - foreach ($media_types as $media_type) { - if (stripos($settings['media'], $media_type) !== FALSE) { - $file_has_type = TRUE; - break; - } - } - } - foreach ($media_blocks as $css_rules) { if (strpos($css_rules, '@media') !== FALSE) { // Get start and end of the rules for this media query block. @@ -918,55 +589,47 @@ function advagg_get_css_aggregate_contents(array $files, array $aggregate_settin $css_selectors_rules = substr($css_rules, $start + 1, $end - ($start + 1)); // Add in main media rule if needed. - if (!empty($settings['media']) - && strpos($media_rules, $settings['media']) === FALSE - && $settings['media'] !== $global_file_media - ) { - $rule_has_type = FALSE; - if ($file_has_type) { - foreach ($media_types as $media_type) { - if (stripos($media_rules, $media_type) !== FALSE) { - $rule_has_type = TRUE; - break; - } - } - } - if (!$rule_has_type) { - $media_rules = $settings['media'] . ' and ' . $media_rules; - } + if (strpos($media_rules, $settings['media']) === FALSE) { + $media_rules = $settings['media'] . ' ' . $media_rules; } } else { $media_rules = $settings['media']; $css_selectors_rules = $css_rules; } + // Remove the all rule. + $media_rules = str_replace('all', '', $media_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}"; + // Start of stylesheet. + if (is_null($last_media)) { + if (!empty($media_rules)) { + $output = '@media ' . $media_rules . ' {' . $css_selectors_rules; } else { - $css_selectors_rules .= $rules; + $output = $css_selectors_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 = "{$font_face_string} \n@media {$media_rules} {\n {$css_selectors_rules} \n}"; + elseif ($media_rules != $last_media) { + if (!empty($media_rules)) { + if (!empty($last_media)) { + $output = "} \n@media " . $media_rules . ' {' . $css_selectors_rules; + } + else { + $output = "\n@media " . $media_rules . ' {' . $css_selectors_rules; + } + } + else { + $output = "} \n " . $css_selectors_rules; + } } else { - $output = "{$font_face_string} \n {$css_selectors_rules}"; + $output = ' ' . $css_selectors_rules; } - $contents .= trim($output); + $last_media = $media_rules; + $contents .= $output; } } @@ -978,11 +641,11 @@ function advagg_get_css_aggregate_contents(array $files, array $aggregate_settin $contents = preg_replace($regexp, '', $contents); // Add the import statements with the media query of the current file. $import_media = isset($settings['media']) ? $settings['media'] : ''; - $import_media = trim($import_media); + $import_media = trim(str_replace('all', '', $import_media)); $import_statements[] = array($import_media, $matches[0]); // Close any open comment blocks. - $contents .= "\n/*})'\"*/\n"; + $contents .= '/**/'; if (variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL) < 0) { $contents .= "\n/* Above code came from $file */\n\n"; } @@ -990,6 +653,11 @@ function advagg_get_css_aggregate_contents(array $files, array $aggregate_settin $data .= $contents; } + // Close the final media bracket. + if ($media_changes && !empty($last_media)) { + $data .= '}'; + } + // Add import statements to the top of the stylesheet. $import_string = ''; foreach ($import_statements as $values) { @@ -1010,7 +678,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 array($data, $write_aggregate); + return $data; } /** @@ -1020,63 +688,30 @@ 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, $aggregate_filename = '') { - $write_aggregate = TRUE; +function advagg_get_js_aggregate_contents(array $files, array $aggregate_settings) { $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 (is_readable($filename)) { - $file_contents = (string) @advagg_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 (isset($info_on_files[$cid]['content_hash']) && $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); - } - - // Make sure that the file is ended properly. - $file_contents .= "\n;/*})'\"*/\n"; + if (file_exists($filename)) { + $contents .= file_get_contents($filename) . ";/**/\n"; if (variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL) < 0) { - $file_contents .= "/* Above code came from $filename */\n\n"; + $contents .= "/* Above code came from $filename */\n\n"; } - $contents .= $file_contents; - } - else { - // File is not readable. - $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'])) { drupal_alter('advagg_get_js_file_contents', $contents, $filename, $aggregate_settings); } - // Make sure that the file is ended properly. - if (!empty($contents)) { - $contents .= ";/*})'\"*/\n"; - } $data .= $contents; } } @@ -1086,64 +721,10 @@ 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 array($data, $write_aggregate); -} - -/** - * Let other modules know that this file couldn't be found. - * - * @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(); - list($css_path, $js_path) = advagg_get_root_files_dir(); - - // 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 does not start in the - // public dir and the advagg dirs. - if (empty($cache) - && strpos($filename, 'public://') !== 0 - && strpos($filename, $css_path[1]) !== 0 - && strpos($filename, $js_path[1]) !== 0 - ) { - if ($fs_read_failure) { - watchdog('advagg', 'Reading from the file system failed. This can sometimes happen during a deployment and/or a clear cache operation. Filename: %file Aggregate Filename: %aggregate. If this continues to happen go to the <a href="@operations">Operations page</a> and under Drastic Measures - Reset the AdvAgg Files table click the Truncate advagg_files button.', array( - '%file' => $filename, - '%aggregate' => $aggregate_filename, - '@operations' => url('admin/config/development/performance/advagg/operations', array('fragment' => 'edit-reset-advagg-files')), - ), WATCHDOG_WARNING); - } - else { - watchdog('advagg', 'The content hash for %file does not match the stored content hash from the database. Please <a href="@url">flush the advagg cache</a> under Smart Cache Flush. This can sometimes happen during a deployment. Filename: %file Aggregate Filename: %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 30 minutes. - $write_aggregate = TRUE; - } - return $write_aggregate; + return $data; } +// File save functions. /** * Save an aggregate given a filename, the files included in it, and the type. * @@ -1157,35 +738,20 @@ function advagg_missing_file_not_readable($filename, $aggregate_filename = '', $ * Array of settings. * * @return array - * array($files_to_save, $errors). + * $files_to_save array. */ -function advagg_save_aggregate($filename, array $files, $type, array $aggregate_settings = array()) { +function advagg_save_aggregate($filename, array $files, $type, array $aggregate_settings) { list($css_path, $js_path) = advagg_get_root_files_dir(); - $uri = ''; - if ($type === 'css') { - $uri = $css_path[0] . '/' . $filename; - } - elseif ($type === 'js') { - $uri = $js_path[0] . '/' . $filename; - } - - if (empty($aggregate_settings)) { - $aggregate_settings = advagg_current_hooks_hash_array(); - } - - // Allow other modules to alter the location, files included, and settings. - if (empty($aggregate_settings['settings']['no_alters'])) { - // Call hook_advagg_save_aggregate_pre_alter(). - drupal_alter('advagg_save_aggregate_pre', $uri, $files, $aggregate_settings); - } // Build the aggregates contents. $contents = ''; if ($type === 'css') { - list($contents, $write_aggregate) = advagg_get_css_aggregate_contents($files, $aggregate_settings, $filename); + $contents = advagg_get_css_aggregate_contents($files, $aggregate_settings); + $uri = $css_path[0] . '/' . $filename; } elseif ($type === 'js') { - list($contents, $write_aggregate) = advagg_get_js_aggregate_contents($files, $aggregate_settings, $filename); + $contents = advagg_get_js_aggregate_contents($files, $aggregate_settings); + $uri = $js_path[0] . '/' . $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; @@ -1196,55 +762,40 @@ function advagg_save_aggregate($filename, array $files, $type, array $aggregate_ $uri => $contents, ); - // Allow other modules to alter the contents and add new files to save. + // Allow other modules to alter the contents and add new files to save (.gz). // Call hook_advagg_save_aggregate_alter(). $other_parameters = array($files, $type); if (empty($aggregate_settings['settings']['no_alters'])) { drupal_alter('advagg_save_aggregate', $files_to_save, $aggregate_settings, $other_parameters); } - $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. !errors. %full_dir.', array( - '%uri' => $uri, - '!errors' => print_r($errors, TRUE), - '%full_dir' => $full_dir, - ), WATCHDOG_ALERT); - } - elseif (!is_writable($full_dir)) { - watchdog('advagg', 'Write to file system failed. Check directory permissions. %uri. !errors. %full_dir.', array( - '%uri' => $uri, - '!errors' => print_r($errors, TRUE), - '%full_dir' => $full_dir, - ), WATCHDOG_ERROR); - } - else { - watchdog('advagg', 'Write to file system failed. %uri. !errors. %full_dir.', array( - '%uri' => $uri, - '!errors' => print_r($errors, TRUE), - '%full_dir' => $full_dir, - ), 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); - } + 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); } } } - return array($files_to_save, $errors); + return $files_to_save; } /** @@ -1259,33 +810,14 @@ 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); - - // Prepare dir if needed. - $dir = dirname($uri); - $dir_good = file_prepare_directory($dir, FILE_CREATE_DIRECTORY); - if (!$dir_good) { - $errors[1] = $t('The directory for @file can not be created or is not writable.', array('@file' => $uri)); - return $errors; - } - // File already exists. if (!$overwrite && file_exists($uri) && filesize($uri) > 0) { - if (variable_get('advagg_debug', ADVAGG_DEBUG) >= 2) { - watchdog('advagg-debug', 'File @uri exists and overwrite is false.', array('@uri' => $uri), WATCHDOG_DEBUG); - } - $errors[2] = $t('File (@file) already exits.', array('@file' => $uri)); - return $errors; + return; } // If data is empty, write a space. @@ -1297,163 +829,51 @@ function advagg_save_data($uri, $data, $overwrite = FALSE) { // writing to the same file, the best option is to create a temporary file in // the same directory and then rename it to the destination. A temporary file // is needed if the directory is mounted on a separate machine; thus ensuring - // the rename command stays local and atomic. + // the rename command stays local. // // Get a temporary filename in the destination directory. - $dir = $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); - if ($wrapper && method_exists($wrapper, 'getDirectoryPath')) { - $wrapper_dir_path = $wrapper->getDirectoryPath(); - if (!empty($wrapper_dir_path)) { - $dir = $wrapper_dir_path . '/' . substr($uri_dir, strlen($scheme . '://')); - $uri = $dir . substr($uri, strlen($uri_dir)); - } - } - } + $dir = drupal_dirname($uri) . '/'; + $temporary_file = drupal_tempnam($dir, 'file_advagg_'); + $temporary_file_copy = $temporary_file; // Get the extension of the original filename and append it to the temp file // name. Preserves the mime type in different stream wrapper implementations. $parts = pathinfo($uri); - if (variable_get('advagg_debug', ADVAGG_DEBUG) >= 2) { - watchdog('advagg-debug', 'Creating URI @uri', array('@uri' => $uri), WATCHDOG_DEBUG); - watchdog('advagg-debug', 'File Parts <pre>@parts</pre>', array('@parts' => print_r($parts, TRUE)), WATCHDOG_DEBUG); - } $extension = '.' . $parts['extension']; - if ($extension === '.gz' || $extension === '.br') { + if ($extension === '.gz') { $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; - // Create temp filename. - $temporary_file = $dir . 'advagg_file_' . drupal_hash_base64(microtime(TRUE) . mt_rand()) . $extension; + // Preform the rename, adding the extension to the temp file. + if (!@rename($temporary_file_copy, $temporary_file)) { + // Remove if rename failed. + @unlink($temporary_file_copy); + } // Save to temporary filename in the destination directory. $filepath = file_unmanaged_save_data($data, $temporary_file, FILE_EXISTS_REPLACE); if ($filepath) { // Perform the rename operation. - if (!advagg_rename($filepath, $uri)) { + $result = @rename($filepath, $uri); + if (!$result) { // Unlink and try again for windows. Rename on windows does not replace // the file if it already exists. - if (variable_get('advagg_debug', ADVAGG_DEBUG) >= 2) { - watchdog('advagg-debug', 'Rename failed. @to', array('@to' => $uri), WATCHDOG_WARNING); - } @unlink($uri); + $result = @rename($filepath, $uri); // Remove temporary_file if rename failed. - if (!advagg_rename($filepath, $uri)) { - $errors[20] = $t('Renaming the filename (@incorrect) to (@correct) failed.', array('@incorrect' => $filepath, '@correct' => $uri)); - + if (!$result) { @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); - } - } - - // Check the filesize. - $file_size = @filesize($uri); - $expected_size = _advagg_string_size_in_bytes($data); - if ($file_size === 0) { - // Zero byte file. - $errors[26] = $t('Write successful, but the file is empty. @file', array('@file' => $filepath)); - watchdog('advagg', 'Write successful, but the file is empty. Target: target. The empty file has been removed. If this error continues, performance will be greatly degraded.', array( - '%target' => $uri, - ), WATCHDOG_ERROR); - // Better to serve straight from Drupal than have a broken file. - @unlink($uri); - } - elseif ($file_size > 0 && $file_size != $expected_size) { - // Data written to disk doesn't match. - $errors[28] = $t('Write successful, but the file is the wrong size. @file Expected size is @expected_size, actual size is @file_size', array( - '@file' => $uri, - '@expected_size' => $expected_size, - '@file_size' => $file_size, - )); - watchdog('advagg', 'Write successful, but the file is the wrong size. %file Expected size is %expected_size, actual size is %file_size. The broken file has been removed. If this error continues, performance will be greatly degraded.', array( - '%file' => $uri, - '%expected_size' => $expected_size, - '%file_size' => $file_size, - ), WATCHDOG_ERROR); - // Better to serve straight from Drupal than have a broken file. - @unlink($uri); - } - } - else { - $errors[24] = $t('Write failed. @file', array('@file' => $temporary_file)); - watchdog('advagg', 'Write failed. Target: %target', array( - '%target' => $temporary_file, - ), WATCHDOG_ERROR); - } - // Cleanup leftover files. - if (file_exists($temporary_file)) { - @unlink($temporary_file); - } - if (file_exists($filepath)) { - @unlink($filepath); - } - return $errors; -} - -/** - * Given a string, what is the size that it should be as a file? - * - * @param string $string - * Input data to be sized in bytes. - * @link http://stackoverflow.com/a/3511239/231914. - * - * @return int - * Number of bytes this string uses. - */ -function _advagg_string_size_in_bytes($string) { - if (function_exists('mb_strlen')) { - return mb_strlen($string, '8bit'); - } - else { - return strlen($string); - } -} - -/** - * Rename; fallback to copy delete if this fails. - * - * @param string $source - * A string containing the source location. - * @param string $destination - * A string containing the destination location. - * - * @return mixed - * Destination string on success, FALSE on failure. - */ -function advagg_rename($source, $destination) { - $real_source = drupal_realpath($source); - $real_source = $real_source ? $real_source : $source; - $real_destination = drupal_realpath($destination); - $real_destination = $real_destination ? $real_destination : $destination; - - // Try php rename. - if (!@rename($real_source, $real_destination)) { - // Try drupal move. - if (!file_unmanaged_move($source, $destination)) { - // Try file scheme's rename method if it exists. - $fs_wrapper = file_stream_wrapper_get_instance_by_scheme(file_uri_scheme($source)); - if (!$fs_wrapper || !method_exists($fs_wrapper, 'rename') || !$fs_wrapper->rename($source, $destination)) { - return FALSE; } } } - - return $destination; } +// Helper functions. /** * Send out a fast 404 and exit. * @@ -1461,10 +881,8 @@ function advagg_rename($source, $destination) { * (optional) Small message reporting why the file didn't get created. */ function advagg_missing_fast404($msg = '') { - drupal_page_is_cacheable(FALSE); - - // Strip new lines & separators and limit header message to 512 characters. - $msg = substr(preg_replace("/[^\w\. ]+/", "", $msg), 0, 512); + // Strip new lines and limit header message to 512 characters. + $msg = substr(str_replace(array("\n", "\r"), '', $msg), 0, 512); // Add in headers if possible. if (!headers_sent()) { @@ -1532,22 +950,20 @@ 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/a/14145856/125684 + * @see http://stackoverflow.com/questions/14145620/regular-expression-for-media-queries-in-css */ -function advagg_parse_media_blocks($css, $starting_string = '@media') { +function advagg_parse_media_blocks($css) { $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, $starting_string, $start)) !== FALSE) { + while (($start = strpos($css, "@media", $start)) !== FALSE) { // Stack to manage brackets. $s = array(); @@ -1564,15 +980,14 @@ function advagg_parse_media_blocks($css, $starting_string = '@media') { // Move past first bracket. ++$i; - // Find the closing bracket for the @media statement. But ensure we don't - // overflow if there's an error. - while (!empty($s) && isset($css[$i])) { + // Find the closing bracket for the @media statement. + while (!empty($s)) { // If the character is an opening bracket, push it onto the stack, // otherwise pop the stack. - if ($css[$i] === "{") { + if ($css[$i] == "{") { array_push($s, "{"); } - elseif ($css[$i] === "}") { + elseif ($css[$i] == "}") { array_pop($s); } ++$i; @@ -1602,56 +1017,3 @@ function advagg_parse_media_blocks($css, $starting_string = '@media') { return $media_blocks; } - -/** - * Given a filename create that file; usually works if PHP goes fatal. - * - * @param string $filename - * Just the filename no path information. - * - * @return mixed - * On failure a string saying why it failed. - * On success the $files_to_save array. - */ -function advagg_missing_fatal_handler($filename) { - static $counter = 0; - // Bail out if there is no error. - $error = error_get_last(); - if ($error === NULL) { - return; - } - - $counter++; - // Bail out if this is still in a loop. - if ($counter > 2) { - return; - } - - // Bail out if the file already exists. - $data = advagg_get_hashes_from_filename($filename); - $type = $data[0]; - list($css_path, $js_path) = advagg_get_root_files_dir(); - $uri = ''; - if ($type === 'css') { - $uri = $css_path[0] . '/' . $filename; - } - elseif ($type === 'js') { - $uri = $js_path[0] . '/' . $filename; - } - if (file_exists($uri)) { - return; - } - - // Generate the file with no alters. - set_time_limit(0); - $return = advagg_missing_create_file($filename, TRUE); - if (is_array($return) && !headers_sent()) { - $redirect_counter = isset($_GET['redirect_counter']) ? intval($_GET['redirect_counter']) : 0; - // 307 if headers have not been sent yet. - $uri = advagg_generate_location_uri($filename, $data[0], $data[3]); - ++$redirect_counter; - $uri .= '?redirect_counter=' . $redirect_counter; - header('Location: ' . $uri, TRUE, 307); - exit(); - } -} diff --git a/sites/all/modules/contrib/advagg/advagg.module b/sites/all/modules/contrib/advagg/advagg.module index 2b497ef05c..7b5d6233a2 100644 --- a/sites/all/modules/contrib/advagg/advagg.module +++ b/sites/all/modules/contrib/advagg/advagg.module @@ -5,12 +5,7 @@ * Advanced CSS/JS aggregation module. */ -/** - * @defgroup default_variables default values for variables - * @{ - * Default values for various variables are defined here. - */ - +// Define default variables. /** * Default space characters. */ @@ -29,12 +24,7 @@ define('ADVAGG_GZIP', TRUE); /** * Default value to see we use core's default grouping of CSS/JS files. */ -if (module_exists('advagg_bundler') && variable_get('advagg_bundler_active', TRUE)) { - define('ADVAGG_CORE_GROUPS', FALSE); -} -else { - define('ADVAGG_CORE_GROUPS', TRUE); -} +define('ADVAGG_CORE_GROUPS', TRUE); /** * Default value to see if we cache the full CSS/JS structure. @@ -49,7 +39,7 @@ define('ADVAGG_GLOBAL_COUNTER', 0); /** * Send non blocking requests in order to generate aggregated files via HTTPRL. */ -define('ADVAGG_USE_HTTPRL', FALSE); +define('ADVAGG_USE_HTTPRL', TRUE); /** * Combine css files by using media queries instead of media attributes. @@ -117,9 +107,9 @@ define('ADVAGG_CLEAR_SCRIPTS', TRUE); define('ADVAGG_NEEDS_UPDATE', FALSE); /** - * How long to wait until advagg cron will run again. Default is 23 hours. + * How long to wait until advagg cron will run again. Default is 24 hours. */ -define('ADVAGG_CRON_FREQUENCY', 82800); +define('ADVAGG_CRON_FREQUENCY', 86400); /** * How long to wait until unaccessed aggregates are removed from the database. @@ -161,7 +151,7 @@ define('ADVAGG_INCLUDE_BASE_URL', FALSE); /** * Convert absolute path CSS/JS src/url() to be relative if self referencing. */ -define('ADVAGG_CONVERT_ABSOLUTE_TO_RELATIVE_PATH', TRUE); +define('ADVAGG_CONVERT_ABSOLUTE_TO_RELATIVE_PATH', FALSE); /** * Convert absolute path CSS/JS src/url() to be protocol relative. @@ -173,11 +163,6 @@ define('ADVAGG_CONVERT_ABSOLUTE_TO_PROTOCOL_RELATIVE_PATH', TRUE); */ define('ADVAGG_FORCE_HTTPS_PATH', FALSE); -/** - * Convert relative path CSS inside src() to be absolute. - */ -define('ADVAGG_CSS_ABSOLUTE_PATH', FALSE); - /** * If TRUE then the css is being rendered via javascript. */ @@ -198,308 +183,11 @@ define('ADVAGG_AUTH_BASIC_USER', ''); */ define('ADVAGG_AUTH_BASIC_PASS', ''); +// Core hook implementations. /** - * Skip far future check on status page. - */ -define('ADVAGG_SKIP_FAR_FUTURE_CHECK', FALSE); - -/** - * Skip preprocess check on status page. - */ -define('ADVAGG_SKIP_ENABLED_PREPROCESS_CHECK', FALSE); - -/** - * Prefetch External domains for CSS/JS. - */ -define('ADVAGG_RESOURCE_HINTS_DNS_PREFETCH', FALSE); - -/** - * Preconnect External domains for CSS/JS. - */ -define('ADVAGG_RESOURCE_HINTS_PRECONNECT', FALSE); - -/** - * Preload CSS/JS and sub requests. - */ -define('ADVAGG_RESOURCE_HINTS_PRELOAD', FALSE); - -/** - * Location of CSS/JS and sub requests resource hints. - */ -define('ADVAGG_RESOURCE_HINTS_LOCATION', 1); - -/** - * 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. - */ -if (module_exists('cdn')) { - define('ADVAGG_SKIP_FILE_CREATE_URL_INSIDE_CSS', FALSE); -} -else { - define('ADVAGG_SKIP_FILE_CREATE_URL_INSIDE_CSS', TRUE); -} - -/** - * 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', 1800); - -/** - * See if the mtime != if TRUE < if FALSE. - */ -define('ADVAGG_STRICT_MTIME_CHECK', TRUE); - -/** - * Default value to see if .br files should be created as well. - */ -if (function_exists('brotli_compress')) { - define('ADVAGG_BROTLI', TRUE); -} -else { - define('ADVAGG_BROTLI', FALSE); -} - -/** - * Default value to see zopfli_encode should not be used. - */ -define('ADVAGG_NO_ZOPFLI', FALSE); - -/** - * If true do test for 304 files on the status report page. - */ -define('ADVAGG_SKIP_304_CHECK', FALSE); - -/** - * If false do not set the immutable header. - */ -define('ADVAGG_RESOURCE_HINTS_USE_IMMUTABLE', TRUE); - -/** - * If true chrome=1 will be added to the X-UA-Compatible header. - */ -define('ADVAGG_CHROME_HEADER_ENABLED', FALSE); - -/** - * If true advagg htaccess Options uses SymLinksIfOwnerMatch vs FollowSymLinks. - */ -define('ADVAGG_HTACCESS_SYMLINKSIFOWNERMATCH', FALSE); - -/** - * How far down a JS file to look for use strict. - */ -define('ADVAGG_JS_HEADER_LENGTH', 24576); - -/** - * If not empty advagg htaccess will include the given rewritebase. - */ -define('ADVAGG_HTACCESS_REWRITEBASE', ''); - -/** - * Preload CSS/JS header limit 3kb. - */ -define('ADVAGG_RESOURCE_HINTS_PRELOAD_MAX_SIZE', 3072); - -/** - * If TRUE advagg will search for and remove empty CSS files from aggregates. - */ -define('ADVAGG_CSS_REMOVE_EMPTY_FILES', FALSE); - -/** - * If TRUE advagg will search for and remove empty JS files from aggregates. - */ -define('ADVAGG_JS_REMOVE_EMPTY_FILES', FALSE); - -/** - * If TRUE advagg will be disabled on admin pages. - */ -define('ADVAGG_DISABLE_ON_ADMIN', FALSE); - -/** - * Default is 200; 203 has been requested in the past. - */ -define('ADVAGG_HTTP_200_CODE', 200); - -/** - * Verify all 3 hashes from the filename, if TRUE only verify 1st hash. - */ -define('ADVAGG_WEAK_FILE_VERIFICATION', FALSE); - -/** - * If FALSE lock_acquire is used before writing a file. - */ -define('ADVAGG_NO_LOCKS', FALSE); - -/** - * If TRUE drupal_get_html_head will be rendered at the top of the css section. - */ -define('ADVAGG_HTML_HEAD_IN_CSS_LOCATION', FALSE); - -/** - * If 4 the admin section gets unlocked. - */ -define('ADVAGG_ADMIN_MODE', 4); - -/** - * If TRUE farfuture headers will go out if the file is delivered by php. - */ -define('ADVAGG_FARFUTURE_PHP', FALSE); - -/** - * Internal urls set here will have advagg disabled on those pages. - */ -define('ADVAGG_DISABLE_ON_LISTED_PAGES', ''); - -/** - * @} End of "defgroup default_variables". - */ - -/** - * @addtogroup hooks - * @{ - */ - -/** - * Implements hook_help(). - */ -function advagg_help($path, $arg) { - switch ($path) { - case 'admin/help#advagg': - $filepath = dirname(__FILE__) . '/README.txt'; - if (file_exists($filepath)) { - $readme = file_get_contents($filepath); - } - if (!isset($readme)) { - return NULL; - } - if (module_exists('markdown')) { - $filters = module_invoke('markdown', 'filter_info'); - $info = $filters['filter_markdown']; - - if (function_exists($info['process callback'])) { - $output = $info['process callback']($readme, NULL); - } - else { - $output = '<pre>' . $readme . '</pre>'; - } - } - else { - $output = '<pre>' . $readme . '</pre>'; - } - return $output; - } -} - -/** - * Implements hook_block_view_alter(). - */ -function advagg_block_view_alter(&$data, $block) { - // Do not run hook if AdvAgg is disabled. - if (!advagg_enabled()) { - return; - } - // Do not run hook if setting is disabled. - if (!variable_get('advagg_scripts_scope_anywhere', ADVAGG_SCRIPTS_SCOPE_ANYWHERE)) { - return; - } - - if (empty($data) || empty($data['content'])) { - return; - } - - $block_info = $block->module . ':' . $block->delta; - $prefix = "<!-- AdvAgg block:prefix:$block_info tag -->"; - $suffix = "<!-- AdvAgg block:suffix:$block_info tag -->"; - if (is_string($data['content'])) { - $data['content'] = $prefix . $data['content'] . $suffix; - } - else { - if (!isset($data['content']['#prefix'])) { - $data['content']['#prefix'] = ''; - } - $data['content']['#prefix'] .= $prefix; - if (!isset($data['content']['#suffix'])) { - $data['content']['#suffix'] = ''; - } - $data['content']['#suffix'] .= $suffix; - } -} - -/** - * Implements hook_views_pre_render(). - */ -function advagg_views_pre_render(&$view) { - // Do not run hook if AdvAgg is disabled. - if (!advagg_enabled()) { - return; - } - // Do not run hook if setting is disabled. - if (!variable_get('advagg_scripts_scope_anywhere', ADVAGG_SCRIPTS_SCOPE_ANYWHERE)) { - return; - } - - $info = "{$view->name}:{$view->current_display}"; - $prefix = "<!-- AdvAgg view:prefix:$info tag -->"; - $suffix = "<!-- AdvAgg view:suffix:$info tag -->"; - if (!isset($view->attachment_before)) { - $view->attachment_before = ''; - } - $view->attachment_before .= $prefix; - if (!isset($view->attachment_after)) { - $view->attachment_after = ''; - } - $view->attachment_after .= $suffix; -} - -/** - * Implements hook_panels_pre_render(). - */ -function advagg_panels_pre_render($panels_display, &$renderer) { - // Do not run hook if AdvAgg is disabled. - if (!advagg_enabled()) { - return; - } - // Do not run hook if setting is disabled. - if (!variable_get('advagg_scripts_scope_anywhere', ADVAGG_SCRIPTS_SCOPE_ANYWHERE)) { - return; - } - - $info = "{$panels_display->layout}:{$panels_display->css_id}"; - $prefix = "<!-- AdvAgg panels:prefix:$info tag -->"; - $suffix = "<!-- AdvAgg panels:suffix:$info tag -->"; - if (!isset($renderer->prefix)) { - $renderer->prefix = ''; - } - $renderer->prefix .= $prefix; - if (!isset($renderer->suffix)) { - $renderer->suffix = ''; - } - $renderer->suffix .= $suffix; -} - -/** - * Implements hook_url_inbound_alter(). + * Inbound URL rewrite helper. * - * Inbound URL rewrite helper. If host includes subdomain, rewrite URI and - * internal path if necessary. + * If host includes subdomain, rewrite URI and internal path if necessary. */ function advagg_url_inbound_alter(&$path, $original_path, $path_language) { // Do nothing if this has been disabled. @@ -522,7 +210,8 @@ function advagg_url_inbound_alter(&$path, $original_path, $path_language) { // If requested path was for an advagg file but now it is something else // switch is back to the advagg file. - if (!empty($path) + // @ignore sniffer_whitespace_openbracketspacing_openingwhitespace:2 + if ( !empty($path) && $path != $request_path && advagg_match_file_pattern($request_path) ) { @@ -544,7 +233,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 && !empty($lang->prefix) && strpos($request_path, $lang->prefix) !== FALSE) { + if ($lang->enabled && strpos($request_path, $lang->prefix) !== FALSE) { $prefixes[$lang->prefix] = $lang->prefix; } } @@ -573,15 +262,12 @@ function advagg_hook_info() { // advagg_get_root_files_dir_alter // because these 3 hooks are used on most requests. $advagg_hooks = array( - 'advagg_get_css_file_contents_pre_alter', 'advagg_get_css_file_contents_alter', 'advagg_get_js_file_contents_alter', 'advagg_get_css_aggregate_contents_alter', 'advagg_get_js_aggregate_contents_alter', - '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', @@ -591,7 +277,6 @@ 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) { @@ -604,75 +289,77 @@ function advagg_hook_info() { * Implements hook_module_implements_alter(). */ function advagg_module_implements_alter(&$implementations, $hook) { - // Move advagg_theme_registry_alter to the top. + // Move advagg to the top. if ($hook === 'theme_registry_alter' && array_key_exists('advagg', $implementations)) { $item = array('advagg' => $implementations['advagg']); unset($implementations['advagg']); $implementations = array_merge($item, $implementations); } - // Move advagg_ajax_render_alter to the top. + // Move advagg to the top. if ($hook === 'ajax_render_alter' && array_key_exists('advagg', $implementations)) { $item = array('advagg' => $implementations['advagg']); unset($implementations['advagg']); $implementations = array_merge($item, $implementations); } - // Move advagg_element_info_alter to the bottom. + // Move advagg to the bottom. if ($hook === 'element_info_alter' && array_key_exists('advagg', $implementations)) { $item = $implementations['advagg']; unset($implementations['advagg']); $implementations['advagg'] = $item; } - // Replace locale_js_alter with _advagg_locale_js_alter. if ($hook === 'js_alter' && array_key_exists('locale', $implementations)) { + // Replace locale with _advagg_locale. unset($implementations['locale']); $implementations['_advagg_locale'] = FALSE; } - // Move advagg_file_url_alter to the bottom. - if ($hook === 'file_url_alter' && array_key_exists('advagg', $implementations)) { - $item = $implementations['advagg']; - unset($implementations['advagg']); - $implementations['advagg'] = $item; - } - - if ($hook === 'requirements') { - // Move advagg_requirements to the bottom. - if (array_key_exists('advagg', $implementations)) { + if ($hook === 'js_alter' && array_key_exists('advagg', $implementations)) { + if (variable_get('advagg_run_alter_after_theme', ADVAGG_RUN_ALTER_AFTER_THEME)) { + // Remove advagg & advagg_mod; runs after drupal_alter('js', $js). + unset($implementations['advagg']); + if (array_key_exists('advagg_mod', $implementations)) { + unset($implementations['advagg_mod']); + } + } + else { + // Move advagg & advagg_mod to the bottom, but advagg is above advagg_mod. $item = $implementations['advagg']; unset($implementations['advagg']); $implementations['advagg'] = $item; + if (array_key_exists('advagg_mod', $implementations)) { + $item = $implementations['advagg_mod']; + unset($implementations['advagg_mod']); + $implementations['advagg_mod'] = $item; + } } - // Move advagg_css_cdn to the bottom. - if (array_key_exists('advagg_css_cdn', $implementations)) { - $item = $implementations['advagg_css_cdn']; - unset($implementations['advagg_css_cdn']); - $implementations['advagg_css_cdn'] = $item; - } - // Move advagg_css_compress to the bottom. - if (array_key_exists('advagg_css_compress', $implementations)) { - $item = $implementations['advagg_css_compress']; - unset($implementations['advagg_css_compress']); - $implementations['advagg_css_compress'] = $item; - } - // Move advagg_js_cdn to the bottom. - if (array_key_exists('advagg_js_cdn', $implementations)) { - $item = $implementations['advagg_js_cdn']; - unset($implementations['advagg_js_cdn']); - $implementations['advagg_js_cdn'] = $item; + } + + if ($hook === 'css_alter' && array_key_exists('advagg', $implementations)) { + if (variable_get('advagg_run_alter_after_theme', ADVAGG_RUN_ALTER_AFTER_THEME)) { + // Remove advagg & advagg_mod; runs after drupal_alter('css', $css). + unset($implementations['advagg']); + if (array_key_exists('advagg_mod', $implementations)) { + unset($implementations['advagg_mod']); + } } - // Move advagg_js_compress to the bottom. - if (array_key_exists('advagg_js_compress', $implementations)) { - $item = $implementations['advagg_js_compress']; - unset($implementations['advagg_js_compress']); - $implementations['advagg_js_compress'] = $item; + else { + // Move advagg & advagg_mod to the bottom, but advagg is above advagg_mod. + $item = $implementations['advagg']; + unset($implementations['advagg']); + $implementations['advagg'] = $item; + if (array_key_exists('advagg_mod', $implementations)) { + $item = $implementations['advagg_mod']; + unset($implementations['advagg_mod']); + $implementations['advagg_mod'] = $item; + } } } - // Move advagg_cron to the bottom. - if ($hook === 'cron' && array_key_exists('advagg', $implementations)) { + // Move advagg to the bottom. + if ($hook === 'file_url_alter' && array_key_exists('advagg', $implementations)) { $item = $implementations['advagg']; unset($implementations['advagg']); $implementations['advagg'] = $item; @@ -685,57 +372,12 @@ function advagg_module_implements_alter(&$implementations, $hook) { * This is a locking wrapper for locale_js_alter(). */ function _advagg_locale_js_alter(&$js) { - // If the variable is empty then get the latest variable from the database. - $name = 'javascript_parsed'; - $parsed = variable_get($name, array()); - if (empty($parsed)) { - $variables = array_map('unserialize', db_query('SELECT name, value FROM {variable} WHERE name = :name', array(':name' => $name))->fetchAllKeyed()); - if (!empty($variables[$name])) { - $GLOBALS['conf'][$name] = $variables[$name]; - } - } - - // See if locale_js_alter() needs to do anything. - $dir = 'public://' . variable_get('locale_js_directory', 'languages'); - $new_files = FALSE; - // See if a rebuild of the translation file for the current language is - // needed. - if (!empty($parsed['refresh:' . $GLOBALS['language']->language])) { - $new_files = TRUE; - } - // Check for new js source files. - if (empty($new_files)) { - foreach ($js as $item) { - if ($item['type'] === 'file' - && !in_array($item['data'], $parsed) - && substr($item['data'], 0, strlen($dir)) != $dir - ) { - $new_files = TRUE; - break; - } - } - } - if (empty($new_files)) { - // No new files to manage, just add in available i18n files. - advagg_locale_js_add_translations($js, $dir); - // Exit function. - return; - } - $count = 0; - while (!lock_acquire('locale_js_alter', 10)) { + while (!lock_acquire('locale_js_alter', 5)) { ++$count; - // If we've waited over 3 times then skip. - if ($count > 3) { + // If we've waited over 5 times then skip. + if ($count > 5) { lock_release('locale_js_alter'); - // Add in available i18n files. - advagg_locale_js_add_translations($js, $dir); - - // Disable saving to the cache as translations might be missing. - drupal_page_is_cacheable(FALSE); - if (variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL) > 1) { - $GLOBALS['conf']['advagg_cache_level'] = 0; - } return; } @@ -743,19 +385,10 @@ function _advagg_locale_js_alter(&$js) { lock_wait('locale_js_alter'); } - try { - // Run the alter. - 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: <code>@info</code>', array('@info' => $e)); - } - } + // Run the alter and return. + $return = locale_js_alter($js); lock_release('locale_js_alter'); + return $return; } /** @@ -769,7 +402,9 @@ function advagg_system_info_alter(&$info, $file, $type) { } // Replace advagg path. - if (!empty($info['configure']) + // @ignore sniffer_whitespace_openbracketspacing_openingwhitespace:2 + // @ignore sniffer_whitespace_closebracketspacing_closingwhitespace:8 + if ( !empty($info['configure']) && strpos($info['configure'], '/advagg') !== FALSE && ((!empty($info['dependencies']) && is_array($info['dependencies']) @@ -804,14 +439,20 @@ function advagg_file_url_alter(&$original_uri) { return; } + // Ignore coder warnings. + // @ignore sniffer_commenting_inlinecomment_spacingbefore:10 + // @ignore sniffer_whitespace_openbracketspacing_openingwhitespace:11 + // @ignore sniffer_whitespace_closebracketspacing_closingwhitespace:19 + // @ignore sniffer_commenting_inlinecomment_spacingafter + // // CDN fix. // Do nothing if // in maintenance_mode // CDN module does not exist // CDN far future is disabled // CDN mode is not basic - // URI does not contain cdn/farfuture/. - if (variable_get('maintenance_mode', FALSE) + // 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) || variable_get(CDN_MODE_VARIABLE, CDN_MODE_BASIC) != CDN_MODE_BASIC @@ -832,75 +473,22 @@ function advagg_menu() { $file_path = drupal_get_path('module', 'advagg'); $config_path = advagg_admin_config_root_path(); - $path_defined = FALSE; - if (advagg_s3fs_evaluate_no_rewrite_cssjs(FALSE)) { - $external_css = trim(parse_url(str_replace('/test.css', '/%', file_create_url($css_path[0] . '/test.css')), PHP_URL_PATH)); - if (strpos($external_css, $GLOBALS['base_path']) === 0) { - $external_css = substr($external_css, strlen($GLOBALS['base_path'])); - } - $external_js = trim(parse_url(str_replace('/test.js', '/%', file_create_url($js_path[0] . '/test.js')), PHP_URL_PATH)); - if (strpos($external_js, $GLOBALS['base_path']) === 0) { - $external_js = substr($external_js, strlen($GLOBALS['base_path'])); - } - $items[$external_css] = array( - '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', - ); - $items[$external_js] = array( - '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', - ); - $path_defined = TRUE; - } - - if (!$path_defined) { - $items[$css_path[1] . '/%'] = array( - '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', - ); - $items[$js_path[1] . '/%'] = array( - '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', - ); - } - - // If mutiple paths are symlinked to the same location; allow advagg to handle - // those addtional locations. - $advagg_additional_generate_paths = variable_get('advagg_additional_generate_paths', array()); - if (!empty($advagg_additional_generate_paths)) { - foreach ($advagg_additional_generate_paths as $path) { - $items[$path] = array( - 'title' => "Generate CSS/JS 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', - ); - } - } - + $items[$css_path[1] . '/%'] = array( + 'title' => "Generate CSS Aggregate", + 'page callback' => 'advagg_missing_aggregate', + 'type' => MENU_CALLBACK, + 'access callback' => TRUE, + 'file path' => $file_path, + 'file' => 'advagg.missing.inc', + ); + $items[$js_path[1] . '/%'] = array( + 'title' => "Generate JS Aggregate", + 'page callback' => 'advagg_missing_aggregate', + 'type' => MENU_CALLBACK, + 'access callback' => TRUE, + 'file path' => $file_path, + 'file' => 'advagg.missing.inc', + ); $items[$config_path . '/default'] = array( 'title' => 'Performance', 'type' => MENU_DEFAULT_LOCAL_TASK, @@ -948,15 +536,16 @@ function advagg_menu() { return $items; } +// @ignore sniffer_commenting_functioncomment_hookparamdoc:7 /** * Implements hook_cron(). * * This will be ran once a day at most. + * + * @param bool $bypass_time_check + * Set to TRUE to skip the 24 hour check. */ function advagg_cron($bypass_time_check = FALSE) { - // @param bool $bypass_time_check - // Set to TRUE to skip the 24 hour check. - // // Execute once a day (24 hours). if (!$bypass_time_check && variable_get('advagg_cron_timestamp', 0) > (REQUEST_TIME - variable_get('advagg_cron_frequency', ADVAGG_CRON_FREQUENCY))) { return array(); @@ -971,10 +560,7 @@ function advagg_cron($bypass_time_check = FALSE) { module_load_include('inc', 'advagg', 'advagg.cache'); $return[] = advagg_delete_stale_aggregates(); - // Delete all empty aggregated files. - $return[] = advagg_delete_empty_aggregates(); - - // Delete orphaned aggregates. + // Remove orphaned files. $return[] = advagg_delete_orphaned_aggregates(); // Remove aggregates that include missing files. @@ -985,34 +571,25 @@ 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(); - - // Update libraries data. - advagg_get_remote_libraries_versions(TRUE); - return $return; } +// @ignore sniffer_commenting_functioncomment_hookparamdoc:5 /** * Implements hook_flush_caches(). + * + * @param bool $all_bins + * TRUE: Get all advagg cache bins. + * @param bool $push_new_changes + * FALSE: Do not scan for changes. */ function advagg_flush_caches($all_bins = FALSE, $push_new_changes = TRUE) { - // * @param bool $all_bins - // * TRUE: Get all advagg cache bins. - // * @param bool $push_new_changes - // * FALSE: Do not scan for changes. - // // Send back a blank array if aav table doesn't exist. if (!db_table_exists('advagg_aggregates_versions')) { return array(); } - // Scan for and push new changes. + // Scan for & push new changes. module_load_include('inc', 'advagg', 'advagg.cache'); if ($push_new_changes) { advagg_push_new_changes(); @@ -1030,62 +607,45 @@ function advagg_flush_caches($all_bins = FALSE, $push_new_changes = TRUE) { * Implements hook_element_info_alter(). */ function advagg_element_info_alter(&$type) { - // Replace drupal_pre_render_styles with advagg_pre_render_styles. - $type['styles']['#items'] = array(); - if (!isset($type['styles']['#pre_render'])) { - $type['styles']['#pre_render'] = array(); - } - $key = array_search('drupal_pre_render_styles', $type['styles']['#pre_render']); - if ($key !== FALSE) { - $type['styles']['#pre_render'][$key] = 'advagg_pre_render_styles'; - } - else { - $type['styles']['#pre_render'][] = 'advagg_pre_render_styles'; - } - // Allow for other code to easily change the render with alter hooks. - $type['styles']['#pre_render'][] = 'advagg_modify_css_pre_render'; - $type['styles']['#group_callback'] = 'drupal_group_css'; // Swap in our own aggregation callback. - $type['styles']['#aggregate_callback'] = '_advagg_aggregate_css'; - $type['styles']['#type'] = 'styles'; + if (isset($type['styles']['#aggregate_callback'])) { + $type['styles']['#aggregate_callback'] = '_advagg_aggregate_css'; - // Replace drupal_pre_render_scripts with advagg_pre_render_scripts. - $type['scripts']['#items'] = array(); - if (!isset($type['scripts']['#pre_render'])) { - $type['scripts']['#pre_render'] = array(); - } - $key_drupal = array_search('drupal_pre_render_scripts', $type['scripts']['#pre_render']); - $key_omega = array_search('omega_pre_render_scripts', $type['scripts']['#pre_render']); - $key_aurora = array_search('aurora_pre_render_scripts', $type['scripts']['#pre_render']); - if ($key_drupal !== FALSE) { - $type['scripts']['#pre_render'][$key_drupal] = 'advagg_pre_render_scripts'; - } - elseif ($key_omega !== FALSE) { - $type['scripts']['#pre_render'][$key_omega] = 'advagg_pre_render_scripts'; + // Replace drupal_pre_render_styles with advagg_pre_render_styles. + $key = array_search('drupal_pre_render_styles', $type['styles']['#pre_render']); + if ($key !== FALSE) { + $type['styles']['#pre_render'][$key] = 'advagg_pre_render_styles'; + } + else { + $type['styles']['#pre_render'][] = 'advagg_pre_render_styles'; + } + + // Allow for other code to easily change the render with alter hooks. + $type['styles']['#pre_render'][] = 'advagg_modify_css_pre_render'; } - elseif ($key_aurora !== FALSE) { - $type['scripts']['#pre_render'][$key_aurora] = 'advagg_pre_render_scripts'; + + // Swap in our own aggregation callback. + if (isset($type['scripts']['#aggregate_callback'])) { + $type['scripts']['#aggregate_callback'] = '_advagg_aggregate_js'; } else { - $type['scripts']['#pre_render'][] = 'advagg_pre_render_scripts'; + $type['scripts'] = array( + '#items' => array(), + '#pre_render' => array('advagg_pre_render_scripts'), + '#group_callback' => 'advagg_group_js', + '#aggregate_callback' => '_advagg_aggregate_js', + '#type' => 'scripts', + ); } + // Allow for other code to easily change the render with alter hooks. $type['scripts']['#pre_render'][] = 'advagg_modify_js_pre_render'; - $type['scripts']['#group_callback'] = 'advagg_group_js'; - // Swap in our own aggregation callback. - $type['scripts']['#aggregate_callback'] = '_advagg_aggregate_js'; - $type['scripts']['#type'] = 'scripts'; - - // Copy html_tag to html_script_tag. - $type['html_script_tag'] = $type['html_tag']; - $type['html_script_tag']['#theme'] = 'html_script_tag'; - $type['html_script_tag']['#type'] = 'html_script_tag'; } /** * Implements hook_theme_registry_alter(). * - * Replace template_process_html with _advagg_process_html. + * Replace template_process_html with _advagg_process_html */ function advagg_theme_registry_alter(&$theme_registry) { if (!isset($theme_registry['html'])) { @@ -1097,23 +657,6 @@ function advagg_theme_registry_alter(&$theme_registry) { if ($index !== FALSE) { $theme_registry['html']['process functions'][$index] = '_advagg_process_html'; } - else { - // Put AdvAgg at the bottom if we can't find the replacement. - $theme_registry['html']['process functions'][] = '_advagg_process_html'; - } - - // Copy html_tag to html_script_tag. - $theme_registry['html_script_tag'] = $theme_registry['html_tag']; - $theme_registry['html_script_tag']['function'] = 'theme_html_script_tag'; - - // Fix imce_page. - if (isset($theme_registry['imce_page'])) { - $advagg_path = drupal_get_path('module', 'advagg'); - $imce_path = drupal_get_path('module', 'imce'); - if (strpos($theme_registry['imce_page']['path'], $imce_path) !== FALSE) { - $theme_registry['imce_page']['path'] = $advagg_path . '/tpl'; - } - } } /** @@ -1143,10 +686,8 @@ function advagg_ajax_render_alter(&$commands) { $scripts_header = $scripts_footer = ''; if (!empty($items['js'])) { $scripts_footer_array = advagg_get_js('footer', $items['js'], TRUE); - // Function advagg_pre_render_scripts() gets called here. $scripts_footer = drupal_render($scripts_footer_array); $scripts_header_array = advagg_get_js('header', $items['js'], TRUE); - // Function advagg_pre_render_scripts() gets called here. $scripts_header = drupal_render($scripts_header_array); } @@ -1157,7 +698,11 @@ function advagg_ajax_render_alter(&$commands) { continue; } - if ($values['command'] === 'settings' + // Ignore coder warnings. + // @ignore sniffer_whitespace_openbracketspacing_openingwhitespace:22 + // @ignore sniffer_whitespace_closebracketspacing_closingwhitespace:25 + // @ignore sniffer_commenting_inlinecomment_spacingafter + if ( $values['command'] === 'settings' && is_array($values['settings']) && !empty($values['merge']) ) { @@ -1165,7 +710,7 @@ function advagg_ajax_render_alter(&$commands) { unset($commands[$key]); continue; } - if ($values['command'] === 'insert' + if ( $values['command'] === 'insert' && is_null($values['settings']) && $values['method'] === 'prepend' && $values['data'] == $core_scripts_header @@ -1174,7 +719,7 @@ function advagg_ajax_render_alter(&$commands) { unset($commands[$key]); continue; } - if ($values['command'] === 'insert' + if ( $values['command'] === 'insert' && is_null($values['settings']) && $values['method'] === 'append' && $values['data'] == $core_scripts_footer @@ -1197,224 +742,105 @@ function advagg_ajax_render_alter(&$commands) { $commands = array_merge($extra_commands, $commands); } if (!empty($settings)) { - array_unshift($commands, ajax_command_settings(advagg_cleanup_settings_array(drupal_array_merge_deep_array(array_filter($settings['data'], 'is_array'))), TRUE)); + array_unshift($commands, ajax_command_settings(drupal_array_merge_deep_array($settings['data']), TRUE)); } } /** - * Implements hook_preprocess_page(). - */ -function advagg_preprocess_page() { - // Scan for changes to any CSS/JS files if in development mode. - advagg_scan_filesystem_for_changes_live(); -} - -/** - * Implements hook_preprocess_html(). - * - * Add in rendering IE meta tag if "combine CSS" is enabled. + * Implements hook_js_alter(). */ -function advagg_preprocess_html() { - // http://www.phpied.com/conditional-comments-block-downloads/#update - // Prevent conditional comments from stalling css downloads. - $fix_blocking_css_ie = array( - '#weight' => '-999999', - '#type' => 'markup', - '#markup' => "<!--[if IE]><![endif]-->\n", - ); - // Add markup for IE conditional comments to head. - drupal_add_html_head($fix_blocking_css_ie, 'fix_blocking_css_ie'); - - // Do not force IE rendering mode if "combine CSS" is disabled. - if (!variable_get('advagg_combine_css_media', ADVAGG_COMBINE_CSS_MEDIA)) { +function advagg_js_alter(&$js) { + if (!advagg_enabled() || !variable_get('advagg_js_fix_type', ADVAGG_JS_FIX_TYPE)) { return; } - // Send IE meta tag to force IE rendering mode header. - $x_ua_compatible = 'IE=edge'; - if (variable_get('advagg_chrome_header_enabled', ADVAGG_CHROME_HEADER_ENABLED)) { - $x_ua_compatible .= ',chrome=1'; - } - drupal_add_http_header('X-UA-Compatible', $x_ua_compatible); -} - -/** - * 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); -} + // Get hostname and base path. + $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); -/** - * Implements hook_js_alter(). - */ -function advagg_js_alter(&$js) { - if (module_exists('admin_menu')) { - // Fix for admin menu; put JS in footer. - $path = drupal_get_path('module', 'admin_menu'); - $filename = $path . '/admin_menu.js'; - if (isset($js[$filename])) { - $js[$filename]['scope'] = 'footer'; + // Fix type if it was incorrectly set. + foreach ($js as &$value) { + if (empty($value['data']) || !is_string($value['data'])) { + continue; } - } -} - -/** - * @} End of "addtogroup hooks". - */ -/** - * @defgroup 3rd_party_hooks 3rd party hook implementations - * @{ - * Hooks that are not apart of core or AdvAgg. - */ + // If type is external but doesn't start with http, https, or // change it + // to file. + if ( $value['type'] === 'external' + && stripos($value['data'], 'http://') !== 0 + && stripos($value['data'], 'https://') !== 0 + && stripos($value['data'], '//') !== 0 + ) { + $value['type'] = 'file'; + } -/** - * Implements hook_cron_alter(). - */ -function advagg_cron_alter(&$data) { - // Run this cron job every 2 minutes. - if (isset($data['advagg_js_compress_cron'])) { - $data['advagg_js_compress_cron']['rule'] = '*/2 * * * *'; - } - // Run this cron job every 5 minutes. - if (isset($data['advagg_relocate_cron'])) { - $data['advagg_relocate_cron']['rule'] = '*/5 * * * *'; - } - // Run this cron job every day. - if (isset($data['advagg_cron'])) { - $data['advagg_cron']['rule'] = '0 0 * * *'; - } -} + // If type is file but it starts with http, https, or // change it to + // external. + if ( $value['type'] === 'file' + && (stripos($value['data'], 'http://') === 0 + || stripos($value['data'], 'https://') === 0 + || (stripos($value['data'], '//') === 0 && stripos($value['data'], '///') === FALSE)) + ) { + $value['type'] = 'external'; + } -/** - * Implements hook_password_policy_force_change_allowed_paths_alter(). - */ -function advagg_password_policy_force_change_allowed_paths_alter(&$allowed_paths) { - $advagg_items = advagg_menu(); - foreach ($advagg_items as $path => $attributes) { - if (!empty($attributes['page callback']) && $attributes['page callback'] === 'advagg_missing_aggregate') { - $allowed_paths[] = str_replace('/%', '/*', $path); + // If type is external & starts with http, https, or // but points to this + // host change it to file, but move it to the top of the aggregation stack. + if ( $value['type'] === 'external' + && stripos($value['data'], $mod_base_url) !== FALSE + && ( stripos($value['data'], 'http://') === 0 + || stripos($value['data'], 'https://') === 0 + || stripos($value['data'], '//') === 0 + ) + ) { + $value['type'] = 'file'; + $value['group'] = JS_LIBRARY; + $value['every_page'] = TRUE; + $value['weight'] = -40000; + $value['data'] = substr($value['data'], stripos($value['data'], $mod_base_url) + $mod_base_url_len); } } + unset($value); } /** - * Implements hook_s3fs_upload_params_alter(). - * - * Set headers for advagg files. + * Implements hook_css_alter(). */ -function advagg_s3fs_upload_params_alter(&$upload_params) { - // Get advagg dir. - list($css_path, $js_path) = advagg_get_root_files_dir(); - $scheme = file_uri_scheme($css_path[1]); - if ($scheme) { - $css_path_dir = parse_url($css_path[1]); - $css_path_dir = str_replace("$scheme://", '', $css_path[1]); - } - else { - $css_path_dir = ltrim($css_path[1], '/'); - } - $scheme = file_uri_scheme($js_path[1]); - if ($scheme) { - $js_path_dir = parse_url($js_path[1]); - $js_path_dir = str_replace("$scheme://", '', $js_path_dir[1]); - } - else { - $js_path_dir = ltrim($js_path[1], '/'); - } - - // Get file type in advagg dir, css or js. - $type = ''; - if (strpos($upload_params['Bucket'] . '/' . $upload_params['Key'], $css_path_dir) !== FALSE) { - $type = 'css'; - } - if (strpos($upload_params['Bucket'] . '/' . $upload_params['Key'], $js_path_dir) !== FALSE) { - $type = 'js'; - } - if ($js_path_dir === $css_path_dir && !empty($type)) { - $pathinfo = pathinfo($upload_params['Key']); - if ($pathinfo['extension'] === 'gz') { - $pathinfo = pathinfo($pathinfo['filename']); - } - $type = $pathinfo['extension']; - } - if (empty($type)) { - // Only change advagg files. +function advagg_css_alter(&$css) { + if (!advagg_enabled() || !variable_get('advagg_css_fix_type', ADVAGG_CSS_FIX_TYPE)) { return; } - // Cache control is 52 weeeks. - if (variable_get('advagg_resource_hints_use_immutable', ADVAGG_RESOURCE_HINTS_USE_IMMUTABLE)) { - $upload_params['CacheControl'] = 'max-age=31449600, no-transform, public, immutable'; - } - else { - $upload_params['CacheControl'] = 'max-age=31449600, no-transform, public'; - } - // Expires in 365 days. - $upload_params['Expires'] = gmdate('D, d M Y H:i:s \G\M\T', REQUEST_TIME + 365 * 24 * 60 * 60); - - // The extension is .css or .js. - $pathinfo = pathinfo($upload_params['Key']); - if ($pathinfo['extension'] === $type) { - if (variable_get('advagg_gzip', ADVAGG_GZIP)) { - // Set gzip. - $upload_params['ContentEncoding'] = 'gzip'; - } - elseif (variable_get('advagg_brotli', ADVAGG_BROTLI)) { - // Set br. - $upload_params['ContentEncoding'] = 'br'; + // Fix type if it was incorrectly set. + foreach ($css as &$value) { + if (empty($value['data']) || !is_string($value['data'])) { + continue; } - } -} -/** - * Return s3fs configuration settings and values. - * - * @param string $key - * A specific key available in the s3fs configuration. NULL by default. - * - * @return array|string|null - * The full s3fs configuration settings, value of a specific key, - * or NULL if s3fs and the function do not exist. - */ -function advagg_get_s3fs_config($key = NULL) { - if (module_exists('s3fs') && is_callable('_s3fs_get_config')) { - $s3fs_config = _s3fs_get_config(); - return (empty($key)) ? $s3fs_config : $s3fs_config[$key]; - } - else { - return NULL; - } -} + // If type is external but doesn't start with http, https, or // change it + // to file. + if ( $value['type'] === 'external' + && stripos($value['data'], 'http://') !== 0 + && stripos($value['data'], 'https://') !== 0 + && stripos($value['data'], '//') !== 0 + ) { + $value['type'] = 'file'; + } -/** - * Shortcut to evaluate if s3fs no_rewrite_cssjs is set or empty. - * - * If this needs to be accessed in a loop, it is more efficient to call - * advagg_get_s3fs_config() once from outside of the loop. An example - * can be seen in the advagg_install_check_via_http function. - * - * @param bool $is_set - * Check if no_write_cssjs field is set (TRUE) or empty (FALSE). - * - * @return bool - * TRUE or FALSE is returned based on evaluating the field. If - * s3fs_config returns a NULL, evaluate the function to FALSE. - * - * @see advagg_get_s3fs_config() - */ -function advagg_s3fs_evaluate_no_rewrite_cssjs($is_set = TRUE) { - $s3fs_no_rewrite_cssjs = advagg_get_s3fs_config('no_rewrite_cssjs'); - if (!is_null($s3fs_no_rewrite_cssjs)) { - return ($is_set) ? !empty($s3fs_no_rewrite_cssjs) : empty($s3fs_no_rewrite_cssjs); - } - else { - return FALSE; + // If type is file but it starts with http, https, or // change it to + // external. + if ( $value['type'] === 'file' + && ( stripos($value['data'], 'http://') === 0 + || stripos($value['data'], 'https://') === 0 + || ( stripos($value['data'], '//') === 0 + && stripos($value['data'], '///') === FALSE + ) + ) + ) { + $value['type'] = 'external'; + } } + unset($value); } /** @@ -1445,52 +871,52 @@ function advagg_admin_menu_output_alter(array &$content) { } /** - * Implements hook_anonymous_login_paths_alter(). + * Implements hook_preprocess_page(). */ -function advagg_anonymous_login_paths_alter(&$paths) { - // Exclude advagg css/js paths. - list($css_path, $js_path) = advagg_get_root_files_dir(); - $paths['exclude'][] = $css_path[1] . '/*'; - $paths['exclude'][] = $js_path[1] . '/*'; +function advagg_preprocess_page() { + // Scan for changes to any CSS/JS files if in development mode. + advagg_scan_filesystem_for_changes_live(); } /** - * Implements hook_pre_flush_all_caches(). + * Implements hook_preprocess_html(). + * + * Add in rendering IE meta tag if "combine CSS" is enabled. */ -function advagg_pre_flush_all_caches() { - static $run_once; +function advagg_preprocess_html() { + // http://www.phpied.com/conditional-comments-block-downloads/#update + // Prevent conditional comments from stalling css downloads. + $fix_blocking_css_ie = array( + '#weight' => '-999999', + '#type' => 'markup', + '#markup' => "<!--[if IE]><![endif]-->\n", + ); + // Add markup for IE conditional comments to head. + drupal_add_html_head($fix_blocking_css_ie, 'fix_blocking_css_ie'); - if (!isset($run_once)) { - $run_once = TRUE; - // Only invoked by registry_rebuild. - module_load_include('admin.inc', 'advagg'); - // Truncate the advagg_files table. - advagg_admin_truncate_advagg_files(); + // Do not force IE rendering mode if "combine CSS" is disabled. + if (!variable_get('advagg_combine_css_media', ADVAGG_COMBINE_CSS_MEDIA)) { + return; } -} -/** - * @} End of "defgroup 3rd_party_hooks". - */ + // Setup IE meta tag to force IE rendering mode. + $meta_ie_render_engine = array( + '#type' => 'html_tag', + '#tag' => 'meta', + '#attributes' => array( + 'http-equiv' => 'X-UA-Compatible', + 'content' => 'IE=edge,chrome=1', + ), + '#weight' => '-99999', + '#prefix' => '<!--[if IE]>', + '#suffix' => '<![endif]-->', + ); -/** - * Only the alter part of locale_js_alter(), not the parsing part. - * - * @param array $javascript - * An array with all JavaScript code. Defaults to the default - * JavaScript array for the given scope. - * @param string $dir - * String pointing to the public locale_js_directory. - */ -function advagg_locale_js_add_translations(array &$javascript, $dir) { - // Add the translation JavaScript file to the page. - if (!empty($GLOBALS['language']->javascript)) { - // Add the translation JavaScript file to the page. - $file = $dir . '/' . $GLOBALS['language']->language . '_' . $GLOBALS['language']->javascript . '.js'; - $javascript[$file] = drupal_js_defaults($file); - } + // Add meta tag for IE to head. + drupal_add_html_head($meta_ie_render_engine, 'meta_ie_render_engine'); } +// Core CSS/JS override functions. /** * Callback for pre_render so elements can be modified before they are rendered. * @@ -1508,22 +934,20 @@ function advagg_locale_js_add_translations(array &$javascript, $dir) { * A render array that will render to a string of JavaScript tags. */ function advagg_modify_js_pre_render(array $elements) { - // Get the children elements. - $children = array_intersect_key($elements, array_flip(element_children($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); - // Allow other modules to modify $children and $elements before they are + // Allow other modules to modify $children & $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; } @@ -1544,21 +968,17 @@ function advagg_modify_js_pre_render(array $elements) { * A render array that will render to a string of JavaScript tags. */ function advagg_modify_css_pre_render(array $elements) { - if (!advagg_enabled()) { - return $elements; - } - // Put children elements into a reference array. $children = array(); foreach ($elements as $key => &$value) { - if ($key !== '' && is_string($key) && (0 === strpos($key, '#'))) { + if ($key !== '' && $key[0] === '#') { continue; } $children[$key] = &$value; } unset($value); - // Allow other modules to modify $children and $elements before they are + // Allow other modules to modify $children & $elements before they are // rendered. // Call hook_advagg_modify_css_pre_render_alter() drupal_alter('advagg_modify_css_pre_render', $children, $elements); @@ -1634,9 +1054,9 @@ function _advagg_aggregate_css(array &$css_groups) { if (!empty($files_to_aggregate)) { $hooks_hash = advagg_get_current_hooks_hash(); - $serialize_function = variable_get('advagg_serialize', ADVAGG_SERIALIZE); - $css_hash = drupal_hash_base64($serialize_function($files_to_aggregate)); + $css_hash = drupal_hash_base64(serialize($files_to_aggregate)); $cache_id = 'advagg:css:' . $hooks_hash . ':' . $css_hash; + // @ignore druplart_andor_assignment if (variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL) >= 1 && $cache = cache_get($cache_id, 'cache_advagg_aggregates')) { $plans = $cache->data; } @@ -1699,7 +1119,7 @@ function _advagg_aggregate_js(array &$js_groups) { if ($preprocess_js) { // Set boolean to TRUE if all JS in footer. $all_in_footer = FALSE; - if (module_exists('advagg_mod') && variable_get('advagg_mod_js_footer', ADVAGG_MOD_JS_FOOTER) >= 2) { + if (module_exists('advagg_mod') && variable_get('advagg_mod_js_footer', ADVAGG_MOD_JS_FOOTER) == 2) { $all_in_footer = TRUE; } foreach ($js_groups as $key => &$group) { @@ -1707,7 +1127,7 @@ function _advagg_aggregate_js(array &$js_groups) { // If a file group can be aggregated into a single file, do so, and set // the group's data property to the file path of the aggregate file. case 'file': - if (!empty($group['preprocess'])) { + if ($group['preprocess']) { // Special handing for when all JS is in the footer. if ($all_in_footer && $group['scope'] === 'footer' && $group['group'] > 9000) { ++$gap_counter; @@ -1736,9 +1156,9 @@ function _advagg_aggregate_js(array &$js_groups) { if (!empty($files_to_aggregate)) { $hooks_hash = advagg_get_current_hooks_hash(); - $serialize_function = variable_get('advagg_serialize', ADVAGG_SERIALIZE); - $js_hash = drupal_hash_base64($serialize_function($files_to_aggregate)); + $js_hash = drupal_hash_base64(serialize($files_to_aggregate)); $cache_id = 'advagg:js:' . $hooks_hash . ':' . $js_hash; + // @ignore druplart_andor_assignment if (variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL) >= 1 && $cache = cache_get($cache_id, 'cache_advagg_aggregates')) { $plans = $cache->data; } @@ -1769,14 +1189,12 @@ function _advagg_aggregate_js(array &$js_groups) { function _advagg_build_css_arrays_for_rendering($skip_alter = FALSE) { // Get the raw CSS variable. $raw_css = drupal_add_css(); - // Process and Sort css. + // Process & Sort css. $full_css = advagg_get_css($raw_css, $skip_alter); // Add attached js to drupal_add_js() function. - if (!empty($full_css['#attached'])) { - drupal_process_attached($full_css); - // Remove #attached since it's been added to the javascript array now. - unset($full_css['#attached']); - } + drupal_process_attached($full_css); + // Remove #attached since it's been added to the javascript array now. + unset($full_css['#attached']); return array($raw_css, $full_css); } @@ -1793,7 +1211,7 @@ function _advagg_build_css_arrays_for_rendering($skip_alter = FALSE) { function _advagg_build_js_arrays_for_rendering($skip_alter = FALSE) { // Get the raw JS variable. $javascript = drupal_add_js(); - // Process and Sort JS. + // Process & Sort JS. $full_javascript = advagg_get_full_js($javascript, $skip_alter); // Get scopes used in the js. $scopes = advagg_get_js_scopes($full_javascript); @@ -1812,60 +1230,29 @@ function _advagg_build_js_arrays_for_rendering($skip_alter = FALSE) { // Get the js settings. $js_scope_settings_array[$scope]['settings'] = $scripts['#items']['settings']; // Exclude JS Settings from the array; we'll add it back later. - $scripts['#items']['settings'] = array(); + unset($scripts['#items']['settings']); } $js_scope_array[$scope] = $scripts; } - - // Fix settings; if more than 1 is set, use the largest one. - if (count($js_scope_settings_array) > 1) { - $max = -1; - $max_scope = ''; - foreach ($js_scope_settings_array as $scope => $settings) { - $count = count($settings); - $max = max($max, $count); - if ($max == $count) { - $max_scope = $scope; - } - } - - foreach ($js_scope_settings_array as $scope => $settings) { - if ($scope !== $max_scope) { - unset($js_scope_settings_array[$scope]); - } - } - } return array($javascript, $js_scope_settings_array, $js_scope_array); } /** * Returns TRUE if the CSS is being loaded via JavaScript. * - * @param object $css_cache - * Cache object from cache_get(). - * * @return bool * TRUE if CSS loaded via JS. FALSE if not. */ -function advagg_css_in_js($css_cache = NULL) { - if (module_exists('advagg_mod') - && variable_get('advagg_mod_css_defer', ADVAGG_MOD_CSS_DEFER) - ) { - return TRUE; - } - if (module_exists('css_delivery') - && css_delivery_enabled() +function advagg_css_in_js() { + if (( module_exists('advagg_mod') + && variable_get('advagg_mod_css_defer', ADVAGG_MOD_CSS_DEFER) + ) || ( + module_exists('css_delivery') + && css_delivery_enabled() + ) ) { return TRUE; } - // Critical css added by another means. - if (!empty($css_cache->data[1]['#items'])) { - foreach ($css_cache->data[1]['#items'] as $values) { - if (!empty($values['critical-css'])) { - return TRUE; - } - } - } return variable_get('advagg_css_in_js', ADVAGG_CSS_IN_JS); } @@ -1880,7 +1267,7 @@ function advagg_css_in_js($css_cache = NULL) { * _advagg_build_js_arrays_for_rendering(). * * @return array - * Array containing the $css_cache, $js_cache, $css_cache_id, $js_cache_id. + * Array containing the $css_cache, $js_cache, $css_cache_id, & $js_cache_id. */ function advagg_get_render_cache(array $full_css, array $js_scope_array) { $cids = array(); @@ -1889,21 +1276,20 @@ function advagg_get_render_cache(array $full_css, array $js_scope_array) { // Get advagg hash. $hooks_hash = advagg_get_current_hooks_hash(); - $serialize_function = variable_get('advagg_serialize', ADVAGG_SERIALIZE); if (advagg_file_aggregation_enabled('css')) { // Generate css cache id. - $cids[] = $css_cache_id = 'advagg:css:full:1.1:' . $hooks_hash . ':' . drupal_hash_base64($serialize_function($full_css)); + $cids[] = $css_cache_id = 'advagg:css:full:' . $hooks_hash . ':' . drupal_hash_base64(serialize($full_css)); } if (advagg_file_aggregation_enabled('js')) { // Generate js cache id. - $cids[] = $js_cache_id = 'advagg:js:full:1.1:' . $hooks_hash . ':' . drupal_hash_base64($serialize_function($js_scope_array)); + $cids[] = $js_cache_id = 'advagg:js:full:' . $hooks_hash . ':' . drupal_hash_base64(serialize($js_scope_array)); } if (!empty($cids)) { // 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]; } @@ -1913,10 +1299,10 @@ function advagg_get_render_cache(array $full_css, array $js_scope_array) { } // Special handling if the css is loaded via JS. - if (!empty($css_cache) + if ( advagg_css_in_js() + && !empty($css_cache) && empty($js_cache) - && advagg_css_in_js($css_cache) - ) { + ) { // If CSS is being loaded via JavaScript and the css cache is set but the // js cache is not set; then unset the css cache as well. unset($css_cache); @@ -2010,48 +1396,23 @@ function _advagg_process_html(&$variables) { } // Render page_top and page_bottom into top level variables. - if (isset($variables['page']) && is_array($variables['page']) && isset($variables['page']['page_top'])) { + if (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']) && is_array($variables['page']) && isset($variables['page']['page_bottom'])) { + if (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']) && is_array($variables['page']) && isset($variables['page']['#children'])) { + if (isset($variables['page']['#children'])) { $variables['page'] = $variables['page']['#children']; } - $advagg_script_alt_scope_scripts = array(); - if (variable_get('advagg_scripts_scope_anywhere', ADVAGG_SCRIPTS_SCOPE_ANYWHERE)) { - $prefix = "<!-- AdvAgg page:prefix tag -->"; - $suffix = "<!-- AdvAgg page:suffix tag -->"; - $variables['page'] = $prefix . $variables['page'] . $suffix; - $prefix = "<!-- AdvAgg page_top:prefix tag -->"; - $suffix = "<!-- AdvAgg page_top:suffix tag -->"; - $variables['page_top'] = $prefix . $variables['page_top'] . $suffix; - $prefix = "<!-- AdvAgg page_bottom:prefix tag -->"; - $suffix = "<!-- AdvAgg page_bottom:suffix tag -->"; - $variables['page_bottom'] = $prefix . $variables['page_bottom'] . $suffix; - - $matches = array(); - preg_match_all('/<!-- AdvAgg (.*?) tag -->/', $variables['page_top'], $matches); - $advagg_script_alt_scope_scripts = array_merge($matches[1], $advagg_script_alt_scope_scripts); - preg_match_all('/<!-- AdvAgg (.*?) tag -->/', $variables['page'], $matches); - $advagg_script_alt_scope_scripts = array_merge($matches[1], $advagg_script_alt_scope_scripts); - preg_match_all('/<!-- AdvAgg (.*?) tag -->/', $variables['page_bottom'], $matches); - $advagg_script_alt_scope_scripts = array_merge($matches[1], $advagg_script_alt_scope_scripts); - } - - // Parts of drupal_get_html_head(). - $elements = drupal_add_html_head(); - if (is_callable('advagg_mod_html_head_post_alter')) { - advagg_mod_html_head_post_alter($elements); - } + $variables['head'] = drupal_get_html_head(); // Get default javascript. // @see http://drupal.org/node/1279226 @@ -2062,9 +1423,8 @@ function _advagg_process_html(&$variables) { // Try the render cache. if (!variable_get('advagg_debug', ADVAGG_DEBUG)) { - // No Alter. - if (variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL) >= 5 && !module_exists('advagg_relocate')) { - // Get all CSS and JS variables needed; running no alters. + if (variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL) >= 5) { + // Get all CSS & JS variables needed; running no alters. list($variables['css'], $full_css) = _advagg_build_css_arrays_for_rendering(TRUE); list($javascript, $js_scope_settings_array, $js_scope_array) = _advagg_build_js_arrays_for_rendering(TRUE); @@ -2072,9 +1432,8 @@ function _advagg_process_html(&$variables) { list($css_cache, $js_cache, $css_cache_id_no_alter, $js_cache_id_no_alter) = advagg_get_render_cache($full_css, $js_scope_array); } - // With Alter. if ((empty($css_cache->data) || empty($js_cache->data)) && variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL) >= 3) { - // Get all CSS and JS variables needed; running alters. + // Get all CSS & JS variables needed; running alters. list($variables['css'], $full_css) = _advagg_build_css_arrays_for_rendering(); list($javascript, $js_scope_settings_array, $js_scope_array) = _advagg_build_js_arrays_for_rendering(); @@ -2086,53 +1445,22 @@ function _advagg_process_html(&$variables) { // CSS has nice hooks so we don't need to work around it. if (!empty($css_cache->data)) { // Use render cache. - list($variables['styles'], $full_css) = $css_cache->data; + $variables['styles'] = $css_cache->data; } else { // Get the css if we have not done so. if (empty($full_css)) { list($variables['css'], $full_css) = _advagg_build_css_arrays_for_rendering(); } - // Render the CSS; advagg_pre_render_styles() gets called here. + // Render the CSS. $variables['styles'] = drupal_render($full_css); - if (!empty($css_cache_id) && variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL) >= 3) { + if (!empty($css_cache_id)) { // Save to the cache. - cache_set($css_cache_id, array($variables['styles'], $full_css), 'cache_advagg_aggregates', CACHE_TEMPORARY); + cache_set($css_cache_id, $variables['styles'], 'cache_advagg_aggregates', CACHE_TEMPORARY); } - if (!empty($css_cache_id_no_alter) && variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL) >= 5) { + if (!empty($css_cache_id_no_alter)) { // Save to the cache. - cache_set($css_cache_id_no_alter, array($variables['styles'], $full_css), 'cache_advagg_aggregates', CACHE_TEMPORARY); - } - } - - if (module_exists('advagg_font') && variable_get('advagg_font_fontfaceobserver', ADVAGG_FONT_FONTFACEOBSERVER)) { - $fonts = array(); - foreach ($full_css['#groups'] as $groups) { - if (isset($groups['items']['files'])) { - foreach ($groups['items']['files'] as $file) { - if (isset($file['advagg_font'])) { - foreach ($file['advagg_font'] as $class => $name) { - $fonts[$class] = $name; - } - } - } - } - } - if (!empty($fonts)) { - if (isset($js_scope_settings_array)) { - $key = key($js_scope_settings_array); - $js_scope_settings_array[$key]['settings']['data'][] = array('advagg_font' => $fonts); - } - drupal_add_js(array('advagg_font' => $fonts), array('type' => 'setting')); - } - } - - if (variable_get('advagg_resource_hints_preload', ADVAGG_RESOURCE_HINTS_PRELOAD)) { - foreach ($full_css['#groups'] as $groups) { - if (empty($groups['data']) || $groups['type'] === 'inline') { - continue; - } - advagg_add_preload_header(advagg_convert_abs_to_rel(file_create_url($groups['data'])), 'style'); + cache_set($css_cache_id_no_alter, $variables['styles'], 'cache_advagg_aggregates', CACHE_TEMPORARY); } } @@ -2156,17 +1484,14 @@ function _advagg_process_html(&$variables) { // Replace cached settings with current ones. $js_settings_used = array(); - $js_scope_settings_array_copy = $js_scope_settings_array; if (variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL) >= 5) { - if (!empty($js_scope_settings_array_copy['header']) && empty($js_scope_settings_array_copy['footer'])) { + if (!empty($js_scope_settings_array['header']) && empty($js_scope_settings_array['footer'])) { // Copy header settings into the footer. - $js_scope_settings_array_copy['footer'] = $js_scope_settings_array_copy['header']; + $js_scope_settings_array['footer'] = $js_scope_settings_array['header']; } } - list($js_cache_data, $js_scope_array) = $js_cache->data; - - foreach ($js_cache_data as $scope => $value) { + foreach ($js_cache->data as $scope => $value) { $scope_settings = $scope; if ($scope_settings === 'scripts') { $scope_settings = 'header'; @@ -2179,28 +1504,27 @@ function _advagg_process_html(&$variables) { if ($start !== FALSE) { // If the cache and current settings scope's do not match; do not use // the cached version. - if (!isset($js_scope_settings_array_copy[$scope_settings]['settings'])) { + if (!isset($js_scope_settings_array[$scope_settings]['settings'])) { $use_cache = FALSE; break; } // Replace cached Drupal.settings with current Drupal.settings for this // page. - $merged = advagg_cleanup_settings_array(drupal_array_merge_deep_array(array_filter($js_scope_settings_array_copy[$scope_settings]['settings']['data'], 'is_array'))); + $merged = drupal_array_merge_deep_array($js_scope_settings_array[$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; - - // Replace the drupal settings string. - $value = advagg_replace_drupal_settings_string($value, $json_data); + // Replace Drupal.settings json. + $value = substr($value, 0, $start + 30) . $json_data . substr($value, strpos($value, '});', $start) + 1); } } $add_to_variables[$scope] = $value; } if ($use_cache) { - $all_used = array_diff(array_keys($js_scope_settings_array_copy), array_keys($js_settings_used)); + $all_used = array_diff(array_keys($js_scope_settings_array), array_keys($js_settings_used)); // Ignore this check if the cache level is less than 5. if (!empty($all_used) && variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL) < 5 && !empty($js_settings_used)) { // Some js settings did not make it into the output. Skip cache. @@ -2223,9 +1547,8 @@ function _advagg_process_html(&$variables) { // If the cache isn't used. if (!$use_cache) { - if (!empty($js_cache->data) && !empty($css_cache->data) && advagg_css_in_js($css_cache)) { - // Render the css so it will be added to the js array; - // advagg_pre_render_styles() gets called here. + if (advagg_css_in_js() && !empty($js_cache->data) && !empty($css_cache->data)) { + // Render the css so it will be added to the js array. $variables['styles'] = drupal_render($full_css); } @@ -2242,12 +1565,12 @@ function _advagg_process_html(&$variables) { $js_cache['scripts'] = ''; if (!empty($js_scope_array)) { // Add JS to the header and footer of the page. - foreach ($js_scope_array as $scope => &$scripts_array) { + foreach ($js_scope_array as $scope => $scripts_array) { // Add js settings. if (!empty($js_scope_settings_array[$scope]['settings'])) { $scripts_array['#items']['settings'] = $js_scope_settings_array[$scope]['settings']; } - // Render js; advagg_pre_render_scripts() gets called here. + // Render js. $scripts = drupal_render($scripts_array); if ($scope === 'header') { @@ -2261,15 +1584,9 @@ function _advagg_process_html(&$variables) { $variables['page_bottom'] .= $scripts; $js_cache['page_bottom'] = $scripts; } - // Above css scripts. - elseif ($scope === 'above_css') { - // Put in this new section. - $variables['above_css'] = $scripts; - $js_cache['above_css'] = $scripts; - } elseif (variable_get('advagg_scripts_scope_anywhere', ADVAGG_SCRIPTS_SCOPE_ANYWHERE)) { // Scripts in other places. - if (isset($variables[$scope]) + if ( isset($variables[$scope]) && is_string($variables[$scope]) && array_key_exists($scope, $GLOBALS['theme_info']->info['regions']) ) { @@ -2277,407 +1594,36 @@ function _advagg_process_html(&$variables) { $variables[$scope] .= $scripts; $js_cache[$scope] = $scripts; } - elseif (array_search($scope, $advagg_script_alt_scope_scripts, TRUE) !== FALSE) { - // Add to the inline html. - $pos_page_top = strpos($variables['page_top'], "<!-- AdvAgg $scope tag -->"); - $pos_page = strpos($variables['page'], "<!-- AdvAgg $scope tag -->"); - $pos_page_bottom = strpos($variables['page_bottom'], "<!-- AdvAgg $scope tag -->"); - if ($pos_page_top !== FALSE) { - $pos_page_top += strlen("<!-- AdvAgg $scope tag -->"); - $variables['page_top'] = substr_replace($variables['page_top'], "\n$scripts", $pos_page_top, 0); - $js_cache[$scope] = $scripts; - } - elseif ($pos_page !== FALSE) { - $pos_page += strlen("<!-- AdvAgg $scope tag -->"); - $variables['page'] = substr_replace($variables['page'], "\n$scripts", $pos_page, 0); - $js_cache[$scope] = $scripts; - } - elseif ($pos_page_bottom !== FALSE) { - $pos_page_bottom += strlen("<!-- AdvAgg $scope tag -->"); - $variables['page_bottom'] = substr_replace($variables['page_bottom'], "\n$scripts", $pos_page_bottom, 0); - $js_cache[$scope] = $scripts; - } - } // Add javascript to scripts if we can't find the region in the theme. - elseif (strpos($scope, ':') === FALSE) { + else { // Add to the bottom of this section. $variables['scripts'] .= $scripts; $js_cache['scripts'] .= $scripts; } } } - unset($scripts_array); - - // Clear drupal settings so cache is smaller. - foreach ($js_cache as &$string) { - $string = advagg_replace_drupal_settings_string($string, '{}'); - } - unset($string); - - // Clear drupal settings and not needed items from render cache. - $js_scope_array = array_intersect_key($js_scope_array, array_flip(element_children($js_scope_array))); - foreach ($js_scope_array as $scope => &$scripts_array) { - // Clear element children. - $scripts_array = array_diff_key($scripts_array, array_flip(element_children($scripts_array))); - if (isset($scripts_array['#children'])) { - unset($scripts_array['#children']); - } - // Clear drupal settings. - if (isset($scripts_array['#items']['settings']['data']) && is_array($scripts_array['#items']['settings']['data'])) { - $scripts_array['#items']['settings']['data'] = array(); - } - // Clear printed keys. - if (isset($scripts_array['#printed'])) { - unset($scripts_array['#printed']); - } - // Clear not used groups. - foreach ($scripts_array['#groups'] as $key => $groups) { - if (!isset($groups['items']['files'])) { - unset($scripts_array['#groups'][$key]); - } - } - } - unset($scripts_array); - - 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) && 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); - } - } - } - if (!empty($variables['above_css'])) { - $variables['styles'] = $variables['above_css'] . $variables['styles']; - } - if (variable_get('advagg_resource_hints_preload', ADVAGG_RESOURCE_HINTS_PRELOAD)) { - foreach ($js_scope_array as $scope => &$scripts_array) { - if ($scope !== 'header' - && $scope !== 'footer' - && $scope !== 'above_css' - && !variable_get('advagg_scripts_scope_anywhere', ADVAGG_SCRIPTS_SCOPE_ANYWHERE) - ) { - continue; + if (!empty($js_cache_id) && !empty($js_cache)) { + cache_set($js_cache_id, $js_cache, 'cache_advagg_aggregates', CACHE_TEMPORARY); } - - foreach ($scripts_array['#groups'] as $groups) { - if (empty($groups['data']) || $groups['type'] === 'inline') { - continue; - } - advagg_add_preload_header(advagg_convert_abs_to_rel(file_create_url($groups['data'])), 'script'); + if (!empty($js_cache_id_no_alter) && !empty($js_cache)) { + cache_set($js_cache_id_no_alter, $js_cache, 'cache_advagg_aggregates', CACHE_TEMPORARY); } } } - $head_elements_before = drupal_add_html_head(); - if (variable_get('advagg_resource_hints_dns_prefetch', ADVAGG_RESOURCE_HINTS_DNS_PREFETCH) - || variable_get('advagg_resource_hints_preconnect', ADVAGG_RESOURCE_HINTS_PRECONNECT) - || variable_get('advagg_resource_hints_preload', ADVAGG_RESOURCE_HINTS_PRELOAD) - ) { - // Prefetch css domains. - foreach ($full_css['#items'] as $file) { - advagg_add_resource_hints_array($file); - } - foreach ($full_css['#groups'] as $groups) { - if (isset($groups['items']['files'])) { - foreach ($groups['items']['files'] as $file) { - advagg_add_resource_hints_array($file); - } - } + // Output debug info. + if (variable_get('advagg_debug', ADVAGG_DEBUG)) { + $debug = $GLOBALS['_advagg']['debug']; + if (module_exists('httprl')) { + $output = ' ' . httprl_pr($debug); } - - // Prefetch js domains. - foreach ($js_scope_array as $scope_js) { - foreach ($scope_js['#items'] as $file) { - advagg_add_resource_hints_array($file); - } - if (isset($scope_js['#groups'])) { - foreach ($scope_js['#groups'] as $groups) { - if (isset($groups['items']['files'])) { - foreach ($groups['items']['files'] as $file) { - advagg_add_resource_hints_array($file); - } - } - } - } + else { + $output = '<pre>' . str_replace(array('<', '>'), array('<', '>'), print_r($debug, TRUE)) . '</pre>'; } + watchdog('advagg_debug', $output, array(), WATCHDOG_DEBUG); } - - // Add in preload link headers. - advagg_add_preload_header(); - - // Add in the headers added by advagg. - $head_elements_after = drupal_add_html_head(); - $elements += array_diff_key($head_elements_after, $head_elements_before); - - // Parts of drupal_get_html_head(). - drupal_alter('html_head', $elements); - $head = drupal_render($elements); - if (variable_get('advagg_html_head_in_css_location', ADVAGG_HTML_HEAD_IN_CSS_LOCATION)) { - $variables['styles'] = $head . $variables['styles']; - $variables['head'] = ''; - } - else { - $variables['head'] = $head; - } - - // Remove AdvAgg comments. - if (variable_get('advagg_scripts_scope_anywhere', ADVAGG_SCRIPTS_SCOPE_ANYWHERE) - && !empty($advagg_script_alt_scope_scripts) - && !variable_get('theme_debug', FALSE) - ) { - $variables['page_top'] = preg_replace('/<!-- AdvAgg (.*?) tag -->/', '', $variables['page_top']); - $variables['page'] = preg_replace('/<!-- AdvAgg (.*?) tag -->/', '', $variables['page']); - $variables['page_bottom'] = preg_replace('/<!-- AdvAgg (.*?) tag -->/', '', $variables['page_bottom']); - } - - // Output debug info. - if (variable_get('advagg_debug', ADVAGG_DEBUG)) { - $debug = $GLOBALS['_advagg']['debug']; - if (is_callable('httprl_pr')) { - $output = ' ' . httprl_pr($debug); - } - else { - $output = '<pre>' . str_replace(array('<', '>'), array('<', '>'), print_r($debug, TRUE)) . '</pre>'; - } - watchdog('advagg_debug', $output, array(), WATCHDOG_DEBUG); - } -} - -/** - * Replace inline drupal settings script. - * - * @param string $subject - * Inline js. - * @param string $replace - * JS settings replacement. - * - * @return string - * Returns the subject with the replacement in place if this is a drupal - * settings json blob. - */ -function advagg_replace_drupal_settings_string($subject, $replace) { - $start = strpos($subject, 'jQuery.extend(Drupal.settings,'); - if ($start === FALSE) { - return $subject; - } - - // Find the end of the Drupal.settings. - $script_end = stripos($subject, '</script>', $start); - $settings_substring = substr($subject, $start, $script_end - $start); - $json_end = strripos($settings_substring, '});'); - - // Check if LABjs has added an additional wrapper around Drupal settings. - $script_tag_start = strripos(substr($subject, 0, $start), '<script'); - if (strpos(substr($subject, $script_tag_start, $start), '$L.wait(') !== FALSE) { - // Refine JSON end position. - $_json_end = strripos(substr($settings_substring, 0, $json_end), '});'); - if ($_json_end !== FALSE) { - $json_end = $_json_end; - } - } - - // Replace Drupal.settings json. - $subject = substr($subject, 0, $start + 30) . $replace . substr($subject, $json_end + $start + 1); - return $subject; -} - -/** - * 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(). - * - * @param array $values - * Attributes added via code for the file. - */ -function advagg_add_resource_hints_array(array $values) { - if (variable_get('advagg_resource_hints_dns_prefetch', ADVAGG_RESOURCE_HINTS_DNS_PREFETCH) - || variable_get('advagg_resource_hints_preconnect', ADVAGG_RESOURCE_HINTS_PRECONNECT)) { - if (!empty($values['type']) - && ($values['type'] === 'external' || $values['type'] === 'file') - ) { - // Get external domains. - advagg_add_dns_prefetch($values['data']); - } - if (!empty($values['dns_prefetch'])) { - // Grab domains that will be access when this file is loaded. - if (is_array($values['dns_prefetch'])) { - foreach ($values['dns_prefetch'] as $url) { - advagg_add_dns_prefetch($url); - } - } - else { - advagg_add_dns_prefetch($values['dns_prefetch']); - } - } - } - if (!empty($values['preload']) && variable_get('advagg_resource_hints_preload', ADVAGG_RESOURCE_HINTS_PRELOAD)) { - if (is_array($values['preload'])) { - foreach ($values['preload'] as $url) { - advagg_add_preload_header($url); - } - } - else { - advagg_add_preload_header($values['preload']); - } - } -} - -/** - * Add in the dns-prefetch header for CSS and JS external files. - * - * @param string $url - * The url of the external host. - * - * @return bool - * TRUE if it was added to the head. - */ -function advagg_add_dns_prefetch($url) { - // Keep the order. - $advagg_resource_hints_location = variable_get('advagg_resource_hints_location', ADVAGG_RESOURCE_HINTS_LOCATION); - static $weight = -1001; - if ($advagg_resource_hints_location == 3) { - $weight = -999.9; - } - $weight += 0.0001; - - // Get the host. - $parse = @parse_url($url); - if (empty($parse['host'])) { - // If just the hostname was given, build proper url. - if (strpos($url, '.') && strpos($url, '/') === FALSE) { - $parse['scheme'] = '//'; - $parse['host'] = $url; - // Check for fragment. - $pos = strpos($url, '#'); - if ($pos !== FALSE) { - $parse['fragment'] = substr($url, $pos + 1); - $parse['host'] = substr($url, 0, $pos); - } - // Put it back together and parse again. - $url = advagg_glue_url($parse); - $parse = @parse_url($url); - } - if (empty($parse['host'])) { - return FALSE; - } - } - - // Filter out wrong schemes. - if (!empty($parse['scheme']) - && $parse['scheme'] !== 'http' - && $parse['scheme'] !== 'https' - ) { - return FALSE; - } - - // Filter out local host. - $host = @parse_url($GLOBALS['base_root'], PHP_URL_HOST); - if ($parse['host'] === $host) { - return FALSE; - } - - // Add DNS information for more domains. - if (strpos($parse['host'], 'fonts.googleapis.com') !== FALSE) { - // Add fonts.gstatic.com when fonts.googleapis.com is added. - advagg_add_dns_prefetch('https://fonts.gstatic.com/#crossorigin'); - } - - // Build render array. - if (variable_get('advagg_resource_hints_dns_prefetch', ADVAGG_RESOURCE_HINTS_DNS_PREFETCH)) { - $element = array( - '#type' => 'html_tag', - '#tag' => 'link', - '#attributes' => array( - 'rel' => 'dns-prefetch', - 'href' => '//' . $parse['host'], - ), - '#weight' => $weight, - ); - // Add markup for dns-prefetch to html_head. - drupal_add_html_head($element, 'advagg_resource_hints_dns_prefetch:' . $parse['host']); - } - if (variable_get('advagg_resource_hints_preconnect', ADVAGG_RESOURCE_HINTS_PRECONNECT)) { - // HTTPS use Protocol Relative; HTTP and scheme defined use given scheme. - $href = '//' . $parse['host']; - if (!$GLOBALS['is_https'] && isset($parse['scheme'])) { - $href = "{$parse['scheme']}://{$parse['host']}"; - } - - $element = array( - '#type' => 'html_tag', - '#tag' => 'link', - '#attributes' => array( - 'rel' => 'preconnect', - 'href' => $href, - ), - '#weight' => $weight, - ); - if (!empty($parse['fragment']) && $parse['fragment'] === 'crossorigin') { - $element['#attributes']['crossorigin'] = ''; - } - // Add markup for dns-prefetch to html_head. - drupal_add_html_head($element, 'advagg_resource_hints_preconnect:' . $parse['host']); - } - - // Build render array. Goes after charset tag. - if (!empty($parse['fragment']) && $parse['fragment'] === 'prefetch') { - // Hacky way to open up a connection to the remote host. - $element = array( - '#type' => 'html_tag', - '#tag' => 'link', - '#attributes' => array( - 'rel' => 'prefetch', - 'href' => '//' . $parse['host'] . '/robots.txt', - ), - '#weight' => $weight, - ); - drupal_add_html_head($element, 'advagg_prefetch:' . $parse['host']); - } - return TRUE; -} +} /** * Returns a themed representation of all stylesheets to attach to the page. @@ -2715,13 +1661,14 @@ function advagg_get_css(array $css = array(), $skip_alter = FALSE) { // Allow modules and themes to alter the CSS items. if (!$skip_alter) { - advagg_add_default_dns_lookups($css, 'css'); - // Call hook_css_alter(). drupal_alter('css', $css); - // Call hook_css_post_alter(). - drupal_alter('css_post', $css); - // Call these advagg functions after the hook_css_alter was called. - advagg_fix_type($css, 'css'); + if (variable_get('advagg_run_alter_after_theme', ADVAGG_RUN_ALTER_AFTER_THEME)) { + // Call these advagg css_alter hooks after the theme's hooks were called. + advagg_css_alter($css); + if (module_exists('advagg_mod')) { + advagg_mod_css_alter($css); + } + } } // Sort CSS items, so that they appear in the correct order. @@ -2753,9 +1700,6 @@ function advagg_get_css(array $css = array(), $skip_alter = FALSE) { } } - // Remove empty files. - advagg_remove_empty_files($css); - // Render the HTML needed to load the CSS. $styles = array( '#type' => 'styles', @@ -2801,8 +1745,8 @@ 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. - if (empty($javascript) + // only the settings array is used & scope is header, + if ( empty($javascript) || (isset($javascript['settings']) && count($javascript) == 1) ) { return array(); @@ -2810,44 +1754,16 @@ function advagg_get_full_js(array $javascript = array(), $skip_alter = FALSE) { // Allow modules to alter the JavaScript. if (!$skip_alter) { - advagg_add_default_dns_lookups($javascript, 'js'); - if (is_callable('advagg_mod_js_pre_alter')) { - advagg_mod_js_pre_alter($javascript); - } // Call hook_js_alter(). drupal_alter('js', $javascript); - // Call hook_js_post_alter(). - drupal_alter('js_post', $javascript); - // Call these advagg functions after the hook_js_alter was called. - advagg_fix_type($javascript, 'js'); - } - elseif (is_callable('advagg_mod_js_move_to_footer')) { - if (variable_get('advagg_mod_js_footer', ADVAGG_MOD_JS_FOOTER) == 3) { - 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); + if (variable_get('advagg_run_alter_after_theme', ADVAGG_RUN_ALTER_AFTER_THEME)) { + // Call these advagg js_alter hooks after the theme's hooks were called. + advagg_js_alter($javascript); + if (module_exists('advagg_mod')) { + advagg_mod_js_alter($javascript); } } } - - // Remove empty files. - advagg_remove_empty_files($javascript); - return $javascript; } @@ -2884,23 +1800,30 @@ function advagg_get_full_js(array $javascript = array(), $skip_alter = FALSE) { * @see drupal_js_defaults() */ function advagg_get_js($scope = 'header', array $javascript = array(), $ajax = FALSE) { - // Keep track of js added for ajaxPageState. - $page_state = &drupal_static(__FUNCTION__, array()); + $javascript_settings_data = &drupal_static(__FUNCTION__, array()); // Add in javascript if none was passed in. if (empty($javascript) && !$ajax) { $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(); } + // Set the js_in_footer variable. + $js_in_footer = FALSE; + if ( module_exists('advagg_mod') + && variable_get('advagg_mod_js_footer', ADVAGG_MOD_JS_FOOTER) == 2 + ) { + $js_in_footer = TRUE; + } + // Filter out elements of the given scope. $items = array(); foreach ($javascript as $key => $item) { - if (!empty($item['scope']) && $item['scope'] === $scope) { + if ($item['scope'] == $scope) { $items[$key] = $item; } } @@ -2913,24 +1836,36 @@ function advagg_get_js($scope = 'header', array $javascript = array(), $ajax = F // without that group. We do not use the $key => $item type of iteration, // because PHP uses an internal array pointer for that, and we're modifying // the array order inside the loop. - if ($scope === 'footer' && !empty($items['settings'])) { - // Remove settings array from items. - $settings_js['settings'] = $items['settings']; - unset($items['settings']); - - // Move $settings_js to the bottom of the js that was added to the - // header, but has now been moved to the footer via advagg_mod. - $counter = 0; - foreach ($items as $key => $item) { - if ($item['group'] > 9000) { - advagg_array_splice_assoc($items, $counter, 0, $settings_js); - unset($settings_js); - break; + if ($scope === 'footer' && $js_in_footer) { + // Get all settings from the items array. + $settings_js = array(); + foreach (array_keys($items) as $key) { + if ($items[$key]['type'] === 'setting') { + if (isset($settings_js[$key])) { + $settings_js[$key] += $items[$key]; + } + else { + $settings_js[$key] = $items[$key]; + } + unset($items[$key]); + } + } + + // Add settings array back in right before the footer shift happened. + if (!empty($settings_js)) { + $counter = 0; + foreach ($items as $key => $item) { + // Move $settings_js to the bottom of the js that was added to the + // header, but has now been moved to the footer via advagg_mod. + if ($item['group'] > 9000) { + advagg_array_splice_assoc($items, $counter, 0, $settings_js); + unset($settings_js); + break; + } + ++$counter; } - ++$counter; } - // Nothing in the footer, add settings to the bottom of the array. - if (isset($settings_js)) { + if (!empty($settings_js)) { $items = array_merge($items, $settings_js); } } @@ -2946,7 +1881,21 @@ 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. - $page_state = array_merge($page_state, array_fill_keys(array_keys($items), 1)); + // 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']); + drupal_add_js($setting, 'setting'); + + // Move 'settings' javascript to the header region. + if (!empty($javascript_settings_data) && (($scope === 'header' && !$js_in_footer) || ($scope === 'footer' && $js_in_footer))) { + foreach ($javascript_settings_data as $js_data) { + $items['settings']['data'][] = $js_data; + } + $javascript_settings_data = array(); + } + else { + $javascript_settings_data[] = $setting; + } // 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 @@ -2955,12 +1904,8 @@ function advagg_get_js($scope = 'header', array $javascript = array(), $ajax = F // stripped of settings, potentially in order to override how settings get // output, so in this case, do not add the setting to this output. // Also output the settings if we have pushed all javascript to the footer. - if (isset($items['settings'])) { - $items['settings']['data'][] = array( - 'ajaxPageState' => array( - 'js' => $page_state, - ), - ); + if (isset($items['settings']) && (($scope === 'header' && !$js_in_footer) || ($scope === 'footer' && $js_in_footer))) { + $items['settings']['data'][] = $setting; } // Do not include jQuery.extend(Drupal.settings) if the output is ajax. @@ -2993,7 +1938,7 @@ function advagg_get_js($scope = 'header', array $javascript = array(), $ajax = F '#items' => $items, ); - // Aurora and Omega themes uses alter without checking previous value. + // Aurora & Omega themes uses alter without checking previous value. if (variable_get('advagg_enforce_scripts_callback', TRUE)) { // Get the element_info for scripts. $scripts = element_info('scripts'); @@ -3001,18 +1946,9 @@ function advagg_get_js($scope = 'header', array $javascript = array(), $ajax = F // Directly alter the static. $element_info = &drupal_static('element_info'); advagg_element_info_alter($element_info); - if (function_exists('advagg_mod_element_info_alter')) { - advagg_mod_element_info_alter($element_info); - } } } - // Remove ajaxPageState CSS/JS from Drupal.settings if ajax.js is not used. - if (function_exists('advagg_mod_js_no_ajaxpagestate')) { - if (variable_get('advagg_mod_js_no_ajaxpagestate', ADVAGG_MOD_JS_NO_AJAXPAGESTATE)) { - advagg_mod_js_no_ajaxpagestate($elements); - } - } return $elements; } @@ -3059,20 +1995,18 @@ function advagg_array_splice_assoc(array &$input, $offset, $length, $replacement /** * Callback for array_filter. Will return FALSE if strlen < 3. * - * @param string $value - * A value from an array/object. - * @param int $min_len - * The strlen check length. + * @param string $array_value + * A value from an array. * * @return bool * TRUE or FALSE. */ -function advagg_remove_short_keys($value, $min_len = 3) { - if (strlen($value) < $min_len) { - return TRUE; +function advagg_remove_short_keys($array_value) { + if (strlen($array_value) < 3) { + return FALSE; } else { - return FALSE; + return TRUE; } } @@ -3093,8 +2027,7 @@ function advagg_get_js_scopes(array $javascript) { // Filter out elements of the given scope. $scopes = array(); - $js_settings_in_footer = FALSE; - foreach ($javascript as $name => $item) { + foreach ($javascript as $item) { // Skip if the scope is not set. if (!is_array($item) || empty($item['scope'])) { continue; @@ -3102,9 +2035,6 @@ function advagg_get_js_scopes(array $javascript) { if (!isset($scopes[$item['scope']])) { $scopes[$item['scope']] = TRUE; } - if ($name === 'settings' && $item['scope'] === 'footer') { - $js_settings_in_footer = TRUE; - } } // Default to header if nothing found. @@ -3120,9 +2050,11 @@ function advagg_get_js_scopes(array $javascript) { } // Process footer last if everything has been moved to the footer. - if (isset($scopes['footer']) + // @ignore sniffer_whitespace_openbracketspacing_openingwhitespace + if ( isset($scopes['footer']) && count($scopes) > 1 - && $js_settings_in_footer + && module_exists('advagg_mod') + && variable_get('advagg_mod_js_footer', ADVAGG_MOD_JS_FOOTER) == 2 ) { $temp = $scopes['footer']; unset($scopes['footer']); @@ -3152,7 +2084,8 @@ function advagg_merge_plans(array $css_js_groups, array $plans) { // Remove files from the old css/js array. $file_removed = FALSE; foreach ($css_js_groups[$key]['items'] as $k => $values) { - if (is_array($values) + // @ignore sniffer_whitespace_openbracketspacing_openingwhitespace + if ( is_array($values) && array_key_exists('data', $values) && is_array($plan['items']['files']) && is_string($values['data']) @@ -3183,7 +2116,7 @@ function advagg_merge_plans(array $css_js_groups, array $plans) { $step = 0; do { ++$step; - $insert_key = '' . floatval($key) . '.' . sprintf('%03d', $step); + $insert_key = '' . floatval($key) . '.' . $step; } while (array_key_exists($insert_key, $css_js_groups)); $css_js_groups[(string) $insert_key] = $plan; $plan_added = TRUE; @@ -3199,7 +2132,8 @@ function advagg_merge_plans(array $css_js_groups, array $plans) { if (!$plan_added) { foreach ($css_js_groups as $key => $group) { - if (empty($group['items']['aggregate_filenames_hash']) + // @ignore sniffer_whitespace_openbracketspacing_openingwhitespace + if ( empty($group['items']['aggregate_filenames_hash']) || $group['items']['aggregate_filenames_hash'] != $plan['items']['aggregate_filenames_hash'] || empty($group['items']['aggregate_contents_hash']) || $group['items']['aggregate_contents_hash'] != $plan['items']['aggregate_contents_hash'] @@ -3209,7 +2143,7 @@ function advagg_merge_plans(array $css_js_groups, array $plans) { // Insert a unique key. do { - $key = '' . (floatval($key) + 0.01); + $key = '' . floatval($key) + 0.01; } while (array_key_exists((string) $key, $css_js_groups) || array_key_exists((string) $key, $used_keys)); $used_keys[(string) $key] = TRUE; $css_js_groups[(string) $key] = $plan; @@ -3226,6 +2160,7 @@ function advagg_merge_plans(array $css_js_groups, array $plans) { return $css_js_groups; } +// Helper functions. /** * Function used to see if aggregation is enabled. * @@ -3239,11 +2174,6 @@ function advagg_enabled() { return variable_get('advagg_enabled', ADVAGG_ENABLED); } - // Set base_path if not set. - if (empty($GLOBALS['base_path'])) { - $GLOBALS['base_path'] = rtrim(dirname($_SERVER['SCRIPT_NAME']), '\/') . '/'; - } - $init = TRUE; // Disable AdvAgg if module needs to be upgraded from 1.x to 2.x. if (variable_get('advagg_needs_update', ADVAGG_NEEDS_UPDATE)) { @@ -3258,60 +2188,12 @@ function advagg_enabled() { } } else { - // Get values and fill in defaults if needed. - $config_path = advagg_admin_config_root_path(); - $current_path = current_path(); - $arg = arg(); - $arg += array(1 => '', 2 => '', 3 => '', 4 => '', 5 => ''); - $admin_theme = variable_get('admin_theme'); - - // List of all the pages which will not have Advanced Aggregator enabled. - $list_of_pages = variable_get('advagg_disable_on_listed_pages'); - - $pages = trim(drupal_strtolower($list_of_pages)); - // Convert the Drupal path to lowercase. - $path = drupal_strtolower(drupal_get_path_alias(current_path())); - - // Compare the lowercase internal and lowercase path alias (if any). - $page_match = drupal_match_path($path, $pages); - if ($page_match) { - $GLOBALS['conf']['advagg_enabled'] = FALSE; - $GLOBALS['conf']['preprocess_css'] = FALSE; - $GLOBALS['conf']['preprocess_js'] = FALSE; - } - - // Disable advagg if on admin page and configured to do so. - // AND theme is admin theme - // AND NOT /admin/reports/status - // AND NOT /admin/config/development/performance/. - // AND NOT /admin/appearance/settings/*. - // AND NOT /admin/config/development/performance/advagg/*. - if (variable_get('advagg_disable_on_admin', ADVAGG_DISABLE_ON_ADMIN) - && $GLOBALS['theme'] === $admin_theme - && path_is_admin($current_path) - && !($arg[1] === 'reports' && $arg[2] === 'status') - && !($arg[2] === 'development' && $arg[3] === 'performance' && empty($arg[4])) - && !($arg[1] === 'appearance' && $arg[2] === 'settings' && !empty($arg[3])) - && stripos($current_path, $config_path . '/advagg') !== 0 - ) { - $GLOBALS['conf']['advagg_enabled'] = FALSE; - $GLOBALS['conf']['preprocess_css'] = FALSE; - $GLOBALS['conf']['preprocess_js'] = FALSE; - } - - // Check if the advagg cookie is set. - $cookie_name = 'AdvAggDisabled'; - $bypass_cookie = FALSE; - $key = drupal_hmac_base64('advagg_cookie', drupal_get_private_key() . drupal_get_hash_salt() . variable_get('cron_key', 'drupal')); - if (!empty($_COOKIE[$cookie_name]) && $_COOKIE[$cookie_name] == $key) { - $bypass_cookie = TRUE; - } - // Allow for AdvAgg to be enabled per request. - if (isset($_GET['advagg']) + // @ignore sniffer_whitespace_openbracketspacing_openingwhitespace + if ( isset($_GET['advagg']) && $_GET['advagg'] == 1 && !defined('MAINTENANCE_MODE') - && (user_access('bypass advanced aggregation') || $bypass_cookie) + && user_access('bypass advanced aggregation') ) { $GLOBALS['conf']['advagg_enabled'] = TRUE; $GLOBALS['conf']['preprocess_css'] = TRUE; @@ -3326,11 +2208,12 @@ function advagg_enabled() { if (variable_get('advagg_enabled', ADVAGG_ENABLED)) { // Do not use AdvAgg or preprocessing functions if the disable cookie is // set. - if ($bypass_cookie && !isset($_GET['advagg'])) { + $cookie_name = 'AdvAggDisabled'; + $key = drupal_hash_base64(drupal_get_private_key()); + if (!empty($_COOKIE[$cookie_name]) && $_COOKIE[$cookie_name] == $key) { $GLOBALS['conf']['advagg_enabled'] = FALSE; $GLOBALS['conf']['preprocess_css'] = FALSE; $GLOBALS['conf']['preprocess_js'] = FALSE; - $bypass_cookie = TRUE; // Let the user know that the AdvAgg bypass cookie is currently set. static $msg_set; @@ -3338,7 +2221,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 <a href="@advagg_operations">AdvAgg Operations</a> page and clicking the <em>Toggle the "aggregation bypass cookie" for this browser</em> 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 { @@ -3350,36 +2233,40 @@ function advagg_enabled() { } } // Disable advagg if requested. - if (isset($_GET['advagg']) + // @ignore sniffer_whitespace_openbracketspacing_openingwhitespace + if ( isset($_GET['advagg']) && $_GET['advagg'] == -1 - && (user_access('bypass advanced aggregation') || $bypass_cookie) + && user_access('bypass advanced aggregation') ) { $GLOBALS['conf']['advagg_enabled'] = FALSE; $GLOBALS['conf']['preprocess_css'] = FALSE; $GLOBALS['conf']['preprocess_js'] = FALSE; } // Disable core preprocessing if requested. - if (isset($_GET['advagg-core']) + // @ignore sniffer_whitespace_openbracketspacing_openingwhitespace + if ( isset($_GET['advagg-core']) && $_GET['advagg-core'] == 0 - && (user_access('bypass advanced aggregation') || $bypass_cookie) + && user_access('bypass advanced aggregation') ) { $GLOBALS['conf']['preprocess_css'] = FALSE; $GLOBALS['conf']['preprocess_js'] = FALSE; } // Enable core preprocessing if requested. - if (isset($_GET['advagg-core']) + // @ignore sniffer_whitespace_openbracketspacing_openingwhitespace + if ( isset($_GET['advagg-core']) && $_GET['advagg-core'] == 1 - && (user_access('bypass advanced aggregation') || $bypass_cookie) + && user_access('bypass advanced aggregation') ) { $GLOBALS['conf']['preprocess_css'] = TRUE; $GLOBALS['conf']['preprocess_js'] = TRUE; } // Enable debugging if requested. - if (isset($_GET['advagg-debug']) - && (user_access('bypass advanced aggregation') || $bypass_cookie) + // @ignore sniffer_whitespace_openbracketspacing_openingwhitespace + if ( isset($_GET['advagg-debug']) + && $_GET['advagg-debug'] == 1 + && user_access('bypass advanced aggregation') ) { - // Cast to an int. - $GLOBALS['conf']['advagg_debug'] = (int) $_GET['advagg-debug']; + $GLOBALS['conf']['advagg_debug'] = TRUE; } } } @@ -3405,17 +2292,14 @@ function advagg_admin_config_root_path() { */ function advagg_current_hooks_hash_array() { $aggregate_settings = &drupal_static(__FUNCTION__); - if (!empty($aggregate_settings)) { + 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( 'variables' => array( 'advagg_gzip' => variable_get('advagg_gzip', ADVAGG_GZIP), - 'advagg_brotli' => variable_get('advagg_brotli', ADVAGG_BROTLI), - 'advagg_no_zopfli' => variable_get('advagg_no_zopfli', ADVAGG_NO_ZOPFLI), 'is_https' => $GLOBALS['is_https'], 'advagg_global_counter' => advagg_get_global_counter(), 'base_path' => $GLOBALS['base_path'], @@ -3425,10 +2309,7 @@ function advagg_current_hooks_hash_array() { 'advagg_devel' => variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL) < 0 ? TRUE : FALSE, '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_css_absolute_path' => variable_get('advagg_css_absolute_path', ADVAGG_CSS_ABSOLUTE_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), ); @@ -3463,7 +2344,7 @@ function advagg_current_hooks_hash_array() { } /** - * Get the hash of all hooks and settings that affect aggregated files contents. + * Get the hash of all hooks & settings that affect aggregated files contents. * * @return string * hash value. @@ -3471,13 +2352,12 @@ function advagg_current_hooks_hash_array() { function advagg_get_current_hooks_hash() { $current_hash = &drupal_static(__FUNCTION__); - if (empty($current_hash)) { + if (!isset($current_hash)) { // Get all advagg hooks and variables in use. $aggregate_settings = advagg_current_hooks_hash_array(); // Generate the hash. - $serialize_function = variable_get('advagg_serialize', ADVAGG_SERIALIZE); - $current_hash = drupal_hash_base64($serialize_function($aggregate_settings)); + $current_hash = drupal_hash_base64(serialize($aggregate_settings)); // Save into variables for verification purposes later on if not found. $settings = advagg_get_hash_settings($current_hash); @@ -3493,18 +2373,13 @@ 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 */ -function advagg_set_hash_settings($hash, array $settings = array()) { +function advagg_set_hash_settings($hash, $settings) { return db_merge('advagg_aggregates_hashes') ->key(array('hash' => $hash)) - ->fields(array( + ->insertFields(array( 'hash' => $hash, 'settings' => serialize($settings), )) @@ -3524,12 +2399,10 @@ function advagg_set_hash_settings($hash, array $settings = array()) { function advagg_hooks_implemented($all = TRUE) { // Get hooks in use. $hooks = array( - 'advagg_get_css_file_contents_pre_alter' => array(), 'advagg_get_css_file_contents_alter' => array(), 'advagg_get_css_aggregate_contents_alter' => array(), 'advagg_get_js_file_contents_alter' => array(), 'advagg_get_js_aggregate_contents_alter' => array(), - 'advagg_save_aggregate_pre_alter' => array(), 'advagg_save_aggregate_alter' => array(), 'advagg_current_hooks_hash_array_alter' => array(), 'advagg_get_root_files_dir_alter' => array(), @@ -3538,7 +2411,6 @@ 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(), @@ -3548,7 +2420,6 @@ 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(), ); @@ -3557,8 +2428,7 @@ function advagg_hooks_implemented($all = TRUE) { drupal_alter('advagg_hooks_implemented', $hooks, $all); // Cache module_implements as this will load up .inc files. - $serialize_function = variable_get('advagg_serialize', ADVAGG_SERIALIZE); - $cid = 'advagg_hooks_implemented:' . (int) $all . ':' . drupal_hash_base64($serialize_function($hooks)); + $cid = 'advagg_hooks_implemented:' . (int) $all . ':' . drupal_hash_base64(serialize($hooks)); $cache = cache_get($cid, 'cache_bootstrap'); if (!empty($cache->data)) { $hooks = $cache->data; @@ -3603,8 +2473,9 @@ function advagg_get_hash_settings($hash) { return !empty($settings) ? unserialize($settings) : array(); } +// @ignore production_code:20 /** - * Get the CSS and JS path for advagg. + * Get the CSS & JS path for advagg. * * @param bool $reset * Set to TRUE to reset the static variables. @@ -3631,33 +2502,15 @@ function advagg_get_root_files_dir($reset = FALSE) { // Make sure directories are available and writable. if (empty($css_paths) || empty($js_paths) || $reset) { - // 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'; + $css_paths[0] = 'public://advagg_css'; + $js_paths[0] = 'public://advagg_js'; file_prepare_directory($css_paths[0], FILE_CREATE_DIRECTORY); file_prepare_directory($js_paths[0], FILE_CREATE_DIRECTORY); // Set the URI of the directory. - $css_paths[1] = advagg_get_relative_path($css_paths[0], 'css'); - $js_paths[1] = advagg_get_relative_path($js_paths[0], 'js'); - - // If the css or js got a path, use it for the other missing one. - if (empty($css_paths[1]) && !empty($js_paths[1])) { - $css_paths[1] = str_replace('/advagg_js', '/advagg_css', $js_paths[1]); - } - elseif (empty($js_paths[1]) && !empty($css_paths[1])) { - $js_paths[1] = str_replace('/advagg_css', '/advagg_js', $css_paths[1]); - } - - // Fix if empty. - if (empty($css_paths[1])) { - $css_paths[1] = $css_paths[0]; - } - if (empty($js_paths[1])) { - $js_paths[1] = $js_paths[0]; - } + $css_paths[1] = advagg_get_relative_path($css_paths[0]); + $js_paths[1] = advagg_get_relative_path($js_paths[0]); // Allow other modules to alter css and js paths. // Call hook_advagg_get_root_files_dir_alter() @@ -3672,33 +2525,22 @@ function advagg_get_root_files_dir($reset = FALSE) { * * @param string $uri * The uri for the stream wrapper. - * @param string $type - * (Optional) String: css or js. * * @return string * The relative path of the uri. * * @see https://www.drupal.org/node/837794#comment-9124435 */ -function advagg_get_relative_path($uri, $type = '') { +function advagg_get_relative_path($uri) { $wrapper = file_stream_wrapper_get_instance_by_uri($uri); if ($wrapper instanceof DrupalLocalStreamWrapper) { $relative_path = $wrapper->getDirectoryPath() . '/' . file_uri_target($uri); } else { $relative_path = parse_url(file_create_url($uri), PHP_URL_PATH); - if (empty($relative_path) && !empty($uri)) { - $filename = advagg_generate_advagg_filename_from_db($type); - $relative_path = parse_url(file_create_url("{$uri}/{$filename}"), PHP_URL_PATH); - $end = strpos($relative_path, "/{$filename}"); - if ($end !== FALSE) { - $relative_path = substr($relative_path, 0, $end); - } - } if (substr($relative_path, 0, strlen($GLOBALS['base_path'])) == $GLOBALS['base_path']) { $relative_path = substr($relative_path, strlen($GLOBALS['base_path'])); } - $relative_path = ltrim($relative_path, '/'); } return $relative_path; } @@ -3721,7 +2563,7 @@ function advagg_build_aggregates(array $filenames, $type) { } if (empty($type)) { $filename = reset($filenames); - $type = strtolower(pathinfo($filename, PATHINFO_EXTENSION)); + $type = pathinfo($filename, PATHINFO_EXTENSION); } // Call the file generation function directly. @@ -3742,10 +2584,7 @@ function advagg_build_aggregates(array $filenames, $type) { // Only create the file if we have a lock. $lock_name = 'advagg_' . $filename; - if (variable_get('advagg_no_locks', ADVAGG_NO_LOCKS)) { - $return[$filename] = advagg_missing_create_file($filename); - } - elseif (lock_acquire($lock_name, 10)) { + if (lock_acquire($lock_name, 10)) { $return[$filename] = advagg_missing_create_file($filename); lock_release($lock_name); } @@ -3785,7 +2624,6 @@ function advagg_build_ajax_js_css() { } else { $function = 'drupal_add_' . $type; - // Get the current css/js needed for this page. $items[$type] = $function(); // Call hook_js_alter() OR hook_css_alter(). drupal_alter($type, $items[$type]); @@ -3810,12 +2648,10 @@ function advagg_build_ajax_js_css() { // HTML in the page. We pass TRUE as the $skip_alter argument to prevent the // data from being altered again, as we already altered it above. Settings are // handled separately, afterwards. - $scripts = drupal_add_js(); - if (isset($scripts['settings'])) { - $settings = $scripts['settings']; + if (isset($items['js']['settings'])) { + $settings = $items['js']['settings']; unset($items['js']['settings']); } - $styles = drupal_get_css($items['css'], TRUE); $scripts_footer = drupal_get_js('footer', $items['js'], TRUE); $scripts_header = drupal_get_js('header', $items['js'], TRUE); @@ -3847,7 +2683,7 @@ function advagg_file_aggregation_enabled($type) { } /** - * Update atime inside advagg_aggregates_versions and cache_advagg_info. + * Update atime advagg_aggregates_versions table & cache_advagg_info cache bin. * * @param array $files * List of files in the aggregate as well as the aggregate name. @@ -3874,7 +2710,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]); @@ -3891,8 +2727,8 @@ 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'])) + )) + ->updateFields(array('atime' => $record['atime'])) ->execute(); if (!$write_done && $result) { $write_done = TRUE; @@ -3935,83 +2771,8 @@ function advagg_admin_flush_cache() { advagg_admin_flush_cache_button(); } -/** - * Returns HTML for a generic HTML tag with attributes. - * - * @param array $variables - * An associative array containing: - * - element: An associative array describing the tag: - * - #tag: The tag name to output. Typical tags added to the HTML HEAD: - * - meta: To provide meta information, such as a page refresh. - * - link: To refer to stylesheets and other contextual information. - * - script: To load JavaScript. - * - #attributes: (optional) An array of HTML attributes to apply to the - * tag. - * - #value: (optional) A string containing tag content, such as inline - * CSS. - * - #value_prefix: (optional) A string to prepend to #value, e.g. a CDATA - * wrapper prefix. - * - #value_suffix: (optional) A string to append to #value, e.g. a CDATA - * wrapper suffix. - */ -function theme_html_script_tag(array $variables) { - $element = $variables['element']; - $attributes = ''; - $onload = ''; - $onerror = ''; - if (isset($element['#attributes'])) { - // On Load. - if (!empty($element['#attributes']['onload'])) { - $onload = $element['#attributes']['onload']; - unset($element['#attributes']['onload']); - } - // On Error. - if (!empty($element['#attributes']['onerror'])) { - $onerror = $element['#attributes']['onerror']; - unset($element['#attributes']['onerror']); - } - $attributes = !empty($element['#attributes']) ? drupal_attributes($element['#attributes']) : ''; - if (!empty($onload)) { - $attributes .= ' onload="' . advagg_jsspecialchars($onload) . '"'; - } - if (!empty($onerror)) { - $attributes .= ' onerror="' . advagg_jsspecialchars($onerror) . '"'; - } - } - - if (!isset($element['#value'])) { - return '<' . $element['#tag'] . $attributes . " />\n"; - } - else { - $output = '<' . $element['#tag'] . $attributes . '>'; - if (isset($element['#value_prefix'])) { - $output .= $element['#value_prefix']; - } - $output .= $element['#value']; - if (isset($element['#value_suffix'])) { - $output .= $element['#value_suffix']; - } - $output .= '</' . $element['#tag'] . ">\n"; - return $output; - } -} - -/** - * Replace quotes with the html version of it. - * - * @param string $string - * Input string. Convert quotes to html chars. - * - * @return string - * Transformed string. - */ -function advagg_jsspecialchars($string = '') { - $string = str_replace('"', '"', $string); - $string = str_replace("'", ''', $string); - - return $string; -} +// Exact Copies of core functions from patches. /** * Callback for pre_render to add elements needed for JavaScript to be rendered. * @@ -4037,18 +2798,11 @@ function advagg_jsspecialchars($string = '') { * @see drupal_get_js() */ function advagg_pre_render_scripts(array $elements) { - // Don't run it twice. - if (!empty($elements['#groups'])) { - return $elements; - } - // Group and aggregate the items. if (isset($elements['#group_callback'])) { - // Call advagg_group_js(). $elements['#groups'] = $elements['#group_callback']($elements['#items']); } if (isset($elements['#aggregate_callback'])) { - // Call _advagg_aggregate_js(). $elements['#aggregate_callback']($elements['#groups']); } @@ -4072,17 +2826,13 @@ function advagg_pre_render_scripts(array $elements) { // Defaults for each SCRIPT element. $element_defaults = array( - '#type' => 'html_script_tag', + '#type' => 'html_tag', '#tag' => 'script', '#value' => '', '#attributes' => array( 'type' => 'text/javascript', ), ); - $hooks = theme_get_registry(FALSE); - if (empty($hooks['html_script_tag'])) { - $element_defaults['#type'] = 'html_tag'; - } // Loop through each group. foreach ($elements['#groups'] as $group) { @@ -4111,9 +2861,6 @@ function advagg_pre_render_scripts(array $elements) { } $element['#attributes']['onerror'] .= $group['onerror']; } - if (!empty($group['attributes'])) { - $element['#attributes'] += $group['attributes']; - } $elements[] = $element; } // For non-file types, and non-aggregated files, add a script element per @@ -4145,9 +2892,6 @@ function advagg_pre_render_scripts(array $elements) { } $element['#attributes']['onerror'] .= $item['onerror']; } - if (!empty($group['attributes'])) { - $element['#attributes'] += $group['attributes']; - } $element['#browsers'] = isset($item['browsers']) ? $item['browsers'] : array(); // Crude type detection if needed. @@ -4168,7 +2912,7 @@ function advagg_pre_render_scripts(array $elements) { // Element properties that depend on item type. switch ($item['type']) { case 'setting': - $data = advagg_cleanup_settings_array(drupal_array_merge_deep_array(array_filter($item['data'], 'is_array'))); + $data = 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 . ");"; @@ -4176,25 +2920,16 @@ function advagg_pre_render_scripts(array $elements) { break; case 'inline': - // If a BOM is found, convert the string to UTF-8. - $encoding = advagg_get_encoding_from_bom($item['data']); - if (!empty($encoding)) { - $item['data'] = advagg_convert_to_utf8($item['data'], $encoding); - } - $element['#value_prefix'] = $embed_prefix; $element['#value'] = $item['data']; $element['#value_suffix'] = $embed_suffix; break; case 'file': + $query_string = empty($item['version']) ? $default_query_string : $js_version_string . $item['version']; $query_string_separator = (strpos($item['data'], '?') !== FALSE) ? '&' : '?'; - $cache_validator = REQUEST_TIME; - if (!empty($item['cache'])) { - $cache_validator = empty($item['version']) ? $default_query_string : $js_version_string . $item['version'];; - } - $element['#attributes']['src'] = advagg_file_create_url($item['data']) . $query_string_separator . $cache_validator; + $element['#attributes']['src'] = advagg_file_create_url($item['data']) . $query_string_separator . ($item['cache'] ? $query_string : REQUEST_TIME); break; case 'external': @@ -4289,23 +3024,11 @@ function advagg_get_css_prefix_suffix() { * @see drupal_get_css() */ function advagg_pre_render_styles(array $elements) { - // Skip if advagg is disabled. - if (!advagg_enabled()) { - return drupal_pre_render_styles($elements); - } - - // Don't run it twice. - if (!empty($elements['#groups'])) { - return $elements; - } - // Group and aggregate the items. if (isset($elements['#group_callback'])) { - // Call drupal_group_css(). $elements['#groups'] = $elements['#group_callback']($elements['#items']); } if (isset($elements['#aggregate_callback'])) { - // Call _advagg_aggregate_css(). $elements['#aggregate_callback']($elements['#groups']); } @@ -4360,9 +3083,6 @@ 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 @@ -4384,7 +3104,7 @@ function advagg_pre_render_styles(array $elements) { if (file_exists($item['data'])) { // The dummy query string needs to be added to the URL to control // browser-caching. IE7 does not support a media type on the - // "@import" statement, so we instead specify the media for the + // @import statement, so we instead specify the media for the // group on the STYLE tag. $import[] = '@import url("' . check_plain(advagg_file_create_url($item['data']) . '?' . $query_string) . '");'; } @@ -4402,9 +3122,6 @@ 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; } } @@ -4427,9 +3144,6 @@ 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; } } @@ -4446,9 +3160,6 @@ 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 { @@ -4459,9 +3170,6 @@ 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; } } @@ -4480,9 +3188,6 @@ 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; @@ -4533,9 +3238,6 @@ function advagg_group_js(array $javascript) { $current_group_keys = NULL; $index = -1; foreach ($javascript as $key => $item) { - if (empty($item)) { - continue; - } // The browsers for which the JavaScript item needs to be loaded is part of // the information that determines when a new group is needed, but the order // of keys in the array doesn't matter, and we don't want a new group if all @@ -4547,32 +3249,8 @@ function advagg_group_js(array $javascript) { $item['browsers'] = array(); } - // Fix missing types. - if (empty($item['type'])) { - // Setting is easy. - if ($key === 'settings') { - $item['type'] = 'setting'; - } - // Check for the schema or // for protocol-relative. - elseif ((stripos($item['data'], 'http://') === 0 - || stripos($item['data'], 'https://') === 0 - || (strpos($item['data'], '//') === 0 && strpos($item['data'], '///') === FALSE)) - ) { - $item['type'] = 'external'; - } - // See if the data contains a semicolon, new line, $, or quotes. - elseif (strpos($item['data'], ';') !== FALSE - || strpos($item['data'], "\n") - || strpos($item['data'], "$") - || strpos($item['data'], "'") - || strpos($item['data'], '"') - ) { - $item['type'] = 'inline'; - } - // Ends in .js. - elseif (stripos(strrev($item['data']), strrev('.js')) === 0) { - $item['type'] = 'file'; - } + if (empty($item['type']) && $key === 'settings') { + $item['type'] = 'setting'; } switch ($item['type']) { @@ -4581,7 +3259,7 @@ function advagg_group_js(array $javascript) { // Help ensure maximum reuse of aggregate files by only grouping // together items that share the same 'group' value and 'every_page' // flag. See drupal_add_js() for details about that. - $group_keys = !empty($item['preprocess']) ? array( + $group_keys = $item['preprocess'] ? array( $item['type'], $item['group'], $item['every_page'], @@ -4599,11 +3277,6 @@ function advagg_group_js(array $javascript) { default: // Define this here so we don't get undefined alerts down below. $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); break; } @@ -4654,24 +3327,7 @@ function advagg_drupal_sort_css_js_stable(array &$items) { // the aggregate file generated for all of the common files to be reused // across a site visit without being cut by a page using a less common file. $nested = array(); - foreach ($items as $key => &$item) { - // If weight is not set, make it 0. - if (!isset($item['weight'])) { - $item['weight'] = 0; - } - // If every_page is not set, make it FALSE. - if (!isset($item['every_page'])) { - $item['every_page'] = FALSE; - } - // If group is not set, make it CSS_DEFAULT/JS_DEFAULT (0). - if (!isset($item['group'])) { - $item['group'] = 0; - } - // If scope is not set, make it header. - if (!isset($item['scope'])) { - $item['scope'] = 'header'; - } - + foreach ($items as $key => $item) { // Weight cast to string to preserve float. $weight = (string) $item['weight']; $nested[$item['group']][$item['every_page'] ? 1 : 0][$weight][$key] = $item; @@ -4728,7 +3384,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); } @@ -4736,11 +3392,11 @@ function advagg_json_encode($data) { // Default json encode options. $options = JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP | JSON_HEX_QUOT; if ($php550 && variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL) >= 0) { - // Output partial json if not in development mode and PHP >= 5.5.0. + // Output partial json if not in development mode & PHP >= 5.5.0. $options |= JSON_PARTIAL_OUTPUT_ON_ERROR; } if ($php540 && variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL) < 0) { - // Pretty print JSON if in development mode and PHP >= 5.4.0. + // Pretty print JSON if in development mode & PHP >= 5.4.0. $options |= JSON_PRETTY_PRINT; } // Encode to JSON. @@ -4779,12 +3435,12 @@ function advagg_json_encode($data) { break; } if (!empty($error_message)) { - if (is_callable('httprl_pr')) { + if (module_exists('httprl') && function_exists('httprl_pr')) { $pretty_data = httprl_pr($data); } - elseif (is_callable('kprint_r')) { - // @codingStandardsIgnoreLine - $pretty_data = kprint_r($data, TRUE); + elseif (module_exists('devel') && function_exists('kprint_r')) { + // @ignore production_php:1 + $pretty_data = kprint_r($data); } else { $pretty_data = '<pre>' . filter_xss(print_r($data, TRUE)) . '</pre>'; @@ -4799,7 +3455,7 @@ function advagg_json_encode($data) { } /** - * Will scan, flush, use, and report any changes to css/js files in aggregates. + * Will scan, flush, use & report any changes to css/js files in aggregates. */ function advagg_scan_filesystem_for_changes_live() { static $function_has_ran; @@ -4810,7 +3466,7 @@ function advagg_scan_filesystem_for_changes_live() { $bypass_cookie = FALSE; $cookie_name = 'AdvAggDisabled'; - $key = drupal_hmac_base64('advagg_cookie', drupal_get_private_key() . drupal_get_hash_salt() . variable_get('cron_key', 'drupal')); + $key = drupal_hash_base64(drupal_get_private_key()); if (!empty($_COOKIE[$cookie_name]) && $_COOKIE[$cookie_name] == $key) { $bypass_cookie = TRUE; } @@ -4835,16 +3491,13 @@ function advagg_scan_filesystem_for_changes_live() { // Do not report on css files manged in the parts directory. continue; } - 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. Trigger: <code>@changes</code>', array( - '%filename' => $filename, - '%db_usage' => count($data[0]), - '%db_count' => count($data[1]), - '@changes' => print_r($data[2], TRUE), - '%type' => $ext, - ))); - } + $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, + ))); } } @@ -4866,38 +3519,14 @@ function advagg_match_file_pattern($filename) { * * @param string $path * Path to check. - * @param bool $strip_base_path - * Do no add the base path to the given path if TRUE. * * @return string * The path. */ -function advagg_convert_abs_to_rel($path, $strip_base_path = FALSE) { - $base_url = $GLOBALS['base_url']; - // Add a slash to end if none is found. - if (strpos(strrev($base_url), '/') !== 0) { - $base_url .= '/'; - } - // Set base path. - $base_path = $GLOBALS['base_path']; - if ($strip_base_path) { - $base_path = ''; +function advagg_convert_abs_to_rel($path) { + if (strpos($path, $GLOBALS['base_url']) === 0) { + $path = str_replace($GLOBALS['base_url'] . '/', $GLOBALS['base_path'], $path); } - - // Do conversion of https and http to self references. - $base_url_https = advagg_force_https_path($base_url); - $path = str_replace($base_url_https, $base_path, $path); - $base_url_http = advagg_force_http_path($base_url); - $path = str_replace($base_url_http, $base_path, $path); - - $base_url = advagg_convert_abs_to_protocol($GLOBALS['base_url']); - // Add a slash to end if none is found. - if (strpos(strrev($base_url), '/') !== 0) { - $base_url .= '/'; - } - // Do conversion of protocol relative to self references. - $path = str_replace($base_url, $base_path, $path); - return $path; } @@ -4911,14 +3540,17 @@ function advagg_convert_abs_to_rel($path, $strip_base_path = FALSE) { * The path. */ function advagg_convert_abs_to_protocol($path) { - if (strpos($path, 'http://') === 0) { + if (strpos($path, 'https://') === 0) { + $path = substr($path, 6); + } + elseif (strpos($path, 'http://') === 0) { $path = substr($path, 5); } return $path; } /** - * Convert http:// and // to https://. + * Convert http:// to https://. * * @param string $path * Path to check. @@ -4930,9 +3562,6 @@ function advagg_force_https_path($path) { if (strpos($path, 'http://') === 0) { $path = 'https://' . substr($path, 7); } - elseif (strpos($path, '//') === 0) { - $path = 'https:' . $path; - } return $path; } @@ -4959,23 +3588,12 @@ function advagg_force_http_path($path) { * Path to check. * @param array $aggregate_settings * Array of settings used. - * @param bool $run_file_create_url - * If TRUE then run the given path through file_create_url(). - * @param string $source_type - * CSS or JS; if empty url in not embedded in another file. * * @return string * The file uri. */ -function advagg_file_create_url($path, array $aggregate_settings = array(), $run_file_create_url = TRUE, $source_type = '') { - $file_uri = $path; - if ($run_file_create_url) { - // This calls hook_file_url_alter(). - $file_uri = file_create_url($path); - } - elseif (strpos($path, '/') !== 0 && !advagg_is_external($path)) { - $file_uri = '/' . $path; - } +function advagg_file_create_url($path, array $aggregate_settings = array()) { + $file_uri = file_create_url($path); // Ideally convert to relative path. if ((isset($aggregate_settings['variables']['advagg_convert_absolute_to_relative_path']) && $aggregate_settings['variables']['advagg_convert_absolute_to_relative_path']) @@ -4992,43 +3610,6 @@ function advagg_file_create_url($path, array $aggregate_settings = array(), $run ) { $file_uri = advagg_convert_abs_to_protocol($file_uri); } - if ($source_type === 'css' - && !advagg_is_external($file_uri) - && ((isset($aggregate_settings['variables']['advagg_css_absolute_path']) - && $aggregate_settings['variables']['advagg_css_absolute_path']) - || (!isset($aggregate_settings['variables']['advagg_css_absolute_path']) - && variable_get('advagg_css_absolute_path', ADVAGG_CSS_ABSOLUTE_PATH))) - ) { - // Get public dir. - list($css_path) = advagg_get_root_files_dir(); - $parsed = parse_url($css_path[0]); - $new_parsed = array(); - if (!empty($parsed['host'])) { - $new_parsed['host'] = $parsed['host']; - } - if (!empty($parsed['path'])) { - $new_parsed['path'] = $parsed['path']; - } - $css_path_0 = advagg_glue_url($new_parsed); - $parsed = parse_url($css_path[1]); - $new_parsed = array(); - if (!empty($parsed['host'])) { - $new_parsed['host'] = $parsed['host']; - } - if (!empty($parsed['path'])) { - $new_parsed['path'] = $parsed['path']; - } - $css_path_1 = advagg_glue_url($new_parsed); - $pos = strpos($css_path_1, $css_path_0); - if (!empty($pos)) { - $public_dir = substr($css_path_1, 0, $pos); - - // If public dir is not in the file uri, use absolute URL. - if (strpos($file_uri, $public_dir) === FALSE) { - $file_uri = url($path, array('absolute' => TRUE)); - } - } - } // Finally force https. if ((isset($aggregate_settings['variables']['advagg_force_https_path']) && $aggregate_settings['variables']['advagg_force_https_path']) @@ -5040,6 +3621,7 @@ function advagg_file_create_url($path, array $aggregate_settings = array(), $run return $file_uri; } + /** * Loads the stylesheet and resolves all @import commands. * @@ -5063,14 +3645,14 @@ function advagg_file_create_url($path, array $aggregate_settings = array(), $run * * @see drupal_load_stylesheet() */ -function advagg_load_stylesheet($file, $optimize = FALSE, $reset_basepath = TRUE, $contents = '') { - // These static's are not cache variables, so we don't use drupal_static(). +function advagg_load_stylesheet($file, $optimize = FALSE, $reset_basepath = TRUE) { + // These statics are not cache variables, so we don't use drupal_static(). static $_optimize, $basepath; if ($reset_basepath) { $basepath = ''; } - // Store the value of $optimize for preg_replace_callback with nested @import - // loops. + // Store the value of $optimize for preg_replace_callback with nested + // @import loops. if (isset($optimize)) { $_optimize = $optimize; } @@ -5089,9 +3671,7 @@ 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 = ''; - if (empty($contents) && !empty($file)) { - $contents = (string) @advagg_file_get_contents($file); - } + $contents = @file_get_contents($file); if ($contents) { // Return the processed stylesheet. $content = advagg_load_stylesheet_content($contents, $_optimize); @@ -5106,94 +3686,31 @@ function advagg_load_stylesheet($file, $optimize = FALSE, $reset_basepath = TRUE } /** - * Decodes UTF byte-order mark (BOM) into the encoding's name. + * Processes the contents of a stylesheet for aggregation. * - * @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. + * @param string $contents + * The contents of the stylesheet. + * @param bool $optimize + * (Optional) Boolean whether CSS contents should be minified. Defaults to + * FALSE. * - * @return string|bool - * The name of the encoding, or FALSE if no byte order mark was present. + * @return string + * Contents of the stylesheet including the imported stylesheets. * - * @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. - * - * @param string $contents - * The contents of the stylesheet. - * @param bool $optimize - * (Optional) Boolean whether CSS contents should be minified. Defaults to - * FALSE. - * - * @return string - * Contents of the stylesheet including the imported stylesheets. - * - * @see drupal_load_stylesheet_content() + * @see drupal_load_stylesheet_content() */ function advagg_load_stylesheet_content($contents, $optimize = FALSE) { - // If a BOM is found, convert the file to UTF-8. Used for inline CSS here. - $encoding = advagg_get_encoding_from_bom($contents); - if (!empty($encoding)) { - $contents = advagg_convert_to_utf8($contents, $encoding); - } + // Remove multiple charset declarations for standards compliance (and fixing + // Safari problems). + $contents = preg_replace('/^@charset\s+[\'"](\S*?)\b[\'"];/i', '', $contents); if ($optimize) { // Perform some safe CSS optimizations. // Regexp to match comment blocks. - // Regexp to match double quoted strings. - // Regexp to match single quoted strings. $comment = '/\*[^*]*\*+(?:[^/*][^*]*\*+)*/'; + // Regexp to match double quoted strings. $double_quot = '"[^"\\\\]*(?:\\\\.[^"\\\\]*)*"'; + // Regexp to match single quoted strings. $single_quot = "'[^'\\\\]*(?:\\\\.[^'\\\\]*)*'"; // Strip all comment blocks, but keep double/single quoted strings. $contents = preg_replace( @@ -5201,11 +3718,11 @@ function advagg_load_stylesheet_content($contents, $optimize = FALSE) { "$1", $contents ); - // Remove certain whitespace. // There are different conditions for removing leading and trailing // whitespace. // @see http://php.net/manual/regexp.reference.subpatterns.php + // @ignore comment_comment_shell:13 $contents = preg_replace('< # Do not strip any space from within single or double quotes (' . $double_quot . '|' . $single_quot . ') @@ -5218,7 +3735,7 @@ function advagg_load_stylesheet_content($contents, $optimize = FALSE) { # - Opening parenthesis: Retain "@media (bar) and foo". # - Colon: Retain :pseudo-selectors. | ([\(:])\s+ - >xSs', + >xS', // Only one of the three capturing groups will match, so its reference // will contain the wanted value and the references for the // two non-matching groups will be replaced with empty strings. @@ -5230,10 +3747,6 @@ function advagg_load_stylesheet_content($contents, $optimize = FALSE) { $contents .= "\n"; } - // Remove multiple charset declarations for standards compliance (and fixing - // Safari problems). - $contents = preg_replace('/^@charset\s+[\'"](\S*?)\b[\'"];/i', '', $contents); - // Replaces @import commands with the actual stylesheet content. // This happens recursively but omits external files. $contents = preg_replace_callback('%@import\s*+(?:url\(\s*+)?+[\'"]?+(?![a-z]++:|/)([^\'"()\s]++)[\'"]?+\s*+\)?+\s*+;%i', '_advagg_load_stylesheet', $contents); @@ -5259,24 +3772,6 @@ function _advagg_load_stylesheet(array $matches) { // Load the imported stylesheet and replace @import commands in there as well. $file = advagg_load_stylesheet($filename, NULL, FALSE); - if (empty($file)) { - if (strpos($matches[0], 'http://') === 0 - || strpos($matches[0], 'https://') === 0 - || strpos($matches[0], '//') === 0 - ) { - return $matches[0]; - } - if (variable_get('advagg_debug', ADVAGG_DEBUG) >= 2) { - watchdog('advagg-debug', 'Trying to load @file via @import statement but it was not found.', array('@file' => $filename), WATCHDOG_DEBUG); - } - if (variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL) <= 1) { - return $matches[0]; - } - else { - return ''; - } - } - // Determine the file's directory. $directory = dirname($filename); // If the file is in the current directory, make sure '.' doesn't appear in @@ -5301,27 +3796,12 @@ function advagg_aggressive_cache_conflicts() { $hooks[$hook] = module_implements($hook); // Also check themes as drupal_alter() allows for themes to alter things. - $themes = list_themes(); - $theme_keys = array_keys($themes); + $theme_keys = array_keys(list_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 = (string) @advagg_file_get_contents($file); - if (stripos($contents, $function)) { - $hooks[$hook][] = $theme_key; - } } } } @@ -5342,46 +3822,40 @@ 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', - // +// 'ais', + // No control; same every time. 'bluecheese', // drupal_static('clientside_validation_settings') array. - // 'clientside_validation', - // - // version_compare(VERSION, '7.14', '<'). - 'conditional_fields', +// 'clientside_validation', // _css_injector_load_rule() function. // Changes the weight of all files added in init so no special handling. - // 'css_injector', - // +// 'css_injector', + // disable_css_ . $theme . _all variable; default: FALSE. // disable_css_ . $theme . _modules; default: array(). // disable_css_ . $theme . _files; default: array(). - // 'disable_css', - // +// 'disable_css', + // Empty call; commented code is same every time. 'elfinder', // excluded_css_custom variable; Default: ''. // excluded_javascript_custom variable; Default: ''. - // 'excluded', - // +// 'excluded', + // No control; same every time. 'fences', // jqmulti_jquery_path() function. // jqmulti_get_files() function. // jqmulti_load_always variable; Default: FALSE. - // 'jqmulti', - // +// 'jqmulti', + // No control; same every time. 'jquery_dollar', @@ -5428,7 +3902,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 fragment will be removed from the output. + * If set to TRUE the query & fragemnt will be removed from the output. * * @return string * URI is returned. @@ -5436,24 +3910,7 @@ function advagg_aggressive_cache_conflicts() { * @see http://php.net/parse-url#85963 */ function advagg_glue_url(array $parsed, $strip_query_and_fragment = FALSE) { - $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['scheme']) ? $parsed['scheme'] . ':' . ((strtolower($parsed['scheme']) === 'mailto') ? '' : '//') : ''; $uri .= isset($parsed['user']) ? $parsed['user'] . (isset($parsed['pass']) ? ':' . $parsed['pass'] : '') . '@' : ''; $uri .= isset($parsed['host']) ? $parsed['host'] : ''; $uri .= !empty($parsed['port']) ? ':' . $parsed['port'] : ''; @@ -5468,1510 +3925,3 @@ 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); -} - -/** - * Get the resource hint settings for the preload attribute. - * - * @param bool $return_defaults - * Default FALSE, TRUE returns the default values. - * - * @return array - * Ordered 2 dimensional array. - */ -function advagg_get_resource_hints_preload_settings($return_defaults = FALSE) { - $sub_defaults = array( - 'enabled' => 1, - 'push' => 0, - 'local' => 1, - 'external' => 1, - ); - // Collect your data. - $advagg_resource_hints_preload_settings_defaults = array( - 'style' => $sub_defaults + array( - '#weight' => -10, - 'title' => t('CSS Files'), - ), - 'font' => $sub_defaults + array( - '#weight' => -9, - 'title' => t('Font Files'), - ), - 'script' => $sub_defaults + array( - '#weight' => -8, - 'title' => t('JS Files'), - ), - 'svg' => $sub_defaults + array( - '#weight' => -7, - 'title' => t('SVG Files'), - ), - 'image' => $sub_defaults + array( - '#weight' => -6, - 'title' => t('Image Files'), - ), - 'all_others' => $sub_defaults + array( - '#weight' => -5, - 'title' => t('All Other Files'), - ), - ); - if ($return_defaults) { - return $advagg_resource_hints_preload_settings_defaults; - } - $advagg_resource_hints_preload_settings = variable_get('advagg_resource_hints_preload_settings', $advagg_resource_hints_preload_settings_defaults); - // Merge in defaults. - foreach ($advagg_resource_hints_preload_settings as $id => &$entry) { - if (isset($advagg_resource_hints_preload_settings_defaults[$id])) { - $entry += $advagg_resource_hints_preload_settings_defaults[$id]; - } - ksort($entry); - } - unset($entry); - // Sort the rows. - uasort($advagg_resource_hints_preload_settings, 'element_sort'); - return $advagg_resource_hints_preload_settings; -} - -/** - * See if the .htaccess file uses the RewriteBase directive. - * - * @param string $location - * The location of the .htaccess file. - * - * @return string - * The last active RewriteBase entry in htaccess. - */ -function advagg_htaccess_rewritebase($location = DRUPAL_ROOT) { - if (is_readable($location . '/.htaccess')) { - $htaccess = advagg_file_get_contents($location . '/.htaccess'); - $matches = array(); - $found = preg_match_all('/\\n\s*RewriteBase\s.*/i', $htaccess, $matches); - if ($found && !empty($matches[0])) { - $matches[0] = array_map('trim', $matches[0]); - return array_pop($matches[0]); - } - } - return ''; -} - -/** - * Get the latest version number for the remote version. - * - * @param array $library - * An associative array containing all information about the library. - * @param array $options - * An associative array containing options for the version parser. - * @param string $url - * URL for the remote version to lookup. - * - * @return string - * The latest version number as a string or 0 on failure. - */ -function advagg_get_github_version_json(array $library, array $options, $url) { - $http_options = array('timeout' => 2); - $package = drupal_http_request($url, $http_options); - if (empty($package->data)) { - // Try again. - $package = drupal_http_request($url, array('timeout' => 5)); - } - if (empty($package->data)) { - // Try again but force http. - $url = advagg_force_http_path($url); - $package = drupal_http_request($url, $http_options); - } - if (!empty($package->data)) { - $package = json_decode($package->data); - if (isset($package->version)) { - return (string) $package->version; - } - } - return 0; -} - -/** - * Get the latest version number for the remote version. - * - * @param array $library - * An associative array containing all information about the library. - * @param array $options - * An associative array containing options for the version parser. - * @param string $url - * URL for the remote version to lookup. - * - * @return string - * The latest version number as a string or 0 on failure. - */ -function advagg_get_github_version_txt(array $library, array $options, $url) { - $http_options = array('timeout' => 2); - $request = drupal_http_request($url, $http_options); - if (empty($request->data)) { - // Try again. - $request = drupal_http_request($url, array('timeout' => 5)); - } - if (empty($request->data)) { - // Try again but force http. - $url = advagg_force_http_path($url); - $request = drupal_http_request($url, $http_options); - } - if (!empty($request->data)) { - $matches = array(); - if (preg_match($options['pattern'], $request->data, $matches)) { - return $matches[1]; - } - } - return '0'; -} - -/** - * Update github version numbers to the latest. - * - * @param bool $refresh - * Set to TRUE to skip the cache and force a refresh of the data. - * - * @return mixed - * Key Value pair of the project name and remote version number. If $target is - * set then that version number is returned. - */ -function advagg_get_remote_libraries_versions($refresh = FALSE) { - $cid = __FUNCTION__; - $versions = array(); - if (!$refresh) { - $versions = advagg_get_remote_libraries_versions_cache($cid); - if (!empty($versions)) { - return $versions; - } - } - - if (is_callable('libraries_info')) { - $libraries = libraries_info(); - foreach ($libraries as $key => $library) { - // Get current version. - $libraries_detect = libraries_detect($key); - if (isset($libraries_detect['version'])) { - $versions[$key]['local'] = $libraries_detect['version']; - } - elseif (!empty($libraries_detect['local version'])) { - $versions[$key]['local'] = $libraries_detect['local version']; - } - - // Check if callback is live. - $remote = advagg_get_remote_libraries_version($key, $library, FALSE); - if (!empty($remote)) { - $versions[$key]['remote'] = $remote; - } - } - } - - if (!empty($versions)) { - cache_set($cid, $versions, 'cache_advagg_info'); - } - return $versions; -} - -/** - * Get the remote and local versions cache of the available libraries. - * - * @param string $cid - * Cache ID. - * - * @return array - * Library name => (local, remote). - */ -function advagg_get_remote_libraries_versions_cache($cid = '') { - if (empty($cid)) { - $cid = 'advagg_get_remote_libraries_versions'; - } - $versions = &drupal_static($cid, array()); - if (empty($versions)) { - $cache = cache_get($cid, 'cache_advagg_info'); - if (!empty($cache) && !empty($cache->data)) { - $versions = $cache->data; - } - } - return $versions; -} - -/** - * Get the latest version number for the remote version. - * - * @param string $name - * Name of the library. - * @param array $library - * An associative array containing all information about the library. - * @param bool $use_cache - * TRUE try the cache first. - * - * @return string - * The latest version number as a string or 0 on failure. - */ -function advagg_get_remote_libraries_version($name, array $library, $use_cache = TRUE) { - if ($use_cache) { - $cid = 'advagg_get_remote_libraries_versions'; - $versions = advagg_get_remote_libraries_versions_cache($cid); - if (isset($versions[$name]['remote'])) { - return $versions[$name]['remote']; - } - } - - // Remote url is not set, see if we can generate it given the current data. - if (empty($library['remote']['url']) - && !empty($library['version arguments']) - ) { - if (!isset($library['version arguments']['file']) - && isset($library['version arguments']['variants']) - ) { - // Use the first variant. - $file = reset($library['version arguments']['variants']); - $library['version arguments']['file'] = $file['file']; - $library['version arguments']['pattern'] = $file['pattern']; - } - - if (!empty($library['version arguments']['file'])) { - if (!empty($library['vendor url'])) { - // Use vendor url if it's a github one. - if (strpos($library['vendor url'], 'https://github.com/') === 0) { - $parsed_vendor = @parse_url($library['vendor url']); - // Previously: https://rawgit.com{$parsed_vendor['path']}/master/{$library['version arguments']['file']} . - $library['remote']['url'] = "https://cdn.jsdelivr.net/gh{$parsed_vendor['path']}@master/{$library['version arguments']['file']}"; - } - } - if (empty($library['remote']['url']) && !empty($library['download url'])) { - // Use download url if it's a github one. - if (strpos($library['download url'], 'https://github.com/') === 0) { - $parsed_download = @parse_url($library['download url']); - $paths = explode('/', $parsed_download['path']); - $parsed_download['path'] = "/{$paths[1]}/{$paths[2]}"; - // Previously: https://rawgit.com{$parsed_download['path']}/master/{$library['version arguments']['file']} . - $library['remote']['url'] = "https://cdn.jsdelivr.net/gh{$parsed_download['path']}@master/{$library['version arguments']['file']}"; - } - } - } - } - - // Remote callback is not set, try to generate it given the current data. - if (empty($library['remote']['callback']) - && isset($library['version arguments']['file']) - ) { - if (!empty($library['version callback'])) { - // Use defined parser. - $library['remote']['callback'] = $library['version callback']; - } - else { - if ($library['version arguments']['file'] === 'package.json') { - // JSON parser. - $library['remote']['callback'] = 'advagg_get_github_version_json'; - } - else { - // Text parser. - $library['remote']['callback'] = 'advagg_get_github_version_txt'; - } - } - } - - // Get remote version. - $return = 0; - if (!empty($library['remote']) - && !empty($library['remote']['callback']) - && !empty($library['remote']['url']) - && isset($library['version arguments']) - && is_callable($library['remote']['callback']) - ) { - $return = $library['remote']['callback']($library, $library['version arguments'], $library['remote']['url']); - - // Try package.json on failure. - if (empty($return) && $library['version arguments']['file'] !== 'package.json') { - $pos = strrpos($library['remote']['url'], $library['version arguments']['file']); - $library['remote']['url'] = substr($library['remote']['url'], 0, $pos) . 'package.json'; - $library['remote']['callback'] = 'advagg_get_github_version_json'; - $return = $library['remote']['callback']($library, $library['version arguments'], $library['remote']['url']); - } - } - return $return; -} - -/** - * Get the latest version number for the remote version. - * - * @param string $name - * Name of the library. - * @param string $module_name - * Name of the module where the library is registered. - * - * @return array - * The library array. - */ -function advagg_get_library($name, $module_name) { - $library = cache_get($name, 'cache_libraries'); - if ($library) { - $library = $library->data; - } - else { - if (is_callable('libraries_detect')) { - $library = libraries_detect($name); - } - elseif (is_callable("{$module_name}_libraries_info")) { - $library = call_user_func("{$module_name}_libraries_info"); - $library = $library[$name]; - } - } - return $library; -} - -/** - * Alter the js array fixing the type key if set incorrectly. - * - * @param array $array - * CSS or JS array. - * @param string $type - * CSS or JS. - */ -function advagg_fix_type(array &$array, $type) { - // Skip if advagg is disabled. - if (!advagg_enabled()) { - return; - } - - // Skip if setting is turned off. - if ($type === 'css' && !variable_get('advagg_css_fix_type', ADVAGG_CSS_FIX_TYPE)) { - return; - } - if ($type === 'js' && !variable_get('advagg_js_fix_type', ADVAGG_JS_FIX_TYPE)) { - return; - } - - // Fix type if it was incorrectly set. - // Get hostname and base path. - $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); - - foreach ($array as &$value) { - // Skip if the data is empty or not a string. - if (empty($value['data']) || !is_string($value['data'])) { - continue; - } - - // Default to file if type is not set. - if (!isset($value['type'])) { - $value['type'] = 'file'; - } - - // If inline, be done with processing. - if ($value['type'] === 'inline') { - continue; - } - - // Default to file if not file, inline, external, setting. - if ($value['type'] !== 'file' - && $value['type'] !== 'inline' - && $value['type'] !== 'external' - && $value['type'] !== 'setting' - ) { - if ($value['type'] === 'settings') { - $value['type'] = 'setting'; - } - else { - $value['type'] = 'file'; - } - } - - $lower = strtolower($value['data']); - $http_pos = strpos($lower, 'http://'); - $https_pos = strpos($lower, 'https://'); - $double_slash_pos = strpos($lower, '//'); - $tripple_slash_pos = strpos($lower, '///'); - $mod_base_url_pos = stripos($value['data'], $mod_base_url); - - // If type is external but doesn't start with http, https, or // change it - // to file. - if ($value['type'] === 'external' - && $http_pos !== 0 - && $https_pos !== 0 - && $double_slash_pos !== 0 - ) { - if (is_readable($value['data'])) { - $value['type'] = 'file'; - } - else { - // Fix for subdir issues. - $parsed = parse_url($value['data']); - if (strpos($parsed['path'], $GLOBALS['base_path']) === 0) { - $path = substr($parsed['path'], strlen($GLOBALS['base_path'])); - if (is_readable($path)) { - $value['data'] = $path; - $value['type'] = 'file'; - } - } - } - } - - // If type is file but it starts with http, https, or // change it to - // external. Skip tripple slash for local files. - if ($value['type'] === 'file' - && ($http_pos === 0 - || $https_pos === 0 - || ($double_slash_pos === 0 && $tripple_slash_pos === FALSE)) - ) { - $value['type'] = 'external'; - } - - // If type is external and starts with http, https, or // but points to - // this host change to file, but move it to the top of the aggregation - // stack. - if ($value['type'] === 'external' - && $mod_base_url_pos - 2 === $double_slash_pos - && ($http_pos === 0 - || $https_pos === 0 - || $double_slash_pos === 0 - ) - ) { - $path = substr($value['data'], $mod_base_url_pos + $mod_base_url_len); - if (is_readable($path)) { - $value['data'] = $path; - $value['type'] = 'file'; - $value['group'] = JS_LIBRARY; - $value['every_page'] = TRUE; - $value['weight'] = -40000; - } - else { - // Fix for subdir issues. - $parsed = parse_url($path); - if (strpos($parsed['path'], $GLOBALS['base_path']) === 0) { - $path = substr($parsed['path'], strlen($GLOBALS['base_path'])); - if (is_readable($path)) { - $value['data'] = $path; - $value['type'] = 'file'; - $value['group'] = JS_LIBRARY; - $value['every_page'] = TRUE; - $value['weight'] = -40000; - } - } - } - } - } - unset($value); -} - -/** - * Alter the CSS or JS array removing empty files from the aggregates. - * - * @param array $array - * CSS or JS array. - */ -function advagg_remove_empty_files(array &$array) { - if (!variable_get('advagg_js_remove_empty_files', ADVAGG_JS_REMOVE_EMPTY_FILES)) { - return; - } - - if (variable_get('advagg_fast_filesystem', ADVAGG_FAST_FILESYSTEM)) { - foreach ($array as $key => $value) { - if ($value['type'] !== 'file') { - continue; - } - // Check locally. - if (!is_readable($value['data']) || filesize($value['data']) == 0) { - unset($array[$key]); - } - } - } - else { - module_load_include('inc', 'advagg', 'advagg'); - $files = array(); - foreach ($array as $key => $value) { - if ($value['type'] !== 'file') { - continue; - } - $files[$key] = $value['data']; - } - // Check cache/db. - $info = advagg_get_info_on_files($files); - foreach ($info as $key => $values) { - if (empty($values['filesize'])) { - $key = array_search($values['data'], $files); - if (isset($array[$key])) { - unset($array[$key]); - } - } - } - } -} - -/** - * Alter the CSS or JS array adding in DNS domain info. - * - * @param array $array - * CSS or JS array. - * @param string $type - * CSS or JS. - */ -function advagg_add_default_dns_lookups(array &$array, $type) { - // Skip if advagg is disabled. - if (!advagg_enabled()) { - return; - } - // Remove this return once CSS lookups are needed. - if ($type !== 'js') { - return; - } - - // Add DNS information for some of the more popular modules. - foreach ($array as &$value) { - if (!is_string($value['data'])) { - continue; - } - // Google Ad Manager. - if (strpos($value['data'], '/google_service.') !== FALSE) { - if (!empty($value['dns_prefetch']) && is_string($value['dns_prefetch'])) { - $temp = $value['dns_prefetch']; - unset($value['dns_prefetch']); - $value['dns_prefetch'] = array($temp); - } - // Domains in the google_service.js file. - $value['dns_prefetch'][] = 'https://csi.gstatic.com'; - $value['dns_prefetch'][] = 'https://pubads.g.doubleclick.net'; - $value['dns_prefetch'][] = 'https://partner.googleadservices.com'; - $value['dns_prefetch'][] = 'https://securepubads.g.doubleclick.net'; - - // Domains in the google_ads.js file. - $value['dns_prefetch'][] = 'https://pagead2.googlesyndication.com'; - - // Other domains that usually get hit. - $value['dns_prefetch'][] = 'https://cm.g.doubleclick.net'; - $value['dns_prefetch'][] = 'https://tpc.googlesyndication.com'; - } - - // Google Analytics. - if (strpos($value['data'], 'GoogleAnalyticsObject') !== FALSE - || strpos($value['data'], '.google-analytics.com/ga.js') !== FALSE - ) { - if (!empty($value['dns_prefetch']) && is_string($value['dns_prefetch'])) { - $temp = $value['dns_prefetch']; - unset($value['dns_prefetch']); - $value['dns_prefetch'] = array($temp); - } - if ($GLOBALS['is_https'] && strpos($value['data'], '.google-analytics.com/ga.js') !== FALSE) { - $value['dns_prefetch'][] = 'https://ssl.google-analytics.com'; - } - else { - $value['dns_prefetch'][] = 'https://www.google-analytics.com'; - } - $value['dns_prefetch'][] = 'https://stats.g.doubleclick.net'; - } - } -} - -/** - * Insert element into an array at a specific position. - * - * @param array $input_array - * The original array. - * @param array $new_value - * The element that is getting inserted. - * @param int $location_key - * The key location. - * - * @return array - * The new array with the element merged in. - */ -function advagg_insert_into_array_at_location(array $input_array, array $new_value, $location_key) { - return array_merge(array_slice($input_array, 0, $location_key), $new_value, array_slice($input_array, $location_key)); -} - -/** - * Insert element into an array at a specific key location. - * - * @param array $input_array - * The original array. - * @param array $insert - * The element that is getting inserted; array(key => value). - * @param string $target_key - * The key name. - * @param int $location - * After is 1 , 0 is replace, -1 is before. - * - * @return array - * The new array with the element merged in. - */ -function advagg_insert_into_array_at_key(array $input_array, array $insert, $target_key, $location = 1) { - $output = array(); - $new_value = reset($insert); - $new_key = key($insert); - foreach ($input_array as $key => $value) { - if ($key === $target_key) { - // Insert before. - if ($location == -1) { - $output[$new_key] = $new_value; - $output[$key] = $value; - } - // Replace. - if ($location == 0) { - $output[$new_key] = $new_value; - } - // After. - if ($location == 1) { - $output[$key] = $value; - $output[$new_key] = $new_value; - } - } - else { - // Pick next key if there is an number collision. - if (is_numeric($key)) { - while (isset($output[$key])) { - $key++; - } - } - $output[$key] = $value; - } - } - // Add to array if not found. - if (!isset($output[$new_key])) { - // Before everything. - if ($location == -1) { - $output = $insert + $output; - } - // After everything. - if ($location == 1) { - $output[$new_key] = $new_value; - } - - } - return $output; -} - -/** - * Given a URL output a filename. - * - * @param string $url - * The url. - * @param string $strict - * If FALSE then slashes will be kept. - * - * @return string - * The filename. - */ -function advagg_url_to_filename($url, $strict = TRUE) { - // Keep filtering till there are no more changes. - $decoded1 = _advagg_url_to_filename_filter($url, $strict); - $decoded2 = _advagg_url_to_filename_filter($decoded1, $strict); - while ($decoded1 != $decoded2) { - $decoded1 = _advagg_url_to_filename_filter($decoded2, $strict); - $decoded2 = _advagg_url_to_filename_filter($decoded1, $strict); - } - $filename = $decoded1; - - // Shorten filename to a max of 250 characters. - $filename = mb_strcut($filename, 0, 250, mb_detect_encoding($filename)); - return $filename; -} - -/** - * Given a URL output a filtered filename. - * - * @param string $url - * The url. - * @param string $strict - * If FALSE then slashes will be kept. - * - * @return string - * The filename. - */ -function _advagg_url_to_filename_filter($url, $strict = TRUE) { - // URL Decode if needed. - $decoded1 = $url; - $decoded2 = rawurldecode($decoded1); - while ($decoded1 != $decoded2) { - $decoded1 = rawurldecode($decoded2); - $decoded2 = rawurldecode($decoded1); - } - $url = $decoded1; - - // Replace url spaces with a dash. - $filename = str_replace(array('%20', '+'), '-', $url); - - // Remove file system reserved characters - // https://en.wikipedia.org/wiki/Filename#Reserved_characters_and_words - // Remove control charters - // http://msdn.microsoft.com/en-us/library/windows/desktop/aa365247%28v=vs.85%29.aspx - // Remove non-printing characters DEL, NO-BREAK SPACE, SOFT HYPHEN - // Remove URI reserved characters - // https://tools.ietf.org/html/rfc3986#section-2.2 - // Remove URL unsafe characters - // https://www.ietf.org/rfc/rfc1738.txt - if ($strict) { - $filename = preg_replace('~[<>:"/\\|?*]|[\x00-\x1F]|[\x7F\xA0\xAD]|[#\[\]@!$&\'()+,;=%]|[{}^\~`]~x', '-', $filename); - } - else { - $filename = preg_replace('~[<>:"\\|?*]|[\x00-\x1F]|[\x7F\xA0\xAD]|[#\[\]@!$&\'()+,;=%]|[{}^\~`]~x', '-', $filename); - } - - // Replce all white spaces with a dash. - $filename = preg_replace('/[\r\n\t -]+/', '-', $filename); - - // Avoid ".", ".." or ".hiddenFiles". - $filename = ltrim($filename, '.-'); - - // Compress spaces in a file name and replace with a dash. - // Compress underscores in a file name and replace with a dash. - // Compress dashes in a file name and replace with a dash. - $filename = preg_replace(array('/ +/', '/_+/', '/-+/'), '-', $filename); - - // Compress dashes and dots in a file name and replace with a dot. - $filename = preg_replace(array('/-*\.-*/', '/\.{2,}/'), '.', $filename); - - // Lowercase for windows/unix interoperability - // http://support.microsoft.com/kb/100625. - $filename = mb_strtolower($filename, 'UTF-8'); - // Remove ? \ .. - $filename = str_replace(array('?', '\\', '..'), '', $filename); - - // ".file-name.-" becomes "file-name". - $filename = trim($filename, '.-'); - - return $filename; -} - -/** - * Given a URI return TRUE if it is external. - * - * @param string $uri - * The uri. - * - * @return bool - * TRUE if external. - */ -function advagg_is_external($uri) { - $http_pos = strpos($uri, 'http://'); - $https_pos = strpos($uri, 'https://'); - $double_slash_pos = strpos($uri, '//'); - if ($http_pos !== 0 - && $https_pos !== 0 - && $double_slash_pos !== 0 - ) { - return FALSE; - } - return TRUE; -} - -/** - * Same as file_get_contents() but will convert string to UTF-8 if needed. - * - * @return mixed - * The files contents or FALSE on failure. - */ -function advagg_file_get_contents() { - // Get the file contents. - $file_contents = call_user_func_array('file_get_contents', func_get_args()); - if ($file_contents === FALSE) { - return $file_contents; - } - - // If a BOM is found, convert the file to UTF-8. - $encoding = advagg_get_encoding_from_bom($file_contents); - if (!empty($encoding)) { - $file_contents = advagg_convert_to_utf8($file_contents, $encoding); - } - - return $file_contents; -} - -/** - * Get the description text based off the library version. - * - * @param string $library_name - * Name of the library. - * @param string $module_name - * Name of the module that contains hook_libraries_info for this library. - * - * @return array - * Description text and info array. - */ -function advagg_get_version_description($library_name, $module_name, $only_remote_ok = FALSE) { - $t = get_t(); - - // Get local and external library version numbers. - $versions = &drupal_static(__FUNCTION__); - if (!isset($versions)) { - $versions = advagg_get_remote_libraries_versions(TRUE); - } - - $description = ''; - if (!empty($versions[$library_name]['remote']) - && (empty($versions[$library_name]['local']) - || $versions[$library_name]['local'] !== $versions[$library_name]['remote'] - )) { - $library = advagg_get_library($library_name, $module_name); - if (empty($versions[$library_name]['local'])) { - $versions[$library_name]['local'] = 'NULL'; - } - if (!empty($library['installed'])) { - $description = $t('Go to the <a href="@url-page">@name</a> page and <a href="@url-zip">download</a> the latest version (@remote) into the @lib_path folder. An example valid filename is %version_file. Current Version: %version.', array( - '@name' => $library['name'], - '@lib_path' => $library['library path'], - '@url-page' => $library['vendor url'], - '@url-zip' => $library['download url'], - '@remote' => $versions[$library_name]['remote'], - '%version' => $versions[$library_name]['local'], - '%version_file' => $library['library path'] . '/' . $library['version arguments']['file'], - )); - } - elseif (!$only_remote_ok && is_callable('libraries_load')) { - $description = $t('Go to the <a href="@url-page">@name</a> page and <a href="@url-zip">download</a> the latest version (@remote) into the libraries folder (usually sites/all/libraries). An example valid filename is %version_file.', array( - '@name' => $library['name'], - '@url-page' => $library['vendor url'], - '@url-zip' => $library['download url'], - '@remote' => $versions[$library_name]['remote'], - '%version_file' => "sites/all/libraries/$library_name/{$library['version arguments']['file']}", - )); - } - elseif (!$only_remote_ok) { - $description = $t('Install the <a href="@url-lib-api">Libraries API</a> module and then go to the <a href="@url-page">@name</a> page and <a href="@url-zip">download</a> the latest version (@remote) into the libraries folder (usually sites/all/libraries). An example valid filename is %version_file.', array( - '@name' => $library['name'], - '@url-page' => $library['vendor url'], - '@url-zip' => $library['download url'], - '@remote' => $versions[$library_name]['remote'], - '%version_file' => "sites/all/libraries/$library_name/{$library['version arguments']['file']}", - '@url-lib-api' => 'https://www.drupal.org/project/libraries', - )); - } - } - $path = drupal_get_path('module', $module_name); - $info = drupal_parse_info_file("{$path}/{$module_name}.info"); - - // Check if library was unzipped with -master. - if (!empty($description) && is_callable('libraries_get_libraries')) { - $libraries_paths = libraries_get_libraries(); - if (!empty($libraries_paths["{$library_name}-master"])) { - $description = $t('Rename @lib_dir_master to @lib_dir at this location: @lib_path_master.', array( - '@lib_dir_master' => "{$library_name}-master", - '@lib_path_master' => $libraries_paths["{$library_name}-master"], - '@lib_dir' => $library_name, - )); - } - } - - return array($description, $info); -} - -/** - * 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_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; - } -} - -/** - * Display a message if there are requirement issues with AdvAgg. - * - * @param array $requirements - * Other requirements to list besides the standard ones. - */ -function advagg_display_message_if_requirements_not_met(array $requirements = array()) { - include_once DRUPAL_ROOT . '/includes/install.inc'; - module_load_include('install', 'advagg'); - $requirements += advagg_install_fast_checks(); - if (!empty($requirements)) { - module_load_include('admin.inc', 'system'); - usort($requirements, '_system_sort_requirements'); - $report = theme('status_report', array('requirements' => $requirements)); - drupal_set_message(t('Go to the <a href="@url">status report page</a> and fix the issues that AdvAgg lists there. Sneak peak: !report', array( - '@url' => url('admin/reports/status'), - '!report' => $report, - ))); - } -} - -/** - * Add in the preload header for CSS and JS external files. - * - * @param string $url - * The url of the external host. - * - * @return bool - * TRUE if it was added to the head. - */ -function advagg_add_preload_header($url = '', $as = '') { - // Get defaults and setup static's. - $list = &drupal_static(__FUNCTION__ . ':list', array()); - $output = &drupal_static(__FUNCTION__ . ':output'); - $header_strlen = &drupal_static(__FUNCTION__ . ':strlen', 0); - static $resource_hints_preload_order; - if (!isset($resource_hints_preload_order)) { - $resource_hints_preload_order = advagg_get_resource_hints_preload_settings(); - } - if (!isset($output)) { - $keys = array_keys($resource_hints_preload_order); - $output = array_fill_keys($keys, array()); - } - - // Output headers. - if (empty($url)) { - - // Call hook_advagg_preload_header_alter() - drupal_alter('advagg_preload_header', $output); - - // Build header string. - $header_value = ''; - foreach ($output as $value) { - if (!empty($value)) { - // Remove empty values. - $value = array_filter($value); - foreach ($value as $string) { - $header_strlen += strlen($string) + 2; - // Don't add if over the limit. - if ($header_strlen >= variable_get('advagg_resource_hints_preload_max_size', ADVAGG_RESOURCE_HINTS_PRELOAD_MAX_SIZE)) { - continue; - } - // Add to $header_value string. - if (empty($header_value)) { - $header_value = $string; - } - else { - $header_value .= ',' . $string; - } - } - } - } - if (!empty($header_value)) { - drupal_add_http_header('Link', $header_value, TRUE); - } - return FALSE; - } - - // Check for duplicates. - if (isset($list[$url])) { - return FALSE; - } - - // Fill in missing info. - $payload = "<{$url}>; rel=preload"; - list($as, $type, $crossorigin, $parse) = advagg_get_preload_info_from_url($url, $as); - if (!empty($as) && $as === 'php') { - $list[$url] = FALSE; - return FALSE; - } - - $additional_info = array(); - if (!empty($crossorigin)) { - $additional_info[] = "crossorigin"; - } - if (!empty($type)) { - $additional_info[] = $type; - } - $additional_info = implode('; ', $additional_info); - - // Get settings. - if (!empty($as) && !empty($resource_hints_preload_order[$as])) { - $settings = $resource_hints_preload_order[$as]; - } - elseif (!empty($resource_hints_preload_order['all_others'])) { - $settings = $resource_hints_preload_order['all_others']; - } - - // Apply settings. - if (!$settings['enabled']) { - $list[$url] = FALSE; - return FALSE; - } - if (!$settings['local'] && empty($parse['host'])) { - $list[$url] = FALSE; - return FALSE; - } - if (!$settings['external'] && !empty($parse['host'])) { - $list[$url] = FALSE; - return FALSE; - } - - // Add additional info and queue. - if (!empty($as)) { - $payload .= "; as={$as}"; - } - if (!empty($additional_info)) { - $payload .= "; {$additional_info}"; - } - if (!$settings['push']) { - $payload .= "; nopush"; - } - $list[$url] = TRUE; - $output[$as][] = $payload; -} - -/** - * Given a link get the as, type, and crossorigin attributes. - * - * @param string $url - * Link to the url that will be preloaded. - * @param string $as - * What type of content is this; font, image, video, etc. - * @param string $type - * The mime type of the file. - * @param string $crossorigin - * Preload cross-origin resources; fonts always need to be CORS. - * - * @return array - * An array containing - */ -function advagg_get_preload_info_from_url($url, $as = '', $type = '', $crossorigin = NULL) { - // Get extension. - $parse = @parse_url($url); - if (empty($parse['path'])) { - return FALSE; - } - $file_ext = strtolower(pathinfo($parse['path'], PATHINFO_EXTENSION)); - if (empty($file_ext)) { - $file_ext = basename($parse['path']); - } - - // Detect missing parts. - $list = advagg_preload_list(); - if (empty($as) && !empty($file_ext)) { - foreach ($list as $as_key => $list_type) { - $key = array_search($file_ext, $list_type); - if ($key !== FALSE) { - $as = $as_key; - // Type of font, ext is svg but file doesn't contain string font. - // This will be treated as an image. - if ($as === 'font' - && $file_ext === 'svg' - && stripos($url, 'font') === FALSE - ) { - $as = ''; - } - } - if (!empty($as)) { - break; - } - } - } - if (empty($type) && !empty($as)) { - $type = "$as/$file_ext"; - if ($file_ext === 'svg') { - $type .= '+xml'; - } - if ($file_ext === 'js') { - $type = 'text/javascript'; - } - } - if ($as === 'font' && is_null($crossorigin)) { - $crossorigin = 'anonymous'; - } - return array($as, $type, $crossorigin, $parse); -} - -/** - * Add preload link to the top of the html head. - * - * @param string $url - * Link to the url that will be preloaded. - * @param string $media - * Media types or media queries, allowing for responsive preloading. - * @param string $as - * What type of content is this; font, image, video, etc. - * @param string $type - * The mime type of the file. - * @param string $crossorigin - * Preload cross-origin resources; fonts always need to be CORS. - */ -function advagg_add_preload_link($url, $media = '', $as = '', $type = '', $crossorigin = NULL) { - static $weight = -2000; - $weight += 0.0001; - - $href = advagg_file_create_url($url); - $key = "advagg_preload:{$href}"; - - // Return here if url has already been added. - $stored_head = drupal_static('drupal_add_html_head'); - if (isset($stored_head[$key])) { - return TRUE; - } - - // Add basic attributes. - $attributes = array( - 'rel' => 'preload', - 'href' => $href, - ); - - // Fill in missing info. - list($as, $type, $crossorigin) = advagg_get_preload_info_from_url($url, $as, $type, $crossorigin); - - // Exit if no as. - if (empty($as)) { - return FALSE; - } - // Build up attributes array. - $attributes['as'] = $as; - if (!empty($type)) { - $attributes['type'] = $type; - } - if (!empty($crossorigin)) { - $attributes['crossorigin'] = $crossorigin; - } - if (!empty($media)) { - $attributes['media'] = $media; - } - // Call hook_advagg_preload_link_attributes_alter() - drupal_alter('advagg_preload_link_attributes', $attributes); - - // Add to HTML head. - $element = array( - '#type' => 'html_tag', - '#tag' => 'link', - '#attributes' => $attributes, - '#weight' => $weight, - ); - drupal_add_html_head($element, $key); - return TRUE; -} - -/** - * Generate a list of file types for the as field given the extension. - * - * @return array - * Returns an array of arrays. - */ -function advagg_preload_list() { - $list = array( - 'font' => array( - 'woff2', - 'woff', - 'ttf', - 'otf', - 'eot', - // Need to check if the svg file is in a font folder. - 'svg', - ), - 'image' => array( - 'gif', - 'jpg', - 'jpeg', - 'jpe', - 'jif', - 'jfif', - 'jfi', - 'png', - 'webp', - 'jp2', - 'jpx', - 'jxr', - 'heif', - 'heic', - 'bpg', - 'svg', - ), - 'style' => array( - 'css', - ), - 'script' => array( - 'js', - ), - 'video' => array( - 'mp4', - 'webm', - 'ogg', - ), - ); - - // Call hook_advagg_preload_list_alter() - drupal_alter('advagg_preload_list', $list); - return $list; -} - -/** - * Save form defaults or recommended values. - * - * @param array $element - * Form element or child element. - * - * @return array - * An array of form names and the recommended value for that setting. - */ -function advagg_find_all_recommended_admin_values(array &$element, $key_name = '#recommended_value') { - $results = array(); - $children = element_children($element); - foreach ($children as $key) { - $child = $element[$key]; - if (is_array($child)) { - if (!empty($child['#type']) - && !empty($child['#name']) - && isset($child[$key_name]) - ) { - $results[$child['#name']] = $child[$key_name]; - } - $results = array_merge($results, advagg_find_all_recommended_admin_values($child, $key_name)); - } - unset($child); - } - return $results; -} - -/** - * Get form values that have changed. - * - * @param array $element - * Form element or child element. - * - * @return array - * An array of form names and the recommended value for that setting. - */ -function advagg_find_all_changed_admin_values(array &$element) { - $results = array(); - $children = element_children($element); - foreach ($children as $key) { - $child = $element[$key]; - if (is_array($child)) { - if (!empty($child['#type']) - && !empty($child['#name']) - && isset($child['#default_value']) - && isset($child['#value']) - ) { - if ($child['#type'] === 'checkboxes') { - // Add in not selected by default values. - $child['#value'] += array_diff_assoc($child['#default_value'], $child['#value']); - } - if ($child['#default_value'] != $child['#value']) { - $results[$child['#name']] = array($child['#value'], $child['#default_value']); - } - } - $results = array_merge($results, advagg_find_all_changed_admin_values($child)); - } - unset($child); - } - return $results; -} - -/** - * Get form title and description. - * - * @param array $element - * Form element or child element. - * - * @return array - * An array of form names and the recommended value for that setting. - */ -function advagg_find_title(array &$element) { - $results = array(); - $children = element_children($element); - foreach ($children as $key) { - $child = $element[$key]; - if (is_array($child)) { - if (!empty($child['#type']) - && !empty($child['#name']) - && isset($child['#title']) - && isset($child['#default_value']) - && !isset($results[$child['#name']]) - && $child['#type'] !== 'radio' - ) { - $results[$child['#name']] = $child['#title']; - } - $results = array_merge($results, advagg_find_title($child)); - } - unset($child); - } - return $results; -} - -/** - * Save form defaults or recommended values. - * - * @param array $form_state - * Form state array from drupal form submit. - * @param string $trigger_key - * The key of the setting from the form that controls this. - */ -function advagg_set_admin_form_defaults_recommended(array &$form_state, $trigger_key) { - $changed = array(); - $recommended_values = array(); - - // Set to recommended values. - if ($form_state['values'][$trigger_key] == 2) { - $recommended_values = advagg_find_all_recommended_admin_values($form_state['complete form']); - foreach ($recommended_values as $key => $value) { - if (!isset($form_state['values'][$key])) { - $changed[$key] = array($value); - } - elseif ($value != $form_state['values'][$key]) { - $changed[$key] = array($value, $form_state['values'][$key]); - } - $form_state['values'][$key] = $value; - } - } - - // Set to defaults. - if ($form_state['values'][$trigger_key] == 0 || $form_state['values'][$trigger_key] == 2) { - // Reset to defaults. - foreach ($form_state['values'] as $key => &$value) { - // Skip non advagg settings, trigger key, or if a recommended value. - if (strpos($key, 'advagg_') !== 0 - || $key === $trigger_key - || isset($changed[$key]) - || isset($recommended_values[$key]) - ) { - continue; - } - - // Default to FALSE. - $default = FALSE; - // Get easy defaults. - if (defined(strtoupper($key))) { - $default = constant(strtoupper($key)); - } - - // Get more complex default values. - if ($key === 'advagg_resource_hints_preload_settings') { - $default = advagg_get_resource_hints_preload_settings(TRUE); - foreach ($default as $key => &$values) { - $default[$key]['weight'] = $values['#weight']; - unset($default[$key]['#weight'], $values['#weight'], $default[$key]['title'], $values['title']); - ksort($values); - } - ksort($default); - foreach ($value as $key => &$values) { - ksort($values); - } - ksort($value); - } - if ($key === 'advagg_relocate_css_inline_import_browsers') { - $default = array( - 'woff2' => 'woff2', - 'woff' => 'woff', - 'ttf' => 'ttf', - 'eot' => 0, - 'svg' => 0, - ); - } - - // See if it changed. - if ($default != $value) { - // After, Before. - $changed[$key] = array($default, $value); - $value = $default; - } - } - } - - if ($form_state['values'][$trigger_key] == 4) { - $changed = advagg_find_all_changed_admin_values($form_state['complete form']); - if (isset($changed[$trigger_key])) { - unset($changed[$trigger_key]); - } - } - - $all_titles_descriptions = advagg_find_title($form_state['complete form']); - foreach ($changed as $key => $values) { - - // Remove things that didn't really change. - if (isset($values[1])) { - if ($values[0] == $values[1]) { - unset($changed[$key]); - continue; - } - if (is_string($values[0]) - && is_string($values[1]) - && trim($values[0]) == trim($values[1]) - ) { - unset($changed[$key]); - continue; - } - } - - // Make output nicer. - if (!isset($values[1])) { - $values[1] = NULL; - } - if (is_bool($values[0]) && !is_bool($values[1]) - || !is_bool($values[0]) && is_bool($values[1]) - ) { - $values[0] = (bool) $values[0]; - $values[1] = (bool) $values[1]; - } - if (is_int($values[0]) && !is_int($values[1]) - || !is_int($values[0]) && is_int($values[1]) - ) { - $values[0] = (int) $values[0]; - $values[1] = (int) $values[1]; - } - - // Let user know what changed. - if (empty($all_titles_descriptions[$key])) { - drupal_set_message(t('%before -> %after for %title', array( - '%title' => $key, - '%before' => var_export($values[1], TRUE), - '%after' => var_export($values[0], TRUE), - ))); - } - else { - drupal_set_message(t('%before -> %after for %title', array( - '%title' => $all_titles_descriptions[$key], - '%before' => var_export($values[1], TRUE), - '%after' => var_export($values[0], TRUE), - ))); - } - } - - return $changed; -} - -/** - * Given a list of items see what ones need to be inserted/updated or deleted. - * - * @param array $defaults - * Array of default values, representing a row in the db. - * @param mixed $new_values - * Array of edited values, representing a row in the db. - * - * @return array - * Nested array strucutre; only the diff. - */ -function advagg_diff_multi(array $defaults, $new_values) { - $result = array(); - - foreach ($defaults as $key => $val) { - if (is_array($val) && isset($new_values[$key])) { - $tmp = advagg_diff_multi($val, $new_values[$key]); - if ($tmp) { - $result[$key] = $tmp; - } - } - elseif (!isset($new_values[$key])) { - $result[$key] = NULL; - } - elseif ($val != $new_values[$key]) { - $result[$key] = $new_values[$key]; - } - if (isset($new_values[$key])) { - unset($new_values[$key]); - } - } - - $result = $result + $new_values; - return $result; -} 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 d7bfd0316a..b46c04833d 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 @@ -8,108 +8,17 @@ /** * Form builder; Configure advagg settings. * - * @ingroup advagg_forms + * @ingroup forms * * @see system_settings_form() */ function advagg_bundler_admin_settings_form() { drupal_set_title(t('AdvAgg: Bundler')); - advagg_display_message_if_requirements_not_met(); $form = array(); - $options = array( - 0 => t('Use HTTP 1.1 settings'), - 2 => t('Use HTTP 2.0 settings'), - 4 => t('Use customized settings'), - ); - $form['advagg_bundler_admin_mode'] = array( - '#type' => 'radios', - '#title' => t('AdvAgg Settings'), - '#default_value' => variable_get('advagg_bundler_admin_mode', ADVAGG_BUNDLER_ADMIN_MODE), - '#options' => $options, - '#description' => t("Default settings will mirror core as closely as possible. <br>Recommended settings are optimized for speed."), - ); - - // Test http2. - $http2_support = 0; - $url = 'https://tools.keycdn.com/http2-query.php?url=' . url('', array('absolute' => TRUE)); - if (filter_var($_SERVER['HTTP_HOST'], FILTER_VALIDATE_IP) === FALSE && $_SERVER['HTTP_HOST'] !== 'localhost') { - $response = drupal_http_request($url, array( - 'timeout' => 3, - )); - } - else { - $response = new stdClass(); - $response->code = 0; - } - if ($response->code == 200 && !empty($response->data)) { - $http2_support = 1; - if (stripos($response->data, 'success') !== FALSE) { - $http2_support = 2; - } - } - if (stripos($_SERVER['SERVER_SOFTWARE'], 'apache') !== FALSE && is_callable('apache_get_modules')) { - $modules = apache_get_modules(); - $key_a = array_search('mod_http2', $modules); - $key_b = array_search('mod_h2', $modules); - if ($key_a !== FALSE || $key_b !== FALSE) { - $http2_support += 4; - } - } - // Display http2 info. - if ($http2_support & 2) { - $description = t('This server supports HTTP 2.0; the test from @url came back ok.', array('@url' => $url)); - } - else { - if ($http2_support == 0) { - $description = t('AdvAgg can not detect if this server supports HTTP 2.0. You can <a href="@url">go here to learn how to enable it</a>.', array('@url' => 'https://httpd.apache.org/docs/2.4/mod/mod_http2.html')); - } - elseif ($http2_support & 4) { - $description = t('It appears that this server could support HTTP 2.0 (apache <a href="@url">mod_http2</a> found)', array( - '@url' => 'https://httpd.apache.org/docs/2.4/mod/mod_http2.html', - )); - if ($http2_support & 1) { - $description .= t(', but it may not be configured to do so. The test from <a href="@test">here</a> was inconclusive.', array( - '@test' => $url, - )); - } - else { - if (count($response) == 1) { - $description .= t(', but the test from <a href="@test">here</a> was not actually done due to the URL being an IP address or localhost.', array( - '@test' => $url, - )); - } - else { - $description .= t(', but the test from <a href="@test">here</a> was inconclusive.', array( - '@test' => $url, - )); - } - } - } - else { - $description = t('This server does not support HTTP 2.0.'); - } - $description .= ' ' . t('<a href="@url">This guide</a> can help you get http2 enabled on your site.', array('@url' => 'https://geekflare.com/http2-implementation-apache-nginx/')); - } - if (!empty($description)) { - $form['advagg_http2'] = array( - '#markup' => "<p>{$description}</p>", - ); - } - - $form['global_container'] = array( - '#type' => 'container', - '#hidden' => TRUE, - '#states' => array( - 'visible' => array( - ':input[name="advagg_bundler_admin_mode"]' => array('value' => '4'), - ), - ), - ); - - $form['global_container']['advagg_bundler_active'] = array( + $form['advagg_bundler_active'] = array( '#type' => 'checkbox', - '#title' => t('Bundler is Active (recommended)'), + '#title' => t('Bundler is Active'), '#default_value' => variable_get('advagg_bundler_active', ADVAGG_BUNDLER_ACTIVE), '#description' => t('If not checked, the bundler will passively monitor your site, but it will not split up aggregates.'), ); @@ -131,62 +40,33 @@ function advagg_bundler_admin_settings_form() { 13 => 13, 14 => 14, 15 => 15, - 16 => 16, - 17 => 17, - 18 => 18, - 19 => 19, - 20 => 20, - 21 => 21, - 22 => 22, - 23 => 23, - 24 => 24, - 25 => 25, ); - $form['global_container']['advagg_bundler_max_css'] = array( + $form['advagg_bundler_max_css'] = array( '#type' => 'select', '#title' => t('Target Number Of CSS Bundles Per Page'), '#default_value' => variable_get('advagg_bundler_max_css', ADVAGG_BUNDLER_MAX_CSS), '#options' => $options, - '#description' => t('If 0 is selected then the bundler is disabled. 2 is recommended for HTTP 1.1 and 25 for HTTP 2.0.'), + '#description' => t('If 0 is selected then the bundler is disabled'), '#states' => array( 'disabled' => array( '#edit-advagg-bundler-active' => array('checked' => FALSE), ), ), - '#recommended_value' => 25, ); - $form['global_container']['advagg_bundler_max_js'] = array( + $form['advagg_bundler_max_js'] = array( '#type' => 'select', '#title' => t('Target Number Of JS Bundles Per Page'), '#default_value' => variable_get('advagg_bundler_max_js', ADVAGG_BUNDLER_MAX_JS), '#options' => $options, - '#description' => t('If 0 is selected then the bundler is disabled. 5 is recommended for HTTP 1.1 and 25 for HTTP 2.0.'), - '#states' => array( - 'disabled' => array( - '#edit-advagg-bundler-active' => array('checked' => FALSE), - ), - ), - '#recommended_value' => 25, - ); - - $form['global_container']['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.'), + '#description' => t('If 0 is selected then the bundler is disabled'), '#states' => array( 'disabled' => array( '#edit-advagg-bundler-active' => array('checked' => FALSE), ), ), - '#recommended_value' => 0, ); - $form['global_container']['info'] = array( + $form['info'] = array( '#type' => 'fieldset', '#collapsible' => TRUE, '#collapsed' => TRUE, @@ -196,34 +76,26 @@ function advagg_bundler_admin_settings_form() { $analysis = advagg_bundler_analysis('', TRUE); $rawtext = print_r($analysis, TRUE); - $form['global_container']['info']['advagg_bundler_info'] = array( + $form['info']['advagg_bundler_info'] = array( '#type' => 'textarea', '#title' => t('%count different groupings', array('%count' => count($analysis))), '#default_value' => $rawtext, '#rows' => 30, ); - // Clear the cache bins on submit. Also remove advagg_bundler_info so it - // doesn't get added to the variable table. + // Clear the cache bins on submit. $form['#submit'][] = 'advagg_bundler_admin_settings_form_submit'; return system_settings_form($form); } +// Submit callback. /** - * Submit callback, clear out the advagg cache bin. - * - * @ingroup advagg_forms_callback + * Clear out the advagg cache bin when the save configuration button is pressed. */ function advagg_bundler_admin_settings_form_submit($form, &$form_state) { - // Clear caches. - advagg_cache_clear_admin_submit(); - - // Unset advagg_bundler_info. - if (isset($form_state['values']['advagg_bundler_info'])) { - unset($form_state['values']['advagg_bundler_info']); + $cache_bins = advagg_flush_caches(); + foreach ($cache_bins as $bin) { + cache_clear_all('*', $bin, TRUE); } - - // Reset this form to defaults or recommended values; also show what changed. - advagg_set_admin_form_defaults_recommended($form_state, 'advagg_bundler_admin_mode'); } 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 1fed98a6e2..e712cc17f7 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 @@ -5,11 +5,6 @@ * Advanced aggregation bundler module. */ -/** - * @addtogroup advagg_hooks - * @{ - */ - /** * Implements hook_advagg_build_aggregate_plans_alter(). */ @@ -42,20 +37,17 @@ function advagg_bundler_advagg_build_aggregate_plans_alter(&$files, &$modified, $group = advagg_bundler_analysis($fileinfo['data']); $fileinfo['bundler'] = $group; - if (!empty($group['group_hash'])) { - // Set $last_group if this is the first run of this foreach loop. - if (empty($last_group)) { - $last_group = $group['group_hash']; - } - - if ($last_group != $group['group_hash']) { - // Bump Counter if group has changed from the last one. - ++$counter; - $last_group = $group['group_hash']; - $modified = TRUE; - } + // Set $last_group if this is the first run of this foreach loop. + if (empty($last_group)) { + $last_group = $group['group_hash']; } + if ($last_group != $group['group_hash']) { + // Bump Counter if group has changed from the last one. + ++$counter; + $last_group = $group['group_hash']; + $modified = TRUE; + } $groupings[$counter][] = $fileinfo; } // Make sure we didn't go over the max; if we did merge the smallest bundles @@ -99,10 +91,6 @@ function advagg_bundler_advagg_build_aggregate_plans_alter(&$files, &$modified, $files = advagg_generate_filenames($final_groupings, $type); } -/** - * @} End of "addtogroup advagg_hooks". - */ - /** * Merge bundles together if too many where created. * @@ -124,22 +112,10 @@ 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) { - // Skip if bundler data is missing. - if (empty($data['bundler'])) { - continue; - } - $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; + $group_hash_counts[$key] = intval(substr($data['bundler']['group_hash'], 0, 8)); } } @@ -191,7 +167,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 = PHP_INT_MAX; + $min = 32767; $first = NULL; $last = NULL; $last_min = NULL; @@ -258,7 +234,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 = PHP_INT_MAX; + $merge_candidate_count = 32767; $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 31477cffe1..58f449da76 100644 --- a/sites/all/modules/contrib/advagg/advagg_bundler/advagg_bundler.info +++ b/sites/all/modules/contrib/advagg/advagg_bundler/advagg_bundler.info @@ -6,8 +6,9 @@ dependencies[] = advagg configure = admin/config/development/performance/advagg/bundler -; Information added by Drupal.org packaging script on 2020-11-19 -version = "7.x-2.35" +; Information added by Drupal.org packaging script on 2015-04-14 +version = "7.x-2.8" core = "7.x" project = "advagg" -datestamp = "1605792717" +datestamp = "1429049283" + diff --git a/sites/all/modules/contrib/advagg/advagg_bundler/advagg_bundler.install b/sites/all/modules/contrib/advagg/advagg_bundler/advagg_bundler.install deleted file mode 100644 index 0cfaf6e4e6..0000000000 --- a/sites/all/modules/contrib/advagg/advagg_bundler/advagg_bundler.install +++ /dev/null @@ -1,37 +0,0 @@ -<?php - -/** - * @file - * Handles Advanced Aggregation installation and upgrade tasks. - */ - -/** - * @addtogroup hooks - * @{ - */ - -/** - * Implements hook_install(). - */ -function advagg_bundler_install() { - // New install gets a locked admin section. - variable_set('advagg_bundler_admin_mode', 0); -} - -/** - * Remove the advagg_bundler_info variable if set. - */ -function advagg_bundler_update_7201() { - $advagg_bundler_info = variable_get('advagg_bundler_info'); - if (!empty($advagg_bundler_info)) { - variable_del('advagg_bundler_info'); - return t('Removed the advagg_bundler_info variable.'); - } - else { - return t('Nothing needed to happen.'); - } -} - -/** - * @} End of "addtogroup hooks". - */ 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 8a0d0b738b..b027cead19 100644 --- a/sites/all/modules/contrib/advagg/advagg_bundler/advagg_bundler.module +++ b/sites/all/modules/contrib/advagg/advagg_bundler/advagg_bundler.module @@ -5,20 +5,15 @@ * Advanced aggregation bundler module. */ -/** - * @addtogroup default_variables - * @{ - */ - /** * Default for maximum number of CSS bundles used in a themes region. */ -define('ADVAGG_BUNDLER_MAX_CSS', 2); +define('ADVAGG_BUNDLER_MAX_CSS', 4); /** * Default for maximum number of JS bundles used in a themes region. */ -define('ADVAGG_BUNDLER_MAX_JS', 5); +define('ADVAGG_BUNDLER_MAX_JS', 4); /** * Default of the last used time before the bundle is considered outdated. @@ -35,25 +30,6 @@ define('ADVAGG_BUNDLER_OUTDATED', 1209600); */ define('ADVAGG_BUNDLER_ACTIVE', TRUE); -/** - * Default value to for bundler, set to file size. - */ -define('ADVAGG_BUNDLER_GROUPING_LOGIC', 1); - -/** - * If 4 the admin section gets unlocked. - */ -define('ADVAGG_BUNDLER_ADMIN_MODE', 4); - -/** - * @} End of "addtogroup default_variables". - */ - -/** - * @addtogroup hooks - * @{ - */ - /** * Implements hook_menu(). */ @@ -105,10 +81,6 @@ function advagg_bundler_form_advagg_admin_settings_form_alter(&$form, $form_stat } } -/** - * @} End of "addtogroup hooks". - */ - /** * Returns TRUE if the bundler will run. */ @@ -125,15 +97,11 @@ 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, $safesql = FALSE, $depth = 0) { +function advagg_bundler_analysis($filename = '', $force = FALSE) { // Cache query in a static. static $analysis = array(); if (empty($analysis)) { @@ -184,18 +152,100 @@ function advagg_bundler_analysis($filename = '', $force = FALSE, $safesql = FALS } if ($force || empty($cache->data)) { - try { - $analysis = advagg_bundler_analyisis_query($safesql); - // Save results to the cache. - cache_set($ideal_cid, $analysis, 'cache_advagg_aggregates', CACHE_TEMPORARY); + // "Magic Query"; only needs to run once. + // 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(); + + // Create join query for the advagg_aggregates table. + $subquery_aggregates = db_select('advagg_aggregates', 'aa'); + if ($db_type === 'sqlsrv') { + // MS SQL does not support LPAD. + $subquery_aggregates->addExpression("RIGHT(REPLICATE('0',8) + CAST(COUNT(aav.aggregate_filenames_hash) AS char(8)),8)", 'counter'); + } + else { + $subquery_aggregates->addExpression("LPAD(CAST(COUNT(aav.aggregate_filenames_hash) AS char(8)), 8, '0')", 'counter'); + } + + $fields = array('counter'); + if ($db_type === 'mysql') { + db_query('SET SESSION group_concat_max_len = 65535'); + $fields = array('counter', 'hashlist'); + $subquery_aggregates->addExpression('GROUP_CONCAT(DISTINCT aa.aggregate_filenames_hash ORDER BY aa.aggregate_filenames_hash ASC)', 'hashlist'); } - catch (PDOException $e) { - if ($depth > 2) { - throw $e; + + // 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); } - $depth++; - return advagg_bundler_analysis($filename, TRUE, TRUE, $depth); + + $analysis[$row->filename] = array( + 'group_hash' => $row->counter . ' ' . drupal_hash_base64($row->hashlist), + 'mtime' => $row->mtime, + 'filesize' => $row->filesize, + '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); + + // Save results to the cache. + cache_set($ideal_cid, $analysis, 'cache_advagg_aggregates', CACHE_TEMPORARY); } else { $analysis = $cache->data; @@ -216,194 +266,3 @@ function advagg_bundler_analysis($filename = '', $force = FALSE, $safesql = FALS // didn't give us anything. return 0; } - -/** - * Run the analysis query and return the analysis array. - * - * "Magic Query"; only needs to run once. Results are cached. - * This is what the raw SQL looks like: - * - * @code - * 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 - * @endcode - * - * @param bool $safesql - * Turn off SQL language that might cause errors. - * - * @return array - * The analysis array. - */ -function advagg_bundler_analyisis_query($safesql) { - // 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_critical_css/advagg_critical_css.admin.inc b/sites/all/modules/contrib/advagg/advagg_critical_css/advagg_critical_css.admin.inc deleted file mode 100644 index c9cae0350e..0000000000 --- a/sites/all/modules/contrib/advagg/advagg_critical_css/advagg_critical_css.admin.inc +++ /dev/null @@ -1,381 +0,0 @@ -<?php - -/** - * @file - * Admin page callbacks for the advagg critical css module. - */ - -/** - * Form builder; Configure advagg settings. - * - * @ingroup advagg_forms - * - * @see system_settings_form() - */ -function advagg_critical_css_admin_settings_form() { - drupal_set_title(t('AdvAgg: Critical CSS')); - advagg_display_message_if_requirements_not_met(); - - $form = array(); - - $default_theme = variable_get('theme_default', 'bartik'); - $global_theme = $GLOBALS['theme']; - $themes = array_keys(list_themes()); - - $form['#attached']['css'][] = array( - 'data' => ".form-item-lookup{padding-bottom:0;margin-bottom:0;}", - 'type' => 'inline', - ); - $form['add_item']['theme'] = array( - '#type' => 'select', - '#title' => t('Theme'), - '#options' => array_combine($themes, $themes), - '#default_value' => $default_theme, - '#description' => t('Theme Default: %default, Current Theme: %current', array( - '%default' => $default_theme, - '%current' => $global_theme, - )), - ); - $form['add_item']['user'] = array( - '#type' => 'select', - '#title' => t('User type'), - '#default_value' => 0, - '#options' => array( - 'anonymous' => t('anonymous'), - 'authenticated' => t('authenticated'), - 'all' => t('all'), - ), - ); - $type_options = array( - 0 => t('Disabled'), - 2 => t('URL'), - 8 => t('Node Type'), - ); - $form['add_item']['type'] = array( - '#type' => 'select', - '#title' => t('Type of lookup'), - '#default_value' => 2, - '#options' => $type_options, - ); - - $form['add_item']['lookup'] = array( - '#type' => 'textfield', - '#title' => t('Value to lookup'), - '#maxlength' => 255, - '#states' => array( - 'disabled' => array( - ':input[name="type"]' => array('value' => 0), - ), - ), - ); - $form['add_item']['lookup_container_disabled'] = array( - '#type' => 'container', - '#states' => array( - 'visible' => array( - ':input[name="type"]' => array('value' => 0), - ), - ), - ); - $form['add_item']['lookup_container_disabled']['disabled'] = array( - '#markup' => '<br>', - ); - $form['add_item']['lookup_container_current_path'] = array( - '#type' => 'container', - '#states' => array( - 'visible' => array( - ':input[name="type"]' => array('value' => 2), - ), - ), - ); - $form['add_item']['lookup_container_current_path']['current_path'] = array( - '#markup' => t('%front is the front page; can use internal URLs like %internal or an alias like %here', array( - '%front' => '<front>', - '%internal' => 'node/2', - '%here' => current_path(), - )), - ); - $form['add_item']['lookup_container_node_type'] = array( - '#type' => 'container', - '#states' => array( - 'visible' => array( - ':input[name="type"]' => array('value' => 8), - ), - ), - ); - $form['add_item']['lookup_container_node_type']['node_type'] = array( - '#markup' => t('Node type is the machine name of the node; list of node types: @node_types', array( - '@current_path' => 'https://api.drupal.org/api/drupal/includes%21path.inc/function/current_path/7.x', - '@request_path' => 'https://api.drupal.org/api/drupal/includes%21bootstrap.inc/function/request_path/7.x', - '@request_uri' => 'https://api.drupal.org/api/drupal/includes%21bootstrap.inc/function/request_uri/7.x', - '@node_types' => implode(', ', array_keys(node_type_get_names())), - )), - ); - - $form['add_item']['css'] = array( - '#type' => 'textarea', - '#title' => t('Critical CSS'), - '#description' => t('Can be generated via <a href="@url">https://www.sitelocity.com/critical-path-css-generator</a>. If this field is empty this entry will be deleted.', array( - '@url' => 'https://www.sitelocity.com/critical-path-css-generator', - )), - '#default_value' => '', - ); - $form['add_item']['dns'] = array( - '#type' => 'textarea', - '#title' => t('Hostnames to lookup'), - '#description' => t('Hosts that will be connected to.'), - '#default_value' => '', - ); - $form['add_item']['pre'] = array( - '#type' => 'textarea', - '#title' => t('Urls to Preload'), - '#description' => t('Assets for the browser that should be downloaded at a high priority.'), - '#default_value' => '', - ); - - // Lookup saved data. - $query = db_select('advagg_critical_css', 'acc') - ->fields('acc') - ->comment('Query called from ' . __FUNCTION__ . '()'); - $results = $query->execute(); - // Put results into array. - $counter = 0; - foreach ($results as $row) { - $counter++; - $row = (array) $row; - - foreach ($form['add_item'] as $key => $values) { - // Fix the states array for type. - if (!empty($values['#states'])) { - foreach ($values['#states'] as $states_key => $states_values) { - $states_value = reset($values['#states'][$states_key]); - $values['#states'][$states_key] = array(":input[name=\"{$counter}_type\"]" => $states_value); - } - } - $form['existing_items'][$counter]["{$counter}_{$key}"] = $values; - if (isset($row[$key])) { - $form['existing_items'][$counter]["{$counter}_{$key}"]['#default_value'] = $row[$key]; - } - } - - // Add in css to move the text hint up. - $form['#attached']['css'][] = array( - 'data' => ".form-item-{$counter}-lookup{padding-bottom:0;margin-bottom:0;}", - 'type' => 'inline', - ); - - // Add fieldset. - $filename = advagg_url_to_filename($row['lookup'], FALSE); - $base = drupal_get_path('theme', $row['theme']) . "/critical-css/{$row['user']}/"; - if ($row['type'] == 2) { - $base .= "urls/$filename"; - } - elseif ($row['type'] == 8) { - $base .= "type/$filename"; - } - else { - $base = ''; - } - $form['existing_items'][$counter] += array( - '#type' => 'fieldset', - '#collapsible' => TRUE, - '#collapsed' => TRUE, - '#title' => t('@type @theme @user @lookup', array( - '@theme' => $row['theme'], - '@type' => $type_options[$row['type']], - '@user' => $row['user'], - '@lookup' => $row['lookup'], - )), - ); - - if (!empty($base)) { - $form['existing_items'][$counter]['#description'] = t('If you wish to store this configuration in a file<br>Critical CSS: <code>@css</code>', array( - '@css' => "$base.css", - )); - if (!empty($row['dns'])) { - $form['existing_items'][$counter]['#description'] .= t('<br>Hostnames: <code>@dns</code>', array( - '@dns' => "$base.dns", - )); - } - if (!empty($row['pre'])) { - $form['existing_items'][$counter]['#description'] .= t('<br>Preload: <code>@pre</code>', array( - '@pre' => "$base.pre", - )); - } - } - } - - // Add top level fieldsets. - $form['add_item'] += array( - '#type' => 'fieldset', - '#title' => t('Add Critical CSS'), - '#collapsible' => TRUE, - '#collapsed' => $results->rowCount(), - ); - if (!empty($form['existing_items'])) { - $form['existing_items'] += array( - '#type' => 'fieldset', - '#title' => t('Edit Critical CSS'), - ); - } - - $form['advagg_critical_css_selector_blacklist'] = array( - '#type' => 'textarea', - '#title' => t('Selector Blacklist'), - '#description' => t('Selectors to exclude. Enter one per line. Useful for things like google ads.'), - '#default_value' => variable_get('advagg_critical_css_selector_blacklist', ''), - ); - - // Clear the cache bins on submit. - $form['#submit'][] = 'advagg_critical_css_admin_settings_form_submit'; - - // Most code below taken from system_settings_form(). - $form['actions']['#type'] = 'actions'; - $form['actions']['submit'] = array( - '#type' => 'submit', - '#value' => t('Save configuration'), - ); - $form['actions']['disable'] = array( - '#type' => 'submit', - '#value' => t('Disable All From Database'), - '#submit' => array('advagg_critical_css_admin_settings_form_submit_disable'), - ); - if (!empty($_POST) && form_get_errors()) { - drupal_set_message(t('The settings have not been saved because of the errors.'), 'error'); - } - // By default, render the form using theme_system_settings_form(). - if (!isset($form['#theme'])) { - $form['#theme'] = 'system_settings_form'; - } - return $form; -} - -/** - * Submit callback, process the advagg_critical_css form. - * - * Also clear out the advagg cache bin. - * - * @ingroup advagg_forms_callback - */ -function advagg_critical_css_admin_settings_form_submit_disable($form, &$form_state) { - $query = db_select('advagg_critical_css', 'acc') - ->fields('acc') - ->comment('Query called from ' . __FUNCTION__ . '()'); - $results = $query->execute(); - // Put results into array. - $insert_update = array(); - foreach ($results as $row) { - $row = (array) $row; - $new_row = $row; - $new_row['type'] = 0; - $insert_update[] = array( - $row, - $new_row, - ); - } - advagg_critical_css_table_insert_update($insert_update); -} - -/** - * Submit callback, process the advagg_critical_css form. - * - * Also clear out the advagg cache bin. - * - * @ingroup advagg_forms_callback - */ -function advagg_critical_css_admin_settings_form_submit($form, &$form_state) { - // Exclude unnecessary elements. - form_state_values_clean($form_state); - - // Save advagg_critical_css_selector_blacklist. - if (!isset($form_state['values']['advagg_critical_css_selector_blacklist'])) { - $form_state['values']['advagg_critical_css_selector_blacklist'] = ''; - } - $advagg_critical_css_selector_blacklist = variable_get('advagg_critical_css_selector_blacklist', ''); - if ($form_state['values']['advagg_critical_css_selector_blacklist'] !== $advagg_critical_css_selector_blacklist) { - variable_set('advagg_critical_css_selector_blacklist', $form_state['values']['advagg_critical_css_selector_blacklist']); - } - unset($form_state['values']['advagg_critical_css_selector_blacklist']); - - // Rearrange form values into key value pairs. - $items = advagg_critical_css_get_rows_from_form($form_state['values']); - // Get default values. - $default_values = advagg_find_all_recommended_admin_values($form_state['complete form'], '#default_value'); - unset($default_values['form_token']); - $default_items = advagg_critical_css_get_rows_from_form($default_values); - - // Get diff, see what items need to be saved. - $diff = advagg_diff_multi($default_items, $items); - $changed_items = array(); - foreach ($diff as $key => $values) { - $changed_items[$key] = $items[$key]; - } - - // Get items to insert/update and delete. - list($insert_update, $delete) = advagg_critical_css_get_db_operations_arrays($changed_items, $default_items); - advagg_critical_css_table_insert_update($insert_update); - advagg_critical_css_table_delete($delete); - - // Clear caches. - advagg_cache_clear_admin_submit(); - drupal_set_message(t('The configuration options have been saved.')); -} - -/** - * Translate from state values into a nested array strucutre. - * - * @param array $form_state_values - * From state values; from $form_state['values']. - * - * @return array - * Nested array strucutre, each index is a row in the db. - */ -function advagg_critical_css_get_rows_from_form(array $form_state_values) { - $items = array(); - $counter = 0; - foreach ($form_state_values as $key => $values) { - // Get the index from the start of the form name. - $matches = array(); - // 1_type turns into $counter = 1 and $key = type. - preg_match('/^(\d)_(.*)/', $key, $matches); - if (!empty($matches)) { - $counter = $matches[1]; - $key = $matches[2]; - } - $items[$counter][$key] = $values; - } - return $items; -} - -/** - * Given a list of items see what ones need to be inserted/updated or deleted. - * - * @param array $items - * Array of values, representing a row in the db. - * - * @return array - * Nested array strucutre, index 0 is the insert update, 1 is the deleted. - */ -function advagg_critical_css_get_db_operations_arrays(array $items, array $old_items) { - $insert_update = array(); - $delete = array(); - foreach ($items as $key => $values) { - // If the css is empty then this needs to be deleted. - if (empty($values['css'])) { - // Do not delete the new items entry (0); it's not in the db currently. - if (!empty($key)) { - $delete[$key] = $values; - } - } - else { - // Pass along the old key value pairs for db_merge. - if (!empty($old_items[$key])) { - $keys = $old_items[$key] + $values; - } - else { - $keys = $values; - } - $insert_update[$key] = array($keys, $values); - } - } - return array($insert_update, $delete); -} diff --git a/sites/all/modules/contrib/advagg/advagg_critical_css/advagg_critical_css.info b/sites/all/modules/contrib/advagg/advagg_critical_css/advagg_critical_css.info deleted file mode 100644 index a6a78db0aa..0000000000 --- a/sites/all/modules/contrib/advagg/advagg_critical_css/advagg_critical_css.info +++ /dev/null @@ -1,14 +0,0 @@ -name = AdvAgg Critical CSS -description = Control Critical CSS via UI and/or a 3rd party service. -package = Advanced CSS/JS Aggregation -core = 7.x -dependencies[] = advagg -dependencies[] = advagg_mod - -configure = admin/config/development/performance/advagg/critical-css - -; Information added by Drupal.org packaging script on 2020-11-19 -version = "7.x-2.35" -core = "7.x" -project = "advagg" -datestamp = "1605792717" diff --git a/sites/all/modules/contrib/advagg/advagg_critical_css/advagg_critical_css.install b/sites/all/modules/contrib/advagg/advagg_critical_css/advagg_critical_css.install deleted file mode 100644 index 98c954d6f7..0000000000 --- a/sites/all/modules/contrib/advagg/advagg_critical_css/advagg_critical_css.install +++ /dev/null @@ -1,84 +0,0 @@ -<?php - -/** - * @file - * Handles AdvAgg Critical CSS installation and upgrade tasks. - */ - -/** - * @addtogroup hooks - * @{ - */ - -/** - * Implements hook_schema(). - */ -function advagg_critical_css_schema() { - // Create database table. - $schema['advagg_critical_css'] = array( - 'description' => 'The critical css to inline.', - 'fields' => array( - 'theme' => array( - 'description' => 'The theme name.', - 'type' => 'varchar', - 'length' => 255, - 'default' => '', - 'binary' => TRUE, - ), - 'type' => array( - 'description' => 'Type like url or node.', - 'type' => 'int', - 'not null' => TRUE, - 'default' => 0, - ), - 'user' => array( - 'description' => 'User Type or UID.', - 'type' => 'varchar', - 'length' => 255, - 'default' => '', - 'binary' => TRUE, - ), - 'lookup' => array( - 'description' => 'Value from current_path if url or node type if node.', - 'type' => 'varchar', - 'length' => 255, - 'default' => '', - 'binary' => TRUE, - ), - 'css' => array( - 'description' => 'Critical CSS.', - 'type' => 'blob', - 'size' => 'big', - ), - 'dns' => array( - 'description' => 'Hosts for dns lookedup.', - 'type' => 'blob', - 'size' => 'big', - ), - 'pre' => array( - 'description' => 'URLs for preloading.', - 'type' => 'blob', - 'size' => 'big', - ), - 'settings' => array( - 'description' => 'Extra settings if desired.', - 'type' => 'blob', - 'size' => 'big', - 'translatable' => TRUE, - 'serialize' => TRUE, - ), - ), - 'primary key' => array( - 'lookup', - 'user', - 'type', - 'theme', - ), - ); - - return $schema; -} - -/** - * @} End of "addtogroup hooks". - */ diff --git a/sites/all/modules/contrib/advagg/advagg_critical_css/advagg_critical_css.module b/sites/all/modules/contrib/advagg/advagg_critical_css/advagg_critical_css.module deleted file mode 100644 index 6bd3c57271..0000000000 --- a/sites/all/modules/contrib/advagg/advagg_critical_css/advagg_critical_css.module +++ /dev/null @@ -1,239 +0,0 @@ -<?php - -/** - * @file - * Advanced aggregation critical css module. - */ - -/** - * @addtogroup hooks - * @{ - */ - -/** - * Implements hook_menu(). - */ -function advagg_critical_css_menu() { - $file_path = drupal_get_path('module', 'advagg_critical_css'); - $config_path = advagg_admin_config_root_path(); - - $items[$config_path . '/advagg/critical-css'] = array( - 'title' => 'Critical CSS', - 'description' => 'Control critical css.', - 'page callback' => 'drupal_get_form', - 'page arguments' => array('advagg_critical_css_admin_settings_form'), - 'type' => MENU_LOCAL_TASK, - 'access arguments' => array('administer site configuration'), - 'file path' => $file_path, - 'file' => 'advagg_critical_css.admin.inc', - 'weight' => 10, - ); - - return $items; -} - -/** - * Implements hook_module_implements_alter(). - */ -function advagg_critical_css_module_implements_alter(&$implementations, $hook) { - // Move critical_css_advagg_mod_critical_css_file_pre_alter to the bottom. - if ($hook === 'critical_css_advagg_mod_critical_css_file_pre_alter' && array_key_exists('advagg_critical_css', $implementations)) { - $item = $implementations['advagg_critical_css']; - unset($implementations['advagg_critical_css']); - $implementations['advagg_critical_css'] = $item; - } -} - -/** - * @} End of "addtogroup hooks". - */ - -/** - * @addtogroup advagg_hooks - * @{ - */ - -/** - * Implements hook_advagg_mod_critical_css_file_pre_alter(). - */ -function advagg_critical_css_advagg_mod_critical_css_file_pre_alter(&$filename, &$params, &$inline_strings) { - list($dirs, $front_page, $object) = $params; - - // Build query parameters. - $lookup = array($dirs[6]); - if ($front_page) { - $lookup = array('<front>'); - } - $lookup[] = $dirs[9]; - $lookup[] = $dirs[10]; - if (!empty($object->type)) { - $lookup[] = $object->type; - } - $type = array(2, 8); - $users = array(rtrim($dirs[2], '/\\'), rtrim($dirs[3], '/\\')); - - // Get Results. - $result = advagg_critical_css_table_get($GLOBALS['theme'], $type, $lookup, $users); - - // Put into the inline strings array. - if (!empty($result)) { - // Set string values. - $inline_strings[0] = $result['css']; - $inline_strings[1] = $result['dns']; - $inline_strings[2] = $result['pre']; - // Disable file lookup. - $dirs[0] = ''; - $dirs[1] = ''; - } - - // Repack the $params array. - $params = array($dirs, $front_page, $object); -} - -/** - * Implements hook_advagg_mod_critical_css_file_post_alter(). - */ -function advagg_critical_css_advagg_mod_critical_css_file_post_alter(&$filename, &$params, &$inline_strings) { - if (!empty($inline_strings[0])) { - // Remove given css selectors. - $selectors = variable_get('advagg_critical_css_selector_blacklist', ''); - $selectors_array = array_filter(array_map('trim', explode("\n", $selectors))); - foreach ($selectors_array as $pattern) { - $pattern = preg_quote($pattern, '/'); - $pattern = "/([^}]*{$pattern}[^{]*[^}]*\})/s"; - $inline_strings[0] = preg_replace($pattern, '', $inline_strings[0]); - } - } -} - -/** - * @} End of "addtogroup advagg_hooks". - */ - -/** - * Get the db select return object. - * - * @param string $theme - * Name of the current theme. - * @param array $type - * Array of int types to lookup. - * @param array $lookup - * The lookup value. - * @param array $user - * Array of user string values. - * - * @return SelectQuery - * Return the SelectQuery object after it has been executed. - */ -function advagg_critical_css_table_get($theme, array $type, array $lookup, array $user) { - $output = array(); - try { - $results = db_select('advagg_critical_css', 'acc') - ->fields('acc') - ->condition('theme', $theme) - ->condition('type', $type, 'IN') - ->condition('user', $user, 'IN') - ->condition('lookup', $lookup, 'IN') - ->orderBy('type', 'DESC') - ->execute(); - - // Get first result. - $output = $results->fetchAssoc(); - - // Check for a better match in other results if they exist. - foreach ($results as $values) { - $values = (array) $values; - if ($values['type'] < $output['type']) { - $output = $values; - break; - } - if ($values['type'] = $output['type']) { - if (($values['user'] === 'anonymous' || $values['user'] === 'authenticated') - && $output['user'] === 'all' - ) { - $output = $values; - break; - } - if (is_int($values['user'])) { - $output = $values; - break; - } - } - } - } - catch (PDOException $e) { - // Log the error if in development mode. - if (variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL) < 0) { - watchdog('advagg_critical_css', 'Development Mode - Caught PDO Exception: <code>@info</code>', array('@info' => $e)); - } - } - - return $output; -} - -/** - * Insert/Update data in the advagg_critical_css table. - * - * @param array $records - * List of rows needed that need to be changed in the db. - * - * @return array - * Return array of booleans if anything was written to the database. - */ -function advagg_critical_css_table_insert_update(array $records) { - $return = array(); - foreach ($records as $values) { - list($keys, $record) = $values; - if (!isset($record['settings'])) { - $record['settings'] = ''; - } - try { - $return[] = db_merge('advagg_critical_css') - ->key(array( - 'theme' => $keys['theme'], - 'user' => $keys['user'], - 'type' => $keys['type'], - 'lookup' => $keys['lookup'], - )) - ->fields($record) - ->execute(); - } - catch (PDOException $e) { - // Log the error if in development mode. - if (variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL) < 0) { - watchdog('advagg_critical_css', 'Development Mode - Caught PDO Exception: <code>@info</code>', array('@info' => $e)); - } - } - } - return $return; -} - -/** - * Delete data in the advagg_critical_css table. - * - * @param array $records - * List of rows needed that need to be removed from the db. - * - * @return array - * Return array of booleans if anything was removed from the database. - */ -function advagg_critical_css_table_delete(array $records) { - $return = array(); - foreach ($records as $record) { - try { - $return[] = db_delete('advagg_critical_css') - ->condition('theme', $record['theme']) - ->condition('user', $record['user']) - ->condition('type', $record['type']) - ->condition('lookup', $record['lookup']) - ->execute(); - } - catch (PDOException $e) { - // Log the error if in development mode. - if (variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL) < 0) { - watchdog('advagg_critical_css', 'Development Mode - Caught PDO Exception: <code>@info</code>', array('@info' => $e)); - } - } - } - return $return; -} 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 e6e0319a97..0813467169 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,8 +4,9 @@ package = Advanced CSS/JS Aggregation core = 7.x dependencies[] = advagg -; Information added by Drupal.org packaging script on 2020-11-19 -version = "7.x-2.35" +; Information added by Drupal.org packaging script on 2015-04-14 +version = "7.x-2.8" core = "7.x" project = "advagg" -datestamp = "1605792717" +datestamp = "1429049283" + diff --git a/sites/all/modules/contrib/advagg/advagg_css_cdn/advagg_css_cdn.install b/sites/all/modules/contrib/advagg/advagg_css_cdn/advagg_css_cdn.install index 213c2599cf..64c31a8c29 100644 --- a/sites/all/modules/contrib/advagg/advagg_css_cdn/advagg_css_cdn.install +++ b/sites/all/modules/contrib/advagg/advagg_css_cdn/advagg_css_cdn.install @@ -5,11 +5,6 @@ * Handles Advanced Aggregation installation and upgrade tasks. */ -/** - * @addtogroup hooks - * @{ - */ - /** * Implements hook_requirements(). */ @@ -59,7 +54,3 @@ function advagg_css_cdn_requirements($phase) { return $requirements; } - -/** - * @} End of "addtogroup hooks". - */ 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 bd6cd42837..3984d00745 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 @@ -5,11 +5,6 @@ * Advanced aggregation js cdn module. */ -/** - * @addtogroup default_variables - * @{ - */ - /** * Default value to see if jquery-ui should be grabbed from the Google CDN. */ @@ -20,20 +15,11 @@ define('ADVAGG_CSS_CDN_JQUERY_UI', TRUE); */ define('ADVAGG_CSS_CDN_JQUERY_UI_VERSION', '1.8.7'); -/** - * @} End of "addtogroup default_variables". - */ - -/** - * @addtogroup hooks - * @{ - */ - /** * 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; } @@ -44,31 +30,45 @@ function advagg_css_cdn_css_alter(&$css) { $ui_mapping = advagg_css_cdn_get_ui_mapping(); foreach ($css as $name => $values) { + // @ignore sniffer_commenting_inlinecomment_spacingbefore:4 + // @ignore sniffer_whitespace_openbracketspacing_openingwhitespace:8 // Only modify if // advagg_css_cdn_jquery_ui is enabled, // name is in the $ui_mapping array. // and type is file. - if (variable_get('advagg_css_cdn_jquery_ui', ADVAGG_CSS_CDN_JQUERY_UI) + if ( variable_get('advagg_css_cdn_jquery_ui', ADVAGG_CSS_CDN_JQUERY_UI) && array_key_exists($name, $ui_mapping) && $css[$name]['type'] === 'file' ) { $css[$name]['data'] = '//ajax.googleapis.com/ajax/libs/jqueryui/' . $jquery_ui_version . '/themes/base/jquery.' . $ui_mapping[$name] . '.css'; $css[$name]['type'] = 'external'; - // Fallback does not work do to - // "SecurityError: The operation is insecure.". + // Fallback can not work do to "SecurityError: The operation is insecure." } } } /** - * @} End of "addtogroup hooks". - */ - -/** - * @addtogroup advagg_hooks - * @{ + * Return an array of jquery ui files. */ +function advagg_css_cdn_get_ui_mapping() { + // Replace jQuery UI's CSS, beginning by defining the mapping. + $ui_mapping = array( + 'misc/ui/jquery.ui.accordion.css' => 'ui.accordion', + 'misc/ui/jquery.ui.autocomplete.css' => 'ui.autocomplete', + 'misc/ui/jquery.ui.button.css' => 'ui.button', + 'misc/ui/jquery.ui.core.css' => 'ui.core', + 'misc/ui/jquery.ui.datepicker.css' => 'ui.datepicker', + 'misc/ui/jquery.ui.dialog.css' => 'ui.dialog', + 'misc/ui/jquery.ui.progressbar.css' => 'ui.progressbar', + 'misc/ui/jquery.ui.resizable.css' => 'ui.resizable', + 'misc/ui/jquery.ui.selectable.css' => 'ui.selectable', + 'misc/ui/jquery.ui.slider.css' => 'ui.slider', + 'misc/ui/jquery.ui.tabs.css' => 'ui.tabs', + 'misc/ui/jquery.ui.theme.css' => 'ui.theme', + ); + return $ui_mapping; +} /** * Implements hook_advagg_css_groups_alter(). @@ -116,7 +116,8 @@ function advagg_css_cdn_advagg_css_groups_alter(&$css_groups, $preprocess_css) { } else { $diff = array_merge(array_diff_assoc($group['browsers'], $target['browsers']), array_diff_assoc($target['browsers'], $group['browsers'])); - if ($group['type'] != $target['type'] + // @ignore sniffer_whitespace_openbracketspacing_openingwhitespace:8 + if ( $group['type'] != $target['type'] || $group['group'] != $target['group'] || $group['every_page'] != $target['every_page'] || $group['media'] != $target['media'] @@ -126,7 +127,8 @@ function advagg_css_cdn_advagg_css_groups_alter(&$css_groups, $preprocess_css) { ) { if (!empty($last_group)) { $diff = array_merge(array_diff_assoc($last_group['browsers'], $target['browsers']), array_diff_assoc($target['browsers'], $last_group['browsers'])); - if ($last_group['type'] != $target['type'] + // @ignore sniffer_whitespace_openbracketspacing_openingwhitespace:8 + if ( $last_group['type'] != $target['type'] || $last_group['group'] != $target['group'] || $last_group['every_page'] != $target['every_page'] || $last_group['media'] != $target['media'] @@ -167,29 +169,3 @@ function advagg_css_cdn_advagg_css_groups_alter(&$css_groups, $preprocess_css) { } ksort($css_groups); } - -/** - * @} End of "addtogroup advagg_hooks". - */ - -/** - * Return an array of jquery ui files. - */ -function advagg_css_cdn_get_ui_mapping() { - // Replace jQuery UI's CSS, beginning by defining the mapping. - $ui_mapping = array( - 'misc/ui/jquery.ui.accordion.css' => 'ui.accordion', - 'misc/ui/jquery.ui.autocomplete.css' => 'ui.autocomplete', - 'misc/ui/jquery.ui.button.css' => 'ui.button', - 'misc/ui/jquery.ui.core.css' => 'ui.core', - 'misc/ui/jquery.ui.datepicker.css' => 'ui.datepicker', - 'misc/ui/jquery.ui.dialog.css' => 'ui.dialog', - 'misc/ui/jquery.ui.progressbar.css' => 'ui.progressbar', - 'misc/ui/jquery.ui.resizable.css' => 'ui.resizable', - 'misc/ui/jquery.ui.selectable.css' => 'ui.selectable', - 'misc/ui/jquery.ui.slider.css' => 'ui.slider', - 'misc/ui/jquery.ui.tabs.css' => 'ui.tabs', - 'misc/ui/jquery.ui.theme.css' => 'ui.theme', - ); - return $ui_mapping; -} 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 66e1a10769..53e4af3d56 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 @@ -8,13 +8,12 @@ /** * Form builder; Configure advagg settings. * - * @ingroup advagg_forms + * @ingroup forms * * @see system_settings_form() */ function advagg_css_compress_admin_settings_form($form, $form_state) { drupal_set_title(t('AdvAgg: CSS Compression Settings')); - advagg_display_message_if_requirements_not_met(); $config_path = advagg_admin_config_root_path(); $form = array(); @@ -24,18 +23,11 @@ function advagg_css_compress_admin_settings_form($form, $form_state) { ); } - // Tell user to update library if a new version is available. - $library_name = 'YUI-CSS-compressor-PHP-port'; - $module_name = 'advagg_css_compress'; - list($description) = advagg_get_version_description($library_name, $module_name); - if (!empty($description)) { - $form['advagg_version_msg'] = array( - '#markup' => "<p>{$description}</p>", - ); - } - - list($options, $description) = advagg_css_compress_configuration(); - + $description = ''; + $options = array( + 0 => t('Disabled'), + 2 => t('YUI'), + ); $form['advagg_css_compressor'] = array( '#type' => 'radios', '#title' => t('File Compression: Select a Compressor'), @@ -43,14 +35,11 @@ function advagg_css_compress_admin_settings_form($form, $form_state) { '#options' => $options, '#description' => filter_xss($description), ); - $inline_options = $options; - unset($inline_options[-1]); - $inline_options[0] = t('Disabled'); $form['advagg_css_compress_inline'] = array( '#type' => 'radios', '#title' => t('Inline Compression: Select a Compressor'), '#default_value' => variable_get('advagg_css_compress_inline', ADVAGG_CSS_COMPRESS_INLINE), - '#options' => $inline_options, + '#options' => $options, '#description' => filter_xss($description), ); $form['advagg_css_compress_inline_if_not_cacheable'] = array( @@ -65,7 +54,7 @@ function advagg_css_compress_admin_settings_form($form, $form_state) { ), ); - $options[ADVAGG_CSS_COMPRESSOR_FILE_SETTINGS] = t('Default'); + $options[-1] = t('Default'); ksort($options); $form['per_file_settings'] = array( @@ -74,11 +63,11 @@ function advagg_css_compress_admin_settings_form($form, $form_state) { '#collapsible' => TRUE, '#collapsed' => TRUE, ); - // Get filename and filename_hash. + // Get filename & filename_hash. $results = db_select('advagg_files', 'af') ->fields('af', array('filename')) ->condition('filetype', 'css') - ->orderBy('filename', 'ASC') + ->orderBy('af.filename', 'ASC') ->execute(); $file_settings = variable_get('advagg_css_compressor_file_settings', array()); foreach ($results as $row) { @@ -104,27 +93,23 @@ 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'; return system_settings_form($form); } +// Submit callback. /** - * Submit callback that clears out the advagg cache bin. + * Clear out the advagg cache bin when the save configuration button is pressed. * * Also remove default settings inside of the per_file_settings fieldgroup. - * - * @ingroup advagg_forms_callback */ function advagg_css_compress_admin_settings_form_submit($form, &$form_state) { - // Clear caches. - advagg_cache_clear_admin_submit(); + $cache_bins = advagg_flush_caches(); + foreach ($cache_bins as $bin) { + cache_clear_all('*', $bin, TRUE); + } // Get current defaults. $file_settings = variable_get('advagg_css_compressor_file_settings', array()); @@ -132,7 +117,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; } @@ -142,9 +127,6 @@ function advagg_css_compress_admin_settings_form_submit($form, &$form_state) { continue; } $new_settings[substr($key, 36)] = $value; - - // Do not save this field into its own variable. - unset($form_state['values'][$key]); } if (!empty($new_settings) || !empty($file_settings)) { if (empty($new_settings)) { diff --git a/sites/all/modules/contrib/advagg/advagg_css_compress/advagg_css_compress.advagg.inc b/sites/all/modules/contrib/advagg/advagg_css_compress/advagg_css_compress.advagg.inc index d152670a4e..cfd7454716 100644 --- a/sites/all/modules/contrib/advagg/advagg_css_compress/advagg_css_compress.advagg.inc +++ b/sites/all/modules/contrib/advagg/advagg_css_compress/advagg_css_compress.advagg.inc @@ -5,28 +5,6 @@ * Advanced aggregation css compression module. */ -/** - * @addtogroup advagg_hooks - * @{ - */ - -/** - * Implements hook_advagg_get_css_file_contents_pre_alter(). - */ -function advagg_css_compress_advagg_get_css_file_contents_pre_alter(&$file, &$optimize, &$aggregate_settings) { - // Get per file settings. - if (!empty($aggregate_settings['variables']['advagg_css_compressor_file_settings'])) { - $form_api_filename = str_replace(array('/', '.'), array('__', '--'), $file); - if (isset($aggregate_settings['variables']['advagg_css_compressor_file_settings'][$form_api_filename])) { - $aggregate_settings['variables']['advagg_css_compressor'] = $aggregate_settings['variables']['advagg_css_compressor_file_settings'][$form_api_filename]; - } - } - - if (isset($aggregate_settings['variables']['advagg_css_compressor']) && $aggregate_settings['variables']['advagg_css_compressor'] == -1) { - $optimize = FALSE; - } -} - /** * Implements hook_advagg_get_css_aggregate_contents_alter(). */ @@ -55,56 +33,21 @@ function advagg_css_compress_advagg_get_css_aggregate_contents_alter(&$data, $fi return; } - list(, , , $functions) = advagg_css_compress_configuration(); - - if (isset($functions[$aggregate_settings['variables']['advagg_css_compressor']])) { - $run = $functions[$aggregate_settings['variables']['advagg_css_compressor']]; - if (function_exists($run)) { - $functions[$aggregate_settings['variables']['advagg_css_compressor']]($data); - } + if ($aggregate_settings['variables']['advagg_css_compressor'] == 2) { + advagg_css_compress_yui_cssmin($data); } } -/** - * @} End of "addtogroup advagg_hooks". - */ - /** * Use the CSSmin library from YUI to compress the CSS. */ function advagg_css_compress_yui_cssmin(&$data) { - // Try libraries for YUI. - if (is_callable('libraries_load')) { - libraries_load('YUI-CSS-compressor-PHP-port'); - if (class_exists('tubalmartin\CssMin\Minifier')) { - // The "use" alias requires php 5.3. - // @codingStandardsIgnoreLine - $cssmin = new tubalmartin\CssMin\Minifier(); - } - elseif (class_exists('CSSmin')) { - $cssmin = new CSSmin(); - } - } - if (!isset($cssmin)) { - // Load CSSMin.inc if the CSSmin class variable is not set. - if (!class_exists('CSSmin')) { - include drupal_get_path('module', 'advagg_css_compress') . '/yui/CSSMin.inc'; - } - $cssmin = new CSSmin(); - } - if (!isset($cssmin)) { - return; + // Only include CSSMin.inc if the CSSmin class doesn't exist. + if (!class_exists('CSSmin')) { + include drupal_get_path('module', 'advagg_css_compress') . '/yui/CSSMin.inc'; } - // Set line break to 4k of text. - if (method_exists($cssmin, 'setLineBreakPosition')) { - $cssmin->setLineBreakPosition(4096); - } + $cssmin = new CSSmin(FALSE); // Compress the CSS splitting lines after 4k of text. - if (method_exists($cssmin, 'run')) { - $compressed = $cssmin->run($data, 4096); - } - if (!empty($compressed)) { - $data = $compressed; - } + $data = $cssmin->run($data, 4096); } 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 aba0cc76ee..bf818c3619 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 @@ -3,12 +3,12 @@ description = Compress CSS with a 3rd party compressor, YUI currently. package = Advanced CSS/JS Aggregation core = 7.x dependencies[] = advagg -recommends[] = libraries configure = admin/config/development/performance/advagg/css-compress -; Information added by Drupal.org packaging script on 2020-11-19 -version = "7.x-2.35" +; Information added by Drupal.org packaging script on 2015-04-14 +version = "7.x-2.8" core = "7.x" project = "advagg" -datestamp = "1605792717" +datestamp = "1429049283" + diff --git a/sites/all/modules/contrib/advagg/advagg_css_compress/advagg_css_compress.install b/sites/all/modules/contrib/advagg/advagg_css_compress/advagg_css_compress.install index b4daf3f6bb..7521f21975 100644 --- a/sites/all/modules/contrib/advagg/advagg_css_compress/advagg_css_compress.install +++ b/sites/all/modules/contrib/advagg/advagg_css_compress/advagg_css_compress.install @@ -5,11 +5,6 @@ * Handles AdvAgg CSS compress installation and upgrade tasks. */ -/** - * @addtogroup hooks - * @{ - */ - /** * Implements hook_requirements(). */ @@ -24,7 +19,8 @@ function advagg_css_compress_requirements($phase) { } // Make sure a compressor is being used. - if (variable_get('advagg_css_compressor', ADVAGG_CSS_COMPRESSOR) == 0 + // @ignore sniffer_whitespace_openbracketspacing_openingwhitespace + if ( variable_get('advagg_css_compressor', ADVAGG_CSS_COMPRESSOR) == 0 && variable_get('advagg_css_compress_inline', ADVAGG_CSS_COMPRESS_INLINE) == 0 ) { // Check all files. @@ -52,25 +48,13 @@ function advagg_css_compress_requirements($phase) { ); } } - - // Check version. - $lib_name = 'YUI-CSS-compressor-PHP-port'; - $module_name = 'advagg_css_compress'; - list($description, $info) = advagg_get_version_description($lib_name, $module_name); - if (!empty($description)) { - $requirements["{$module_name}_{$lib_name}_updates"] = array( - 'title' => $t('@module_name', array('@module_name' => $info['name'])), - 'severity' => REQUIREMENT_WARNING, - 'value' => $t('The @name library needs to be updated.', array('@name' => $lib_name)), - 'description' => $description, - ); - } - return $requirements; } /** - * Upgrade AdvAgg CSS Compress versions (6.x-1.x and 7.x-1.x) to 7.x-2.x. + * Upgrade AdvAgg CSS Compress previous versions (6.x-1.x & 7.x-1.x) to 7.x-2.x. + * + * Implements hook_update_N(). */ function advagg_css_compress_update_7200(&$sandbox) { // Bail if old DB Table does not exist. @@ -91,6 +75,8 @@ function advagg_css_compress_update_7200(&$sandbox) { /** * Change variable names so they are prefixed with the modules name. + * + * Implements hook_update_N(). */ function advagg_css_compress_update_7201(&$sandbox) { // Rename advagg_css_inline_compressor to advagg_css_compress_inline. @@ -112,17 +98,3 @@ function advagg_css_compress_update_7201(&$sandbox) { variable_set('advagg_css_compress_inline_if_not_cacheable', $old); } } - -/** - * Remove unused variables from the variable table. - */ -function advagg_css_compress_update_7202(&$sandbox) { - // Remove all old advagg css compress variables. - db_delete('variable') - ->condition('name', 'advagg_css_compressor_file_settings_%', 'LIKE') - ->execute(); -} - -/** - * @} End of "addtogroup hooks". - */ diff --git a/sites/all/modules/contrib/advagg/advagg_css_compress/advagg_css_compress.module b/sites/all/modules/contrib/advagg/advagg_css_compress/advagg_css_compress.module index 5f6fbb2315..2eeb714f15 100644 --- a/sites/all/modules/contrib/advagg/advagg_css_compress/advagg_css_compress.module +++ b/sites/all/modules/contrib/advagg/advagg_css_compress/advagg_css_compress.module @@ -5,11 +5,6 @@ * Advanced aggregation css compression module. */ -/** - * @addtogroup default_variables - * @{ - */ - /** * Default value for which css compression library to use. 0 is Disabled. */ @@ -28,16 +23,7 @@ define('ADVAGG_CSS_COMPRESS_INLINE_IF_NOT_CACHEABLE', FALSE); /** * Default value for per file compression settings. */ -define('ADVAGG_CSS_COMPRESSOR_FILE_SETTINGS', -10); - -/** - * @} End of "addtogroup default_variables". - */ - -/** - * @addtogroup hooks - * @{ - */ +define('ADVAGG_CSS_COMPRESSOR_FILE_SETTINGS', -1); /** * Implements hook_menu(). @@ -61,15 +47,6 @@ function advagg_css_compress_menu() { return $items; } -/** - * @} End of "addtogroup hooks". - */ - -/** - * @addtogroup advagg_hooks - * @{ - */ - /** * Implements hook_advagg_current_hooks_hash_array_alter(). */ @@ -109,123 +86,3 @@ function advagg_css_compress_advagg_modify_css_pre_render_alter(&$children, &$el unset($values); } } - -/** - * @} End of "addtogroup advagg_hooks". - */ - -/** - * @addtogroup 3rd_party_hooks - * @{ - */ - -/** - * Implements hook_libraries_info(). - */ -function advagg_css_compress_libraries_info() { - $libraries['YUI-CSS-compressor-PHP-port'] = array( - // Only used in administrative UI of Libraries API. - 'name' => 'YUI CSS compressor PHP port', - 'vendor url' => 'https://github.com/tubalmartin/YUI-CSS-compressor-PHP-port', - 'download url' => 'https://github.com/tubalmartin/YUI-CSS-compressor-PHP-port/archive/master.zip', - 'version callback' => 'advagg_css_compress_libraries_get_version', - 'version arguments' => array( - 'file' => 'README.md', - 'pattern' => '/###\s+v([0-9a-zA-Z\.-]+)/', - 'lines' => 1000, - 'cols' => 20, - 'default_version' => '2.4.8', - ), - 'local version' => '2.4.8-p10', - 'remote' => array( - 'callback' => 'advagg_get_github_version_txt', - 'url' => 'https://cdn.jsdelivr.net/gh/tubalmartin/YUI-CSS-compressor-PHP-port@master/README.md', - ), - 'versions' => array( - '2' => array( - 'files' => array( - 'php' => array( - 'cssmin.php', - 'data/hex-to-named-color-map.php', - 'data/named-to-hex-color-map.php', - ), - ), - ), - '3' => array( - 'files' => array( - 'php' => array( - 'src/Minifier.php', - 'src/Utils.php', - 'src/Colors.php', - 'src/data/hex-to-named-color-map.php', - 'src/data/named-to-hex-color-map.php', - ), - ), - ), - '4' => array( - 'files' => array( - 'php' => array( - 'src/Minifier.php', - 'src/Utils.php', - 'src/Colors.php', - ), - ), - ), - ), - ); - - return $libraries; -} - -/** - * @} End of "addtogroup 3rd_party_hooks". - */ - -/** - * Try libraries_get_version(), on failure use the passed in default_version. - * - * @param array $library - * An associative array containing all information about the library. - * @param array $options - * An associative array containing options for the version parser. - * - * @return string - * Version number. - */ -function advagg_css_compress_libraries_get_version(array $library, array $options) { - $return = libraries_get_version($library, $options); - if (empty($return) && !empty($options['default_version'])) { - $file = DRUPAL_ROOT . '/' . $library['library path'] . '/' . $options['file']; - if (is_readable($file)) { - return $options['default_version']; - } - } - return $return; -} - -/** - * Generate the js compress configuration. - * - * @return array - * Array($options, $description, $compressors, $functions). - */ -function advagg_css_compress_configuration() { - $description = ''; - $options = array( - -1 => t('Disable Core'), - 0 => t('Core'), - 2 => t('YUI'), - ); - - $compressors = array(); - $functions = array( - 2 => 'advagg_css_compress_yui_cssmin', - ); - - // Allow for other modules to alter this list. - $options_desc = array($options, $description); - drupal_alter('advagg_css_compress_configuration', $options_desc, $compressors, $functions); - list($options, $description) = $options_desc; - - return array($options, $description, $compressors, $functions); -} 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 88df451301..c0c9bc378d 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 @@ -15,7 +15,7 @@ // @ignore :file /*! - * cssmin.php + * cssmin.php v2.4.8-4 * Author: Tubal Martin - http://tubalmartin.me/ * Repo: https://github.com/tubalmartin/YUI-CSS-compressor-PHP-port * @@ -37,137 +37,130 @@ class CSSmin { const NL = '___YUICSSMIN_PRESERVED_NL___'; - const CLASSCOLON = '___YUICSSMIN_PSEUDOCLASSCOLON___'; - const QUERY_FRACTION = '___YUICSSMIN_QUERY_FRACTION___'; - const TOKEN = '___YUICSSMIN_PRESERVED_TOKEN_'; const COMMENT = '___YUICSSMIN_PRESERVE_CANDIDATE_COMMENT_'; - const AT_RULE_BLOCK = '___YUICSSMIN_PRESERVE_AT_RULE_BLOCK_'; + const CLASSCOLON = '___YUICSSMIN_PSEUDOCLASSCOLON___'; + const QUERY_FRACTION = '___YUICSSMIN_QUERY_FRACTION___'; private $comments; - private $atRuleBlocks; - private $preservedTokens; - private $chunkLength = 5000; - private $minChunkLength = 100; - private $memoryLimit; - private $maxExecutionTime = 60; // 1 min - private $pcreBacktrackLimit; - private $pcreRecursionLimit; - private $raisePhpLimits; - - private $unitsGroupRegex = '(?:ch|cm|em|ex|gd|in|mm|px|pt|pc|q|rem|vh|vmax|vmin|vw|%)'; - private $numRegex; + private $preserved_tokens; + private $memory_limit; + private $max_execution_time; + private $pcre_backtrack_limit; + private $pcre_recursion_limit; + private $raise_php_limits; /** - * @param bool|int $raisePhpLimits If true, PHP settings will be raised if needed + * @param bool|int $raise_php_limits + * If true, PHP settings will be raised if needed */ - public function __construct($raisePhpLimits = true) + public function __construct($raise_php_limits = TRUE) { - $this->memoryLimit = 128 * 1048576; // 128MB in bytes - $this->pcreBacktrackLimit = 1000 * 1000; - $this->pcreRecursionLimit = 500 * 1000; - - $this->raisePhpLimits = (bool) $raisePhpLimits; + // Set suggested PHP limits + $this->memory_limit = 128 * 1048576; // 128MB in bytes + $this->max_execution_time = 60; // 1 min + $this->pcre_backtrack_limit = 1000 * 1000; + $this->pcre_recursion_limit = 500 * 1000; - $this->numRegex = '(?:\+|-)?\d*\.?\d+' . $this->unitsGroupRegex .'?'; + $this->raise_php_limits = (bool) $raise_php_limits; } /** - * Minifies a string of CSS + * Minify a string of CSS * @param string $css - * @param int|bool $linebreakPos + * @param int|bool $linebreak_pos * @return string */ - public function run($css = '', $linebreakPos = false) + public function run($css = '', $linebreak_pos = FALSE) { if (empty($css)) { return ''; } - if ($this->raisePhpLimits) { - $this->doRaisePhpLimits(); + if ($this->raise_php_limits) { + $this->do_raise_php_limits(); } $this->comments = array(); - $this->atRuleBlocks = array(); - $this->preservedTokens = array(); + $this->preserved_tokens = array(); - // process data urls - $css = $this->processDataUrls($css); + $start_index = 0; + $length = strlen($css); - // process comments - $css = preg_replace_callback('/(?<!\\\\)\/\*(.*?)\*(?<!\\\\)\//Ss', array($this, 'processComments'), $css); + $css = $this->extract_data_urls($css); - // process strings so their content doesn't get accidentally minified - $css = preg_replace_callback( - '/(?:"(?:[^\\\\"]|\\\\.|\\\\)*")|'."(?:'(?:[^\\\\']|\\\\.|\\\\)*')/S", - array($this, 'processStrings'), - $css - ); + // collect all comment blocks... + while (($start_index = $this->index_of($css, '/*', $start_index)) >= 0) { + $end_index = $this->index_of($css, '*/', $start_index + 2); + if ($end_index < 0) { + $end_index = $length; + } + $comment_found = $this->str_slice($css, $start_index + 2, $end_index); + $this->comments[] = $comment_found; + $comment_preserve_string = self::COMMENT . (count($this->comments) - 1) . '___'; + $css = $this->str_slice($css, 0, $start_index + 2) . $comment_preserve_string . $this->str_slice($css, $end_index); + // Set correct start_index: Fixes issue #2528130 + $start_index = $end_index + 2 + strlen($comment_preserve_string) - strlen($comment_found); + } - // Safe chunking: process at rule blocks so after chunking nothing gets stripped out - $css = preg_replace_callback( - '/@(?:document|(?:-(?:atsc|khtml|moz|ms|o|wap|webkit)-)?keyframes|media|supports).+?\}\s*\}/si', - array($this, 'processAtRuleBlocks'), - $css - ); + // preserve strings so their content doesn't get accidentally minified + $css = preg_replace_callback('/(?:"(?:[^\\\\"]|\\\\.|\\\\)*")|'."(?:'(?:[^\\\\']|\\\\.|\\\\)*')/S", array($this, 'replace_string'), $css); - // Let's divide css code in chunks of {$this->chunkLength} chars aprox. + // Let's divide css code in chunks of 5.000 chars aprox. // Reason: PHP's PCRE functions like preg_replace have a "backtrack limit" // of 100.000 chars by default (php < 5.3.7) so if we're dealing with really // long strings and a (sub)pattern matches a number of chars greater than // the backtrack limit number (i.e. /(.*)/s) PCRE functions may fail silently // returning NULL and $css would be empty. $charset = ''; - $charsetRegexp = '/(@charset)( [^;]+;)/i'; - $cssChunks = array(); + $charset_regexp = '/(@charset)( [^;]+;)/i'; + $css_chunks = array(); + $css_chunk_length = 5000; // aprox size, not exact + $start_index = 0; + $i = $css_chunk_length; // save initial iterations $l = strlen($css); - // if the number of characters is <= {$this->chunkLength}, do not chunk - if ($l <= $this->chunkLength) { - $cssChunks[] = $css; + + // if the number of characters is 5000 or less, do not chunk + if ($l <= $css_chunk_length) { + $css_chunks[] = $css; } else { // chunk css code securely - for ($startIndex = 0, $i = $this->chunkLength; $i < $l; $i++) { - if ($css[$i - 1] === '}' && $i - $startIndex >= $this->chunkLength) { - $cssChunks[] = $this->strSlice($css, $startIndex, $i); - $startIndex = $i; - // Move forward saving iterations when possible! - if ($startIndex + $this->chunkLength < $l) { - $i += $this->chunkLength; + while ($i < $l) { + $i += 50; // save iterations + if ($l - $start_index <= $css_chunk_length || $i >= $l) { + $css_chunks[] = $this->str_slice($css, $start_index); + break; + } + if ($css[$i - 1] === '}' && $i - $start_index > $css_chunk_length) { + // If there are two ending curly braces }} separated or not by spaces, + // join them in the same chunk (i.e. @media blocks) + $next_chunk = substr($css, $i); + if (preg_match('/^\s*\}/', $next_chunk)) { + $i = $i + $this->index_of($next_chunk, '}') + 1; } + + $css_chunks[] = $this->str_slice($css, $start_index, $i); + $start_index = $i; } } - - // Final chunk - $cssChunks[] = $this->strSlice($css, $startIndex); } // Minify each chunk - for ($i = 0, $n = count($cssChunks); $i < $n; $i++) { - $cssChunks[$i] = $this->minify($cssChunks[$i], $linebreakPos); + for ($i = 0, $n = count($css_chunks); $i < $n; $i++) { + $css_chunks[$i] = $this->minify($css_chunks[$i], $linebreak_pos); // Keep the first @charset at-rule found - if (empty($charset) && preg_match($charsetRegexp, $cssChunks[$i], $matches)) { + if (empty($charset) && preg_match($charset_regexp, $css_chunks[$i], $matches)) { $charset = strtolower($matches[1]) . $matches[2]; } // Delete all @charset at-rules - $cssChunks[$i] = preg_replace($charsetRegexp, '', $cssChunks[$i]); + $css_chunks[$i] = preg_replace($charset_regexp, '', $css_chunks[$i]); } // Update the first chunk and push the charset to the top of the file. - $cssChunks[0] = $charset . $cssChunks[0]; + $css_chunks[0] = $charset . $css_chunks[0]; - return trim(implode('', $cssChunks)); - } - - /** - * Sets the approximate number of characters to use when splitting a string in chunks. - * @param int $length - */ - public function set_chunk_length($length) - { - $length = (int) $length; - $this->chunkLength = $length < $this->minChunkLength ? $this->minChunkLength : $length; + return implode('', $css_chunks); } /** @@ -176,7 +169,7 @@ class CSSmin */ public function set_memory_limit($limit) { - $this->memoryLimit = $this->normalizeInt($limit); + $this->memory_limit = $this->normalize_int($limit); } /** @@ -185,7 +178,7 @@ class CSSmin */ public function set_max_execution_time($seconds) { - $this->maxExecutionTime = (int) $seconds; + $this->max_execution_time = (int) $seconds; } /** @@ -194,7 +187,7 @@ class CSSmin */ public function set_pcre_backtrack_limit($limit) { - $this->pcreBacktrackLimit = (int) $limit; + $this->pcre_backtrack_limit = (int) $limit; } /** @@ -203,272 +196,151 @@ class CSSmin */ public function set_pcre_recursion_limit($limit) { - $this->pcreRecursionLimit = (int) $limit; + $this->pcre_recursion_limit = (int) $limit; } /** - * Tries to configure PHP to use at least the suggested minimum settings - * @return void + * Try to configure PHP to use at least the suggested minimum settings */ - private function doRaisePhpLimits() + private function do_raise_php_limits() { - $phpLimits = array( - 'memory_limit' => $this->memoryLimit, - 'max_execution_time' => $this->maxExecutionTime, - 'pcre.backtrack_limit' => $this->pcreBacktrackLimit, - 'pcre.recursion_limit' => $this->pcreRecursionLimit + $php_limits = array( + 'memory_limit' => $this->memory_limit, + 'max_execution_time' => $this->max_execution_time, + 'pcre.backtrack_limit' => $this->pcre_backtrack_limit, + 'pcre.recursion_limit' => $this->pcre_recursion_limit ); // If current settings are higher respect them. - foreach ($phpLimits as $name => $suggested) { - $current = $this->normalizeInt(ini_get($name)); - - if ($current > $suggested) { - continue; - } - - // memoryLimit exception: allow -1 for "no memory limit". - if ($name === 'memory_limit' && $current === -1) { - continue; + foreach ($php_limits as $name => $suggested) { + $current = $this->normalize_int(ini_get($name)); + // memory_limit exception: allow -1 for "no memory limit". + if ($current > -1 && ($suggested == -1 || $current < $suggested)) { + ini_set($name, $suggested); } - - // maxExecutionTime exception: allow 0 for "no memory limit". - if ($name === 'max_execution_time' && $current === 0) { - continue; - } - - ini_set($name, $suggested); } } /** - * Registers a preserved token - * @param $token - * @return string The token ID string - */ - private function registerPreservedToken($token) - { - $this->preservedTokens[] = $token; - return self::TOKEN . (count($this->preservedTokens) - 1) .'___'; - } - - /** - * Gets the regular expression to match the specified token ID string - * @param $id - * @return string - */ - private function getPreservedTokenPlaceholderRegexById($id) - { - return '/'. self::TOKEN . $id .'___/'; - } - - /** - * Registers a candidate comment token - * @param $comment - * @return string The comment token ID string - */ - private function registerComment($comment) - { - $this->comments[] = $comment; - return '/*'. self::COMMENT . (count($this->comments) - 1) .'___*/'; - } - - /** - * Gets the candidate comment token ID string for the specified comment token ID - * @param $id - * @return string - */ - private function getCommentPlaceholderById($id) - { - return self::COMMENT . $id .'___'; - } - - /** - * Gets the regular expression to match the specified comment token ID string - * @param $id - * @return string - */ - private function getCommentPlaceholderRegexById($id) - { - return '/'. $this->getCommentPlaceholderById($id) .'/'; - } - - /** - * Registers an at rule block token - * @param $block - * @return string The comment token ID string - */ - private function registerAtRuleBlock($block) - { - $this->atRuleBlocks[] = $block; - return self::AT_RULE_BLOCK . (count($this->atRuleBlocks) - 1) .'___'; - } - - /** - * Gets the regular expression to match the specified at rule block token ID string - * @param $id - * @return string - */ - private function getAtRuleBlockPlaceholderRegexById($id) - { - return '/'. self::AT_RULE_BLOCK . $id .'___/'; - } - - /** - * Minifies the given input CSS string + * Does bulk of the minification * @param string $css - * @param int|bool $linebreakPos + * @param int|bool $linebreak_pos * @return string */ - private function minify($css, $linebreakPos) + private function minify($css, $linebreak_pos) { - // Restore preserved at rule blocks - for ($i = 0, $max = count($this->atRuleBlocks); $i < $max; $i++) { - $css = preg_replace( - $this->getAtRuleBlockPlaceholderRegexById($i), - $this->escapeReplacementString($this->atRuleBlocks[$i]), - $css, - 1 - ); - } - // strings are safe, now wrestle the comments for ($i = 0, $max = count($this->comments); $i < $max; $i++) { - $comment = $this->comments[$i]; - $commentPlaceholder = $this->getCommentPlaceholderById($i); - $commentPlaceholderRegex = $this->getCommentPlaceholderRegexById($i); + + $token = $this->comments[$i]; + $placeholder = '/' . self::COMMENT . $i . '___/'; // ! in the first position of the comment means preserve // so push to the preserved tokens keeping the ! - if (preg_match('/^!/', $comment)) { - $preservedTokenPlaceholder = $this->registerPreservedToken($comment); - $css = preg_replace($commentPlaceholderRegex, $preservedTokenPlaceholder, $css, 1); + if (substr($token, 0, 1) === '!') { + $this->preserved_tokens[] = $token; + $token_tring = self::TOKEN . (count($this->preserved_tokens) - 1) . '___'; + $css = preg_replace($placeholder, $token_tring, $css, 1); // Preserve new lines for /*! important comments - $css = preg_replace('/\R+\s*(\/\*'. $preservedTokenPlaceholder .')/', self::NL.'$1', $css); - $css = preg_replace('/('. $preservedTokenPlaceholder .'\*\/)\s*\R+/', '$1'.self::NL, $css); + $css = preg_replace('/\s*[\n\r\f]+\s*(\/\*'. $token_tring .')/S', self::NL.'$1', $css); + $css = preg_replace('/('. $token_tring .'\*\/)\s*[\n\r\f]+\s*/', '$1'.self::NL, $css); continue; } // \ in the last position looks like hack for Mac/IE5 // shorten that to /*\*/ and the next one to /**/ - if (preg_match('/\\\\$/', $comment)) { - $preservedTokenPlaceholder = $this->registerPreservedToken('\\'); - $css = preg_replace($commentPlaceholderRegex, $preservedTokenPlaceholder, $css, 1); + if (substr($token, (strlen($token) - 1), 1) === '\\') { + $this->preserved_tokens[] = '\\'; + $css = preg_replace($placeholder, self::TOKEN . (count($this->preserved_tokens) - 1) . '___', $css, 1); $i = $i + 1; // attn: advancing the loop - $preservedTokenPlaceholder = $this->registerPreservedToken(''); - $css = preg_replace($this->getCommentPlaceholderRegexById($i), $preservedTokenPlaceholder, $css, 1); + $this->preserved_tokens[] = ''; + $css = preg_replace('/' . self::COMMENT . $i . '___/', self::TOKEN . (count($this->preserved_tokens) - 1) . '___', $css, 1); continue; } // keep empty comments after child selectors (IE7 hack) // e.g. html >/**/ body - if (strlen($comment) === 0) { - $startIndex = $this->indexOf($css, $commentPlaceholder); - if ($startIndex > 2) { - if (substr($css, $startIndex - 3, 1) === '>') { - $preservedTokenPlaceholder = $this->registerPreservedToken(''); - $css = preg_replace($commentPlaceholderRegex, $preservedTokenPlaceholder, $css, 1); - continue; + if (strlen($token) === 0) { + $start_index = $this->index_of($css, $this->str_slice($placeholder, 1, -1)); + if ($start_index > 2) { + if (substr($css, $start_index - 3, 1) === '>') { + $this->preserved_tokens[] = ''; + $css = preg_replace($placeholder, self::TOKEN . (count($this->preserved_tokens) - 1) . '___', $css, 1); } } } // in all other cases kill the comment - $css = preg_replace('/\/\*' . $commentPlaceholder . '\*\//', '', $css, 1); + $css = preg_replace('/\/\*' . $this->str_slice($placeholder, 1, -1) . '\*\//', '', $css, 1); } + // Normalize all whitespace strings to single spaces. Easier to work with that way. $css = preg_replace('/\s+/', ' ', $css); - // Remove spaces before & after newlines - $css = preg_replace('/\s*'. self::NL .'\s*/', self::NL, $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, 'processOldIeSpecificMatrixDefinition'), - $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, 'processCalc'), $css); + $css = preg_replace_callback('/calc(\(((?:[^\(\)]+|(?1))*)\))/i', array($this, 'replace_calc'), $css); // Replace positive sign from numbers preceded by : or a white-space before the leading space is removed // +1.2em to 1.2em, +.8px to .8px, +2% to 2% - $css = preg_replace('/((?<!\\\\):|\s)\+(\.?\d+)/S', '$1$2', $css); + $css = preg_replace('/((?<!\\\\)\:|\s)\+(\.?\d+)/S', '$1$2', $css); // Remove leading zeros from integer and float numbers preceded by : or a white-space // 000.6 to .6, -0.8 to -.8, 0050 to 50, -01.05 to -1.05 - $css = preg_replace('/((?<!\\\\):|\s)(-?)0+(\.?\d+)/S', '$1$2$3', $css); + $css = preg_replace('/((?<!\\\\)\:|\s)(\-?)0+(\.?\d+)/S', '$1$2$3', $css); // Remove trailing zeros from float numbers preceded by : or a white-space // -6.0100em to -6.01em, .0100 to .01, 1.200px to 1.2px - $css = preg_replace('/((?<!\\\\):|\s)(-?)(\d?\.\d+?)0+([^\d])/S', '$1$2$3$4', $css); + $css = preg_replace('/((?<!\\\\)\:|\s)(\-?)(\d?\.\d+?)0+([^\d])/S', '$1$2$3$4', $css); // Remove trailing .0 -> -9.0 to -9 - $css = preg_replace('/((?<!\\\\):|\s)(-?\d+)\.0([^\d])/S', '$1$2$3', $css); + $css = preg_replace('/((?<!\\\\)\:|\s)(\-?\d+)\.0([^\d])/S', '$1$2$3', $css); // Replace 0 length numbers with 0 - $css = preg_replace('/((?<!\\\\):|\s)-?\.?0+([^\d])/S', '${1}0$2', $css); + $css = preg_replace('/((?<!\\\\)\:|\s)\-?\.?0+([^\d])/S', '${1}0$2', $css); // Remove the spaces before the things that should not have spaces before them. // But, be careful not to turn "p :link {...}" into "p:link{...}" // Swap out any pseudo-class colons with the token, and then swap back. - $css = preg_replace_callback('/(?:^|\})[^{]*\s+:/', array($this, 'processColon'), $css); + $css = preg_replace_callback('/(?:^|\})[^\{]*\s+\:/', array($this, 'replace_colon'), $css); // Remove spaces before the things that should not have spaces before them. - $css = preg_replace('/\s+([!{};:>+()\]~=,])/', '$1', $css); + $css = preg_replace('/\s+([\!\{\}\;\:\>\+\(\)\]\~\=,])/', '$1', $css); // Restore spaces for !important - $css = preg_replace('/!important/i', ' !important', $css); + $css = preg_replace('/\!important/i', ' !important', $css); // bring back the colon - $css = preg_replace('/'. self::CLASSCOLON .'/', ':', $css); + $css = preg_replace('/' . self::CLASSCOLON . '/', ':', $css); // retain space for special IE6 cases - $css = preg_replace_callback('/:first-(line|letter)(\{|,)/i', array($this, 'lowercasePseudoFirst'), $css); + $css = preg_replace_callback('/\:first\-(line|letter)(\{|,)/i', array($this, 'lowercase_pseudo_first'), $css); // no space after the end of a preserved comment $css = preg_replace('/\*\/ /', '*/', $css); // lowercase some popular @directives - $css = preg_replace_callback( - '/@(document|font-face|import|(?:-(?:atsc|khtml|moz|ms|o|wap|webkit)-)?keyframes|media|namespace|page|' . - 'supports|viewport)/i', - array($this, 'lowercaseDirectives'), - $css - ); + $css = preg_replace_callback('/@(font-face|import|(?:-(?:atsc|khtml|moz|ms|o|wap|webkit)-)?keyframe|media|page|namespace)/i', array($this, 'lowercase_directives'), $css); // lowercase some more common pseudo-elements - $css = preg_replace_callback( - '/:(active|after|before|checked|disabled|empty|enabled|first-(?:child|of-type)|focus|hover|' . - 'last-(?:child|of-type)|link|only-(?:child|of-type)|root|:selection|target|visited)/i', - array($this, 'lowercasePseudoElements'), - $css - ); + $css = preg_replace_callback('/:(active|after|before|checked|disabled|empty|enabled|first-(?:child|of-type)|focus|hover|last-(?:child|of-type)|link|only-(?:child|of-type)|root|:selection|target|visited)/i', array($this, 'lowercase_pseudo_elements'), $css); // lowercase some more common functions - $css = preg_replace_callback( - '/:(lang|not|nth-child|nth-last-child|nth-last-of-type|nth-of-type|(?:-(?:moz|webkit)-)?any)\(/i', - array($this, 'lowercaseCommonFunctions'), - $css - ); + $css = preg_replace_callback('/:(lang|not|nth-child|nth-last-child|nth-last-of-type|nth-of-type|(?:-(?:moz|webkit)-)?any)\(/i', array($this, 'lowercase_common_functions'), $css); // lower case some common function that can be values // NOTE: rgb() isn't useful as we replace with #hex later, as well as and() is already done for us - $css = preg_replace_callback( - '/([:,( ]\s*)(attr|color-stop|from|rgba|to|url|-webkit-gradient|' . - '(?:-(?:atsc|khtml|moz|ms|o|wap|webkit)-)?(?:calc|max|min|(?:repeating-)?(?:linear|radial)-gradient))/iS', - array($this, 'lowercaseCommonFunctionsValues'), - $css - ); + $css = preg_replace_callback('/([:,\( ]\s*)(attr|color-stop|from|rgba|to|url|(?:-(?:atsc|khtml|moz|ms|o|wap|webkit)-)?(?:calc|max|min|(?:repeating-)?(?:linear|radial)-gradient)|-webkit-gradient)/iS', array($this, 'lowercase_common_functions_values'), $css); // Put the space back in some cases, to support stuff like // @media screen and (-webkit-min-device-pixel-ratio:0){ - $css = preg_replace_callback('/(\s|\)\s)(and|not|or)\(/i', array($this, 'processAtRulesOperators'), $css); + $css = preg_replace('/\band\(/i', 'and (', $css); // Remove the spaces after the things that should not have spaces after them. - $css = preg_replace('/([!{}:;>+(\[~=,])\s+/S', '$1', $css); + $css = preg_replace('/([\!\{\}\:;\>\+\(\[\~\=,])\s+/S', '$1', $css); // remove unnecessary semicolons $css = preg_replace('/;+\}/', '}', $css); @@ -476,378 +348,160 @@ class CSSmin // Fix for issue: #2528146 // Restore semicolon if the last property is prefixed with a `*` (lte IE7 hack) // to avoid issues on Symbian S60 3.x browsers. - $css = preg_replace('/(\*[a-z0-9\-]+\s*:[^;}]+)(\})/', '$1;$2', $css); + $css = preg_replace('/(\*[a-z0-9\-]+\s*\:[^;\}]+)(\})/', '$1;$2', $css); - // Shorten zero values for safe properties only - $css = $this->shortenZeroValues($css); + // Replace 0 <length> and 0 <percentage> values with 0. + // <length> data type: https://developer.mozilla.org/en-US/docs/Web/CSS/length + // <percentage> data type: https://developer.mozilla.org/en-US/docs/Web/CSS/percentage + $css = preg_replace('/([^\\\\]\:|\s)0(?:em|ex|ch|rem|vw|vh|vm|vmin|cm|mm|in|px|pt|pc|%)/iS', '${1}0', $css); - // Shorten font-weight values - $css = preg_replace('/(font-weight:)bold\b/i', '${1}700', $css); - $css = preg_replace('/(font-weight:)normal\b/i', '${1}400', $css); + // 0% step in a keyframe? restore the % unit + $css = preg_replace_callback('/(@[a-z\-]*?keyframes[^\{]+\{)(.*?)(\}\})/iS', array($this, 'replace_keyframe_zero'), $css); - // Shorten suitable shorthand properties with repeated non-zero values - $css = preg_replace( - '/(margin|padding):('.$this->numRegex.') ('.$this->numRegex.') (?:\2) (?:\3)(;|\}| !)/i', - '$1:$2 $3$4', - $css - ); - $css = preg_replace( - '/(margin|padding):('.$this->numRegex.') ('.$this->numRegex.') ('.$this->numRegex.') (?:\3)(;|\}| !)/i', - '$1:$2 $3 $4$5', - $css - ); + // Replace 0 0; or 0 0 0; or 0 0 0 0; with 0. + $css = preg_replace('/\:0(?: 0){1,3}(;|\}| \!)/', ':0$1', $css); + + // Fix for issue: #2528142 + // Replace text-shadow:0; with text-shadow:0 0 0; + $css = preg_replace('/(text-shadow\:0)(;|\}| \!)/i', '$1 0 0$2', $css); + + // Replace background-position:0; with background-position:0 0; + // same for transform-origin + // 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); // 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) // This makes it more likely that it'll get further compressed in the next step. - $css = preg_replace_callback('/rgb\s*\(\s*([0-9,\s\-.%]+)\s*\)(.{1})/i', array($this, 'rgbToHex'), $css); - $css = preg_replace_callback('/hsl\s*\(\s*([0-9,\s\-.%]+)\s*\)(.{1})/i', array($this, 'hslToHex'), $css); + $css = preg_replace_callback('/rgb\s*\(\s*([0-9,\s\-\.\%]+)\s*\)(.{1})/i', array($this, 'rgb_to_hex'), $css); + $css = preg_replace_callback('/hsl\s*\(\s*([0-9,\s\-\.\%]+)\s*\)(.{1})/i', array($this, 'hsl_to_hex'), $css); - // Shorten colors from #AABBCC to #ABC or shorter color name. - $css = $this->shortenHexColors($css); + // Shorten colors from #AABBCC to #ABC or short color name. + $css = $this->compress_hex_colors($css); - // Shorten long named colors: white -> #fff. - $css = $this->shortenNamedColors($css); + // border: none to border:0, outline: none to outline:0 + $css = preg_replace('/(border\-?(?:top|right|bottom|left|)|outline)\:none(;|\}| \!)/iS', '$1:0$2', $css); // shorter opacity IE filter - $css = preg_replace('/progid:DXImageTransform\.Microsoft\.Alpha\(Opacity=/i', 'alpha(opacity=', $css); + $css = preg_replace('/progid\:DXImageTransform\.Microsoft\.Alpha\(Opacity\=/i', 'alpha(opacity=', $css); // Find a fraction that is used for Opera's -o-device-pixel-ratio query // Add token to add the "\" back in later $css = preg_replace('/\(([a-z\-]+):([0-9]+)\/([0-9]+)\)/i', '($1:$2'. self::QUERY_FRACTION .'$3)', $css); - // Patch new lines to avoid being removed when followed by empty rules cases - $css = preg_replace('/'. self::NL .'/', self::NL .'}', $css); - // Remove empty rules. - $css = preg_replace('/[^{};\/]+\{\}/S', '', $css); - - // Restore new lines for /*! important comments - $css = preg_replace('/'. self::NL .'}/', "\n", $css); + $css = preg_replace('/[^\};\{\/]+\{\}/S', '', $css); // Add "/" back to fix Opera -o-device-pixel-ratio query $css = preg_replace('/'. self::QUERY_FRACTION .'/', '/', $css); - // Replace multiple semi-colons in a row by a single one + // Replace multiple semi-colons in a row by a single one // See SF bug #1980989 $css = preg_replace('/;;+/', ';', $css); + // Restore new lines for /*! important comments + $css = preg_replace('/'. self::NL .'/', "\n", $css); + // Lowercase all uppercase properties - $css = preg_replace_callback('/(\{|;)([A-Z\-]+)(:)/', array($this, 'lowercaseProperties'), $css); + $css = preg_replace_callback('/(\{|\;)([A-Z\-]+)(\:)/', array($this, 'lowercase_properties'), $css); // Some source control tools don't like it when files containing lines longer // than, say 8000 characters, are checked in. The linebreak option is used in // that case to split long lines after a specific column. - if ($linebreakPos !== false && (int) $linebreakPos >= 0) { - $linebreakPos = (int) $linebreakPos; - for ($startIndex = $i = 1, $l = strlen($css); $i < $l; $i++) { - if ($css[$i - 1] === '}' && $i - $startIndex > $linebreakPos) { - $css = $this->strSlice($css, 0, $i) . "\n" . $this->strSlice($css, $i); - $l = strlen($css); - $startIndex = $i; + if ($linebreak_pos !== FALSE && (int) $linebreak_pos >= 0) { + $linebreak_pos = (int) $linebreak_pos; + $start_index = $i = 0; + while ($i < strlen($css)) { + $i++; + if ($css[$i - 1] === '}' && $i - $start_index > $linebreak_pos) { + $css = $this->str_slice($css, 0, $i) . "\n" . $this->str_slice($css, $i); + $start_index = $i; } } } // restore preserved comments and strings in reverse order - for ($i = count($this->preservedTokens) - 1; $i >= 0; $i--) { - $css = preg_replace( - $this->getPreservedTokenPlaceholderRegexById($i), - $this->escapeReplacementString($this->preservedTokens[$i]), - $css, - 1 - ); + for ($i = count($this->preserved_tokens) - 1; $i >= 0; $i--) { + $css = preg_replace('/' . self::TOKEN . $i . '___/', $this->preserved_tokens[$i], $css, 1); } - // Trim the final string for any leading or trailing white space but respect newlines! - $css = preg_replace('/(^ | $)/', '', $css); - - return $css; + // Trim the final string (for any leading or trailing white spaces) + return trim($css); } /** - * Searches & replaces all data urls with tokens before we start compressing, - * to avoid performance issues running some of the subsequent regexes against large string chunks. + * Utility method to replace all data urls with tokens before we start + * compressing, to avoid performance issues running some of the subsequent + * regexes against large strings chunks. + * * @param string $css * @return string */ - private function processDataUrls($css) + private function extract_data_urls($css) { // Leave data urls alone to increase parse performance. - $maxIndex = strlen($css) - 1; - $appenIndex = $index = $lastIndex = $offset = 0; + $max_index = strlen($css) - 1; + $append_index = $index = $last_index = $offset = 0; $sb = array(); - $pattern = '/url\(\s*(["\']?)data:/i'; + $pattern = '/url\(\s*(["\']?)data\:/i'; // Since we need to account for non-base64 data urls, we need to handle // ' and ) being part of the data string. Hence switching to indexOf, // to determine whether or not we have matching string terminators and // handling sb appends directly, instead of using matcher.append* methods. + while (preg_match($pattern, $css, $m, 0, $offset)) { - $index = $this->indexOf($css, $m[0], $offset); - $lastIndex = $index + strlen($m[0]); - $startIndex = $index + 4; // "url(".length() - $endIndex = $lastIndex - 1; + $index = $this->index_of($css, $m[0], $offset); + $last_index = $index + strlen($m[0]); + $start_index = $index + 4; // "url(".length() + $end_index = $last_index - 1; $terminator = $m[1]; // ', " or empty (not quoted) - $terminatorFound = false; + $found_terminator = FALSE; if (strlen($terminator) === 0) { $terminator = ')'; } - while ($terminatorFound === false && $endIndex+1 <= $maxIndex) { - $endIndex = $this->indexOf($css, $terminator, $endIndex + 1); + while ($found_terminator === FALSE && $end_index+1 <= $max_index) { + $end_index = $this->index_of($css, $terminator, $end_index + 1); + // endIndex == 0 doesn't really apply here - if ($endIndex > 0 && substr($css, $endIndex - 1, 1) !== '\\') { - $terminatorFound = true; - if (')' !== $terminator) { - $endIndex = $this->indexOf($css, ')', $endIndex); + if ($end_index > 0 && substr($css, $end_index - 1, 1) !== '\\') { + $found_terminator = TRUE; + if (')' != $terminator) { + $end_index = $this->index_of($css, ')', $end_index); } } } // Enough searching, start moving stuff over to the buffer - $sb[] = $this->strSlice($css, $appenIndex, $index); - - if ($terminatorFound) { - $token = $this->strSlice($css, $startIndex, $endIndex); - // Remove all spaces only for base64 encoded URLs. - $token = preg_replace_callback( - '/.+base64,.+/s', - array($this, 'removeSpacesFromDataUrls'), - trim($token) - ); - $preservedTokenPlaceholder = $this->registerPreservedToken($token); - $sb[] = 'url('. $preservedTokenPlaceholder .')'; - $appenIndex = $endIndex + 1; + $sb[] = $this->str_slice($css, $append_index, $index); + + if ($found_terminator) { + $token = $this->str_slice($css, $start_index, $end_index); + $token = preg_replace('/\s+/', '', $token); + $this->preserved_tokens[] = $token; + + $preserver = 'url(' . self::TOKEN . (count($this->preserved_tokens) - 1) . '___)'; + $sb[] = $preserver; + + $append_index = $end_index + 1; } else { // No end terminator found, re-add the whole match. Should we throw/warn here? - $sb[] = $this->strSlice($css, $index, $lastIndex); - $appenIndex = $lastIndex; + $sb[] = $this->str_slice($css, $index, $last_index); + $append_index = $last_index; } - $offset = $lastIndex; + $offset = $last_index; } - $sb[] = $this->strSlice($css, $appenIndex); + $sb[] = $this->str_slice($css, $append_index); return implode('', $sb); } /** - * Shortens all zero values for a set of safe properties - * e.g. padding: 0px 1px; -> padding:0 1px - * e.g. padding: 0px 0rem 0em 0.0pc; -> padding:0 - * @param string $css - * @return string - */ - private function shortenZeroValues($css) - { - $unitsGroupReg = $this->unitsGroupRegex; - $numOrPosReg = '('. $this->numRegex .'|top|left|bottom|right|center)'; - $oneZeroSafeProperties = array( - '(?:line-)?height', - '(?:(?:min|max)-)?width', - 'top', - 'left', - 'background-position', - 'bottom', - 'right', - 'border(?:-(?:top|left|bottom|right))?(?:-width)?', - 'border-(?:(?:top|bottom)-(?:left|right)-)?radius', - 'column-(?:gap|width)', - 'margin(?:-(?:top|left|bottom|right))?', - 'outline-width', - 'padding(?:-(?:top|left|bottom|right))?' - ); - $nZeroSafeProperties = array( - 'margin', - 'padding', - 'background-position' - ); - - $regStart = '/(;|\{)'; - $regEnd = '/i'; - - // First zero regex start - $oneZeroRegStart = $regStart .'('. implode('|', $oneZeroSafeProperties) .'):'; - - // Multiple zeros regex start - $nZerosRegStart = $regStart .'('. implode('|', $nZeroSafeProperties) .'):'; - - $css = preg_replace( - array( - $oneZeroRegStart .'0'. $unitsGroupReg . $regEnd, - $nZerosRegStart . $numOrPosReg .' 0'. $unitsGroupReg . $regEnd, - $nZerosRegStart . $numOrPosReg .' '. $numOrPosReg .' 0'. $unitsGroupReg . $regEnd, - $nZerosRegStart . $numOrPosReg .' '. $numOrPosReg .' '. $numOrPosReg .' 0'. $unitsGroupReg . $regEnd - ), - array( - '$1$2:0', - '$1$2:$3 0', - '$1$2:$3 $4 0', - '$1$2:$3 $4 $5 0' - ), - $css - ); - - // Remove background-position - array_pop($nZeroSafeProperties); - - // Replace 0 0; or 0 0 0; or 0 0 0 0; with 0 for safe properties only. - $css = preg_replace( - '/('. implode('|', $nZeroSafeProperties) .'):0(?: 0){1,3}(;|\}| !)'. $regEnd, - '$1:0$2', - $css - ); - - // Replace 0 0 0; or 0 0 0 0; with 0 0 for background-position property. - $css = preg_replace('/(background-position):0(?: 0){2,3}(;|\}| !)'. $regEnd, '$1:0 0$2', $css); - - return $css; - } - - /** - * Shortens all named colors with a shorter HEX counterpart for a set of safe properties - * e.g. white -> #fff - * @param string $css - * @return string - */ - private function shortenNamedColors($css) - { - $patterns = array(); - $replacements = array(); - $longNamedColors = array( - 'aliceblue' => '#f0f8ff', - 'antiquewhite' => '#faebd7', - 'aquamarine' => '#7fffd4', - 'black' => '#000', - 'blanchedalmond' => '#ffebcd', - 'blueviolet' => '#8a2be2', - 'burlywood' => '#deb887', - 'cadetblue' => '#5f9ea0', - 'chartreuse' => '#7fff00', - 'chocolate' => '#d2691e', - 'cornflowerblue' => '#6495ed', - 'cornsilk' => '#fff8dc', - 'darkblue' => '#00008b', - 'darkcyan' => '#008b8b', - 'darkgoldenrod' => '#b8860b', - 'darkgray' => '#a9a9a9', - 'darkgreen' => '#006400', - 'darkgrey' => '#a9a9a9', - 'darkkhaki' => '#bdb76b', - 'darkmagenta' => '#8b008b', - 'darkolivegreen' => '#556b2f', - 'darkorange' => '#ff8c00', - 'darkorchid' => '#9932cc', - 'darksalmon' => '#e9967a', - 'darkseagreen' => '#8fbc8f', - 'darkslateblue' => '#483d8b', - 'darkslategray' => '#2f4f4f', - 'darkslategrey' => '#2f4f4f', - 'darkturquoise' => '#00ced1', - 'darkviolet' => '#9400d3', - 'deeppink' => '#ff1493', - 'deepskyblue' => '#00bfff', - 'dodgerblue' => '#1e90ff', - 'firebrick' => '#b22222', - 'floralwhite' => '#fffaf0', - 'forestgreen' => '#228b22', - 'fuchsia' => '#f0f', - 'gainsboro' => '#dcdcdc', - 'ghostwhite' => '#f8f8ff', - 'goldenrod' => '#daa520', - 'greenyellow' => '#adff2f', - 'honeydew' => '#f0fff0', - 'indianred' => '#cd5c5c', - 'lavender' => '#e6e6fa', - 'lavenderblush' => '#fff0f5', - 'lawngreen' => '#7cfc00', - 'lemonchiffon' => '#fffacd', - 'lightblue' => '#add8e6', - 'lightcoral' => '#f08080', - 'lightcyan' => '#e0ffff', - 'lightgoldenrodyellow' => '#fafad2', - 'lightgray' => '#d3d3d3', - 'lightgreen' => '#90ee90', - 'lightgrey' => '#d3d3d3', - 'lightpink' => '#ffb6c1', - 'lightsalmon' => '#ffa07a', - 'lightseagreen' => '#20b2aa', - 'lightskyblue' => '#87cefa', - 'lightslategray' => '#778899', - 'lightslategrey' => '#778899', - 'lightsteelblue' => '#b0c4de', - 'lightyellow' => '#ffffe0', - 'limegreen' => '#32cd32', - 'mediumaquamarine' => '#66cdaa', - 'mediumblue' => '#0000cd', - 'mediumorchid' => '#ba55d3', - 'mediumpurple' => '#9370db', - 'mediumseagreen' => '#3cb371', - 'mediumslateblue' => '#7b68ee', - 'mediumspringgreen' => '#00fa9a', - 'mediumturquoise' => '#48d1cc', - 'mediumvioletred' => '#c71585', - 'midnightblue' => '#191970', - 'mintcream' => '#f5fffa', - 'mistyrose' => '#ffe4e1', - 'moccasin' => '#ffe4b5', - 'navajowhite' => '#ffdead', - 'olivedrab' => '#6b8e23', - 'orangered' => '#ff4500', - 'palegoldenrod' => '#eee8aa', - 'palegreen' => '#98fb98', - 'paleturquoise' => '#afeeee', - 'palevioletred' => '#db7093', - 'papayawhip' => '#ffefd5', - 'peachpuff' => '#ffdab9', - 'powderblue' => '#b0e0e6', - 'rebeccapurple' => '#663399', - 'rosybrown' => '#bc8f8f', - 'royalblue' => '#4169e1', - 'saddlebrown' => '#8b4513', - 'sandybrown' => '#f4a460', - 'seagreen' => '#2e8b57', - 'seashell' => '#fff5ee', - 'slateblue' => '#6a5acd', - 'slategray' => '#708090', - 'slategrey' => '#708090', - 'springgreen' => '#00ff7f', - 'steelblue' => '#4682b4', - 'turquoise' => '#40e0d0', - 'white' => '#fff', - 'whitesmoke' => '#f5f5f5', - 'yellow' => '#ff0', - 'yellowgreen' => '#9acd32' - ); - $propertiesWithColors = array( - 'color', - 'background(?:-color)?', - 'border(?:-(?:top|right|bottom|left|color)(?:-color)?)?', - 'outline(?:-color)?', - '(?:text|box)-shadow' - ); - - $regStart = '/(;|\{)('. implode('|', $propertiesWithColors) .'):([^;}]*)\b'; - $regEnd = '\b/iS'; - - foreach ($longNamedColors as $colorName => $colorCode) { - $patterns[] = $regStart . $colorName . $regEnd; - $replacements[] = '$1$2:$3'. $colorCode; - } - - // Run at least 4 times to cover most cases (same color used several times for the same property) - for ($i = 0; $i < 4; $i++) { - $css = preg_replace($patterns, $replacements, $css); - } - - return $css; - } - - /** - * Compresses HEX color values of the form #AABBCC to #ABC or short color name. + * Utility method to compress hex color values of the form #AABBCC to #ABC or short color name. * * DOES NOT compress CSS ID selectors which match the above pattern (which would break things). * e.g. #AddressForm { ... } @@ -861,383 +515,239 @@ class CSSmin * @param string $css * @return string */ - private function shortenHexColors($css) + private function compress_hex_colors($css) { - // Look for hex colors inside { ... } (to avoid IDs) and - // which don't have a =, or a " in front of them (to avoid filters) - $pattern = - '/(=\s*?["\']?)?#([0-9a-f])([0-9a-f])([0-9a-f])([0-9a-f])([0-9a-f])([0-9a-f])(\}|[^0-9a-f{][^{]*?\})/iS'; - $_index = $index = $lastIndex = $offset = 0; - $longHexColors = array( - '#f0ffff' => 'azure', - '#f5f5dc' => 'beige', - '#ffe4c4' => 'bisque', - '#a52a2a' => 'brown', - '#ff7f50' => 'coral', - '#ffd700' => 'gold', + // Look for hex colors inside { ... } (to avoid IDs) and which don't have a =, or a " in front of them (to avoid filters) + $pattern = '/(\=\s*?["\']?)?#([0-9a-f])([0-9a-f])([0-9a-f])([0-9a-f])([0-9a-f])([0-9a-f])(\}|[^0-9a-f{][^{]*?\})/iS'; + $_index = $index = $last_index = $offset = 0; + $sb = array(); + // See: http://ajaxmin.codeplex.com/wikipage?title=CSS%20Colors + $short_safe = array( '#808080' => 'gray', '#008000' => 'green', - '#4b0082' => 'indigo', - '#fffff0' => 'ivory', - '#f0e68c' => 'khaki', - '#faf0e6' => 'linen', '#800000' => 'maroon', '#000080' => 'navy', - '#fdf5e6' => 'oldlace', '#808000' => 'olive', '#ffa500' => 'orange', - '#da70d6' => 'orchid', - '#cd853f' => 'peru', - '#ffc0cb' => 'pink', - '#dda0dd' => 'plum', '#800080' => 'purple', - '#f00' => 'red', - '#fa8072' => 'salmon', - '#a0522d' => 'sienna', '#c0c0c0' => 'silver', - '#fffafa' => 'snow', - '#d2b48c' => 'tan', '#008080' => 'teal', - '#ff6347' => 'tomato', - '#ee82ee' => 'violet', - '#f5deb3' => 'wheat' + '#f00' => 'red' ); - $sb = array(); while (preg_match($pattern, $css, $m, 0, $offset)) { - $index = $this->indexOf($css, $m[0], $offset); - $lastIndex = $index + strlen($m[0]); - $isFilter = $m[1] !== null && $m[1] !== ''; + $index = $this->index_of($css, $m[0], $offset); + $last_index = $index + strlen($m[0]); + $is_filter = $m[1] !== null && $m[1] !== ''; - $sb[] = $this->strSlice($css, $_index, $index); + $sb[] = $this->str_slice($css, $_index, $index); - if ($isFilter) { + if ($is_filter) { // Restore, maintain case, otherwise filter will break - $sb[] = $m[1] .'#'. $m[2] . $m[3] . $m[4] . $m[5] . $m[6] . $m[7]; + $sb[] = $m[1] . '#' . $m[2] . $m[3] . $m[4] . $m[5] . $m[6] . $m[7]; } else { if (strtolower($m[2]) == strtolower($m[3]) && strtolower($m[4]) == strtolower($m[5]) && strtolower($m[6]) == strtolower($m[7])) { // Compress. - $hex = '#'. strtolower($m[3] . $m[5] . $m[7]); + $hex = '#' . strtolower($m[3] . $m[5] . $m[7]); } else { // Non compressible color, restore but lower case. - $hex = '#'. strtolower($m[2] . $m[3] . $m[4] . $m[5] . $m[6] . $m[7]); + $hex = '#' . strtolower($m[2] . $m[3] . $m[4] . $m[5] . $m[6] . $m[7]); } - // replace Hex colors with shorter color names - $sb[] = array_key_exists($hex, $longHexColors) ? $longHexColors[$hex] : $hex; + // replace Hex colors to short safe color names + $sb[] = array_key_exists($hex, $short_safe) ? $short_safe[$hex] : $hex; } - $_index = $offset = $lastIndex - strlen($m[8]); + $_index = $offset = $last_index - strlen($m[8]); } - $sb[] = $this->strSlice($css, $_index); + $sb[] = $this->str_slice($css, $_index); return implode('', $sb); } - // --------------------------------------------------------------------------------------------- - // CALLBACKS - // --------------------------------------------------------------------------------------------- - - private function processComments($matches) - { - $match = !empty($matches[1]) ? $matches[1] : ''; - return $this->registerComment($match); - } + /* CALLBACKS + * --------------------------------------------------------------------------------------------- + */ - private function processStrings($matches) + private function replace_string($matches) { $match = $matches[0]; $quote = substr($match, 0, 1); - $match = $this->strSlice($match, 1, -1); + // Must use addcslashes in PHP to avoid parsing of backslashes + $match = addcslashes($this->str_slice($match, 1, -1), '\\'); // maybe the string contains a comment-like substring? // one, maybe more? put'em back then - if (($pos = strpos($match, self::COMMENT)) !== false) { + if (($pos = $this->index_of($match, self::COMMENT)) >= 0) { for ($i = 0, $max = count($this->comments); $i < $max; $i++) { - $match = preg_replace( - $this->getCommentPlaceholderRegexById($i), - $this->escapeReplacementString($this->comments[$i]), - $match, - 1 - ); + $match = preg_replace('/' . self::COMMENT . $i . '___/', $this->comments[$i], $match, 1); } } // minify alpha opacity in filter strings - $match = preg_replace('/progid:DXImageTransform\.Microsoft\.Alpha\(Opacity=/i', 'alpha(opacity=', $match); - - $preservedTokenPlaceholder = $this->registerPreservedToken($match); - return $quote . $preservedTokenPlaceholder . $quote; - } + $match = preg_replace('/progid\:DXImageTransform\.Microsoft\.Alpha\(Opacity\=/i', 'alpha(opacity=', $match); - private function processAtRuleBlocks($matches) - { - return $this->registerAtRuleBlock($matches[0]); + $this->preserved_tokens[] = $match; + return $quote . self::TOKEN . (count($this->preserved_tokens) - 1) . '___' . $quote; } - private function processCalc($matches) + private function replace_colon($matches) { - $token = preg_replace( - '/\)([+\-]{1})/', - ') $1', - preg_replace( - '/([+\-]{1})\(/', - '$1 (', - trim(preg_replace('/\s*([*\/(),])\s*/', '$1', $matches[2])) - ) - ); - $preservedTokenPlaceholder = $this->registerPreservedToken($token); - return 'calc('. $preservedTokenPlaceholder .')'; + return preg_replace('/\:/', self::CLASSCOLON, $matches[0]); } - private function processOldIeSpecificMatrixDefinition($matches) + private function replace_calc($matches) { - $preservedTokenPlaceholder = $this->registerPreservedToken($matches[1]); - return 'filter:progid:DXImageTransform.Microsoft.Matrix('. $preservedTokenPlaceholder .')'; + $this->preserved_tokens[] = trim(preg_replace('/\s*([\*\/\(\),])\s*/', '$1', $matches[2])); + return 'calc('. self::TOKEN . (count($this->preserved_tokens) - 1) . '___' . ')'; } - private function processColon($matches) - { - return preg_replace('/\:/', self::CLASSCOLON, $matches[0]); + 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 removeSpacesFromDataUrls($matches) + private function replace_keyframe_zero($matches) { - return preg_replace('/\s+/', '', $matches[0]); + return $matches[1] . preg_replace('/0(\{|,[^\)\{]+\{)/', '0%$1', $matches[2]) . $matches[3]; } - private function rgbToHex($matches) + private function rgb_to_hex($matches) { - $hexColors = array(); - $rgbColors = explode(',', $matches[1]); + // Support for percentage values rgb(100%, 0%, 45%); + if ($this->index_of($matches[1], '%') >= 0){ + $rgbcolors = explode(',', str_replace('%', '', $matches[1])); + for ($i = 0; $i < count($rgbcolors); $i++) { + $rgbcolors[$i] = $this->round_number(floatval($rgbcolors[$i]) * 2.55); + } + } else { + $rgbcolors = explode(',', $matches[1]); + } // Values outside the sRGB color space should be clipped (0-255) - for ($i = 0, $l = count($rgbColors); $i < $l; $i++) { - $hexColors[$i] = sprintf("%02x", $this->clampNumberSrgb($this->rgbPercentageToRgbInteger($rgbColors[$i]))); + for ($i = 0; $i < count($rgbcolors); $i++) { + $rgbcolors[$i] = $this->clamp_number(intval($rgbcolors[$i], 10), 0, 255); + $rgbcolors[$i] = sprintf("%02x", $rgbcolors[$i]); } // Fix for issue #2528093 - if (!preg_match('/[\s,);}]/', $matches[2])) { - $matches[2] = ' '. $matches[2]; + if (!preg_match('/[\s\,\);\}]/', $matches[2])){ + $matches[2] = ' ' . $matches[2]; } - return '#'. implode('', $hexColors) . $matches[2]; + return '#' . implode('', $rgbcolors) . $matches[2]; } - private function hslToHex($matches) + private function hsl_to_hex($matches) { - $hslValues = explode(',', $matches[1]); + $values = explode(',', str_replace('%', '', $matches[1])); + $h = floatval($values[0]); + $s = floatval($values[1]); + $l = floatval($values[2]); - $rgbColors = $this->hslToRgb($hslValues); + // Wrap and clamp, then fraction! + $h = ((($h % 360) + 360) % 360) / 360; + $s = $this->clamp_number($s, 0, 100) / 100; + $l = $this->clamp_number($l, 0, 100) / 100; - return $this->rgbToHex(array('', implode(',', $rgbColors), $matches[2])); - } + if ($s == 0) { + $r = $g = $b = $this->round_number(255 * $l); + } else { + $v2 = $l < 0.5 ? $l * (1 + $s) : ($l + $s) - ($s * $l); + $v1 = (2 * $l) - $v2; + $r = $this->round_number(255 * $this->hue_to_rgb($v1, $v2, $h + (1/3))); + $g = $this->round_number(255 * $this->hue_to_rgb($v1, $v2, $h)); + $b = $this->round_number(255 * $this->hue_to_rgb($v1, $v2, $h - (1/3))); + } - private function processAtRulesOperators($matches) - { - return $matches[1] . strtolower($matches[2]) .' ('; + return $this->rgb_to_hex(array('', $r.','.$g.','.$b, $matches[2])); } - private function lowercasePseudoFirst($matches) + private function lowercase_pseudo_first($matches) { return ':first-'. strtolower($matches[1]) .' '. $matches[2]; } - private function lowercaseDirectives($matches) + private function lowercase_directives($matches) { return '@'. strtolower($matches[1]); } - private function lowercasePseudoElements($matches) + private function lowercase_pseudo_elements($matches) { return ':'. strtolower($matches[1]); } - private function lowercaseCommonFunctions($matches) + private function lowercase_common_functions($matches) { return ':'. strtolower($matches[1]) .'('; } - private function lowercaseCommonFunctionsValues($matches) + private function lowercase_common_functions_values($matches) { return $matches[1] . strtolower($matches[2]); } - private function lowercaseProperties($matches) + private function lowercase_properties($matches) { - return $matches[1] . strtolower($matches[2]) . $matches[3]; + return $matches[1].strtolower($matches[2]).$matches[3]; } - // --------------------------------------------------------------------------------------------- - // HELPERS - // --------------------------------------------------------------------------------------------- - - /** - * Clamps a number between a minimum and a maximum value. - * @param int|float $n the number to clamp - * @param int|float $min the lower end number allowed - * @param int|float $max the higher end number allowed - * @return int|float + /* HELPERS + * --------------------------------------------------------------------------------------------- */ - private function clampNumber($n, $min, $max) - { - return min(max($n, $min), $max); - } - /** - * Clamps a RGB color number outside the sRGB color space - * @param int|float $n the number to clamp - * @return int|float - */ - private function clampNumberSrgb($n) + private function hue_to_rgb($v1, $v2, $vh) { - return $this->clampNumber($n, 0, 255); + $vh = $vh < 0 ? $vh + 1 : ($vh > 1 ? $vh - 1 : $vh); + if ($vh * 6 < 1) return $v1 + ($v2 - $v1) * 6 * $vh; + if ($vh * 2 < 1) return $v2; + if ($vh * 3 < 2) return $v1 + ($v2 - $v1) * ((2/3) - $vh) * 6; + return $v1; } - /** - * Escapes backreferences such as \1 and $1 in a regular expression replacement string - * @param $string - * @return string - */ - private function escapeReplacementString($string) + private function round_number($n) { - return addcslashes($string, '\\$'); + return intval(floor(floatval($n) + 0.5), 10); } - /** - * Converts a HSL color into a RGB color - * @param array $hslValues - * @return array - */ - private function hslToRgb($hslValues) + private function clamp_number($n, $min, $max) { - $h = floatval($hslValues[0]); - $s = floatval(str_replace('%', '', $hslValues[1])); - $l = floatval(str_replace('%', '', $hslValues[2])); - - // Wrap and clamp, then fraction! - $h = ((($h % 360) + 360) % 360) / 360; - $s = $this->clampNumber($s, 0, 100) / 100; - $l = $this->clampNumber($l, 0, 100) / 100; - - if ($s == 0) { - $r = $g = $b = $this->roundNumber(255 * $l); - } else { - $v2 = $l < 0.5 ? $l * (1 + $s) : ($l + $s) - ($s * $l); - $v1 = (2 * $l) - $v2; - $r = $this->roundNumber(255 * $this->hueToRgb($v1, $v2, $h + (1/3))); - $g = $this->roundNumber(255 * $this->hueToRgb($v1, $v2, $h)); - $b = $this->roundNumber(255 * $this->hueToRgb($v1, $v2, $h - (1/3))); - } - - return array($r, $g, $b); - } - - /** - * Tests and selects the correct formula for each RGB color channel - * @param $v1 - * @param $v2 - * @param $vh - * @return mixed - */ - private function hueToRgb($v1, $v2, $vh) - { - $vh = $vh < 0 ? $vh + 1 : ($vh > 1 ? $vh - 1 : $vh); - - if ($vh * 6 < 1) { - return $v1 + ($v2 - $v1) * 6 * $vh; - } - - if ($vh * 2 < 1) { - return $v2; - } - - if ($vh * 3 < 2) { - return $v1 + ($v2 - $v1) * ((2 / 3) - $vh) * 6; - } - - return $v1; + return min(max($n, $min), $max); } /** * PHP port of Javascript's "indexOf" function for strings only - * Author: Tubal Martin + * Author: Tubal Martin http://blog.margenn.com * * @param string $haystack * @param string $needle * @param int $offset index (optional) * @return int */ - private function indexOf($haystack, $needle, $offset = 0) + private function index_of($haystack, $needle, $offset = 0) { $index = strpos($haystack, $needle, $offset); - return ($index !== false) ? $index : -1; - } - - /** - * Convert strings like "64M" or "30" to int values - * @param mixed $size - * @return int - */ - private function normalizeInt($size) - { - if (is_string($size)) { - $letter = substr($size, -1); - $size = intval($size); - switch ($letter) { - case 'M': - case 'm': - return (int) $size * 1048576; - case 'K': - case 'k': - return (int) $size * 1024; - case 'G': - case 'g': - return (int) $size * 1073741824; - } - } - return (int) $size; - } - - /** - * Converts a string containing and RGB percentage value into a RGB integer value i.e. '90%' -> 229.5 - * @param $rgbPercentage - * @return int - */ - private function rgbPercentageToRgbInteger($rgbPercentage) - { - if (strpos($rgbPercentage, '%') !== false) { - $rgbPercentage = $this->roundNumber(floatval(str_replace('%', '', $rgbPercentage)) * 2.55); - } - - return intval($rgbPercentage, 10); - } - - /** - * Rounds a number to its closest integer - * @param $n - * @return int - */ - private function roundNumber($n) - { - return intval(round(floatval($n)), 10); + return ($index !== FALSE) ? $index : -1; } /** * PHP port of Javascript's "slice" function for strings only - * Author: Tubal Martin + * Author: Tubal Martin http://blog.margenn.com + * Tests: http://margenn.com/tubal/str_slice/ * * @param string $str * @param int $start index * @param int|bool $end index (optional) * @return string */ - private function strSlice($str, $start = 0, $end = false) + private function str_slice($str, $start = 0, $end = FALSE) { - if ($end !== false && ($start < 0 || $end <= 0)) { + if ($end !== FALSE && ($start < 0 || $end <= 0)) { $max = strlen($str); if ($start < 0) { @@ -1257,7 +767,25 @@ class CSSmin } } - $slice = ($end === false) ? substr($str, $start) : substr($str, $start, $end - $start); - return ($slice === false) ? '' : $slice; + $slice = ($end === FALSE) ? substr($str, $start) : substr($str, $start, $end - $start); + return ($slice === FALSE) ? '' : $slice; + } + + /** + * Convert strings like "64M" or "30" to int values + * @param mixed $size + * @return int + */ + private function normalize_int($size) + { + if (is_string($size)) { + switch (substr($size, -1)) { + case 'M': case 'm': return $size * 1048576; + case 'K': case 'k': return $size * 1024; + case 'G': case 'g': return $size * 1073741824; + } + } + + return (int) $size; } } diff --git a/sites/all/modules/contrib/advagg/advagg_ext_compress/advagg_ext_compress.admin.inc b/sites/all/modules/contrib/advagg/advagg_ext_compress/advagg_ext_compress.admin.inc deleted file mode 100644 index ec9457a1c7..0000000000 --- a/sites/all/modules/contrib/advagg/advagg_ext_compress/advagg_ext_compress.admin.inc +++ /dev/null @@ -1,83 +0,0 @@ -<?php - -/** - * @file - * Admin page callbacks for the advagg external compression module. - */ - -/** - * Form builder; Configure advagg settings. - * - * @ingroup advagg_forms - * - * @see system_settings_form() - */ -function advagg_ext_compress_admin_settings_form($form, $form_state) { - drupal_set_title(t('AdvAgg: External Compressor')); - advagg_display_message_if_requirements_not_met(); - - $form = array(); - // CSS command line. - advagg_ext_compress_admin_cmd_generate($form, array('css', t('CSS'))); - // JS command line. - advagg_ext_compress_admin_cmd_generate($form, array('js', t('JavaScript'))); - - return system_settings_form($form); -} - -/** - * Generate a form for css or js depending on the input. - * - * @param array $form - * The form array to add to. - * @param array $params - * An array where key 0 is the machine name and key 1 is the title. - */ -function advagg_ext_compress_admin_cmd_generate(array &$form, array $params) { - $form[$params[0]] = array( - '#type' => 'fieldset', - '#title' => t('@title', array('@title' => $params[1])), - ); - $form[$params[0]]['cmd'] = array( - '#type' => 'fieldset', - '#title' => t('Command Line'), - ); - - $description = t('{%CWD%} = DRUPAL_ROOT. <br /> {%IN%} = input file. <br /> {%IN_URL_ENC%} = url pointing to the input file that has been url encoded. <br /> {%OUT%} = output file. <br /><br />'); - if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') { - $description .= ' ' . t('Example using the <a href="@link1">Microsoft Ajax Minifier</a>. <p><code>@code1</code></p>', array( - '@link1' => 'http://ajaxmin.codeplex.com/', - '@code1' => 'AjaxMinifier {%IN%} -o {%OUT%}', - )); - } - - if ($params[0] === 'js') { - $description .= ' ' . t('Example using the <a href="@link1">Google Closure Compiler</a>. <p><code>@code1</code></p>', array( - '@link1' => 'https://developers.google.com/closure/compiler/docs/gettingstarted_app', - '@code1' => 'java -jar compiler.jar --js {%CWD%}/{%IN%} --js_output_file {%OUT%}', - )); - - $description .= ' ' . t('Example using curl to compress via the <a href="@link1">Online Google Closure Compiler</a>. <p><code>@code1</code></p>', array( - '@link1' => 'https://developers.google.com/closure/compiler/docs/api-ref', - '@code1' => 'curl -o {%OUT%} -d output_info=compiled_code -d code_url={%IN_URL_ENC%} http://closure-compiler.appspot.com/compile', - )); - } - if ($params[0] === 'css') { - $description .= ' ' . t('Example using the <a href="@link1">YUI Compressor</a>. <p><code>@code1</code></p>', array( - '@link1' => 'http://yui.github.io/yuicompressor/', - '@code1' => 'java -jar yuicompressor-x.y.z.jar --type css --line-break 4096 {%CWD%}/{%IN%} -o {%OUT%}', - )); - - $description .= ' ' . t('Example using curl to compress via an online <a href="@link1">CSS Compressor</a>. <p><code>@code1</code></p>', array( - '@link1' => 'http://cnvyr.io/', - '@code1' => 'curl -o {%OUT%} -F \'files0=@{%IN%}\' http://srv.cnvyr.io/v1?min=css', - )); - } - - $form[$params[0]]['cmd']['advagg_ext_compress_' . $params[0] . '_cmd'] = array( - '#type' => 'textfield', - '#title' => t('Command to run'), - '#default_value' => variable_get('advagg_ext_compress_' . $params[0] . '_cmd', ''), - '#description' => $description, - ); -} 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 deleted file mode 100644 index 955a0c5708..0000000000 --- a/sites/all/modules/contrib/advagg/advagg_ext_compress/advagg_ext_compress.info +++ /dev/null @@ -1,13 +0,0 @@ -name = AdvAgg External Compression -description = Compress Javascript and/or CSS with a command line compressor. -package = Advanced CSS/JS Aggregation -core = 7.x -dependencies[] = advagg - -configure = admin/config/development/performance/advagg/ext-compress - -; Information added by Drupal.org packaging script on 2020-11-19 -version = "7.x-2.35" -core = "7.x" -project = "advagg" -datestamp = "1605792717" 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 deleted file mode 100644 index 5bfe00f4b1..0000000000 --- a/sites/all/modules/contrib/advagg/advagg_ext_compress/advagg_ext_compress.module +++ /dev/null @@ -1,241 +0,0 @@ -<?php - -/** - * @file - * Advanced CSS/JS aggregation external compression module. - */ - -/** - * @addtogroup hooks - * @{ - */ - -/** - * Implements hook_menu(). - */ -function advagg_ext_compress_menu() { - $file_path = drupal_get_path('module', 'advagg_ext_compress'); - $config_path = advagg_admin_config_root_path(); - - $items[$config_path . '/advagg/ext-compress'] = array( - 'title' => 'External Compression', - 'description' => 'Adjust External Compression settings.', - 'page callback' => 'drupal_get_form', - 'page arguments' => array('advagg_ext_compress_admin_settings_form'), - 'type' => MENU_LOCAL_TASK, - 'access arguments' => array('administer site configuration'), - 'file path' => $file_path, - 'file' => 'advagg_ext_compress.admin.inc', - 'weight' => 10, - ); - - return $items; -} - -/** - * @} End of "addtogroup hooks". - */ - -/** - * @addtogroup advagg_hooks - * @{ - */ - -/** - * Implements hook_advagg_js_compress_configuration_alter(). - */ -function advagg_ext_compress_advagg_js_compress_configuration_alter(&$options_desc, &$compressors, &$functions) { - list($options, $description) = $options_desc; - - $key = 10; - while (isset($options[$key])) { - $key++; - } - $options[$key] = t('AdvAgg Command Line Compressor'); - $compressors[$key] = 'advagg_cmdline'; - $functions[$key] = 'advagg_ext_compress_js_compress'; - - $options_desc = array($options, $description); -} - -/** - * Implements hook_advagg_css_compress_configuration_alter(). - */ -function advagg_ext_compress_advagg_css_compress_configuration_alter(&$options_desc, &$compressors, &$functions) { - list($options, $description) = $options_desc; - - $key = 10; - while (isset($options[$key])) { - $key++; - } - $options[$key] = t('AdvAgg Command Line Compressor'); - $functions[$key] = 'advagg_ext_compress_css_compress'; - - $options_desc = array($options, $description); -} - -/** - * @} End of "addtogroup advagg_hooks". - */ - -/** - * Compress Javascript using via command line. - * - * @param string $input_file - * The file containing the uncompressed css/js data. - * @param string $ext - * The string css or js. - * @param array $debug - * Optional debug array. - * - * @return string - * The filename containing the compressed css/js data. - */ -function advagg_ext_compress_execute_cmd($input_file, $ext = '', array &$debug = array()) { - $run = variable_get("advagg_ext_compress_{$ext}_cmd", ''); - if (empty($run)) { - return FALSE; - } - - // Get file extension. - if (empty($ext)) { - $ext = strtolower(pathinfo($input_file, PATHINFO_EXTENSION)); - if ($ext !== 'css' && $ext !== 'js') { - // Get the $ext from the database. - $row = db_select('advagg_files', 'af') - ->fields('af') - ->condition('filename', $input_file) - ->execute()->fetchAssoc(); - if (!empty($row['filetype'])) { - $ext = $row['filetype']; - } - if ($ext === 'less') { - $ext = 'css'; - } - } - } - - // Generate temp file. - $temp_file = drupal_tempnam('temporary://', 'advagg_file_'); - $new_temp_file = $temp_file . '.' . basename($input_file); - @rename($temp_file, $new_temp_file); - // Set the permissions on the temp file. - drupal_chmod($new_temp_file); - $output = advagg_get_relative_path($new_temp_file); - - // Create command to run. - $cmd = str_replace(array( - '{%CWD%}', - '{%IN%}', - '{%IN_URL_ENC%}', - '{%OUT%}', - ), array( - DRUPAL_ROOT, - $input_file, - urlencode(file_create_url($input_file)), - escapeshellarg(realpath($output)), - ), $run); - - // Run command and return the output file. - $shell_output = array(); - $return_var = 0; - $shell = exec($cmd, $shell_output, $return_var); - $debug = array($cmd, $shell_output, $return_var, $shell); - - // Cleanup leftover files. - if (file_exists($temp_file)) { - @unlink($temp_file); - } - return $output; -} - -/** - * Compress Javascript using via command line. - * - * @param string $contents - * The JavaScript to compress. - * @param bool $log_errors - * TRUE to log errors. - * - * @return bool - * FALSE if this failed. - */ -function advagg_ext_compress_js_compress(&$contents, $log_errors) { - return advagg_ext_compress_string($contents, 'js', $log_errors); -} - -/** - * Compress CSS using via command line. - * - * @param string $contents - * The CSS to compress. - * @param bool $log_errors - * TRUE to log errors. - * - * @return bool - * FALSE if this failed. - */ -function advagg_ext_compress_css_compress(&$contents, $log_errors) { - return advagg_ext_compress_string($contents, 'css', $log_errors); -} - -/** - * Compress CSS using via command line. - * - * @param string $contents - * The data to compress. - * @param string $type - * Should be css or js. - * @param bool $log_errors - * TRUE to log errors. - * - * @return bool - * TRUE on success. - */ -function advagg_ext_compress_string(&$contents, $type, $log_errors) { - list($css_path, $js_path) = advagg_get_root_files_dir(); - if ($type === 'css') { - $dir = $css_path[0]; - } - else { - $dir = $js_path[0]; - } - $new_temp_file = $dir . '/advagg_file_' . drupal_hash_base64(microtime(TRUE) . mt_rand()) . '.' . $type; - $temp_file_full = advagg_get_relative_path($new_temp_file); - - file_put_contents($new_temp_file, $contents); - // Set the permissions on the temp file. - drupal_chmod($new_temp_file); - $debug = array(); - $output = advagg_ext_compress_execute_cmd($temp_file_full, $type, $debug); - if (empty($output)) { - return FALSE; - } - $new_contents = advagg_file_get_contents($output); - if (strpos($new_contents, 'Error') === 0) { - if ($log_errors) { - watchdog('advagg_ext_compress', "@a \n<br>\n<br> @b", array( - // Only log 4k of data. - '@a' => substr($new_contents, 0, 4096), - '@b' => print_r($debug, TRUE), - )); - } - $return = FALSE; - } - else { - $contents = $new_contents; - $return = TRUE; - } - - // Cleanup. - if (file_exists($new_temp_file)) { - unlink($new_temp_file); - } - if (file_exists($temp_file_full)) { - unlink($temp_file_full); - } - if (file_exists($output)) { - unlink($output); - } - return $return; -} 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 deleted file mode 100644 index 32406e5850..0000000000 --- a/sites/all/modules/contrib/advagg/advagg_font/advagg_font.admin.inc +++ /dev/null @@ -1,209 +0,0 @@ -<?php - -/** - * @file - * Admin page callbacks for the advagg font module. - */ - -/** - * Form builder; Configure advagg settings. - * - * @ingroup advagg_forms - * - * @see system_settings_form() - */ -function advagg_font_admin_settings_form() { - drupal_set_title(t('AdvAgg: Async Font Loader')); - advagg_display_message_if_requirements_not_met(); - $form = array(); - - $library = advagg_get_library('fontfaceobserver', 'advagg_font'); - $version = advagg_get_remote_libraries_version('fontfaceobserver', $library); - $options = array( - 0 => t('Disabled'), - 6 => t('Externally load the latest from github (version: @version)', array('@version' => $version)), - ); - $description = t('This will use the fallback font until the font has been downloaded. See <a href="@link">fontfaceobserver</a> for more info.', array( - '@link' => $library['vendor url'], - )); - if (function_exists('libraries_info')) { - if ($library['installed']) { - $options += array( - 2 => t('Inline javascript (version: @version)', array('@version' => $library['version'])), - 4 => t('Local file included in aggregate (version: @version)', array('@version' => $library['version'])), - ); - } - elseif (!is_readable('sites/all/libraries/fontfaceobserver/fontfaceobserver.js')) { - $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('Go to the <a href="@url">library report page</a> and make sure fontfaceobserver is installed correctly.', array( - '@url' => url('admin/reports/libraries'), - )); - } - } - elseif (is_readable('sites/all/libraries/fontfaceobserver/fontfaceobserver.js')) { - $description .= ' ' . t('To use fontfaceobserver locally the <a href="@url">libraries api module</a> needs to be installed.', array( - '@url' => 'https://www.drupal.org/project/libraries', - )); - } - else { - $description .= ' ' . t('To use fontfaceobserver locally the <a href="@url">libraries api module</a> 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', - )); - } - - ksort($options); - $form['advagg_font_fontfaceobserver'] = array( - '#type' => 'radios', - '#title' => t('Use font face observer to load fonts asynchronously.'), - '#default_value' => variable_get('advagg_font_fontfaceobserver', ADVAGG_FONT_FONTFACEOBSERVER), - '#options' => $options, - '#description' => $description, - ); - - $form['container'] = array( - '#type' => 'container', - '#states' => array( - 'invisible' => array( - ':input[name="advagg_font_fontfaceobserver"]' => array('value' => '0'), - ), - ), - ); - $form['container']['advagg_font_storage'] = array( - '#type' => 'checkbox', - '#title' => t('Use localStorage so the flash of unstyled text (FOUT) only happens once.'), - '#default_value' => variable_get('advagg_font_storage', ADVAGG_FONT_STORAGE), - '#description' => t('Data is stored in localStorage under advagg_fonts. If this is a problem you can disable localStorage from being used; if doing so the FOUT (Flash of Unstyled Text) will happen on every page load if cookies is also not being used.', array('@cookie' => 'advaggfont_pt-sans=PT Sans')), - ); - $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 <code>@cookie</code>. If this is a problem you can disable cookies from being set; if doing so the FOUT (Flash of Unstyled Text) will happen on every page load if localStorage is also not being used.', 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), - '#edit-advagg-font-storage' => array('checked' => FALSE), - ), - ), - ); - - // Get all css files and scan for quoted fonts. - $form['fonts'] = array( - '#type' => 'fieldset', - '#collapsible' => TRUE, - '#collapsed' => TRUE, - '#title' => t('Async Loaded fonts in CSS'), - '#description' => t('Assumes quoted fonts will be downloaded and unquoted fonts are fallbacks.'), - ); - $form['fonts_not_async'] = array( - '#type' => 'fieldset', - '#collapsible' => TRUE, - '#collapsed' => TRUE, - '#title' => t('NOT Async Loaded in CSS'), - '#description' => t('Assumes quoted fonts will be downloaded and unquoted fonts are fallbacks. If there is no fallback it will apear below.'), - ); - // Get filename, filename_hash, and changes. - $results = db_select('advagg_files', 'af') - ->fields('af', array('filename', 'filename_hash', 'changes')) - ->condition('filetype', 'css') - ->orderBy('filename', 'ASC') - ->execute(); - while ($row = $results->fetchAssoc()) { - if (!file_exists($row['filename'])) { - continue; - } - // Get the file contents. - $file_contents = (string) @advagg_file_get_contents($row['filename']); - // Get font names. - list($replacements, $fonts_with_no_replacements) = advagg_font_get_replacements_array($file_contents); - if (!empty($replacements)) { - $fonts = array(); - foreach ($replacements as $key => $replacement) { - // Do not display !important after the fallback font name. - $replacement[5] = str_replace(' !important', '', $replacement[5]); - $fonts[$key . ' ' . $replacement[3]] = $replacement[5]; - } - - $form['fonts'][$row['filename_hash']] = array( - '#markup' => '<div>' . t('%file - <code>@replacements</code><br />', array( - '@replacements' => str_ireplace('array', '', print_r($fonts, TRUE)), - '%file' => $row['filename'], - )) . '</div>', - ); - } - if (!empty($fonts_with_no_replacements)) { - $fonts = array(); - foreach ($fonts_with_no_replacements as $key => $replacement) { - // Do not display !important after the fallback font name. - $replacement = str_replace(' !important', '', $replacement); - $fonts[$key . ' ' . $replacement] = $replacement; - } - - $form['fonts_not_async'][$row['filename_hash']] = array( - '#markup' => '<div>' . t('%file - <code>@replacements</code><br />', array( - '@replacements' => str_ireplace('array', '', print_r($fonts, TRUE)), - '%file' => $row['filename'], - )) . '</div>', - ); - } - } - $children = element_children($form['fonts']); - - // If no fonts are found; disable this module. - if (count($children) == 0) { - $form['advagg_font_fontfaceobserver']['#default_value'] = 0; - $form['advagg_font_fontfaceobserver']['#disabled'] = TRUE; - - 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. - $form['#submit'][] = 'advagg_font_admin_settings_form_submit'; - - return system_settings_form($form); -} - -/** - * Submit callback, clear out the advagg cache bin. - * - * @ingroup advagg_forms_callback - */ -function advagg_font_admin_settings_form_submit($form, &$form_state) { - // Clear caches. - advagg_cache_clear_admin_submit(); - - // Disable cookie and local storage if ffo is disabled. - if (empty($form_state['values']['advagg_font_fontfaceobserver'])) { - $form_state['values']['advagg_font_cookie'] = 0; - $form_state['values']['advagg_font_storage'] = 0; - } - // Disable no fout if cookies and local storage are disabled. - if (empty($form_state['values']['advagg_font_cookie']) - && empty($form_state['values']['advagg_font_storage']) - ) { - $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 deleted file mode 100644 index ef1ef3a040..0000000000 --- a/sites/all/modules/contrib/advagg/advagg_font/advagg_font.advagg.inc +++ /dev/null @@ -1,63 +0,0 @@ -<?php - -/** - * @file - * Advanced aggregation css compression module. - */ - -/** - * @addtogroup advagg_hooks - * @{ - */ - -/** - * Implements hook_advagg_get_css_file_contents_alter(). - */ -function advagg_font_advagg_get_css_file_contents_alter(&$contents, $file, $aggregate_settings) { - // Do nothing if this is disabled. - if (empty($aggregate_settings['variables']['advagg_font_fontfaceobserver'])) { - return; - } - - list($replacements) = advagg_font_get_replacements_array($contents); - foreach ($replacements as $replace) { - $contents = str_replace($replace[0], $replace[1] . $replace[2], $contents); - } -} - -/** - * Implements hook_advagg_get_info_on_files_alter(). - * - * Used to add external font info for css file. - */ -function advagg_font_advagg_get_info_on_files_alter(&$return, $cached_data, $bypass_cache) { - $advagg_font_ffo = variable_get('advagg_font_fontfaceobserver', ADVAGG_FONT_FONTFACEOBSERVER); - if (!empty($advagg_font_ffo)) { - foreach ($return as &$info) { - // Skip if not a css file. - if (empty($info['fileext']) || $info['fileext'] !== 'css') { - continue; - } - // Get the file contents. - $file_contents = (string) @advagg_file_get_contents($info['data']); - if (!empty($file_contents)) { - // Get font names. - list($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]] = str_replace(array('"', "'"), '', $replace[3]); - } - } - } - unset($info); - } -} - -/** - * @} End of "addtogroup advagg_hooks". - */ diff --git a/sites/all/modules/contrib/advagg/advagg_font/advagg_font.info b/sites/all/modules/contrib/advagg/advagg_font/advagg_font.info deleted file mode 100644 index da55e629b5..0000000000 --- a/sites/all/modules/contrib/advagg/advagg_font/advagg_font.info +++ /dev/null @@ -1,14 +0,0 @@ -name = AdvAgg Async Font Loader -description = Allows one to load fonts in an async manner -package = Advanced CSS/JS Aggregation -core = 7.x -dependencies[] = advagg -recommends[] = libraries - -configure = admin/config/development/performance/advagg/font - -; Information added by Drupal.org packaging script on 2020-11-19 -version = "7.x-2.35" -core = "7.x" -project = "advagg" -datestamp = "1605792717" diff --git a/sites/all/modules/contrib/advagg/advagg_font/advagg_font.inline.js b/sites/all/modules/contrib/advagg/advagg_font/advagg_font.inline.js deleted file mode 100644 index ded1a40c16..0000000000 --- a/sites/all/modules/contrib/advagg/advagg_font/advagg_font.inline.js +++ /dev/null @@ -1,39 +0,0 @@ -/** - * @file - * Used to add a class to the top level element based off of cookies. - */ - -/** - * Get advagg cookies for fonts or localStorage. - * - * Changes the top level class to include the font name found in the cookie. - */ -function advagg_font_inline() { - 'use strict'; - // Cookie handler. - var fonts = document.cookie.split('advaggf'); - for (var i = 0; i < fonts.length; i++) { - var font = fonts[i].split('='); - var pos = font[0].indexOf('ont_'); - if (pos !== -1) { - // Only allow alpha numeric class names. - window.document.documentElement.className += ' ' + font[0].substr(4).replace(/[^a-zA-Z0-9-]/g, ''); - } - } - - // Local Storage handler. - if (Storage !== void 0) { - fonts = JSON.parse(localStorage.getItem('advagg_fonts')); - var current_time = new Date().getTime(); - for (var key in fonts) { - if (fonts[key] >= current_time) { - // Only allow alpha numeric class names. - window.document.documentElement.className += ' ' + key.replace(/[^a-zA-Z0-9-]/g, ''); - } - } - } -} - - -// Check cookies ASAP and set class. -advagg_font_inline(); diff --git a/sites/all/modules/contrib/advagg/advagg_font/advagg_font.install b/sites/all/modules/contrib/advagg/advagg_font/advagg_font.install deleted file mode 100644 index 24081aba97..0000000000 --- a/sites/all/modules/contrib/advagg/advagg_font/advagg_font.install +++ /dev/null @@ -1,61 +0,0 @@ -<?php - -/** - * @file - * Handles Advanced Aggregation installation and upgrade tasks. - */ - -/** - * @addtogroup hooks - * @{ - */ - -/** - * Implements hook_requirements(). - */ -function advagg_font_requirements($phase) { - $requirements = array(); - // If not at runtime, return here. - if ($phase !== 'runtime') { - return $requirements; - } - - // Ensure translations don't break at install time. - $t = get_t(); - // Get config path. - $config_path = advagg_admin_config_root_path(); - - // See if module is on. - if (variable_get('advagg_font_fontfaceobserver', ADVAGG_FONT_FONTFACEOBSERVER) == 0) { - $requirements['advagg_font_not_on'] = array( - 'title' => $t('AdvAgg Font'), - 'severity' => REQUIREMENT_WARNING, - 'value' => $t('AdvAgg async font loading is disabled.'), - 'description' => $t('Go to the <a href="@settings">AdvAgg Async Font Loader settings page</a> and select an option other than disabled, or go to the <a href="@modules">modules page</a> and disable the "AdvAgg Async Font Loader" module.', array( - '@settings' => url($config_path . '/advagg/font'), - '@modules' => url('admin/modules', array( - 'fragment' => 'edit-modules-advanced-cssjs-aggregation', - )), - )), - ); - } - - // Check version. - $lib_name = 'fontfaceobserver'; - $module_name = 'advagg_css_compress'; - list($description, $info) = advagg_get_version_description($lib_name, $module_name); - if (!empty($description)) { - $requirements["{$module_name}_{$lib_name}_updates"] = array( - 'title' => $t('@module_name', array('@module_name' => $info['name'])), - 'severity' => REQUIREMENT_WARNING, - 'value' => $t('The @name library needs to be updated.', array('@name' => $lib_name)), - 'description' => $description, - ); - } - - return $requirements; -} - -/** - * @} End of "addtogroup hooks". - */ diff --git a/sites/all/modules/contrib/advagg/advagg_font/advagg_font.js b/sites/all/modules/contrib/advagg/advagg_font/advagg_font.js deleted file mode 100644 index 8b031464f1..0000000000 --- a/sites/all/modules/contrib/advagg/advagg_font/advagg_font.js +++ /dev/null @@ -1,110 +0,0 @@ -/** - * @file - * Used to add a class to the top level element when an external font is ready. - */ - -/* global Drupal:false */ - -/** - * Run the check. - * - * @param {string} key - * The class name to add to the html tag. - * @param {string} value - * The font name. - */ -function advagg_run_check(key, value) { - 'use strict'; - // Only run if window.FontFaceObserver is defined. - if (window.FontFaceObserver) { - // Only alpha numeric value. - key = key.replace(/[^a-zA-Z0-9-]/g, ''); - if (typeof window.FontFaceObserver.prototype.load === 'function') { - new window.FontFaceObserver(value).load().then(function () { - advagg_run_check_inner(key, value); - }, function () {}); - } - else { - new window.FontFaceObserver(value).check().then(function () { - advagg_run_check_inner(key, value); - }, function () {}); - } - } - else { - // Try again in 100 ms. - window.setTimeout(function () { - advagg_run_check(key, value); - }, 100); - } -} - -/** - * Run the check. - * - * @param {string} key - * The class name to add to the html tag. - * @param {string} value - * The font name. - */ -function advagg_run_check_inner(key, value) { - 'use strict'; - // Set Class. - if (parseInt(Drupal.settings.advagg_font_no_fout, 10) !== 1) { - window.document.documentElement.className += ' ' + key; - } - - // Set for a day. - var expire_date = new Date().getTime() + 86400 * 1000; - - if (Storage !== void 0 && parseInt(Drupal.settings.advagg_font_storage, 10) === 1) { - // Use local storage. - var fonts = JSON.parse(localStorage.getItem('advagg_fonts')); - if (!fonts) { - fonts = {}; - } - fonts[key] = expire_date; - localStorage.setItem('advagg_fonts', JSON.stringify(fonts)); - } - else if (parseInt(Drupal.settings.advagg_font_cookie, 10) === 1) { - // Use cookies if enabled and local storage not available. - expire_date = new Date(expire_date).toUTCString(); - document.cookie = 'advaggfont_' + key + '=' + value - + '; expires=' + expire_date - + '; domain=.' + document.location.hostname - + '; path=/'; - } -} - -/** - * 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) { - if (Drupal.settings.advagg_font.hasOwnProperty(key)) { - var html_class = (' ' + window.document.documentElement.className + ' ').indexOf(' ' + key + ' '); - // If the class already exists in the html element do nothing. - if (html_class === -1) { - // Wait till the font is downloaded, then set cookie & class. - advagg_run_check(key, Drupal.settings.advagg_font[key]); - } - } - } -} - -/** - * Make sure window.Drupal.settings.advagg_font is defined before running. - */ -function advagg_font_check() { - 'use strict'; - if (window.Drupal && window.Drupal.settings && window.Drupal.settings.advagg_font) { - advagg_font_add_font_classes_on_load(); - } - else { - // Try again in 20 ms. - window.setTimeout(advagg_font_check, 20); - } -} - -// Start the process. -advagg_font_check(); diff --git a/sites/all/modules/contrib/advagg/advagg_font/advagg_font.module b/sites/all/modules/contrib/advagg/advagg_font/advagg_font.module deleted file mode 100644 index 9f3a9c8686..0000000000 --- a/sites/all/modules/contrib/advagg/advagg_font/advagg_font.module +++ /dev/null @@ -1,542 +0,0 @@ -<?php - -/** - * @file - * Advanced aggregation font module. - */ - -/** - * @addtogroup default_variables - * @{ - */ - -/** - * Default value to use font face observer for asynchronous font loading. - */ -define('ADVAGG_FONT_FONTFACEOBSERVER', 0); - -/** - * Default value to include font info in critical css. - */ -define('ADVAGG_FONT_ADD_TO_CRITICAL_CSS', 1); - -/** - * Default value to use localStorage in order to prevent the FOUT. - */ -define('ADVAGG_FONT_STORAGE', 1); - -/** - * 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); - -/** - * @} End of "addtogroup default_variables". - */ - -/** - * @addtogroup hooks - * @{ - */ - -/** - * 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_alter() { - // Skip if advagg is disabled. - if (!advagg_enabled()) { - return; - } - $advagg_font_ffo = variable_get('advagg_font_fontfaceobserver', ADVAGG_FONT_FONTFACEOBSERVER); - // Fontface Observer is disabled. - if (empty($advagg_font_ffo)) { - return; - } - - // Add settings. - drupal_add_js(array( - 'advagg_font' => array(), - 'advagg_font_storage' => variable_get('advagg_font_storage', ADVAGG_FONT_STORAGE), - '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), - ), array('type' => 'setting')); - - // Add inline script for reading the cookies and adding the fonts already - // loaded to the html class. - if (variable_get('advagg_font_cookie', ADVAGG_FONT_COOKIE) || variable_get('advagg_font_storage', ADVAGG_FONT_STORAGE)) { - $inline_script_min = 'for(var fonts=document.cookie.split("advaggf"),i=0;i<fonts.length;i++){var font=fonts[i].split("="),pos=font[0].indexOf("ont_");-1!==pos&&(window.document.documentElement.className+=" "+font[0].substr(4).replace(/[^a-zA-Z0-9\-]/g,""))}if(void 0!==Storage){fonts=JSON.parse(localStorage.getItem("advagg_fonts"));var current_time=(new Date).getTime();for(var key in fonts)fonts[key]>=current_time&&(window.document.documentElement.className+=" "+key.replace(/[^a-zA-Z0-9\-]/g,""))}'; - drupal_add_js($inline_script_min, array( - 'type' => 'inline', - 'group' => JS_LIBRARY - 1, - 'weight' => -50000, - 'scope' => 'above_css', - 'scope_lock' => TRUE, - 'movable' => FALSE, - 'no_defer' => TRUE, - )); - } - - // Get library data for fontfaceobserver. - $library = advagg_get_library('fontfaceobserver', 'advagg_font'); - // If libraries_load() does not exist load library externally. - if (!is_callable('libraries_load')) { - $advagg_font_ffo = 6; - } - // Add fontfaceobserver.js. - if ($advagg_font_ffo != 6 && empty($library['installed'])) { - // The fontfaceobserver library is not installed; use external variant. - $advagg_font_ffo = 6; - } - if ($advagg_font_ffo == 6) { - // Use the external variant. - foreach ($library['variants']['external']['files']['js'] as $data => $options) { - drupal_add_js($data, $options); - } - } - else { - // Load the fontfaceobserver library. - if ($advagg_font_ffo == 2) { - // Use the inline variant. - libraries_load('fontfaceobserver', 'inline'); - } - else { - libraries_load('fontfaceobserver'); - } - } - - // 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'; - drupal_add_js($file_path, array( - 'async' => TRUE, - 'defer' => TRUE, - )); -} - -/** - * Implements hook_css_alter(). - */ -function advagg_css_alter(&$css) { - // Skip if advagg is disabled. - if (!advagg_enabled()) { - return; - } - - // Skip if fontface is disabled. - if (empty(variable_get('advagg_font_fontfaceobserver', ADVAGG_FONT_FONTFACEOBSERVER))) { - return; - } - - // Skip if fonts added to critical css is disabled. - if (empty(variable_get('advagg_font_add_to_critical_css', ADVAGG_FONT_ADD_TO_CRITICAL_CSS))) { - return; - } - - $critical_css_key = NULL; - foreach ($css as $key => $values) { - if (!empty($values['critical-css']) && $values['type'] === 'inline') { - $critical_css_key = $key; - } - } - - // Skip if no critical css. - if (is_null($critical_css_key)) { - return; - } - - module_load_include('inc', 'advagg', 'advagg'); - $css_to_add = ''; - foreach ($css as $key => $values) { - if ($values['type'] === 'file') { - $info = advagg_get_info_on_file($key); - if (!empty($info['advagg_font'])) { - // Get the file contents. - $file_contents = (string) @advagg_file_get_contents($info['data']); - if (empty($file_contents)) { - continue; - } - list($replacements) = advagg_font_get_replacements_array($file_contents); - foreach ($replacements as $replace) { - $css_to_add .= $replace[2]; - } - } - } - } - if (!empty($css_to_add)) { - $css[$critical_css_key]['data'] .= "\n{$css_to_add}"; - } -} - -/** - * Implements hook_menu(). - */ -function advagg_font_menu() { - $file_path = drupal_get_path('module', 'advagg_font'); - $config_path = advagg_admin_config_root_path(); - - $items[$config_path . '/advagg/font'] = array( - 'title' => 'Async Font Loader', - 'description' => 'Load external fonts in a non blocking manner.', - 'page callback' => 'drupal_get_form', - 'page arguments' => array('advagg_font_admin_settings_form'), - 'type' => MENU_LOCAL_TASK, - 'access arguments' => array('administer site configuration'), - 'file path' => $file_path, - 'file' => 'advagg_font.admin.inc', - 'weight' => 10, - ); - - return $items; -} - -/** - * @} End of "addtogroup hooks". - */ - -/** - * @addtogroup 3rd_party_hooks - * @{ - */ - -/** - * Implements hook_libraries_info(). - */ -function advagg_font_libraries_info() { - $libraries['fontfaceobserver'] = array( - // Only used in administrative UI of Libraries API. - 'name' => 'fontfaceobserver', - 'vendor url' => 'https://github.com/bramstein/fontfaceobserver', - 'download url' => 'https://github.com/bramstein/fontfaceobserver/archive/master.zip', - 'version arguments' => array( - 'file' => 'package.json', - // 1.50. : "version": "1.5.0". - 'pattern' => '/"version":\\s+"([0-9\.]+)"/', - 'lines' => 10, - ), - 'remote' => array( - 'callback' => 'advagg_get_github_version_json', - 'url' => 'https://cdn.jsdelivr.net/gh/bramstein/fontfaceobserver@master/package.json', - ), - 'files' => array( - 'js' => array( - 'fontfaceobserver.js' => array( - 'type' => 'file', - 'group' => JS_LIBRARY, - 'async' => TRUE, - 'defer' => TRUE, - ), - ), - ), - 'variants' => array(), - ); - // Get the latest tagged version for external file loading. - $version = advagg_get_remote_libraries_version('fontfaceobserver', $libraries['fontfaceobserver']); - $libraries['fontfaceobserver']['variants'] += array( - 'external' => array( - 'files' => array( - 'js' => array( - "https://cdn.jsdelivr.net/gh/bramstein/fontfaceobserver@v{$version}/fontfaceobserver.js" => array( - 'type' => 'external', - 'data' => "https://cdn.jsdelivr.net/gh/bramstein/fontfaceobserver@v{$version}/fontfaceobserver.js", - 'async' => TRUE, - 'defer' => TRUE, - ), - ), - ), - ), - ); - // Inline if local js is there. - $libraries_paths = array(); - if (is_callable('libraries_get_libraries')) { - $libraries_paths = libraries_get_libraries(); - } - if (!empty($libraries_paths['fontfaceobserver']) && is_readable($libraries_paths['fontfaceobserver'] . '/fontfaceobserver.js')) { - $libraries['fontfaceobserver']['variants'] += array( - 'inline' => array( - 'files' => array( - 'js' => array( - 'loadCSS_inline' => array( - 'type' => 'inline', - 'data' => (string) @advagg_file_get_contents($libraries_paths['fontfaceobserver'] . '/fontfaceobserver.js'), - 'no_defer' => TRUE, - ), - ), - ), - ), - ); - } - - return $libraries; -} - -/** - * @} End of "addtogroup 3rd_party_hooks". - */ - -/** - * @addtogroup advagg_hooks - * @{ - */ - -/** - * Implements hook_advagg_current_hooks_hash_array_alter(). - */ -function advagg_font_advagg_current_hooks_hash_array_alter(&$aggregate_settings) { - $aggregate_settings['variables']['advagg_font_fontfaceobserver'] = variable_get('advagg_font_fontfaceobserver', ADVAGG_FONT_FONTFACEOBSERVER); -} - -/** - * @} End of "addtogroup advagg_hooks". - */ - -/** - * Get the replacements array for the css. - * - * @param string $css_string - * String of CSS. - * - * @return array - * An array containing the replacemnts and the font class name. - */ -function advagg_font_get_replacements_array($css_string) { - // Get the CSS that contains a font-family rule. - $length = strlen($css_string); - $property_position = 0; - $property = 'font'; - $property_alt = 'font-family'; - $replacements = array(); - $fonts_with_no_replacements = array(); - $lower = strtolower($css_string); - $safe_fonts_list = array( - 'georgia' => TRUE, - 'palatino' => TRUE, - 'times new roman' => TRUE, - 'times' => TRUE, - - 'arial' => TRUE, - 'helvetica' => TRUE, - 'gadget' => TRUE, - 'verdana' => TRUE, - 'geneva' => TRUE, - 'tahoma' => TRUE, - 'garamond' => TRUE, - 'bookman' => TRUE, - 'comic sans ms' => TRUE, - 'cursive' => TRUE, - 'trebuchet ms' => TRUE, - 'arial black' => TRUE, - 'impact' => TRUE, - 'charcoal' => TRUE, - - 'courier new' => TRUE, - 'courier' => TRUE, - 'monaco' => TRUE, - - 'system' => TRUE, - ); - - while (($property_position = strpos($lower, $property, $property_position)) !== FALSE) { - // Find the start of the values for the property. - $start_of_values = strpos($css_string, ':', $property_position); - // Get the property at this location of the css. - $property_in_loop = trim(substr($css_string, $property_position, ($start_of_values - $property_position))); - - // Make sure this property is one of the ones we're looking for. - if ($property_in_loop !== $property && $property_in_loop !== $property_alt) { - $property_position += strlen($property); - continue; - } - - // 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 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_a = strpos($css_string, ';', $property_position); - $declaration_end_b = strpos($css_string, '}', $property_position); - if ($declaration_end_a === FALSE) { - $declaration_end = $declaration_end_b; - } - else { - $declaration_end = min($declaration_end_a, $declaration_end_b); - } - if ($declaration_end > $end) { - $declaration_end = $end; - } - // Add one in order to capture the } when we ge the full rule set. - $end++; - // Advance position for the next run of the while loop. - $property_position = $end; - - // Get values assigned to this property. - $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); - if (empty($values_array)) { - continue; - } - - // Values array, first element is a quoted string. - $dq = strpos($values_array[0], '"'); - $sq = strpos($values_array[0], "'"); - $quote_pos = ($sq !== FALSE) ? $sq : $dq; - // Skip if the first font is not quoted. - if ($quote_pos === FALSE) { - continue; - } - - $values_array[0] = trim($values_array[0]); - // Skip if only one font is listed. - if (count($values_array) === 1) { - $fonts_with_no_replacements[$values_array[0]] = ''; - continue; - } - - // Save the first value to a variable; starting at the quote. - $removed_value_original = substr($values_array[0], max($quote_pos - 1, 0)); - - // Resave first value. - if ($quote_pos > 1) { - $values_array[0] = trim(substr($values_array[0], 0, $quote_pos - 1)); - } - - // 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. - $values_array_copy = $values_array; - foreach ($values_array as $key => $value) { - if (strpos($value, '"') !== FALSE || strpos($value, "'") !== FALSE) { - unset($values_array[$key]); - } - elseif ($key !== 0) { - break; - } - } - - if (empty($values_array)) { - // See if there's a "safe" fallback that is quoted. - $values_array = $values_array_copy; - foreach ($values_array as $key => $value) { - if (strpos($value, '"') !== FALSE || strpos($value, "'") !== FALSE) { - if ($key !== 0) { - $lower_key = trim(trim(strtolower(trim($value)), '"'), "'"); - if (!empty($safe_fonts_list[$lower_key])) { - break; - } - } - unset($values_array[$key]); - } - elseif ($key !== 0) { - break; - } - } - if (empty($values_array)) { - // No unquoted values left; do not modify the css. - $key = array_shift($values_array_copy); - $fonts_with_no_replacements[$key] = implode(',', $values_array_copy); - continue; - } - } - - $extra = ''; - if (isset($values_array[0])) { - $extra = $values_array[0] . ' '; - unset($values_array[0]); - } - // 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 = $extra . 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 (stripos($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. - // Regexp to match comment blocks. - // Regexp to match double quoted strings. - // Regexp to match single quoted strings. - $comment = '/\*[^*]*\*+(?:[^/*][^*]*\*+)*/'; - $double_quot = '"[^"\\\\]*(?:\\\\.[^"\\\\]*)*"'; - $single_quot = "'[^'\\\\]*(?:\\\\.[^'\\\\]*)*'"; - // Strip all comment blocks, but keep double/single quoted strings. - $selectors_stripped = preg_replace( - "<($double_quot|$single_quot)|$comment>Ss", - "$1", - $selectors - ); - - // Add css class to all the selectors. - $selectors_array = explode(',', $selectors_stripped); - foreach ($selectors_array as &$selector) { - // Remove extra whitespace. - $selector = trim($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_string, $full_rule_set); - // Add in old rule set with new selectors. - $new_selectors_full_rule_set = $new_selectors . '{' . $property_in_loop . ': ' . $values_string . ';}'; - - // Record info. - $replacements[] = array( - $full_rule_set, - $new_values_full_rule_set, - $new_selectors_full_rule_set, - $removed_value_original, - $removed_value_classname, - $next_value_original, - ); - } - return array($replacements, $fonts_with_no_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 d7a6735cf7..4abc1d89c3 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,8 +4,9 @@ package = Advanced CSS/JS Aggregation core = 7.x dependencies[] = advagg -; Information added by Drupal.org packaging script on 2020-11-19 -version = "7.x-2.35" +; Information added by Drupal.org packaging script on 2015-04-14 +version = "7.x-2.8" core = "7.x" project = "advagg" -datestamp = "1605792717" +datestamp = "1429049283" + diff --git a/sites/all/modules/contrib/advagg/advagg_js_cdn/advagg_js_cdn.install b/sites/all/modules/contrib/advagg/advagg_js_cdn/advagg_js_cdn.install index 39502ea5c3..61f9ae193a 100644 --- a/sites/all/modules/contrib/advagg/advagg_js_cdn/advagg_js_cdn.install +++ b/sites/all/modules/contrib/advagg/advagg_js_cdn/advagg_js_cdn.install @@ -5,11 +5,6 @@ * Handles Advanced Aggregation installation and upgrade tasks. */ -/** - * @addtogroup hooks - * @{ - */ - /** * Implements hook_requirements(). */ @@ -57,13 +52,9 @@ function advagg_js_cdn_requirements($phase) { 'title' => $t('Adv JS CDN'), 'severity' => REQUIREMENT_OK, 'value' => $t('OK'), - 'description' => $t('jQuery and jQuery UI JS should be coming from a CDN.') . ' ' . $description, + 'description' => $t('jQuery & jQuery UI JS should be coming from a CDN.') . ' ' . $description, ); } return $requirements; } - -/** - * @} End of "addtogroup hooks". - */ 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 bcb164d83a..74fefb1753 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 @@ -5,11 +5,6 @@ * Advanced aggregation js cdn module. */ -/** - * @addtogroup default_variables - * @{ - */ - /** * Default value to see if jquery should be grabbed from the Google CDN. */ @@ -35,20 +30,11 @@ define('ADVAGG_JS_CDN_JQUERY_UI_VERSION', '1.8.7'); */ define('ADVAGG_JS_CDN_COMPRESSION', TRUE); -/** - * @} End of "addtogroup default_variables". - */ - -/** - * @addtogroup hooks - * @{ - */ - /** * 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; } @@ -66,11 +52,13 @@ function advagg_js_cdn_js_alter(&$javascript) { $add_in_ui = FALSE; foreach ($javascript as $name => $values) { + // @ignore sniffer_commenting_inlinecomment_spacingbefore:4 + // @ignore sniffer_whitespace_openbracketspacing_openingwhitespace:8 // Only modify if // advagg_js_cdn_jquery is enabled, // name is misc/jquery.js, // and type is file. - if (variable_get('advagg_js_cdn_jquery', ADVAGG_JS_CDN_JQUERY) + if ( variable_get('advagg_js_cdn_jquery', ADVAGG_JS_CDN_JQUERY) && $name === 'misc/jquery.js' && $javascript[$name]['type'] === 'file' ) { @@ -85,11 +73,13 @@ function advagg_js_cdn_js_alter(&$javascript) { $javascript[$name]['type'] = 'external'; } + // @ignore sniffer_commenting_inlinecomment_spacingbefore:4 + // @ignore sniffer_whitespace_openbracketspacing_openingwhitespace:8 // Only modify if // advagg_js_cdn_jquery_ui is enabled, // name is in the $ui_mapping array. // and type is file. - if (variable_get('advagg_js_cdn_jquery_ui', ADVAGG_JS_CDN_JQUERY_UI) + if ( variable_get('advagg_js_cdn_jquery_ui', ADVAGG_JS_CDN_JQUERY_UI) && array_key_exists($name, $ui_mapping) && $javascript[$name]['type'] === 'file' ) { @@ -113,10 +103,6 @@ function advagg_js_cdn_js_alter(&$javascript) { } } -/** - * @} End of "addtogroup hooks". - */ - /** * Return an array of jquery ui files. * 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 3d9a309456..bb7721ff40 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,7 +1,4 @@ // @codingStandardsIgnoreFile -/* jshint ignore:start */ -/*eslint-disable */ - /*! * jQuery UI 1.8.7 * @@ -715,7 +712,7 @@ $.widget("ui.mouse", { return this.mouseDelayMet; }, - // These are placeholder methods, to be overridden by extending plugin + // These are placeholder methods, to be overriden by extending plugin _mouseStart: function(event) {}, _mouseDrag: function(event) {}, _mouseStop: function(event) {}, @@ -2804,7 +2801,7 @@ $.widget("ui.selectable", $.ui.mouse, { }); } } else { - // DESELECT + // UNSELECT if (selectee.selecting) { if (event.metaKey && selectee.startselected) { selectee.$element.removeClass('ui-selecting'); @@ -3842,7 +3839,7 @@ $.widget("ui.sortable", $.ui.mouse, { //Various things done here to improve the performance: // 1. we create a setTimeout, that calls refreshPositions - // 2. on the instance, we have a counter variable, that gets higher after every append + // 2. on the instance, we have a counter variable, that get's higher after every append // 3. on the local scope, we copy the counter variable, and check in the timeout, if it's still the same // 4. this lets only the last addition to the timeout stack through this.counter = this.counter ? ++this.counter : 1; @@ -4959,7 +4956,7 @@ $.effects.explode = function(o) { var el = $(this).show().css('visibility', 'hidden'); var offset = el.offset(); - //Subtract the margins - not fixing the problem yet. + //Substract the margins - not fixing the problem yet. offset.top -= parseInt(el.css("marginTop"),10) || 0; offset.left -= parseInt(el.css("marginLeft"),10) || 0; @@ -7198,7 +7195,7 @@ function Datepicker() { onChangeMonthYear: null, // Define a callback function when the month or year is changed onClose: null, // Define a callback function when the datepicker is closed numberOfMonths: 1, // Number of months to show at a time - showCurrentAtPos: 0, // The position in multiple months at which to show the current month (starting at 0) + showCurrentAtPos: 0, // The position in multipe months at which to show the current month (starting at 0) stepMonths: 1, // Number of months to step back/forward stepBigMonths: 12, // Number of months to step back/forward for the big links altField: '', // Selector for an alternate field to store selected dates into @@ -7803,7 +7800,7 @@ $.extend(Datepicker.prototype, { if (inst == $.datepicker._curInst && $.datepicker._datepickerShowing && inst.input && inst.input.is(':visible') && !inst.input.is(':disabled')) inst.input.focus(); - // deferred render of the years select (to avoid flashes on Firefox) + // deffered render of the years select (to avoid flashes on Firefox) if( inst.yearshtml ){ var origyearshtml = inst.yearshtml; setTimeout(function(){ @@ -8550,20 +8547,20 @@ $.extend(Datepicker.prototype, { for (var col = 0; col < numMonths[1]; col++) { var selectedDate = this._daylightSavingAdjust(new Date(drawYear, drawMonth, inst.selectedDay)); var cornerClass = ' ui-corner-all'; - var calendar = ''; + var calender = ''; if (isMultiMonth) { - calendar += '<div class="ui-datepicker-group'; + calender += '<div class="ui-datepicker-group'; if (numMonths[1] > 1) switch (col) { - case 0: calendar += ' ui-datepicker-group-first'; + case 0: calender += ' ui-datepicker-group-first'; cornerClass = ' ui-corner-' + (isRTL ? 'right' : 'left'); break; - case numMonths[1]-1: calendar += ' ui-datepicker-group-last'; + case numMonths[1]-1: calender += ' ui-datepicker-group-last'; cornerClass = ' ui-corner-' + (isRTL ? 'left' : 'right'); break; - default: calendar += ' ui-datepicker-group-middle'; cornerClass = ''; break; + default: calender += ' ui-datepicker-group-middle'; cornerClass = ''; break; } - calendar += '">'; + calender += '">'; } - calendar += '<div class="ui-datepicker-header ui-widget-header ui-helper-clearfix' + cornerClass + '">' + + calender += '<div class="ui-datepicker-header ui-widget-header ui-helper-clearfix' + cornerClass + '">' + (/all|left/.test(cornerClass) && row == 0 ? (isRTL ? next : prev) : '') + (/all|right/.test(cornerClass) && row == 0 ? (isRTL ? prev : next) : '') + this._generateMonthYearHeader(inst, drawMonth, drawYear, minDate, maxDate, @@ -8576,7 +8573,7 @@ $.extend(Datepicker.prototype, { thead += '<th' + ((dow + firstDay + 6) % 7 >= 5 ? ' class="ui-datepicker-week-end"' : '') + '>' + '<span title="' + dayNames[day] + '">' + dayNamesMin[day] + '</span></th>'; } - calendar += thead + '</tr></thead><tbody>'; + calender += thead + '</tr></thead><tbody>'; var daysInMonth = this._getDaysInMonth(drawYear, drawMonth); if (drawYear == inst.selectedYear && drawMonth == inst.selectedMonth) inst.selectedDay = Math.min(inst.selectedDay, daysInMonth); @@ -8584,7 +8581,7 @@ $.extend(Datepicker.prototype, { var numRows = (isMultiMonth ? 6 : Math.ceil((leadDays + daysInMonth) / 7)); // calculate the number of rows to generate var printDate = this._daylightSavingAdjust(new Date(drawYear, drawMonth, 1 - leadDays)); for (var dRow = 0; dRow < numRows; dRow++) { // create date picker rows - calendar += '<tr>'; + calender += '<tr>'; var tbody = (!showWeek ? '' : '<td class="ui-datepicker-week-col">' + this._get(inst, 'calculateWeek')(printDate) + '</td>'); for (var dow = 0; dow < 7; dow++) { // create date picker days @@ -8616,16 +8613,16 @@ $.extend(Datepicker.prototype, { printDate.setDate(printDate.getDate() + 1); printDate = this._daylightSavingAdjust(printDate); } - calendar += tbody + '</tr>'; + calender += tbody + '</tr>'; } drawMonth++; if (drawMonth > 11) { drawMonth = 0; drawYear++; } - calendar += '</tbody></table>' + (isMultiMonth ? '</div>' + + calender += '</tbody></table>' + (isMultiMonth ? '</div>' + ((numMonths[0] > 0 && col == numMonths[1]-1) ? '<div class="ui-datepicker-row-break"></div>' : '') : ''); - group += calendar; + group += calender; } html += group; } @@ -11513,5 +11510,3 @@ $.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 461abcb255..74cfb727e2 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 @@ -8,14 +8,12 @@ /** * Form builder; Configure advagg settings. * - * @ingroup advagg_forms + * @ingroup forms * * @see system_settings_form() */ function advagg_js_compress_admin_settings_form($form, $form_state) { drupal_set_title(t('AdvAgg: JS Compression')); - $requirements = advagg_js_compress_check_cache_bin(); - advagg_display_message_if_requirements_not_met($requirements); $config_path = advagg_admin_config_root_path(); $form = array(); @@ -24,27 +22,37 @@ function advagg_js_compress_admin_settings_form($form, $form_state) { '#markup' => '<p>' . t('The settings below will not have any effect because AdvAgg is currently in <a href="@devel">development mode</a>. Once the cache settings have been set to normal or aggressive, JS minification will take place.', array('@devel' => url($config_path . '/advagg', array('fragment' => 'edit-advagg-cache-level')))) . '</p>', ); } - list($list, $redo_list) = advagg_js_compress_all_js_files_list(); - if (!empty($redo_list)) { - $form['advagg_js_compressor_batch'] = array( - '#markup' => '<p>' . t('There are %count js files that need to be minified. <a href="@batch">Click this batch compress link to process these files.</a>', array( - '@batch' => url($config_path . '/advagg/js-compress/batch'), - '%count' => count($redo_list), - )) . '</p>', - ); - $form['advagg_js_compressor_redo_list'] = array( - '#type' => 'fieldset', - '#title' => t('List of files that need to be recompressed'), - '#collapsible' => TRUE, - '#collapsed' => TRUE, - ); - $form['advagg_js_compressor_redo_list']['list'] = array( - '#markup' => '<p><pre><tt>' . (print_r($redo_list, TRUE)) . '</tt></pre></p>', + $description = ''; + $options = array( + 0 => t('Disabled'), + 1 => t('JSMin+ ~1300ms'), + // 2 => t('Packer ~500ms'), + // 3 is JSMin c extension. + // 4 is JShrink. + // 5 is JSqueeze. + ); + if (function_exists('jsmin')) { + $options[3] = t('JSMin ~2ms'); + $description .= t('JSMin is the very fast C complied version. Recommend using it.'); + } + else { + if (!defined('PHP_VERSION_ID') || PHP_VERSION_ID < 50310) { + $link = 'http://www.ypass.net/software/php_jsmin/'; + } + else { + $link = 'https://github.com/sqmk/pecl-jsmin/'; + } + $description .= t('You can use the much faster C version of JSMin (~2ms) by installing the <a href="@php_jsmin">JSMin PHP Extension</a> on this server.', array('@php_jsmin' => $link)); + } + // Add in JShrink & JSqueeze if using php 5.3 or higher. + if (defined('PHP_VERSION_ID') & PHP_VERSION_ID >= 50300) { + $options += array( + 4 => t('JShrink ~1000ms'), + 5 => t('JSqueeze ~600ms'), ); } - list($options, $description) = advagg_js_compress_configuration(); $form['advagg_js_compressor'] = array( '#type' => 'radios', '#title' => t('File Compression: Select a Compressor'), @@ -72,44 +80,44 @@ function advagg_js_compress_admin_settings_form($form, $form_state) { ), ); - if (variable_get('advagg_gzip', ADVAGG_GZIP) || variable_get('advagg_brotli', ADVAGG_BROTLI)) { - $form['advagg_js_compress_packer'] = array( - '#type' => 'checkbox', - '#title' => t('Here there be dragons! Use Packer on non gz/br JS Aggregates.'), - '#default_value' => variable_get('advagg_js_compress_packer', ADVAGG_JS_COMPRESS_PACKER), - '#description' => t('If enabled the non gzip/brotli version of JS files will be compressed using the JS Packer. Packer works similar to gz/br, thus using packer on a gzip/brotli file does not give a big improvement in terms of bytes transferred over the wire. WARNING: This has a high chance of breaking your JS. Only Enable on production after testing the non gzip/brotli version locally.'), - '#states' => array( - 'disabled' => array( - ':input[name="advagg_js_compressor"]' => array('value' => "0"), - ), + $form['advagg_js_compress_packer'] = array( + '#type' => 'checkbox', + '#title' => t('Use Packer on non GZip JS Aggregates'), + '#default_value' => variable_get('advagg_js_compress_packer', ADVAGG_JS_COMPRESS_PACKER), + '#description' => t('If enabled the non gzip version of JS files will be compressed using the JS Packer. Packer works similar to gzip, thus using packer on a gzipped file does not give a big improvement in terms of bytes transferred over the wire. WARNING: This has a high chance of breaking your JS. Only Enable on production after testing the non gzipped version locally.'), + '#states' => array( + 'disabled' => array( + ':input[name="advagg_js_compressor"]' => array('value' => "0"), ), - ); - } + ), + ); $form['advagg_js_compress_add_license'] = array( - '#type' => 'radios', - '#title' => t('Licensing comments'), + '#type' => 'checkbox', + '#title' => t('Add licensing comments'), '#default_value' => variable_get('advagg_js_compress_add_license', ADVAGG_JS_COMPRESS_ADD_LICENSE), - '#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 (smallest files)'), - 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'), - ), + '#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."), ); $options[-1] = t('Default'); ksort($options); + $form['per_file_settings'] = array( '#type' => 'fieldset', '#title' => t('Per File Settings'), '#collapsible' => TRUE, '#collapsed' => TRUE, ); + // Get filename & filename_hash. + $results = db_select('advagg_files', 'af') + ->fields('af', array('filename')) + ->condition('filetype', 'js') + ->orderBy('af.filename', 'ASC') + ->execute(); $file_settings = variable_get('advagg_js_compressor_file_settings', array()); - foreach ($list as $row) { - $dir = dirname($row['data']); + foreach ($results as $row) { + $dir = dirname($row->filename); if (!isset($form['per_file_settings'][$dir])) { $form['per_file_settings'][$dir] = array( '#type' => 'fieldset', @@ -118,25 +126,12 @@ function advagg_js_compress_admin_settings_form($form, $form_state) { '#collapsed' => TRUE, ); } - $options_copy = $options; - if (!empty($row['advagg_js_compress'])) { - foreach ($row['advagg_js_compress'] as $key => $values) { - if (!isset($options_copy[$key])) { - continue; - } - if ($values['code'] != 1) { - unset($options_copy[$key]); - continue; - } - $options_copy[$key] .= " Ratio: {$values['ratio']}"; - } - } - $form_api_filename = str_replace(array('/', '.'), array('__', '--'), $row['data']); + $form_api_filename = str_replace(array('/', '.'), array('__', '--'), $row->filename); $form['per_file_settings'][$dir]['advagg_js_compressor_file_settings_' . $form_api_filename] = array( '#type' => 'radios', - '#title' => t('%filename: Select a Compressor', array('%filename' => $row['data'])), + '#title' => t('%filename: Select a Compressor', array('%filename' => $row->filename)), '#default_value' => isset($file_settings[$form_api_filename]) ? $file_settings[$form_api_filename] : ADVAGG_JS_COMPRESSOR_FILE_SETTINGS, - '#options' => $options_copy, + '#options' => $options, ); if ($form['per_file_settings'][$dir]['advagg_js_compressor_file_settings_' . $form_api_filename]['#default_value'] != ADVAGG_JS_COMPRESSOR_FILE_SETTINGS) { $form['per_file_settings'][$dir]['#collapsed'] = FALSE; @@ -144,26 +139,23 @@ function advagg_js_compress_admin_settings_form($form, $form_state) { } } - // No js files are found. - if (empty($list)) { - $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'; return system_settings_form($form); } +// Submit callback. /** - * Submit callback, clear out the advagg cache bin. + * Clear out the advagg cache bin when the save configuration button is pressed. * * Also remove default settings inside of the per_file_settings fieldgroup. - * - * @ingroup advagg_forms_callback */ function advagg_js_compress_admin_settings_form_submit($form, &$form_state) { - advagg_cache_clear_admin_submit(); + $cache_bins = advagg_flush_caches(); + foreach ($cache_bins as $bin) { + cache_clear_all('*', $bin, TRUE); + } // Get current defaults. $file_settings = variable_get('advagg_js_compressor_file_settings', array()); @@ -171,7 +163,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; } @@ -181,9 +173,6 @@ function advagg_js_compress_admin_settings_form_submit($form, &$form_state) { continue; } $new_settings[substr($key, 35)] = $value; - - // Do not save this field into its own variable. - unset($form_state['values'][$key]); } if (!empty($new_settings) || !empty($file_settings)) { if (empty($new_settings)) { 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 d05a1f7bd0..86cb5bb114 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 @@ -5,112 +5,89 @@ * Advanced CSS/JS aggregation js compression module. */ -if (defined('PHP_VERSION_ID') && constant('PHP_VERSION_ID') >= 50300) { - // Include functions that use namespaces. - module_load_include('inc', 'advagg_js_compress', 'advagg_js_compress.php53'); -} - -/** - * @addtogroup advagg_hooks - * @{ - */ - /** * Implements hook_advagg_get_info_on_files_alter(). * * Used to make sure the info is up to date in the cache. */ function advagg_js_compress_advagg_get_info_on_files_alter(&$return, $cached_data, $bypass_cache) { - $compressor_list = advagg_js_compress_get_enabled_compressors(array(), -1); + // Do nothing if compressors are disabled. + $compressors = advagg_js_compress_get_enabled_compressors(); + if (empty($compressors)) { + return; + } - // Get cache ids. $cache_ids = array(); foreach ($return as $filename => &$info) { if (empty($info['fileext']) || $info['fileext'] !== 'js') { continue; } - // Check the cache. - $cache_id = 'advagg:js_compress:info:' . $info['filename_hash']; - $cache_id .= !empty($info['content_hash']) ? ':' . $info['content_hash'] : ''; - $cache_ids[$filename] = $cache_id; - - // Verify current data. - $advagg_js_compress = array(); - if (!empty($info['advagg_js_compress'])) { - foreach ($info['advagg_js_compress'] as $values) { - $array_key = array_search($values['name'], $compressor_list); - if ($array_key !== FALSE) { - $cache_hits_data[$array_key] = $values; + // New or updated data or no advagg_js_compress data. + // @ignore sniffer_whitespace_openbracketspacing_openingwhitespace + if ( empty($cached_data[$info['cache_id']]) + || empty($info['advagg_js_compress']) + || $info['content_hash'] != $cached_data[$info['cache_id']]['content_hash'] + ) { + // Check the cache. + $cache_id = 'advagg:js_compress:info:' . $info['filename_hash']; + $cache_id .= !empty($info['content_hash']) ? ':' . $info['content_hash'] : ''; + $cache_ids[$filename] = $cache_id; + } + else { + foreach ($compressors as $id => $name) { + if (empty($info['advagg_js_compress'][$id])) { + $cache_id = 'advagg:js_compress:info:' . $info['filename_hash']; + $cache_id .= !empty($info['content_hash']) ? ':' . $info['content_hash'] : ''; + $cache_ids[$filename] = $cache_id; + break; } } } - ksort($advagg_js_compress); - $info['advagg_js_compress'] = $advagg_js_compress; } - unset($info); + // If no cache ids are found bail out. if (empty($cache_ids)) { return; } // Get cached values. - $values = array_values($cache_ids); - $cache_hits = cache_get_multiple($values, 'cache_advagg_info'); - $compressors = advagg_js_compress_get_enabled_compressors(); - $advagg_get_info_on_file_cached_data = drupal_static('advagg_get_info_on_file'); + $cache_hits = array(); + if (!$bypass_cache) { + $values = array_values($cache_ids); + $cache_hits = cache_get_multiple($values, 'cache_advagg_info'); + } - // Add cached values into $return. $filenames_info = array(); foreach ($cache_ids as $filename => $cache_id) { $info = &$return[$filename]; - // Add in cached values. if (!empty($cache_hits[$cache_id])) { - // Verify cache data. - $cache_hits_data = array(); - foreach ($cache_hits[$cache_id]->data as $values) { - $array_key = array_search($values['name'], $compressor_list); - if ($array_key !== FALSE) { - $cache_hits_data[$array_key] = $values; + $info['advagg_js_compress'] = $cache_hits[$cache_id]->data; + foreach ($compressors as $id => $name) { + if (empty($info['advagg_js_compress'][$id])) { + // Generate values. + $filenames_info[$filename] = $info; + break; } } - ksort($cache_hits_data); - - $info['advagg_js_compress'] = array_replace($info['advagg_js_compress'], $cache_hits_data); } - - // Generate missing values if needed. - foreach ($compressors as $id => $name) { - if (empty($info['advagg_js_compress'][$id])) { - $filenames_info[$filename] = $info; - break; - } - // Generate values if bypass cache is set and hashes do not match. - if ($bypass_cache - && (empty($advagg_get_info_on_file_cached_data[$info['cache_id']]['content_hash']) - || $info['content_hash'] !== $advagg_get_info_on_file_cached_data[$info['cache_id']]['content_hash'] - ) - ) { - $filenames_info[$filename] = $info; - break; - } + // Generate values. + else { + $filenames_info[$filename] = $info; } } - // Do nothing if compressors are disabled or cache level does not equal 0. - if (empty($compressors) || variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL) != 0) { - return; - } - if (!empty($filenames_info)) { $results = advagg_js_compress_run_mutiple_tests($filenames_info, $compressors); foreach ($results as $filename => $data) { $info = &$return[$filename]; if (!empty($info['advagg_js_compress'])) { - $data += $info['advagg_js_compress']; + $info['advagg_js_compress'] += $data; + } + else { + $info['advagg_js_compress'] = $data; } - $info['advagg_js_compress'] = $data; } } } @@ -140,18 +117,12 @@ function advagg_js_compress_advagg_get_js_file_contents_alter(&$contents, $filen $info = advagg_get_info_on_file($filename); if (!isset($info['advagg_js_compress'][$compressor]['code'])) { // Test file here on the spot. - if (variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL) == 0 - || variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL) == 1 - ) { - $compressors_to_test = advagg_js_compress_get_enabled_compressors($aggregate_settings); - $info['advagg_js_compress'] = advagg_js_compress_run_test($filename, $info, $compressors_to_test); - } + $compressors_to_test = advagg_js_compress_get_enabled_compressors($aggregate_settings); + $info['advagg_js_compress'] = advagg_js_compress_run_test($filename, $info, $compressors_to_test); } // Compress it if it passes the test. - if (!empty($info['advagg_js_compress'][$compressor]['code']) - && $info['advagg_js_compress'][$compressor]['code'] == 1 - ) { + if (!empty($info['advagg_js_compress'][$compressor]['code']) && $info['advagg_js_compress'][$compressor]['code'] == 1) { advagg_js_compress_prep($contents, $filename, $aggregate_settings); } } @@ -159,25 +130,23 @@ function advagg_js_compress_advagg_get_js_file_contents_alter(&$contents, $filen /** * Implements hook_advagg_save_aggregate_alter(). * - * Used to add in a .gz file if none exits and use packer on non gzip file. + * Used to add in a .gz file if none exits & use packer on non gzip file. */ function advagg_js_compress_advagg_save_aggregate_alter(&$files_to_save, $aggregate_settings, $other_parameters) { list($files, $type) = $other_parameters; - // Return if gzip and brotli are disabled. + // Return if gzip is disabled. // Return if packer is disabled. // Return if type is not js. - if ((empty($aggregate_settings['variables']['advagg_gzip']) && empty($aggregate_settings['variables']['advagg_brotli'])) + // @ignore sniffer_whitespace_openbracketspacing_openingwhitespace + if ( empty($aggregate_settings['variables']['advagg_gzip']) || empty($aggregate_settings['variables']['advagg_js_compress_packer']) || $type !== 'js' ) { return; } - // Use the first file in the array. - $data = reset($files_to_save); - $uri = key($files_to_save); - // Use packer on non gzip/brotli js files. + // Use packer on non gzip js files. $compressor = 2; module_load_include('inc', 'advagg', 'advagg'); @@ -192,7 +161,8 @@ function advagg_js_compress_advagg_save_aggregate_alter(&$files_to_save, $aggreg } // If this file causes php to bomb or the ratio is way too good then do not // use packer on this aggregate. - if (!isset($info['advagg_js_compress'][$compressor]['code']) + // @ignore sniffer_whitespace_openbracketspacing_openingwhitespace + if ( !isset($info['advagg_js_compress'][$compressor]['code']) || $info['advagg_js_compress'][$compressor]['code'] == -1 || $info['advagg_js_compress'][$compressor]['code'] == -3 ) { @@ -200,16 +170,38 @@ function advagg_js_compress_advagg_save_aggregate_alter(&$files_to_save, $aggreg } } - // Use packer on non gzip/brotli JS data. + // See if a .gz file of this aggregate already exists. + $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 (!empty($pos)) { + $len = strlen($uri); + // .gz file exists, exit loop. + if ($pos == $len - 3) { + $gzip_exists = TRUE; + break; + } + } + } + + // Use the first file in the array. + $data = reset($files_to_save); + $uri = key($files_to_save); + + // If a .gz file does not exist, create one. + if (!$gzip_exists) { + // Compress it and add it to the $files_to_save array. + $compressed = gzencode($data, 9, FORCE_GZIP); + $files_to_save[$uri . '.gz'] = $compressed; + } + + // Use packer on non gzip JS data. $aggregate_settings['variables']['advagg_js_compressor'] = $compressor; advagg_js_compress_prep($data, $uri, $aggregate_settings, FALSE); $files_to_save[$uri] = $data; } -/** - * @} End of "addtogroup advagg_hooks". - */ - /** * Get a list of enabled compressors. * @@ -220,10 +212,22 @@ function advagg_js_compress_advagg_save_aggregate_alter(&$files_to_save, $aggreg */ function advagg_js_compress_get_enabled_compressors(array $aggregate_settings = array(), $compressor = 0) { // Create array. - list(, , $compressors) = advagg_js_compress_configuration(); + $compressors = array( + 1 => 'jsminplus', + 2 => 'packer', + ); + if (function_exists('jsmin')) { + $compressors[3] = 'jsmin'; + } if ($compressor == -1) { return $compressors; } + if (defined('PHP_VERSION_ID') & PHP_VERSION_ID >= 50300) { + $compressors += array( + 4 => 'jshrink', + 5 => 'jsqueeze', + ); + } $return_compressors = array(); if (!empty($compressor)) { @@ -244,8 +248,8 @@ function advagg_js_compress_get_enabled_compressors(array $aggregate_settings = $packer = variable_get('advagg_js_compress_packer', ADVAGG_JS_COMPRESS_PACKER); } - if (isset($compressors[$file])) { - $return_compressors[$file] = $compressors[$file]; + if ($file == 1) { + $return_compressors[1] = $compressors[1]; } if ($packer) { $return_compressors[2] = $compressors[2]; @@ -264,115 +268,15 @@ function advagg_js_compress_get_enabled_compressors(array $aggregate_settings = $return_compressors[1] = $compressors[1]; } } - } - - return $return_compressors; -} - -/** - * Compress a JS string. - * - * @param string $contents - * Javascript string. - * @param array $info - * Info about the js file. - * @param int $compressor - * Use a particular compressor. - * @param bool $force_run - * TRUE to skip cache and force the compression test. - * @param array $aggregate_settings - * The aggregate_settings array. - * @param bool $add_licensing - * FALSE to remove Source and licensing information comment. - * @param bool $log_errors - * FALSE to disable logging to watchdog on failure. - * - * @return bool - * FALSE if there was an error. - */ -function advagg_js_compress_do_it(&$contents, array $info, $compressor, $force_run, array $aggregate_settings, $log_errors) { - $no_errors = TRUE; - - // Try cache. - $content_hash = !empty($info['content_hash']) ? ":{$info['content_hash']}" : ''; - $cache_id = "advagg:js_compress:{$compressor}:{$info['filename_hash']}:{$content_hash}"; - - $cache = cache_get($cache_id, 'cache_advagg_aggregates'); - $force_run = variable_get('advagg_js_compress_force_run', $force_run); - if (!$force_run && !empty($cache->data)) { - $contents = $cache->data; - } - else { - // Strip Byte Order Marks (BOM's), preg_* cannot parse these well. - $contents = str_replace(pack("CCC", 0xef, 0xbb, 0xbf), "", $contents); - // Jsmin may have errors (incorrectly determining EOLs) with mixed tabs - // and spaces. An example: jQuery.Cycle 3.0.3 - http://jquery.malsup.com/ - if ($compressor == 3) { - $contents = str_replace("\t", " ", $contents); + if ($file == 4) { + $return_compressors[4] = $compressors[4]; } - - // Add end string to get true end of file. - // Generate random variable contents and add to end of js string. - $random = dechex(mt_rand()); - $end_string = "var advagg_end=\"$random\";"; - $contents .= "\n$end_string"; - - // Use the compressor. - list(, , , $functions) = advagg_js_compress_configuration(); - if (isset($functions[$compressor])) { - $run = $functions[$compressor]; - if (function_exists($run)) { - $no_errors = $run($contents, $log_errors, $aggregate_settings); - } - } - else { - return; - } - - // Get location of random variable. - $end_string = substr($end_string, 0, -1); - $pos = strrpos($contents, $end_string); - if ($pos === FALSE) { - $end_string = str_replace('"', "'", $end_string); - $pos = strrpos($contents, $end_string); - } - if ($pos !== FALSE) { - // Cut everything after random variable out of string. - $contents = substr($contents, 0, $pos); - } - - // 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. - if ($compressor == 3 || $compressor == 5) { - $a = strrpos($contents, ';'); - $b = strrpos($contents, '}'); - $c = strrpos($contents, ')'); - - // Simple scripts can just end, check to make sure there's a - // match before cutting. - if ($a !== FALSE || $b !== FALSE || $c !== FALSE) { - $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. - $contents = trim($contents) . ';'; - } - - if (empty($info['#no_cache'])) { - // Cache minified data for 1 week. - cache_set($cache_id, $contents, 'cache_advagg_aggregates', REQUEST_TIME + (86400 * 7)); - } - else { - // Cache minified inline data for 1 hour. - cache_set($cache_id, $contents, 'cache_advagg_aggregates', REQUEST_TIME + 3600); + if ($file == 5) { + $return_compressors[5] = $compressors[5]; } } - return $no_errors; + + return $return_compressors; } /** @@ -390,14 +294,8 @@ function advagg_js_compress_do_it(&$contents, array $info, $compressor, $force_r * FALSE to disable logging to watchdog on failure. * @param bool $test_ratios * FALSE to disable compression ratio testing. - * @param bool $force_run - * TRUE to skip cache and force the compression test. - * - * @return bool - * FALSE if there was an error. */ -function advagg_js_compress_prep(&$contents, $filename, array $aggregate_settings, $add_licensing = TRUE, $log_errors = TRUE, $test_ratios = TRUE, $force_run = FALSE) { - $no_errors = TRUE; +function advagg_js_compress_prep(&$contents, $filename, array $aggregate_settings, $add_licensing = TRUE, $log_errors = TRUE, $test_ratios = TRUE) { // Get the info on this file. module_load_include('inc', 'advagg', 'advagg'); $compressor = $aggregate_settings['variables']['advagg_js_compressor']; @@ -405,22 +303,9 @@ function advagg_js_compress_prep(&$contents, $filename, array $aggregate_setting if ($compressor == 0) { return; } - // Do nothing if the file is already minified. - $url = file_create_url($filename); - $semicolon_count = substr_count($contents, ';'); - if ($compressor != 2 - && $semicolon_count > 10 - && $semicolon_count > (substr_count($contents, "\n", strpos($contents, ';')) * 5) - && !$force_run - ) { - $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)) { - $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"; - return; - } + // Do nothing if the cache settings are set to Development. + if (variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL) < 0) { + return; } // Get the JS string length before the compression operation. @@ -429,7 +314,7 @@ function advagg_js_compress_prep(&$contents, $filename, array $aggregate_setting // Do not use jsmin() if the function can not be called. if ($compressor == 3 && !function_exists('jsmin')) { - if (defined('PHP_VERSION_ID') && constant('PHP_VERSION_ID') >= 50300) { + if (defined('PHP_VERSION_ID') & PHP_VERSION_ID >= 50300) { $compressor = 5; watchdog('advagg_js_compress', 'The jsmin function does not exist. Using JSqueeze.'); } @@ -439,24 +324,44 @@ 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.'); + // Try cache. + $info = advagg_get_info_on_files(array($filename), FALSE, FALSE); + $info = $info[$filename]; + $cache_id = 'advagg:js_compress:' . $compressor . ':' . $info['filename_hash']; + $cache_id .= !empty($info['content_hash']) ? ':' . $info['content_hash'] : ''; + $cache = cache_get($cache_id, 'cache_advagg_aggregates'); + if (!empty($cache->data)) { + $contents = $cache->data; + } + else { + // Strip Byte Order Marks (BOM's), preg_* cannot parse these well. + $contents = str_replace(pack("CCC", 0xef, 0xbb, 0xbf), "", $contents); + // Use the compressor. + if ($compressor == 1) { + advagg_js_compress_jsminplus($contents, $log_errors); } - 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.'); + elseif ($compressor == 2) { + advagg_js_compress_jspacker($contents); + } + elseif ($compressor == 3) { + $contents = jsmin($contents); + } + elseif ($compressor == 4) { + advagg_js_compress_jshrink($contents); + } + elseif ($compressor == 5) { + advagg_js_compress_jsqueeze($contents); } - } - $info = advagg_get_info_on_files(array($filename), FALSE, FALSE); - $info = $info[$filename]; + // Ensure that $contents ends with ; or }. + if (strpbrk(substr(trim($contents), -1), ';}') === FALSE) { + // ; or } not found, add in ; to the end of $contents. + $contents = trim($contents) . ';'; + } - $no_errors = advagg_js_compress_do_it($contents, $info, $compressor, $force_run, $aggregate_settings, $log_errors, $url); + // Cache minified data for at least 1 week. + cache_set($cache_id, $contents, 'cache_advagg_aggregates', REQUEST_TIME + (86400 * 7)); + } // Make sure compression ratios are good. $after = strlen($contents); @@ -465,79 +370,22 @@ function advagg_js_compress_prep(&$contents, $filename, array $aggregate_setting $ratio = ($before - $after) / $before; } - // Get ratios settings. - $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); - $aggregate_settings['variables']['advagg_js_compress_ratio'] - = isset($aggregate_settings['variables']['advagg_js_compress_ratio']) ? - $aggregate_settings['variables']['advagg_js_compress_ratio'] : - variable_get('advagg_js_compress_ratio', ADVAGG_JS_COMPRESS_RATIO); - - // Get license settings. - $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) - || empty($ratio) - || $ratio < $aggregate_settings['variables']['advagg_js_compress_ratio'] - || $ratio > $aggregate_settings['variables']['advagg_js_compress_max_ratio'] - ) { - $contents = $contents_before; - if ($compressor !== 1) { - // Try again using jsmin+. - $no_errors = advagg_js_compress_do_it($contents, $info, 1, $force_run, $aggregate_settings, $log_errors, $url); - // Make sure compression ratios are good. - $after = strlen($contents); - $ratio = 0; - if ($before != 0) { - $ratio = ($before - $after) / $before; - } - if (empty($contents) - || empty($ratio) - || $ratio < $aggregate_settings['variables']['advagg_js_compress_ratio'] - || $ratio > $aggregate_settings['variables']['advagg_js_compress_max_ratio'] - ) { - $contents = $contents_before; - $add_licensing = FALSE; - } + 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); + // Make sure the returned string is not empty or has a VERY high + // compression ratio. + if ( empty($contents) + || empty($ratio) + || $ratio < 0 + || $ratio > $aggregate_settings['variables']['advagg_js_compress_max_ratio'] + ) { + $contents = $contents_before; + } + elseif ($add_licensing && variable_get('advagg_js_compress_add_license', ADVAGG_JS_COMPRESS_ADD_LICENSE)) { + $url = url($filename, array('absolute' => TRUE)); + $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"; } } - - if ($add_licensing && ($add_license_setting == 1 || $add_license_setting == 3)) { - $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"; - } - - // Reset if cache settings are set to Development. - if (variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL) < 0 && !$force_run) { - $contents = $contents_before; - } - return $no_errors; -} - -/** - * 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; } /** @@ -549,130 +397,141 @@ function advagg_js_compress_string_contains_multibyte_characters($string) { * FALSE to disable logging to watchdog on failure. */ function advagg_js_compress_jsminplus(&$contents, $log_errors = TRUE) { - $no_errors = TRUE; $contents_before = $contents; - $old_error = error_get_last(); - // Set max nesting level. + // Only include jsminplus.inc if the JSMinPlus class doesn't exist. if (!class_exists('JSMinPlus')) { + include drupal_get_path('module', '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); } } - - // Try libraries for jsminplus. - if (is_callable('libraries_load')) { - libraries_load('jsminplus'); - } - // Only include jsminplus.inc if the JSMinPlus class doesn't exist. - if (!class_exists('JSMinPlus')) { - include drupal_get_path('module', 'advagg_js_compress') . '/jsminplus.inc'; - } - ob_start(); try { // JSMin+ the contents of the aggregated file. - $contents = @JSMinPlus::minify($contents); + $contents = JSMinPlus::minify($contents); // Capture any output from JSMinPlus. $error = trim(ob_get_contents()); if (!empty($error)) { - $no_errors = FALSE; - throw new Exception($error); - } - $recent_error = error_get_last(); - if (!empty($recent_error) && serialize($recent_error) !== serialize($old_error)) { - $no_errors = FALSE; - $error = print_r($recent_error, TRUE); throw new Exception($error); } } catch (Exception $e) { - $no_errors = FALSE; // Log the exception thrown by JSMin+ and roll back to uncompressed content. if ($log_errors) { - watchdog('advagg_js_compress', '@message <pre> @contents </pre>', array( - '@message' => $e->getMessage(), - '@contents' => $contents_before, - ), WATCHDOG_WARNING); + watchdog('advagg_js_compress', $e->getMessage() . '<pre>' . $contents_before . '</pre>', NULL, WATCHDOG_WARNING); } $contents = $contents_before; } ob_end_clean(); - return $no_errors; } /** - * Compress a JS string using packer. + * Compress a JS string using jshrink. * * @param string $contents * Javascript string. * @param bool $log_errors * FALSE to disable logging to watchdog on failure. */ -function advagg_js_compress_jspacker(&$contents, $log_errors = TRUE) { - $no_errors = TRUE; +function advagg_js_compress_jshrink(&$contents, $log_errors = TRUE) { $contents_before = $contents; - // Try libraries for jspacker. - if (is_callable('libraries_load')) { - libraries_load('jspacker'); - } - if (!class_exists('JavaScriptPacker')) { - include drupal_get_path('module', 'advagg_js_compress') . '/jspacker.inc'; + // Only include jshrink.inc if the JShrink\Minifier class doesn't exist. + if (!class_exists('JShrink\Minifier')) { + include drupal_get_path('module', 'advagg_js_compress') . '/jshrink.inc'; + $nesting_level = ini_get('xdebug.max_nesting_level'); + if (!empty($nesting_level) && $nesting_level < 200) { + ini_set('xdebug.max_nesting_level', 200); + } } - - // Add semicolons to the end of lines if missing. - $contents = str_replace("}\n", "};\n", $contents); - $contents = str_replace("\nfunction", ";\nfunction", $contents); - - // Use Packer on the contents of the aggregated file. + ob_start(); try { - $packer = new JavaScriptPacker($contents, 62, TRUE, FALSE); - $contents = $packer->pack(); + // JShrink the contents of the aggregated file. + $contents = JShrink\Minifier::minify($contents, array('flaggedComments' => FALSE)); + + // Capture any output from JShrink. + $error = trim(ob_get_contents()); + if (!empty($error)) { + throw new Exception($error); + } } catch (Exception $e) { - $no_errors = FALSE; - // Log the exception thrown by JSMin+ and roll back to uncompressed content. + // Log the exception thrown by JShrink & roll back to uncompressed content. if ($log_errors) { - watchdog('advagg_js_compress', '@message <pre> @contents </pre>', array( - '@message' => $e->getMessage(), - '@contents' => $contents_before, - ), WATCHDOG_WARNING); + watchdog('advagg_js_compress', $e->getMessage() . '<pre>' . $contents_before . '</pre>', NULL, WATCHDOG_WARNING); } $contents = $contents_before; } - return $no_errors; + ob_end_clean(); } /** - * Compress a JS string using jsmin. + * Compress a JS string using jsqueeze. * * @param string $contents * Javascript string. * @param bool $log_errors * FALSE to disable logging to watchdog on failure. */ -function advagg_js_compress_jsmin(&$contents, $log_errors = TRUE) { - $no_errors = TRUE; +function advagg_js_compress_jsqueeze(&$contents, $log_errors = TRUE) { $contents_before = $contents; + // Only include jshrink.inc if the Patchwork\JSqueeze class doesn't exist. + if (!class_exists('Patchwork\JSqueeze')) { + include drupal_get_path('module', 'advagg_js_compress') . '/jsqueeze.inc'; + $nesting_level = ini_get('xdebug.max_nesting_level'); + if (!empty($nesting_level) && $nesting_level < 200) { + ini_set('xdebug.max_nesting_level', 200); + } + } + ob_start(); try { - $contents = jsmin($contents); + // 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), + FALSE + ); + + // Capture any output from JSqueeze. + $error = trim(ob_get_contents()); + if (!empty($error)) { + throw new Exception($error); + } } catch (Exception $e) { - $no_errors = FALSE; - // Log the exception thrown by JSMin+ and roll back to uncompressed content. + // Log the exception thrown by JSqueeze & roll back to uncompressed content. if ($log_errors) { - watchdog('advagg_js_compress', '@message <pre> @contents </pre>', array( - '@message' => $e->getMessage(), - '@contents' => $contents_before, - ), WATCHDOG_WARNING); + watchdog('advagg_js_compress', $e->getMessage() . '<pre>' . $contents_before . '</pre>', NULL, WATCHDOG_WARNING); } $contents = $contents_before; } - return $no_errors; + ob_end_clean(); +} + +/** + * Compress a JS string using packer. + * + * @param string $contents + * Javascript string. + */ +function advagg_js_compress_jspacker(&$contents) { + // Use Packer on the contents of the aggregated file. + if (!class_exists('JavaScriptPacker')) { + include drupal_get_path('module', 'advagg_js_compress') . '/jspacker.inc'; + } + + // Add semicolons to the end of lines if missing. + $contents = str_replace("}\n", "};\n", $contents); + $contents = str_replace("\nfunction", ";\nfunction", $contents); + + $packer = new JavaScriptPacker($contents, 62, TRUE, FALSE); + $contents = $packer->pack(); } /** @@ -714,10 +573,8 @@ function advagg_js_compress_run_test($filename, array $info = array(), array $co foreach ($compressor_list as $key => $name) { $results[$key] = array('code' => -1, 'ratio' => 0, 'name' => $name); } - $run_locally = TRUE; // Run this via httprl if possible. if (module_exists('httprl') && httprl_is_background_callback_capable()) { - $run_locally = FALSE; // Setup callback options array. $callback_options = array( array( @@ -752,16 +609,8 @@ function advagg_js_compress_run_test($filename, array $info = array(), array $co $results[$key] = $sub_result[$key]; } } - - // Try locally if all return back -1 (something wrong with httprl). - foreach ($results as $key => $value) { - if ($value['code'] != -1) { - $run_locally = TRUE; - break; - } - } } - if ($run_locally) { + else { // Save results, so if PHP bombs, this file is marked as bad. // CACHE_PERMANENT isn't good here. Use 2 weeks from now + 0-45 days. // The random 0 to 45 day addition is to prevent a cache stampeed. @@ -772,7 +621,7 @@ function advagg_js_compress_run_test($filename, array $info = array(), array $co } } - // Save and return results. + // Save & return results. // Save results, so if PHP bombs, this file is marked as bad. // CACHE_PERMANENT isn't good here. Use 2 weeks from now + 0-45 days. // The random 0 to 45 day addition is to prevent a cache stampeed. @@ -808,18 +657,15 @@ function advagg_js_compress_run_mutiple_tests(array $filenames_info, array $comp drupal_set_time_limit(0); } else { - $max_execution_time = ini_get('max_execution_time'); - if ($max_execution_time != 0) { - $current_time = 5; - if (is_callable('getrusage')) { - $dat = getrusage(); - $current_time = $dat["ru_utime.tv_sec"]; - } - $max_time = max(30, ini_get('max_execution_time')); - $time_left = $max_time - $current_time; - // Give every file 3 seconds. - drupal_set_time_limit(count($filenames_info) * 3 + $time_left); + $current_time = 5; + if (is_callable('getrusage')) { + $dat = getrusage(); + $current_time = $dat["ru_utime.tv_sec"]; } + $max_time = max(30, ini_get('max_execution_time')); + $time_left = $max_time - $current_time; + // Give every file 3 seconds. + drupal_set_time_limit(count($filenames_info) * 3 + $time_left); } } diff --git a/sites/all/modules/contrib/advagg/advagg_js_compress/advagg_js_compress.drush.inc b/sites/all/modules/contrib/advagg/advagg_js_compress/advagg_js_compress.drush.inc deleted file mode 100644 index 9f3092ccf0..0000000000 --- a/sites/all/modules/contrib/advagg/advagg_js_compress/advagg_js_compress.drush.inc +++ /dev/null @@ -1,111 +0,0 @@ -<?php - -/** - * @file - * Drush commands for AdvAgg JS minification. - */ - -/** - * @addtogroup 3rd_party_hooks - * @{ - */ - -/** - * Implements hook_drush_help(). - */ -function advagg_js_compress_drush_help($command) { - switch ($command) { - case 'drush:advagg-js-compress': - return dt('Run js minification for all js files.'); - - } -} - -/** - * Implements hook_drush_command(). - */ -function advagg_js_compress_drush_command() { - $items = array(); - $items['advagg-js-compress'] = array( - 'callback' => 'drush_advagg_js_compress', - 'description' => dt('Run js minification.'), - 'core' => array('7+'), - 'arguments' => array( - 'filename' => 'all will do all files, or specify the filename to target that file.', - ), - 'examples' => array( - 'drush advagg-js-compress' => dt('Minify only the files that need to be done.'), - 'drush advagg-js-compress all' => dt('Minify all js files again.'), - 'drush advagg-js-compress misc/jquery.once.js' => dt('Minify the misc/jquery.once.js file.'), - ), - 'aliases' => array( - 'advagg-jsc', - 'advagg-jsmin', - '', - ), - ); - - return $items; -} - -/** - * @} End of "addtogroup 3rd_party_hooks". - */ - -/** - * Callback function for drush advagg-js-compress. - * - * Callback is called by using drush_hook_command() where - * hook is the name of the module (advagg) and command is the name of - * the Drush command with all "-" characters converted to "_" characters. - * - * @param string $filename - * The filename to compress or all to redo all files. - */ -function drush_advagg_js_compress($filename = '') { - // Get the redo list. - list($list, $redo_list) = advagg_js_compress_all_js_files_list(); - - // Handle special use cases. - if (!empty($filename)) { - // Do all. - if (strtolower($filename) === 'all') { - $redo_list = $list; - } - else { - // Do a single file, search for it in the $list. - $redo_list = array(); - foreach ($list as $values) { - if ($values['data'] === $filename) { - $redo_list = array($values); - break; - } - } - - // Let user know if that file was not found. - if (empty($redo_list)) { - drush_log(dt('The file @filename was not found.', array( - '@filename' => $filename, - )), 'notice'); - return; - } - } - } - - // Return if nothing to do. - if (empty($redo_list)) { - drush_log(dt('All of @total js files are already minified.', array( - '@total' => count($list), - )), 'ok'); - return; - } - - // Let user know what will happen. - drush_log(dt('A total of @redo out of @total js files will be minified.', array( - '@redo' => count($redo_list), - '@total' => count($list), - )), 'ok'); - - // Compress js files and cache. - advagg_js_compress_redo_files($redo_list, 0, TRUE); -} 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 24b9757fdb..1e80416c77 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 @@ -1,14 +1,14 @@ name = AdvAgg Compress Javascript -description = Compress Javascript with a 3rd party compressor; JSMin+, JSMin c ext, JShrink, and JSqueeze currently. +description = Compress Javascript with a 3rd party compressor; JSMin+, JSMin c ext, JShrink, & JSqueeze currently. package = Advanced CSS/JS Aggregation core = 7.x dependencies[] = advagg -recommends[] = libraries configure = admin/config/development/performance/advagg/js-compress -; Information added by Drupal.org packaging script on 2020-11-19 -version = "7.x-2.35" +; Information added by Drupal.org packaging script on 2015-04-14 +version = "7.x-2.8" core = "7.x" project = "advagg" -datestamp = "1605792717" +datestamp = "1429049283" + diff --git a/sites/all/modules/contrib/advagg/advagg_js_compress/advagg_js_compress.install b/sites/all/modules/contrib/advagg/advagg_js_compress/advagg_js_compress.install index 674253bcfc..3dcb38de06 100644 --- a/sites/all/modules/contrib/advagg/advagg_js_compress/advagg_js_compress.install +++ b/sites/all/modules/contrib/advagg/advagg_js_compress/advagg_js_compress.install @@ -5,11 +5,6 @@ * Handles AdvAgg JS compress installation and upgrade tasks. */ -/** - * @addtogroup hooks - * @{ - */ - /** * Implements hook_requirements(). */ @@ -24,9 +19,10 @@ function advagg_js_compress_requirements($phase) { } // Make sure a compressor is being used. - if (variable_get('advagg_js_compressor', ADVAGG_JS_COMPRESSOR) == 0 - && variable_get('advagg_js_compress_inline', ADVAGG_JS_COMPRESS_INLINE) == 0 - ) { + // @ignore sniffer_whitespace_openbracketspacing_openingwhitespace + if ( variable_get('advagg_js_compressor', ADVAGG_JS_COMPRESSOR) == 0 + && variable_get('advagg_js_compress_inline', ADVAGG_JS_COMPRESS_INLINE) == 0 + ) { // Check all files. $file_settings = variable_get('advagg_js_compressor_file_settings', array()); $compression_used = FALSE; @@ -53,13 +49,13 @@ function advagg_js_compress_requirements($phase) { } } - $requirements += advagg_js_compress_check_cache_bin(); - return $requirements; } /** - * Upgrade AdvAgg JS Compress versions (6.x-1.x and 7.x-1.x) to 7.x-2.x. + * Upgrade AdvAgg JS Compress previous versions (6.x-1.x & 7.x-1.x) to 7.x-2.x. + * + * Implements hook_update_N(). */ function advagg_js_compress_update_7200(&$sandbox) { // Bail if old DB Table does not exist. @@ -85,6 +81,8 @@ function advagg_js_compress_update_7200(&$sandbox) { /** * Clear the cache_advagg_info cache. + * + * Implements hook_update_N(). */ function advagg_js_compress_update_7201(&$sandbox) { cache_clear_all('advagg:js_compress:', 'cache_advagg_info', TRUE); @@ -93,6 +91,8 @@ function advagg_js_compress_update_7201(&$sandbox) { /** * Change variable names so they are prefixed with the modules name. + * + * Implements hook_update_N(). */ function advagg_js_compress_update_7202(&$sandbox) { // Rename advagg_js_inline_compressor to advagg_js_compress_inline. @@ -133,19 +133,3 @@ function advagg_js_compress_update_7202(&$sandbox) { variable_set('advagg_js_compress_max_ratio', $old); } } - -/** - * Remove unused variables from the variable table. - * - * Implements hook_update_N(). - */ -function advagg_js_compress_update_7203(&$sandbox) { - // Remove all old advagg css compress variables. - db_delete('variable') - ->condition('name', 'advagg_js_compressor_file_settings_%', 'LIKE') - ->execute(); -} - -/** - * @} End of "addtogroup hooks". - */ 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 699ec266f1..4e4c4db760 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 @@ -5,11 +5,6 @@ * Advanced CSS/JS aggregation js compression module. */ -/** - * @addtogroup default_variables - * @{ - */ - /** * Default value to see packer is enabled. */ @@ -48,38 +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', 3); - -/** - * Default value to refresh the data in the cache 1 week before the TTL expires. - */ -define('ADVAGG_JS_COMPRESS_REFRESH_BEFORE_CACHE_TTL', 604800); - - -/** - * @} End of "addtogroup default_variables". - */ - -/** - * @addtogroup hooks - * @{ - */ - -/** - * Implements hook_cron(). - */ -function advagg_js_compress_cron() { - // Get the redo list. - list(, $redo_list) = advagg_js_compress_all_js_files_list(); - - // Return if nothing to do. - if (empty($redo_list)) { - return; - } - - // Compress js files and cache. - advagg_js_compress_redo_files($redo_list); -} +define('ADVAGG_JS_COMPRESS_ADD_LICENSE', TRUE); /** * Implements hook_menu(). @@ -99,67 +63,18 @@ function advagg_js_compress_menu() { 'file' => 'advagg_js_compress.admin.inc', 'weight' => 10, ); - $items[$config_path . '/advagg/js-compress/batch'] = array( - 'title' => 'Batch Generate', - 'page callback' => 'advagg_js_compress_batch_callback', - 'access arguments' => array('administer site configuration'), - ); return $items; } -/** - * Implements hook_module_implements_alter(). - */ -function advagg_js_compress_module_implements_alter(&$implementations, $hook) { - // Move advagg_js_compress below advagg. - if ($hook === 'advagg_save_aggregate_alter' && array_key_exists('advagg_js_compress', $implementations)) { - $advagg_key = ''; - $advagg_js_compress_key = ''; - $counter = 0; - foreach ($implementations as $key => $value) { - if ($key == 'advagg') { - $advagg_key = $counter; - } - if ($key == 'advagg_js_compress') { - $advagg_js_compress_key = $counter; - } - $counter++; - } - - if ($advagg_js_compress_key > $advagg_key) { - // Move advagg_js_compress to the top. - $item = array('advagg_js_compress' => $implementations['advagg_js_compress']); - unset($implementations['advagg_js_compress']); - $implementations = array_merge($item, $implementations); - - // Move advagg to the very top. - $item = array('advagg' => $implementations['advagg']); - unset($implementations['advagg']); - $implementations = array_merge($item, $implementations); - } - } -} - -/** - * @} End of "addtogroup hooks". - */ - -/** - * @addtogroup advagg_hooks - * @{ - */ - /** * Implements hook_advagg_current_hooks_hash_array_alter(). */ function advagg_js_compress_advagg_current_hooks_hash_array_alter(&$aggregate_settings) { $aggregate_settings['variables']['advagg_js_compressor'] = variable_get('advagg_js_compressor', ADVAGG_JS_COMPRESSOR); $aggregate_settings['variables']['advagg_js_compress_packer'] = variable_get('advagg_js_compress_packer', ADVAGG_JS_COMPRESS_PACKER); - $aggregate_settings['variables']['advagg_js_compress_ratio'] = variable_get('advagg_js_compress_ratio', ADVAGG_JS_COMPRESS_RATIO); $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); } /** @@ -170,6 +85,7 @@ function advagg_js_compress_advagg_current_hooks_hash_array_alter(&$aggregate_se function advagg_js_compress_advagg_modify_js_pre_render_alter(&$children, &$elements) { // Get variables. $aggregate_settings['variables']['advagg_js_compressor'] = variable_get('advagg_js_compress_inline', ADVAGG_JS_COMPRESS_INLINE); + $aggregate_settings['variables']['advagg_js_compress_max_ratio'] = variable_get('advagg_js_compress_max_ratio', ADVAGG_JS_COMPRESS_MAX_RATIO); // Do nothing if the compressor is disabled. if (empty($aggregate_settings['variables']['advagg_js_compressor'])) { @@ -212,85 +128,6 @@ function advagg_js_compress_advagg_modify_js_pre_render_alter(&$children, &$elem unset($values); } -/** - * @} End of "addtogroup advagg_hooks". - */ - -/** - * @addtogroup 3rd_party_hooks - * @{ - */ - -/** - * Implements hook_libraries_info(). - */ -function advagg_js_compress_libraries_info() { - $libraries['JShrink'] = array( - 'name' => 'JShrink', - 'vendor url' => 'https://github.com/tedious/JShrink', - 'download url' => 'https://github.com/tedious/JShrink/archive/master.zip', - 'local version' => '1.2.0', - 'version' => '1.2.0', - 'files' => array( - 'php' => array( - 'src/JShrink/Minifier.php', - ), - ), - ); - $libraries['jsqueeze'] = array( - 'name' => 'JSqueeze', - 'vendor url' => 'https://github.com/tchwork/jsqueeze', - 'download url' => 'https://github.com/tchwork/jsqueeze/archive/master.zip', - 'local version' => '2.0.5', - 'version' => '2.0.5', - 'files' => array( - 'php' => array( - 'src/JSqueeze.php', - ), - ), - ); - $libraries['jsminplus'] = array( - 'vendor url' => 'https://github.com/JSMinPlus/JSMinPlus', - 'download url' => 'https://github.com/JSMinPlus/JSMinPlus/archive/master.zip', - 'name' => 'JSMinPlus', - 'local version' => '1.4', - 'version arguments' => array( - 'file' => 'jsminplus.php', - 'pattern' => '/JSMinPlus\s+version\s+([0-9a-zA-Z\.-]+)/', - 'lines' => 10, - 'cols' => 40, - ), - 'files' => array( - 'php' => array( - 'jsminplus.php', - ), - ), - ); - $libraries['jspacker'] = array( - 'vendor url' => 'https://github.com/tholu/php-packer', - 'download url' => 'https://github.com/tholu/php-packer/archive/master.zip', - 'name' => 'JSPacker', - 'local version' => '1.1', - 'version arguments' => array( - 'file' => 'src/Packer.php', - 'pattern' => '/\.\s+version\s+([0-9a-zA-Z\.-]+)/', - 'lines' => 4, - 'cols' => 40, - ), - 'files' => array( - 'php' => array( - 'src/Packer.php', - ), - ), - ); - - return $libraries; -} - -/** - * @} End of "addtogroup 3rd_party_hooks". - */ - /** * Test a file, making sure it is compressible. * @@ -305,7 +142,7 @@ function advagg_js_compress_libraries_info() { * Array showing the results of the compression tests. */ function advagg_js_compress_test_file($filename, array $compressors, $cache_id) { - $contents = (string) @advagg_file_get_contents($filename); + $contents = file_get_contents($filename); // Get the JS string length before the compression operation. $contents_before = $contents; $before = strlen($contents); @@ -316,25 +153,18 @@ function advagg_js_compress_test_file($filename, array $compressors, $cache_id) foreach ($compressors as $key => $name) { $contents = $contents_before; $aggregate_settings['variables']['advagg_js_compressor'] = $key; + $aggregate_settings['variables']['advagg_js_compress_max_ratio'] = variable_get('advagg_js_compress_max_ratio', ADVAGG_JS_COMPRESS_MAX_RATIO); // Compress it. - $no_errors = advagg_js_compress_prep($contents, $filename, $aggregate_settings, FALSE, FALSE, FALSE, TRUE); + advagg_js_compress_prep($contents, $filename, $aggregate_settings, FALSE, FALSE, FALSE); $after = strlen($contents); $ratio = 0; if ($before != 0) { $ratio = ($before - $after) / $before; } - // Set to "-1" if the compressor threw an error. - if ($no_errors === FALSE) { - $results[$key] = array( - 'code' => -1, - 'ratio' => round($ratio, 5), - 'name' => $name, - ); - } // Set to "-2" if compression ratio sucks (it's already compressed). - elseif ($ratio < 0.001) { + if ($ratio < variable_get('advagg_js_compress_ratio', ADVAGG_JS_COMPRESS_RATIO)) { $results[$key] = array( 'code' => -2, 'ratio' => round($ratio, 5), @@ -342,7 +172,7 @@ function advagg_js_compress_test_file($filename, array $compressors, $cache_id) ); } // Set to "-3" if the compression ratio is way too good (bad js output). - elseif ($ratio > 0.999) { + elseif ($ratio > variable_get('advagg_js_compress_max_ratio', ADVAGG_JS_COMPRESS_MAX_RATIO)) { $results[$key] = array( 'code' => -3, 'ratio' => round($ratio, 5), @@ -357,7 +187,6 @@ function advagg_js_compress_test_file($filename, array $compressors, $cache_id) 'name' => $name, ); } - } $cache = cache_get($cache_id, 'cache_advagg_info'); @@ -378,399 +207,3 @@ function advagg_js_compress_test_file($filename, array $compressors, $cache_id) return $results; } - -/** - * Generate the js compress configuration. - * - * @return array - * Array($options, $description, $compressors, $functions). - */ -function advagg_js_compress_configuration() { - // Set the defaults. - $description = ''; - $options = array( - 0 => t('Disabled'), - 1 => t('JSMin+ ~1300ms'), - // 2 => t('Packer ~500ms'), - // 3 is JSMin c extension. - // 4 is JShrink. - // 5 is JSqueeze. - ); - if (function_exists('jsmin')) { - $options[3] = t('JSMin ~2ms'); - $description .= t('JSMin is the very fast C complied version. Recommend using it.'); - } - else { - if (!defined('PHP_VERSION_ID') || constant('PHP_VERSION_ID') < 50310) { - $link = 'http://www.ypass.net/software/php_jsmin/'; - } - else { - $link = 'https://github.com/sqmk/pecl-jsmin/'; - } - $description .= t('You can use the much faster C version of JSMin (~2ms) by installing the <a href="@php_jsmin">JSMin PHP Extension</a> on this server.', array('@php_jsmin' => $link)); - } - // Add in JShrink and JSqueeze if using php 5.3 or higher. - if (defined('PHP_VERSION_ID') && constant('PHP_VERSION_ID') >= 50300) { - $options += array( - 4 => t('JShrink ~1000ms'), - 5 => t('JSqueeze ~600ms'), - ); - } - - $compressors = array( - 1 => 'jsminplus', - 2 => 'packer', - ); - if (function_exists('jsmin')) { - $compressors[3] = 'jsmin'; - } - if (defined('PHP_VERSION_ID') && constant('PHP_VERSION_ID') >= 50300) { - $compressors += array( - 4 => 'jshrink', - 5 => 'jsqueeze', - ); - } - - $functions = array( - 1 => 'advagg_js_compress_jsminplus', - 2 => 'advagg_js_compress_jspacker', - 3 => 'advagg_js_compress_jsmin', - 4 => 'advagg_js_compress_jshrink', - 5 => 'advagg_js_compress_jsqueeze', - ); - - // Allow for other modules to alter this list. - $options_desc = array($options, $description); - // Call hook_advagg_js_compress_configuration_alter(). - drupal_alter('advagg_js_compress_configuration', $options_desc, $compressors, $functions); - list($options, $description) = $options_desc; - - return array($options, $description, $compressors, $functions); -} - -/** - * Get all js files and js files that are not compressed. - * - * @return array - * Array($list, $redo_list). - */ -function advagg_js_compress_all_js_files_list() { - // Get all files stored in the database. - $result = db_select('advagg_files', 'af') - ->fields('af') - ->condition('filetype', 'js') - ->orderBy('filename', 'ASC') - ->execute(); - if (empty($result)) { - return array(); - } - - module_load_include('inc', 'advagg_js_compress', 'advagg_js_compress.advagg'); - $compressor_list = advagg_js_compress_get_enabled_compressors(array(), -1); - $compressor_list_count = count($compressor_list); - - // Check if files have been compressed. - module_load_include('inc', 'advagg', 'advagg'); - $redo_list = array(); - $failed_redo_list = array(); - $list = array(); - $cache_ids = array(); - foreach ($result as $row) { - $row = (array) $row; - // Check cache for jsmin info. - $info = advagg_get_info_on_file($row['filename']); - if ($info['filesize'] == 0) { - continue; - } - $list[$row['filename']] = $info; - - // Get the cache id as well. - $cache_id = 'advagg:js_compress:info:' . $info['filename_hash']; - $cache_id .= !empty($info['content_hash']) ? ':' . $info['content_hash'] : ''; - $cache_ids[$cache_id] = $row['filename']; - } - - // Check for soon to expire cache ids. - $values = array_keys($cache_ids); - $cache_hits = cache_get_multiple($values, 'cache_advagg_info'); - $ttl = variable_get('advagg_js_compress_refresh_before_cache_ttl', ADVAGG_JS_COMPRESS_REFRESH_BEFORE_CACHE_TTL); - foreach ($cache_hits as $cache) { - if (!empty($cache->expire) && $cache->expire - $ttl < REQUEST_TIME) { - $info = $list[$cache_ids[$cache->cid]]; - $redo_list[$info['data']] = $info; - } - } - - foreach ($list as $info) { - // No jsmin info or incomplete data => rerun compression tests. - if (empty($info['advagg_js_compress']) || count($info['advagg_js_compress']) !== $compressor_list_count) { - $redo_list[$info['data']] = $info; - continue; - } - $empty_ratio_count = 0; - $bad_compression_count = 0; - foreach ($info['advagg_js_compress'] as $values) { - if (empty($values['ratio'])) { - if ($values['code'] != -1) { - $empty_ratio_count++; - } - else { - $bad_compression_count++; - } - } - } - // More than one compressor has an empty ratio. - if ($empty_ratio_count > 1) { - $failed_redo_list[$info['data']] = $info; - } - // All failed; try again. - if ($bad_compression_count == count($info['advagg_js_compress'])) { - $failed_redo_list[$info['data']] = $info; - } - } - - $redo_list = array_merge($redo_list, $failed_redo_list); - $reversed_needle = strrev('.min.js'); - $advagg_js_compressor_file_settings = variable_get('advagg_js_compressor_file_settings', []); - foreach ($redo_list as $key => $info) { - // Filter out file if the compressor is disabled. - $filename = str_replace(['/', '.'], ['__', '--'], $key); - if (isset($advagg_js_compressor_file_settings[$filename]) && $advagg_js_compressor_file_settings[$filename] == 0) { - unset($redo_list[$key]); - continue; - } - - // Filter out .min.js if they have already been ran. - if (stripos(strrev($info['data']), $reversed_needle) === 0 - && !empty($info['advagg_js_compress'][2]['ratio']) - ) { - unset($redo_list[$key]); - continue; - } - - // Filter out file if it is empty. - $data = file_get_contents($info['data']); - if (empty($data)) { - unset($redo_list[$key]); - continue; - } - - // Filter out file if it only contains a small amount of whitespace. - $count_ws = strlen($data); - $count = strlen(preg_replace('/\s/', '', $data)); - if ($count / $count_ws > 0.97) { - unset($redo_list[$key]); - continue; - } - } - - shuffle($redo_list); - return array($list, $redo_list); -} - -/** - * Get all js files and js files that are not compressed. - * - * @param array $redo_list - * JS files that need to be compressed. - * @param int $max_time - * Max amount of time to spend on compressing. - * @param bool $drush - * Set to TRUE to output drush info when running. - * - * @return array - * Array($list, $redo_list). - */ -function advagg_js_compress_redo_files(array $redo_list, $max_time = 30, $drush = FALSE) { - // Get the compressor list and start the clock. - module_load_include('inc', 'advagg_js_compress', 'advagg_js_compress.advagg'); - $compressor_list = advagg_js_compress_get_enabled_compressors(array(), -1); - shuffle($redo_list); - $time_start = microtime(TRUE); - if ($drush && (!is_callable('drush_log') || !is_callable('dt'))) { - $drush = FALSE; - } - - // Change settings for testing. - if (isset($GLOBALS['conf']['advagg_js_compress_force_run'])) { - $advagg_js_compress_force_run = $GLOBALS['conf']['advagg_js_compress_force_run']; - } - if (isset($GLOBALS['conf']['advagg_js_compress_add_license'])) { - $advagg_js_compress_add_license = $GLOBALS['conf']['advagg_js_compress_add_license']; - } - if (isset($GLOBALS['conf']['httprl_background_callback'])) { - $httprl_background_callback = $GLOBALS['conf']['httprl_background_callback']; - } - $GLOBALS['conf']['advagg_js_compress_force_run'] = TRUE; - $GLOBALS['conf']['advagg_js_compress_add_license'] = 0; - $GLOBALS['conf']['httprl_background_callback'] = FALSE; - - $counter = array(); - foreach ($redo_list as $key => $values) { - // Test the files for up to 30 seconds. - $filenames_info = array(); - $filenames_info[$values['data']] = $values; - $compressors = $compressor_list; - if (isset($values['compressors'])) { - $compressors = $values['compressors']; - } - - if ($drush) { - drush_log(dt('Compressing @data.', array( - '@data' => $values['data'], - )), 'ok'); - } - - // Remove jsqueeze if compression failed. - if (!empty($values['advagg_js_compress'])) { - $neg_one_counter = 0; - foreach ($values['advagg_js_compress'] as $compressor_data) { - if ($compressor_data['code'] == -1) { - $neg_one_counter++; - } - } - if (count($values['advagg_js_compress']) === $neg_one_counter) { - $compressor_key = array_search('jsqueeze', $compressors); - if ($compressor_key !== FALSE) { - unset($compressors[$compressor_key]); - } - } - } - - // Prime cache. - advagg_js_compress_run_mutiple_tests($filenames_info, $compressors); - // Add to cache. - advagg_get_info_on_file($values['data'], TRUE, TRUE); - - $counter[$key] = $values; - - // Stop after 30 seconds of processing. - $time_end = microtime(TRUE); - $time = $time_end - $time_start; - if (!empty($max_time) && $time > $max_time) { - break; - } - } - - // Put them back to normal. - if (isset($advagg_js_compress_force_run)) { - $GLOBALS['conf']['advagg_js_compress_force_run'] = $advagg_js_compress_force_run; - } - if (isset($advagg_js_compress_add_license)) { - $GLOBALS['conf']['advagg_js_compress_add_license'] = $advagg_js_compress_add_license; - } - if (isset($httprl_background_callback)) { - $GLOBALS['conf']['httprl_background_callback'] = $httprl_background_callback; - } - - return $counter; -} - -/** - * The batch callback. - */ -function advagg_js_compress_batch_callback() { - $batch = array( - 'operations' => array(), - 'finished' => 'advagg_js_compress_batch_done', - 'title' => t('Batch JS Minification'), - 'init_message' => t('Starting'), - 'progress_message' => t('Processed @current out of @total.'), - 'error_message' => t('JS minification has encountered an error.'), - ); - - list($list, $redo_list) = advagg_js_compress_all_js_files_list(); - $config_path = advagg_admin_config_root_path(); - if (empty($redo_list)) { - $redo_list = $list; - } - - foreach ($redo_list as $redo) { - $batch['operations'][] = array('advagg_js_compress_batch_process', array($redo)); - } - batch_set($batch); - // The path to redirect to when done. - batch_process($config_path . '/advagg/js-compress'); -} - -/** - * The batch processor. - */ -function advagg_js_compress_batch_process($redo, &$context) { - // Give it up to 3 minutes. - $max_time = ini_get('max_execution_time'); - if ($max_time != 0 && $max_time < 180) { - set_time_limit(180); - } - ignore_user_abort(TRUE); - - // Display a progress message... - $context['message'] = t("Now processing @filename...", array('@filename' => $redo['data'])); - // Do heavy lifting here... - advagg_js_compress_redo_files(array($redo)); -} - -/** - * The batch finish handler. - */ -function advagg_js_compress_batch_done($success, $results, $operations) { - if ($success) { - drupal_set_message(t('Done!')); - } - else { - $error_operation = reset($operations); - $message = t('An error occurred while processing %error_operation with arguments: @arguments', array( - '%error_operation' => $error_operation[0], - '@arguments' => print_r($error_operation[1], TRUE), - )); - drupal_set_message($message, 'error'); - } -} - -/** - * Test the cache_advagg_info cache bin; make sure it works. - * - * @return array - * Single section of the requirements array. - */ -function advagg_js_compress_check_cache_bin() { - $t = get_t(); - - $requirements = array(); - // Make sure cache table is working. - $cid = 'advagg_js_compres:install:cache_test'; - $bin = 'cache_advagg_info'; - cache_set($cid, TRUE, $bin); - $cache = cache_get($cid, $bin); - $working = FALSE; - if (!empty($cache->data)) { - cache_clear_all($cid, $bin); - $cache = cache_get($cid, $bin); - if (empty($cache->data)) { - $working = TRUE; - } - } - - if (empty($working)) { - $broken_name = get_class(_cache_get_object($bin)); - - if ($broken_name === 'DrupalFakeCache') { - $working_name = get_class(_cache_get_object('cache_form')); - if ($working_name === 'DrupalFakeCache') { - $working_name = 'DrupalDatabaseCache'; - } - $extra_description = t('Please add this to the bottom of your settings.php file. <p><code>@string</code></p>', array('@string' => "\$conf['cache_class_cache_advagg_info'] = '{$working_name}';")); - } - $requirements['advagg_js_compres_cache_bin'] = array( - 'title' => $t('AdvAgg JS Compressor - The %bin cache table does not work', array('%bin' => $bin)), - 'severity' => REQUIREMENT_WARNING, - 'value' => $t('The %class_name cache bin appears to be broken.', array('%class_name' => $broken_name)), - 'description' => $t('You need to adjust your settings.php file so that the %bin bin is working correctly.', array( - '%bin' => $bin, - )), - ); - $requirements['advagg_js_compres_cache_bin']['description'] .= " {$extra_description}"; - } - - return $requirements; -} 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 deleted file mode 100644 index a4fac7ca11..0000000000 --- a/sites/all/modules/contrib/advagg/advagg_js_compress/advagg_js_compress.php53.inc +++ /dev/null @@ -1,138 +0,0 @@ -<?php - -/** - * @file - * Advanced CSS/JS aggregation js compression php 5.3+ functions. - */ - -use JShrink\Minifier; -use Patchwork\JSqueeze; - -/** - * Compress a JS string using jshrink. - * - * @param string $contents - * Javascript string. - * @param bool $log_errors - * FALSE to disable logging to watchdog on failure. - */ -function advagg_js_compress_jshrink(&$contents, $log_errors = TRUE) { - $no_errors = TRUE; - $contents_before = $contents; - - // Set max nesting level. - if (!class_exists('JShrink\Minifier')) { - $nesting_level = ini_get('xdebug.max_nesting_level'); - if (!empty($nesting_level) && $nesting_level < 200) { - ini_set('xdebug.max_nesting_level', 200); - } - } - - // Try libraries for JShrink. - if (is_callable('libraries_load')) { - libraries_load('JShrink'); - } - // Only include jshrink.inc if the JShrink\Minifier class doesn't exist. - if (!class_exists('JShrink\Minifier')) { - include drupal_get_path('module', 'advagg_js_compress') . '/jshrink.inc'; - } - - ob_start(); - try { - // JShrink the contents of the aggregated file. - $contents = Minifier::minify($contents, array('flaggedComments' => FALSE)); - - // Capture any output from JShrink. - $error = trim(ob_get_contents()); - if (!empty($error)) { - $no_errors = FALSE; - throw new Exception($error); - } - } - catch (Exception $e) { - $no_errors = FALSE; - // Log the JShrink exception and rollback to uncompressed content. - if ($log_errors) { - watchdog('advagg_js_compress', '@message <pre> @contents </pre>', array( - '@message' => $e->getMessage(), - '@contents' => $contents_before, - ), WATCHDOG_WARNING); - } - $contents = $contents_before; - } - ob_end_clean(); - return $no_errors; -} - -/** - * Compress a JS string using jsqueeze. - * - * @param string $contents - * 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, array $aggregate_settings = array()) { - $no_errors = TRUE; - $contents_before = $contents; - - // Set max nesting level. - if (!class_exists('Patchwork\JSqueeze')) { - $nesting_level = ini_get('xdebug.max_nesting_level'); - if (!empty($nesting_level) && $nesting_level < 200) { - ini_set('xdebug.max_nesting_level', 200); - } - } - - // Try libraries for jsqueeze. - if (is_callable('libraries_load')) { - libraries_load('jsqueeze'); - } - if (!class_exists('Patchwork\JSqueeze')) { - // Only include jshrink.inc if the Patchwork\JSqueeze class doesn't exist. - include drupal_get_path('module', 'advagg_js_compress') . '/jsqueeze.inc'; - } - - 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 JSqueeze(); - $contents = $jz->squeeze( - $contents, - TRUE, - $keep_important_comments, - FALSE - ); - - // Capture any output from JSqueeze. - $error = trim(ob_get_contents()); - if (!empty($error)) { - $no_errors = FALSE; - throw new Exception($error); - } - } - catch (Exception $e) { - $no_errors = FALSE; - // Log the JSqueeze exception and rollback to uncompressed content. - if ($log_errors) { - watchdog('advagg_js_compress', '@message <pre> @contents </pre>', array( - '@message' => $e->getMessage(), - '@contents' => $contents_before, - ), WATCHDOG_WARNING); - } - $contents = $contents_before; - } - ob_end_clean(); - return $no_errors; -} diff --git a/sites/all/modules/contrib/advagg/advagg_js_compress/jshrink.inc b/sites/all/modules/contrib/advagg/advagg_js_compress/jshrink.inc index 5c0699fc02..4886a801c6 100644 --- a/sites/all/modules/contrib/advagg/advagg_js_compress/jshrink.inc +++ b/sites/all/modules/contrib/advagg/advagg_js_compress/jshrink.inc @@ -116,11 +116,6 @@ class Minifier */ protected $options; - /** - * These characters are used to define strings. - */ - protected $stringDelimiters = array("'", '"', '`'); - /** * Contains the default options for minification. This array is merged with * the one passed in by the user to create the request specific set of @@ -162,7 +157,9 @@ class Minifier unset($jshrink); return $js; + } catch (\Exception $e) { + if (isset($jshrink)) { // Since the breakdownScript function probably wasn't finished // we clean it out before discarding it. @@ -221,11 +218,12 @@ class Minifier protected function loop() { while ($this->a !== false && !is_null($this->a) && $this->a !== '') { + switch ($this->a) { // new lines case "\n": // if the next line is something that can't stand alone preserve the newline - if ($this->b && strpos('(-+[@', $this->b) !== false) { + if (strpos('(-+{[@', $this->b) !== false) { echo $this->a; $this->saveString(); break; @@ -233,17 +231,14 @@ class Minifier // if B is a space we skip the rest of the switch block and go down to the // string/regex check below, resetting $this->b with getReal - if ($this->b === ' ') { + if($this->b === ' ') break; - } // otherwise we treat the newline like a space - // no break case ' ': - if (static::isAlphaNumeric($this->b)) { + if(static::isAlphaNumeric($this->b)) echo $this->a; - } $this->saveString(); break; @@ -264,16 +259,14 @@ class Minifier break; case ' ': - if (!static::isAlphaNumeric($this->a)) { + if(!static::isAlphaNumeric($this->a)) break; - } - // no break default: // check for some regex that breaks stuff if ($this->a === '/' && ($this->b === '\'' || $this->b === '"')) { $this->saveRegex(); - break; + continue; } echo $this->a; @@ -285,9 +278,8 @@ class Minifier // do reg check of doom $this->b = $this->getReal(); - if (($this->b == '/' && strpos('(,=:[!&|?', $this->a) !== false)) { + if(($this->b == '/' && strpos('(,=:[!&|?', $this->a) !== false)) $this->saveRegex(); - } } } @@ -317,7 +309,7 @@ class Minifier $char = $this->c; unset($this->c); - // Otherwise we start pulling from the input. + // Otherwise we start pulling from the input. } else { $char = substr($this->input, $this->index, 1); @@ -332,9 +324,9 @@ class Minifier // Normalize all whitespace except for the newline character into a // standard space. - if ($char !== "\n" && ord($char) < 32) { + if($char !== "\n" && ord($char) < 32) + return ' '; - } return $char; } @@ -362,13 +354,10 @@ class Minifier $this->c = $this->getChar(); if ($this->c === '/') { - $this->processOneLineComments($startIndex); + return $this->processOneLineComments($startIndex); - return $this->getReal(); } elseif ($this->c === '*') { - $this->processMultiLineComments($startIndex); - - return $this->getReal(); + return $this->processMultiLineComments($startIndex); } return $char; @@ -378,8 +367,8 @@ class Minifier * Removed one line comments, with the exception of some very specific types of * conditional comments. * - * @param int $startIndex The index point where "getReal" function started - * @return void + * @param int $startIndex The index point where "getReal" function started + * @return string */ protected function processOneLineComments($startIndex) { @@ -388,12 +377,17 @@ class Minifier // kill rest of line $this->getNext("\n"); - unset($this->c); - if ($thirdCommentString == '@') { $endPoint = $this->index - $startIndex; - $this->c = "\n" . substr($this->input, $startIndex, $endPoint); + unset($this->c); + $char = "\n" . substr($this->input, $startIndex, $endPoint); + } else { + // first one is contents of $this->c + $this->getChar(); + $char = $this->getChar(); } + + return $char; } /** @@ -401,7 +395,7 @@ class Minifier * Conditional comments and "license" style blocks are preserved. * * @param int $startIndex The index point where "getReal" function started - * @return void + * @return bool|string False if there's no character * @throws \RuntimeException Unclosed comments will throw an error */ protected function processMultiLineComments($startIndex) @@ -411,13 +405,14 @@ class Minifier // kill everything up to the next */ if it's there if ($this->getNext('*/')) { + $this->getChar(); // get * $this->getChar(); // get / $char = $this->getChar(); // get next real character // Now we reinsert conditional comments and YUI-style licensing comments if (($this->options['flaggedComments'] && $thirdCommentString === '!') - || ($thirdCommentString === '@')) { + || ($thirdCommentString === '@') ) { // If conditional comments or flagged comments are not the first thing in the script // we need to echo a and fill it with a space before moving on. @@ -434,20 +429,21 @@ class Minifier $endPoint = ($this->index - 1) - $startIndex; echo substr($this->input, $startIndex, $endPoint); - $this->c = $char; - - return; + return $char; } + } else { $char = false; } - if ($char === false) { + if($char === false) throw new \RuntimeException('Unclosed multiline comment at position: ' . ($this->index - 2)); - } // if we're here c is part of the comment and therefore tossed - $this->c = $char; + if(isset($this->c)) + unset($this->c); + + return $char; } /** @@ -464,9 +460,9 @@ class Minifier $pos = strpos($this->input, $string, $this->index); // If it's not there return false. - if ($pos === false) { + if($pos === false) + return false; - } // Adjust position of index to jump ahead to the asked for string $this->index = $pos; @@ -490,7 +486,7 @@ class Minifier $this->a = $this->b; // If this isn't a string we don't need to do anything. - if (!in_array($this->a, $this->stringDelimiters)) { + if ($this->a !== "'" && $this->a !== '"') { return; } @@ -519,11 +515,7 @@ class Minifier // character, so those will be treated just fine using the switch // block below. case "\n": - if ($stringType === '`') { - echo $this->a; - } else { - throw new \RuntimeException('Unclosed string at position: ' . $startpos); - } + throw new \RuntimeException('Unclosed string at position: ' . $startpos ); break; // Escaped characters get picked up here. If it's an escaped new line it's not really needed @@ -563,18 +555,16 @@ class Minifier echo $this->a . $this->b; while (($this->a = $this->getChar()) !== false) { - if ($this->a === '/') { + if($this->a === '/') break; - } if ($this->a === '\\') { echo $this->a; $this->a = $this->getChar(); } - if ($this->a === "\n") { + if($this->a === "\n") throw new \RuntimeException('Unclosed regex pattern at position: ' . $this->index); - } echo $this->a; } @@ -589,7 +579,7 @@ class Minifier */ protected static function isAlphaNumeric($char) { - return preg_match('/^[\w\$\pL]$/', $char) === 1 || $char == '/'; + return preg_match('/^[\w\$]$/', $char) === 1 || $char == '/'; } /** @@ -635,4 +625,5 @@ class Minifier return $js; } + } diff --git a/sites/all/modules/contrib/advagg/advagg_js_compress/jsminplus.inc b/sites/all/modules/contrib/advagg/advagg_js_compress/jsminplus.inc index af909ff09d..f3346d2fe6 100644 --- a/sites/all/modules/contrib/advagg/advagg_js_compress/jsminplus.inc +++ b/sites/all/modules/contrib/advagg/advagg_js_compress/jsminplus.inc @@ -300,16 +300,16 @@ class JSMinPlus { // FALL THROUGH case JS_BLOCK: - $children = $n->treeNodes; + $childs = $n->treeNodes; $lastType = 0; - for ($c = 0, $i = 0, $j = count($children); $i < $j; $i++) { - $type = $children[$i]->type; - $t = $this->parseTree($children[$i]); + for ($c = 0, $i = 0, $j = count($childs); $i < $j; $i++) { + $type = $childs[$i]->type; + $t = $this->parseTree($childs[$i]); if (strlen($t)) { if ($c) { $s = rtrim($s, ';'); - if ($type == KEYWORD_FUNCTION && $children[$i]->functionForm == DECLARED_FORM) { + if ($type == KEYWORD_FUNCTION && $childs[$i]->functionForm == DECLARED_FORM) { // put declared functions on a new line $s .= "\n"; } @@ -476,9 +476,9 @@ class JSMinPlus { case KEYWORD_VAR: case KEYWORD_CONST: $s = $n->value . ' '; - $children = $n->treeNodes; - for ($i = 0, $j = count($children); $i < $j; $i++) { - $t = $children[$i]; + $childs = $n->treeNodes; + for ($i = 0, $j = count($childs); $i < $j; $i++) { + $t = $childs[$i]; $s .= ($i ? ',' : '') . $t->name; $u = $t->initializer; if ($u) { @@ -531,9 +531,9 @@ class JSMinPlus { case TOKEN_CONDCOMMENT_START: case TOKEN_CONDCOMMENT_END: $s = $n->value . ($n->type == TOKEN_CONDCOMMENT_START ? ' ' : ''); - $children = $n->treeNodes; - for ($i = 0, $j = count($children); $i < $j; $i++) { - $s .= $this->parseTree($children[$i]); + $childs = $n->treeNodes; + for ($i = 0, $j = count($childs); $i < $j; $i++) { + $s .= $this->parseTree($childs[$i]); } break; @@ -548,9 +548,9 @@ class JSMinPlus { break; case OP_COMMA: - $children = $n->treeNodes; - for ($i = 0, $j = count($children); $i < $j; $i++) { - $s .= ($i ? ',' : '') . $this->parseTree($children[$i]); + $childs = $n->treeNodes; + for ($i = 0, $j = count($childs); $i < $j; $i++) { + $s .= ($i ? ',' : '') . $this->parseTree($childs[$i]); } break; @@ -648,9 +648,9 @@ class JSMinPlus { break; case JS_LIST: - $children = $n->treeNodes; - for ($i = 0, $j = count($children); $i < $j; $i++) { - $s .= ($i ? ',' : '') . $this->parseTree($children[$i]); + $childs = $n->treeNodes; + for ($i = 0, $j = count($childs); $i < $j; $i++) { + $s .= ($i ? ',' : '') . $this->parseTree($childs[$i]); } break; @@ -665,18 +665,18 @@ class JSMinPlus { case JS_ARRAY_INIT: $s = '['; - $children = $n->treeNodes; - for ($i = 0, $j = count($children); $i < $j; $i++) { - $s .= ($i ? ',' : '') . $this->parseTree($children[$i]); + $childs = $n->treeNodes; + for ($i = 0, $j = count($childs); $i < $j; $i++) { + $s .= ($i ? ',' : '') . $this->parseTree($childs[$i]); } $s .= ']'; break; case JS_OBJECT_INIT: $s = '{'; - $children = $n->treeNodes; - for ($i = 0, $j = count($children); $i < $j; $i++) { - $t = $children[$i]; + $childs = $n->treeNodes; + for ($i = 0, $j = count($childs); $i < $j; $i++) { + $t = $childs[$i]; if ($i) { $s .= ','; } @@ -1435,7 +1435,7 @@ class JSParser { } if ($tt == OP_DOT) { - $this->t->mustMatch(TOKEN_IDENTIFIER, true); + $this->t->mustMatch(TOKEN_IDENTIFIER); array_push($operands, new JSNode($this->t, OP_DOT, array_pop($operands), new JSNode($this->t))); } else { @@ -1765,9 +1765,9 @@ class JSParser { } // Include closing bracket or postfix operator in [start,end] - $token_end = $this->t->currentToken()->end; - if ($n->end < $token_end) { - $n->end = $token_end; + $te = $this->t->currentToken()->end; + if ($n->end < $te) { + $n->end = $te; } array_push($operands, $n); @@ -1806,8 +1806,6 @@ class JSNode { public $varDecls = array(); public function __construct($t, $type = 0) { - $args = func_get_args(); - if ($token = $t->currentToken()) { $this->type = $type ? $type : $token->type; $this->value = $token->value; @@ -1821,6 +1819,7 @@ class JSNode { } if (($numargs = func_num_args()) > 2) { + $args = func_get_args(); for ($i = 2; $i < $numargs; $i++) { $this->addNode($args[$i]); } @@ -1974,12 +1973,12 @@ class JSTokenizer { return $this->peek() == TOKEN_END; } - public function match($tt, $op_dot = false) { - return $this->get(1000, $op_dot) == $tt || $this->unget(); + public function match($tt) { + return $this->get() == $tt || $this->unget(); } - public function mustMatch($tt, $op_dot = false) { - if (!$this->match($tt, $op_dot)) { + public function mustMatch($tt) { + if (!$this->match($tt)) { throw $this->newSyntaxError('Unexpected token; token ' . $tt . ' expected'); } @@ -2018,7 +2017,7 @@ class JSTokenizer { } } - public function get($chunksize = 1000, $op_dot = false) { + public function get($chunksize = 1000) { while ($this->lookahead) { $this->lookahead--; $this->tokenIndex = ($this->tokenIndex + 1) & 3; @@ -2265,14 +2264,7 @@ class JSTokenizer { throw $this->newSyntaxError('Illegal token'); } } - - // Identifiers after an OP_DOT can include otherwise reserve keywords. - if ($op_dot) { - $tt = TOKEN_IDENTIFIER; - } - else { - $tt = in_array($match[0], $this->keywords) ? $match[0] : TOKEN_IDENTIFIER; - } + $tt = in_array($match[0], $this->keywords) ? $match[0] : TOKEN_IDENTIFIER; } } diff --git a/sites/all/modules/contrib/advagg/advagg_js_compress/jspacker.inc b/sites/all/modules/contrib/advagg/advagg_js_compress/jspacker.inc index aa8a8768b7..4bcc8d4aae 100644 --- a/sites/all/modules/contrib/advagg/advagg_js_compress/jspacker.inc +++ b/sites/all/modules/contrib/advagg/advagg_js_compress/jspacker.inc @@ -224,7 +224,7 @@ class JavaScriptPacker { private function _analyze($script, $regexp, $encode) { // analyse - // retrieve all words in the script + // retreive all words in the script $all = array(); preg_match_all($regexp, $script, $all); $_sorted = array(); // list of words sorted by frequency @@ -345,7 +345,7 @@ class JavaScriptPacker { $decode = preg_replace($ENCODE, $inline, $decode); } // special case: when $count==0 there are no keywords. I want to keep - // the basic shape of the unpacking function so i'll frig the code... +// the basic shape of the unpacking funcion so i'll frig the code... if ($count == 0) { $decode = preg_replace($this->_safeRegExp('($count)\\s*=\\s*1'), '$1=0', $decode, 1); } 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 b5d42d2cec..322a451399 100644 --- a/sites/all/modules/contrib/advagg/advagg_js_compress/jsqueeze.inc +++ b/sites/all/modules/contrib/advagg/advagg_js_compress/jsqueeze.inc @@ -1,9 +1,26 @@ <?php // @codingStandardsIgnoreFile +// @ignore comment_docblock_file:file +// @ignore style_curly_braces:file +// @ignore style_string_spacing:file +// @ignore style_else_spacing:file +// @ignore comment_comment_docblock_missing:file +// @ignore comment_comment_eg:file +// @ignore production_code:file +// @ignore druplart_unary:file +// @ignore style_uppercase_constants:file +// @ignore comment_comment_space:file +// @ignore druplart_conditional_assignment:file +// @ignore style_paren_spacing:file +// @ignore style_no_tabs:file +// @ignore comment_docblock_comment:file +// @ignore comment_comment_indent:file +// @ignore style_comma_spacing:file +// @ignore style_elseif:file // @ignore :file /* - * Copyright (C) 2016 Nicolas Grekas - p@tchwork.com + * Copyright (C) 2015 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): @@ -16,7 +33,7 @@ namespace Patchwork; /* * * This class shrinks Javascript code -* (a process called minification nowadays) +* (a process called minification nowdays) * * Should work with most valid Javascript code, * even when semi-colons are missing. @@ -35,7 +52,7 @@ namespace Patchwork; * Notes: * - Source code must be parse error free before processing. * - In order to maximise later HTTP compression (deflate, gzip), -* new variables names are chosen by considering closures, +* new variables names are choosen by considering closures, * variables' frequency and characters' frequency. * - If you use with/eval then be careful. * @@ -82,21 +99,18 @@ class JSqueeze $varRx = '(?:[a-zA-Z_$])[a-zA-Z0-9_$]*', $reserved = array( - // 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', + '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', ); - public function __construct() + + function __construct() { $this->reserved = array_flip($this->reserved); $this->charFreq = array_fill(0, 256, 0); @@ -126,19 +140,20 @@ class JSqueeze * $parser = new JSqueeze; * $squeezed_js = $parser->squeeze($fat_js); */ - public function squeeze($code, $singleLine = true, $keepImportantComments = true, $specialVarRx = false) + + function squeeze($code, $singleLine = true, $keepImportantComments = true, $specialVarRx = false) { $code = trim($code); - if ('' === $code) { - return ''; - } + if ('' === $code) return ''; $this->argFreq = array(-1 => 0); $this->specialVarRx = $specialVarRx; $this->keepImportantComments = !!$keepImportantComments; - if (preg_match("#//[ \t]*jsqueeze\.specialVarRx[ \t]*=[ \t]*([\"']?)(.*)\\1#i", $code, $key)) { - if (!$key[1]) { + if (preg_match("#//[ \t]*jsqueeze\.specialVarRx[ \t]*=[ \t]*([\"']?)(.*)\1#i", $code, $key)) + { + if (!$key[1]) + { $key[2] = trim($key[2]); $key[1] = strtolower($key[2]); $key[1] = $key[1] && $key[1] != 'false' && $key[1] != 'none' && $key[1] != 'off'; @@ -150,16 +165,16 @@ class JSqueeze // Remove capturing parentheses $this->specialVarRx && $this->specialVarRx = preg_replace('/(?<!\\\\)((?:\\\\\\\\)*)\((?!\?)/', '(?:', $this->specialVarRx); - false !== strpos($code, "\r") && $code = strtr(str_replace("\r\n", "\n", $code), "\r", "\n"); - false !== strpos($code, "\xC2\x85") && $code = str_replace("\xC2\x85", "\n", $code); // Next Line + false !== strpos($code, "\r" ) && $code = strtr(str_replace("\r\n", "\n", $code), "\r", "\n"); + false !== strpos($code, "\xC2\x85" ) && $code = str_replace("\xC2\x85" , "\n", $code); // Next Line false !== strpos($code, "\xE2\x80\xA8") && $code = str_replace("\xE2\x80\xA8", "\n", $code); // Line Separator false !== strpos($code, "\xE2\x80\xA9") && $code = str_replace("\xE2\x80\xA9", "\n", $code); // Paragraph Separator - list($code, $this->strings) = $this->extractStrings($code); + list($code, $this->strings ) = $this->extractStrings( $code); list($code, $this->closures) = $this->extractClosures($code); - $key = "//''\"\"#0'"; // This crap has a wonderful property: it can not happen in any valid javascript, even in strings - $this->closures[$key] = &$code; + $key = "//''\"\"#0'"; // This crap has a wonderful property: it can not happend in any valid javascript, even in strings + $this->closures[$key] =& $code; $tree = array($key => array('parent' => false)); $this->makeVars($code, $tree[$key], $key); @@ -168,15 +183,10 @@ class JSqueeze $code = substr($tree[$key]['code'], 1); $code = preg_replace("'\breturn !'", 'return!', $code); $code = preg_replace("'\}(?=(else|while)[^\$.a-zA-Z0-9_])'", "}\r", $code); - // preg_replace is much more efficient than str_replace here - // because we don't need to scan the entire code each time for every replacement - $code = preg_replace_callback('#//\'\'""\\d++(?:/?+\'|\\])#', array($this, 'restoreString'), $code); - - if ($singleLine) { - $code = strtr($code, "\n", ';'); - } else { - $code = str_replace("\n", ";\n", $code); - } + $code = str_replace(array_keys($this->strings), array_values($this->strings), $code); + + if ($singleLine) $code = strtr($code, "\n", ';'); + else $code = str_replace("\n", ";\n", $code); false !== strpos($code, "\r") && $code = strtr(trim($code), "\r", "\n"); // Cleanup memory @@ -187,9 +197,11 @@ class JSqueeze return $code; } + protected function extractStrings($f) { - if ($cc_on = false !== strpos($f, '@cc_on')) { + if ($cc_on = false !== strpos($f, '@cc_on')) + { // Protect conditional comments from being removed $f = str_replace('#', '##', $f); $f = str_replace('/*@', '1#@', $f); @@ -213,266 +225,210 @@ class JSqueeze ); // Extract strings, removes comments - for ($i = 0; $i < $len; ++$i) { - if ($instr) { - if ('//' == $instr) { - if ("\n" == $f[$i]) { + for ($i = 0; $i < $len; ++$i) + { + if ($instr) + { + if ('//' == $instr) + { + if ("\n" == $f[$i]) + { $f[$i--] = ' '; $instr = false; } - } elseif ($f[$i] == $instr || ('/' == $f[$i] && "/'" == $instr)) { - if ('!' == $instr) { - } elseif ('*' == $instr) { - if ('/' == $f[$i + 1]) { + } + else if ($f[$i] == $instr || ('/' == $f[$i] && "/'" == $instr)) + { + if ('!' == $instr) ; + else if ('*' == $instr) + { + if ('/' == $f[$i+1]) + { ++$i; $instr = false; } - } else { - if ("/'" == $instr) { - while (isset($f[$i + 1]) && false !== strpos('gmi', $f[$i + 1])) { - $s[] = $f[$i++]; - } + } + else + { + if ("/'" == $instr) + { + while (false !== strpos('gmi', $f[$i+1])) $s[] = $f[$i++]; $s[] = $f[$i]; } $instr = false; } - } elseif ('*' == $instr) { - } elseif ('!' == $instr) { - if ('*' == $f[$i] && '/' == $f[$i + 1]) { + } + else if ('*' == $instr) ; + else if ('!' == $instr) + { + if ('*' == $f[$i] && '/' == $f[$i+1]) + { $s[] = "*/\r"; ++$i; $instr = false; - } elseif ("\n" == $f[$i]) { - $s[] = "\r"; - } else { - $s[] = $f[$i]; } - } elseif ('\\' == $f[$i]) { + else if ("\n" == $f[$i]) $s[] = "\r"; + else $s[] = $f[$i]; + } + else if ('\\' == $f[$i]) + { ++$i; - if ("\n" != $f[$i]) { + if ("\n" != $f[$i]) + { isset($q[$f[$i]]) && ++$q[$f[$i]]; - $s[] = '\\'.$f[$i]; + $s[] = '\\' . $f[$i]; } - } elseif ('[' == $f[$i] && "/'" == $instr) { - $instr = '/['; - $s[] = '['; - } elseif (']' == $f[$i] && '/[' == $instr) { - $instr = "/'"; - $s[] = ']'; - } elseif ("'" == $f[$i] || '"' == $f[$i]) { + } + else if ("'" == $f[$i] || '"' == $f[$i]) + { ++$q[$f[$i]]; - $s[] = '\\'.$f[$i]; - } else { - $s[] = $f[$i]; + $s[] = '\\' . $f[$i]; + } + else $s[] = $f[$i]; + } + else switch ($f[$i]) + { + case ';': + // Remove triple semi-colon + if ($i>0 && ';' == $f[$i-1] && $i+1 < $len && ';' == $f[$i+1]) $f[$i] = $f[$i+1] = '/'; + else + { + $code[++$j] = ';'; + break; } - } else { - switch ($f[$i]) { - case ';': - // Remove triple semi-colon - if ($i > 0 && ';' == $f[$i - 1] && $i + 1 < $len && ';' == $f[$i + 1]) { - $f[$i] = $f[$i + 1] = '/'; - } else { - $code[++$j] = ';'; - break; - } - case '/': - if ('*' == $f[$i + 1]) { - ++$i; - $instr = '*'; + case '/': + if ('*' == $f[$i+1]) + { + ++$i; + $instr = '*'; - if ($this->keepImportantComments && '!' == $f[$i + 1]) { - ++$i; - // no break here - } else { - break; - } - } elseif ('/' == $f[$i + 1]) { + if ($this->keepImportantComments && '!' == $f[$i+1]) + { ++$i; - $instr = '//'; - break; - } else { - $a = $j && (' ' == $code[$j] || "\x7F" == $code[$j]) ? $code[$j - 1] : $code[$j]; - if (false !== strpos('-!%&;<=>~:^+|,()*?[{} ', $a) - || (false !== strpos('oenfd', $a) - && preg_match( - "'(?<![\$.a-zA-Z0-9_])(do|else|return|typeof|yield[ \x7F]?\*?)[ \x7F]?$'", - substr($code, $j - 7, 8) - )) - ) { - if (')' === $a && $j > 1) { - $a = 1; - $k = $j - (' ' == $code[$j] || "\x7F" == $code[$j]) - 1; - while ($k >= 0 && $a) { - if ('(' === $code[$k]) { - --$a; - } elseif (')' === $code[$k]) { - ++$a; - } - --$k; - } - if (!preg_match("'(?<![\$.a-zA-Z0-9_])(if|for|while)[ \x7F]?$'", substr($code, 0, $k + 1))) { - $code[++$j] = '/'; - break; - } - } - - $key = "//''\"\"".$K++.$instr = "/'"; - $a = $j; - $code .= $key; - while (isset($key[++$j - $a - 1])) { - $code[$j] = $key[$j - $a - 1]; - } - --$j; - isset($s) && ($s = implode('', $s)) && $cc_on && $this->restoreCc($s); - $strings[$key] = array('/'); - $s = &$strings[$key]; - } else { - $code[++$j] = '/'; - } - - break; + // no break here } - - case "'": - case '"': - $instr = $f[$i]; - $key = "//''\"\"".$K++.('!' == $instr ? ']' : "'"); - $a = $j; - $code .= $key; - while (isset($key[++$j - $a - 1])) { - $code[$j] = $key[$j - $a - 1]; + else break; + } + else if ('/' == $f[$i+1]) + { + ++$i; + $instr = '//'; + break; + } + else + { + $a = $j && ' ' == $code[$j] ? $code[$j-1] : $code[$j]; + if (false !== strpos('-!%&;<=>~:^+|,(*?[{ ', $a) + || (false !== strpos('oenfd', $a) + && preg_match( + "'(?<![\$.a-zA-Z0-9_])(do|else|return|typeof|yield) ?$'", + substr($code, $j-7, 8) + ))) + { + $key = "//''\"\"" . $K++ . $instr = "/'"; + $a = $j; + $code .= $key; + while (isset($key[++$j-$a-1])) $code[$j] = $key[$j-$a-1]; --$j; + isset($s) && ($s = implode('', $s)) && $cc_on && $this->restoreCc($s); + $strings[$key] = array('/'); + $s =& $strings[$key]; } - --$j; - isset($s) && ($s = implode('', $s)) && $cc_on && $this->restoreCc($s); - $strings[$key] = array(); - $s = &$strings[$key]; - '!' == $instr && $s[] = "\r/*!"; + else $code[++$j] = '/'; break; + } - case "\n": - if ($j > 3) { - if (' ' == $code[$j] || "\x7F" == $code[$j]) { - --$j; - } - if (false === strpos('oefd', $code[$j]) - || !preg_match( - "'(?<![\$.a-zA-Z0-9_])(?:do|else|typeof|void)[ \x7F]?$'", - substr($code, $j - 6, 8) - ) - ) { - $code[++$j] = - false !== strpos('kend', $code[$j - 1]) - && preg_match( - "'(?<![\$.a-zA-Z0-9_])(?:break|continue|return|yield[ \x7F]?\*?)[ \x7F]?$'", - substr($code, $j - 9, 10) - ) - ? ';' : "\x7F"; - - break; - } - } + case "'": + case '"': + $instr = $f[$i]; + $key = "//''\"\"" . $K++ . ('!' == $instr ? '!' : "'"); + $a = $j; + $code .= $key; + while (isset($key[++$j-$a-1])) $code[$j] = $key[$j-$a-1]; --$j; + isset($s) && ($s = implode('', $s)) && $cc_on && $this->restoreCc($s); + $strings[$key] = array(); + $s =& $strings[$key]; + '!' == $instr && $s[] = "\r/*!"; - case "\t": $f[$i] = ' '; - case ' ': - if (!$j || ' ' == $code[$j] || "\x7F" == $code[$j]) { - break; - } + break; - default: - $code[++$j] = $f[$i]; + case "\n": + if ($j > 5) + { + ' ' == $code[$j] && --$j; + + $code[++$j] = + false !== strpos('kend', $code[$j-1]) + && preg_match( + "'(?<![\$.a-zA-Z0-9_])(break|continue|return|yield) ?$'", + substr($code, $j-8, 9) + ) + ? ';' : ' '; + + break; } + + case "\t": $f[$i] = ' '; + case ' ': + if (!$j || ' ' == $code[$j]) break; + + default: + $code[++$j] = $f[$i]; } } isset($s) && ($s = implode('', $s)) && $cc_on && $this->restoreCc($s); unset($s); - $code = substr($code, 0, $j + 1); + $code = substr($code, 0, $j+1); $cc_on && $this->restoreCc($code, false); - // Deal with newlines before/after postfix/prefix operators - // (a string literal starts with `//` and ends with `'` at this stage) - // http://inimino.org/~inimino/blog/javascript_semicolons - // Newlines before prefix are a new statement when a completed expression precedes because postfix is a "restrictd production" - // A closing bracket `)` from if/for/while does not complete an expression, so mark possible `;` as `#` to deal with later - $code = preg_replace("#(?<=[a-zA-Z\$_\\d'\\]}])\x7F(--|\\+\\+)#", ';$1', $code); - $code = preg_replace("#(?<=\\))\x7F(--|\\+\\+)#", '#$1', $code); - // Newlines after postfix are a new statement if the following token can't be parsed otherwise - // i.e. it's a keyword, identifier, string or number literal, prefix operator, opening brace - // But a prefix operator can have a newline before its operand, so check a completed expression precedes to be sure it's a postfix - // Again mark case after closing bracket with `#` to deal with later - // Also ensure keywords that may be followed by an expression aren't mistaken for the end of a completed expression - // (note that postfix cannot apply to an expression completed with `}`) - $code = preg_replace("#(?<![\$.a-zA-Z0-9_])(do|else|return|throw|typeof|void|yield) ?+(--|\\+\\+)\x7F#", '$1$2 ', $code); - $code = preg_replace("#(?<=[a-zA-Z\$_\\d'\\]]) ?+(--|\\+\\+)\x7F(?=//|--|\\+\\+|[a-zA-Z\$_\\d[({])#", '$1;', $code); - $code = preg_replace("#(?<=\\)) ?+(--|\\+\\+)\x7F(?=//|--|\\+\\+|[a-zA-Z\$_\\d[({])#", '$1#', $code); - // Protect wanted spaces and remove unwanted ones - $code = strtr($code, "\x7F", ' '); $code = str_replace('- -', "-\x7F-", $code); $code = str_replace('+ +', "+\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); + $code = preg_replace( "#([-!%&;<=>~:.^+|,()*?[\]{}/]+) #", '$1', $code); // Replace new Array/Object by []/{} - false !== strpos($code, 'new Array') && $code = preg_replace("'new Array(?:\(\)|([;\])},:]))'", '[]$1', $code); + false !== strpos($code, 'new Array' ) && $code = preg_replace( "'new Array(?:\(\)|([;\])},:]))'", '[]$1', $code); false !== strpos($code, 'new Object') && $code = preg_replace("'new Object(?:\(\)|([;\])},:]))'", '{}$1', $code); // Add missing semi-colons after curly braces // This adds more semi-colons than strictly needed, // but it seems that later gzipping is favorable to the repetition of "};" - $code = preg_replace("'\}(?![:,;.()\[\]}\|&?]|(else|catch|finally|while)[^\$.a-zA-Z0-9_])'", '};', $code); + $code = preg_replace("'\}(?![:,;.()\[\]}\|&]|(else|catch|finally|while)[^\$.a-zA-Z0-9_])'", '};', $code); // Tag possible empty instruction for easy detection - $code = preg_replace("'(?<![\$.a-zA-Z0-9_])if\('", '1#(', $code); - $code = preg_replace("'(?<![\$.a-zA-Z0-9_])for\('", '2#(', $code); - $code = preg_replace("'(?<![\$.a-zA-Z0-9_])do while\('", '4#(', $code); + $code = preg_replace("'(?<![\$.a-zA-Z0-9_])if\('" , '1#(', $code); + $code = preg_replace("'(?<![\$.a-zA-Z0-9_])for\('" , '2#(', $code); $code = preg_replace("'(?<![\$.a-zA-Z0-9_])while\('", '3#(', $code); - $code = preg_replace("'(?<![\$.a-zA-Z0-9_])do(?![\$a-zA-Z0-9_])'", '5#', $code); $forPool = array(); $instrPool = array(); - $doPool = array(); $s = 0; - $d = 0; $f = array(); $j = -1; // Remove as much semi-colon as possible $len = strlen($code); - for ($i = 0; $i < $len; ++$i) { - switch ($code[$i]) { + for ($i = 0; $i < $len; ++$i) + { + switch ($code[$i]) + { case '(': - if ($j >= 0 && "\n" == $f[$j]) { - $f[$j] = ';'; - } + if ($j>=0 && "\n" == $f[$j]) $f[$j] = ';'; ++$s; - if ($i > 1 && '#' == $code[$i - 1]) { - switch ($code[$i - 2]) { - case '3': - if (isset($doPool[$d])) { - $instrPool[$s - 1] = 5; // `while` corresponds to `do` - unset($doPool[$d]); - } else { - $instrPool[$s - 1] = 1; - } - break; - case '2': - $forPool[$s] = 1; - // also set $instrPool - case '1': - case '4': - $instrPool[$s - 1] = 1; - } + if ($i && '#' == $code[$i-1]) + { + $instrPool[$s - 1] = 1; + if ('2' == $code[$i-2]) $forPool[$s] = 1; } $f[++$j] = '('; @@ -480,88 +436,42 @@ class JSqueeze case ']': case ')': - if ($i + 1 < $len && !isset($forPool[$s]) && !isset($instrPool[$s - 1]) && preg_match("'[a-zA-Z0-9_\$]'", $code[$i + 1])) { + if ($i+1 < $len && !isset($forPool[$s]) && !isset($instrPool[$s-1]) && preg_match("'[a-zA-Z0-9_\$]'", $code[$i+1])) + { $f[$j] .= $code[$i]; $f[++$j] = "\n"; - } else { - $f[++$j] = $code[$i]; } + else $f[++$j] = $code[$i]; - if (')' == $code[$i]) { + if (')' == $code[$i]) + { unset($forPool[$s]); --$s; - if (isset($instrPool[$s]) && 5 === $instrPool[$s]) { - $f[$j - 1] .= ')'; - $f[$j] = ';'; - } } continue 2; - case '{': - ++$d; - $f[++$j] = '{'; - break; - case '}': - --$d; - if ("\n" == $f[$j]) { - $f[$j] = '}'; - } else { - $f[++$j] = '}'; - } + if ("\n" == $f[$j]) $f[$j] = '}'; + else $f[++$j] = '}'; break; - case '+': - case '-': - $f[++$j] = $code[$i]; - if ($i + 1 < $len - && ($code[$i] === $code[$i + 1] || '#' === $code[$i + 1]) - ) { - // delay unsetting $instrPool[$s] - continue 2; - } + case ';': + if (isset($forPool[$s]) || isset($instrPool[$s])) $f[++$j] = ';'; + else if ($j>=0 && "\n" != $f[$j] && ';' != $f[$j]) $f[++$j] = "\n"; + break; case '#': - switch ($f[$j]) { + switch ($f[$j]) + { case '1': $f[$j] = 'if'; break 2; case '2': $f[$j] = 'for'; break 2; case '3': $f[$j] = 'while'; break 2; - case '4': - // special case `while` that doesn't correspond to the `do` - $f[$j] = 'do while'; - $doPool[$d] = 1; - break 2; - case '5': - $f[$j] = 'do'; - $doPool[$d] = 1; - case ';': // added after `do..while` - no extra `;` needed - break 2; - case ')': - case '+': - case '-': - if (isset($instrPool[$s])) { - // prefix operator in conditional/loop statement - no `;` - break 2; - } - //else treat as `;` - //default should never happen } - case ';': - if (isset($forPool[$s]) || isset($instrPool[$s]) && 5 !== $instrPool[$s]) { - $f[++$j] = ';'; - } elseif ($j >= 0 && "\n" != $f[$j] && ';' != $f[$j]) { - $f[++$j] = "\n"; - } - - break; - case '['; - if ($j >= 0 && "\n" == $f[$j]) { - $f[$j] = ';'; - } + if ($j>=0 && "\n" == $f[$j]) $f[$j] = ';'; default: $f[++$j] = $code[$i]; } @@ -570,39 +480,37 @@ class JSqueeze } $f = implode('', $f); - $cc_on && $f = str_replace('@#3', "\r", $f); + $cc_on && $f = str_replace('@#3', "\n", $f); // Fix "else ;" empty instructions - $f = preg_replace("'(?<![\$.a-zA-Z0-9_])else([\n}])'", '$1', $f); + $f = preg_replace("'(?<![\$.a-zA-Z0-9_])else\n'", "\n", $f); $r1 = array( // keywords with a direct object - 'case','delete','do','else','function','in','instanceof','of','break', + 'case','delete','do','else','function','in','instanceof','break', 'new','return','throw','typeof','var','void','yield','let','if', - 'const','get','set','continue', ); $r2 = array( // keywords with a subject - 'in','instanceof','of', + 'in','instanceof', ); // Fix missing semi-colons - $f = preg_replace("'(?<!(?<![a-zA-Z0-9_\$])".implode(')(?<!(?<![a-zA-Z0-9_\$])', $r1).') (?!('.implode('|', $r2).")(?![a-zA-Z0-9_\$]))'", "\n", $f); + $f = preg_replace("'(?<!(?<![a-zA-Z0-9_\$])" . implode(')(?<!(?<![a-zA-Z0-9_\$])', $r1) . ") (?!(" . implode('|', $r2) . ")(?![a-zA-Z0-9_\$]))'", "\n", $f); $f = preg_replace("'(?<!(?<![a-zA-Z0-9_\$])do)(?<!(?<![a-zA-Z0-9_\$])else) if\('", "\nif(", $f); - $f = preg_replace("'(?<=--|\+\+)(?<![a-zA-Z0-9_\$])(".implode('|', $r1).")(?![a-zA-Z0-9_\$])'", "\n$1", $f); + $f = preg_replace("'(?<=--|\+\+)(?<![a-zA-Z0-9_\$])(" . implode('|', $r1) . ")(?![a-zA-Z0-9_\$])'", "\n$1", $f); $f = preg_replace("'(?<![a-zA-Z0-9_\$])for\neach\('", 'for each(', $f); - $f = preg_replace("'(?<![a-zA-Z0-9_\$])\n(".implode('|', $r2).")(?![a-zA-Z0-9_\$])'", '$1', $f); + $f = preg_replace("'(?<![a-zA-Z0-9_\$])\n(" . implode('|', $r2) . ")(?![a-zA-Z0-9_\$])'", '$1', $f); // Merge strings - if ($q["'"] > $q['"']) { - $q = array($q[1], $q[0]); - } - $f = preg_replace("#//''\"\"[0-9]+'#", $q[0].'$0'.$q[0], $f); - strpos($f, $q[0].'+'.$q[0]) && $f = str_replace($q[0].'+'.$q[0], '', $f); + if ($q["'"] > $q['"']) $q = array($q[1], $q[0]); + $f = preg_replace("#//''\"\"[0-9]+'#", $q[0] . '$0' . $q[0], $f); + strpos($f, $q[0] . '+' . $q[0]) && $f = str_replace($q[0] . '+' . $q[0], '', $f); $len = count($strings); - foreach ($strings as $r1 => &$r2) { + foreach ($strings as $r1 => &$r2) + { $r2 = "/'" == substr($r1, -2) ? str_replace(array("\\'", '\\"'), array("'", '"'), $r2) - : str_replace('\\'.$q[1], $q[1], $r2); + : str_replace('\\' . $q[1], $q[1], $r2); } // Restore wanted spaces @@ -613,11 +521,12 @@ class JSqueeze protected function extractClosures($code) { - $code = ';'.$code; + $code = ';' . $code; $this->argFreq[-1] += substr_count($code, '}catch('); - if ($this->argFreq[-1]) { + if ($this->argFreq[-1]) + { // Special catch scope handling // FIXME: this implementation doesn't work with nested catch scopes who need @@ -625,29 +534,31 @@ class JSqueeze $f = preg_split("@}catch\(({$this->varRx})@", $code, -1, PREG_SPLIT_DELIM_CAPTURE); - $code = 'catch$scope$var'.mt_rand(); - $this->specialVarRx = $this->specialVarRx ? '(?:'.$this->specialVarRx.'|'.preg_quote($code).')' : preg_quote($code); + $code = 'catch$scope$var' . mt_rand(); + $this->specialVarRx = $this->specialVarRx ? '(?:' . $this->specialVarRx . '|' . preg_quote($code) . ')' : preg_quote($code); $i = count($f) - 1; - while ($i) { + while ($i) + { $c = 1; $j = 0; $l = strlen($f[$i]); - while ($c && $j < $l) { + while ($c && $j < $l) + { $s = $f[$i][$j++]; $c += '(' == $s ? 1 : (')' == $s ? -1 : 0); } - if (!$c) { - do { - $s = $f[$i][$j++]; - $c += '{' == $s ? 1 : ('}' == $s ? -1 : 0); - } while ($c && $j < $l); + if (!$c) do + { + $s = $f[$i][$j++]; + $c += '{' == $s ? 1 : ('}' == $s ? -1 : 0); } + while ($c && $j < $l); - $c = preg_quote($f[$i - 1], '#'); - $f[$i - 2] .= '}catch('.preg_replace("#([.,{]?)(?<![a-zA-Z0-9_\$@]){$c}\\b#", '$1'.$code, $f[$i - 1].substr($f[$i], 0, $j)).substr($f[$i], $j); + $c = preg_quote($f[$i-1], '#'); + $f[$i-2] .= '}catch(' . preg_replace("#([.,{]?)(?<![a-zA-Z0-9_\$@]){$c}\\b#", '$1' . $code, $f[$i-1] . substr($f[$i], 0, $j)) . substr($f[$i], $j); unset($f[$i--], $f[$i--]); } @@ -655,39 +566,39 @@ class JSqueeze $code = $f[0]; } - $f = preg_split("'(?<![a-zA-Z0-9_\$])((?:function[ (]|get |set ).*?\{)'", $code, -1, PREG_SPLIT_DELIM_CAPTURE); + $f = preg_split("'(?<![a-zA-Z0-9_\$])(function[ (].*?\{)'", $code, -1, PREG_SPLIT_DELIM_CAPTURE); $i = count($f) - 1; $closures = array(); - while ($i) { + while ($i) + { $c = 1; $j = 0; $l = strlen($f[$i]); - while ($c && $j < $l) { + while ($c && $j < $l) + { $s = $f[$i][$j++]; $c += '{' == $s ? 1 : ('}' == $s ? -1 : 0); } - switch (substr($f[$i - 2], -1)) { - default: - if (false !== $c = strpos($f[$i - 1], ' ', 8)) { - break; - } + switch (substr($f[$i-2], -1)) + { + default: if (false !== $c = strpos($f[$i-1], ' ', 8)) break; case false: case "\n": case ';': case '{': case '}': case ')': case ']': - $c = strpos($f[$i - 1], '(', 4); + $c = strpos($f[$i-1], '(', 8); } $l = "//''\"\"#$i'"; - $code = substr($f[$i - 1], $c); - $closures[$l] = $code.substr($f[$i], 0, $j); - $f[$i - 2] .= substr($f[$i - 1], 0, $c).$l.substr($f[$i], $j); + $code = substr($f[$i-1], $c); + $closures[$l] = $code . substr($f[$i], 0, $j); + $f[$i-2] .= substr($f[$i-1], 0, $c) . $l . substr($f[$i], $j); - if ('(){' !== $code) { + if ('(){' !== $code) + { $j = substr_count($code, ','); - do { - isset($this->argFreq[$j]) ? ++$this->argFreq[$j] : $this->argFreq[$j] = 1; - } while ($j--); + do isset($this->argFreq[$j]) ? ++$this->argFreq[$j] : $this->argFreq[$j] = 1; + while ($j--); } $i -= 2; @@ -698,57 +609,58 @@ class JSqueeze protected function makeVars($closure, &$tree, $key) { - $tree['code'] = &$closure; + $tree['code'] =& $closure; $tree['nfe'] = false; $tree['used'] = array(); $tree['local'] = array(); // Replace multiple "var" declarations by a single one - $closure = preg_replace_callback("'(?<=[\n\{\}])var [^\n\{\};]+(?:\nvar [^\n\{\};]+)+'", array($this, 'mergeVarDeclarations'), $closure); + $closure = preg_replace_callback("'(?<=[\n\{\}])var [^\n\{\}]+(?:\nvar [^\n\{\}]+)+'", array(&$this, 'mergeVarDeclarations'), $closure); // Get all local vars (functions, arguments and "var" prefixed) - $vars = &$tree['local']; + $vars =& $tree['local']; - if (preg_match("'^( [^(]*)?\((.*?)\)\{'", $closure, $v)) { - if ($v[1]) { + if (preg_match("'^( [^(]*)?\((.*?)\)\{'", $closure, $v)) + { + if ($v[1]) + { $vars[$tree['nfe'] = substr($v[1], 1)] = -1; - $tree['parent']['local'][';'.$key] = &$vars[$tree['nfe']]; + $tree['parent']['local'][';' . $key] =& $vars[$tree['nfe']]; } - if ($v[2]) { + if ($v[2]) + { $i = 0; $v = explode(',', $v[2]); - foreach ($v as $w) { - $vars[$w] = $this->argFreq[$i++] - 1; // Give a bonus to argument variables - } + foreach ($v as $w) $vars[$w] = $this->argFreq[$i++] - 1; // Give a bonus to argument variables } } $v = preg_split("'(?<![\$.a-zA-Z0-9_])var '", $closure); - if ($i = count($v) - 1) { + if ($i = count($v) - 1) + { $w = array(); - while ($i) { + while ($i) + { $j = $c = 0; $l = strlen($v[$i]); - while ($j < $l) { - switch ($v[$i][$j]) { + while ($j < $l) + { + switch ($v[$i][$j]) + { case '(': case '[': case '{': ++$c; break; case ')': case ']': case '}': - if ($c-- <= 0) { - break 2; - } + if ($c-- <= 0) break 2; break; case ';': case "\n": - if (!$c) { - break 2; - } + if (!$c) break 2; default: $c || $w[] = $v[$i][$j]; @@ -762,142 +674,115 @@ class JSqueeze } $v = explode(',', implode('', $w)); - foreach ($v as $w) { - if (preg_match("'^{$this->varRx}'", $w, $v)) { - isset($vars[$v[0]]) || $vars[$v[0]] = 0; - } - } + foreach ($v as $w) if (preg_match("'^{$this->varRx}'", $w, $v)) isset($vars[$v[0]]) || $vars[$v[0]] = 0; } - if (preg_match_all("@function ({$this->varRx})//''\"\"#@", $closure, $v)) { - foreach ($v[1] as $w) { - isset($vars[$w]) || $vars[$w] = 0; - } + if (preg_match_all("@function ({$this->varRx})//''\"\"#@", $closure, $v)) + { + foreach ($v[1] as $w) isset($vars[$w]) || $vars[$w] = 0; } - if ($this->argFreq[-1] && preg_match_all("@}catch\(({$this->varRx})@", $closure, $v)) { + if ($this->argFreq[-1] && preg_match_all("@}catch\(({$this->varRx})@", $closure, $v)) + { $v[0] = array(); - foreach ($v[1] as $w) { - isset($v[0][$w]) ? ++$v[0][$w] : $v[0][$w] = 1; - } - foreach ($v[0] as $w => $v) { - $vars[$w] = $this->argFreq[-1] - $v; - } + foreach ($v[1] as $w) isset($v[0][$w]) ? ++$v[0][$w] : $v[0][$w] = 1; + foreach ($v[0] as $w => $v) $vars[$w] = $this->argFreq[-1] - $v; } // Get all used vars, local and non-local - $vars = &$tree['used']; - - if (preg_match_all("#([.,{]?(?:[gs]et )?)(?<![a-zA-Z0-9_\$])({$this->varRx})(:?)#", $closure, $w, PREG_SET_ORDER)) { - foreach ($w as $k) { - if (isset($k[1][0]) && (',' === $k[1][0] || '{' === $k[1][0])) { - if (':' === $k[3]) { - $k = '.'.$k[2]; - } elseif ('get ' === substr($k[1], 1, 4) || 'set ' === substr($k[1], 1, 4)) { - ++$this->charFreq[ord($k[1][1])]; // "g" or "s" - ++$this->charFreq[101]; // "e" - ++$this->charFreq[116]; // "t" - $k = '.'.$k[2]; - } else { - $k = $k[2]; - } - } else { - $k = $k[1].$k[2]; + $vars =& $tree['used']; + + if (preg_match_all("#([.,{]?)(?<![a-zA-Z0-9_\$])({$this->varRx})(:?)#", $closure, $w, PREG_SET_ORDER)) + { + foreach ($w as $k) + { + if (',' === $k[1] || '{' === $k[1]) + { + if (':' === substr($k[3], -1)) $k = '.' . $k[2]; + else $k = $k[2]; } + else $k = $k[1] . $k[2]; isset($vars[$k]) ? ++$vars[$k] : $vars[$k] = 1; } } - if (preg_match_all("#//''\"\"[0-9]+(?:['!]|/')#", $closure, $w)) { - foreach ($w[0] as $a) { - $v = "'" === substr($a, -1) && "/'" !== substr($a, -2) && $this->specialVarRx - ? preg_split("#([.,{]?(?:[gs]et )?(?<![a-zA-Z0-9_\$@]){$this->specialVarRx}:?)#", $this->strings[$a], -1, PREG_SPLIT_DELIM_CAPTURE) - : array($this->strings[$a]); - $a = count($v); - - for ($i = 0; $i < $a; ++$i) { - $k = $v[$i]; - - if (1 === $i % 2) { - if (',' === $k[0] || '{' === $k[0]) { - if (':' === substr($k, -1)) { - $k = '.'.substr($k, 1, -1); - } elseif ('get ' === substr($k, 1, 4) || 'set ' === substr($k, 1, 4)) { - ++$this->charFreq[ord($k[1])]; // "g" or "s" - ++$this->charFreq[101]; // "e" - ++$this->charFreq[116]; // "t" - $k = '.'.substr($k, 5); - } else { - $k = substr($k, 1); - } - } elseif (':' === substr($k, -1)) { - $k = substr($k, 0, -1); - } + if (preg_match_all("#//''\"\"[0-9]+(?:['!]|/')#", $closure, $w)) foreach ($w[0] as $a) + { + $v = "'" === substr($a, -1) && "/'" !== substr($a, -2) && $this->specialVarRx + ? preg_split("#([.,{]?(?<![a-zA-Z0-9_\$@]){$this->specialVarRx}:?)#", $this->strings[$a], -1, PREG_SPLIT_DELIM_CAPTURE) + : array($this->strings[$a]); + $a = count($v); + + for ($i = 0; $i < $a; ++$i) + { + $k = $v[$i]; + + if (1 === $i%2) + { + if (',' === $k[0] || '{' === $k[0]) + { + if (':' === substr($k, -1)) $k = '.' . substr($k, 1, -1); + else $k = substr($k, 1); + } + else if (':' === substr($k, -1)) $k = substr($k, 0, -1); - $w = &$tree; + $w =& $tree; - while (isset($w['parent']) && !(isset($w['used'][$k]) || isset($w['local'][$k]))) { - $w = &$w['parent']; - } + while (isset($w['parent']) && !(isset($w['used'][$k]) || isset($w['local'][$k]))) $w =& $w['parent']; - (isset($w['used'][$k]) || isset($w['local'][$k])) && (isset($vars[$k]) ? ++$vars[$k] : $vars[$k] = 1); + (isset($w['used'][$k]) || isset($w['local'][$k])) && (isset($vars[$k]) ? ++$vars[$k] : $vars[$k] = 1); - unset($w); - } - - if (0 === $i % 2 || !isset($vars[$k])) { - foreach (count_chars($v[$i], 1) as $k => $w) { - $this->charFreq[$k] += $w; - } - } + unset($w); } + + if (0 === $i%2 || !isset($vars[$k])) foreach (count_chars($v[$i], 1) as $k => $w) $this->charFreq[$k] += $w; } } // Propagate the usage number to parents - foreach ($vars as $w => $a) { - $k = &$tree; + foreach ($vars as $w => $a) + { + $k =& $tree; $chain = array(); - do { - $vars = &$k['local']; - $chain[] = &$k; - if (isset($vars[$w])) { + do + { + $vars =& $k['local']; + $chain[] =& $k; + if (isset($vars[$w])) + { unset($k['used'][$w]); - if (isset($vars[$w])) { - $vars[$w] += $a; - } else { - $vars[$w] = $a; - } + if (isset($vars[$w])) $vars[$w] += $a; + else $vars[$w] = $a; $a = false; break; } - } while ($k['parent'] && $k = &$k['parent']); + } + while ($k['parent'] && $k =& $k['parent']); - if ($a && !$k['parent']) { - if (isset($vars[$w])) { - $vars[$w] += $a; - } else { - $vars[$w] = $a; - } + if ($a && !$k['parent']) + { + if (isset($vars[$w])) $vars[$w] += $a; + else $vars[$w] = $a; } - if (isset($tree['used'][$w]) && isset($vars[$w])) { - foreach ($chain as &$b) { - isset($b['local'][$w]) || $b['used'][$w] = &$vars[$w]; - } + if (isset($tree['used'][$w]) && isset($vars[$w])) foreach ($chain as &$b) + { + isset($b['local'][$w]) || $b['used'][$w] =& $vars[$w]; } } - // Analyse children + // Analyse childs - $tree['children'] = array(); - $vars = &$tree['children']; + $tree['childs'] = array(); + $vars =& $tree['childs']; - if (preg_match_all("@//''\"\"#[0-9]+'@", $closure, $w)) { - foreach ($w[0] as $a) { + if (preg_match_all("@//''\"\"#[0-9]+'@", $closure, $w)) + { + foreach ($w[0] as $a) + { $vars[$a] = array('parent' => &$tree); $this->makeVars($this->closures[$a], $vars[$a], $a); } @@ -911,125 +796,120 @@ class JSqueeze protected function renameVars(&$tree, $root) { - if ($root) { + if ($root) + { $tree['local'] += $tree['used']; $tree['used'] = array(); - foreach ($tree['local'] as $k => $v) { - if ('.' == $k[0]) { - $k = substr($k, 1); - } + foreach ($tree['local'] as $k => $v) + { + if ('.' == $k[0]) $k = substr($k, 1); - if ('true' === $k) { - $this->charFreq[48] += $v; - } elseif ('false' === $k) { - $this->charFreq[49] += $v; - } elseif (!$this->specialVarRx || !preg_match("#^{$this->specialVarRx}$#", $k)) { - foreach (count_chars($k, 1) as $k => $w) { - $this->charFreq[$k] += $w * $v; - } - } elseif (2 == strlen($k)) { - $tree['used'][] = $k[1]; + if ('true' === $k) $this->charFreq[48] += $v; + else if ('false' === $k) $this->charFreq[49] += $v; + else if (!$this->specialVarRx || !preg_match("#^{$this->specialVarRx}$#", $k)) + { + foreach (count_chars($k, 1) as $k => $w) $this->charFreq[$k] += $w * $v; } + else if (2 == strlen($k)) $tree['used'][] = $k[1]; } - $this->charFreq = $this->rsort($this->charFreq); + arsort($this->charFreq); $this->str0 = ''; $this->str1 = ''; - foreach ($this->charFreq as $k => $v) { - if (!$v) { - break; - } + foreach ($this->charFreq as $k => $v) + { + if (!$v) break; $v = chr($k); - if ((64 < $k && $k < 91) || (96 < $k && $k < 123)) { // A-Z a-z + if ((64 < $k && $k < 91) || (96 < $k && $k < 123)) // A-Z a-z + { $this->str0 .= $v; $this->str1 .= $v; - } elseif (47 < $k && $k < 58) { // 0-9 + } + else if (47 < $k && $k < 58) // 0-9 + { $this->str1 .= $v; } } - if ('' === $this->str0) { + if ('' === $this->str0) + { $this->str0 = 'claspemitdbfrugnjvhowkxqyzCLASPEMITDBFRUGNJVHOWKXQYZ'; - $this->str1 = $this->str0.'0123456789'; + $this->str1 = $this->str0 . '0123456789'; } - foreach ($tree['local'] as $var => $root) { - if ('.' != substr($var, 0, 1) && isset($tree['local'][".{$var}"])) { - $tree['local'][$var] += $tree['local'][".{$var}"]; - } + foreach ($tree['local'] as $var => $root) + { + if ('.' != substr($var, 0, 1) && isset($tree['local'][".{$var}"])) $tree['local'][$var] += $tree['local'][".{$var}"]; } - foreach ($tree['local'] as $var => $root) { - if ('.' == substr($var, 0, 1) && isset($tree['local'][substr($var, 1)])) { - $tree['local'][$var] = $tree['local'][substr($var, 1)]; - } + foreach ($tree['local'] as $var => $root) + { + if ('.' == substr($var, 0, 1) && isset($tree['local'][substr($var, 1)])) $tree['local'][$var] = $tree['local'][substr($var, 1)]; } - $tree['local'] = $this->rsort($tree['local']); + arsort($tree['local']); - foreach ($tree['local'] as $var => $root) { - switch (substr($var, 0, 1)) { - case '.': - if (!isset($tree['local'][substr($var, 1)])) { - $tree['local'][$var] = '#'.($this->specialVarRx && 3 < strlen($var) && preg_match("'^\.{$this->specialVarRx}$'", $var) ? $this->getNextName($tree).'$' : substr($var, 1)); - } - break; + foreach ($tree['local'] as $var => $root) switch (substr($var, 0, 1)) + { + case '.': + if (!isset($tree['local'][substr($var, 1)])) + { + $tree['local'][$var] = '#' . ($this->specialVarRx && 3 < strlen($var) && preg_match("'^\.{$this->specialVarRx}$'", $var) ? $this->getNextName($tree) . '$' : substr($var, 1)); + } + break; - case ';': $tree['local'][$var] = 0 === $root ? '' : $this->getNextName($tree); - case '#': break; + case ';': $tree['local'][$var] = 0 === $root ? '' : $this->getNextName($tree); + case '#': break; - default: - $root = $this->specialVarRx && 2 < strlen($var) && preg_match("'^{$this->specialVarRx}$'", $var) ? $this->getNextName($tree).'$' : $var; - $tree['local'][$var] = $root; - if (isset($tree['local'][".{$var}"])) { - $tree['local'][".{$var}"] = '#'.$root; - } - } + default: + $root = $this->specialVarRx && 2 < strlen($var) && preg_match("'^{$this->specialVarRx}$'", $var) ? $this->getNextName($tree) . '$' : $var; + $tree['local'][$var] = $root; + if (isset($tree['local'][".{$var}"])) $tree['local'][".{$var}"] = '#' . $root; } - foreach ($tree['local'] as $var => $root) { - $tree['local'][$var] = preg_replace("'^#'", '.', $tree['local'][$var]); - } - } else { - $tree['local'] = $this->rsort($tree['local']); - if (false !== $tree['nfe']) { - $tree['used'][] = $tree['local'][$tree['nfe']]; - } + foreach ($tree['local'] as $var => $root) $tree['local'][$var] = preg_replace("'^#'", '.', $tree['local'][$var]); + } + else + { + arsort($tree['local']); + if (false !== $tree['nfe']) $tree['used'][] = $tree['local'][$tree['nfe']]; - foreach ($tree['local'] as $var => $root) { - if ($tree['nfe'] !== $var) { + foreach ($tree['local'] as $var => $root) + if ($tree['nfe'] !== $var) $tree['local'][$var] = 0 === $root ? '' : $this->getNextName($tree); - } - } } - $this->local_tree = &$tree['local']; - $this->used_tree = &$tree['used']; + $this->local_tree =& $tree['local']; + $this->used_tree =& $tree['used']; - $tree['code'] = preg_replace_callback("#[.,{ ]?(?:[gs]et )?(?<![a-zA-Z0-9_\$@]){$this->varRx}:?#", array($this, 'getNewName'), $tree['code']); - - if ($this->specialVarRx && preg_match_all("#//''\"\"[0-9]+'#", $tree['code'], $b)) { - foreach ($b[0] as $a) { - $this->strings[$a] = preg_replace_callback( - "#[.,{]?(?:[gs]et )?(?<![a-zA-Z0-9_\$@]){$this->specialVarRx}:?#", - array($this, 'getNewName'), - $this->strings[$a] - ); - } - } + $tree['code'] = preg_replace_callback("#[.,{ ]?(?<![a-zA-Z0-9_\$@]){$this->varRx}:?#", array(&$this, 'getNewName'), $tree['code']); + $this->specialVarRx && $tree['code'] = preg_replace_callback("#//''\"\"[0-9]+'#", array(&$this, 'renameInString'), $tree['code']); - foreach ($tree['children'] as $a => &$b) { + foreach ($tree['childs'] as $a => &$b) + { $this->renameVars($b, false); $tree['code'] = str_replace($a, $b['code'], $tree['code']); - unset($tree['children'][$a]); + unset($tree['childs'][$a]); } } + protected function renameInString($a) + { + $b =& $this->strings[$a[0]]; + unset($this->strings[$a[0]]); + + return preg_replace_callback( + "#[.,{]?(?<![a-zA-Z0-9_\$@]){$this->specialVarRx}:?#", + array(&$this, 'getNewName'), + $b + ); + } + protected function getNewName($m) { $m = $m[0]; @@ -1037,25 +917,25 @@ class JSqueeze $pre = '.' === $m[0] ? '.' : ''; $post = ''; - if (',' === $m[0] || '{' === $m[0] || ' ' === $m[0]) { + if (',' === $m[0] || '{' === $m[0] || ' ' === $m[0]) + { $pre = $m[0]; - if (':' === substr($m, -1)) { + if (':' === substr($m, -1)) + { $post = ':'; - $m = (' ' !== $m[0] ? '.' : '').substr($m, 1, -1); - } elseif ('get ' === substr($m, 1, 4) || 'set ' === substr($m, 1, 4)) { - $pre .= substr($m, 1, 4); - $m = '.'.substr($m, 5); - } else { - $m = substr($m, 1); + $m = (' ' !== $m[0] ? '.' : '') . substr($m, 1, -1); } - } elseif (':' === substr($m, -1)) { + else $m = substr($m, 1); + } + else if (':' === substr($m, -1)) + { $post = ':'; $m = substr($m, 0, -1); } $post = (isset($this->reserved[$m]) - ? ('true' === $m ? '!0' : ('false' === $m ? '!1' : $m)) + ? ('true' === $m ? '!0' : ('false' === $m ? '!1': $m)) : ( isset($this->local_tree[$m]) ? $this->local_tree[$m] @@ -1065,20 +945,20 @@ class JSqueeze : $m ) ) - ).$post; + ) . $post; - return '' === $post ? '' : ($pre.('.' === $post[0] ? substr($post, 1) : $post)); + return '' === $post ? '' : ($pre . ('.' === $post[0] ? substr($post, 1) : $post)); } protected function getNextName(&$tree = array(), &$counter = false) { - if (false === $counter) { - $counter = &$tree['counter']; + if (false === $counter) + { + $counter =& $tree['counter']; isset($counter) || $counter = -1; $exclude = array_flip($tree['used']); - } else { - $exclude = $tree; } + else $exclude = $tree; ++$counter; @@ -1088,7 +968,8 @@ class JSqueeze $name = $this->str0[$counter % $len0]; $i = intval($counter / $len0) - 1; - while ($i >= 0) { + while ($i>=0) + { $name .= $this->str1[ $i % $len1 ]; $i = intval($i / $len1) - 1; } @@ -1105,47 +986,4 @@ class JSqueeze $s = str_replace('1#@', '/*@', $s); $s = str_replace('##', '#', $s); } - - protected function restoreString($m) - { - return $this->strings[$m[0]]; - } - - private function rsort($array) - { - if (!$array) { - return $array; - } - - $i = 0; - $tuples = array(); - foreach ($array as $k => &$v) { - $tuples[] = array(++$i, $k, &$v); - } - - usort($tuples, function ($a, $b) { - if ($b[2] > $a[2]) { - return 1; - } - if ($b[2] < $a[2]) { - return -1; - } - if ($b[0] > $a[0]) { - return -1; - } - if ($b[0] < $a[0]) { - return 1; - } - - return 0; - }); - - $array = array(); - - foreach ($tuples as $t) { - $array[$t[1]] = &$t[2]; - } - - return $array; - } } 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 b3a4ba6588..478888dc7c 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 @@ -8,200 +8,116 @@ /** * Form builder; Configure advagg settings. * - * @ingroup advagg_forms + * @ingroup forms * * @see system_settings_form() */ function advagg_mod_admin_settings_form() { drupal_set_title(t('AdvAgg: Modifications')); - advagg_display_message_if_requirements_not_met(); - $config_path = advagg_admin_config_root_path(); - $form = array(); - - $options = array( - 0 => t('Use default (safe) settings'), - 2 => t('Use recommended (optimized) settings'), - 4 => t('Use customized settings'), - ); - $form['advagg_mod_admin_mode'] = array( - '#type' => 'radios', - '#title' => t('AdvAgg Settings'), - '#default_value' => variable_get('advagg_mod_admin_mode', ADVAGG_MOD_ADMIN_MODE), - '#options' => $options, - '#description' => t("Default settings will mirror core as closely as possible. <br>Recommended settings are optimized for speed."), - ); - $form['global_container'] = array( - '#type' => 'container', - '#hidden' => TRUE, - '#states' => array( - 'visible' => array( - ':input[name="advagg_mod_admin_mode"]' => array('value' => '4'), - ), - ), - ); - // Tell user to update library if a new version is available. - $library_name = 'loadCSS'; - $module_name = 'advagg_mod'; - list($description) = advagg_get_version_description($library_name, $module_name); - if (!empty($description)) { - $form['global_container']['advagg_version_msg'] = array( - '#markup' => "<p>{$description}</p>", - ); - } - $form['global_container']['js'] = array( + $form = array(); + $form['js'] = array( '#type' => 'fieldset', '#title' => t('JS'), ); - $form['global_container']['js']['advagg_mod_js_preprocess'] = array( + $form['js']['advagg_mod_js_preprocess'] = array( '#type' => 'checkbox', - '#title' => t('Enable preprocess on all JS (recommended)'), + '#title' => t('Enable preprocess on all JS'), '#default_value' => variable_get('advagg_mod_js_preprocess', ADVAGG_MOD_JS_PREPROCESS), '#description' => t('Force all JavaScript to have the preprocess attribute be set to TRUE. All JavaScript files will be aggregated if enabled.'), - '#recommended_value' => TRUE, ); - $form['global_container']['js']['advagg_mod_js_remove_unused'] = array( + $form['js']['advagg_mod_js_remove_unused'] = array( '#type' => 'checkbox', '#title' => t('Remove unused JavaScript if possible'), '#default_value' => variable_get('advagg_mod_js_remove_unused', ADVAGG_MOD_JS_REMOVE_UNUSED), - '#description' => t('This will scan all included JS files for references to jquery and drupal. If none are found then the core JavaScript (jquery.js, drupal.js, Drupal.settings) is removed and not loaded on that page. If you have a site that does not use a lot of Javascript this might be helpful as it could prevent unused JavaScript from being executed, thus speeding up your sites frontend performance. Enabling this usually has negative backend performance impact.'), + '#description' => t('This will scan all included JS files for references to jquery and drupal. If none are found then the core JavaScript (jquery.js, drupal.js, & Drupal.settings) is removed and not loaded on that page. If you have a site that does not use a lot of Javascript this might be helpful as it could prevent unused JavaScript from being executed, thus speeding up your sites frontend performance. Enabling this usually has negative backend performance impact.'), ); - $form['global_container']['js']['advagg_mod_js_no_ajaxpagestate'] = array( - '#type' => 'checkbox', - '#title' => t('Remove ajaxPageState CSS and JS data if ajax.js is not used on this page (recommended)'), - '#default_value' => variable_get('advagg_mod_js_no_ajaxpagestate', ADVAGG_MOD_JS_NO_AJAXPAGESTATE), - '#description' => t('This assumes that the only thing that uses Drupal.settings.ajaxPageState.css and Drupal.settings.ajaxPageState.js is ajax.js.'), - '#recommended_value' => TRUE, - ); - $form['global_container']['js']['advagg_mod_js_inline_resource_hints'] = array( - '#type' => 'checkbox', - '#title' => t('Resource hint src attributes found in the HTML content (recommended)'), - '#default_value' => variable_get('advagg_mod_js_inline_resource_hints', ADVAGG_MOD_JS_INLINE_RESOURCE_HINTS), - '#description' => t('Scan any JavaScript that was added to the content part of the page incorrectly for a src attribute; if found use the advagg <a href="@url">resource hints settings</a> for faster loading.', array('@url' => url($config_path . '/advagg', array('fragment' => 'edit-resource-hints')))), - '#disabled' => !variable_get('advagg_resource_hints_dns_prefetch', ADVAGG_RESOURCE_HINTS_DNS_PREFETCH) && !variable_get('advagg_resource_hints_preconnect', ADVAGG_RESOURCE_HINTS_PRECONNECT) && !variable_get('advagg_resource_hints_preload', ADVAGG_RESOURCE_HINTS_PRELOAD), - '#recommended_value' => TRUE, - ); - if ($form['global_container']['js']['advagg_mod_js_inline_resource_hints']['#disabled']) { - $form['global_container']['js']['advagg_mod_js_inline_resource_hints']['#description'] .= ' ' . t('Currently disabled; to enable check the desired checkboxes under Resource Hints on the <a href="@url">configuration page</a>', array('@url' => url($config_path . '/advagg', array('fragment' => 'edit-resource-hints')))); - } // Optimize JavaScript Ordering. - $form['global_container']['js']['adjust_sort'] = array( + $form['js']['adjust_sort'] = array( '#type' => 'fieldset', '#title' => t('Optimize JavaScript Ordering'), '#description' => t('The settings in here might change the order in which the JavaScript is loaded. It will move the scripts around so that more optimal aggregates are built. In most cases enabling these checkboxes will cause no negative side effects.'), ); - $form['global_container']['js']['adjust_sort']['advagg_mod_js_head_extract'] = array( + $form['js']['adjust_sort']['advagg_mod_js_head_extract'] = array( '#type' => 'checkbox', - '#title' => t('Move JavaScript added by drupal_add_html_head() into drupal_add_js() (recommended)'), + '#title' => t('Move JavaScript added by drupal_add_html_head() into drupal_add_js()'), '#default_value' => variable_get('advagg_mod_js_head_extract', ADVAGG_MOD_JS_HEAD_EXTRACT), '#description' => t('This will move JavaScript added incorrectly to Drupal into the top of the drupal_add_js() queue.'), - '#recommended_value' => TRUE, ); - $form['global_container']['js']['adjust_sort']['advagg_mod_js_adjust_sort_external'] = array( + $form['js']['adjust_sort']['advagg_mod_js_adjust_sort_external'] = array( '#type' => 'checkbox', - '#title' => t('Move all external scripts to the top of the execution order (recommended)'), + '#title' => t('Move all external scripts to the top of the execution order'), '#default_value' => variable_get('advagg_mod_js_adjust_sort_external', ADVAGG_MOD_JS_ADJUST_SORT_EXTERNAL), '#description' => t('This will group all external JavaScript files to be above all other JavaScript.'), - '#recommended_value' => TRUE, ); - $form['global_container']['js']['adjust_sort']['advagg_mod_js_adjust_sort_inline'] = array( + $form['js']['adjust_sort']['advagg_mod_js_adjust_sort_inline'] = array( '#type' => 'checkbox', - '#title' => t('Move all inline scripts to the bottom of the execution order (recommended)'), + '#title' => t('Move all inline scripts to the bottom of the execution order'), '#default_value' => variable_get('advagg_mod_js_adjust_sort_inline', ADVAGG_MOD_JS_ADJUST_SORT_INLINE), '#description' => t('This will group all inline JavaScript to be below all other JavaScript.'), - '#recommended_value' => TRUE, ); - $form['global_container']['js']['adjust_sort']['advagg_mod_js_adjust_sort_browsers'] = array( + $form['js']['adjust_sort']['advagg_mod_js_adjust_sort_browsers'] = array( '#type' => 'checkbox', - '#title' => t('Move all browser conditional JavaScript to the bottom of the group (recommended)'), + '#title' => t('Move all browser conditional JavaScript to the bottom of the group'), '#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.'), - '#recommended_value' => TRUE, ); - if (module_exists('googleanalytics') - && is_callable('googleanalytics_api') - && is_callable('_googleanalytics_cache') - ) { - $form['global_container']['js']['adjust_sort']['expert'] = array( + if (module_exists('googleanalytics') && variable_get('googleanalytics_cache', 0)) { + $form['js']['adjust_sort']['expert'] = array( '#type' => 'fieldset', '#title' => t('Experimental Settings'), '#collapsible' => TRUE, '#collapsed' => !variable_get('advagg_mod_ga_inline_to_file', ADVAGG_MOD_GA_INLINE_TO_FILE), ); - $form['global_container']['js']['adjust_sort']['expert']['advagg_mod_ga_inline_to_file'] = array( + $form['js']['adjust_sort']['expert']['advagg_mod_ga_inline_to_file'] = array( '#type' => 'checkbox', '#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), - '#description' => t('The AdvAgg Relocate module is more capable. Recommend using it if you are using this functionality. This setting will soon be deprecated.'), - ); - $form['global_container']['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. If the browser supports preconnect and under Resource Hints on the <a href="@url">configuration page</a>, preconnect is enabled (currently %state), then it will use preconnect instead of this robots.txt hack.', array( - '@url' => url($config_path . '/advagg', array('fragment' => 'edit-resource-hints')), - '%state' => variable_get('advagg_resource_hints_preconnect', ADVAGG_RESOURCE_HINTS_PRECONNECT) ? t('enabled') : t('disabled'), - )), ); } // Adjust javascript location and execution. - $form['global_container']['js']['placement'] = array( + $form['js']['placement'] = array( '#type' => 'fieldset', '#title' => t('Adjust javascript location and execution'), '#description' => t('Most of the time adjusting the settings are safe but in some rare cases adjusting these can cause serious JavaScript issues with your site.'), ); - $form['global_container']['js']['placement']['advagg_mod_js_footer'] = array( + $form['js']['placement']['advagg_mod_js_footer'] = array( '#type' => 'radios', '#title' => t('Move JS to the footer'), '#default_value' => variable_get('advagg_mod_js_footer', ADVAGG_MOD_JS_FOOTER), '#options' => array( 0 => t('Disabled'), 1 => t('All but JavaScript Libraries'), - 3 => t('All but what is in the $all_in_footer_list (recommended)'), - 2 => t('All (might break things)'), + 2 => t('All'), ), '#description' => t("If you have JavaScript inline in the body of your document, such as if you are displaying ads, you may need to keep Drupal JS Libraries in the head instead of moving them to the footer. This will keep all JS added with the JS_LIBRARY group in the head while still moving all other JavaScript to the footer."), - '#recommended_value' => 3, ); - $form['global_container']['js']['placement']['advagg_mod_js_defer'] = array( - '#type' => 'radios', + $form['js']['placement']['advagg_mod_js_defer'] = array( + '#type' => 'checkbox', '#title' => t('Deferred JavaScript Execution: Add The defer Tag To All Script Tags'), '#default_value' => variable_get('advagg_mod_js_defer', ADVAGG_MOD_JS_DEFER), - '#options' => array( - 0 => t('Disabled'), - 2 => t('All but external scripts (recommended)'), - 1 => t('All (might break things)'), - ), '#description' => t('This will delay the script execution until the HTML parser has finished. This will have a similar effect to moving all JavaScript to the footer. This might break javascript (especially inline); only use after extensive testing! <a href="@link">More Info</a>', array( '@link' => 'http://peter.sh/experiments/asynchronous-and-deferred-javascript-execution-explained/', )), - '#recommended_value' => 2, - ); - $form['global_container']['js']['placement']['advagg_mod_js_async_in_header'] = array( - '#type' => 'checkbox', - '#title' => t('Asynchronous JavaScript Execution: Group together in the header (recommended)'), - '#default_value' => variable_get('advagg_mod_js_async_in_header', ADVAGG_MOD_JS_ASYNC_IN_HEADER), - '#description' => t('This will move all async JavaScript code to the header in the same group.'), - '#recommended_value' => TRUE, ); - $form['global_container']['js']['placement']['advagg_mod_js_footer_inline_alter'] = array( + $form['js']['placement']['advagg_mod_js_footer_inline_alter'] = array( '#type' => 'checkbox', - '#title' => t('Put a wrapper around inline JS if it was added in the content section incorrectly (recommended)'), + '#title' => t('Put a wrapper around inline JS if it was added in the content section incorrectly'), '#default_value' => variable_get('advagg_mod_js_footer_inline_alter', ADVAGG_MOD_JS_FOOTER_INLINE_ALTER), '#description' => t('This will put a wrapper around any inline JavaScript that was added to the content part of the page incorrectly. The wrapper will check every 250ms until window.Drupal.settings and window.jQuery are defined; at that point the inlined code will then run.'), - '#recommended_value' => TRUE, + '#states' => array( 'disabled' => array( ':input[name="advagg_mod_js_footer"]' => array('value' => '0'), - ':input[name="advagg_mod_js_defer"]' => array('value' => '0'), + ':input[name="advagg_mod_js_defer"]' => array('checked' => FALSE), ':input[name="advagg_mod_js_async"]' => array('checked' => FALSE), ), ), ); $advagg_mod_wrap_inline_js_skip_list = trim(variable_get('advagg_mod_wrap_inline_js_skip_list', ADVAGG_MOD_WRAP_INLINE_JS_SKIP_LIST)); $advagg_mod_wrap_inline_js_xpath = variable_get('advagg_mod_wrap_inline_js_xpath', ADVAGG_MOD_WRAP_INLINE_JS_XPATH); - $form['global_container']['js']['placement']['advagg_mod_wrap_inline'] = array( + $form['js']['placement']['advagg_mod_wrap_inline'] = array( '#type' => 'fieldset', '#title' => t('Inline Wrapper Settings'), '#collapsible' => TRUE, @@ -212,7 +128,7 @@ function advagg_mod_admin_settings_form() { ), ), ); - $form['global_container']['js']['placement']['advagg_mod_wrap_inline']['advagg_mod_wrap_inline_js_skip_list'] = array( + $form['js']['placement']['advagg_mod_wrap_inline']['advagg_mod_wrap_inline_js_skip_list'] = array( '#type' => 'textarea', '#title' => t('Inline skip list for wrapper code'), '#default_value' => variable_get('advagg_mod_wrap_inline_js_skip_list', ADVAGG_MOD_WRAP_INLINE_JS_SKIP_LIST), @@ -222,85 +138,54 @@ function advagg_mod_admin_settings_form() { '@url' => 'https://developers.google.com/adwords-remarketing-tag/asynchronous/', )), ); - $form['global_container']['js']['placement']['advagg_mod_wrap_inline']['advagg_mod_wrap_inline_js_xpath'] = array( + $form['js']['placement']['advagg_mod_wrap_inline']['advagg_mod_wrap_inline_js_xpath'] = array( '#type' => 'checkbox', '#title' => t('Use XPath instead of regex when searching for inline scripts'), '#default_value' => variable_get('advagg_mod_wrap_inline_js_xpath', ADVAGG_MOD_WRAP_INLINE_JS_XPATH), '#description' => t('In general this should be disabled due to the unpredictable nature of parsing html snippets using DOMDocument loadHTML(). Only enable if you have script tags inside a textarea that have not been ran through htmlentities().'), - '#recommended_value' => TRUE, - ); - $form['global_container']['js']['placement']['advagg_mod_js_defer_inline_alter'] = array( - '#type' => 'checkbox', - '#title' => t('Deferred inline JavaScript Execution: Put a wrapper around inline JS so it runs from a setTimeout call (recommended).'), - '#default_value' => variable_get('advagg_mod_js_defer_inline_alter', ADVAGG_MOD_JS_DEFER_INLINE_ALTER), - '#description' => t('This will put a wrapper around any inline JavaScript.'), - '#recommended_value' => TRUE, ); - $advagg_mod_defer_inline_js_skip_list = trim(variable_get('advagg_mod_defer_inline_js_skip_list', ADVAGG_MOD_DEFER_INLINE_JS_SKIP_LIST)); - $form['global_container']['js']['placement']['advagg_mod_defer_inline'] = array( - '#type' => 'fieldset', - '#title' => t('Deferred Inline Settings'), - '#collapsible' => TRUE, - '#collapsed' => empty($advagg_mod_defer_inline_js_skip_list), - '#states' => array( - 'visible' => array( - ':input[name="advagg_mod_js_defer_inline_alter"]' => array('checked' => TRUE), - ), - ), - ); - $form['global_container']['js']['placement']['advagg_mod_defer_inline']['advagg_mod_defer_inline_js_skip_list'] = array( - '#type' => 'textarea', - '#title' => t('Inline skip list for wrapper code'), - '#default_value' => variable_get('advagg_mod_defer_inline_js_skip_list', ADVAGG_MOD_DEFER_INLINE_JS_SKIP_LIST), - '#description' => t("If the inline JavaScript matches a given string then the whole inline script will not be wrapped. Enter one per line."), - ); - $form['global_container']['js']['placement']['expert'] = array( + $form['js']['placement']['expert'] = array( '#type' => 'fieldset', '#title' => t('Experimental Settings'), + '#description' => t('Here there be dragons.'), '#collapsible' => TRUE, '#collapsed' => !variable_get('advagg_mod_js_async', ADVAGG_MOD_JS_ASYNC), ); - $form['global_container']['js']['placement']['expert']['advagg_mod_js_async'] = array( + $form['js']['placement']['expert']['advagg_mod_js_async'] = array( '#type' => 'checkbox', - '#title' => t('Here there be dragons! Asynchronous JavaScript Execution: Add The async Tag To All Script Tags'), + '#title' => t('Asynchronous JavaScript Execution: Add The async Tag To All Script Tags'), '#default_value' => variable_get('advagg_mod_js_async', ADVAGG_MOD_JS_ASYNC), '#description' => t('This will cause the script to be downloaded in the background and executed out of order. This will break most javascript as most code is not async safe; only use after extensive testing! <a href="@link">More Info</a>', array( '@link' => 'http://peter.sh/experiments/asynchronous-and-deferred-javascript-execution-explained/', )), ); // Outdated settings. - $form['global_container']['js']['old'] = array( + $form['js']['old'] = array( '#type' => 'fieldset', '#title' => t('Outdated settings that should not be used'), '#collapsible' => TRUE, '#collapsed' => !variable_get('advagg_mod_js_async_shim', ADVAGG_MOD_JS_ASYNC_SHIM), ); - $form['global_container']['js']['old']['advagg_mod_js_async_shim'] = array( + $form['js']['old']['advagg_mod_js_async_shim'] = array( '#type' => 'checkbox', '#title' => t('Rewrite asynchronous script tags to inline, old-browser-compatible scripts.'), '#default_value' => variable_get('advagg_mod_js_async_shim', ADVAGG_MOD_JS_ASYNC_SHIM), '#description' => t('Rewrites all scripts in the page with an "async" attribute to an inline JavaScript loading the script asynchronously in an old browser compatible way. List of <a href="@link">supported browsers</a>. Once all commonly used browsers support the "async" attribute you can happily disable this checkbox.', array('@link' => 'http://caniuse.com/script-async')), ); - $form['global_container']['css'] = array( + $config_path = advagg_admin_config_root_path(); + $form['css'] = array( '#type' => 'fieldset', '#title' => t('CSS'), ); - $form['global_container']['css']['advagg_mod_css_preprocess'] = array( + $form['css']['advagg_mod_css_preprocess'] = array( '#type' => 'checkbox', - '#title' => t('Enable preprocess on all CSS (recommended)'), + '#title' => t('Enable preprocess on all CSS'), '#default_value' => variable_get('advagg_mod_css_preprocess', ADVAGG_MOD_CSS_PREPROCESS), '#description' => t('Force all CSS to have the preprocess attribute be set to TRUE. All CSS files will be aggregated if enabled.'), - '#recommended_value' => TRUE, ); - $form['global_container']['css']['advagg_mod_css_preprocess']['#description'] .= module_exists('advagg_bundler') ? ' ' . t('You might want to increase the <a href="@link">CSS Bundles Per Page</a> if this is checked.', array('@link' => url($config_path . '/advagg/bundler'))) : ''; - if (is_callable('omega_extension_enabled') - && is_callable('omega_theme_get_setting') - && omega_extension_enabled('development') - && omega_theme_get_setting('omega_livereload', TRUE) - ) { - $form['global_container']['css']['advagg_mod_css_preprocess']['#description'] .= ' ' . t('The omega theme is in development mode and livereload is also enabled. This setting combination disables CSS aggregation. If you wish to have CSS aggregation turned on, go to the <a href="@url">theme settings page</a>, select the Omega theme/subtheme and turn off LiveReload and/or turn off the Development extension.', array('@url' => url('admin/appearance/settings'))); - } + // @ignore security_fapi_title + $form['css']['advagg_mod_css_preprocess']['#description'] .= module_exists('advagg_bundler') ? ' ' . t('You might want to increase the <a href="@link">CSS Bundles Per Page</a> if this is checked.', array('@link' => url($config_path . '/advagg/bundler'))) : ''; // Only test the translate option if // the locale function is defined OR // the locale_custom_strings variable is not empty. @@ -309,7 +194,7 @@ function advagg_mod_admin_settings_form() { // Only show option if something comes back translated. $files = advagg_mod_admin_test_css_files('css'); if (!empty($files)) { - $form['global_container']['css']['advagg_mod_css_translate'] = array( + $form['css']['advagg_mod_css_translate'] = array( '#type' => 'checkbox', '#title' => t('Translate CSS content strings'), '#default_value' => variable_get('advagg_mod_css_translate', ADVAGG_MOD_CSS_TRANSLATE), @@ -321,47 +206,47 @@ function advagg_mod_admin_settings_form() { } } // Optimize CSS Ordering. - $form['global_container']['css']['adjust_sort'] = array( + $form['css']['adjust_sort'] = array( '#type' => 'fieldset', '#title' => t('Optimize CSS Ordering'), '#description' => t('The settings in here might change the order in which the CSS is loaded. It will move the CSS around so that more optimal aggregates are built. In most cases enabling these checkboxes will cause no negative side effects.'), ); - $form['global_container']['css']['adjust_sort']['advagg_mod_css_head_extract'] = array( + $form['css']['adjust_sort']['advagg_mod_css_head_extract'] = array( '#type' => 'checkbox', - '#title' => t('Move CSS added by drupal_add_html_head() into drupal_add_css() (recommended)'), + '#title' => t('Move CSS added by drupal_add_html_head() into drupal_add_css()'), '#default_value' => variable_get('advagg_mod_css_head_extract', ADVAGG_MOD_CSS_HEAD_EXTRACT), '#description' => t('This will move CSS added incorrectly to Drupal into the top of the drupal_add_css() queue.'), - '#recommended_value' => TRUE, ); - $form['global_container']['css']['adjust_sort']['advagg_mod_css_adjust_sort_external'] = array( + $form['css']['adjust_sort']['advagg_mod_css_adjust_sort_external'] = array( '#type' => 'checkbox', - '#title' => t('Move all external CSS to the top of the execution order (recommended)'), + '#title' => t('Move all external CSS to the top of the execution order'), '#default_value' => variable_get('advagg_mod_css_adjust_sort_external', ADVAGG_MOD_CSS_ADJUST_SORT_EXTERNAL), '#description' => t('This will group all external CSS files to be above all other CSS.'), - '#recommended_value' => TRUE, ); - $form['global_container']['css']['adjust_sort']['advagg_mod_css_adjust_sort_inline'] = array( + $form['css']['adjust_sort']['advagg_mod_css_adjust_sort_inline'] = array( '#type' => 'checkbox', - '#title' => t('Move all inline CSS to the bottom of the execution order (recommended)'), + '#title' => t('Move all inline CSS to the bottom of the execution order'), '#default_value' => variable_get('advagg_mod_css_adjust_sort_inline', ADVAGG_MOD_CSS_ADJUST_SORT_INLINE), '#description' => t('This will group all inline CSS to be below all other CSS.'), - '#recommended_value' => TRUE, ); - $form['global_container']['css']['adjust_sort']['advagg_mod_css_adjust_sort_browsers'] = array( + $form['css']['adjust_sort']['advagg_mod_css_adjust_sort_browsers'] = array( '#type' => 'checkbox', - '#title' => t('Move all browser conditional CSS to the bottom of the group (recommended)'), + '#title' => t('Move all browser conditional CSS to the bottom of the group'), '#default_value' => variable_get('advagg_mod_css_adjust_sort_browsers', ADVAGG_MOD_CSS_ADJUST_SORT_BROWSERS), '#description' => t('This will group all browser conditional CSS to be in the lowest group of that conditional rule.'), - '#recommended_value' => TRUE, ); // Adjust CSS location and execution. - $form['global_container']['css']['placement'] = array( + $form['css']['placement'] = array( '#type' => 'fieldset', '#title' => t('Adjust CSS location and execution'), + ); + $form['css']['placement']['expert'] = array( + '#type' => 'fieldset', + '#title' => t('Experimental Settings'), '#collapsible' => TRUE, '#collapsed' => !variable_get('advagg_mod_css_defer', ADVAGG_MOD_CSS_DEFER), ); - $form['global_container']['css']['placement']['advagg_mod_css_defer'] = array( + $form['css']['placement']['expert']['advagg_mod_css_defer'] = array( '#type' => 'radios', '#title' => t('Deferred CSS Execution: Use JS to load CSS'), '#default_value' => variable_get('advagg_mod_css_defer', ADVAGG_MOD_CSS_DEFER), @@ -369,254 +254,94 @@ function advagg_mod_admin_settings_form() { 0 => t('Disabled'), 1 => t('All in head, above js'), 3 => t('All in head'), - 4 => t('All in head, use link rel="preload" (recommended)'), - 5 => t('All in footer except for JS loading code'), + 5 => t('All in footer except for JS loading code (If enabled this is recommended)'), 7 => t('All in footer'), ), '#description' => t('This will try to optimize CSS delivery by using JavaScript to load the CSS. This might break CSS on different browsers and will cause a flash of unstyled content (FOUC). Only enable after extensive testing! <a href="@link">More Info</a>', array( '@link' => 'http://stackoverflow.com/questions/19374843/css-delivery-optimization-how-to-defer-css-loading', )), - '#recommended_value' => 4, ); - // 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 (blacklist)'), - ADVAGG_MOD_VISIBILITY_LISTED => t('Only the listed pages (whitelist)'), - ); - $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' => '<front>', - )); - - if (module_exists('php') && $access) { - $options += array(ADVAGG_MOD_VISIBILITY_PHP => t('Pages on which this PHP code returns <code>TRUE</code> (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' => '<?php ?>')); - } - else { - $title = t('Pages'); - } - $options += array(ADVAGG_MOD_VISIBILITY_FILE_CONTROLLED => t('File Controlled (Recommended).')); - - if ($visibility_defer == ADVAGG_MOD_VISIBILITY_PHP && !$access) { - $form['global_container']['css']['placement']['visibility_all'] = array( - '#type' => 'value', - '#value' => $visibility_defer, - ); - $form['global_container']['css']['placement']['pages_all'] = array( - '#type' => 'value', - '#value' => $css_defer_pages, - ); - } - else { - $form['global_container']['css']['placement']['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. File Controlled will only use loadCSS() if a critical css file has been found and can be inlined.'), - '#states' => array( - 'disabled' => array( - ':input[name="advagg_mod_css_defer"]' => array('value' => '0'), - ), - ), - '#recommended_value' => ADVAGG_MOD_VISIBILITY_FILE_CONTROLLED, - ); - $form['global_container']['css']['placement']['advagg_mod_css_defer_pages'] = array( - '#type' => 'textarea', - '#title' => '<span class="element-invisible">' . $title . '</span>', - '#default_value' => $css_defer_pages, - '#description' => $description, - '#states' => array( - 'disabled' => array( - ':input[name="advagg_mod_css_defer"]' => array('value' => '0'), - ), - 'invisible' => array( - ':input[name="advagg_mod_css_defer_visibility"]' => array('value' => '3'), - ), - ), - ); - list(, $params) = advagg_mod_find_critical_css_file(); - list($dirs) = $params; - $theme_path = drupal_get_path('theme', variable_get('theme_default', NULL)) . '/'; - $form['global_container']['css']['placement']['advagg_mod_css_defer_visibility_file_explained'] = array( - '#type' => 'item', - '#markup' => t('Valid example file locations for this page: <p><code>@line1</code><br><code>@line2</code><br><code>@line3</code></p> valid example file locations for the "front" page: <p><code>@line4</code><br><code>@line5</code><br><code>@line6</code></p> valid example file locations for "node/1" (<a href="@url">current_path()</a>) page: <p><code>@line7</code><br><code>@line8</code><br><code>@line9</code></p> valid example file locations for the node type of "page": <p><code>@line10</code><br><code>@line11</code><br><code>@line12</code></p>', array( - '@url' => 'https://api.drupal.org/api/drupal/includes%21path.inc/function/current_path/7.x', - '@line1' => "{$dirs[0]}{$dirs[1]}{$dirs[2]}{$dirs[4]}{$dirs[6]}.css", - '@line2' => "{$dirs[0]}{$dirs[1]}anonymous/{$dirs[4]}{$dirs[6]}.css", - '@line3' => "{$dirs[0]}{$dirs[1]}{$dirs[3]}{$dirs[4]}{$dirs[6]}.css", - '@line4' => "{$theme_path}{$dirs[1]}{$dirs[2]}{$dirs[4]}front.css", - '@line5' => "{$theme_path}{$dirs[1]}anonymous/{$dirs[4]}front.css", - '@line6' => "{$theme_path}{$dirs[1]}{$dirs[3]}{$dirs[4]}front.css", - '@line7' => "{$theme_path}{$dirs[1]}{$dirs[2]}{$dirs[4]}node/1.css", - '@line8' => "{$theme_path}{$dirs[1]}anonymous/{$dirs[4]}node/1.css", - '@line9' => "{$theme_path}{$dirs[1]}{$dirs[3]}{$dirs[4]}node/1.css", - '@line10' => "{$theme_path}{$dirs[1]}{$dirs[2]}{$dirs[5]}page.css", - '@line11' => "{$theme_path}{$dirs[1]}anonymous/{$dirs[5]}page.css", - '@line12' => "{$theme_path}{$dirs[1]}{$dirs[3]}{$dirs[5]}page.css", - )), - '#states' => array( - 'visible' => array( - ':input[name="advagg_mod_css_defer_visibility"]' => array('value' => '3'), - ), - ), - ); - } - $form['global_container']['css']['placement']['advagg_mod_css_defer_skip_first_file'] = array( - '#type' => 'radios', - '#title' => t('Do not defer the first css file'), - '#default_value' => variable_get('advagg_mod_css_defer_skip_first_file', ADVAGG_MOD_CSS_DEFER_SKIP_FIRST_FILE), - '#description' => t('Link Stylesheet will make the first css file be blocking. Inline CSS will inline up to @size of the first CSS files.', array('@size' => advagg_mod_admin_byte2human(variable_get('advagg_mod_css_defer_inline_size_limit', ADVAGG_MOD_CSS_DEFER_INLINE_SIZE_LIMIT)))), - '#options' => array( - 0 => t('Disabled'), - 2 => t('Link Stylesheet'), - 4 => t('Inline CSS (no more than @size)', array('@size' => advagg_mod_admin_byte2human(variable_get('advagg_mod_css_defer_inline_size_limit', ADVAGG_MOD_CSS_DEFER_INLINE_SIZE_LIMIT)))), - ), - '#states' => array( - 'disabled' => array( - ':input[name="advagg_mod_css_defer"]' => array('value' => '0'), - ), - 'invisible' => array( - ':input[name="advagg_mod_css_defer_visibility"]' => array('value' => '3'), - ), - ), - ); - $form['global_container']['css']['placement']['advagg_mod_css_defer_js_code'] = array( + $form['css']['placement']['expert']['advagg_mod_css_defer_js_code'] = array( '#type' => 'radios', '#title' => t('How to include the JS loading code'), '#default_value' => variable_get('advagg_mod_css_defer_js_code', ADVAGG_MOD_CSS_DEFER_JS_CODE), '#options' => array( - 0 => t('Inline javascript loader library (recommended)'), + 0 => t('Inline javascript loader library (If enabled this is recommended)'), 2 => t('Local file included in aggregate'), - 4 => t('Externally load the latest from github'), + // 4 => t('Externally load the latest from github (SLOW! Not a CDN!!!)'), ), '#description' => t('The <a href="@link">loadCSS</a> library can be included in various ways.', array( '@link' => 'https://github.com/filamentgroup/loadCSS', )), - '#recommended_value' => 0, '#states' => array( 'disabled' => array( ':input[name="advagg_mod_css_defer"]' => array('value' => '0'), ), ), - - ); - $form['global_container']['css']['placement']['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'), - '#states' => array( - 'disabled' => array( - ':input[name="advagg_mod_css_defer"]' => array('value' => '0'), - ), - 'invisible' => array( - ':input[name="advagg_mod_css_defer_visibility"]' => array('value' => '3'), - ), - ), ); - $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], $options[ADVAGG_MOD_VISIBILITY_FILE_CONTROLLED]); - $form['global_container']['landing_page'] = array( + $form['landing_page'] = array( '#type' => 'fieldset', '#title' => t('Inline CSS/JS on specific pages'), - '#description' => t('For most people these are settings that should not be used. This will prevent all local CSS and/or JavaScript files from being downloaded; instead the contents of them will all be inlined. This will cause the raw HTML downloaded to be a lot bigger but it will cause less connections to your webserver from being created. This can sometimes be useful for certain landing pages.'), + '#description' => t('For most people this is a setting that should not be used. This will prevent all local CSS & JavaScript files from being downloaded; instead the contents of them will all be inlined. This will cause the raw HTML downloaded to be a lot bigger but it will cause less connections to your webserver from being created. This can sometimes be useful for certain landing pages.'), '#collapsible' => TRUE, - '#collapsed' => ($pages_all || $pages_css || $pages_js) ? FALSE : TRUE, + '#collapsed' => TRUE, ); - $visibility_all = variable_get('advagg_mod_inline_visibility', ADVAGG_MOD_VISIBILITY_LISTED); - if ($visibility_all == ADVAGG_MOD_VISIBILITY_PHP && !$access) { - $form['global_container']['landing_page']['path']['visibility_all'] = array( + // Taken from block_admin_configure(). + $access = user_access('use PHP for settings'); + $visibility = variable_get('advagg_mod_inline_visibility', ADVAGG_MOD_VISIBILITY_LISTED); + $pages = variable_get('advagg_mod_inline_pages', ''); + if ($visibility == ADVAGG_MOD_VISIBILITY_PHP && !$access) { + $form['landing_page']['path']['visibility'] = array( '#type' => 'value', - '#value' => $visibility_all, + '#value' => $visibility, ); - $form['global_container']['landing_page']['path']['pages_all'] = array( + $form['landing_page']['path']['pages'] = array( '#type' => 'value', - '#value' => $pages_all, + '#value' => $pages, ); } else { - $form['global_container']['landing_page']['path']['advagg_mod_inline_visibility'] = array( - '#type' => 'radios', - '#title' => t('Inline CSS and JS on specific pages'), - '#options' => $options, - '#default_value' => $visibility_all, + $options = array( + // ADVAGG_MOD_VISIBILITY_NOTLISTED => t('All pages except those listed'), + ADVAGG_MOD_VISIBILITY_LISTED => t('Only the listed pages'), ); - $form['global_container']['landing_page']['path']['advagg_mod_inline_pages'] = array( - '#type' => 'textarea', - '#title' => '<span class="element-invisible">' . $title . '</span>', - '#default_value' => $pages_all, - '#description' => $description, - ); - } + $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' => '<front>', + )); - $visibility_css = variable_get('advagg_mod_inline_css_visibility', ADVAGG_MOD_VISIBILITY_LISTED); - if ($visibility_css == ADVAGG_MOD_VISIBILITY_PHP && !$access) { - $form['global_container']['landing_page']['path']['visibility_all'] = array( - '#type' => 'value', - '#value' => $visibility_css, - ); - $form['global_container']['landing_page']['path']['pages_all'] = array( - '#type' => 'value', - '#value' => $pages_css, - ); - } - else { - $form['global_container']['landing_page']['path']['advagg_mod_inline_css_visibility'] = array( - '#type' => 'radios', - '#title' => t('Inline CSS on specific pages'), - '#options' => $options, - '#default_value' => $visibility_css, - ); - $form['global_container']['landing_page']['path']['advagg_mod_inline_css_pages'] = array( - '#type' => 'textarea', - '#title' => '<span class="element-invisible">' . $title . '</span>', - '#default_value' => $pages_css, - '#description' => $description, - ); - } - $visibility_js = variable_get('advagg_mod_inline_js_visibility', ADVAGG_MOD_VISIBILITY_LISTED); - if ($visibility_js == ADVAGG_MOD_VISIBILITY_PHP && !$access) { - $form['global_container']['landing_page']['path']['visibility_all'] = array( - '#type' => 'value', - '#value' => $visibility_js, - ); - $form['global_container']['landing_page']['path']['pages_all'] = array( - '#type' => 'value', - '#value' => $pages_js, - ); - } - else { - $form['global_container']['landing_page']['path']['advagg_mod_inline_js_visibility'] = array( + if (module_exists('php') && $access) { + $options += array(ADVAGG_MOD_VISIBILITY_PHP => t('Pages on which this PHP code returns <code>TRUE</code> (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' => '<?php ?>')); + } + else { + $title = t('Pages'); + } + $form['landing_page']['path']['advagg_mod_inline_settings'] = array( '#type' => 'radios', - '#title' => t('Inline JS on specific pages'), + '#title' => t('Inline CSS/JS on specific pages'), '#options' => $options, - '#default_value' => $visibility_js, + '#default_value' => $visibility, ); - $form['global_container']['landing_page']['path']['advagg_mod_inline_js_pages'] = array( + $form['landing_page']['path']['advagg_mod_inline_pages'] = array( '#type' => 'textarea', '#title' => '<span class="element-invisible">' . $title . '</span>', - '#default_value' => $pages_js, + '#default_value' => $pages, '#description' => $description, ); } - $form['global_container']['unified_multisite'] = array( + $form['unified_multisite'] = array( '#type' => 'fieldset', '#title' => t('Unified Multisite'), '#description' => t('For most people this is a setting that should not be used.'), '#collapsible' => TRUE, '#collapsed' => TRUE, ); - $form['global_container']['unified_multisite']['advagg_mod_unified_multisite_dir'] = array( + $form['unified_multisite']['advagg_mod_unified_multisite_dir'] = array( '#type' => 'textfield', '#title' => t('Shared Directory'), '#default_value' => variable_get('advagg_mod_unified_multisite_dir', ''), @@ -638,62 +363,42 @@ function advagg_mod_admin_settings_form() { return system_settings_form($form); } +// Validate callback. /** - * Validate callback, check that the unified multisite directory was created. - * - * @ingroup advagg_forms_callback + * Make sure the unified multisite directory was created correctly. */ function advagg_mod_admin_settings_form_validate($form, &$form_state) { - // Unified Mmultisite. $multisite_dir = rtrim($form_state['values']['advagg_mod_unified_multisite_dir'], '/'); - if (!empty($multisite_dir)) { - // Prepare directory. - $css_dir = $multisite_dir . '/advagg_css'; - $js_dir = $multisite_dir . '/advagg_js'; - if (!file_prepare_directory($css_dir, FILE_CREATE_DIRECTORY) - || !file_prepare_directory($js_dir, FILE_CREATE_DIRECTORY) - ) { - if (!is_dir($multisite_dir) || !is_writable($multisite_dir)) { - form_set_error('advagg_mod_unified_multisite_dir', t('%dir is not a directory or can not be written to. The shared directory needs to have the same permissions as the "Public file system path" found on the <a href="@file_system_link">File System configuration page</a>.', array( - '%dir' => $multisite_dir, - '@file_system_link' => url('admin/config/media/file-system'), - ))); - return; - } - } + // Return if unified_multisite_dir is not set. + if (empty($multisite_dir)) { + return; } - // Use JS to load CSS. - if ($form_state['values']['advagg_mod_css_defer']) { - if ($form_state['values']['advagg_mod_css_defer_visibility'] == ADVAGG_MOD_VISIBILITY_LISTED - && empty($form_state['values']['advagg_mod_css_defer_pages']) - ) { - form_set_error('advagg_mod_css_defer_pages', t('The Deferred CSS Execution setting will run only on specific pages. On other pages it will act as being Disabled. Please input what pages you wish to have the CSS be deferred. Currently none are selected.')); - } - if ($form_state['values']['advagg_mod_css_defer_visibility'] == ADVAGG_MOD_VISIBILITY_NOTLISTED - && $form_state['values']['advagg_mod_css_defer_pages'] === '*' - ) { - form_set_error('advagg_mod_css_defer_pages', t('The Deferred CSS Execution setting will run only on specific pages. On other pages it will act as being Disabled. Please input what pages you wish to have the CSS not be deferred. Currently all pages are disabled (*).')); + // Prepare directory. + $css_dir = $multisite_dir . '/advagg_css'; + $js_dir = $multisite_dir . '/advagg_js'; + // @ignore sniffer_whitespace_openbracketspacing_openingwhitespace + if ( !file_prepare_directory($css_dir, FILE_CREATE_DIRECTORY) + || !file_prepare_directory($js_dir, FILE_CREATE_DIRECTORY) + ) { + if (!is_dir($multisite_dir) || !is_writable($multisite_dir)) { + form_set_error('advagg_mod_unified_multisite_dir', t('%dir is not a directory or can not be written to. The shared directory needs to have the same permissions as the "Public file system path" found on the <a href="@file_system_link">File System configuration page</a>.', array( + '%dir' => $multisite_dir, + '@file_system_link' => url('admin/config/media/file-system'), + ))); + return; } } } +// Submit callback. /** - * Submit callback, clear out the advagg cache bin. - * - * @ingroup advagg_forms_callback + * Clear out the advagg cache bin when the save configuration button is pressed. */ function advagg_mod_admin_settings_form_submit($form, &$form_state) { - // Clear caches. - advagg_cache_clear_admin_submit(); - - // Reset this form to defaults or recommended values; also show what changed. - advagg_set_admin_form_defaults_recommended($form_state, 'advagg_mod_admin_mode'); - - // If file controlled, turn off skip first file turn on admin defer. - if ($form_state['values']['advagg_mod_css_defer_visibility'] == 3) { - $form_state['values']['advagg_mod_css_defer_skip_first_file'] = 0; - $form_state['values']['advagg_mod_css_defer_admin'] = TRUE; + $cache_bins = advagg_flush_caches(); + foreach ($cache_bins as $bin) { + cache_clear_all('*', $bin, TRUE); } // If unified_multisite_dir has changed, flush menu router at the end of the @@ -704,10 +409,6 @@ function advagg_mod_admin_settings_form_submit($form, &$form_state) { register_shutdown_function('advagg_get_root_files_dir', TRUE); register_shutdown_function('menu_rebuild'); } - - if (empty($form_state['values']['advagg_mod_js_defer_inline_alter']) && !empty($form_state['values']['advagg_mod_js_defer_jquery'])) { - $form_state['values']['advagg_mod_js_defer_jquery'] = FALSE; - } } /** @@ -721,8 +422,8 @@ function advagg_mod_admin_test_css_files() { // Get list of files. $query_files = db_select('advagg_files', 'af') ->fields('af', array('filename_hash', 'filename')) - ->condition('filetype', 'css') - ->orderBy('filename', 'ASC') + ->condition('af.filetype', 'css') + ->orderBy('filename', 'DESC') ->execute() ->fetchAllKeyed(); $files = array_values($query_files); @@ -737,7 +438,7 @@ function advagg_mod_admin_test_css_files() { continue; } // Load CSS file. - $file_contents = advagg_load_stylesheet_content((string) @advagg_file_get_contents($filename), TRUE); + $file_contents = advagg_load_stylesheet_content(file_get_contents($filename), TRUE); // Code taken from drupal_load_stylesheet_content(). // Regexp to match double quoted strings. @@ -772,7 +473,7 @@ function advagg_mod_admin_test_css_files() { continue; } // Run t function. - // @codingStandardsIgnoreLine + // @ignore sniffer_semantics_functioncall_notliteralstring $after = t($before); // Only include it if strings are different. @@ -786,26 +487,3 @@ function advagg_mod_admin_test_css_files() { } return $output; } - -/** - * Converts a number of bytes into human readable format. - * - * @param string $bytes - * Number to convert into a more human readable format. - * @param int $precision - * Number of decimals to output. - * - * @return string - * Human readable format of the bytes. - */ -function advagg_mod_admin_byte2human($bytes, $precision = 0) { - $units = array('', 'K', 'M', 'G', 'T'); - - $bytes = max($bytes, 0); - $pow = floor(($bytes ? log($bytes) : 0) / log(1024)); - $pow = min($pow, count($units) - 1); - $bytes /= (1 << (10 * $pow)); - - $output = ceil(round($bytes, $precision + 2) * 10) / 10; - return $output . '' . $units[$pow]; -} diff --git a/sites/all/modules/contrib/advagg/advagg_mod/advagg_mod.advagg.inc b/sites/all/modules/contrib/advagg/advagg_mod/advagg_mod.advagg.inc index b3fac149c1..743751192b 100644 --- a/sites/all/modules/contrib/advagg/advagg_mod/advagg_mod.advagg.inc +++ b/sites/all/modules/contrib/advagg/advagg_mod/advagg_mod.advagg.inc @@ -5,11 +5,6 @@ * Advanced CSS/JS aggregation modifier module. */ -/** - * @addtogroup advagg_hooks - * @{ - */ - /** * Implements hook_advagg_get_info_on_files_alter(). * @@ -21,7 +16,8 @@ function advagg_mod_advagg_get_info_on_files_alter(&$return, $cached_data, $bypa continue; } // New or updated data or no advagg_js_compress data. - if (empty($cached_data[$info['cache_id']]) + // @ignore sniffer_whitespace_openbracketspacing_openingwhitespace + if ( empty($cached_data[$info['cache_id']]) || empty($info['advagg_mod']) || $info['content_hash'] != $cached_data[$info['cache_id']]['content_hash'] ) { @@ -32,16 +28,6 @@ function advagg_mod_advagg_get_info_on_files_alter(&$return, $cached_data, $bypa unset($info); } -/** - * Implements hook_advagg_get_js_file_contents_alter(). - */ -function advagg_mod_advagg_get_js_file_contents_alter(&$contents, $filename, $aggregate_settings) { - // Fix adminimal_admin_menu js issues with admin menu. - if (module_exists('adminimal_admin_menu')) { - advagg_mod_fix_js_adminimal_admin_menu($contents, $filename); - } -} - /** * Implements hook_advagg_get_css_file_contents_alter(). * @@ -68,10 +54,6 @@ function advagg_mod_advagg_get_css_file_contents_alter(&$contents, $filename, $a $contents = preg_replace_callback($css_content_pattern, 'advagg_mod_advagg_css_content_t_replace_callback', $contents); } -/** - * @} End of "addtogroup advagg_hooks". - */ - /** * Run preg matches through the t() function. * @@ -100,67 +82,8 @@ function advagg_mod_advagg_css_content_t_replace_callback(array $matches) { } // Run t function. - // @codingStandardsIgnoreLine + // @ignore sniffer_semantics_functioncall_notliteralstring $after = t($before); // Put back. return str_replace($matches[1], str_replace($before, $after, $matches[1]), $matches[0]); } - -/** - * Fix admin menu. - * - * @param string $contents - * The contents of the js file. - * @param string $filename - * The filename. - */ -function advagg_mod_fix_js_adminimal_admin_menu(&$contents, $filename) { - // Get adminimal_admin_menu path. - $adminimal_admin_menu_path = drupal_get_path('module', 'adminimal_admin_menu'); - // Only match slicknav js. - if ($filename !== "$adminimal_admin_menu_path/js/slicknav/jquery.slicknav.js" - && $filename !== "$adminimal_admin_menu_path/js/slicknav/jquery-no-conflict.slicknav.js" - ) { - return; - } - - // Lines to look for. - $inserted_string_1 = "Drupal.admin = Drupal.admin || {};\n"; - $inserted_string_2 = "Drupal.admin.behaviors = Drupal.admin.behaviors || {};\n"; - if (strpos($contents, $inserted_string_1) !== FALSE - && strpos($contents, $inserted_string_2) !== FALSE - ) { - // Do nothing if the lines already exist. - return; - } - - // Get length of slicknav javascript. - $strlen = strlen($contents); - // Get the first occurrence of the problem string. - $end = strpos($contents, 'Drupal.admin.behaviors.responsivemenu'); - if ($end === FALSE) { - return; - } - // Get the start of this code block. - $start = strrpos($contents, '(function($)', $end - $strlen); - if ($start === FALSE) { - return; - } - // Get the lcoation of {. - $middle = strpos($contents, "{\n", $start); - if ($middle === FALSE) { - $middle = strpos($contents, "{", $start); - if ($middle === FALSE) { - return; - } - else { - $middle += 1; - } - } - else { - $middle += 2; - } - - // Insert new js code. - $contents = substr_replace($contents, $inserted_string_1 . $inserted_string_2, $middle, 0); -} 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 5ecf7d1e8d..3e8945399e 100644 --- a/sites/all/modules/contrib/advagg/advagg_mod/advagg_mod.info +++ b/sites/all/modules/contrib/advagg/advagg_mod/advagg_mod.info @@ -3,12 +3,12 @@ description = Allows one to alter the CSS and JS array. package = Advanced CSS/JS Aggregation core = 7.x dependencies[] = advagg -recommends[] = libraries configure = admin/config/development/performance/advagg/mod -; Information added by Drupal.org packaging script on 2020-11-19 -version = "7.x-2.35" +; Information added by Drupal.org packaging script on 2015-04-14 +version = "7.x-2.8" core = "7.x" project = "advagg" -datestamp = "1605792717" +datestamp = "1429049283" + diff --git a/sites/all/modules/contrib/advagg/advagg_mod/advagg_mod.install b/sites/all/modules/contrib/advagg/advagg_mod/advagg_mod.install deleted file mode 100644 index 9cb2892e2a..0000000000 --- a/sites/all/modules/contrib/advagg/advagg_mod/advagg_mod.install +++ /dev/null @@ -1,87 +0,0 @@ -<?php - -/** - * @file - * Handles AdvAgg mod installation and upgrade tasks. - */ - -/** - * @addtogroup hooks - * @{ - */ - -/** - * Implements hook_install(). - */ -function advagg_mod_install() { - // New install gets a locked admin section. - variable_set('advagg_mod_admin_mode', 0); -} - -/** - * Merge advagg_mod_css_defer_rel_preload into advagg_mod_css_defer. - */ -function advagg_mod_update_7201(&$sandbox) { - // Merge advagg_mod_css_defer_rel_preload into advagg_mod_css_defer. - $advagg_mod_css_defer = variable_get('advagg_mod_css_defer', ADVAGG_MOD_CSS_DEFER); - $advagg_mod_css_defer_rel_preload = variable_get('advagg_mod_css_defer_rel_preload', ADVAGG_MOD_CSS_DEFER_REL_PRELOAD); - variable_del('advagg_mod_css_defer_rel_preload'); - if (!empty($advagg_mod_css_defer) && !empty($advagg_mod_css_defer_rel_preload)) { - variable_set('advagg_mod_css_defer', 4); - return t('The advagg_mod_css_defer_rel_preload variable has been deleted. The advagg_mod_css_defer variable has been set to a value of 4 (All in head, use link rel="preload"). You can adjust this value on the <a href="@url">AdvAgg Modifications page</a>', array('@url' => url('admin/config/development/performance/advagg/mod', array('fragment' => 'edit-advagg-mod-css-defer')))); - } - else { - return t('The advagg_mod_css_defer_rel_preload variable has been deleted.'); - } -} - -/** - * Move advagg_mod_js_get_external_dns to advagg_mod_js_inline_resource_hints. - */ -function advagg_mod_update_7202(&$sandbox) { - $advagg_mod_js_inline_resource_hints = variable_get('advagg_mod_js_inline_resource_hints', NULL); - $advagg_mod_js_get_external_dns = variable_get('advagg_mod_js_get_external_dns', NULL); - - if (is_null($advagg_mod_js_get_external_dns)) { - return t('Nothing needed to happen.'); - } - variable_del('advagg_mod_js_get_external_dns'); - if (!is_null($advagg_mod_js_inline_resource_hints) || empty($advagg_mod_js_inline_resource_hints)) { - return t('Nothing needed to happen.'); - } - variable_set('advagg_mod_js_inline_resource_hints', $advagg_mod_js_inline_resource_hints); - return t('Variable has been moved.'); -} - -/** - * @} End of "addtogroup hooks". - */ - -/** - * Implements hook_requirements(). - */ -function advagg_mod_requirements($phase) { - $requirements = array(); - // Ensure translations don't break at install time. - $t = get_t(); - - // If not at runtime, return here. - if ($phase !== 'runtime') { - return $requirements; - } - - // Check version. - $lib_name = 'loadCSS'; - $module_name = 'advagg_mod'; - list($description, $info) = advagg_get_version_description($lib_name, $module_name); - if (!empty($description)) { - $requirements["{$module_name}_{$lib_name}_updates"] = array( - 'title' => $t('@module_name', array('@module_name' => $info['name'])), - 'severity' => REQUIREMENT_WARNING, - 'value' => $t('The @name library needs to be updated.', array('@name' => $lib_name)), - 'description' => $description, - ); - } - - return $requirements; -} 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 1a56b3e97a..1580baeb24 100644 --- a/sites/all/modules/contrib/advagg/advagg_mod/advagg_mod.module +++ b/sites/all/modules/contrib/advagg/advagg_mod/advagg_mod.module @@ -5,11 +5,7 @@ * Advanced aggregation modifier module. */ -/** - * @addtogroup default_variables - * @{ - */ - +// Define default variables. /** * Default value to move all JS to the footer. */ @@ -23,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', 0); +define('ADVAGG_MOD_JS_DEFER', FALSE); /** * Default value to add use the async script shim for script tags. @@ -78,12 +74,7 @@ define('ADVAGG_MOD_CSS_ADJUST_SORT_BROWSERS', FALSE); /** * Default value to use JavaScript to defer CSS loading. */ -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); +define('ADVAGG_MOD_CSS_DEFER', FALSE); /** * Default value to move CSS into drupal_add_css(). @@ -103,15 +94,15 @@ define('ADVAGG_MOD_JS_ASYNC', FALSE); /** * Default value to wrap inline content javascript so it runs when it is ready. */ -define('ADVAGG_MOD_JS_FOOTER_INLINE_ALTER', FALSE); +define('ADVAGG_MOD_JS_FOOTER_INLINE_ALTER', TRUE); /** - * Turns on functionality on every page except the listed pages (blacklist). + * Turns on functionality on every page except the listed pages. */ define('ADVAGG_MOD_VISIBILITY_NOTLISTED', 0); /** - * Turns on functionality only on the listed pages (whitelist). + * Turns on functionality only on the listed pages. */ define('ADVAGG_MOD_VISIBILITY_LISTED', 1); @@ -120,11 +111,6 @@ define('ADVAGG_MOD_VISIBILITY_LISTED', 1); */ define('ADVAGG_MOD_VISIBILITY_PHP', 2); -/** - * Turns on functionality if there is a file matching the page pattern. - */ -define('ADVAGG_MOD_VISIBILITY_FILE_CONTROLLED', 3); - /** * Default value of the inclusion method for the loadCSS code. */ @@ -145,206 +131,40 @@ define('ADVAGG_MOD_WRAP_INLINE_JS_SKIP_LIST', ''); */ define('ADVAGG_MOD_WRAP_INLINE_JS_XPATH', FALSE); -/** - * Default value to wrap inline content javascript so it runs deferred. - */ -define('ADVAGG_MOD_JS_DEFER_INLINE_ALTER', FALSE); - -/** - * Default value for inline scripts that should not be deferred. - */ -define('ADVAGG_MOD_DEFER_INLINE_JS_SKIP_LIST', ''); - -/** - * Default value to move async js to the header. - */ -define('ADVAGG_MOD_JS_ASYNC_IN_HEADER', FALSE); - -/** - * Default value to remove ajaxPageState if ajax.js is not used. - */ -define('ADVAGG_MOD_JS_NO_AJAXPAGESTATE', FALSE); - -/** - * Default value to scan html for src tags and do resource hints on it. - */ -define('ADVAGG_MOD_JS_INLINE_RESOURCE_HINTS', FALSE); - -/** - * Default value to defer jquery. - */ -define('ADVAGG_MOD_JS_DEFER_JQUERY', TRUE); - -/** - * 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); - -/** - * Default value to not defer the first CSS file. - */ -define('ADVAGG_MOD_CSS_DEFER_SKIP_FIRST_FILE', 0); - -/** - * Default value of the inlined css size. - */ -define('ADVAGG_MOD_CSS_DEFER_INLINE_SIZE_LIMIT', 12288); - -/** - * Default to strip !important from inline critical css. - */ -define('ADVAGG_MOD_INLINE_CRITICAL_CSS_STRIP_IMPORTANT', TRUE); - -/** - * If 4 the admin section gets unlocked. - */ -define('ADVAGG_MOD_ADMIN_MODE', 4); - -/** - * Default value. - */ -define('ADVAGG_MOD_INLINE_JS_VISIBILITY', ADVAGG_MOD_VISIBILITY_LISTED); - -/** - * Default value. - */ -define('ADVAGG_MOD_INLINE_CSS_VISIBILITY', ADVAGG_MOD_VISIBILITY_LISTED); - -/** - * Default value. - */ -define('ADVAGG_MOD_INLINE_VISIBILITY', ADVAGG_MOD_VISIBILITY_LISTED); - -/** - * Default value. - */ -define('ADVAGG_MOD_CSS_DEFER_VISIBILITY', ADVAGG_MOD_VISIBILITY_LISTED); - -/** - * @} End of "addtogroup default_variables". - */ - -/** - * @addtogroup hooks - * @{ - */ - -/** - * Implements hook_module_implements_alter(). - */ -function advagg_mod_module_implements_alter(&$implementations, $hook) { - // Move advagg_mod to the top. - if ($hook === 'library_alter' && array_key_exists('advagg_mod', $implementations)) { - $item = array('advagg_mod' => $implementations['advagg_mod']); - 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']); - } - - // Move advagg_mod_css_post_alter to the bottom. - if ($hook === 'css_post_alter' && array_key_exists('advagg_mod', $implementations)) { - $item = $implementations['advagg_mod']; - unset($implementations['advagg_mod']); - $implementations['advagg_mod'] = $item; - } - - // Move advagg_mod_js_post_alter to the bottom. - if ($hook === 'js_post_alter' && array_key_exists('advagg_mod', $implementations)) { - $item = $implementations['advagg_mod']; - unset($implementations['advagg_mod']); - $implementations['advagg_mod'] = $item; - } -} - -/** - * Implements hook_library_alter(). - */ -function advagg_mod_library_alter(&$javascript, $module) { - if (!advagg_enabled()) { - return; - } - if (!module_exists('jquery_update')) { - return; - } - if (!advagg_mod_inline_page_js()) { - return; - } - // Set the CDN to none for this page as everything is going to inlined. - $GLOBALS['conf']['jquery_update_jquery_cdn'] = 'none'; - $GLOBALS['conf']['jquery_update_jquery_migrate_cdn'] = 'none'; -} - +// Core hook implementations. /** * Implements hook_init(). */ function advagg_mod_init() { - if (!module_exists('advagg') || !advagg_enabled()) { + // 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)) { return; } - // 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); + $counter_filename = $dir . '/' . ADVAGG_SPACE . 'advagg_global_counter'; + $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); } + else { + $shared_counter = (int) file_get_contents($counter_filename); - // 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)) { - $counter_filename = $dir . '/' . ADVAGG_SPACE . 'advagg_global_counter'; - $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); + if ($shared_counter == $local_counter) { + // Counters are the same, return. + return; } - else { - $shared_counter = (int) advagg_file_get_contents($counter_filename); - - if ($shared_counter == $local_counter) { - // Counters are the same, return. - } - 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); - } - elseif ($shared_counter > $local_counter) { - // Shared counter is higher, update local copy and return. - variable_set('advagg_global_counter', $shared_counter); - } + 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; } - } - - // Disable js in footer on imce page. - // Disable js defer on imce page. - // https://www.drupal.org/node/2817523 - if (module_exists('imce')) { - $args = arg(); - if ($args[0] === 'imce' && empty($args[1])) { - if (variable_get('advagg_mod_js_footer', ADVAGG_MOD_JS_FOOTER)) { - $GLOBALS['conf']['advagg_mod_js_footer'] = 0; - } - if (variable_get('advagg_mod_js_defer', ADVAGG_MOD_JS_DEFER)) { - $GLOBALS['conf']['advagg_mod_js_defer'] = 0; - } + elseif ($shared_counter > $local_counter) { + // Shared counter is higher, update local copy and return. + variable_set('advagg_global_counter', $shared_counter); + return; } } } @@ -372,96 +192,302 @@ function advagg_mod_menu() { } /** - * Implements hook_element_info_alter(). + * Implements hook_js_alter(). */ -function advagg_mod_element_info_alter(&$type) { - if (!isset($type['styles']['#pre_render'])) { - $type['styles']['#pre_render'] = array(); - } - $key_drupal = array_search('drupal_pre_render_styles', $type['styles']['#pre_render']); - $key_advagg = array_search('advagg_pre_render_styles', $type['styles']['#pre_render']); - if ($key_drupal !== FALSE) { - $type['styles']['#pre_render'] = advagg_insert_into_array_at_location($type['styles']['#pre_render'], array('_advagg_mod_pre_render_styles'), $key_drupal); - } - elseif ($key_advagg !== FALSE) { - $type['styles']['#pre_render'] = advagg_insert_into_array_at_location($type['styles']['#pre_render'], array('_advagg_mod_pre_render_styles'), $key_advagg); - } - else { - $type['styles']['#pre_render'][] = '_advagg_mod_pre_render_styles'; +function advagg_mod_js_alter(&$js) { + if (module_exists('advagg') && !advagg_enabled()) { + return; } - if (!isset($type['scripts']['#pre_render'])) { - $type['scripts']['#pre_render'] = array(); - } - $key_drupal = array_search('drupal_pre_render_scripts', $type['scripts']['#pre_render']); - $key_advagg = array_search('advagg_pre_render_scripts', $type['scripts']['#pre_render']); - $key_omega = array_search('omega_pre_render_scripts', $type['scripts']['#pre_render']); - $key_aurora = array_search('aurora_pre_render_scripts', $type['scripts']['#pre_render']); - if ($key_drupal !== FALSE) { - $type['scripts']['#pre_render'] = advagg_insert_into_array_at_location($type['scripts']['#pre_render'], array('_advagg_mod_pre_render_scripts'), $key_drupal); - } - elseif ($key_advagg !== FALSE) { - $type['scripts']['#pre_render'] = advagg_insert_into_array_at_location($type['scripts']['#pre_render'], array('_advagg_mod_pre_render_scripts'), $key_advagg); + // Change google analytics inline loader to be inside of an aggregrated file. + if (variable_get('advagg_mod_ga_inline_to_file', ADVAGG_MOD_GA_INLINE_TO_FILE)) { + advagg_mod_ga_inline_to_file($js); } - elseif ($key_omega !== FALSE) { - $type['scripts']['#pre_render'] = advagg_insert_into_array_at_location($type['scripts']['#pre_render'], array('_advagg_mod_pre_render_scripts'), $key_omega); + + // Only add JS if it's actually needed. + if (variable_get('advagg_mod_js_remove_unused', ADVAGG_MOD_JS_REMOVE_UNUSED)) { + advagg_remove_js_if_not_used($js); } - elseif ($key_aurora !== FALSE) { - $type['scripts']['#pre_render'] = advagg_insert_into_array_at_location($type['scripts']['#pre_render'], array('_advagg_mod_pre_render_scripts'), $key_aurora); + + // Change sort order so aggregates do not get split up. + // @ignore sniffer_whitespace_openbracketspacing_openingwhitespace + if ( variable_get('advagg_mod_js_adjust_sort_external', ADVAGG_MOD_JS_ADJUST_SORT_EXTERNAL) + || variable_get('advagg_mod_js_adjust_sort_inline', ADVAGG_MOD_JS_ADJUST_SORT_INLINE) + || variable_get('advagg_mod_js_adjust_sort_browsers', ADVAGG_MOD_JS_ADJUST_SORT_BROWSERS) + ) { + advagg_mod_sort_css_js($js, 'js'); } - else { - $type['scripts']['#pre_render'][] = '_advagg_mod_pre_render_scripts'; + + // Move all JS to the footer. + $move_js_to_footer = variable_get('advagg_mod_js_footer', ADVAGG_MOD_JS_FOOTER); + if (!empty($move_js_to_footer)) { + foreach ($js as $name => &$values) { + if ($move_js_to_footer == 1 && $values['group'] <= JS_LIBRARY) { + continue; + } + + if (!empty($values['scope_lock'])) { + continue; + } + + // Do not move modernizr js to the footer. + if ( $values['type'] !== 'inline' + && $values['type'] !== 'setting' + && stripos($values['data'], '/modernizr.') !== FALSE + ) { + continue; + } + + // Do not move html5shiv or html5shiv-printshiv js to the footer. + if ( $values['type'] !== 'inline' + && $values['type'] !== 'setting' + && ( stripos($values['data'], '/html5shiv.') !== FALSE + || stripos($values['data'], '/html5shiv-printshiv.') !== FALSE + ) + ) { + continue; + } + + // If JS is not in the header increase group by 10000. + if ($values['scope'] !== 'header') { + $values['group'] += 10000; + } + // If JS is already in the footer increase group by 10000. + if ($values['scope'] === 'footer') { + $values['group'] += 10000; + } + $values['scope'] = 'footer'; + } + unset($values); } -} -/** - * Implements hook_css_alter(). - */ -function advagg_mod_css_alter(&$css) { - if (!module_exists('advagg') || !advagg_enabled()) { + // Do not use preprocessing if JS is inlined. + // Do not use defer if JS is inlined. + if (advagg_mod_inline_page()) { + advagg_mod_inline_js($js); return; } - // Force all CSS to be preprocessed. - if (variable_get('advagg_mod_css_preprocess', ADVAGG_MOD_CSS_PREPROCESS)) { - foreach ($css as &$values) { - if (!empty($values['preprocess_lock'])) { + // Force all JS to be preprocessed. + if (variable_get('advagg_mod_js_preprocess', ADVAGG_MOD_JS_PREPROCESS)) { + foreach ($js as $name => &$values) { + $values['preprocess'] = TRUE; + $values['cache'] = TRUE; + } + unset($values); + } + + // Add the defer or the async tag to all JS. + // @ignore sniffer_whitespace_openbracketspacing_openingwhitespace + if ( variable_get('advagg_mod_js_defer', ADVAGG_MOD_JS_DEFER) + || variable_get('advagg_mod_js_async', ADVAGG_MOD_JS_ASYNC) + ) { + + if (module_exists('openlayers')) { + $has_openlayers = FALSE; + foreach ($js as &$values) { + if ($values['type'] === 'inline' || !is_string($values['data'])) { + continue; + } + if (stripos($values['data'], 'openlayers')) { + $has_openlayers = TRUE; + break; + } + } + unset($values); + if ($has_openlayers) { + // Openlayers fix; external scripts can not be loaded out of order. + $openlayers = array(); + // Cloudmade. + $path = variable_get('openlayers_layers_cloudmade_js', ''); + if (valid_url($path, TRUE)) { + $openlayers['openlayers_layers_cloudmade'] = $path; + } + // Google. + $mapdomain = variable_get('openlayers_layers_google_mapdomain', 'maps.google.com'); + $openlayers['openlayers_layers_google'] = $mapdomain . '/maps'; + // VirtualEarth. + $openlayers['openlayers_layers_virtualearth'] = 'dev.virtualearth.net/mapcontrol'; + // Yahoo. + $openlayers['openlayers_layers_yahoo'] = 'api.maps.yahoo.com/ajaxymap'; + } + } + + $use_on_error = FALSE; + // If everything is async safe then we can use on error. + // Only needed if the jquery_update javascript is loaded via async/defer. + if ($use_on_error) { + $jquery_update_fallback = ''; + $jquery_update_ui_fallback = ''; + $inline_array = array(); + if (module_exists('jquery_update') && variable_get('jquery_update_jquery_cdn', 'none') !== 'none') { + foreach ($js as $name => &$values) { + if ($values['type'] !== 'inline') { + continue; + } + if (strpos($values['data'], 'window.jQuery') !== FALSE) { + $path = drupal_get_path('module', 'jquery_update'); + $version = variable_get('jquery_update_jquery_version', '1.10'); + $min = variable_get('jquery_update_compression_type', 'min') == 'none' ? '' : '.min'; + $jquery_update_fallback = base_path() . $path . '/replace/jquery/' . $version . '/jquery' . $min . '.js'; + $inline_array = $values; + unset($js[$name]); + continue; + } + if (strpos($values['data'], 'window.jQuery.ui') !== FALSE) { + $path = drupal_get_path('module', 'jquery_update'); + $min = variable_get('jquery_update_compression_type', 'min') == 'none' ? '' : '.min'; + $js_path = ($min == '.min') ? '/replace/ui/ui/minified/jquery-ui.min.js' : '/replace/ui/ui/jquery-ui.js'; + $jquery_update_ui_fallback = base_path() . $path . $js_path; + if (empty($inline_array)) { + $inline_array = $values; + } + unset($js[$name]); + continue; + } + } + unset($values); + } + if (!empty($jquery_update_fallback) || !empty($jquery_update_ui_fallback)) { + $inline_array['group'] = '-150'; + $inline_array['weight'] += -10; + $inline_array['data'] = 'function advagg_fallback(file){var head = document.getElementsByTagName("head")[0];var script = document.createElement("script");script.src = file;script.type = "text/javascript";head.appendChild(script);};'; + $js[] = $inline_array; + } + } + + foreach ($js as $name => &$values) { + if ($values['type'] !== 'file' && $values['type'] !== 'external') { continue; } - $values['preprocess'] = TRUE; + if (variable_get('advagg_mod_js_defer', ADVAGG_MOD_JS_DEFER)) { + // Everything is defer. + $values['defer'] = TRUE; + } + if (variable_get('advagg_mod_js_async', ADVAGG_MOD_JS_ASYNC)) { + // Everything is async. + $values['async'] = TRUE; + } + + if (strpos($name, 'jquery.js') !== FALSE || strpos($name, 'jquery.min.js') !== FALSE) { + // jquery_update fallback. + if (module_exists('jquery_update') && variable_get('jquery_update_jquery_cdn', 'none') !== 'none') { + if ($use_on_error) { + if (!isset($values['onerror'])) { + $values['onerror'] = ''; + } + $values['onerror'] .= 'advagg_fallback(\'' . $jquery_update_fallback . '\');'; + } + // Do not defer/async the loading of jquery.js + if (variable_get('advagg_mod_js_defer', ADVAGG_MOD_JS_DEFER)) { + $values['defer'] = FALSE; + } + if (variable_get('advagg_mod_js_async', ADVAGG_MOD_JS_ASYNC)) { + $values['async'] = FALSE; + } + } + } + if (strpos($name, 'jquery-ui.js') !== FALSE || strpos($name, 'jquery-ui.min.js') !== FALSE) { + // jquery_update ui fallback. + if (module_exists('jquery_update') && variable_get('jquery_update_jquery_cdn', 'none') !== 'none') { + if ($use_on_error) { + if (!isset($values['onerror'])) { + $values['onerror'] = ''; + } + $values['onerror'] .= 'advagg_fallback(' . $jquery_update_ui_fallback . ');'; + } + // Do not defer/async the loading of jquery-ui.js + if (variable_get('advagg_mod_js_defer', ADVAGG_MOD_JS_DEFER)) { + $values['defer'] = FALSE; + } + if (variable_get('advagg_mod_js_async', ADVAGG_MOD_JS_ASYNC)) { + $values['async'] = FALSE; + } + } + } + + // Drupal settings. + if ($name === 'misc/drupal.js') { + // Initialize the Drupal.settings JavaScript object after this has + // loaded. + if (!isset($values['onload'])) { + $values['onload'] = ''; + } + $matches[0] = $matches[2] = 'init_drupal_core_settings();'; + $values['onload'] .= advagg_mod_wrap_inline_js($matches, "window.init_drupal_core_settings && window.init_drupal_core_settings && window.jQuery && window.Drupal", 20); + } + + // Openlayers. + if (!empty($openlayers)) { + foreach ($openlayers as $search_string) { + if (strpos($name, $search_string) !== FALSE) { + // Do not defer/async the loading of OpenLayers.js + if (variable_get('advagg_mod_js_defer', ADVAGG_MOD_JS_DEFER)) { + $values['defer'] = FALSE; + } + if (variable_get('advagg_mod_js_async', ADVAGG_MOD_JS_ASYNC)) { + $values['async'] = FALSE; + } + } + } + } + + // Wistia. + if (strpos($name, '//fast.wistia.') !== FALSE) { + // Do not defer/async the loading of any wistia js. + if (variable_get('advagg_mod_js_defer', ADVAGG_MOD_JS_DEFER)) { + $values['defer'] = FALSE; + } + if (variable_get('advagg_mod_js_async', ADVAGG_MOD_JS_ASYNC)) { + $values['async'] = FALSE; + } + } + } unset($values); } } /** - * Implements hook_css_post_alter(). + * Implements hook_css_alter(). */ -function advagg_mod_css_post_alter(&$css) { - if (!module_exists('advagg') || !advagg_enabled()) { +function advagg_mod_css_alter(&$css) { + if (module_exists('advagg') && !advagg_enabled()) { return; } + // @ignore sniffer_whitespace_openbracketspacing_openingwhitespace:5 // Change sort order so aggregates do not get split up. - if (variable_get('advagg_mod_css_adjust_sort_external', ADVAGG_MOD_CSS_ADJUST_SORT_EXTERNAL) + if ( variable_get('advagg_mod_css_adjust_sort_external', ADVAGG_MOD_CSS_ADJUST_SORT_EXTERNAL) || variable_get('advagg_mod_css_adjust_sort_inline', ADVAGG_MOD_CSS_ADJUST_SORT_INLINE) || variable_get('advagg_mod_css_adjust_sort_browsers', ADVAGG_MOD_CSS_ADJUST_SORT_BROWSERS) ) { advagg_mod_sort_css_js($css, 'css'); } + + // Do not use preprocessing if CSS is inlined. + if (advagg_mod_inline_page()) { + advagg_mod_inline_css($css); + return; + } + + // Force all CSS to be preprocessed. + if (variable_get('advagg_mod_css_preprocess', ADVAGG_MOD_CSS_PREPROCESS)) { + foreach ($css as &$values) { + $values['preprocess'] = TRUE; + } + unset($values); + } } /** * Implements hook_html_head_alter(). */ function advagg_mod_html_head_alter(&$head_elements) { - if (!module_exists('advagg') || !advagg_enabled()) { - return; - } - foreach ($head_elements as $key => $element) { - // CSS. - if (variable_get('advagg_mod_css_head_extract', ADVAGG_MOD_CSS_HEAD_EXTRACT) + // CSS + // @ignore sniffer_whitespace_openbracketspacing_openingwhitespace + if ( variable_get('advagg_mod_css_head_extract', ADVAGG_MOD_CSS_HEAD_EXTRACT) && !empty($element['#tag']) && $element['#tag'] === 'link' && !empty($element['#attributes']['type']) @@ -469,7 +495,8 @@ function advagg_mod_html_head_alter(&$head_elements) { && !empty($element['#attributes']['href']) ) { $type = 'file'; - if (strpos($element['#attributes']['href'], 'http://') === 0 + // @ignore sniffer_whitespace_openbracketspacing_openingwhitespace + if ( strpos($element['#attributes']['href'], 'http://') === 0 || strpos($element['#attributes']['href'], 'https://') === 0 || strpos($element['#attributes']['href'], '//') === 0 ) { @@ -483,8 +510,9 @@ function advagg_mod_html_head_alter(&$head_elements) { )); unset($head_elements[$key]); } - // JS. - if (variable_get('advagg_mod_js_head_extract', ADVAGG_MOD_JS_HEAD_EXTRACT) + // JS + // @ignore sniffer_whitespace_openbracketspacing_openingwhitespace + if ( variable_get('advagg_mod_js_head_extract', ADVAGG_MOD_JS_HEAD_EXTRACT) && !empty($element['#tag']) && $element['#tag'] === 'script' && !empty($element['#attributes']['type']) @@ -492,7 +520,8 @@ function advagg_mod_html_head_alter(&$head_elements) { && !empty($element['#attributes']['src']) ) { $type = 'file'; - if (strpos($element['#attributes']['src'], 'http://') === 0 + // @ignore sniffer_whitespace_openbracketspacing_openingwhitespace + if ( strpos($element['#attributes']['src'], 'http://') === 0 || strpos($element['#attributes']['src'], 'https://') === 0 || strpos($element['#attributes']['src'], '//') === 0 ) { @@ -516,10 +545,6 @@ function advagg_mod_html_head_alter(&$head_elements) { * Insert advagg_mod_process_move_js before _advagg_process_html. */ function advagg_mod_theme_registry_alter(&$theme_registry) { - if (!module_exists('advagg') || !advagg_enabled()) { - return; - } - if (!isset($theme_registry['html'])) { return; } @@ -543,220 +568,114 @@ function advagg_mod_theme_registry_alter(&$theme_registry) { * Used to wrap inline JS in a function in order to prevent js errors when JS is * moved to the footer. */ -function advagg_mod_process_move_js(array &$variables) { - if (!module_exists('advagg') || !advagg_enabled()) { +function advagg_mod_process_move_js(&$variables) { + // Only run if. + // $variables['page'] is not empty. + // Setting is enabled. + if (empty($variables['page']) || !variable_get('advagg_mod_js_footer_inline_alter', ADVAGG_MOD_JS_FOOTER_INLINE_ALTER)) { return; } - // Return if settings are disabled. - if (!variable_get('advagg_mod_js_footer_inline_alter', ADVAGG_MOD_JS_FOOTER_INLINE_ALTER) - && !variable_get('advagg_mod_js_inline_resource_hints', ADVAGG_MOD_JS_INLINE_RESOURCE_HINTS) + // @ignore sniffer_whitespace_openbracketspacing_openingwhitespace + if ( variable_get('advagg_mod_js_footer', ADVAGG_MOD_JS_FOOTER) != 2 + && !variable_get('advagg_mod_js_defer', ADVAGG_MOD_JS_DEFER) + && !variable_get('advagg_mod_js_async', ADVAGG_MOD_JS_ASYNC) ) { return; } - // Search all the children for script tags. - foreach (element_children($variables) as $child) { - // Skip if empty. - if (empty($variables[$child])) { - continue; - } - - // Handle strings. - if (is_string($variables[$child]) - && stripos($variables[$child], '<script') !== FALSE - ) { - advagg_mod_js_inline_processor($variables[$child]); + $pattern = '/<script((?:(?!src=).)*?)>(.*?)<\/script>/smix'; + $callback = 'advagg_mod_wrap_inline_js'; + // Wrap inline JS with a check so that it only runs once Drupal.settings & + // jQuery are not undefined. + // @ignore sniffer_whitespace_openbracketspacing_openingwhitespace + if ( !empty($variables['page']['#children']) + && is_string($variables['page']['#children']) + && stripos($variables['page']['#children'], '<script') !== FALSE + ) { + if (variable_get('advagg_mod_wrap_inline_js_xpath', ADVAGG_MOD_WRAP_INLINE_JS_XPATH)) { + $variables['page']['#children'] = advagg_mod_xpath_script_wrapper($variables['page']['#children']); } - if (is_array($variables[$child])) { - if (isset($variables[$child]['#children']) - && is_string($variables[$child]['#children']) - && stripos($variables[$child]['#children'], '<script') !== FALSE - ) { - advagg_mod_js_inline_processor($variables[$child]['#children']); - } - if (isset($variables[$child]['#markup']) - && is_string($variables[$child]['#markup']) - && stripos($variables[$child]['#markup'], '<script') !== FALSE - ) { - // advagg_mod_js_inline_processor($variables[$child]['#markup']); - // Uncomment to also process #markup. - } - // advagg_mod_process_move_js($variables[$child]); - // Uncomment to make this recursive. + else { + $variables['page']['#children'] = preg_replace_callback($pattern, $callback, $variables['page']['#children']); } - } -} - -/** - * Implements hook_page_alter(). - */ -function advagg_mod_page_alter() { - // Skip if advagg is disabled. - if (!advagg_enabled()) { - return; - } - // Return early if this setting is disabled. - list(, , , , , , , , , , $css_defer) = advagg_mod_get_lists(); - if (empty($css_defer)) { return; } - if (variable_get('advagg_mod_css_defer_visibility', ADVAGG_MOD_VISIBILITY_LISTED) != ADVAGG_MOD_VISIBILITY_FILE_CONTROLLED) { + // @ignore sniffer_whitespace_openbracketspacing_openingwhitespace + if ( is_string($variables['page']) + && stripos($variables['page'], '<script') !== FALSE + ) { + if (variable_get('advagg_mod_wrap_inline_js_xpath', ADVAGG_MOD_WRAP_INLINE_JS_XPATH)) { + $variables['page'] = advagg_mod_xpath_script_wrapper($variables['page']); + } + else { + $variables['page'] = preg_replace_callback($pattern, $callback, $variables['page']); + } return; } +} - // Get critical css file. - list(, , $inline_strings) = advagg_mod_find_critical_css_file(); - - $preload_from_inline_css = array(); - $domains_from_inline_css = array(); - // Add inline critical css for front page. - if (!empty($inline_strings[0])) { - // Extract url() references to add to the preloaded links. - $matches = array(); - // Match url ( "' ... '" ). - $pattern = '/url\s*\(\s*[\'"]?(.+?)[\'"]?\s*\)/i'; - preg_match_all($pattern, $inline_strings[0], $matches); - if (!empty($matches[1])) { - foreach ($matches[1] as $key => $url) { - $parsed = parse_url($url); - // Remove data URIs. - if (!empty($parsed['scheme']) && $parsed['scheme'] === 'data') { - unset($matches[1][$key]); - continue; - } - // Remote paths without a period. - if (empty($parsed['path']) || strpos($parsed['path'], '.') === FALSE) { - unset($matches[1][$key]); - continue; - } +/** + * Use DOMDocument's loadHTML along with DOMXPath's query to find script tags. + * + * Once found, it will also wrap them in a javascript loader function. + * + * @param string $html + * HTML fragments. + * + * @return string + * The HTML fragment with less markup errors and script tags wrapped. + */ +function advagg_mod_xpath_script_wrapper($html) { + // Do not throw errors when parsing the html. + libxml_use_internal_errors(TRUE); - if (isset($parsed['host'])) { - $domains_from_inline_css[] = $url; - } - } - $preload_from_inline_css = $matches[1]; - } + $dom = new DOMDocument(); + // Load html with full tags all around. + $dom->loadHTML('<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd"> + <html><head><meta http-equiv="content-type" content="text/html; charset=utf-8"></head><body>' . $html . '</body></html>'); + $xpath = new DOMXPath($dom); + // Get all script tags that are not inside of a textarea and do not contain a + // src attribute. + $nodes = $xpath->query('//script[not(@src)][not(ancestor::textarea)]'); - // Add critical css. - drupal_add_css('advagg_mod_critical_css', array( - 'data' => $inline_strings[0], - 'type' => 'inline', - 'group' => CSS_SYSTEM - 1, - 'weight' => -50000, - 'movable' => FALSE, - 'critical-css' => TRUE, - )); - // Add critical css js loader. - advagg_mod_add_loadcss_js_lib(); - } - - // Add in domain prefetch. - $domains = array(); - if (!empty($inline_strings[1])) { - $domains = preg_split("/\\r\\n|\\r|\\n/", $inline_strings[1]); - } - $domains = array_merge($domains, $domains_from_inline_css); - // Remove duplicates and empty sets. - $domains = array_filter(array_unique($domains)); - if (!empty($domains)) { - foreach ($domains as $domain) { - advagg_add_dns_prefetch(trim($domain)); - } + foreach ($nodes as $node) { + $matches[2] = $node->nodeValue; + // $matches[0] = $dom->saveHTML($node); + $matches[0] = $node->nodeValue; + $new_html = advagg_mod_wrap_inline_js($matches); + $advagg = $dom->createElement('script'); + $advagg->appendchild($dom->createTextNode($new_html)); + $node->parentNode->replaceChild($advagg, $node); } + // Render to HTML. + $output = $dom->saveHTML(); - // Add in files to preload. - $preload = array(); - if (!empty($inline_strings[2])) { - $preload = preg_split("/\\r\\n|\\r|\\n/", $inline_strings[2]); - } - $preload = array_merge($preload, $preload_from_inline_css); - // Remove duplicates and empty sets. - $preload = array_filter(array_unique($preload)); - if (!empty($preload)) { - $preload_array = array(); - $counter = 0; - foreach ($preload as $value) { - if (empty($value)) { - $counter++; - continue; - } - if (stripos($value, 'as: ') === 0) { - $preload_array[$counter]['as'] = trim(substr($value, 4)); - } - elseif (stripos($value, 'type: ') === 0) { - $preload_array[$counter]['type'] = trim(substr($value, 6)); - } - elseif (stripos($value, 'media: ') === 0) { - $preload_array[$counter]['media'] = trim(substr($value, 7)); - } - elseif (stripos($value, 'crossorigin: ') === 0) { - $preload_array[$counter]['crossorigin'] = trim(substr($value, 13)); - } - elseif (stripos($value, 'url: ') === 0) { - if (!empty($preload_array[$counter]['url'])) { - $counter++; - } - $preload_array[$counter]['url'] = trim(substr($value, 4)); - } - else { - if (!empty($preload_array[$counter]['url'])) { - $counter++; - } - $preload_array[$counter]['url'] = trim($value); - } - } - foreach ($preload_array as $values) { - // Skip if url is not set. - if (empty($values['url'])) { - continue; - } + // Remove the tags we added. + $output = str_replace(array('<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd"> + <html><head><meta http-equiv="content-type" content="text/html; charset=utf-8"></head><body>', '</body></html>'), array('', ''), $output); - $url = $values['url']; - $media = ''; - if (!empty($values['media'])) { - $media = $values['media']; - } - $as = ''; - if (!empty($values['as'])) { - $as = $values['as']; - } - $type = ''; - if (!empty($values['type'])) { - $type = $values['type']; - } - $crossorigin = NULL; - if (!empty($values['crossorigin'])) { - $crossorigin = $values['crossorigin']; - } - advagg_add_preload_link($url, $media, $as, $type, $crossorigin); - } - } + // Clear any errors. + libxml_clear_errors(); + return $output; } -/** - * @} End of "addtogroup hooks". - */ - -/** - * @addtogroup advagg_hooks - * @{ - */ - +// AdvAgg hook implementations. /** * Implements hook_advagg_modify_js_pre_render_alter(). */ function advagg_mod_advagg_modify_js_pre_render_alter(&$children, &$elements) { - if (!module_exists('advagg') || !advagg_enabled()) { + if (module_exists('advagg') && !advagg_enabled()) { return; } // Do not use defer/async shim if JS is inlined. - if (advagg_mod_inline_page() || advagg_mod_inline_page_js()) { + if (advagg_mod_inline_page()) { return; } - if (variable_get('advagg_mod_js_defer', ADVAGG_MOD_JS_DEFER) + // @ignore sniffer_whitespace_openbracketspacing_openingwhitespace + if ( variable_get('advagg_mod_js_defer', ADVAGG_MOD_JS_DEFER) || variable_get('advagg_mod_js_async', ADVAGG_MOD_JS_ASYNC) ) { // Capture all onload code. @@ -766,89 +685,8 @@ 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'); - - $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)); - } - } - } - - // Count the number of holdReady's there are. - $holdready_count = array(); - foreach ($children as $key => &$values) { - if (!empty($values['#attributes']['onload']) - && (stripos($values['#attributes']['onload'], 'jQuery.holdReady(true)') !== FALSE - || stripos($values['#attributes']['onload'], 'jQuery.holdReady(!0)') !== FALSE - ) - ) { - // Normalize the src attribute. - $src = $values['#attributes']['src']; - $pos = strpos($values['#attributes']['src'], '#'); - if ($pos !== FALSE) { - $src = substr($values['#attributes']['src'], 0, $pos); - } - $holdready_count[$src] = TRUE; - break; - } - } - foreach ($children as $key => &$values) { + foreach ($children as &$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. @@ -862,11 +700,7 @@ function advagg_mod_advagg_modify_js_pre_render_alter(&$children, &$elements) { } } if ($found) { - $holdready_string = ''; - if (!empty($holdready_count)) { - $holdready_string = "\nif(jQuery.isFunction(jQuery.holdReady)){jQuery.holdReady(false);}"; - } - $values['#value'] = "function init_drupal_core_settings() {{$values['#value']} {$holdready_string}} if(window.jQuery && window.Drupal){init_drupal_core_settings();}"; + $values['#value'] = 'function init_drupal_core_settings() {' . $values['#value'] . '}'; } } } @@ -877,7 +711,8 @@ function advagg_mod_advagg_modify_js_pre_render_alter(&$children, &$elements) { foreach ($children as &$values) { if (isset($values['#attributes']) && isset($values['#attributes']['async']) && $values['#attributes']['async'] === 'async' && !empty($values['#attributes']['src'])) { $source = $values['#attributes']['src']; - if (strpos($source, 'http://') !== 0 + // @ignore sniffer_whitespace_openbracketspacing_openingwhitespace + if ( strpos($source, 'http://') !== 0 && strpos($source, 'https://') !== 0 && strpos($source, '//') !== 0 ) { @@ -903,57 +738,48 @@ function advagg_mod_advagg_modify_js_pre_render_alter(&$children, &$elements) { * Implements hook_advagg_modify_css_pre_render_alter(). */ function advagg_mod_advagg_modify_css_pre_render_alter(&$children, &$elements) { - // Skip if there is no css. - if (empty($children)) { - return; - } - - if (!module_exists('advagg') || !advagg_enabled()) { + if (module_exists('advagg') && !advagg_enabled()) { return; } // Return early if this setting is disabled. - list(, , , , , , , , , , $css_defer) = advagg_mod_get_lists(array(), $elements['#items']); + $css_defer = variable_get('advagg_mod_css_defer', ADVAGG_MOD_CSS_DEFER); if (empty($css_defer)) { return; } + $css_defer_js_code = variable_get('advagg_mod_css_defer_js_code', ADVAGG_MOD_CSS_DEFER_JS_CODE); - $critical_css = FALSE; - // Only check for the critical-css key if configured to do so. - if (variable_get('advagg_mod_css_defer_visibility', ADVAGG_MOD_VISIBILITY_LISTED) == ADVAGG_MOD_VISIBILITY_FILE_CONTROLLED) { - foreach ($elements['#items'] as $item) { - if ($item['type'] === 'inline' && !empty($item['critical-css'])) { - $critical_css = TRUE; - break; - } - } - // Return early if there's no critical css for the path and deferring is - // file controlled. - if (!$critical_css) { - return; - } + // Make advagg_mod_loadStyleSheet() available. + $type = 'external'; + $data = '//rawgit.com/filamentgroup/loadCSS/master/loadCSS.js'; + $min = ''; + if (variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL) >= 0) { + $min = '.min'; } - - if (!$critical_css) { - // 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 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; - } + if ($css_defer_js_code == 2) { + $type = 'file'; + $data = drupal_get_path('module', 'advagg_mod') . "/loadCSS$min.js"; } - - // Modify css. - static $added; - advagg_mod_add_loadcss_js_lib(array(), $elements['#items']); + 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<g.length;d++)g[d].href&&g[d].href.indexOf(a)>-1&&(c=!0);c?b():setTimeout(function(){e.onloadcssdefined(b)})},e.onloadcssdefined(function(){e.media=c||"all"}),e}'; + } + $options = array( + 'type' => $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; + } + drupal_add_js($data, $options); // Wrap CSS in noscript tags. - $defer_skip_first_file = variable_get('advagg_mod_css_defer_skip_first_file', ADVAGG_MOD_CSS_DEFER_SKIP_FIRST_FILE); $options = array( 'type' => 'inline', 'scope' => $css_defer >= 5 ? 'footer' : 'header', @@ -962,1944 +788,100 @@ function advagg_mod_advagg_modify_css_pre_render_alter(&$children, &$elements) { 'weight' => $css_defer == 1 ? -50000 : 0, 'movable' => $css_defer == 1 ? FALSE : TRUE, ); - - // Get the key of the last css file that will use loadcss. - $last_key = NULL; - foreach ($children as $children_key => $values) { - // Do not count inline styles. - if ($values['#tag'] === 'style' || empty($values['#attributes']['href']) - ) { - continue; - } - // Only use if no browser conditionals. - if (isset($values['#browsers']['!IE']) - && $values['#browsers']['!IE'] === TRUE - && isset($values['#browsers']['IE']) - && $values['#browsers']['IE'] === TRUE - ) { - $last_key = $children_key; - } - } - // If all css uses a browser conditional, then use the last one to release. - if ($last_key === NULL) { - $last_key = $children_key; - } - - $preload_array = array(); - foreach ($children as $children_key => &$values) { - // Do not defer inline styles. - if ($values['#tag'] === 'style' || empty($values['#attributes']['href'])) { - continue; - } - if (empty($preload_array) && $defer_skip_first_file == 2) { - $preload_array[] = array(); + foreach ($children as &$values) { + // Do not defer inline scripts. + if ($values['#tag'] === 'style') { continue; - } - - // If this is the last CSS file release the hold on jquery.ready. - $onload_extra = ''; - if ($last_key === $children_key) { - // Run holdready once it is defined. - $holdready_script = 'window.advagg_mod_loadcss = function() {if (window.jQuery) {if (jQuery.isFunction(jQuery.holdReady)){jQuery.holdReady(false);}} else {setTimeout(advagg_mod_loadcss, 100);}};'; - $onload_extra = "{$holdready_script}setTimeout(advagg_mod_loadcss, 200);"; - $GLOBALS['advagg_mod_loadcss_jquery_holdready'] = TRUE; - } - - $id = "advagg_loadcss_$children_key"; - if ($css_defer == 4) { - $copy = $values; - $copy['#attributes']['rel'] = 'preload'; - $copy['#attributes']['as'] = 'style'; - $copy['#attributes']['onload'] = "{$onload_extra}this.onload=null;this.rel='stylesheet'"; - $preload_array[$children_key] = $copy; - } - else { - // Add browsers to the js options. - if (isset($values['#browsers'])) { - $options['browsers'] = $values['#browsers']; - } - - // Create loadCSS wrapper code. - $inline = "loadCSS(\"{$values['#attributes']['href']}\", document.getElementById(\"$id\")"; - if ($values['#attributes']['media'] !== 'all') { - $inline .= ", \"{$values['#attributes']['media']}\""; - } - if (!empty($values['#attributes']['crossorigin'])) { - $inline .= ", \"{$values['#attributes']['crossorigin']}\""; - } - $inline .= ')'; - - // Create onloadCSS wrapper code. - if (!empty($values['#attributes']['onloadCSS'])) { - $inline = "onloadCSS({$inline}, function() {{$onload_extra}{$values['#attributes']['onloadCSS']}});"; - } - elseif (!empty($onload_extra)) { - $inline = "onloadCSS({$inline}, function() {{$onload_extra}});"; - } - - // Make code work if loader code is below loadcss calls. - $matches[2] = $matches[0] = $inline; - $inline = advagg_mod_wrap_inline_js($matches, "window.loadCSS", 40); - - // Add in script tags to load css via js. - if (!isset($added[$values['#attributes']['href']])) { - drupal_add_js($inline, $options); - $added[$values['#attributes']['href']] = TRUE; - } - } - // Wrap current css in noscript tags. - $values['#prefix'] = "<noscript id=\"$id\">\n"; - $values['#suffix'] = '</noscript>'; - // Reset for next item in loop. - if (isset($options['browsers'])) { - unset($options['browsers']); - } - } - unset($values); - - // Add in the element after the noscript element keeping the css order. - $new_elements = array(); - foreach ($elements as $elements_key => $elements_value) { - // Build old array. - if (is_numeric($elements_key)) { - $new_elements[] = $elements_value; - } - else { - $new_elements[$elements_key] = $elements_value; - } - // Splice in new data. - if (isset($preload_array[$elements_key])) { - $new_elements[] = $preload_array[$elements_key]; - } - } - $elements = $new_elements; - unset($new_elements); -} - -/** - * Implements hook_advagg_hooks_implemented_alter(). - */ -function advagg_mod_advagg_hooks_implemented_alter(&$hooks, $all) { - if ($all) { - $hooks += array( - 'advagg_mod_get_lists_alter' => array(), - ); - } -} - -/** - * Implements hook_advagg_get_root_files_dir_alter(). - */ -function advagg_mod_advagg_get_root_files_dir_alter(&$css_paths, &$js_paths) { - $dir = rtrim(variable_get('advagg_mod_unified_multisite_dir', ''), '/'); - if (empty($dir) || !file_exists($dir) || !is_dir($dir)) { - return; - } - // Change directory. - $css_paths[0] = $dir . '/advagg_css'; - $js_paths[0] = $dir . '/advagg_js'; - - file_prepare_directory($css_paths[0], FILE_CREATE_DIRECTORY); - file_prepare_directory($js_paths[0], FILE_CREATE_DIRECTORY); - - // Set the URI of the directory. - $css_paths[1] = advagg_get_relative_path($css_paths[0]); - $js_paths[1] = advagg_get_relative_path($js_paths[0]); -} - -/** - * Implements hook_advagg_current_hooks_hash_array_alter(). - */ -function advagg_mod_advagg_current_hooks_hash_array_alter(&$aggregate_settings) { - // JS Settings. - $aggregate_settings['variables']['advagg_mod_js_async_shim'] = variable_get('advagg_mod_js_async_shim', ADVAGG_MOD_JS_ASYNC_SHIM); - - // Make safe if using the aggressive cache. - if (variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL) >= 5) { - $aggregate_settings['variables']['advagg_mod_js_preprocess'] = variable_get('advagg_mod_js_preprocess', ADVAGG_MOD_JS_PREPROCESS); - $aggregate_settings['variables']['advagg_mod_js_remove_unused'] = variable_get('advagg_mod_js_remove_unused', ADVAGG_MOD_JS_REMOVE_UNUSED); - $aggregate_settings['variables']['advagg_mod_js_head_extract'] = variable_get('advagg_mod_js_head_extract', ADVAGG_MOD_JS_HEAD_EXTRACT); - $aggregate_settings['variables']['advagg_mod_js_adjust_sort_external'] = variable_get('advagg_mod_js_adjust_sort_external', ADVAGG_MOD_JS_ADJUST_SORT_EXTERNAL); - $aggregate_settings['variables']['advagg_mod_js_adjust_sort_inline'] = variable_get('advagg_mod_js_adjust_sort_inline', ADVAGG_MOD_JS_ADJUST_SORT_INLINE); - $aggregate_settings['variables']['advagg_mod_js_adjust_sort_browsers'] = variable_get('advagg_mod_js_adjust_sort_browsers', ADVAGG_MOD_JS_ADJUST_SORT_BROWSERS); - $aggregate_settings['variables']['advagg_mod_ga_inline_to_file'] = variable_get('advagg_mod_ga_inline_to_file', ADVAGG_MOD_GA_INLINE_TO_FILE); - $aggregate_settings['variables']['advagg_mod_js_footer'] = variable_get('advagg_mod_js_footer', ADVAGG_MOD_JS_FOOTER); - $aggregate_settings['variables']['advagg_mod_js_defer'] = variable_get('advagg_mod_js_defer', ADVAGG_MOD_JS_DEFER); - $aggregate_settings['variables']['advagg_mod_js_footer_inline_alter'] = variable_get('advagg_mod_js_footer_inline_alter', ADVAGG_MOD_JS_FOOTER_INLINE_ALTER); - $aggregate_settings['variables']['advagg_mod_js_async'] = variable_get('advagg_mod_js_async', ADVAGG_MOD_JS_ASYNC); - } - - // CSS Settings. - $aggregate_settings['variables']['advagg_mod_css_translate'] = variable_get('advagg_mod_css_translate', ADVAGG_MOD_CSS_TRANSLATE); - if (variable_get('advagg_mod_css_translate', ADVAGG_MOD_CSS_TRANSLATE)) { - $aggregate_settings['variables']['advagg_mod_css_translate_lang'] = isset($GLOBALS['language']->language) ? $GLOBALS['language']->language : 'en'; - } - - $aggregate_settings['variables']['advagg_mod_css_adjust_sort_external'] = variable_get('advagg_mod_css_adjust_sort_external', ADVAGG_MOD_CSS_ADJUST_SORT_EXTERNAL); - $aggregate_settings['variables']['advagg_mod_css_adjust_sort_inline'] = variable_get('advagg_mod_css_adjust_sort_inline', ADVAGG_MOD_CSS_ADJUST_SORT_INLINE); - - // Make safe if using the aggressive cache. - if (variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL) >= 5) { - $aggregate_settings['variables']['advagg_mod_css_preprocess'] = variable_get('advagg_mod_css_preprocess', ADVAGG_MOD_CSS_PREPROCESS); - $aggregate_settings['variables']['advagg_mod_css_head_extract'] = variable_get('advagg_mod_css_head_extract', ADVAGG_MOD_CSS_HEAD_EXTRACT); - $aggregate_settings['variables']['advagg_mod_css_adjust_sort_browsers'] = variable_get('advagg_mod_css_adjust_sort_browsers', ADVAGG_MOD_CSS_ADJUST_SORT_BROWSERS); - $aggregate_settings['variables']['advagg_mod_css_defer'] = variable_get('advagg_mod_css_defer', ADVAGG_MOD_CSS_DEFER); - $aggregate_settings['variables']['advagg_mod_css_defer_js_code'] = variable_get('advagg_mod_css_defer_js_code', ADVAGG_MOD_CSS_DEFER_JS_CODE); - $aggregate_settings['variables']['advagg_mod_inline_visibility'] = variable_get('advagg_mod_inline_visibility', ADVAGG_MOD_VISIBILITY_LISTED); - $aggregate_settings['variables']['advagg_mod_inline_pages'] = variable_get('advagg_mod_inline_pages', ''); - $aggregate_settings['variables']['advagg_mod_css_defer_visibility'] = variable_get('advagg_mod_css_defer_visibility', ADVAGG_MOD_VISIBILITY_LISTED); - $aggregate_settings['variables']['advagg_mod_css_defer_pages'] = variable_get('advagg_mod_css_defer_pages', ''); - - // Run functions for page visibility. - $aggregate_settings['variables']['advagg_mod_inline_page'] = advagg_mod_inline_page(); - $aggregate_settings['variables']['advagg_mod_inline_page_js'] = advagg_mod_inline_page_js(); - $aggregate_settings['variables']['advagg_mod_inline_page_css'] = advagg_mod_inline_page_css(); - $aggregate_settings['variables']['advagg_mod_css_defer_page'] = advagg_mod_css_defer_page(); - } -} - -/** - * @} End of "addtogroup advagg_hooks". - */ - -/** - * @addtogroup 3rd_party_hooks - * @{ - */ - -/** - * Implements hook_libraries_info(). - */ -function advagg_mod_libraries_info() { - $libraries['loadCSS'] = array( - 'name' => 'loadCSS', - 'vendor url' => 'https://github.com/filamentgroup/loadCSS', - 'download url' => 'https://github.com/filamentgroup/loadCSS/archive/master.zip', - 'version arguments' => array( - 'file' => 'package.json', - 'pattern' => '/"version":\\s+"([0-9\.]+)"/', - 'lines' => 100, - ), - // Called before the library is loaded. - 'callbacks' => array( - 'pre-load' => array( - 'advagg_mod_libraries_preload_callback', - ), - ), - 'local version' => '2.0.1', - 'remote' => array( - 'callback' => 'advagg_get_github_version_json', - 'url' => 'https://cdn.jsdelivr.net/gh/filamentgroup/loadCSS@master/package.json', - ), - 'files' => array( - 'js' => array( - 'src/loadCSS.js' => array( - 'type' => 'file', - 'async' => TRUE, - ), - ), - ), - ); - // Get the latest tagged version for external file loading. - $version = advagg_get_remote_libraries_version('loadCSS', $libraries['loadCSS']); - // Get the advagg_mod path for local loading. - $advagg_mod_path = drupal_get_path('module', 'advagg_mod'); - $libraries['loadCSS'] += array( - 'variants' => array( - 'normal-preload' => array( - 'files' => array( - 'js' => array( - 'src/cssrelpreload.js' => array( - 'type' => 'file', - 'async' => TRUE, - ), - ), - ), - ), - 'normal-onload' => array( - 'files' => array( - 'js' => array( - 'src/loadCSS.js' => array( - 'type' => 'file', - 'async' => TRUE, - ), - 'src/onloadCSS.js' => array( - 'type' => 'file', - 'async' => TRUE, - ), - ), - ), - ), - 'minified' => array( - 'files' => array( - 'js' => array( - 'src/loadCSS.min.js' => array( - 'type' => 'file', - 'async' => TRUE, - ), - ), - ), - ), - 'minified-preload' => array( - 'files' => array( - 'js' => array( - 'src/cssrelpreload.min.js' => array( - 'type' => 'file', - 'async' => TRUE, - ), - ), - ), - ), - 'minified-onload' => array( - 'files' => array( - 'js' => array( - 'src/loadCSS.min.js' => array( - 'type' => 'file', - 'async' => TRUE, - ), - 'src/onloadCSS.min.js' => array( - 'type' => 'file', - 'async' => TRUE, - ), - ), - ), - ), - 'external' => array( - 'files' => array( - 'js' => array( - "//cdn.jsdelivr.net/gh/filamentgroup/loadCSS@v{$version}/src/loadCSS.js" => array( - 'type' => 'external', - 'data' => "//cdn.jsdelivr.net/gh/filamentgroup/loadCSS@v{$version}/src/loadCSS.js", - 'async' => TRUE, - ), - ), - ), - ), - 'external-preload' => array( - 'files' => array( - 'js' => array( - "//cdn.jsdelivr.net/gh/filamentgroup/loadCSS@v{$version}/src/cssrelpreload.js" => array( - 'type' => 'external', - 'data' => "//cdn.jsdelivr.net/gh/filamentgroup/loadCSS@v{$version}/src/cssrelpreload.js", - 'async' => TRUE, - ), - ), - ), - ), - 'external-onload' => array( - 'files' => array( - 'js' => array( - "//cdn.jsdelivr.net/gh/filamentgroup/loadCSS@v{$version}/src/loadCSS.js" => array( - 'type' => 'external', - 'data' => "//cdn.jsdelivr.net/gh/filamentgroup/loadCSS@v{$version}/src/loadCSS.js", - 'async' => TRUE, - ), - "//cdn.jsdelivr.net/gh/filamentgroup/loadCSS@v{$version}/src/onloadCSS.js" => array( - 'type' => 'external', - 'data' => "//cdn.jsdelivr.net/gh/filamentgroup/loadCSS@v{$version}/src/onloadCSS.js", - 'async' => TRUE, - ), - ), - ), - ), - 'local' => array( - 'version' => '1.3.1', - 'files' => array( - 'js' => array( - "{$advagg_mod_path}/loadCSS.js" => array( - 'type' => 'file', - 'data' => "{$advagg_mod_path}/loadCSS.js", - 'async' => TRUE, - ), - ), - ), - ), - 'local-preload' => array( - 'version' => '1.3.1', - 'files' => array( - 'js' => array( - "{$advagg_mod_path}/cssrelpreload.js" => array( - 'type' => 'file', - 'data' => "{$advagg_mod_path}/cssrelpreload.js", - 'async' => TRUE, - ), - ), - ), - ), - 'local-onload' => array( - 'version' => '1.3.1', - 'files' => array( - 'js' => array( - "{$advagg_mod_path}/loadCSS.js" => array( - 'type' => 'file', - 'data' => "{$advagg_mod_path}/loadCSS.js", - 'async' => TRUE, - ), - "{$advagg_mod_path}/onloadCSS.js" => array( - 'type' => 'file', - 'data' => "{$advagg_mod_path}/onloadCSS.js", - 'async' => TRUE, - ), - ), - ), - ), - 'local-minified' => array( - 'version' => '1.3.1', - 'files' => array( - 'js' => array( - "{$advagg_mod_path}/loadCSS.min.js" => array( - 'type' => 'file', - 'data' => "{$advagg_mod_path}/loadCSS.min.js", - 'async' => TRUE, - ), - ), - ), - ), - 'local-minified-preload' => array( - 'version' => '1.3.1', - 'files' => array( - 'js' => array( - "{$advagg_mod_path}/cssrelpreload.min.js" => array( - 'type' => 'file', - 'data' => "{$advagg_mod_path}/cssrelpreload.min.js", - 'async' => TRUE, - ), - ), - ), - ), - 'local-minified-onload' => array( - 'version' => '1.3.1', - 'files' => array( - 'js' => array( - "{$advagg_mod_path}/loadCSS.min.js" => array( - 'type' => 'file', - 'data' => "{$advagg_mod_path}/loadCSS.min.js", - 'async' => TRUE, - ), - "{$advagg_mod_path}/onloadCSS.min.js" => array( - 'type' => 'file', - 'data' => "{$advagg_mod_path}/onloadCSS.min.js", - 'async' => TRUE, - ), - ), - ), - ), - ), - ); - - // Add inline data. - $loadcss_loc = "{$advagg_mod_path}/loadCSS.min.js"; - $cssrelpreload_loc = "{$advagg_mod_path}/cssrelpreload.min.js"; - $onloadcss_loc = "{$advagg_mod_path}/onloadCSS.min.js"; - // Use given library if there. - $libraries_paths = array(); - if (is_callable('libraries_get_libraries')) { - $libraries_paths = libraries_get_libraries(); - } - if (isset($libraries_paths['loadCSS'])) { - // Get location of loadCSS. - if (is_readable($libraries_paths['loadCSS'] . '/src/loadCSS.min.js')) { - $loadcss_loc = $libraries_paths['loadCSS'] . '/src/loadCSS.min.js'; - $libraries['loadCSS']['variants']['minified']['#files_exists'] = TRUE; - } - elseif (is_readable($libraries_paths['loadCSS'] . '/src/loadCSS.js')) { - $loadcss_loc = $libraries_paths['loadCSS'] . '/src/loadCSS.js'; - } - - // Get location of cssrelpreload. - if (is_readable($libraries_paths['loadCSS'] . '/src/cssrelpreload.min.js')) { - $cssrelpreload_loc = $libraries_paths['loadCSS'] . '/src/cssrelpreload.min.js'; - if ($libraries['loadCSS']['variants']['minified']['#files_exists']) { - $libraries['loadCSS']['variants']['minified-preload']['#files_exists'] = TRUE; - } - } - elseif (is_readable($libraries_paths['loadCSS'] . '/src/cssrelpreload.js')) { - $cssrelpreload_loc = $libraries_paths['loadCSS'] . '/src/cssrelpreload.js'; - } - - // Get location of onloadCSS. - if (is_readable($libraries_paths['loadCSS'] . '/src/onloadCSS.min.js')) { - $onloadcss_loc = $libraries_paths['loadCSS'] . '/src/onloadCSS.min.js'; - if ($libraries['loadCSS']['variants']['minified']['#files_exists']) { - $libraries['loadCSS']['variants']['minified-preload']['#files_exists'] = TRUE; - } - } - elseif (is_readable($libraries_paths['loadCSS'] . '/src/onloadCSS.js')) { - $onloadcss_loc = $libraries_paths['loadCSS'] . '/src/onloadCSS.js'; - } - } - - // Add inline scripts. - $libraries['loadCSS']['variants'] += array( - 'inline' => array( - 'files' => array( - 'js' => array( - 'loadCSS_inline' => array( - 'type' => 'inline', - 'data' => (string) @advagg_file_get_contents($loadcss_loc), - 'no_defer' => TRUE, - ), - ), - ), - ), - 'inline-preload' => array( - 'files' => array( - 'js' => array( - 'cssrelpreload_inline' => array( - 'type' => 'inline', - 'data' => (string) @advagg_file_get_contents($cssrelpreload_loc), - 'no_defer' => TRUE, - ), - ), - ), - ), - 'inline-onload' => array( - 'files' => array( - 'js' => array( - 'loadCSS_inline' => array( - 'type' => 'inline', - 'data' => (string) @advagg_file_get_contents($loadcss_loc), - 'no_defer' => TRUE, - ), - 'onloadCSS_inline' => array( - 'type' => 'inline', - 'data' => (string) @advagg_file_get_contents($onloadcss_loc), - 'no_defer' => TRUE, - ), - ), - ), - ), - ); - - if (!is_callable('libraries_detect')) { - // Set defaults. - $default_options = advagg_mod_loadcss_js_defaults(); - foreach ($libraries['loadCSS']['files']['js'] as &$value) { - $value += $default_options; - } - foreach ($libraries['loadCSS']['variants'] as &$values) { - foreach ($values['files']['js'] as &$value) { - $value += $default_options; - } - } - } - - return $libraries; -} - -/** - * Implements hook_magic(). - */ -function advagg_mod_magic(array $magic_settings, $theme) { - // $magic_settings is READ ONLY. - $settings = array(); - - // If possible disable access and set default to false. - if (!isset($magic_settings['css']['magic_embedded_mqs']['#access'])) { - $settings['css']['magic_embedded_mqs']['#access'] = FALSE; - } - if (!isset($magic_settings['css']['magic_embedded_mqs']['#default_value'])) { - $settings['css']['magic_embedded_mqs']['#default_value'] = FALSE; - } - if (!isset($magic_settings['js']['magic_footer_js']['#access'])) { - $settings['js']['magic_footer_js']['#access'] = FALSE; - } - if (!isset($magic_settings['js']['magic_footer_js']['#default_value'])) { - $settings['js']['magic_footer_js']['#default_value'] = FALSE; - } - if (!isset($magic_settings['js']['magic_library_head']['#access'])) { - $settings['js']['magic_library_head']['#access'] = FALSE; - } - if (!isset($magic_settings['js']['magic_library_head']['#default_value'])) { - $settings['js']['magic_library_head']['#default_value'] = FALSE; - } - if (!isset($magic_settings['js']['magic_experimental_js']['#access'])) { - $settings['js']['magic_experimental_js']['#access'] = FALSE; - } - if (!isset($magic_settings['js']['magic_experimental_js']['#default_value'])) { - $settings['js']['magic_experimental_js']['#default_value'] = FALSE; - } - - // Add in our own validate function so we can preprocess variables before - // they are saved. - $settings['#validate'] = array('advagg_mod_magic_form_validate'); - // Must not contain anything from the $magic_settings array. - return $settings; -} - -/** - * @} End of "addtogroup 3rd_party_hooks". - */ - -/** - * Get the default loadcss options for the js used. - * - * @return array - * Key => value options array for drupal_add_js(). - */ -function advagg_mod_loadcss_js_defaults() { - list(, , , , , , , , , , $css_defer) = advagg_mod_get_lists(); - $default_options = array( - '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, - ); - return $default_options; -} - -/** - * Callback right before loadcss lib is loaded; set defaults. - * - * @param array $version_variant - * Array of the library that is about to be loaded. - */ -function advagg_mod_libraries_preload_callback(array &$version_variant) { - // Get default options. - $default_options = advagg_mod_loadcss_js_defaults(); - // Set defaults for the given configuration. - foreach ($version_variant['files']['js'] as &$value) { - $value += $default_options; - } -} - -/** - * Adds the loadcss js library if needed. - * - * @param array $js - * The JS array. - * @param array $css - * The CSS array. - */ -function advagg_mod_add_loadcss_js_lib(array $js = array(), array $css = array()) { - if (!module_exists('advagg') || !advagg_enabled()) { - return; - } - - // Return early if this setting is disabled. - list(, , , , , , , , , , $css_defer) = advagg_mod_get_lists($js, $css); - if (empty($css_defer)) { - return; - } - - static $added; - $library = advagg_get_library('loadCSS', 'advagg_mod'); - $options_defaults = advagg_mod_loadcss_js_defaults(); - - $preload = '-onload'; - if ($css_defer == 4) { - $preload = '-preload'; - } - - $css_defer_js_code = variable_get('advagg_mod_css_defer_js_code', ADVAGG_MOD_CSS_DEFER_JS_CODE); - // Inline load. - if ($css_defer_js_code == 0) { - if (!empty($library['installed'])) { - libraries_load('loadCSS', "inline{$preload}"); - } - else { - foreach ($library['variants']["inline{$preload}"]['files']['js'] as $data => $options) { - if (!isset($added[$data])) { - if (!empty($options['data'])) { - drupal_add_js($options['data'], $options + $options_defaults); - $added[$data] = TRUE; - } - else { - // Fallback to load as a file if no inline js. - $css_defer_js_code = 2; - } - } - } - } - } - // Load as a file. - if ($css_defer_js_code == 2) { - if ($library['installed']) { - if (variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL) >= 0 && $library['variants']['minified']['#files_exists']) { - libraries_load('loadCSS', "minified{$preload}"); - } - else { - if ($preload) { - libraries_load('loadCSS'); - } - else { - libraries_load('loadCSS', "normal{$preload}"); - } - } - } - else { - foreach ($library['variants']["local{$preload}"]['files']['js'] as $data => $options) { - if (!isset($added[$data])) { - if (!empty($options['data'])) { - drupal_add_js($options['data'], $options + $options_defaults); - $added[$data] = TRUE; - } - else { - // Fallback to external load. - $css_defer_js_code = 4; - } - } - } - } - } - // Load external library. - if ($css_defer_js_code == 4) { - foreach ($library['variants']["external{$preload}"]['files']['js'] as $data => $options) { - if (!isset($added[$data])) { - drupal_add_js($options['data'], $options + $options_defaults); - $added[$data] = TRUE; - } - } - } -} - -/** - * Try to find the critical css file. - * - * @return array - * The css and dns files to use. - */ -function advagg_mod_find_critical_css_file() { - $filename = FALSE; - - // Normalize request uri. - $base_path = base_path(); - $request_uri = request_uri(); - $pos = strpos($request_uri, $base_path); - if ($pos === 0) { - $request_uri = substr($request_uri, strlen($base_path)); - } - - $dirs = array( - 0 => drupal_get_path('theme', $GLOBALS['theme']) . '/', - 1 => 'critical-css/', - // Use authenticated|anonymous or all. - 2 => user_is_logged_in() ? 'authenticated/' : 'anonymous/', - 3 => 'all/', - // Use urls or object_type. - 4 => 'urls/', - 5 => 'type/', - // Different variations of the current URL. - 6 => current_path(), - 7 => advagg_url_to_filename($request_uri, FALSE), - 8 => advagg_url_to_filename(request_path(), FALSE), - 9 => $request_uri, - 10 => request_path(), - ); - $front_page = drupal_is_front_page(); - if (!$front_page) { - $front_page = drupal_get_path_alias() == variable_get('site_frontpage', 'node'); - } - $object = menu_get_object(); - $params = array($dirs, $front_page, $object); - $inline_strings = array('', '', ''); - - // Allow for altering the starting point. - // Call hook_advagg_mod_critical_css_file_pre_alter(). - drupal_alter('advagg_mod_critical_css_file_pre', $filename, $params, $inline_strings); - list($dirs, $front_page, $object) = $params; - - // Look in themename/critical-css/authenticated|anonymous/urls|object_type. - if ((!empty($dirs[0]) || !empty($dirs[1])) && is_readable("{$dirs[0]}{$dirs[1]}")) { - if (!$filename - && $front_page - && is_readable("{$dirs[0]}{$dirs[1]}{$dirs[2]}{$dirs[4]}front.css") - ) { - $filename = "{$dirs[0]}{$dirs[1]}{$dirs[2]}{$dirs[4]}front"; - } - if (!$filename - && is_readable("{$dirs[0]}{$dirs[1]}{$dirs[2]}{$dirs[4]}{$dirs[6]}.css") - ) { - $filename = "{$dirs[0]}{$dirs[1]}{$dirs[2]}{$dirs[4]}{$dirs[6]}"; - } - if (!$filename - && is_readable("{$dirs[0]}{$dirs[1]}{$dirs[2]}{$dirs[4]}{$dirs[7]}.css") - ) { - $filename = "{$dirs[0]}{$dirs[1]}{$dirs[2]}{$dirs[4]}{$dirs[7]}"; - } - if (!$filename - && is_readable("{$dirs[0]}{$dirs[1]}{$dirs[2]}{$dirs[4]}{$dirs[8]}.css") - ) { - $filename = "{$dirs[0]}{$dirs[1]}{$dirs[2]}{$dirs[4]}{$dirs[8]}"; - } - if (isset($object->type)) { - $filtered_object_type = advagg_url_to_filename($object->type); - if (!$filename - && is_readable("{$dirs[0]}{$dirs[1]}{$dirs[2]}{$dirs[5]}{$object->type}.css") - ) { - $filename = "{$dirs[0]}{$dirs[1]}{$dirs[2]}{$dirs[5]}{$object->type}"; - } - if (!$filename - && is_readable("{$dirs[0]}{$dirs[1]}{$dirs[2]}{$dirs[5]}$filtered_object_type.css") - ) { - $filename = "{$dirs[0]}{$dirs[1]}{$dirs[2]}{$dirs[5]}$filtered_object_type"; - } - } - - // Look in themename/critical-css/all/urls|object_type. - if (!$filename - && $front_page - && is_readable("{$dirs[0]}{$dirs[1]}{$dirs[3]}{$dirs[4]}front.css") - ) { - $filename = "{$dirs[0]}{$dirs[1]}{$dirs[3]}{$dirs[4]}front"; - } - if (!$filename - && is_readable("{$dirs[0]}{$dirs[1]}{$dirs[3]}{$dirs[4]}{$dirs[6]}.css") - ) { - $filename = "{$dirs[0]}{$dirs[1]}{$dirs[3]}{$dirs[4]}{$dirs[6]}"; - } - if (!$filename - && is_readable("{$dirs[0]}{$dirs[1]}{$dirs[3]}{$dirs[4]}{$dirs[7]}.css") - ) { - $filename = "{$dirs[0]}{$dirs[1]}{$dirs[3]}{$dirs[4]}{$dirs[7]}"; - } - if (!$filename - && is_readable("{$dirs[0]}{$dirs[1]}{$dirs[3]}{$dirs[4]}{$dirs[8]}.css") - ) { - $filename = "{$dirs[0]}{$dirs[1]}{$dirs[3]}{$dirs[4]}{$dirs[8]}"; - } - if (isset($object->type)) { - $filtered_object_type = advagg_url_to_filename($object->type); - if (!$filename - && is_readable("{$dirs[0]}{$dirs[1]}{$dirs[3]}{$dirs[5]}{$object->type}.css") - ) { - $filename = "{$dirs[0]}{$dirs[1]}{$dirs[3]}{$dirs[5]}{$object->type}"; - } - if (!$filename - && is_readable("{$dirs[0]}{$dirs[1]}{$dirs[3]}{$dirs[5]}$filtered_object_type.css") - ) { - $filename = "{$dirs[0]}{$dirs[1]}{$dirs[3]}{$dirs[5]}$filtered_object_type"; - } - } - } - - // Build filenames array. - $filenames = array('', '', ''); - if ($filename) { - $filenames = array( - "$filename.css", - "$filename.dns", - "$filename.pre", - ); - } - - // Get inline css string. - if (empty($inline_strings[0]) - && !empty($filenames[0]) - && is_readable($filenames[0]) - ) { - module_load_include('inc', 'advagg', 'advagg'); - $inline_css = advagg_load_stylesheet($filenames[0], TRUE); - - // Allow other modules to modify this files contents. - // Call hook_advagg_get_css_file_contents_alter(). - drupal_alter('advagg_get_css_file_contents', $inline_css, $filenames[0]); - $inline_strings[0] = $inline_css; - } - - // Remove starting and ending style tags. - if (stripos($inline_strings[0], '<style>') === 0) { - $inline_strings[0] = trim(substr($inline_strings[0], 7)); - } - $len = strlen($inline_strings[0]); - if (stripos($inline_strings[0], '</style>') === $len - 8) { - $inline_strings[0] = trim(substr($inline_strings[0], 0, $len - 8)); - } - - // Add in domain prefetch. - if (empty($inline_strings[1]) - && !empty($filenames[1]) - && is_readable($filenames[1]) - ) { - $inline_strings[1] = file_get_contents($filenames[1]); - } - - // Add in files to preload. - if (empty($inline_strings[2]) - && !empty($filenames[2]) - && is_readable($filenames[2]) - ) { - $inline_strings[2] = file_get_contents($filenames[2]); - } - - // Remove !important from all CSS rules. Strips it even without a space in - // front, unlike the earlier version of this code. - if (variable_get('advagg_mod_inline_critical_css_strip_important', ADVAGG_MOD_INLINE_CRITICAL_CSS_STRIP_IMPORTANT)) { - if (!empty($inline_strings)) { - $inline_strings[0] = str_replace('!important', '', $inline_strings[0]); - } - } - - // Allow for altering the ending point. - // Call hook_advagg_mod_critical_css_file_post_alter(). - drupal_alter('advagg_mod_critical_css_file_post', $filenames, $params, $inline_strings); - - return array($filenames, $params, $inline_strings); -} - -/** - * Form validation handler. Disable certain magic settings before being saved. - */ -function advagg_mod_magic_form_validate($form, &$form_state) { - // Disable magic functionality if it is a duplicate of AdvAgg. - $form_state['values']['magic_embedded_mqs'] = 0; - $form_state['values']['magic_footer_js'] = 0; - $form_state['values']['magic_library_head'] = 0; - $form_state['values']['magic_experimental_js'] = 0; -} - -/** - * Alter the js array. - * - * @param array $js - * JS array. - */ -function advagg_mod_js_pre_alter(array &$js) { - if (!module_exists('advagg') || !advagg_enabled()) { - return; - } - - // Change google analytics inline loader to be inside of an aggregrated file. - if (variable_get('advagg_mod_ga_inline_to_file', ADVAGG_MOD_GA_INLINE_TO_FILE)) { - advagg_mod_ga_inline_to_file($js); - } - - if (variable_get('advagg_mod_js_inline_resource_hints', ADVAGG_MOD_JS_INLINE_RESOURCE_HINTS)) { - advagg_mod_find_inline_domains($js); - } -} - -/** - * Add dns_prefetch for inline js domains. - * - * @param array $js - * JS array. - */ -function advagg_mod_find_inline_domains(array &$js) { - $parsed = @parse_url($GLOBALS['base_root']); - $host = $parsed['host']; - - foreach ($js as &$values) { - if ($values['type'] !== 'inline') { - continue; - } - // Find quoted strings in JS. - $matches = array(); - $pattern = "/[\"'](.*?)[\"']/"; - $matched = preg_match_all($pattern, $values['data'], $matches); - - if (!$matched) { - continue; - } - - // Find domains in the quoted strings and dns_prefetch it. - foreach ($matches[1] as $value) { - if (strpos($value, '//') !== FALSE) { - $parsed = @parse_url($value); - if (!empty($parsed['host']) && $host !== $parsed['host']) { - $values['dns_prefetch'][] = $value; - } - } - } - } -} - -/** - * Alter the js array. - * - * @param array $js - * JS array. - */ -function advagg_mod_js_post_alter(array &$js) { - if (!module_exists('advagg') || !advagg_enabled()) { - return; - } - - // Only add JS if it's actually needed. - if (variable_get('advagg_mod_js_remove_unused', ADVAGG_MOD_JS_REMOVE_UNUSED)) { - advagg_mod_remove_js_if_not_used($js); - } - - // Change sort order so aggregates do not get split up. - if (variable_get('advagg_mod_js_adjust_sort_external', ADVAGG_MOD_JS_ADJUST_SORT_EXTERNAL) - || variable_get('advagg_mod_js_adjust_sort_inline', ADVAGG_MOD_JS_ADJUST_SORT_INLINE) - || variable_get('advagg_mod_js_adjust_sort_browsers', ADVAGG_MOD_JS_ADJUST_SORT_BROWSERS) - ) { - advagg_mod_sort_css_js($js, 'js'); - } - - // Move JS to the footer. - advagg_mod_js_move_to_footer($js); - - // Force all JS to be preprocessed. - if (variable_get('advagg_mod_js_preprocess', ADVAGG_MOD_JS_PREPROCESS)) { - foreach ($js as &$values) { - if (!empty($values['preprocess_lock'])) { - continue; - } - $values['preprocess'] = TRUE; - $values['cache'] = TRUE; - } - unset($values); - } - - // Add the defer or the async tag to JS. - $jquery_deferred = advagg_mod_js_async_defer($js); - // Inline JS defer. - advagg_mod_inline_defer($js, $jquery_deferred); - - // Move all async JS to the header. - if (variable_get('advagg_mod_js_async_in_header', ADVAGG_MOD_JS_ASYNC_IN_HEADER)) { - foreach ($js as &$values) { - // Skip if not file or external. - if ($values['type'] !== 'file' && $values['type'] !== 'external') { - continue; - } - // Skip if not async. - if (empty($values['async']) && empty($values['attributes']['async'])) { - continue; - } - // Skip if scope locked. - if (!empty($values['scope_lock'])) { - continue; - } - - // Move to the header with a group of 1000. - $values['scope'] = 'header'; - $values['group'] = 1000; - } - 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)) { - return; - } - foreach ($js as &$values) { - if (!isset($values['dns_prefetch'])) { - continue; - } - foreach ($values['dns_prefetch'] as &$url) { - // Prefetch stats.g.doubleclick.net domain. - if (strpos($url, '//stats.g.doubleclick.net') === FALSE) { - continue; - } - if (variable_get('advagg_resource_hints_preconnect', ADVAGG_RESOURCE_HINTS_PRECONNECT)) { - $parse = @parse_url($url); - $inline_script = 'var preconnect_support = false; try {if (document.createElement("link").relList.supports("preconnect")) {preconnect_support = true;}} catch (e) {} if (!preconnect_support) { var prefetch = document.createElement("link"); prefetch.href = "https://' . $parse['host'] . '/robots.txt"; prefetch.rel="prefetch"; document.getElementsByTagName("head")[0].appendChild(prefetch);}'; - $js['advagg_preconnect_support'] = array( - 'type' => 'inline', - 'group' => JS_LIBRARY - 1, - 'weight' => -50000, - 'scope_lock' => TRUE, - 'movable' => FALSE, - 'no_defer' => TRUE, - 'data' => $inline_script, - ) + drupal_js_defaults($inline_script); - } - else { - $url .= '#prefetch'; - } - } - } -} - -/** - * Remove ajaxPageState CSS/JS if misc/ajax.js is not used. - * - * @param array $scripts - * Render array. - */ -function advagg_mod_js_no_ajaxpagestate(array &$scripts) { - if (!module_exists('advagg') || !advagg_enabled()) { - return; - } - - if (!variable_get('advagg_mod_js_no_ajaxpagestate', ADVAGG_MOD_JS_NO_AJAXPAGESTATE) || variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL) < 0) { - return; - } - - // 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. - if (isset($scripts['#items']['settings']['data'][$delta]['ajaxPageState']['js'])) { - unset($scripts['#items']['settings']['data'][$delta]['ajaxPageState']['js']); - } - // Remove css files. - if (isset($scripts['#items']['settings']['data'][$delta]['ajaxPageState']['css'])) { - unset($scripts['#items']['settings']['data'][$delta]['ajaxPageState']['css']); - } - // Cleanup. - if (empty($scripts['#items']['settings']['data'][$delta]['ajaxPageState'])) { - unset($scripts['#items']['settings']['data'][$delta]['ajaxPageState']); - if (empty($scripts['#items']['settings']['data'][$delta])) { - unset($scripts['#items']['settings']['data'][$delta]); - } - } - } - } - } -} - -/** - * Generate a list of rules and exceptions for js files and inline. - * - * Controls inline wrapping and defer. Controls no async/defer file list. - * Controls files that stay in the header. - * - * @param array $js - * The JS array. - * @param array $css - * The CSS array. - * - * @return array - * A multidimensional array. - */ -function advagg_mod_get_lists(array $js = array(), array $css = array()) { - $lists = &drupal_static(__FUNCTION__); - $js_count = count($js); - $css_count = count($css); - $key = $js_count . '.' . $css_count; - if (!isset($lists[$key])) { - // Do not move to footer file list. - $header_file_list = array( - // Modernizr js. - '/modernizr.', - // Html5shiv and html5shiv-printshiv. - '/html5shiv.', - '/html5shiv-printshiv.', - // Google Admanager Needs to be in the header. - '/google_service.', - ); - - // Do not move to footer inline list. - $header_inline_list = array( - // Google Analytics should be in the header to verify for Webmaster tools. - 'GoogleAnalyticsObject', - 'window.google_analytics_uacct', - // Google Admanager Needs to be in the header. - 'GS_googleAddAdSenseService(', - 'GS_googleEnableAllServices(', - 'GA_googleAddSlot(', - 'GA_googleFetchAds(', - ); - - // Do not defer/async list. - $no_async_defer_list = array( - // Wistia js. - '//fast.wistia.', - // Maps. - '//dev.virtualearth.net', - '//api.maps.yahoo.com', - // Google Admanager can't be forced defer/async. - '/google_service.', - ); - // Openlayers. - if (module_exists('openlayers')) { - // Openlayers fix; external scripts can not be loaded out of order. - // Cloudmade. - $path = variable_get('openlayers_layers_cloudmade_js', ''); - if (valid_url($path, TRUE)) { - $no_async_defer_list['openlayers_layers_cloudmade'] = $path; - } - // Google. - $mapdomain = variable_get('openlayers_layers_google_mapdomain', 'maps.google.com'); - $no_async_defer_list['openlayers_layers_google'] = $mapdomain . '/maps'; - } - - // Wrap inline js so it does not run until the condition is TRUE. - // Inline search string => js condition. - $inline_wrapper_list = array(); - - // 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)))); - $inline_js_wrap_skip_list[] = '.write('; - $inline_js_wrap_skip_list[] = '._fbq'; - $inline_js_wrap_skip_list[] = '.fbq'; - $inline_js_wrap_skip_list[] = 'gtm.start'; - $inline_js_wrap_skip_list[] = '_gaq.push(["_'; - $inline_js_wrap_skip_list[] = 'ga("'; - $inline_js_wrap_skip_list[] = "ga('"; - $inline_js_wrap_skip_list[] = 'GoogleAnalyticsObject'; - $inline_js_wrap_skip_list[] = 'window.google_analytics_uacct'; - $inline_js_wrap_skip_list[] = 'function krumo('; - $inline_js_wrap_skip_list[] = '// no advagg'; - $inline_js_wrap_skip_list[] = '// noadvagg'; - $inline_js_wrap_skip_list[] = '// no-advagg'; - $inline_js_wrap_skip_list[] = '//no advagg'; - $inline_js_wrap_skip_list[] = '//noadvagg'; - $inline_js_wrap_skip_list[] = '//no-advagg'; - // Google Admanager can not be wrapped in a callback function. - $inline_js_wrap_skip_list[] = 'GS_googleAddAdSenseService('; - $inline_js_wrap_skip_list[] = 'GS_googleEnableAllServices('; - $inline_js_wrap_skip_list[] = 'GA_googleAddSlot('; - $inline_js_wrap_skip_list[] = 'GA_googleFetchAds('; - $inline_js_wrap_skip_list[] = 'GA_googleFillSlot('; - $inline_js_wrap_skip_list[] = 'adsbygoogle'; - $inline_js_wrap_skip_list[] = '_paq.push(["'; - if (module_exists('h5p')) { - $inline_js_wrap_skip_list[] = 'H5PIntegration'; - } - - // 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('; - $inline_js_defer_skip_list[] = '._fbq'; - $inline_js_defer_skip_list[] = '.fbq'; - $inline_js_defer_skip_list[] = 'gtm.start'; - $inline_js_defer_skip_list[] = '_gaq.push(["_'; - $inline_js_defer_skip_list[] = 'GoogleAnalyticsObject'; - $inline_js_defer_skip_list[] = 'window.google_analytics_uacct'; - $inline_js_defer_skip_list[] = '// no advagg'; - $inline_js_defer_skip_list[] = '// noadvagg'; - $inline_js_defer_skip_list[] = '// no-advagg'; - $inline_js_defer_skip_list[] = '//no advagg'; - $inline_js_defer_skip_list[] = '//noadvagg'; - $inline_js_defer_skip_list[] = '//no-advagg'; - // Google Admanager can not be wrapped in a callback function. - $inline_js_defer_skip_list[] = 'GS_googleAddAdSenseService('; - $inline_js_defer_skip_list[] = 'GS_googleEnableAllServices('; - $inline_js_defer_skip_list[] = 'GA_googleAddSlot('; - $inline_js_defer_skip_list[] = 'GA_googleFetchAds('; - $inline_js_defer_skip_list[] = 'GA_googleFillSlot('; - $inline_js_defer_skip_list[] = 'adsbygoogle'; - $inline_js_defer_skip_list[] = '_paq.push(["'; - if (module_exists('h5p')) { - $inline_js_defer_skip_list[] = 'H5PIntegration'; - } - - // If there is a fast clicker, ajax links might not work if ajax.js is - // loaded in the footer. - $all_in_footer_list = array( - 'misc/ajax.js' => array( - '/jquery.js', - '/jquery.min.js', - '/jquery.once.js', - '/ajax.js', - '/drupal.js', - 'settings', - ), - ); - - $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); - $css_defer = variable_get('advagg_mod_css_defer', ADVAGG_MOD_CSS_DEFER); - - // Allow other modules to add/edit the above lists. - // Call hook_advagg_mod_get_lists_alter(). - $lists[$key] = array( - $header_file_list, - $header_inline_list, - $no_async_defer_list, - $inline_wrapper_list, - $inline_js_wrap_skip_list, - $inline_js_defer_skip_list, - $all_in_footer_list, - $move_js_to_footer, - $defer_setting, - $async_setting, - $css_defer, - ); - drupal_alter('advagg_mod_get_lists', $lists[$key], $js, $css); - } - return $lists[$key]; -} - -/** - * Move JS to the footer. - * - * @param array $js - * JS array. - */ -function advagg_mod_js_move_to_footer(array &$js) { - // Move all JS to the footer. - 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)) { - foreach ($all_in_footer_list as $key => $search_strings) { - if (isset($js[$key])) { - foreach ($js as $name => &$values) { - foreach ($search_strings as $string) { - if (!empty($string) && strpos($name, (string) $string) !== FALSE) { - $values['scope_lock'] = TRUE; - break; - } - if (is_string($values['data']) && !empty($string) && strpos($values['data'], (string) $string) !== FALSE) { - $values['scope_lock'] = TRUE; - break; - } - if (!empty($values['pre_relocate_data']) && is_string($values['pre_relocate_data']) && !empty($string) && strpos($values['pre_relocate_data'], (string) $string) !== FALSE) { - $values['scope_lock'] = TRUE; - break; - } - } - } - } - } - } - - foreach ($js as $key => &$values) { - // If scope is not set, this js is not getting used. remove it. - if (!isset($values['scope'])) { - unset($js[$key]); - continue; - } - - if (strpos($values['scope'], ':') !== FALSE) { - continue; - } - - // Skip if a library and configured to do so. - if ($move_js_to_footer == 1 && $values['group'] <= JS_LIBRARY) { - continue; - } - - // Skip if the scope has been locked. - if (!empty($values['scope_lock'])) { - continue; - } - - // Allow certain scripts to be kept in the header. - if ($values['type'] !== 'inline' && $values['type'] !== 'setting') { - foreach ($header_file_list as $search_string) { - if (stripos($values['data'], $search_string) !== FALSE) { - continue 2; - } - if (!empty($values['pre_relocate_data']) && stripos($values['pre_relocate_data'], $search_string) !== FALSE) { - continue 2; - } - } - } - - // Allow certain inline scripts to be kept in the header. - if ($values['type'] === 'inline') { - foreach ($header_inline_list as $search_string) { - if (strpos($values['data'], $search_string) !== FALSE) { - continue 2; - } - } - } - - // If group is not set, make it JS_DEFAULT (0). - if (!isset($values['group'])) { - $values['group'] = JS_DEFAULT; - } - // If weight is not set, make it 0. - if (!isset($values['weight'])) { - $values['weight'] = 0; - } - // If every_page is not set, make it FALSE. - if (!isset($values['every_page'])) { - $values['every_page'] = FALSE; - } - - // If JS is not in the header increase group by 10000. - if ($values['scope'] !== 'header' && is_numeric($values['group'])) { - $values['group'] += 10000; - } - // If JS is already in the footer increase group by 10000. - if ($values['scope'] === 'footer' && is_numeric($values['group'])) { - $values['group'] += 10000; - } - $values['scope'] = 'footer'; - } - unset($values); -} - -/** - * Add the defer and or the async tag to js. - * - * @param array $js - * JS array. - * - * @return bool - * TRUE if jQuery is deferred. - */ -function advagg_mod_js_async_defer(array &$js) { - $jquery_deferred = FALSE; - $jquery_rev = strrev('/jquery.js'); - $jquery_min_rev = strrev('/jquery.min.js'); - $jquery_ui_rev = strrev('/jquery-ui.js'); - $jquery_ui_min_rev = strrev('jquery-ui.min.js'); - - // Return early if this is disabled. - list(, , $no_async_defer_list, $inline_wrapper_list, , , , , $defer_setting, $async_setting) = advagg_mod_get_lists($js); - if (!$defer_setting && !$async_setting) { - // Special handling if using loadcss to defer css loading. - if (!empty($GLOBALS['advagg_mod_loadcss_jquery_holdready'])) { - foreach ($js as $name => &$values) { - // Special handling for jQuery. - if (stripos(strrev($name), $jquery_rev) === 0 - || stripos(strrev($name), $jquery_min_rev) === 0 - ) { - // Do not fire jQuery.ready until Drupal.settings has been defined. - $values['onload'] = "if(jQuery.isFunction(jQuery.holdReady)){jQuery.holdReady(true);}"; - break; - } - } - } - return $jquery_deferred; - } - - // Disable this section of code for now; the on error attribute only works - // with async safe JS. - $use_on_error = FALSE; - if (variable_get('advagg_mod_js_defer_jquery', ADVAGG_MOD_JS_DEFER_JQUERY) !== FALSE - && variable_get('advagg_mod_js_defer_inline_alter', ADVAGG_MOD_JS_DEFER_INLINE_ALTER) - && variable_get('jquery_update_jquery_cdn', 'none') !== 'none' - ) { - $use_on_error = TRUE; - } - // If everything is async safe then we can use on error. - // Only needed if the jquery_update javascript is loaded via async/defer. - if ($use_on_error) { - $jquery_update_fallback = ''; - $jquery_update_ui_fallback = ''; - $jquery_migrate_fallback = ''; - $inline_array = array(); - if (module_exists('jquery_update') - && (variable_get('jquery_update_jquery_cdn', 'none') !== 'none' - || variable_get('jquery_update_jquery_migrate_cdn', 'none') !== 'none' - )) { - $min = variable_get('jquery_update_compression_type', 'min') == 'none' ? '' : '.min'; - $path = drupal_get_path('module', 'jquery_update'); - foreach ($js as $name => &$values) { - // Skip if not inline. - if ($values['type'] !== 'inline') { - continue; - } - - // JQuery UI. - if (stripos($values['data'], 'window.jQuery.ui') !== FALSE - && stripos($values['data'], 'document.write("<script') !== FALSE - && variable_get('jquery_update_jquery_cdn', 'none') !== 'none' - ) { - $js_path = ($min == '.min') ? '/replace/ui/ui/minified/jquery-ui.min.js' : '/replace/ui/ui/jquery-ui.js'; - $jquery_update_ui_fallback = "{$GLOBALS['base_path']}{$path}{$js_path}"; - if (empty($inline_array)) { - $inline_array = $values; - } - unset($js[$name]); - continue; - } - // JQuery Migrate. - if (stripos($values['data'], 'window.jQuery.migrateWarnings') !== FALSE - && stripos($values['data'], 'document.write("<script') !== FALSE - && variable_get('jquery_update_jquery_migrate_cdn', 'none') !== 'none' - ) { - $version = '1.2.1'; - $jquery_migrate_fallback = "{$GLOBALS['base_path']}{$path}/replace/jquery-migrate/{$version}/jquery-migrate{$min}.js"; - $inline_array = $values; - unset($js[$name]); - continue; - } - // JQuery. - // This should always be last. - if (stripos($values['data'], 'window.jQuery') !== FALSE - && stripos($values['data'], 'document.write("<script') !== FALSE - && variable_get('jquery_update_jquery_cdn', 'none') !== 'none' - ) { - $version = variable_get('jquery_update_jquery_version', '1.10'); - $jquery_update_fallback = "{$GLOBALS['base_path']}{$path}/replace/jquery/{$version}/jquery{$min}.js"; - $inline_array = $values; - unset($js[$name]); - continue; - } - } - unset($values); - } - if (!empty($jquery_update_fallback) - || !empty($jquery_update_ui_fallback) - || !empty($jquery_migrate_fallback) - ) { - // Add in the advagg_fallback() function so it's available inline. - $inline_array['group'] = '-150'; - $inline_array['weight'] += -10; - $inline_array['data'] = 'function advagg_fallback(file){var head = document.getElementsByTagName("head")[0];var script = document.createElement("script");script.src = file;script.type = "text/javascript";head.appendChild(script);};'; - $inline_array['scope_lock'] = TRUE; - $inline_array['movable'] = FALSE; - $inline_array['no_defer'] = TRUE; - $inline_array['scope'] = 'header'; - $js['advagg_fallback'] = $inline_array; - } - } - - // Make all scripts defer and/or async. - $hold_ready = FALSE; - foreach ($js as $name => &$values) { - // Skip if not a file or external. - if ($values['type'] !== 'file' && $values['type'] !== 'external') { - continue; - } - - // Everything is defer. - if (variable_get('advagg_mod_js_defer', ADVAGG_MOD_JS_DEFER) && empty($values['nodefer'])) { - $values['defer'] = TRUE; - } - // Everything is async. - if (variable_get('advagg_mod_js_async', ADVAGG_MOD_JS_ASYNC) && empty($values['noasync'])) { - $values['async'] = TRUE; - } - - // Special handling for jQuery. - if (stripos(strrev($name), $jquery_rev) === 0 - || stripos(strrev($name), $jquery_min_rev) === 0 - ) { - $jquery_deferred = TRUE; - // Do not fire jQuery.ready until Drupal.settings has been defined. - if (empty($hold_ready)) { - if (!empty($GLOBALS['advagg_mod_loadcss_jquery_holdready'])) { - $values['onload'] = "if(jQuery.isFunction(jQuery.holdReady)){jQuery.holdReady(true);jQuery.holdReady(true);}"; - } - else { - $values['onload'] = "if(jQuery.isFunction(jQuery.holdReady)){jQuery.holdReady(true);}"; - } - - $hold_ready = TRUE; - } - // jquery_update fallback. - if (module_exists('jquery_update') && variable_get('jquery_update_jquery_cdn', 'none') !== 'none') { - if ($use_on_error) { - if (!isset($values['onerror'])) { - $values['onerror'] = ''; - } - $values['onerror'] .= "advagg_fallback('{$jquery_update_fallback}');"; - } - // Do not defer/async the loading of jquery.js. - if (!variable_get('advagg_mod_js_defer_jquery', ADVAGG_MOD_JS_DEFER_JQUERY) !== FALSE - && variable_get('advagg_mod_js_defer_inline_alter', ADVAGG_MOD_JS_DEFER_INLINE_ALTER) - && variable_get('jquery_update_jquery_cdn', 'none') !== 'none' - ) { - if (variable_get('advagg_mod_js_defer', ADVAGG_MOD_JS_DEFER)) { - $values['defer'] = FALSE; - $jquery_deferred = FALSE; - } - } - if (variable_get('advagg_mod_js_async', ADVAGG_MOD_JS_ASYNC)) { - $values['async'] = FALSE; - } - // Defer/async is off; done with loop. - continue; - } - } - // Special handling for jQuery migrate. - if (stripos($name, '/jquery-migrate') !== FALSE) { - // jquery_update ui fallback. - if (module_exists('jquery_update') && variable_get('jquery_update_jquery_migrate_cdn', 'none') !== 'none') { - if ($use_on_error) { - if (!isset($values['onerror'])) { - $values['onerror'] = ''; - } - $values['onerror'] .= "advagg_fallback('{$jquery_migrate_fallback}');"; - } - // Do not defer/async the loading of jquery-migrate. - if (!variable_get('advagg_mod_js_defer_jquery', ADVAGG_MOD_JS_DEFER_JQUERY) !== FALSE - && variable_get('advagg_mod_js_defer_inline_alter', ADVAGG_MOD_JS_DEFER_INLINE_ALTER) - && variable_get('jquery_update_jquery_cdn', 'none') !== 'none' - ) { - if (variable_get('advagg_mod_js_defer', ADVAGG_MOD_JS_DEFER)) { - $values['defer'] = FALSE; - } - } - if (variable_get('advagg_mod_js_async', ADVAGG_MOD_JS_ASYNC)) { - $values['async'] = FALSE; - } - // Defer/async is off; done with loop. - continue; - } - } - // Special handling for jQuery UI. - if (stripos(strrev($name), $jquery_ui_rev) === 0 - || stripos(strrev($name), $jquery_ui_min_rev) === 0 - ) { - // jquery_update ui fallback. - if (module_exists('jquery_update') && variable_get('jquery_update_jquery_cdn', 'none') !== 'none') { - if ($use_on_error) { - if (!isset($values['onerror'])) { - $values['onerror'] = ''; - } - $values['onerror'] .= "advagg_fallback('{$jquery_update_ui_fallback}');"; - } - // Do not defer/async the loading of jquery-ui.js. - if (!variable_get('advagg_mod_js_defer_jquery', ADVAGG_MOD_JS_DEFER_JQUERY) !== FALSE - && variable_get('advagg_mod_js_defer_inline_alter', ADVAGG_MOD_JS_DEFER_INLINE_ALTER) - && variable_get('jquery_update_jquery_cdn', 'none') !== 'none' - ) { - if (variable_get('advagg_mod_js_defer', ADVAGG_MOD_JS_DEFER)) { - $values['defer'] = FALSE; - } - } - if (variable_get('advagg_mod_js_async', ADVAGG_MOD_JS_ASYNC)) { - $values['async'] = FALSE; - } - // Defer/async is off; done with loop. - continue; - } - } - - // Drupal settings; don't run until misc/drupal.js has ran. - if ($name === 'misc/drupal.js') { - // Initialize the Drupal.settings JavaScript object after this has - // loaded. - if (!isset($values['onload'])) { - $values['onload'] = ''; - } - $matches[0] = $matches[2] = 'init_drupal_core_settings();'; - $values['onload'] .= advagg_mod_wrap_inline_js($matches, "window.init_drupal_core_settings && window.jQuery && window.Drupal", 1); - } - - // No async defer list. - foreach ($no_async_defer_list as $search_string) { - if (strpos($name, $search_string) !== FALSE) { - // Do not defer/async the loading this script. - if (variable_get('advagg_mod_js_defer', ADVAGG_MOD_JS_DEFER)) { - $values['defer'] = FALSE; - } - if (variable_get('advagg_mod_js_async', ADVAGG_MOD_JS_ASYNC)) { - $values['async'] = FALSE; - } - } - } - - // Do not defer external scripts setting. - if ($defer_setting == 2 && $values['type'] === 'external') { - $values['defer'] = FALSE; - } - - } - unset($values); - - // Inline script special handling. - foreach ($js as &$values) { - if ($values['type'] !== 'inline') { - continue; - } - foreach ($inline_wrapper_list as $search_string => $js_condition) { - if (strpos($values['data'], $search_string) !== FALSE) { - $matches[0] = $matches[2] = $values['data']; - $values['data'] = advagg_mod_wrap_inline_js($matches, $js_condition); - } - } - } - unset($values); - - return $jquery_deferred; -} - -/** - * Defer inline js by using setTimeout. - * - * @param array $js - * JS array. - * @param bool $jquery_deferred - * TRUE if jquery is deferred. - */ -function advagg_mod_inline_defer(array &$js, $jquery_deferred) { - if ($jquery_deferred) { - $bootstrap_rev = strrev('/bootstrap.js'); - $bootstrap_min_rev = strrev('/bootstrap.min.js'); - foreach ($js as &$values) { - // Defer bootstrap if jquery is deferred. - if (is_string($values['data']) - && (stripos(strrev($values['data']), $bootstrap_rev) === 0 - || stripos(strrev($values['data']), $bootstrap_min_rev) === 0 - )) { - $values['defer'] = TRUE; - continue; - } - - // Only do inline. - if ($values['type'] === 'inline') { - // 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 inline scripts by wrapping the code in setTimeout callback. - $matches[2] = $matches[0] = $values['data']; - $values['data'] = advagg_mod_wrap_inline_js($matches); - } - elseif (stripos($values['data'], 'jQuery.') !== FALSE || stripos($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)) { - $jquery_rev = strrev('/jquery.js'); - $jquery_min_rev = strrev('/jquery.min.js'); - 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 (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. - * - * @param array $elements - * A render array containing: - * - #items: The JavaScript items as returned by drupal_add_js() and - * altered by drupal_get_js(). - * - #group_callback: A function to call to group #items. Following - * this function, #aggregate_callback is called to aggregate items within - * the same group into a single file. - * - #aggregate_callback: A function to call to aggregate the items within - * the groups arranged by the #group_callback function. - * - * @return array - * A render array that will render to a string of JavaScript tags. - * - * @see drupal_get_js() - */ -function _advagg_mod_pre_render_scripts(array $elements) { - if (advagg_mod_inline_page() || advagg_mod_inline_page_js()) { - advagg_mod_inline_js($elements['#items']); - } - - return $elements; -} - -/** - * A #pre_render callback to inline all CSS on this page. - * - * @param array $elements - * A render array containing: - * - '#items': The CSS items as returned by drupal_add_css() and altered by - * drupal_get_css(). - * - '#group_callback': A function to call to group #items to enable the use - * of fewer tags by aggregating files and/or using multiple @import - * statements within a single tag. - * - '#aggregate_callback': A function to call to aggregate the items within - * the groups arranged by the #group_callback function. - * - * @return array - * A render array that will render to a string of XHTML CSS tags. - * - * @see drupal_get_css() - */ -function _advagg_mod_pre_render_styles(array $elements) { - if (!module_exists('advagg') || !advagg_enabled()) { - return $elements; - } - - if (advagg_mod_inline_page() || advagg_mod_inline_page_css()) { - advagg_mod_inline_css($elements['#items']); - } - elseif (variable_get('advagg_mod_css_defer_skip_first_file', ADVAGG_MOD_CSS_DEFER_SKIP_FIRST_FILE) == 4) { - list(, , , , , , , , , , $css_defer) = advagg_mod_get_lists(array(), $elements['#items']); - $css_defer_admin = variable_get('advagg_mod_css_defer_admin', ADVAGG_MOD_CSS_DEFER_ADMIN); - if (advagg_mod_css_defer_page() - && !empty($css_defer) - && (!empty($css_defer_admin) || !path_is_admin(current_path())) - ) { - advagg_mod_inline_css($elements['#items'], 0, variable_get('advagg_mod_css_defer_inline_size_limit', ADVAGG_MOD_CSS_DEFER_INLINE_SIZE_LIMIT)); - } - } - - return $elements; -} - -/** - * Use DOMDocument's loadHTML along with DOMXPath's query to find script tags. - * - * Once found, it will also wrap them in a javascript loader function. - * - * @param string $html - * HTML fragments. - * - * @return string - * The HTML fragment with less markup errors and script tags wrapped. - */ -function advagg_mod_xpath_script_wrapper($html) { - // Do not throw errors when parsing the html. - libxml_use_internal_errors(TRUE); - - $dom = new DOMDocument(); - // Load html with full tags all around. - $dom->loadHTML('<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd"> -<html><head><meta http-equiv="content-type" content="text/html; charset=utf-8"></head><body>' . $html . '</body></html>'); - $xpath = new DOMXPath($dom); - // Get all script tags that - // are not inside of a textarea - // do not contain a src attribute - // and the type is empty or has the type of javascript. - $nodes = $xpath->query("//script[not(@src)][not(ancestor::textarea)][contains(translate(@type, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'), 'javascript') or not(@type)]"); - - foreach ($nodes as $node) { - $matches[2] = $node->nodeValue; - // $matches[0] = $dom->saveHTML($node); - $matches[0] = $node->nodeValue; - $new_html = advagg_mod_wrap_inline_js($matches); - $advagg = $dom->createElement('script'); - $advagg->appendchild($dom->createTextNode($new_html)); - $node->parentNode->replaceChild($advagg, $node); - } - // Render to HTML. - $output = $dom->saveHTML(); - - // Remove the tags we added. - $output = str_replace(array( - '<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd"> -<html><head><meta http-equiv="content-type" content="text/html; charset=utf-8"></head><body>', - '</body></html>', - ), array('', ''), $output); + } + if (!empty($values['#attributes']['href'])) { + // Wrap current css in noscript tags. + $values['#prefix'] = '<noscript>' . "\n"; + $values['#suffix'] = '</noscript>'; - // Clear any errors. - libxml_clear_errors(); - return $output; + // Add browsers to the js options. + if (isset($values['#browsers'])) { + $options['browsers'] = $values['#browsers']; + } + $inline = 'loadCSS("' . $values['#attributes']['href'] . '")'; + // Make async work if it's being used. + if ($type !== 'inline') { + $matches[2] = $matches[0] = $inline; + $inline = advagg_mod_wrap_inline_js($matches, "window.loadCSS", 40); + } + // Add in script tags to load css via js. + drupal_add_js($inline, $options); + // Reset for next item in loop. + if (isset($options['browsers'])) { + unset($options['browsers']); + } + } + } + unset($values); } /** - * Use DOMDocument's loadHTML along with DOMXPath's query to find script tags. - * - * Once found, it will add the src to the dns prefetch list. - * - * @param string $html - * HTML fragments. + * Implements hook_advagg_get_root_files_dir_alter(). */ -function advagg_mod_xpath_script_external_dns($html) { - // Do not throw errors when parsing the html. - libxml_use_internal_errors(TRUE); - - $dom = new DOMDocument(); - // Load html with full tags all around. - $dom->loadHTML('<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd"> -<html><head><meta http-equiv="content-type" content="text/html; charset=utf-8"></head><body>' . $html . '</body></html>'); - $xpath = new DOMXPath($dom); - // Get all script tags that - // are not inside of a textarea - // have a src attribute. - $nodes = $xpath->query("//script[@src][not(ancestor::textarea)]"); - - // Add the src attribute to dns-prefetch. - foreach ($nodes as $node) { - advagg_add_dns_prefetch($node->attributes->getNamedItem('src')->nodeValue); +function advagg_mod_advagg_get_root_files_dir_alter(&$css_paths, &$js_paths) { + $dir = rtrim(variable_get('advagg_mod_unified_multisite_dir', ''), '/'); + if (empty($dir) || !file_exists($dir) || !is_dir($dir)) { + return; } + // Change directory. + $css_paths[0] = $dir . '/advagg_css'; + $js_paths[0] = $dir . '/advagg_js'; - // Clear any errors. - libxml_clear_errors(); + file_prepare_directory($css_paths[0], FILE_CREATE_DIRECTORY); + file_prepare_directory($js_paths[0], FILE_CREATE_DIRECTORY); + + // Set the URI of the directory. + $css_paths[1] = advagg_get_relative_path($css_paths[0]); + $js_paths[1] = advagg_get_relative_path($js_paths[0]); } /** - * Callback for preg_replace_callback. - * - * Used to wrap inline JS in a function in order to defer the inline js code. - * - * @param string $input - * JavaScript code to wrap in setTimeout. - * - * @return string - * Inline javascript code wrapped up in a loader. + * Implements hook_advagg_current_hooks_hash_array_alter(). */ -function advagg_mod_defer_inline_js($input) { - if (variable_get('advagg_mod_js_defer_jquery', ADVAGG_MOD_JS_DEFER_JQUERY) !== FALSE - && variable_get('jquery_update_jquery_cdn', 'none') !== 'none' - && (stripos($input, 'jQuery') !== FALSE - || strpos($input, '$(') !== FALSE - || stripos($input, 'Drupal.') !== FALSE - ) - ) { - $matches[2] = $matches[0] = $input; - return advagg_mod_wrap_inline_js($matches); - } +function advagg_mod_advagg_current_hooks_hash_array_alter(&$aggregate_settings) { + // JS Settings. + $aggregate_settings['variables']['advagg_mod_js_async_shim'] = variable_get('advagg_mod_js_async_shim', ADVAGG_MOD_JS_ASYNC_SHIM); - // Get inline defer js skip list. - list(, , , , , $inline_js_defer_skip_list) = advagg_mod_get_lists(); - if (!empty($inline_js_defer_skip_list)) { - // If the line is on the skip list then do not inline the script. - foreach ($inline_js_defer_skip_list as $string_to_check) { - if (stripos($input, $string_to_check) !== FALSE) { - return $input; - } - } + // Make safe if using the aggressive cache. + if (variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL) >= 5) { + $aggregate_settings['variables']['advagg_mod_js_preprocess'] = variable_get('advagg_mod_js_preprocess', ADVAGG_MOD_JS_PREPROCESS); + $aggregate_settings['variables']['advagg_mod_js_remove_unused'] = variable_get('advagg_mod_js_remove_unused', ADVAGG_MOD_JS_REMOVE_UNUSED); + $aggregate_settings['variables']['advagg_mod_js_head_extract'] = variable_get('advagg_mod_js_head_extract', ADVAGG_MOD_JS_HEAD_EXTRACT); + $aggregate_settings['variables']['advagg_mod_js_adjust_sort_external'] = variable_get('advagg_mod_js_adjust_sort_external', ADVAGG_MOD_JS_ADJUST_SORT_EXTERNAL); + $aggregate_settings['variables']['advagg_mod_js_adjust_sort_inline'] = variable_get('advagg_mod_js_adjust_sort_inline', ADVAGG_MOD_JS_ADJUST_SORT_INLINE); + $aggregate_settings['variables']['advagg_mod_js_adjust_sort_browsers'] = variable_get('advagg_mod_js_adjust_sort_browsers', ADVAGG_MOD_JS_ADJUST_SORT_BROWSERS); + $aggregate_settings['variables']['advagg_mod_ga_inline_to_file'] = variable_get('advagg_mod_ga_inline_to_file', ADVAGG_MOD_GA_INLINE_TO_FILE); + $aggregate_settings['variables']['advagg_mod_js_footer'] = variable_get('advagg_mod_js_footer', ADVAGG_MOD_JS_FOOTER); + $aggregate_settings['variables']['advagg_mod_js_defer'] = variable_get('advagg_mod_js_defer', ADVAGG_MOD_JS_DEFER); + $aggregate_settings['variables']['advagg_mod_js_footer_inline_alter'] = variable_get('advagg_mod_js_footer_inline_alter', ADVAGG_MOD_JS_FOOTER_INLINE_ALTER); + $aggregate_settings['variables']['advagg_mod_js_async'] = variable_get('advagg_mod_js_async', ADVAGG_MOD_JS_ASYNC); } - // Use a counter in order to create unique function names. - static $counter; - ++$counter; - - // JS wrapper code. - $new = " -function advagg_mod_defer_${counter}() { - ${input}; -} -window.setTimeout(advagg_mod_defer_${counter}, 0);"; + // CSS Settings. + $aggregate_settings['variables']['advagg_mod_css_translate'] = variable_get('advagg_mod_css_translate', ADVAGG_MOD_CSS_TRANSLATE); + if (variable_get('advagg_mod_css_translate', ADVAGG_MOD_CSS_TRANSLATE)) { + $aggregate_settings['variables']['advagg_mod_css_translate_lang'] = isset($GLOBALS['language']->language) ? $GLOBALS['language']->language : 'en'; + } - return $new; + // Make safe if using the aggressive cache. + if (variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL) >= 5) { + $aggregate_settings['variables']['advagg_mod_css_preprocess'] = variable_get('advagg_mod_css_preprocess', ADVAGG_MOD_CSS_PREPROCESS); + $aggregate_settings['variables']['advagg_mod_css_head_extract'] = variable_get('advagg_mod_css_head_extract', ADVAGG_MOD_CSS_HEAD_EXTRACT); + $aggregate_settings['variables']['advagg_mod_css_adjust_sort_external'] = variable_get('advagg_mod_css_adjust_sort_external', ADVAGG_MOD_CSS_ADJUST_SORT_EXTERNAL); + $aggregate_settings['variables']['advagg_mod_css_adjust_sort_inline'] = variable_get('advagg_mod_css_adjust_sort_inline', ADVAGG_MOD_CSS_ADJUST_SORT_INLINE); + $aggregate_settings['variables']['advagg_mod_css_adjust_sort_browsers'] = variable_get('advagg_mod_css_adjust_sort_browsers', ADVAGG_MOD_CSS_ADJUST_SORT_BROWSERS); + $aggregate_settings['variables']['advagg_mod_css_defer'] = variable_get('advagg_mod_css_defer', ADVAGG_MOD_CSS_DEFER); + $aggregate_settings['variables']['advagg_mod_css_defer_js_code'] = variable_get('advagg_mod_css_defer_js_code', ADVAGG_MOD_CSS_DEFER_JS_CODE); + $aggregate_settings['variables']['advagg_mod_inline_visibility'] = variable_get('advagg_mod_inline_visibility', ADVAGG_MOD_VISIBILITY_LISTED); + $aggregate_settings['variables']['advagg_mod_inline_pages'] = variable_get('advagg_mod_inline_pages', ''); + } } +// Helper Functions. /** * Callback for preg_replace_callback. * @@ -2917,29 +899,15 @@ window.setTimeout(advagg_mod_defer_${counter}, 0);"; * Inline javascript code wrapped up in a loader to prevent errors. */ function advagg_mod_wrap_inline_js(array $matches, $check_string = NULL, $ms_wait = 250) { - list(, , , $inline_wrapper_list, $inline_js_wrap_skip_list) = advagg_mod_get_lists(); - if (empty($check_string)) { - foreach ($inline_wrapper_list as $search_string => $js_condition) { - if (strpos($matches[2], $search_string) !== FALSE) { - $check_string = $js_condition; - break; - } - } - if (empty($check_string)) { - $check_string = 'window.jQuery && window.Drupal && window.Drupal.settings'; - } + $check_string = 'window.jQuery && window.Drupal && window.Drupal.settings'; } - // Always wrap inline if it contains jquery or drupal. - if (!empty($inline_js_wrap_skip_list) - && stripos($matches[2], '(jQuery') === FALSE - && stripos($matches[2], 'jQuery.') === FALSE - && stripos($matches[2], '(Drupal') === FALSE - && stripos($matches[2], 'Drupal.') === FALSE - ) { + // Get inline js skip list string and convert it to an array. + $inline_js_skip_list = array_filter(array_map('trim', explode("\n", variable_get('advagg_mod_wrap_inline_js_skip_list', '')))); + if (!empty($inline_js_skip_list)) { // If the line is on the skip list then do not inline the script. - foreach ($inline_js_wrap_skip_list as $string_to_check) { + foreach ($inline_js_skip_list as $string_to_check) { if (stripos($matches[2], $string_to_check) !== FALSE) { return $matches[0]; } @@ -3001,7 +969,8 @@ advagg_mod_${counter}_check();"; * String: css or js. */ function advagg_mod_sort_css_js(array &$array, $type) { - if (($type === 'js' && variable_get('advagg_mod_js_adjust_sort_external', ADVAGG_MOD_JS_ADJUST_SORT_EXTERNAL)) + // @ignore sniffer_whitespace_openbracketspacing_openingwhitespace:3 + if ( ($type === 'js' && variable_get('advagg_mod_js_adjust_sort_external', ADVAGG_MOD_JS_ADJUST_SORT_EXTERNAL)) || ($type === 'css' && variable_get('advagg_mod_css_adjust_sort_external', ADVAGG_MOD_CSS_ADJUST_SORT_EXTERNAL)) ) { // Find all external items. @@ -3022,13 +991,13 @@ function advagg_mod_sort_css_js(array &$array, $type) { } // Find "lightest" item. - if (isset($value['group']) && $value['group'] < $group) { + if ($value['group'] < $group) { $group = $value['group']; } - if (!empty($value['every_page']) && !$every_page) { + if ($value['every_page'] && !$every_page) { $every_page = $value['every_page']; } - if (isset($value['weight']) && $value['weight'] < $weight) { + if ($value['weight'] < $weight) { $weight = $value['weight']; } @@ -3064,7 +1033,7 @@ function advagg_mod_sort_css_js(array &$array, $type) { } // If bootstrap is used, it must be loaded after jquery. Don't move // bootstrap if jquery is not above it. - if (strpos($value['data'], 'jquery.min.js') !== FALSE + if ( strpos($value['data'], 'jquery.min.js') !== FALSE || strpos($value['data'], 'jquery.js') !== FALSE ) { $found_jquery = TRUE; @@ -3081,7 +1050,8 @@ function advagg_mod_sort_css_js(array &$array, $type) { } } - if (($type === 'js' && variable_get('advagg_mod_js_adjust_sort_inline', ADVAGG_MOD_JS_ADJUST_SORT_INLINE)) + // @ignore sniffer_whitespace_openbracketspacing_openingwhitespace:3 + if ( ($type === 'js' && variable_get('advagg_mod_js_adjust_sort_inline', ADVAGG_MOD_JS_ADJUST_SORT_INLINE)) || ($type === 'css' && variable_get('advagg_mod_css_adjust_sort_inline', ADVAGG_MOD_CSS_ADJUST_SORT_INLINE)) ) { // Find all inline items. @@ -3102,13 +1072,13 @@ function advagg_mod_sort_css_js(array &$array, $type) { } // Find "heaviest" item. - if (isset($value['group']) && $value['group'] > $group) { + if ($value['group'] > $group) { $group = $value['group']; } - if (empty($value['every_page']) && $every_page) { - $every_page = FALSE; + if (!$value['every_page'] && $every_page) { + $every_page = $value['every_page']; } - if (isset($value['weight']) && $value['weight'] > $weight) { + if ($value['weight'] > $weight) { $weight = $value['weight']; } @@ -3144,7 +1114,8 @@ function advagg_mod_sort_css_js(array &$array, $type) { } } - if (($type === 'js' && variable_get('advagg_mod_js_adjust_sort_browsers', ADVAGG_MOD_JS_ADJUST_SORT_BROWSERS)) + // @ignore sniffer_whitespace_openbracketspacing_openingwhitespace:3 + if ( ($type === 'js' && variable_get('advagg_mod_js_adjust_sort_browsers', ADVAGG_MOD_JS_ADJUST_SORT_BROWSERS)) || ($type === 'css' && variable_get('advagg_mod_css_adjust_sort_browsers', ADVAGG_MOD_CSS_ADJUST_SORT_BROWSERS)) ) { // Get a list of browsers. @@ -3214,10 +1185,10 @@ function advagg_mod_sort_css_js(array &$array, $type) { } /** - * Returns TRUE if this page should have inline CSS and JS. + * Returns TRUE if this page should have inline CSS/JS. * * @return bool - * TRUE or FALSE. Default is FALSE. + * TRUE or FALSE. */ function advagg_mod_inline_page() { $visibility = variable_get('advagg_mod_inline_visibility', ADVAGG_MOD_VISIBILITY_LISTED); @@ -3225,42 +1196,6 @@ function advagg_mod_inline_page() { return advagg_mod_match_path($pages, $visibility); } -/** - * Returns TRUE if this page should have inline CSS. - * - * @return bool - * TRUE or FALSE. Default is FALSE. - */ -function advagg_mod_inline_page_css() { - $visibility = variable_get('advagg_mod_inline_css_visibility', ADVAGG_MOD_VISIBILITY_LISTED); - $pages = variable_get('advagg_mod_inline_css_pages', ''); - return advagg_mod_match_path($pages, $visibility); -} - -/** - * Returns TRUE if this page should have inline JS. - * - * @return bool - * TRUE or FALSE. Default is FALSE. - */ -function advagg_mod_inline_page_js() { - $visibility = variable_get('advagg_mod_inline_js_visibility', ADVAGG_MOD_VISIBILITY_LISTED); - $pages = variable_get('advagg_mod_inline_js_pages', ''); - return advagg_mod_match_path($pages, $visibility); -} - -/** - * Returns TRUE if this page should have critical CSS inlined. - * - * @return bool - * TRUE or FALSE. Default is FALSE. - */ -function advagg_mod_css_defer_page() { - $visibility = variable_get('advagg_mod_css_defer_visibility', ADVAGG_MOD_VISIBILITY_LISTED); - $pages = variable_get('advagg_mod_css_defer_pages', ''); - return advagg_mod_match_path($pages, $visibility); -} - /** * Transforms all JS files into inline JS. * @@ -3277,7 +1212,7 @@ function advagg_mod_inline_js(array &$js) { } $filename = $values['data']; if (file_exists($filename)) { - $contents = (string) @advagg_file_get_contents($filename); + $contents = file_get_contents($filename); } // Allow other modules to modify this files contents. // Call hook_advagg_get_js_file_contents_alter(). @@ -3294,21 +1229,15 @@ function advagg_mod_inline_js(array &$js) { * * @param array $css * CSS array. - * @param int $file_limit - * The number of files to inline; 0 means no limit. - * @param int $size_limit - * The number of bytes to inline; 0 means no limit. * * @see advagg_get_css_aggregate_contents() * @see drupal_build_css_cache() */ -function advagg_mod_inline_css(array &$css, $file_limit = 0, $size_limit = 0) { +function advagg_mod_inline_css(array &$css) { $aggregate_settings = advagg_current_hooks_hash_array(); $optimize = TRUE; module_load_include('inc', 'advagg', 'advagg'); - $count = 0; - $size = 0; foreach ($css as &$values) { // Only process files. if ($values['type'] !== 'file') { @@ -3317,9 +1246,6 @@ function advagg_mod_inline_css(array &$css, $file_limit = 0, $size_limit = 0) { $file = $values['data']; if (file_exists($file)) { - if (!empty($file_limit) && $count > $file_limit) { - break; - } $contents = advagg_load_css_stylesheet($file, $optimize, $aggregate_settings); // Allow other modules to modify this files contents. @@ -3334,14 +1260,8 @@ function advagg_mod_inline_css(array &$css, $file_limit = 0, $size_limit = 0) { $contents = preg_replace($regexp, '', $contents); $contents = implode('', $matches[0]) . $contents; - $size += strlen($contents); - if (!empty($size_limit) && $size > $size_limit) { - break; - } - $values['data'] = $contents; $values['type'] = 'inline'; - $count++; } } unset($values); @@ -3361,33 +1281,22 @@ function advagg_mod_inline_css(array &$css, $file_limit = 0, $size_limit = 0) { * @see block_block_list_alter() */ function advagg_mod_match_path($pages, $visibility) { - // Default to not matching. - $page_match = FALSE; // Limited visibility blocks must list at least one page. - if (empty($pages) && $visibility <= ADVAGG_MOD_VISIBILITY_PHP) { - if ($visibility == ADVAGG_MOD_VISIBILITY_NOTLISTED) { - $page_match = TRUE; - } - } - else { - // Match on php. - if ($visibility == ADVAGG_MOD_VISIBILITY_PHP) { - if (module_exists('php')) { - $page_match = php_eval($pages); - } - } - // Match the given $pages. - elseif ($visibility < ADVAGG_MOD_VISIBILITY_PHP) { - $current_path = current_path(); - // Convert path to lowercase. This allows comparison of the same path - // with different case. Ex: /Page, /page, /PAGE. - $pages = drupal_strtolower($pages); + if ($visibility == ADVAGG_MOD_VISIBILITY_LISTED && empty($pages)) { + $page_match = FALSE; + } + elseif ($pages) { + // Match path if necessary. + // Convert path to lowercase. This allows comparison of the same path + // with different case. Ex: /Page, /page, /PAGE. + $pages = drupal_strtolower($pages); + if ($visibility < ADVAGG_MOD_VISIBILITY_PHP) { // Convert the Drupal path to lowercase. - $path = drupal_strtolower(drupal_get_path_alias($current_path)); + $path = drupal_strtolower(drupal_get_path_alias($_GET['q'])); // Compare the lowercase internal and lowercase path alias (if any). $page_match = drupal_match_path($path, $pages); - if ($path != $current_path) { - $page_match = $page_match || drupal_match_path($current_path, $pages); + if ($path != $_GET['q']) { + $page_match = $page_match || drupal_match_path($_GET['q'], $pages); } // When $visibility has a value of 0 (ADVAGG_MOD_VISIBILITY_NOTLISTED), // the block is displayed on all pages except those listed in $pages. @@ -3395,6 +1304,15 @@ function advagg_mod_match_path($pages, $visibility) { // those pages listed in $block->pages. $page_match = !($visibility xor $page_match); } + elseif (module_exists('php')) { + $page_match = php_eval($pages); + } + else { + $page_match = FALSE; + } + } + else { + $page_match = TRUE; } return $page_match; @@ -3416,7 +1334,8 @@ function advagg_mod_js_contains_jquery_drupal($filename, $type = '') { if ($type === 'inline') { $contents = $filename; } - elseif ($type === 'external' + // @ignore sniffer_whitespace_openbracketspacing_openingwhitespace:1 + elseif ( $type === 'external' || strpos($filename, 'http://') === 0 || strpos($filename, 'https://') === 0 || strpos($filename, '//') === 0 @@ -3427,7 +1346,7 @@ function advagg_mod_js_contains_jquery_drupal($filename, $type = '') { } } elseif (file_exists($filename)) { - $contents = (string) @advagg_file_get_contents($filename); + $contents = file_get_contents($filename); } } @@ -3469,49 +1388,27 @@ function advagg_mod_js_contains_jquery_drupal($filename, $type = '') { */ function advagg_mod_ga_inline_to_file(array &$js) { // Do nothing if the googleanalytics module is not enabled. - if (!module_exists('googleanalytics') - || !is_callable('googleanalytics_api') - || !is_callable('_googleanalytics_cache') - ) { + if (!module_exists('googleanalytics')) { return; } // Get inline GA js and put it inside of an aggregrate. $ga_script = ''; $debug = variable_get('googleanalytics_debug', 0); - $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; - } - } + $library_tracker_url = '//www.google-analytics.com/' . ($debug ? 'analytics_debug.js' : 'analytics.js'); + $library_cache_url = 'http:' . $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)) { foreach ($js as $key => $value) { @@ -3519,41 +1416,22 @@ function advagg_mod_ga_inline_to_file(array &$js) { if ($value['type'] !== 'inline') { 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; + // 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; } + // 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; } } } @@ -3564,7 +1442,12 @@ function advagg_mod_ga_inline_to_file(array &$js) { * @param array $js * JS array. */ -function advagg_mod_remove_js_if_not_used(array &$js) { +function advagg_remove_js_if_not_used(array &$js) { + // Do not run the following code if drupal_add_js_page_defaults exists. + if (function_exists('drupal_add_js_page_defaults')) { + return; + } + $files_skiplist = array( 'drupal.js', 'jquery.js', @@ -3583,23 +1466,23 @@ function advagg_mod_remove_js_if_not_used(array &$js) { $include_drupal = FALSE; module_load_include('inc', 'advagg', 'advagg'); - // Look at each JavaScript entry and get the info on it. + // Look at each JavaScript entry & get the info on it. $files_info_filenames = array(); foreach ($js as &$values) { - if ($values['type'] !== 'file' || !is_string($values['data'])) { - continue; - } - foreach ($files_skiplist as $skip_name) { - if (strlen($skip_name) < strlen($values['data']) && substr_compare($values['data'], $skip_name, -strlen($skip_name), strlen($skip_name)) === 0) { - continue 2; + if ($values['type'] === 'file' || $values['type'] === 'external') { + foreach ($files_skiplist as $skip_name) { + if (substr_compare($values['data'], $skip_name, -strlen($skip_name), strlen($skip_name)) === 0) { + continue 2; + } } + $files_info_filenames[] = $values['data']; + } - $files_info_filenames[] = $values['data']; } unset($values); $files_info = advagg_get_info_on_files($files_info_filenames); - // Look at each JavaScript entry and see if it uses jquery or drupal. + // Look at each JavaScript entry & see if it uses jquery or drupal. foreach ($js as $name => &$values) { if ($values['type'] === 'file' || $values['type'] === 'external') { foreach ($files_skiplist as $skip_name) { @@ -3615,21 +1498,15 @@ function advagg_mod_remove_js_if_not_used(array &$js) { } } } - // Get advagg_mod info if not set; skip external files. - if (!isset($files_info[$name]['advagg_mod']) && $values['type'] !== 'external') { + // Get advagg_mod info if not set. + if (!isset($files_info[$name]['advagg_mod'])) { $files_info[$name]['advagg_mod'] = advagg_mod_js_contains_jquery_drupal($values['data'], $values['type']); } - - // See what needs to be included. - if (!empty($files_info[$name]['advagg_mod']['contents']['drupal'])) { + if ($files_info[$name]['advagg_mod']['contents']['drupal']) { $include_jquery = TRUE; $include_drupal = TRUE; - break; - } - elseif (!empty($files_info[$name]['advagg_mod']['contents']['jquery'])) { - $include_jquery = TRUE; } - elseif (isset($values['requires_jquery']) && !empty($values['requires_jquery'])) { + elseif ($files_info[$name]['advagg_mod']['contents']['jquery']) { $include_jquery = TRUE; } } @@ -3669,189 +1546,62 @@ function advagg_mod_remove_js_if_not_used(array &$js) { } } +// @ignore sniffer_commenting_functioncomment_hookreturndoc:12 +// @ignore sniffer_commenting_functioncomment_hookparamdoc:8 /** - * Given html, do some processing on the script tags included inside it. - * - * @param string $html - * String containing html markup. - */ -function advagg_mod_js_inline_processor(&$html) { - // Add src script to dns-prefetch. - if (variable_get('advagg_mod_js_inline_resource_hints', ADVAGG_MOD_JS_INLINE_RESOURCE_HINTS)) { - advagg_mod_xpath_script_external_dns($html); - } - - // Setting is enabled. - // All JS is in the footer. - // OR All JS is defer. - // OR All JS is async. - if (variable_get('advagg_mod_js_footer_inline_alter', ADVAGG_MOD_JS_FOOTER_INLINE_ALTER) - && (variable_get('advagg_mod_js_footer', ADVAGG_MOD_JS_FOOTER) >= 1 - || variable_get('advagg_mod_js_defer', ADVAGG_MOD_JS_DEFER) - || variable_get('advagg_mod_js_async', ADVAGG_MOD_JS_ASYNC) - )) { - $pattern = '/<script(?![^<>]*src=)(?:(?![^<>]*type=)|(?=[^<>]*type="?[^<>"]*javascript))(.*?)>(.*?)<\/script>/smix'; - $callback = 'advagg_mod_wrap_inline_js'; - // Wrap inline JS with a check so that it only runs once Drupal.settings & - // jQuery are not undefined. - if (variable_get('advagg_mod_wrap_inline_js_xpath', ADVAGG_MOD_WRAP_INLINE_JS_XPATH)) { - $html = advagg_mod_xpath_script_wrapper($html); - } - else { - $html = preg_replace_callback($pattern, $callback, $html); - } - } -} - -/** - * Runs on shutdown to clean up and display developer information. + * Implements hook_magic(). * - * 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. + * @param array $magic_settings + * The renderable form array of the magic module theme settings. READ ONLY. + * @param string $theme + * The theme that the settings will be editing. * - * This function is registered by devel_shutdown() as a shutdown function. + * @return array + * The array of settings within the magic module theme page. Must not contain + * anything from the $magic_settings array. */ -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) && is_object($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); - } +function advagg_mod_magic(array $magic_settings, $theme) { + $settings = array(); - 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; - } + // If possible disable access and set default to false. + if (!isset($magic_settings['css']['magic_embedded_mqs']['#access'])) { + $settings['css']['magic_embedded_mqs']['#access'] = FALSE; } -} - -/** - * 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 { - // @codingStandardsIgnoreLine - $output = '</div>' . dprint_r($queries, TRUE); - } - return $output; + if (!isset($magic_settings['css']['magic_embedded_mqs']['#default_value'])) { + $settings['css']['magic_embedded_mqs']['#default_value'] = FALSE; } -} - -/** - * 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); + if (!isset($magic_settings['js']['magic_footer_js']['#access'])) { + $settings['js']['magic_footer_js']['#access'] = FALSE; } - - $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 (!isset($magic_settings['js']['magic_footer_js']['#default_value'])) { + $settings['js']['magic_footer_js']['#default_value'] = FALSE; + } + if (!isset($magic_settings['js']['magic_library_head']['#access'])) { + $settings['js']['magic_library_head']['#access'] = FALSE; } - if (version_compare(PHP_VERSION, '5.4.0', '>=')) { - $options |= JSON_PRETTY_PRINT; + if (!isset($magic_settings['js']['magic_library_head']['#default_value'])) { + $settings['js']['magic_library_head']['#default_value'] = FALSE; } - - // 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--; + if (!isset($magic_settings['js']['magic_experimental_js']['#access'])) { + $settings['js']['magic_experimental_js']['#access'] = FALSE; } - - 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); + if (!isset($magic_settings['js']['magic_experimental_js']['#default_value'])) { + $settings['js']['magic_experimental_js']['#default_value'] = FALSE; } - $options = array( - 'type' => 'inline', - ); - $options += drupal_js_defaults($inline); - $scripts_array = array( - '#type' => 'scripts', - '#items' => array($options), - ); - $scripts = drupal_render($scripts_array); - print $scripts; + // Add in our own validate function so we can preprocess variables before + // they are saved. + $settings['#validate'] = array('advagg_mod_magic_form_validate'); + return $settings; +} + +/** + * Form validation handler. Disable certain magic settings before being saved. + */ +function advagg_mod_magic_form_validate($form, &$form_state) { + // Disable magic functionality if it is a duplicate of AdvAgg. + $form_state['values']['magic_embedded_mqs'] = 0; + $form_state['values']['magic_footer_js'] = 0; + $form_state['values']['magic_library_head'] = 0; + $form_state['values']['magic_experimental_js'] = 0; } 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 45cb22b7fd..56ed03451b 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 @@ -3,16 +3,13 @@ * Used to load CSS via JS so css doesn't block the browser. */ -/* eslint-disable no-unused-vars */ - /** * 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) { - 'use strict'; if (document.createStyleSheet) { document.createStyleSheet(src); } diff --git a/sites/all/modules/contrib/advagg/advagg_mod/cssrelpreload.js b/sites/all/modules/contrib/advagg/advagg_mod/cssrelpreload.js deleted file mode 100644 index ee140f73df..0000000000 --- a/sites/all/modules/contrib/advagg/advagg_mod/cssrelpreload.js +++ /dev/null @@ -1,112 +0,0 @@ -/** - * @file - * Used to load CSS via JS so css doesn't block the browser. - */ - -/* @codingStandardsIgnoreFile */ -/* eslint-disable */ - -/*! loadCSS. [c]2017 Filament Group, Inc. MIT License */ -/* This file is meant as a standalone workflow for -- testing support for link[rel=preload] -- enabling async CSS loading in browsers that do not support rel=preload -- applying rel preload css once loaded, whether supported or not. -*/ -(function( w ){ - "use strict"; - // rel=preload support test - if( !w.loadCSS ){ - w.loadCSS = function(){}; - } - // define on the loadCSS obj - var rp = loadCSS.relpreload = {}; - // rel=preload feature support test - // runs once and returns a function for compat purposes - rp.support = (function(){ - var ret; - try { - ret = w.document.createElement( "link" ).relList.supports( "preload" ); - } catch (e) { - ret = false; - } - return function(){ - return ret; - }; - })(); - - // if preload isn't supported, get an asynchronous load by using a non-matching media attribute - // then change that media back to its intended value on load - rp.bindMediaToggle = function( link ){ - // remember existing media attr for ultimate state, or default to 'all' - var finalMedia = link.media || "all"; - - function enableStylesheet(){ - link.media = finalMedia; - } - - // bind load handlers to enable media - if( link.addEventListener ){ - link.addEventListener( "load", enableStylesheet ); - } else if( link.attachEvent ){ - link.attachEvent( "onload", enableStylesheet ); - } - - // Set rel and non-applicable media type to start an async request - // note: timeout allows this to happen async to let rendering continue in IE - setTimeout(function(){ - link.rel = "stylesheet"; - link.media = "only x"; - }); - // also enable media after 3 seconds, - // which will catch very old browsers (android 2.x, old firefox) that don't support onload on link - setTimeout( enableStylesheet, 3000 ); - }; - - // loop through link elements in DOM - rp.poly = function(){ - // double check this to prevent external calls from running - if( rp.support() ){ - return; - } - var links = w.document.getElementsByTagName( "link" ); - for( var i = 0; i < links.length; i++ ){ - var link = links[ i ]; - // qualify links to those with rel=preload and as=style attrs - if( link.rel === "preload" && link.getAttribute( "as" ) === "style" && !link.getAttribute( "data-loadcss" ) ){ - // prevent rerunning on link - link.setAttribute( "data-loadcss", true ); - // bind listeners to toggle media back - rp.bindMediaToggle( link ); - } - } - }; - - // if unsupported, run the polyfill - if( !rp.support() ){ - // run once at least - rp.poly(); - - // rerun poly on an interval until onload - var run = w.setInterval( rp.poly, 500 ); - if( w.addEventListener ){ - w.addEventListener( "load", function(){ - rp.poly(); - w.clearInterval( run ); - } ); - } else if( w.attachEvent ){ - w.attachEvent( "onload", function(){ - rp.poly(); - w.clearInterval( run ); - } ); - } - } - - - // commonjs - if( typeof exports !== "undefined" ){ - exports.loadCSS = loadCSS; - } - else { - w.loadCSS = loadCSS; - } -}( typeof global !== "undefined" ? global : 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 deleted file mode 100644 index 928ff479a7..0000000000 --- a/sites/all/modules/contrib/advagg/advagg_mod/cssrelpreload.min.js +++ /dev/null @@ -1,3 +0,0 @@ -/*eslint-disable */ -/*! loadCSS rel=preload polyfill. [c]2017 Filament Group, Inc. MIT License */ -!function(t){"use strict";t.loadCSS||(t.loadCSS=function(){});var e=loadCSS.relpreload={};if(e.support=function(){var e;try{e=t.document.createElement("link").relList.supports("preload")}catch(t){e=!1}return function(){return e}}(),e.bindMediaToggle=function(t){function e(){t.media=a}var a=t.media||"all";t.addEventListener?t.addEventListener("load",e):t.attachEvent&&t.attachEvent("onload",e),setTimeout(function(){t.rel="stylesheet",t.media="only x"}),setTimeout(e,3e3)},e.poly=function(){if(!e.support())for(var a=t.document.getElementsByTagName("link"),n=0;n<a.length;n++){var o=a[n];"preload"!==o.rel||"style"!==o.getAttribute("as")||o.getAttribute("data-loadcss")||(o.setAttribute("data-loadcss",!0),e.bindMediaToggle(o))}},!e.support()){e.poly();var a=t.setInterval(e.poly,500);t.addEventListener?t.addEventListener("load",function(){e.poly(),t.clearInterval(a)}):t.attachEvent&&t.attachEvent("onload",function(){e.poly(),t.clearInterval(a)})}"undefined"!=typeof exports?exports.loadCSS=loadCSS:t.loadCSS=loadCSS}("undefined"!=typeof global?global:this); \ No newline at end of file diff --git a/sites/all/modules/contrib/advagg/advagg_mod/loadCSS.js b/sites/all/modules/contrib/advagg/advagg_mod/loadCSS.js index d00b0afdfe..9d547b765e 100644 --- a/sites/all/modules/contrib/advagg/advagg_mod/loadCSS.js +++ b/sites/all/modules/contrib/advagg/advagg_mod/loadCSS.js @@ -3,89 +3,54 @@ * Used to load CSS via JS so css doesn't block the browser. */ -/* @codingStandardsIgnoreFile */ -/* eslint-disable */ - -/*! loadCSS. [c]2017 Filament Group, Inc. MIT License */ -(function(w){ +/*! +loadCSS: load a CSS file asynchronously. +[c]2014 @scottjehl, Filament Group, Inc. +Licensed MIT +*/ +function loadCSS( href, before, media, callback ){ "use strict"; - /* exported loadCSS */ - var loadCSS = function( href, before, media, crossorigin ){ - // Arguments explained: - // `href` [REQUIRED] is the URL for your CSS file. - // `before` [OPTIONAL] is the element the script should use as a reference for injecting our stylesheet <link> before - // By default, loadCSS attempts to inject the link after the last stylesheet or script in the DOM. However, you might desire a more specific location in your document. - // `media` [OPTIONAL] is the media type or query of the stylesheet. By default it will be 'all' - var doc = w.document; - var ss = doc.createElement( "link" ); - var ref; - if( before ){ - ref = before; - } - else { - var refs = ( doc.body || doc.getElementsByTagName( "head" )[ 0 ] ).childNodes; - ref = refs[ refs.length - 1]; - } - - var sheets = doc.styleSheets; - ss.rel = "stylesheet"; - ss.href = href; - if( crossorigin ){ - ss.setAttribute("crossorigin", crossorigin); - } - - // temporarily set media to something inapplicable to ensure it'll fetch without blocking render - ss.media = "only x"; + // Arguments explained: + // `href` is the URL for your CSS file. + // `before` optionally defines the element we'll use as a reference for injecting our <link> + // By default, `before` uses the first <script> element in the page. + // However, since the order in which stylesheets are referenced matters, you might need a more specific location in your document. + // If so, pass a different reference element to the `before` argument and it'll insert before that instead + // note: `insertBefore` is used instead of `appendChild`, for safety re: http://www.paulirish.com/2011/surefire-dom-element-insertion/ + var ss = window.document.createElement( "link" ); + var ref = before || window.document.getElementsByTagName( "script" )[ 0 ]; + var sheets = window.document.styleSheets; + ss.rel = "stylesheet"; + ss.href = href; + // temporarily, set media to something non-matching to ensure it'll fetch without blocking render + ss.media = "only x"; + // DEPRECATED + if( callback ) { + ss.onload = callback; + } - // wait until body is defined before injecting link. This ensures a non-blocking load in IE11. - function ready( cb ){ - if( doc.body ){ - return cb(); + // inject link + ref.parentNode.insertBefore( ss, ref ); + // This function sets the link's media back to `all` so that the stylesheet applies once it loads + // It is designed to poll until document.styleSheets includes the new sheet. + ss.onloadcssdefined = function( cb ){ + var defined; + for( var i = 0; i < sheets.length; i++ ){ + if( sheets[ i ].href && sheets[ i ].href.indexOf( href ) > -1 ){ + defined = true; } - setTimeout(function(){ - ready( cb ); - }); } - // Inject link - // Note: the ternary preserves the existing behavior of "before" argument, but we could choose to change the argument to "after" in a later release and standardize on ref.nextSibling for all refs - // Note: `insertBefore` is used instead of `appendChild`, for safety re: http://www.paulirish.com/2011/surefire-dom-element-insertion/ - ready( function(){ - ref.parentNode.insertBefore( ss, ( before ? ref : ref.nextSibling ) ); - }); - // A method (exposed on return object for external use) that mimics onload by polling document.styleSheets until it includes the new sheet. - var onloadcssdefined = function( cb ){ - var resolvedHref = ss.href; - var i = sheets.length; - while( i-- ){ - if( sheets[ i ].href === resolvedHref ){ - return cb(); - } - } + if( defined ){ + cb(); + } + else { setTimeout(function() { - onloadcssdefined( cb ); + ss.onloadcssdefined( cb ); }); - }; - - function loadCB(){ - if( ss.addEventListener ){ - ss.removeEventListener( "load", loadCB ); - } - ss.media = media || "all"; } - - // once loaded, set link's media back to `all` so that the stylesheet applies once it loads - if( ss.addEventListener ){ - ss.addEventListener( "load", loadCB); - } - ss.onloadcssdefined = onloadcssdefined; - onloadcssdefined( loadCB ); - return ss; }; - // commonjs - if( typeof exports !== "undefined" ){ - exports.loadCSS = loadCSS; - } - else { - w.loadCSS = loadCSS; - } -}( typeof global !== "undefined" ? global : this )); + ss.onloadcssdefined(function() { + ss.media = media || "all"; + }); + return ss; +} diff --git a/sites/all/modules/contrib/advagg/advagg_mod/loadCSS.min.js b/sites/all/modules/contrib/advagg/advagg_mod/loadCSS.min.js index f4cd4f07c1..3ebe99eca3 100644 --- a/sites/all/modules/contrib/advagg/advagg_mod/loadCSS.min.js +++ b/sites/all/modules/contrib/advagg/advagg_mod/loadCSS.min.js @@ -1,3 +1,2 @@ -/*eslint-disable */ -/*! loadCSS. [c]2017 Filament Group, Inc. MIT License */ -!function(a){"use strict";var b=function(b,c,d,e){function k(a){return f.body?a():void setTimeout(function(){k(a)})}function m(){g.addEventListener&&g.removeEventListener("load",m),g.media=d||"all"}var h,f=a.document,g=f.createElement("link");if(c)h=c;else{var i=(f.body||f.getElementsByTagName("head")[0]).childNodes;h=i[i.length-1]}var j=f.styleSheets;g.rel="stylesheet",g.href=b,e&&g.setAttribute("crossorigin",e),g.media="only x",k(function(){h.parentNode.insertBefore(g,c?h:h.nextSibling)});var l=function(a){for(var b=g.href,c=j.length;c--;)if(j[c].href===b)return a();setTimeout(function(){l(a)})};return g.addEventListener&&g.addEventListener("load",m),g.onloadcssdefined=l,l(m),g};"undefined"!=typeof exports?exports.loadCSS=b:a.loadCSS=b}("undefined"!=typeof global?global:this); \ No newline at end of file +//[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<g.length;d++)g[d].href&&g[d].href.indexOf(a)>-1&&(c=!0);c?b():setTimeout(function(){e.onloadcssdefined(b)})},e.onloadcssdefined(function(){e.media=c||"all"}),e} \ No newline at end of file diff --git a/sites/all/modules/contrib/advagg/advagg_mod/onloadCSS.js b/sites/all/modules/contrib/advagg/advagg_mod/onloadCSS.js deleted file mode 100644 index c51dc0f2aa..0000000000 --- a/sites/all/modules/contrib/advagg/advagg_mod/onloadCSS.js +++ /dev/null @@ -1,37 +0,0 @@ -/** - * @file - * Used to run js after the CSS has been loaded. - */ - -/* @codingStandardsIgnoreFile */ -/* eslint-disable */ - -/*! onloadCSS. (onload callback for loadCSS) [c]2017 Filament Group, Inc. MIT License */ -/* global navigator */ -/* exported onloadCSS */ -function onloadCSS( ss, callback ) { - var called; - function newcb(){ - if( !called && callback ){ - called = true; - callback.call( ss ); - } - } - if( ss.addEventListener ){ - ss.addEventListener( "load", newcb ); - } - if( ss.attachEvent ){ - ss.attachEvent( "onload", newcb ); - } - - // This code is for browsers that don’t support onload - // No support for onload (it'll bind but never fire): - // * Android 4.3 (Samsung Galaxy S4, Browserstack) - // * Android 4.2 Browser (Samsung Galaxy SIII Mini GT-I8200L) - // * Android 2.3 (Pantech Burst P9070) - - // Weak inference targets Android < 4.4 - if( "isApplicationInstalled" in navigator && "onloadcssdefined" in ss ) { - ss.onloadcssdefined( newcb ); - } -} diff --git a/sites/all/modules/contrib/advagg/advagg_mod/onloadCSS.min.js b/sites/all/modules/contrib/advagg/advagg_mod/onloadCSS.min.js deleted file mode 100644 index ca2751b603..0000000000 --- a/sites/all/modules/contrib/advagg/advagg_mod/onloadCSS.min.js +++ /dev/null @@ -1,3 +0,0 @@ -/*eslint-disable */ -/*! onloadCSS. (onload callback for loadCSS) [c]2017 Filament Group, Inc. MIT License */ -function onloadCSS(n,a){function t(){!d&&a&&(d=!0,a.call(n))}var d;n.addEventListener&&n.addEventListener("load",t),n.attachEvent&&n.attachEvent("onload",t),"isApplicationInstalled"in navigator&&"onloadcssdefined"in n&&n.onloadcssdefined(t)} \ No newline at end of file diff --git a/sites/all/modules/contrib/advagg/advagg_relocate/advagg_relocate.admin.inc b/sites/all/modules/contrib/advagg/advagg_relocate/advagg_relocate.admin.inc deleted file mode 100644 index b7ddf711d0..0000000000 --- a/sites/all/modules/contrib/advagg/advagg_relocate/advagg_relocate.admin.inc +++ /dev/null @@ -1,532 +0,0 @@ -<?php - -/** - * @file - * Admin page callbacks for the advagg relocate module. - */ - -/** - * Form builder; Configure advagg settings. - * - * @ingroup advagg_forms - * - * @see system_settings_form() - */ -function advagg_relocate_admin_settings_form() { - drupal_set_title(t('AdvAgg: Relocate')); - advagg_display_message_if_requirements_not_met(); - - $form = array(); - $times[0] = 'No Limit'; - // @codingStandardsIgnoreStart - $times += drupal_map_assoc(array( - 60 * 60 * 12, // 12 hours. - 60 * 60 * 24, // 1 day. - 60 * 60 * 24 * 4, // 4 days. - 60 * 60 * 24 * 7, // 1 week. - 60 * 60 * 24 * 7 * 2, // 2 weeks. - 60 * 60 * 24 * 7 * 3, // 3 weeks. - 60 * 60 * 24 * 30, // 1 month. - ), 'format_interval'); - // @codingStandardsIgnoreEnd - - $options = array( - 0 => t('Use default (safe) settings'), - 2 => t('Use recommended (optimized) settings'), - 4 => t('Use customized settings'), - ); - $form['advagg_relocate_admin_mode'] = array( - '#type' => 'radios', - '#title' => t('AdvAgg Settings'), - '#default_value' => variable_get('advagg_relocate_admin_mode', ADVAGG_RELOCATE_ADMIN_MODE), - '#options' => $options, - '#description' => t("Default settings will mirror core as closely as possible. <br>Recommended settings are optimized for speed."), - ); - $form['global_container'] = array( - '#type' => 'container', - '#hidden' => TRUE, - '#states' => array( - 'visible' => array( - ':input[name="advagg_relocate_admin_mode"]' => array('value' => '4'), - ), - ), - ); - - $form['global_container']['js'] = array( - '#type' => 'fieldset', - '#title' => t('JS Settings'), - ); - $form['global_container']['js']['advagg_relocate_js'] = array( - '#type' => 'checkbox', - '#title' => t('Move external JS files to a local JS file'), - '#default_value' => variable_get('advagg_relocate_js', ADVAGG_RELOCATE_JS), - '#description' => t('External JS will be moved to a JS file that will be included in the aggregated JS output.'), - '#recommended_value' => TRUE, - ); - $form['global_container']['js']['advagg_relocate_js_ttl'] = array( - '#type' => 'select', - '#title' => t('Only cache external JavaScript files if the browser cache TTL is under this amount.'), - '#options' => $times, - '#default_value' => variable_get('advagg_relocate_js_ttl', ADVAGG_RELOCATE_JS_TTL), - '#description' => t('As an example the Minimum needed to not get penalized by Google PageSpeed Insights is <a href="@url">1 week</a>.', array('@url' => 'https://developers.google.com/speed/docs/insights/LeverageBrowserCaching')), - ); - $form['global_container']['js']['advagg_relocate_js_ga_local'] = array( - '#type' => 'checkbox', - '#title' => t('Move inline google analytics inline analytics.js loader code to drupal_add_js'), - '#default_value' => variable_get('advagg_relocate_js_ga_local', ADVAGG_RELOCATE_JS_GA_LOCAL), - '#description' => t('If the inline loader code is included in the drupal_add_js array it can then be loaded locally if the above checkbox is checked.'), - '#recommended_value' => TRUE, - '#states' => array( - 'invisible' => array( - '#edit-advagg-relocate-js' => array('checked' => FALSE), - ), - ), - ); - $form['global_container']['js']['advagg_relocate_js_piwik_local'] = array( - '#type' => 'checkbox', - '#title' => t('Move inline piwik.js loader code to drupal_add_js'), - '#default_value' => variable_get('advagg_relocate_js_piwik_local', ADVAGG_RELOCATE_JS_PIWIK_LOCAL), - '#description' => t('If the inline loader code is included in the drupal_add_js array it can then be loaded locally if the above checkbox is checked.'), - '#recommended_value' => TRUE, - '#states' => array( - 'invisible' => array( - '#edit-advagg-relocate-js' => array('checked' => FALSE), - ), - ), - ); - $form['global_container']['js']['advagg_relocate_js_gtm_local'] = array( - '#type' => 'checkbox', - '#title' => t('Move inline google tag manager inline gtm.js loader code to drupal_add_js'), - '#default_value' => variable_get('advagg_relocate_js_gtm_local', ADVAGG_RELOCATE_JS_GTM_LOCAL), - '#description' => t('If the inline loader code is included in the drupal_add_js array it can then be loaded locally if the above checkbox is checked.'), - '#recommended_value' => TRUE, - '#states' => array( - 'invisible' => array( - '#edit-advagg-relocate-js' => array('checked' => FALSE), - ), - ), - ); - $form['global_container']['js']['advagg_relocate_js_perfectaudience_local'] = array( - '#type' => 'checkbox', - '#title' => t('Move inline perfectaudience loader code to drupal_add_js'), - '#default_value' => variable_get('advagg_relocate_js_perfectaudience_local', ADVAGG_RELOCATE_JS_PERFECTAUDIENCE_LOCAL), - '#description' => t('If the inline loader code is included in the drupal_add_js array it can then be loaded locally if the above checkbox is checked.'), - '#recommended_value' => TRUE, - '#states' => array( - 'invisible' => array( - '#edit-advagg-relocate-js' => array('checked' => FALSE), - ), - ), - ); - $form['global_container']['js']['advagg_relocate_js_twitter_uwt_local'] = array( - '#type' => 'checkbox', - '#title' => t('Move inline twitter loader code to drupal_add_js'), - '#default_value' => variable_get('advagg_relocate_js_twitter_uwt_local', ADVAGG_RELOCATE_JS_TWITTER_UWT_LOCAL), - '#description' => t('If the inline loader code is included in the drupal_add_js array it can then be loaded locally if the above checkbox is checked.'), - '#recommended_value' => TRUE, - '#states' => array( - 'invisible' => array( - '#edit-advagg-relocate-js' => array('checked' => FALSE), - ), - ), - ); - $form['global_container']['js']['advagg_relocate_js_linkedin_insight_local'] = array( - '#type' => 'checkbox', - '#title' => t('Move inline linkedin insight loader code to drupal_add_js'), - '#default_value' => variable_get('advagg_relocate_js_linkedin_insight_local', ADVAGG_RELOCATE_JS_LINKEDIN_INSIGHT_LOCAL), - '#description' => t('If the inline loader code is included in the drupal_add_js array it can then be loaded locally if the above checkbox is checked.'), - '#recommended_value' => TRUE, - '#states' => array( - 'invisible' => array( - '#edit-advagg-relocate-js' => array('checked' => FALSE), - ), - ), - ); - $form['global_container']['js']['advagg_relocate_js_fbds_local'] = array( - '#type' => 'checkbox', - '#title' => t('Move inline fbds.js loader code to drupal_add_js'), - '#default_value' => variable_get('advagg_relocate_js_fbds_local', ADVAGG_RELOCATE_JS_FBDS_LOCAL), - '#description' => t('If the inline loader code is included in the drupal_add_js array it can then be loaded locally if the above checkbox is checked.'), - '#recommended_value' => TRUE, - '#states' => array( - 'invisible' => array( - '#edit-advagg-relocate-js' => array('checked' => FALSE), - ), - ), - ); - $form['global_container']['js']['advagg_relocate_js_fbevents_local'] = array( - '#type' => 'checkbox', - '#title' => t('Move inline fbevents.js loader code to drupal_add_js'), - '#default_value' => variable_get('advagg_relocate_js_fbevents_local', ADVAGG_RELOCATE_JS_FBEVENTS_LOCAL), - '#description' => t('If the inline loader code is included in the drupal_add_js array it can then be loaded locally if the above checkbox is checked.'), - '#recommended_value' => TRUE, - '#states' => array( - 'invisible' => array( - '#edit-advagg-relocate-js' => array('checked' => FALSE), - ), - ), - ); - $form['global_container']['js']['advagg_relocate_js_fbevents_local_ids'] = array( - '#type' => 'textarea', - '#title' => t('List of Facebook Pixel IDs that will be used.'), - '#default_value' => variable_get('advagg_relocate_js_fbevents_local_ids', ADVAGG_RELOCATE_JS_FBEVENTS_LOCAL_IDS), - '#description' => t('New line separated.'), - '#states' => array( - 'invisible' => array( - '#edit-advagg-relocate-js-fbevents-local' => array('checked' => FALSE), - ), - ), - ); - $advagg_relocate_js_files_blacklist = variable_get('advagg_relocate_js_files_blacklist', ADVAGG_RELOCATE_JS_FILES_BLACKLIST); - $form['global_container']['js']['advagg_relocate_js_files_blacklist'] = array( - '#type' => 'textarea', - '#title' => t('External JS files blacklist'), - '#default_value' => $advagg_relocate_js_files_blacklist, - '#description' => t('New line separated; hostname, path, and filename. Example: <br><code>ajax.googleapis.com/ajax/libs/jquery/1.2.6/jquery.min.js</code><br><code>ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js</code>'), - '#states' => array( - 'invisible' => array( - '#edit-advagg-relocate-js' => array('checked' => FALSE), - ), - ), - ); - $advagg_relocate_js_domains_blacklist = variable_get('advagg_relocate_js_domains_blacklist', ADVAGG_RELOCATE_JS_DOMAINS_BLACKLIST); - $form['global_container']['js']['advagg_relocate_js_domains_blacklist'] = array( - '#type' => 'textarea', - '#title' => t('External JS domains blacklist'), - '#default_value' => $advagg_relocate_js_domains_blacklist, - '#description' => t('New line separated; hostname only. Example:<br><code>code.jquery.com</code><br><code>maxcdn.bootstrapcdn.com</code>'), - '#states' => array( - 'invisible' => array( - '#edit-advagg-relocate-js' => array('checked' => FALSE), - ), - ), - ); - - $form['global_container']['css'] = array( - '#type' => 'fieldset', - '#title' => t('CSS Settings'), - ); - $form['global_container']['css']['advagg_relocate_css'] = array( - '#type' => 'checkbox', - '#title' => t('Move external CSS files to a local CSS file'), - '#default_value' => variable_get('advagg_relocate_css', ADVAGG_RELOCATE_CSS), - '#description' => t('External CSS will be moved to a CSS file that will be included in the aggregated CSS output.'), - '#recommended_value' => TRUE, - ); - $form['global_container']['css']['advagg_relocate_css_ttl'] = array( - '#type' => 'select', - '#title' => t('Only cache external CSS files if the browser cache TTL is under this amount.'), - '#options' => $times, - '#default_value' => variable_get('advagg_relocate_css_ttl', ADVAGG_RELOCATE_CSS_TTL), - '#description' => t('As an example the Minimum needed to not get penalized by Google PageSpeed Insights is <a href="@url">1 week</a>.', array('@url' => 'https://developers.google.com/speed/docs/insights/LeverageBrowserCaching')), - ); - $advagg_relocate_css_files_blacklist = variable_get('advagg_relocate_css_files_blacklist', ADVAGG_RELOCATE_CSS_FILES_BLACKLIST); - $form['global_container']['css']['advagg_relocate_css_files_blacklist'] = array( - '#type' => 'textarea', - '#title' => t('External CSS files blacklist'), - '#default_value' => $advagg_relocate_css_files_blacklist, - '#description' => t('New line separated; hostname, path, and filename. Example: <br><code>ajax.googleapis.com/ajax/libs/jqueryui/1.12.1/themes/smoothness/jquery-ui.css</code><br><code>ajax.googleapis.com/ajax/libs/jqueryui/1.9.2/themes/smoothness/jquery-ui.css</code>'), - '#states' => array( - 'invisible' => array( - '#edit-advagg-relocate-css' => array('checked' => FALSE), - ), - ), - ); - $advagg_relocate_css_domains_blacklist = variable_get('advagg_relocate_css_domains_blacklist', ADVAGG_RELOCATE_CSS_DOMAINS_BLACKLIST); - $form['global_container']['css']['advagg_relocate_css_domains_blacklist'] = array( - '#type' => 'textarea', - '#title' => t('External CSS domains blacklist'), - '#default_value' => $advagg_relocate_css_domains_blacklist, - '#description' => t('New line separated; hostname only. Example:<br><code>code.jquery.com</code><br><code>maxcdn.bootstrapcdn.com</code>'), - '#states' => array( - 'invisible' => array( - '#edit-advagg-relocate-css' => array('checked' => FALSE), - ), - ), - ); - - $form['global_container']['css']['fonts'] = array( - '#type' => 'fieldset', - '#title' => t('Font CSS Settings'), - ); - $form['global_container']['css']['fonts']['advagg_relocate_css_inline_import'] = array( - '#type' => 'checkbox', - '#title' => t('Move external CSS font files to inline CSS'), - '#default_value' => variable_get('advagg_relocate_css_inline_import', ADVAGG_RELOCATE_CSS_INLINE_IMPORT), - '#description' => t('External font CSS will be moved to inline style tags.'), - '#recommended_value' => TRUE, - ); - $form['global_container']['css']['fonts']['advagg_relocate_css_inline_external'] = array( - '#type' => 'checkbox', - '#title' => t('Inline @import CSS font files in local .css files'), - '#default_value' => variable_get('advagg_relocate_css_inline_external', ADVAGG_RELOCATE_CSS_INLINE_EXTERNAL), - '#description' => t('External font CSS will be put in the aggregated CSS output.'), - '#recommended_value' => TRUE, - ); - - $options = array( - 'woff2' => t('<a href="@url">woff2</a> - Edge 14+, FF 39+, Chrome 36+, Opera 23+, iOS 10.1+, Android 5+', array('@url' => 'http://caniuse.com/#feat=woff2')), - 'woff' => t('<a href="@url">woff</a> - IE 9+, FF 3.6+, Chrome 5+, Safari 5.1+, Opera 11.5+, iOS 5.1+, Android 4.4+', array('@url' => 'http://caniuse.com/#feat=woff')), - 'ttf' => t('<a href="@url">ttf</a> - Older Safari, Android, iOS.', array('@url' => 'http://caniuse.com/#feat=ttf')), - 'eot' => t('<a href="@url">eot</a> - IE6, IE7, IE8 and IE9 in compatibility mode.', array('@url' => 'http://caniuse.com/#feat=eot')), - 'svg' => t('<a href="@url">svg</a> - Legacy iOS (4.0 and lower).', array('@url' => 'http://caniuse.com/#feat=svg')), - ); - $defaults = array( - 'woff2' => 'woff2', - 'woff' => 'woff', - 'ttf' => 'ttf', - 'eot' => 0, - 'svg' => 0, - ); - $form['global_container']['css']['fonts']['advagg_relocate_css_inline_import_browsers'] = array( - '#type' => 'checkboxes', - '#title' => t('Browser support for fonts'), - '#options' => $options, - '#default_value' => variable_get('advagg_relocate_css_inline_import_browsers', $defaults), - '#description' => t('<a href="@url">Read more</a>.', array('@url' => 'https://css-tricks.com/snippets/css/using-font-face/')), - '#states' => array( - 'invisible' => array( - '#edit-advagg-relocate-css-inline-import' => array('checked' => FALSE), - '#edit-advagg-relocate-css-inline-external' => array('checked' => FALSE), - ), - ), - ); - $form['global_container']['css']['fonts']['per_file_settings'] = array( - '#type' => 'fieldset', - '#title' => t('What import statements should be inlined?'), - '#states' => array( - 'invisible' => array( - '#edit-advagg-relocate-css-inline-import' => array('checked' => FALSE), - '#edit-advagg-relocate-css-inline-external' => array('checked' => FALSE), - ), - ), - ); - $advagg_relocate_css_font_domains = variable_get('advagg_relocate_css_font_domains', ADVAGG_RELOCATE_CSS_FONT_DOMAINS); - $form['global_container']['css']['fonts']['per_file_settings']['advagg_relocate_css_font_domains'] = array( - '#type' => 'textarea', - '#title' => t('What external domains are supported'), - '#default_value' => $advagg_relocate_css_font_domains, - '#description' => t('Currently Only fonts.googleapis.com has been tested. Please <a href="@url">open an issue</a> if you want to inline import statements for other domains.', array('@url' => 'https://www.drupal.org/project/issues/advagg')), - '#disabled' => TRUE, - ); - - $advagg_relocate_css_file_settings = variable_get('advagg_relocate_css_file_settings', array()); - $files_with_import = advagg_relocate_admin_list_files_with_import(); - - if (!empty($files_with_import)) { - $domains = explode("\n", $advagg_relocate_css_font_domains); - foreach ($files_with_import as $filename => $references) { - // Convert filename to form api friendly name. - $form_api_filename = str_replace( - array('/', '.', '=', '&', ' '), - array('__', '--', '_', '-', '-'), - $filename - ); - - // Settings for the all in filename checkbox. - $default_value = TRUE; - if (isset($advagg_relocate_css_file_settings['all:' . $form_api_filename])) { - $default_value = $advagg_relocate_css_file_settings['all:' . $form_api_filename]; - } - $form['global_container']['css']['fonts']['per_file_settings']['advagg_relocate_css_file_settings_all_' . $form_api_filename] = array( - '#type' => 'checkbox', - '#default_value' => $default_value, - '#recommended_value' => TRUE, - '#title' => t('All in @filename', array('@filename' => $filename)), - ); - $form_api_html_filename = str_replace(array('__', '--', '_'), '-', $form_api_filename); - - // Fieldset for the checkboxes. - $form['global_container']['css']['fonts']['per_file_settings'][$form_api_filename] = array( - '#type' => 'fieldset', - '#title' => t('@filename', array('@filename' => $filename)), - '#states' => array( - 'disabled' => array( - '#edit-advagg-relocate-css-file-settings-all-' . $form_api_html_filename => array('checked' => TRUE), - ), - ), - ); - - // Build options and the default enabled settings. - $default_values = array(); - $options = array(); - foreach ($references as $key => $value) { - $include = FALSE; - foreach ($domains as $domain) { - if (strpos($value, $domain) !== FALSE) { - $include = TRUE; - break; - } - } - if ($include) { - $options[$key] = $value; - $default_values[$key] = $key; - } - } - - // Apply saved settings to the checkboxes. - if (isset($advagg_relocate_css_file_settings[$form_api_filename]) - && is_array($advagg_relocate_css_file_settings[$form_api_filename]) - ) { - $default_values = $advagg_relocate_css_file_settings[$form_api_filename]; - } - $form['global_container']['css']['fonts']['per_file_settings'][$form_api_filename]['advagg_relocate_css_file_settings_' . $form_api_filename] = array( - '#type' => 'checkboxes', - '#options' => $options, - '#default_value' => $default_values, - ); - - // No viable options for this file; remove fieldset and checkboxes. - if (empty($options)) { - unset($form['global_container']['css']['fonts']['per_file_settings'][$form_api_filename]); - unset($form['global_container']['css']['fonts']['per_file_settings']['advagg_relocate_css_file_settings_all_' . $form_api_filename]); - } - } - } - - // Clear the cache bins on submit. - $form['#submit'][] = 'advagg_relocate_admin_settings_form_submit'; - - return system_settings_form($form); -} - -/** - * Submit callback, clear out the advagg cache bin. - * - * Also process the advagg_relocate_css_file_settings variable. - * - * @ingroup advagg_forms_callback - */ -function advagg_relocate_admin_settings_form_submit($form, &$form_state) { - // Work around PHP bug with $_POST not containing all the data. - $alt_post = array(); - $input = rawurldecode(file_get_contents('php://input')); - parse_str($input, $alt_post); - $alt_post = drupal_json_decode(str_replace(' ', '+', drupal_json_encode($alt_post))); - $diff = advagg_diff_multi($_POST, $alt_post); - if (!empty($diff)) { - foreach ($diff as $k => $v) { - if (isset($form_state['values'][$k])) { - $form_state['values'][$k] = $v; - } - } - } - - // Reset this form to defaults or recommended values; also show what changed. - advagg_set_admin_form_defaults_recommended($form_state, 'advagg_relocate_admin_mode'); - - // Get settings that start with advagg_relocate_css_file_settings. - if (defined('PHP_VERSION_ID') && PHP_VERSION_ID >= 50600) { - $vars = array_filter($form_state['values'], '_advagg_relocate_admin_is_css_file_settings', ARRAY_FILTER_USE_KEY); - } - else { - $vars = array_intersect_key($form_state['values'], array_flip(array_filter(array_keys($form_state['values']), '_advagg_relocate_admin_is_css_file_settings'))); - } - - // Unset found values. - foreach ($vars as $key => $value) { - if (isset($form_state['values'][$key])) { - unset($form_state['values'][$key]); - } - // If all is set, remove sub options for that file. - if (strpos($key, 'advagg_relocate_css_file_settings_all_') === 0 && !empty($value)) { - $sub_key = str_replace('advagg_relocate_css_file_settings_all_', 'advagg_relocate_css_file_settings_', $key); - if (isset($vars[$sub_key])) { - unset($vars[$sub_key]); - } - } - } - - // Save under one variable. - $saved_values = array(); - foreach ($vars as $key => $value) { - $key = str_replace('advagg_relocate_css_file_settings_all_', 'all:', $key); - $key = str_replace('advagg_relocate_css_file_settings_', '', $key); - $saved_values[$key] = $value; - } - $form_state['values']['advagg_relocate_css_file_settings'] = $saved_values; - - // Clear caches. - advagg_cache_clear_admin_submit(); -} - -/** - * See if the key starts with 'advagg_relocate_css_file_settings'. - * - * @param string $key - * They array key as a string. - * - * @return bool - * TRUE if the input string starts with 'advagg_relocate_css_file_settings'. - */ -function _advagg_relocate_admin_is_css_file_settings($key) { - return strpos($key, 'advagg_relocate_css_file_settings') === 0; -} - -/** - * Check all CSS files, see if any contains an @import that is external. - * - * @param array $files - * An array of filenames to check. - * - * @return array - * An array of filenames that contain an @import that is external. - */ -function advagg_relocate_admin_list_files_with_import(array $files = array()) { - if (empty($files)) { - // Get filename. - $results = db_select('advagg_files', 'af') - ->fields('af', array('filename')) - ->condition('filetype', 'css') - ->orderBy('filename', 'ASC') - ->execute(); - while ($row = $results->fetchAssoc()) { - $files[] = $row['filename']; - } - } - $files_with_import_statements = array(); - if (empty($files)) { - return $files_with_import_statements; - } - - module_load_include('inc', 'advagg', 'advagg'); - foreach ($files as $file) { - if (!file_exists($file)) { - continue; - } - // Get the file contents. - $file_contents = advagg_load_css_stylesheet($file, FALSE); - if (strpos($file_contents, '@import') !== FALSE) { - $matches = array(); - preg_match_all('%@import\s*+(?:url\(\s*+)?+[\'"]?+((?:http:\/\/|https:\/\/|\/\/)(?:[^\'"()\s]++))[\'"]?+\s*+\)?+\s*+;%i', $file_contents, $matches); - if (!empty($matches[0])) { - $output = array(); - foreach ($matches[1] as $k => $v) { - $v = str_replace(array('=', '&', ' '), array('_', '-', '-'), $v); - $output[$v] = $matches[0][$k]; - } - - $files_with_import_statements[$file] = $output; - } - } - } - return $files_with_import_statements; -} - -/** - * Add a cache clear button to the advagg operations form. - */ -function advagg_relocate_admin_form_advagg_admin_operations_form(&$form, &$form_state) { - $form['drastic_measures']['flush_relocate_cache'] = array( - '#type' => 'fieldset', - '#collapsible' => TRUE, - '#collapsed' => TRUE, - '#title' => t('Clear the advagg relocate cache'), - '#description' => t('Rarely needed but useful if a download was corrupted.'), - ); - $form['drastic_measures']['flush_relocate_cache']['advagg_flush'] = array( - '#type' => 'submit', - '#value' => t('Flush AdvAgg Relocate Cache'), - '#submit' => array('advagg_relocate_flush_cache_button'), - ); -} diff --git a/sites/all/modules/contrib/advagg/advagg_relocate/advagg_relocate.advagg.inc b/sites/all/modules/contrib/advagg/advagg_relocate/advagg_relocate.advagg.inc deleted file mode 100644 index 37ee2b76eb..0000000000 --- a/sites/all/modules/contrib/advagg/advagg_relocate/advagg_relocate.advagg.inc +++ /dev/null @@ -1,1104 +0,0 @@ -<?php - -/** - * @file - * Advanced aggregation relocate module. - */ - -/** - * @addtogroup advagg_hooks - * @{ - */ - -/** - * Implements hook_advagg_get_info_on_files_alter(). - */ -function advagg_relocate_advagg_get_info_on_files_alter(&$return, $cached_data, $bypass_cache) { - $aggregate_settings = advagg_current_hooks_hash_array(); - // Check external js setting. - if (empty($aggregate_settings['variables']['advagg_relocate_js'])) { - return; - } - foreach ($return as $key => &$info) { - // Skip if not a js file. - if (empty($info['fileext']) || $info['fileext'] !== 'js') { - continue; - } - // Get the file contents. - $file_contents = (string) @advagg_file_get_contents($info['data']); - if (empty($file_contents)) { - continue; - } - $value['data'] = $file_contents; - $scripts_found = advagg_relocate_js_script_rewrite_list($key, $value, $aggregate_settings); - - if (!empty($scripts_found)) { - $info['advagg_relocate'] = $scripts_found; - } - } -} - -/** - * Implements hook_advagg_get_js_file_contents_alter(). - */ -function advagg_relocate_advagg_get_js_file_contents_alter(&$contents, $filename, $aggregate_settings) { - // Do nothing if this is disabled. - if (empty($aggregate_settings['variables']['advagg_relocate_js'])) { - return; - } - - module_load_include('inc', 'advagg', 'advagg'); - // Get info on file. - $info = advagg_get_info_on_file($filename); - if (!empty($info['advagg_relocate'])) { - // Set values so it thinks its an inline script. - $value['data'] = &$contents; - $key = $filename; - $scripts_found = advagg_relocate_js_script_rewrite_list($key, $value, $aggregate_settings); - - // Do rewrite of the data. - if (!empty($scripts_found)) { - $js[$key] = &$value; - advagg_relocate_js_script_rewrite($js, $scripts_found); - } - } -} - -/** - * Implements hook_advagg_get_css_aggregate_contents_alter(). - */ -function advagg_relocate_advagg_get_css_aggregate_contents_alter(&$data, $files, $aggregate_settings) { - // Set variables if needed. - if (!isset($aggregate_settings['variables']['advagg_relocate_css_inline_import'])) { - $aggregate_settings['variables']['advagg_relocate_css_inline_import'] = variable_get('advagg_relocate_css_inline_import', ADVAGG_RELOCATE_CSS_INLINE_IMPORT); - } - - // Do nothing if this is disabled. - if (empty($aggregate_settings['variables']['advagg_relocate_css_inline_import'])) { - return; - } - - if (strpos($data, '@import') !== FALSE) { - // Set values that will be used when preg_replace_callback is ran. - _advagg_relocate_callback(array(), $files, $aggregate_settings); - - // Replace external import statements with the contents of them. - $data = preg_replace_callback('%@import\s*+(?:url\(\s*+)?+[\'"]?+((?:http:\/\/|https:\/\/|\/\/)(?:[^\'"()\s]++))[\'"]?+\s*+\)?+\s*+;%i', '_advagg_relocate_callback', $data); - - // Per the W3C specification at - // http://www.w3.org/TR/REC-CSS2/cascade.html#at-import, @import rules - // must proceed any other style, so we move those to the top. - $matches = array(); - $regexp = '/@import[^;]+;/i'; - preg_match_all($regexp, $data, $matches); - $data = preg_replace($regexp, '', $data); - // Add import statements to the top of the stylesheet. - $data = implode("\n", $matches[0]) . $data; - } -} - -/** - * Implements hook_advagg_relocate_process_http_request_alter(). - */ -function advagg_relocate_advagg_relocate_process_http_request_alter(&$response, $type) { - if ($type !== 'js' - || strpos($response->url, 'connect.facebook.net/en_US/fbevents.js') === FALSE - ) { - return 1; - } - - // Fix loader so it works if not loaded from connect.facebook.net. - $base_fb_url = 'https://connect.facebook.net'; - $matches = array(); - $pattern = '/function\s+([\w]{1,2})\(\)\s*\{\s*var\s+([\w]{1,2})\s*=\s*null,\s*([\w]{1,2})\s*=\s*null,\s*([\w]{1,2})\s*=\s*([\w]{1,2})\.getElementsByTagName\([\'"]script[\'"]\)/'; - preg_match($pattern, $response->data, $matches); - // Bail out if not matched. - if (empty($matches[0])) { - return 2; - } - // Transform - // function B(){var E=null,F=null,G=b.getElementsByTagName("script"); - // to - // function B(){var E="https://connect.facebook.net",G=b.getElementsByTagName("script"),F=G[0]. - $response->data = str_replace($matches[0], "function {$matches[1]}(){var {$matches[2]}=\"$base_fb_url\",{$matches[4]}={$matches[5]}.getElementsByTagName(\"script\"),{$matches[3]}={$matches[4]}[0]", $response->data); - - // Get Facebook IDs. - $fb_ids = array_filter(array_map('trim', explode("\n", variable_get('advagg_relocate_js_fbevents_local_ids', ADVAGG_RELOCATE_JS_FBEVENTS_LOCAL_IDS)))); - if (empty($fb_ids)) { - return 3; - } - - // Get Facebook Version. - $matches = array(); - $pattern = '/fbq.version\s*=\s*[\'"]([\.\d]+)[\'"]/'; - preg_match($pattern, $response->data, $matches); - if (empty($matches[1])) { - return 4; - } - $version = $matches[1]; - - // Get Release Segment. - $segment = 'stable'; - $matches = array(); - $pattern = '/fbq._releaseSegment\s*=\s*[\'"](.+)[\'"]/'; - preg_match($pattern, $response->data, $matches); - if (!empty($matches[1])) { - $segment = $matches[1]; - } - - // Update local copies of the /signals/config/ js. - $js = array(); - foreach ($fb_ids as $fb_id) { - $url = "$base_fb_url/signals/config/$fb_id?v=$version&r=$segment"; - $js[$url]['data'] = $url; - $js[$url]['type'] = 'external'; - $js[$url]['#fbid'] = "config$fb_id"; - $url = "$base_fb_url/signals/plugins/$fb_id?v=$version&r=$segment"; - $js[$url]['data'] = $url; - $js[$url]['type'] = 'external'; - $js[$url]['#fbid'] = "plugins$fb_id"; - } - if (!empty($js)) { - advagg_relocate_js_post_alter($js, TRUE); - } - // Get a list of the local copies for this version. - $local_copies = array(); - foreach ($js as $values) { - if ($values['type'] === 'file') { - // Create an aggregate just for this file. - $values += drupal_js_defaults($values); - $elements = array($values); - $groups = advagg_group_js($elements); - _advagg_aggregate_js($groups); - if (isset($groups[0]['data'])) { - $local_copies[$values['#fbid']] = advagg_file_create_url($groups[0]['data']); - } - } - } - if (empty($local_copies)) { - return 5; - } - - // Add the local copies to the js file. - $local_copies = json_encode($local_copies); - $matches = array(); - $pattern = '/return\s*\{\s*baseURL:\s*([\w]{1,2}),\s*scriptElement:\s*([\w]{1,2})\s*\}/'; - preg_match($pattern, $response->data, $matches); - // Bail out if not matched. - if (empty($matches[0])) { - return 6; - } - // Transform - // return{baseURL:E,scriptElement:F} - // to - // return{baseURL:E,scriptElement:F,localCopies:ARRAY_OF_LOCAL_JS_FILES}. - $response->data = str_replace($matches[0], "return{baseURL:{$matches[1]},scriptElement:{$matches[2]},localCopies:$local_copies}", $response->data); - - // Change logic so it'll use the local copy if it exists. - $matches = array(); - $pattern = '/([\w]{1,2})\s*=\s*([\w]{1,2})\.baseURL;\s*var\s+([\w]{1,2})\s*=\s*([\w]{1,2})\s*\+\s*[\'"]\/signals\/config\/[\'"]\s*\+\s*([\w]{1,2})\s*\+\s*[\'"]\?v=[\'"]\s*\+\s*([\w]{1,2})\s*\+\s*[\'"]\&r=[\'"]\s*\+\s*([\w]{1,2}),\s*([\w]{1,2})\s*=\s*([\w]{1,2})\.createElement\([\'"]script[\'"]\);\s*[\w]{1,2}.src\s*=\s*[\w]{1,2};/'; - preg_match($pattern, $response->data, $matches); - // Bail out if not matched. - if (empty($matches[0])) { - return 7; - } - - // Transform - // 1 2 3 4 5 6 7 8 9 8 3 - // I=H.baseURL;var J=I+"/signals/config/"+E+"?v="+F+"&r="+G,K=b.createElement("script");K.src=J; - // to - // 1 2 3 4 5 6 7 8 9 2 2 5 3 2 5 8 3 - // I=H.baseURL;var J=I+"/signals/config/"+E+"?v="+F+"&r="+G,K=b.createElement("script");if(H.localCopies&&H.localCopies["config"+E]){J=H.localCopies["config"+E];}K.src=J;. - $response->data = str_replace($matches[0], - "{$matches[1]}={$matches[2]}.baseURL;var {$matches[3]}={$matches[4]}+\"/signals/config/\"+{$matches[5]}+\"?v=\"+{$matches[6]}+\"&r=\"+{$matches[7]},{$matches[8]}={$matches[9]}.createElement(\"script\");if({$matches[2]}.localCopies&&{$matches[2]}.localCopies[\"config\"+{$matches[5]}]){{$matches[3]}={$matches[2]}.localCopies[\"config\"+{$matches[5]}];}{$matches[8]}.src={$matches[3]};", - $response->data - ); - - // Change logic so it'll use the local copy if it exists. - $matches = array(); - $pattern = '/if\s*\(([\w]{1,2})\.baseURL\s*\&\&\s*([\w]{1,2})\.scriptElement\)\s*\{\s*var\s+([\w]{1,2})\s*\=\s*([\w]{1,2})\.baseURL\s*\+\s*[\'"]\/signals\/plugins\/[\'"]\s*\+\s*([\w]{1,2})\s*\+\s*[\'"]\.js\?v\=[\'"]\s*\+\s*([\w]{1,2})\.version;/'; - preg_match($pattern, $response->data, $matches); - // Bail out if not matched. - if (empty($matches[0])) { - return 8; - } - // Transform - // if(C.baseURL&&C.scriptElement){var D=C.baseURL+"/signals/plugins/"+A+".js?v="+g.version; - // to - // if(C.baseURL&&C.scriptElement){var D=C.baseURL+"/signals/plugins/"+A+".js?v="+g.version;if(C.localCopies&&C.localCopies["plugins"+A]){D=C.localCopies["plugins"+A];}. - $response->data = str_replace($matches[0], "if({$matches[1]}.baseURL&&{$matches[2]}.scriptElement){var {$matches[3]}={$matches[4]}.baseURL+\"/signals/plugins/\"+{$matches[5]}+\".js?v=\"+{$matches[6]}.version;if({$matches[1]}.localCopies&&{$matches[1]}.localCopies[\"plugins\"+{$matches[5]}]){{$matches[3]}={$matches[1]}.localCopies[\"plugins\"+{$matches[5]}];}", $response->data); -} - -/** - * @} End of "addtogroup advagg_hooks". - */ - -/** - * Gets external CSS files and puts the contents of it in the aggregate. - * - * @param array $matches - * Array of matched items from preg_replace_callback(). - * @param array $files - * List of files with the media type. - * @param array $aggregate_settings - * Array of settings. - * - * @return string - * Contents of the import statement. - */ -function _advagg_relocate_callback(array $matches = array(), array $files = array(), array $aggregate_settings = array()) { - // Store values for preg_replace_callback callback. - $_args = &drupal_static(__FUNCTION__, array()); - if (!empty($files)) { - $_args['files'] = $files; - } - if (!empty($aggregate_settings)) { - $_args['aggregate_settings'] = $aggregate_settings; - } - // Short circuit if no matches were passed in. - if (empty($matches)) { - return ''; - } - - // Bail if not matched. - if (empty($matches[1])) { - return $matches[0]; - } - - // Check URL. - if (!advagg_relocate_check_domain_of_font_url($matches[1], $_args['aggregate_settings'])) { - return $matches[0]; - } - - // Check per file settings. - if (!isset($_args['aggregate_settings']['variables']['advagg_relocate_css_file_settings'])) { - $_args['aggregate_settings']['variables']['advagg_relocate_css_file_settings'] = variable_get('advagg_relocate_css_file_settings', array()); - } - - $key_to_check = str_replace(array('=', '&', ' '), array('_', '-', '-'), $matches[1]); - - foreach ($_args['files'] as $filename => $values) { - $form_api_filename = str_replace( - array('/', '.', '=', '&', ' '), - array('__', '--', '_', '-', '+'), - $filename - ); - // All has been checked; good to go. - if (!empty($_args['aggregate_settings']['variables']['advagg_relocate_css_file_settings']["all:{$form_api_filename}"])) { - continue; - } - - // This file is good to be inlined. - if (!empty($_args['aggregate_settings']['variables']['advagg_relocate_css_file_settings'][$form_api_filename][$key_to_check])) { - continue; - } - - // No go, return unaltered. - return $matches[0]; - } - - $font_faces = advagg_relocate_get_remote_font_data($matches[1], $_args['aggregate_settings']); - return advagg_relocate_font_face_parser($font_faces); -} - -/** - * Given an array of font data output a new CSS string. - * - * @param array $font_faces - * Array of font data. - * - * @return string - * String of CSS font data. - */ -function advagg_relocate_font_face_parser(array $font_faces) { - $new_css = ''; - foreach ($font_faces as $values => $src) { - $output = ''; - $output .= str_replace('; ', ";\n", $values); - if (isset($src['eot'])) { - $output .= "src: {$src['eot']};\n"; - } - $output .= 'src:'; - foreach ($src as $key => $location) { - if (is_numeric($key)) { - $output .= "$location,"; - } - } - if (isset($src['eot'])) { - $src['eot'] = str_replace('.eot', '.eot?#iefix', $src['eot']); - $output .= "{$src['eot']} format('embedded-opentype'),"; - } - if (isset($src['woff2'])) { - $output .= "{$src['woff2']},"; - } - if (isset($src['woff'])) { - $output .= "{$src['woff']},"; - } - if (isset($src['ttf'])) { - $output .= "{$src['ttf']},"; - } - if (isset($src['svg'])) { - $output .= "{$src['svg']},"; - } - $output = str_replace(array('),l', '),u'), array("),\nl", "),\nu"), trim($output, ',') . ';'); - $new_css .= "@font-face {\n$output\n}\n"; - } - - return $new_css; -} - -/** - * Gets external CSS and JS files; caches and returns response. - * - * @param string $urls - * URLs to get. - * @param string $type - * Will be css or js. - * @param array $options - * Array of settings for the http request. - * @param bool $force_check - * TRUE if you want to force check the external source. - * - * @return array - * Array of http responses. - */ -function advagg_relocate_get_remote_data($urls, $type, array $options = array(), $force_check = FALSE) { - // Set arguments for drupal_http_request(). - $options += array( - 'headers' => array( - 'Accept-Encoding' => 'gzip, deflate', - 'Connection' => 'close', - 'Referer' => $GLOBALS['base_root'] . request_uri(), - ), - 'timeout' => 8, - 'version' => '1.0', - ); - if (function_exists('brotli_uncompress')) { - $options['headers']['Accept-Encoding'] .= ', br'; - } - - // Build CID. - $cids = array(); - foreach ($urls as $k => $v) { - $cids["advagg_relocate_{$type}_external:{$k}"] = "advagg_relocate_{$type}_external:{$k}"; - } - - // Try local cache. - $return = array(); - $responses = array(); - $cached_data = cache_get_multiple($cids, 'cache_advagg_info'); - $cached_data = array_merge($cids, $cached_data); - $url_to_cid = array(); - $request_sent = FALSE; - foreach ($cached_data as $cid => $cache) { - // CID not set, skip. - if (empty($cid)) { - continue; - } - // Set cid, filename and get url. - $options['cid'] = $cid; - $options['filename'] = substr($cid, 26 + strlen($type)); - - // Filename lookup failure, skip. - if (empty($urls[$options['filename']])) { - continue; - } - - // Add url to the lookup array. - $url = advagg_force_https_path($urls[$options['filename']]); - $url_to_cid[$url] = $cid; - - // Reset headers if needed. - if (isset($options['headers']['If-None-Match'])) { - unset($options['headers']['If-None-Match']); - } - if (isset($options['headers']['If-Modified-Since'])) { - unset($options['headers']['If-Modified-Since']); - } - - // Use cached data or setup for 304. - if (!empty($cache->data)) { - if ($cache->expire >= REQUEST_TIME - && isset($cache->data->url) - && empty($force_check) - ) { - $return[$cache->data->url] = $cache->data; - continue; - } - else { - // Set header for 304 response. - if (isset($cached_data->data->headers['etag'])) { - $options['headers']['If-None-Match'] = $cached_data->data->headers['etag']; - } - if (isset($cached_data->created)) { - $options['headers']['If-Modified-Since'] = gmdate('D, d M Y H:i:s T', $cached_data->created); - } - } - } - - // Get data. - if (module_exists('httprl')) { - $request_sent = TRUE; - httprl_request($url, $options); - } - else { - $request_sent = TRUE; - $responses[$url] = drupal_http_request($url, $options); - if (!isset($responses[$url]->options)) { - $responses[$url]->options = $options; - } - if (!isset($responses[$url]->url)) { - $responses[$url]->url = $url; - } - } - } - if ($request_sent && module_exists('httprl')) { - $responses = httprl_send_request(); - } - if (empty($responses)) { - return $return; - } - - // Try failures again. - advagg_relocate_try_failures_again($responses); - - // Process remote data. - foreach ($responses as $url => $response) { - // Content length does not match the response data. - if (!empty($response->headers['content-length']) - && $response->headers['content-length'] > strlen($response->data) - ) { - continue; - } - - // No url is a no go. - if (empty($response->url)) { - $response->url = $url; - } - if (isset($response->options['cid'])) { - $cid = $response->options['cid']; - } - elseif (isset($url_to_cid[$response->url])) { - $cid = $url_to_cid[$response->url]; - } - else { - // Can't match up url to the cid. - continue; - } - // Update object. - if (!isset($response->options['filename'])) { - $response->options['filename'] = substr($cid, 26 + strlen($type)); - } - if (!isset($response->options['cid'])) { - $response->options['cid'] = $cid; - } - - advagg_relocate_process_http_request($response, $type); - - if ($response->code == 304 && !empty($cached_data->data)) { - // Update cache expire time. - cache_set($cid, $cached_data->data, 'cache_advagg_info', REQUEST_TIME + $response->ttl); - - // Return cached data. - $return[$cached_data->data->url] = $cached_data->data; - } - - // Skip if not a 200. - if ($response->code != 200 - && $response->code != 201 - && $response->code != 202 - && $response->code != 206 - ) { - continue; - } - if (empty($response->data)) { - continue; - } - - $response->local_cache = FALSE; - // Save data to the cache. - if (!empty($response->data)) { - $response->hash = drupal_hash_base64($response->data); - $response->local_cache = TRUE; - cache_set($cid, $response, 'cache_advagg_info', REQUEST_TIME + $response->ttl); - $response->local_cache = FALSE; - } - $return[$response->url] = $response; - } - return $return; -} - -/** - * Get the TTL and fix UTF-8 encoding. - * - * @param object $response - * Response from http request. - * @param string $type - * Can be css, js, or font. - */ -function advagg_relocate_process_http_request(&$response, $type) { - // Get ttl from response. - $ttl = 0; - if ($type === 'css') { - $ttl = variable_get('advagg_relocate_css_min_ttl', ADVAGG_RELOCATE_CSS_MIN_TTL); - } - if ($type === 'js') { - $ttl = variable_get('advagg_relocate_js_min_ttl', ADVAGG_RELOCATE_JS_MIN_TTL); - } - - $now = REQUEST_TIME; - if (isset($response->headers['expires'])) { - $expires = strtotime($response->headers['expires']); - if (isset($response->headers['date'])) { - $now = max($now, strtotime($response->headers['date'])); - } - $ttl = max($ttl, $expires - $now); - } - if (isset($response->headers['cache-control'])) { - $cache_control_array = advagg_relocate_parse_cache_control($response->headers['cache-control']); - if (isset($cache_control_array['max-age']) && is_numeric($cache_control_array['max-age'])) { - $ttl = max($ttl, $cache_control_array['max-age']); - } - if (isset($cache_control_array['s-maxage']) && is_numeric($cache_control_array['s-maxage'])) { - $ttl = max($ttl, $cache_control_array['s-maxage']); - } - } - $response->ttl = $ttl; - - // If a BOM is found, convert the string to UTF-8. - if (isset($response->data)) { - $encoding = advagg_get_encoding_from_bom($response->data); - if (!empty($encoding)) { - $response->data = advagg_convert_to_utf8($response->data, $encoding); - } - } - - // Call hook_advagg_relocate_process_http_request_alter(). - drupal_alter('advagg_relocate_process_http_request', $response, $type); -} - -/** - * Decompress the data. - * - * @param object $response - * Response from http request. - * - * @return bool - * FALSE if something went wrong. - */ -function advagg_relocate_uncompress_data(&$response) { - // Uncompress. - if (!empty($response->headers['content-encoding']) - && !empty($response->data) - && (!isset($response->chunk_size) || (!empty($response->headers['content-length']) && $response->headers['content-length'] == strlen($response->data))) - && ($response->headers['content-encoding'] === 'gzip' - || $response->headers['content-encoding'] === 'deflate' - || $response->headers['content-encoding'] === 'br' - )) { - // Do the first level of decoding if not already done. - if ($response->headers['content-encoding'] === 'gzip') { - $chunk = @gzinflate(substr($response->data, 10)); - } - elseif ($response->headers['content-encoding'] === 'deflate') { - $chunk = @gzinflate($response->data); - } - elseif ($response->headers['content-encoding'] === 'br' && is_callable('brotli_uncompress')) { - $chunk = @brotli_uncompress($response->data); - } - if (isset($chunk)) { - if ($chunk !== FALSE) { - $response->data = $chunk; - } - else { - return FALSE; - } - } - } - - return TRUE; -} - -/** - * Detect failures and try again. - * - * @param array $responses - * An array of $response objects from a http request. - */ -function advagg_relocate_try_failures_again(array &$responses) { - // Try failures again. - foreach ($responses as $key => &$response) { - // Strlen doesn't match. - if (!empty($response->headers['content-length']) - && $response->headers['content-length'] > strlen($response->data) - ) { - $response->code = 0; - if (isset($response->options['headers']['connection'])) { - unset($response->options['headers']['connection']); - } - } - - // Decode data. - $decode_ok = advagg_relocate_uncompress_data($response); - if (!$decode_ok) { - // If decoding failed try again. - if (isset($response->options['headers']['Accept-Encoding'])) { - unset($response->options['headers']['Accept-Encoding']); - } - $response->code = 0; - } - - // Try again if code is empty. - if (empty($response->code)) { - $url = $response->url; - $options = $response->options; - $responses[$key] = drupal_http_request($url, $options); - if (!isset($responses[$key]->options)) { - $responses[$key]->options = $options; - } - if (!isset($responses[$key]->url)) { - $responses[$key]->url = $url; - } - } - } - - // Try failures again, but force http. - foreach ($responses as $key => $response) { - if (empty($response->code)) { - $url = advagg_force_http_path($response->url); - $options = $response->options; - $responses[$key] = drupal_http_request($url, $options); - if (!isset($responses[$key]->options)) { - $responses[$key]->options = $options; - } - if (!isset($responses[$key]->url)) { - $responses[$key]->url = $url; - } - } - } -} - -/** - * Gets external CSS files; caches it and returns css font rules. - * - * @param string $url - * URL of the CSS file to import. - * @param array $aggregate_settings - * Array of settings. - * - * @return array - * Array of font data. - */ -function advagg_relocate_get_remote_font_data($url, array $aggregate_settings) { - // Set default settings if needed. - $font_type_defaults = array( - 'woff2' => 'woff2', - 'woff' => 'woff', - 'ttf' => 'ttf', - ); - if (!isset($aggregate_settings['variables']['advagg_relocate_css_inline_import_browsers'])) { - $aggregate_settings['variables']['advagg_relocate_css_inline_import_browsers'] = variable_get('advagg_relocate_css_inline_import_browsers', $font_type_defaults); - } - // Make sure advagg_relocate_css_inline_import_browsers is an array. - if (!is_array($aggregate_settings['variables']['advagg_relocate_css_inline_import_browsers'])) { - $aggregate_settings['variables']['advagg_relocate_css_inline_import_browsers'] = array($aggregate_settings['variables']['advagg_relocate_css_inline_import_browsers'] => $aggregate_settings['variables']['advagg_relocate_css_inline_import_browsers']); - } - // Use defaults if no matches for known font types. - if (!isset($aggregate_settings['variables']['advagg_relocate_css_inline_import_browsers']['woff2']) - && !isset($aggregate_settings['variables']['advagg_relocate_css_inline_import_browsers']['woff']) - && !isset($aggregate_settings['variables']['advagg_relocate_css_inline_import_browsers']['ttf']) - && !isset($aggregate_settings['variables']['advagg_relocate_css_inline_import_browsers']['eot']) - && !isset($aggregate_settings['variables']['advagg_relocate_css_inline_import_browsers']['svg']) - ) { - $aggregate_settings['variables']['advagg_relocate_css_inline_import_browsers'] += $font_type_defaults; - } - - // Set arguments for drupal_http_request(). - $options = array( - 'headers' => array( - 'Accept-Encoding' => 'gzip, deflate', - 'Connection' => 'close', - 'Referer' => $GLOBALS['base_root'] . request_uri(), - ), - 'timeout' => 8, - 'version' => '1.0', - ); - if (function_exists('brotli_uncompress')) { - $options['headers']['Accept-Encoding'] .= ', br'; - } - - // If protocol relative, force https. - if (strpos($url, '//') === 0) { - $url = advagg_force_https_path($url); - } - - // Build CID. - $aggregate_settings['variables']['advagg_relocate_css_inline_import_browsers'] = array_filter($aggregate_settings['variables']['advagg_relocate_css_inline_import_browsers']); - $fonts = implode(',', $aggregate_settings['variables']['advagg_relocate_css_inline_import_browsers']); - $cid = "advagg_relocate_css_inline_import:$fonts:$url"; - - // Try local cache. - $cached_data = cache_get($cid, 'cache_advagg_info'); - if (!empty($cached_data->data[0])) { - if ($cached_data->expire >= REQUEST_TIME) { - return $cached_data->data[0]; - } - else { - // Set header for 304 response. - // $options['headers']['If-None-Match'] = $response->headers['etag'];. - $options['headers']['If-Modified-Since'] = gmdate('D, d M Y H:i:s T', $cached_data->created); - } - } - - // Get external data. - $responses = array(); - if (module_exists('httprl')) { - // Get ttf. - if (!empty($aggregate_settings['variables']['advagg_relocate_css_inline_import_browsers']['ttf'])) { - $options['#font-type'] = 'ttf'; - httprl_request($url . '#ttf', $options); - } - - // Get eot. - if (!empty($aggregate_settings['variables']['advagg_relocate_css_inline_import_browsers']['eot'])) { - $options['#font-type'] = 'eot'; - $options['headers']['User-Agent'] = 'Mozilla/5.0 (Windows; U; MSIE 7.0; Windows NT 6.0; en-US)'; - httprl_request($url . '#eot', $options); - } - - // Get svg. - if (!empty($aggregate_settings['variables']['advagg_relocate_css_inline_import_browsers']['svg'])) { - $options['#font-type'] = 'svg'; - $options['headers']['User-Agent'] = 'Mozilla/5.0 (iPad; U; CPU OS 3_2_2 like Mac OS X; nl-nl) AppleWebKit/531.21.10 (KHTML, like Gecko) Version/4.0.4 Mobile/7B500 Safari/531.21.10'; - httprl_request($url . '#svg', $options); - } - - // Get woff. - if (!empty($aggregate_settings['variables']['advagg_relocate_css_inline_import_browsers']['woff'])) { - $options['#font-type'] = 'woff'; - $options['headers']['User-Agent'] = 'Mozilla/5.0 (Windows; U; MSIE 9.0; Windows NT 9.0; en-US)'; - httprl_request($url . '#woff', $options); - } - - // Get woff2. - if (!empty($aggregate_settings['variables']['advagg_relocate_css_inline_import_browsers']['woff2'])) { - $options['#font-type'] = 'woff2'; - $options['headers']['User-Agent'] = 'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:40.0) Gecko/20100101 Firefox/40.1'; - httprl_request($url . '#woff2', $options); - } - - $responses = httprl_send_request(); - } - if (empty($responses)) { - // Get ttf. - if (!empty($aggregate_settings['variables']['advagg_relocate_css_inline_import_browsers']['ttf'])) { - $options['#font-type'] = 'ttf'; - $responses['ttf'] = drupal_http_request($url . '#ttf', $options); - if (!isset($responses['ttf']->options)) { - $responses['ttf']->options = $options; - } - if (!isset($responses[$url]->url)) { - $responses['ttf']->url = $url . '#ttf'; - } - } - - // Get eot. - if (!empty($aggregate_settings['variables']['advagg_relocate_css_inline_import_browsers']['eot'])) { - $options['#font-type'] = 'eot'; - $options['headers']['User-Agent'] = 'Mozilla/5.0 (Windows; U; MSIE 7.0; Windows NT 6.0; en-US)'; - $responses['eot'] = drupal_http_request($url . '#eot', $options); - if (!isset($responses['eot']->options)) { - $responses['eot']->options = $options; - } - if (!isset($responses[$url]->url)) { - $responses['eot']->url = $url . '#eot'; - } - } - - // Get svg. - if (!empty($aggregate_settings['variables']['advagg_relocate_css_inline_import_browsers']['svg'])) { - $options['#font-type'] = 'svg'; - $options['headers']['User-Agent'] = 'Mozilla/5.0 (iPad; U; CPU OS 3_2_2 like Mac OS X; nl-nl) AppleWebKit/531.21.10 (KHTML, like Gecko) Version/4.0.4 Mobile/7B500 Safari/531.21.10'; - $responses['svg'] = drupal_http_request($url . '#svg', $options); - if (!isset($responses['svg']->options)) { - $responses['svg']->options = $options; - } - if (!isset($responses[$url]->url)) { - $responses['svg']->url = $url . '#svg'; - } - } - - // Get woff. - if (!empty($aggregate_settings['variables']['advagg_relocate_css_inline_import_browsers']['woff'])) { - $options['#font-type'] = 'woff'; - $options['headers']['User-Agent'] = 'Mozilla/5.0 (Windows; U; MSIE 9.0; Windows NT 9.0; en-US)'; - $responses['woff'] = drupal_http_request($url . '#woff', $options); - if (!isset($responses['woff']->options)) { - $responses['woff']->options = $options; - } - if (!isset($responses[$url]->url)) { - $responses['woff']->url = $url . '#woff'; - } - } - - // Get woff2. - if (!empty($aggregate_settings['variables']['advagg_relocate_css_inline_import_browsers']['woff2'])) { - $options['#font-type'] = 'woff2'; - $options['headers']['User-Agent'] = 'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:40.0) Gecko/20100101 Firefox/40.1'; - $responses['woff2'] = drupal_http_request($url . '#woff2', $options); - if (!isset($responses['woff2']->options)) { - $responses['woff2']->options = $options; - } - if (!isset($responses[$url]->url)) { - $responses['woff2']->url = $url . '#woff2'; - } - } - } - - // Try failures again. - advagg_relocate_try_failures_again($responses); - - // Parse data. - $font_faces = array(); - $ttl = 0; - foreach ($responses as $key => $response) { - if ($response->code == 304 && !empty($cached_data->data[0])) { - // This might need to be better handled in the future. - return $cached_data->data[0]; - } - - // Set the font type if not set. - if (empty($response->options['#font-type'])) { - if (!is_numeric($key)) { - $response->options['#font-type'] = $key; - } - else { - continue; - } - } - - if ($response->code != 200 - && $response->code != 201 - && $response->code != 202 - && $response->code != 206 - ) { - return FALSE; - } - if (empty($response->data)) { - return FALSE; - } - - advagg_relocate_process_http_request($response, 'font'); - $ttl = max($ttl, $response->ttl); - - // Parse the CSS. - $font_face = advagg_relocate_parse_css_font_face( - $response->data, - array('font-family', 'font-style', 'font-weight', 'src'), - $response->options['#font-type'] - ); - - // Format into a better data structure and combine. - foreach ($font_face as $k => $values) { - if (!isset($font_faces[$k])) { - $font_faces[$k] = $font_face[$k]; - continue; - } - - foreach ($values as $index => $value) { - if (!in_array($value, $font_faces[$k])) { - if ($index === $response->options['#font-type']) { - $font_faces[$k][$index] = $values[$index]; - } - else { - $font_faces[$k][] = $values[$index]; - } - } - } - } - } - - // Save data to the cache. - if (!empty($font_faces)) { - cache_set($cid, array($font_faces, $responses), 'cache_advagg_info', REQUEST_TIME + $ttl); - } - return $font_faces; -} - -/** - * Parse the cache-control string into a key value array. - * - * @param string $cache_control - * The cache-control string. - * - * @return array - * Returns a key value array. - */ -function advagg_relocate_parse_cache_control($cache_control) { - $cache_control_array = explode(',', $cache_control); - $cache_control_array = array_map('trim', $cache_control_array); - $cache_control_parsed = array(); - foreach ($cache_control_array as $value) { - if (strpos($value, '=') !== FALSE) { - $temp = array(); - parse_str($value, $temp); - $cache_control_parsed += $temp; - } - else { - $cache_control_parsed[$value] = TRUE; - } - } - return $cache_control_parsed; -} - -/** - * Parse the font family string into a structured array. - * - * @param string $css_string - * The raw css string. - * @param array $properties - * The css properties to get. - * @param string $type - * The type of font file. - * - * @return array - * Returns a key value array. - */ -function advagg_relocate_parse_css_font_face($css_string, array $properties, $type) { - // Get the CSS that contains a font-family rule. - $length = strlen($css_string); - $property_position = 0; - $lower = strtolower($css_string); - - $attributes = array(); - foreach ($properties as $property) { - while (($property_position = strpos($lower, $property, $property_position)) !== FALSE) { - // Find the start of the values for the property. - $start_of_values = strpos($css_string, ':', $property_position); - // Get the property at this location of the css. - $property_in_loop = trim(substr($css_string, $property_position, ($start_of_values - $property_position))); - - // Make sure this property is one of the ones we're looking for. - if ($property_in_loop !== $property) { - $property_position += strlen($property); - continue; - } - - // Get position of 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 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 end of the declaration of the property. - $declaration_end_a = strpos($css_string, ';', $property_position); - $declaration_end_b = strpos($css_string, '}', $property_position); - if ($declaration_end_a === FALSE) { - $declaration_end = $declaration_end_b; - } - else { - $declaration_end = min($declaration_end_a, $declaration_end_b); - } - if ($declaration_end > $end) { - $declaration_end = $end; - } - // Add one in order to capture the } when we ge the full rule set. - $end++; - // Advance position for the next run of the while loop. - $property_position = $end; - - // Get values assigned to this property. - $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); - $values_array = array_map('trim', $values_array); - - foreach ($values_array as $key => $value) { - if (stripos($value, "'$type'") !== FALSE - || stripos($value, ".$type") !== FALSE - ) { - unset($values_array[$key]); - $values_array[$type] = $value; - } - } - $attributes[$property][] = $values_array; - } - } - - // Make sure src is the last one. - $temp = $attributes['src']; - unset($attributes['src']); - $attributes['src'] = $temp; - - // Parse attributes into an output array. - $temp = array(); - $output = array(); - foreach ($attributes as $property => $values) { - foreach ($values as $key => $value) { - if ($property !== 'src') { - - $value = implode(',', $value); - if (!isset($temp[$key])) { - $temp[$key] = ''; - } - $temp[$key] .= "$property: $value; "; - } - else { - $output[$temp[$key]] = $value; - } - } - } - - return $output; -} - -/** - * Parse the font family string into a structured array. - * - * @param string $filename - * The filename to save. - * @param string $data - * The data to save to the file. - * @param bool $local_cache - * TRUE if the data came from the cache bin. - * @param string $hash - * Contents hash; if different resave data. - * - * @return array - * Returns an array of errors that might have happened. - */ -function _advagg_relocate_save_remote_asset($filename, $data, $local_cache, $hash) { - // Save remote data. - $errors = array(); - $saved = FALSE; - $dir = variable_get('advagg_relocate_directory', ADVAGG_RELOCATE_DIRECTORY); - $full_filename = $dir . $filename; - $local_hash = ''; - if (!is_readable($full_filename)) { - $errors = advagg_save_data($full_filename, $data); - $saved = TRUE; - } - elseif (empty($local_cache)) { - $file_contents = @advagg_file_get_contents($full_filename); - if (!empty($file_contents)) { - $local_hash = drupal_hash_base64($file_contents); - } - if ($hash !== $local_hash) { - $errors = advagg_save_data($full_filename, $data, TRUE); - $saved = TRUE; - } - } - return array($full_filename, $errors, $saved); -} diff --git a/sites/all/modules/contrib/advagg/advagg_relocate/advagg_relocate.info b/sites/all/modules/contrib/advagg/advagg_relocate/advagg_relocate.info deleted file mode 100644 index 0fea8be883..0000000000 --- a/sites/all/modules/contrib/advagg/advagg_relocate/advagg_relocate.info +++ /dev/null @@ -1,13 +0,0 @@ -name = AdvAgg Relocate -description = Inline css import rules; also make any external file local. -package = Advanced CSS/JS Aggregation -core = 7.x -dependencies[] = advagg - -configure = admin/config/development/performance/advagg/relocate - -; Information added by Drupal.org packaging script on 2020-11-19 -version = "7.x-2.35" -core = "7.x" -project = "advagg" -datestamp = "1605792717" diff --git a/sites/all/modules/contrib/advagg/advagg_relocate/advagg_relocate.install b/sites/all/modules/contrib/advagg/advagg_relocate/advagg_relocate.install deleted file mode 100644 index 17e62ead8e..0000000000 --- a/sites/all/modules/contrib/advagg/advagg_relocate/advagg_relocate.install +++ /dev/null @@ -1,23 +0,0 @@ -<?php - -/** - * @file - * Handles AdvAgg Relocate installation and upgrade tasks. - */ - -/** - * @addtogroup hooks - * @{ - */ - -/** - * Implements hook_install(). - */ -function advagg_relocate_install() { - // New install gets a locked admin section. - variable_set('advagg_relocate_admin_mode', 0); -} - -/** - * @} End of "addtogroup hooks". - */ diff --git a/sites/all/modules/contrib/advagg/advagg_relocate/advagg_relocate.module b/sites/all/modules/contrib/advagg/advagg_relocate/advagg_relocate.module deleted file mode 100644 index f461f7c1e2..0000000000 --- a/sites/all/modules/contrib/advagg/advagg_relocate/advagg_relocate.module +++ /dev/null @@ -1,1350 +0,0 @@ -<?php - -/** - * @file - * Advanced aggregation relocate module. - */ - -/** - * @addtogroup default_variables - * @{ - */ - -/** - * Default value to see if JS is loaded locally. - */ -define('ADVAGG_RELOCATE_JS', FALSE); - -/** - * If the external file has a longer TTL then this value do not cache locally. - */ -define('ADVAGG_RELOCATE_JS_TTL', 604800); - -/** - * Minimum cache lifetime, 600 seconds by default. - */ -define('ADVAGG_RELOCATE_JS_MIN_TTL', 600); - -/** - * Default value to see if GA JS is loaded locally. - */ -define('ADVAGG_RELOCATE_JS_GA_LOCAL', FALSE); - -/** - * Default value to see if GTM JS is loaded locally. - */ -define('ADVAGG_RELOCATE_JS_GTM_LOCAL', FALSE); - -/** - * Default value to see if fbds JS is loaded locally. - */ -define('ADVAGG_RELOCATE_JS_FBDS_LOCAL', FALSE); - -/** - * Default value to see if fbevents JS is loaded locally. - */ -define('ADVAGG_RELOCATE_JS_FBEVENTS_LOCAL', FALSE); - -/** - * Default value to see if piwik JS is loaded locally. - */ -define('ADVAGG_RELOCATE_JS_PIWIK_LOCAL', FALSE); - -/** - * Default value to see if perfectaudience JS is loaded locally. - */ -define('ADVAGG_RELOCATE_JS_PERFECTAUDIENCE_LOCAL', FALSE); - -/** - * Default value to see if twitter uwt JS is loaded locally. - */ -define('ADVAGG_RELOCATE_JS_TWITTER_UWT_LOCAL', FALSE); - -/** - * Default value to see if twitter uwt JS is loaded locally. - */ -define('ADVAGG_RELOCATE_JS_LINKEDIN_INSIGHT_LOCAL', FALSE); - -/** - * Default value to see if CSS is loaded locally. - */ -define('ADVAGG_RELOCATE_CSS', FALSE); - -/** - * If the external file has a longer TTL then this value do not cache locally. - */ -define('ADVAGG_RELOCATE_CSS_TTL', 604800); - -/** - * Minimum cache lifetime, 600 seconds by default. - */ -define('ADVAGG_RELOCATE_CSS_MIN_TTL', 600); - -/** - * Default value to see if css inlining import is enabled. - */ -define('ADVAGG_RELOCATE_CSS_INLINE_IMPORT', FALSE); - -/** - * Default value to see if css inlining external css is enabled. - */ -define('ADVAGG_RELOCATE_CSS_INLINE_EXTERNAL', FALSE); - -/** - * Default value for blacklisted JS domains. - */ -define('ADVAGG_RELOCATE_JS_DOMAINS_BLACKLIST', "js.stripe.com\n"); - -/** - * Default value for blacklisted JS files. - */ -define('ADVAGG_RELOCATE_JS_FILES_BLACKLIST', ''); - -/** - * Default value for list of fbevents.js pixel ids. - */ -define('ADVAGG_RELOCATE_JS_FBEVENTS_LOCAL_IDS', ''); - -/** - * Default value for supported CSS font domains. - */ -define('ADVAGG_RELOCATE_CSS_FONT_DOMAINS', "fonts.googleapis.com"); - -/** - * Default value for blacklisted CSS domains. - */ -define('ADVAGG_RELOCATE_CSS_DOMAINS_BLACKLIST', ''); - -/** - * Default value for blacklisted CSS files. - */ -define('ADVAGG_RELOCATE_CSS_FILES_BLACKLIST', ''); - -/** - * Default value for where relocated files are kept. - */ -define('ADVAGG_RELOCATE_DIRECTORY', 'public://advagg_relocate/'); - -/** - * If 4 the admin section gets unlocked. - */ -define('ADVAGG_RELOCATE_ADMIN_MODE', 4); - -/** - * @} End of "addtogroup default_variables". - */ - -/** - * @addtogroup hooks - * @{ - */ - -/** - * Implements hook_module_implements_alter(). - */ -function advagg_relocate_module_implements_alter(&$implementations, $hook) { - // Move advagg_relocate to the top. - if ($hook === 'advagg_get_js_file_contents_alter' && array_key_exists('advagg_relocate', $implementations)) { - $item = array('advagg_relocate' => $implementations['advagg_relocate']); - unset($implementations['advagg_relocate']); - $implementations = array_merge($item, $implementations); - } -} - -/** - * Implements hook_menu(). - */ -function advagg_relocate_menu() { - $file_path = drupal_get_path('module', 'advagg_relocate'); - $config_path = advagg_admin_config_root_path(); - - $items[$config_path . '/advagg/relocate'] = array( - 'title' => 'Relocate', - 'description' => 'Move external items to be local.', - 'page callback' => 'drupal_get_form', - 'page arguments' => array('advagg_relocate_admin_settings_form'), - 'type' => MENU_LOCAL_TASK, - 'access arguments' => array('administer site configuration'), - 'file path' => $file_path, - 'file' => 'advagg_relocate.admin.inc', - 'weight' => 10, - ); - - return $items; -} - -/** - * Implements hook_css_alter(). - */ -function advagg_relocate_css_alter(&$css) { - if (!module_exists('advagg') || !advagg_enabled()) { - return; - } - - $aggregate_settings = advagg_current_hooks_hash_array(); - // Check external css setting. - if (empty($aggregate_settings['variables']['advagg_relocate_css_inline_external'])) { - return; - } - - // Handle fonts. - $replacements = array(); - foreach ($css as $key => &$values) { - if ($values['type'] !== 'external') { - continue; - } - if (!advagg_relocate_check_domain_of_font_url($key, $aggregate_settings)) { - continue; - } - - module_load_include('advagg.inc', 'advagg_relocate'); - $font_faces = advagg_relocate_get_remote_font_data($key, $aggregate_settings); - if (empty($font_faces)) { - continue; - } - $new_css = advagg_relocate_font_face_parser($font_faces); - $values['data'] = $new_css; - $values['type'] = 'inline'; - - // Add DNS information for font domains. - $parse = @parse_url($key); - if (strpos($parse['host'], 'fonts.googleapis.com') !== FALSE) { - // Add fonts.gstatic.com when fonts.googleapis.com is added. - $values['dns_prefetch'] = 'https://fonts.gstatic.com/#crossorigin'; - $values['preload'] = 'https://fonts.gstatic.com/#crossorigin'; - } - // Move this css to the top. - if (module_exists('advagg_mod') && $aggregate_settings['variables']['advagg_mod_css_adjust_sort_external']) { - $values['group'] = CSS_SYSTEM - 1; - $values['weight'] = -50000; - $values['movable'] = FALSE; - } - // Do not move this css to the bottom. - if (module_exists('advagg_mod') && $aggregate_settings['variables']['advagg_mod_css_adjust_sort_inline']) { - $values['movable'] = FALSE; - } - - $replacements[basename($key)] = $key; - } - if (!empty($replacements)) { - $css = advagg_relocate_key_rename($css, $replacements); - } -} - -/** - * Implements hook_cron(). - */ -function advagg_relocate_cron() { - // Get filenames in directory. - $dir = rtrim(variable_get('advagg_relocate_directory', ADVAGG_RELOCATE_DIRECTORY), '/'); - $files = file_scan_directory($dir, '/.*/'); - - // Get cached objects from filenames. - $cids = array(); - foreach ($files as $info) { - $ext = strtolower(pathinfo($info->filename, PATHINFO_EXTENSION)); - $cids["advagg_relocate_{$ext}_external:{$info->filename}"] = "advagg_relocate_{$ext}_external:{$info->filename}"; - } - $cached_data = cache_get_multiple($cids, 'cache_advagg_info'); - - // Build css and js arrays. - $css = array(); - $js = array(); - foreach ($cached_data as $values) { - $url = $values->data->url; - $ext = strtolower(pathinfo($url, PATHINFO_EXTENSION)); - if ($ext === "css") { - $css[$url]['data'] = $url; - $css[$url]['type'] = 'external'; - } - elseif ($ext === "js") { - $js[$url]['data'] = $url; - $js[$url]['type'] = 'external'; - } - elseif (!empty($values->headers['content-type']) && stripos($values->headers['content-type'], 'css')) { - $css[$url]['data'] = $url; - $css[$url]['type'] = 'external'; - } - elseif (!empty($values->headers['content-type']) && stripos($values->headers['content-type'], 'javascript')) { - $js[$url]['data'] = $url; - $js[$url]['type'] = 'external'; - } - } - - // Refresh cached data. - if (!empty($js)) { - advagg_relocate_js_post_alter($js, TRUE); - } - if (!empty($css)) { - advagg_relocate_css_post_alter($css, TRUE); - } -} - -/** - * Implements hook_form_FORM_ID_alter(). - * - * Give advice on how to temporarily disable css/js aggregation. - */ -function advagg_relocate_form_advagg_admin_operations_form_alter(&$form, &$form_state) { - module_load_include('admin.inc', 'advagg_relocate'); - advagg_relocate_admin_form_advagg_admin_operations_form($form, $form_state); -} - -/** - * @} End of "addtogroup hooks". - */ - -/** - * @addtogroup advagg_hooks - * @{ - */ - -/** - * Alter the css array. - * - * @param array $css - * CSS array. - * @param bool $force_check - * TRUE if you want to force check the external source. - */ -function advagg_relocate_css_post_alter(array &$css, $force_check = FALSE) { - if (!module_exists('advagg') || !advagg_enabled()) { - return; - } - - $aggregate_settings = advagg_current_hooks_hash_array(); - - // Check external css setting. - if (empty($aggregate_settings['variables']['advagg_relocate_css'])) { - return; - } - - // Get all external css files. - $urls = _advagg_relocate_get_urls($css, 'css', $aggregate_settings); - if (empty($urls)) { - return; - } - - // Make advagg_save_data() available. - module_load_include('inc', 'advagg', 'advagg.missing'); - module_load_include('advagg.inc', 'advagg_relocate'); - $responses = advagg_relocate_get_remote_data($urls, 'css', array(), $force_check); - - // Get s3fs no_rewrite_cssjs setting. - $s3fs_no_rewrite_cssjs = advagg_get_s3fs_config('no_rewrite_cssjs'); - - $filenames = array(); - $advagg_relocate_css_ttl = variable_get('advagg_relocate_css_ttl', ADVAGG_RELOCATE_CSS_TTL); - foreach ($responses as $value) { - if (!empty($advagg_relocate_css_ttl) && $value->ttl >= $advagg_relocate_css_ttl) { - continue; - } - - $rehash = FALSE; - // Handle @import statements. - if (strpos($value->data, '@import') !== FALSE) { - // Handle "local" import statements. - advagg_relocate_load_stylesheet_local(array(), dirname($value->url) . '/'); - $value->data = preg_replace_callback('%@import\s*+(?:url\(\s*+)?+[\'"]?+(?![a-z]++:|/)([^\'"()\s]++)[\'"]?+\s*+\)?+\s*+;%i', 'advagg_relocate_load_stylesheet_local', $value->data); - - // Replace external import statements with the contents of them. - $value->data = preg_replace_callback('%@import\s*+(?:url\(\s*+)?+[\'"]?+((?:http:\/\/|https:\/\/|\/\/)(?:[^\'"()\s]++))[\'"]?+\s*+\)?+\s*+;%i', 'advagg_relocate_load_stylesheet_external', $value->data); - $rehash = TRUE; - } - // Fix external url references. - if (strpos($value->data, 'url(') !== FALSE) { - module_load_include('inc', 'advagg', 'advagg'); - // Set anchor point for local url() statements in the css. - _advagg_build_css_path(array(), dirname($value->url) . '/'); - // Anchor all paths in the CSS with its base URL, ignoring external, - // absolute paths, and urls that start with # or %23 (SVG). - $value->data = preg_replace_callback('%url\(\s*+[\'"]?+(?![a-z]++:|/|\#|\%23+)([^\'"\)]++)[\'"]?+\s*+\)%i', '_advagg_build_css_path', $value->data); - $rehash = TRUE; - } - - if ($rehash) { - $value->hash = drupal_hash_base64($value->data); - } - - // Save remote data. - list($full_filename, $errors, $saved) = _advagg_relocate_save_remote_asset($value->options['filename'], $value->data, $value->local_cache, $value->hash); - if (!empty($errors)) { - continue; - } - - // Replace remote data with local data. - $relative_path = advagg_get_relative_path($full_filename); - $key = $urls[$value->options['filename']]; - $css = advagg_relocate_key_rename($css, array($relative_path => $key)); - $css[$relative_path]['pre_relocate_data'] = $css[$relative_path]['data']; - $css[$relative_path]['data'] = $relative_path; - $css[$relative_path]['type'] = 'file'; - if (defined('ADVAGG_MOD_CSS_PREPROCESS') && variable_get('advagg_mod_css_preprocess', ADVAGG_MOD_CSS_PREPROCESS) && empty($css[$relative_path]['preprocess_lock'])) { - $css[$relative_path]['preprocess'] = TRUE; - } - - // Handle domain prefectch. - $key_host = parse_url($key, PHP_URL_HOST); - if (!empty($css[$relative_path]['dns_prefetch'])) { - foreach ($css[$relative_path]['dns_prefetch'] as $key => $domain_name) { - if (strpos($domain_name, $key_host) !== FALSE && strpos($value->data, $key_host)) { - unset($css[$relative_path]['dns_prefetch'][$key]); - } - } - } - // List of files that need the cache cleared. - if ($saved) { - $filenames[] = (!is_null($s3fs_no_rewrite_cssjs) && empty($s3fs_no_rewrite_cssjs)) ? $full_filename : $relative_path; - } - } - if (!empty($filenames)) { - module_load_include('inc', 'advagg'); - module_load_include('cache.inc', 'advagg'); - $files = advagg_get_info_on_files($filenames, TRUE); - advagg_push_new_changes($files); - } -} - -/** - * Alter the js array. - * - * @param string $key - * Key that can be used to lookup the value from the js array. - * @param array $value - * Inner part of the js array. - * @param array $aggregate_settings - * Array of settings. - * - * @return array - * An array of inline scripts found and locations for them in the file. - */ -function advagg_relocate_js_script_rewrite_list($key, array $value, array $aggregate_settings) { - $scripts_found = array(); - // Handle analytics.js. - if (!empty($aggregate_settings['variables']['advagg_relocate_js_ga_local'])) { - $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()'); - $middle = strpos($value['data'], '})(window,document,"script",', $start); - $end = strpos($value['data'], ',"ga");ga("create",', $middle); - // Found the GA code. - if ($start !== FALSE && $middle !== FALSE && $end !== FALSE) { - $scripts_found['analytics.js'] = array($key, $start, $middle, $end); - } - } - - // Handle piwik.js. - if (!empty($aggregate_settings['variables']['advagg_relocate_js_piwik_local'])) { - $start = strpos($value['data'], 'var _paq'); - $middle = strpos($value['data'], '_paq.push(["setTrackerUrl"', $start); - // Skip if not the paq code. - if ($start !== FALSE && $middle !== FALSE) { - $scripts_found['piwik.js'] = array($key, $start, $middle); - } - } - - // Handle gtm.js. - if (!empty($aggregate_settings['variables']['advagg_relocate_js_gtm_local'])) { - $start = strpos($value['data'], '(function(w,d,s,l,i){'); - $middle = strpos($value['data'], 'var f=d.getElementsByTagName(s)[0]', $start); - $end = strpos($value['data'], '})(window,document,', $middle); - // Skip if not the GTM code. - if ($start !== FALSE && $middle !== FALSE && $end !== FALSE) { - $scripts_found['gtm.js'] = array($key, $start, $middle, $end); - } - } - - // Handle fbds.js. - if (!empty($aggregate_settings['variables']['advagg_relocate_js_fbds_local'])) { - $start = strpos($value['data'], 'var _fbq'); - $middle = strpos($value['data'], '(!_fbq.loaded)', $start); - $end = strpos($value['data'], 's.parentNode.insertBefore(fbds', $middle); - // Skip if not the fbds code. - if ($start !== FALSE && $middle !== FALSE && $end !== FALSE) { - $scripts_found['fbds.js'] = array($key, $start, $middle, $end); - } - } - - // Handle fbevents.js. - if (!empty($aggregate_settings['variables']['advagg_relocate_js_fbevents_local'])) { - $end = strpos($value['data'], 'connect.facebook.net/en_US/fbevents.js'); - // Skip if not the fbevents code. - if ($end !== FALSE) { - // Get middle of string. - $matches = array(); - preg_match('/fbq\s*=\s*function\(\)/', $value['data'], $matches, PREG_OFFSET_CAPTURE); - if (!empty($matches[0][1])) { - $middle = $matches[0][1]; - // Get start of string. - $matches = array(); - preg_match('/\!\s*function\(f,b,e,v,n,t,s\)/', $value['data'], $matches, PREG_OFFSET_CAPTURE); - if (isset($matches[0][1])) { - $start = $matches[0][1]; - if ($middle - $start <= 90) { - $scripts_found['fbevents.js'] = array($key, $start, $middle, $end); - } - } - } - } - } - - // Handle perfectaudience.js. - if (!empty($aggregate_settings['variables']['advagg_relocate_js_perfectaudience_local'])) { - $matches = array(); - preg_match('/window\._pa\s*=\s*window._pa\s*\|\|\s*\{\s*\}\s*;/', $value['data'], $matches, PREG_OFFSET_CAPTURE); - if (!empty($matches[0][1])) { - $start = $matches[0][1]; - $middle = strpos($value['data'], '//tag.perfectaudience.com/serve/', $start); - $end = strpos($value['data'], 's.parentNode.insertBefore(pa, s);', $middle); - // Add if perfectaudience code. - if ($start !== FALSE && $middle !== FALSE && $end !== FALSE) { - $scripts_found['perfectaudience.js'] = array( - $key, - $start, - $middle, - $end, - ); - } - } - - // Handle twitter uwt.js. - if (!empty($aggregate_settings['variables']['advagg_relocate_js_twitter_uwt_local'])) { - $start = strpos($value['data'], '!function(e,t,n,s,u,a){e.twq||'); - $middle = strpos($value['data'], '//static.ads-twitter.com/uwt.js', $start); - $end = strpos($value['data'], "a.parentNode.insertBefore(u,a))}(window,document,'script');", $middle); - // Add in twitter uwt.js code. - if ($start !== FALSE && $middle !== FALSE && $end !== FALSE) { - $scripts_found['uwt.js'] = array($key, $start, $middle, $end); - } - } - - // Handle linkedin insight.js. - if (!empty($aggregate_settings['variables']['advagg_relocate_js_linkedin_insight_local'])) { - $start = strpos($value['data'], '_linkedin_data_partner_id'); - $middle = strpos($value['data'], '//snap.licdn.com/li.lms-analytics/insight.min.js', $start); - $end = strpos($value['data'], "s.parentNode.insertBefore(b, s)", $middle); - // Add in linkedin insight.js code. - if ($start !== FALSE && $middle !== FALSE && $end !== FALSE) { - $scripts_found['linkedin_insight.js'] = array( - $key, - $start, - $middle, - $end, - ); - } - } - } - - return $scripts_found; -} - -/** - * Add external. - * - * @param array $js - * JS array. - * @param array $scripts_found - * An array of inline scripts found and locations for them in the file. - */ -function advagg_relocate_js_script_rewrite(array &$js, array $scripts_found) { - // Move inline code to external if possible. - foreach ($scripts_found as $key_name => $values) { - if ($key_name === 'analytics.js') { - list($key, $start, $middle, $end) = $values; - $value = $js[$key]; - - // Get analytics.js URL and add it to the js array. - $analytics_js_url = substr($value['data'], $middle + 29, $end - strlen($value['data']) - 1); - $insert = array( - $analytics_js_url => array( - 'data' => $analytics_js_url, - 'type' => 'external', - 'async' => TRUE, - 'defer' => TRUE, - 'noasync' => FALSE, - 'nodefer' => FALSE, - ), - ); - $js = advagg_insert_into_array_at_key($js, $insert, $key); - $js[$analytics_js_url] += $value; - - // Fix if analytics.js is already local. - $http_pos = strpos($analytics_js_url, 'http://'); - $https_pos = strpos($analytics_js_url, 'https://'); - $double_slash_pos = strpos($analytics_js_url, '//'); - if ($http_pos !== 0 - && $https_pos !== 0 - && $double_slash_pos !== 0 - ) { - $js[$analytics_js_url]['type'] = 'file'; - } - - // Shorten function arguments. - $value['data'] = substr($value['data'], 0, $middle + 27) . substr($value['data'], $end); - // Strip loader string. - $value['data'] = substr($value['data'], 0, $start + 132) . substr($value['data'], $middle); - // Shorten function parameters. - $value['data'] = str_replace('function(i,s,o,g,r,a,m){i["GoogleAnalyticsObject"]', 'function(i,s,o,r){i["GoogleAnalyticsObject"]', $value['data']); - $js[$key]['pre_relocate_data'] = $js[$key]['data']; - $js[$key]['data'] = $value['data']; - // Pin to header as the js expects ga to be there. - $js[$key]['scope'] = 'header'; - $js[$key]['scope_lock'] = TRUE; - - // Handle ga("require", ...) calls for external scripts to be loaded. - $matches = array(); - preg_match_all('/ga\([\'"]require[\'"],\s*[\'"][a-zA-Z0-9_ ]*[\'"],\s*[\'"](.*?)[\'"]\)/', $value['data'], $matches, PREG_SET_ORDER); - foreach ($matches as $match) { - // Remove inline js loader code. - $start = strpos($js[$key]['data'], $match[0]); - if ($start === FALSE) { - continue; - } - $strlen = strlen($match[0]); - $js[$key]['data'] = substr($js[$key]['data'], 0, $start) . substr($js[$key]['data'], $start + $strlen); - - // Add script to the drupal js array. - $url = '//www.google-analytics.com/plugins/ua/' . $match[1]; - $insert = array( - $url => array( - 'data' => $url, - 'type' => 'external', - 'async' => TRUE, - 'defer' => TRUE, - 'noasync' => FALSE, - 'nodefer' => FALSE, - ), - ); - $js = advagg_insert_into_array_at_key($js, $insert, $analytics_js_url); - $js[$url] += $value; - } - } - - if ($key_name === 'piwik.js') { - list($key, $start, $middle) = $values; - $value = $js[$key]; - - // Get URL. - $url = variable_get('matomo_url_http', ''); - if ($GLOBALS['is_https']) { - // Use HTTPS. - $url = variable_get('matomo_url_https', ''); - } - if (is_callable('_matomo_cache') - && variable_get('matomo_cache', 0) - && $matomo_cache = _matomo_cache($url . 'piwik.js') - ) { - // Try cache. - $url = $matomo_cache; - } - if (empty($url)) { - // Extract info from inline script. - // "https:" == document.location.protocol ? "https://.../piwik/" : "http://.../piwik/". - $pattern = '/[\'"]https\:[\'"]\s*==\s*document\.location\.protocol.*?[\'"](.*?)[\'"]\s*\:\s*[\'"](.*?)[\'"]/'; - preg_match($pattern, $value['data'], $matches); - if ($GLOBALS['is_https'] && !empty($matches[1])) { - $url = $matches[1]; - } - elseif (empty($GLOBALS['is_https']) && !empty($matches[2])) { - $url = $matches[2]; - } - } - if (!empty($url)) { - // Use HTTP. - // The $url may or may not have "piwik.js" at the end (it has if it came from _matomo_cache call above, otherwise not). Add if needed. - $url = check_url($url); - if (basename($url) != 'piwik.js') { - $url = $url . 'piwik.js'; - } - } - - // Final checks. - if (!empty($url)) { - $scope = variable_get('matomo_js_scope', $value['scope']); - $url = advagg_convert_abs_to_rel($url, TRUE); - $type = 'file'; - if (advagg_is_external($url)) { - $parsed = parse_url($url); - if (strpos($parsed['path'], $GLOBALS['base_path']) === 0) { - $path = substr($parsed['path'], strlen($GLOBALS['base_path'])); - if (file_exists($path)) { - $url = $path; - } - else { - $type = 'external'; - } - } - else { - $type = 'external'; - } - } - - // Add to js array. - $insert = array( - $url => array( - 'scope' => $scope, - 'data' => $url, - 'type' => $type, - 'async' => TRUE, - 'defer' => TRUE, - 'noasync' => FALSE, - 'nodefer' => FALSE, - ), - ); - $js = advagg_insert_into_array_at_key($js, $insert, $key); - - $matches = array(); - // s.parentNode.insertBefore(g,s);. - $pattern = '/\,?\s*[\w]{1,2}\.parentNode\.insertBefore\(\s*[\w]{1,2}\s*,\s*[\w]{1,2}\s*\)\s*\;*/'; - preg_match($pattern, $value['data'], $matches); - // Strip loader string. - $value['data'] = str_replace($matches[0], '', $value['data']); - - $js[$key]['pre_relocate_data'] = $js[$key]['data']; - $js[$key]['data'] = $value['data']; - // Pin to header as the js expects paq to be loaded before the file. - $js[$key]['scope'] = 'header'; - $js[$key]['scope_lock'] = TRUE; - $js[$key]['weight'] = -50000; - $js[$key]['movable'] = FALSE; - } - } - - if ($key_name === 'gtm.js') { - list($key, $start, $middle, $end) = $values; - $value = $js[$key]; - - // Get URL for script. - $matches_a = array(); - preg_match('/j.src\s*=\s*[\'"](.*?);/', $value['data'], $matches_a); - $matches_b = array(); - preg_match('/\}\)\(window,document,[\'"]script[\'"],[\'"](.*?)[\'"],[\'"](.*?)[\'"]\);/', $value['data'], $matches_b); - if (empty($matches_a[1]) || empty($matches_b[1])) { - continue; - } - if ($matches_b[1] !== 'dataLayer') { - $matches_b[1] = '&l=' . $matches_b[1]; - } - else { - $matches_b[1] = ''; - } - $gtm_url = trim(str_replace(array( - "'+i", - "+dl+'", - "+dl", - ), array( - $matches_b[2], - $matches_b[1], - $matches_b[1], - ), $matches_a[1]), "'"); - - // Add script to the drupal js array. - $insert = array( - $gtm_url => array( - 'data' => $gtm_url, - 'type' => 'external', - 'async' => TRUE, - 'defer' => TRUE, - 'noasync' => FALSE, - 'nodefer' => FALSE, - ), - ); - $js = advagg_insert_into_array_at_key($js, $insert, $key); - $js[$gtm_url] += $value; - - // Shorten function arguments. - $args = explode(',', substr($value['data'], $end + 3, -2)); - $value['data'] = substr($value['data'], 0, $end + 9) . ",{$args[3]});"; - // Strip loader string. - $value['data'] = substr($value['data'], 0, $middle) . substr($value['data'], $end); - // Shorten function parameters. - $value['data'] = str_replace('(function(w,d,s,l,i){', '(function(w,l){', $value['data']); - // Add back. - $js[$key]['pre_relocate_data'] = $js[$key]['data']; - $js[$key]['data'] = $value['data']; - // Pin to header as the js expects dataLayer to be there. - $js[$key]['scope'] = 'header'; - $js[$key]['scope_lock'] = TRUE; - } - - if ($key_name === 'fbds.js') { - list($key, $start, $middle, $end) = $values; - $value = $js[$key]; - - // Get URL for script. - $matches = array(); - preg_match('/fbds.src\s*=\s*[\'"](.*?)[\'"];/', $value['data'], $matches); - if (empty($matches[1])) { - continue; - } - $url = trim($matches[1]); - - // Add script to the drupal js array. - $insert = array( - $url => array( - 'data' => $url, - 'type' => 'external', - 'async' => TRUE, - 'defer' => TRUE, - 'noasync' => FALSE, - 'nodefer' => FALSE, - ), - ); - $js = advagg_insert_into_array_at_key($js, $insert, $key); - $js[$url] += $value; - - // Strip loader string. - $matches = array(); - preg_match('/if\s*\(!_fbq.loaded\)\s*\{/', $value['data'], $matches, PREG_OFFSET_CAPTURE); - $new_js = substr($value['data'], 0, $matches[0][1]); - // Set loaded TRUE. - $matches = array(); - preg_match('/_fbq.loaded\s*=\s*true/', $value['data'], $matches, PREG_OFFSET_CAPTURE); - $new_js .= $matches[0][0] . ';'; - // Get the rest of the JS string. - $end = strpos($value['data'], '}', $matches[0][1]); - $new_js .= trim(substr($value['data'], $end + 1)); - $js[$key]['pre_relocate_data'] = $js[$key]['data']; - $js[$key]['data'] = $new_js; - // Pin to header as the js expects _fbq to be there. - $js[$key]['scope'] = 'header'; - $js[$key]['scope_lock'] = TRUE; - } - - if ($key_name === 'fbevents.js') { - list($key, $start, $middle, $end) = $values; - $value = $js[$key]; - - // Add script to the drupal js array. - $url = 'https://connect.facebook.net/en_US/fbevents.js'; - $insert = array( - $url => array( - 'data' => $url, - 'type' => 'external', - ), - ); - $js = advagg_insert_into_array_at_key($js, $insert, $key); - $js[$url] += $value; - - $before = substr($value['data'], 0, $start); - $after = ltrim(substr($value['data'], $end + 40), ';'); - - // Get all facebook pixel ids. - $fb_ids = array_filter(array_map('trim', explode("\n", variable_get('advagg_relocate_js_fbevents_local_ids', ADVAGG_RELOCATE_JS_FBEVENTS_LOCAL_IDS)))); - $matches = array(); - preg_match('/fbq\(\s*[\'"]init[\'"]\s*,\s*[\'"](\d+)[\'"]\s*\)/', $value['data'], $matches); - if (!empty($matches[1])) { - $fb_ids[] = $matches[1]; - } - $fb_ids = array_filter($fb_ids); - - // Update in place the ids if any others were found inline. - $GLOBALS['conf']['advagg_relocate_js_fbevents_local_ids'] = implode("\n", $fb_ids); - - // Get scripts before and after; replace middle of script. - $new_js = $before . "!function(f,b,e,v,n,t,s){if(f.fbq)return;n=f.fbq=function(){n.callMethod?n.callMethod.apply(n,arguments):n.queue.push(arguments)};if(!f._fbq)f._fbq=n;n.push=n;n.loaded=!0;n.version='2.0';n.queue=[];t=b.createElement(e);t.async=!0;t.src=v;s=b.getElementsByTagName(e)[0]}(window,document,'script','//connect.facebook.net/en_US/fbevents.js');" . $after; - $js[$key]['pre_relocate_data'] = $js[$key]['data']; - $js[$key]['data'] = $new_js; - // Pin to header as the js expects fbq to be there. - $js[$key]['scope'] = 'header'; - $js[$key]['scope_lock'] = TRUE; - } - - if ($key_name === 'perfectaudience.js') { - list($key, $start, $middle, $end) = $values; - $value = $js[$key]; - - // Reindex. - $matches = array(); - preg_match('/window\._pa\s*=\s*window._pa\s*\|\|\s*\{\s*\}\s*;/', $value['data'], $matches, PREG_OFFSET_CAPTURE); - if (!empty($matches[0][1])) { - $start = $matches[0][1]; - $middle = strpos($value['data'], '//tag.perfectaudience.com/serve/', $start); - $end = strpos($value['data'], 's.parentNode.insertBefore(pa, s);', $middle); - - $substr = substr($value['data'], $start, $end - $start + 35); - $matches = array(); - preg_match('/\.src\s*=\s*[\'"](\/\/tag.perfectaudience.com\/serve\/.*\.js)[\'"]/', $substr, $matches); - if (!empty($matches[1])) { - $url = $matches[1]; - - // Add script to the drupal js array. - $insert = array( - $url => array( - 'data' => $url, - 'type' => 'external', - 'async' => TRUE, - 'defer' => TRUE, - 'noasync' => FALSE, - 'nodefer' => FALSE, - ), - ); - $js = advagg_insert_into_array_at_key($js, $insert, $key); - $js[$url] += $value; - - // s.parentNode.insertBefore(pa, s);. - $pattern = '/\,?\s*[\w]{1,2}\.parentNode\.insertBefore\(\s*[\w]{1,2}\s*,\s*[\w]{1,2}\s*\)\s*\;*/'; - // Strip loader string. - $new_substr = preg_replace($pattern, '', $substr); - $js[$key]['data'] = str_replace($substr, $new_substr, $value['data']); - } - } - } - - if ($key_name === 'uwt.js') { - list($key, $start, $middle, $end) = $values; - $value = $js[$key]; - - $url = '//static.ads-twitter.com/uwt.js'; - // Add script to the drupal js array. - $insert = array( - $url => array( - 'data' => $url, - 'type' => 'external', - 'scope_lock' => FALSE, - 'scope' => 'footer', - ), - ); - $js = advagg_insert_into_array_at_key($js, $insert, $key); - $js[$url] += $value; - - // Reindex. - $start = strpos($value['data'], '!function(e,t,n,s,u,a){e.twq||'); - $middle = strpos($value['data'], '//static.ads-twitter.com/uwt.js', $start); - $end = strpos($value['data'], "a.parentNode.insertBefore(u,a))}(window,document,'script');", $middle); - - $substr = substr($value['data'], $start, $end - $start + 61); - // a.parentNode.insertBefore(u,a). - $pattern = '/\,?\s*[\w]{1,2}\.parentNode\.insertBefore\(\s*[\w]{1,2}\s*,\s*[\w]{1,2}\s*\)\s*\;*/'; - // Strip loader string. - $new_substr = preg_replace($pattern, '', $substr); - $js[$key]['data'] = str_replace($substr, $new_substr, $value['data']); - } - - if ($key_name === 'linkedin_insight.js') { - list($key, $start, $middle, $end) = $values; - $value = $js[$key]; - - $url = 'https://snap.licdn.com/li.lms-analytics/insight.min.js'; - // Add script to the drupal js array. - $insert = array( - $url => array( - 'data' => $url, - 'type' => 'external', - 'async' => TRUE, - 'defer' => TRUE, - 'noasync' => FALSE, - 'nodefer' => FALSE, - ), - ); - $js = advagg_insert_into_array_at_key($js, $insert, $key); - $js[$url] += $value; - - $start = strpos($value['data'], '_linkedin_data_partner_id'); - $middle = strpos($value['data'], '//snap.licdn.com/li.lms-analytics/insight.min.js', $start); - $end = strpos($value['data'], "s.parentNode.insertBefore(b, s)", $middle); - - $substr = substr($value['data'], $start, $end - $start + 35); - // a.parentNode.insertBefore(u,a). - $pattern = '/\,?\s*[\w]{1,2}\.parentNode\.insertBefore\(\s*[\w]{1,2}\s*,\s*[\w]{1,2}\s*\)\s*\;*/'; - // Strip loader string. - $new_substr = preg_replace($pattern, '', $substr); - $js[$key]['data'] = str_replace($substr, $new_substr, $value['data']); - } - } -} - -/** - * Alter the js array. - * - * @param array $js - * JS array. - * @param bool $force_check - * TRUE if you want to force check the external source. - */ -function advagg_relocate_js_post_alter(array &$js, $force_check = FALSE) { - if (!module_exists('advagg') || !advagg_enabled()) { - return; - } - - // Check external js setting. - $aggregate_settings = advagg_current_hooks_hash_array(); - if (empty($aggregate_settings['variables']['advagg_relocate_js'])) { - return; - } - - // Look for inline scrtips that add external js via inline code. - $scripts_found = array(); - module_load_include('inc', 'advagg', 'advagg'); - - $temp_inserts = array(); - foreach ($js as $key => $value) { - if ($value['type'] === 'inline') { - $scripts_found += advagg_relocate_js_script_rewrite_list($key, $value, $aggregate_settings); - } - if ($value['type'] === 'file') { - $info = advagg_get_info_on_file($key); - if (!empty($info['advagg_relocate'])) { - // Get the file contents. - $file_contents = (string) @advagg_file_get_contents($info['data']); - if (empty($file_contents)) { - continue; - } - $value['data'] = $file_contents; - $temp_inserts[$key] = array("advagg_relocate:{$key}" => $value); - $scripts_found += advagg_relocate_js_script_rewrite_list("advagg_relocate:{$key}", $value, $aggregate_settings); - } - } - } - - // Add in file as inline so replacement works. - if (!empty($temp_inserts)) { - foreach ($temp_inserts as $key => $value) { - $js = advagg_insert_into_array_at_key($js, $value, $key); - } - } - - // Rewrite inline scrtips and add external references to js array. - advagg_relocate_js_script_rewrite($js, $scripts_found); - - // Remove temp inline code. - if (!empty($temp_inserts)) { - foreach ($temp_inserts as $value) { - reset($value); - $key = key($value); - unset($js[$key]); - } - } - - // Get all external js files. - $urls = _advagg_relocate_get_urls($js, 'js', $aggregate_settings); - if (empty($urls)) { - return; - } - - // Make advagg_save_data() available. - module_load_include('inc', 'advagg', 'advagg.missing'); - module_load_include('advagg.inc', 'advagg_relocate'); - $responses = advagg_relocate_get_remote_data($urls, 'js', array(), $force_check); - - // Get s3fs no_rewrite_cssjs setting. - $s3fs_no_rewrite_cssjs = advagg_get_s3fs_config('no_rewrite_cssjs'); - - $filenames = array(); - $advagg_relocate_js_ttl = variable_get('advagg_relocate_js_ttl', ADVAGG_RELOCATE_JS_TTL); - foreach ($responses as $value) { - // If the external file has a longer TTL than 1 week, do not cache. - if (!empty($advagg_relocate_js_ttl) && $value->ttl >= $advagg_relocate_js_ttl) { - continue; - } - - list($full_filename, $errors, $saved) = _advagg_relocate_save_remote_asset($value->options['filename'], $value->data, $value->local_cache, $value->hash); - if (!empty($errors)) { - watchdog('advagg-relocate', 'Write failed. Errors: <pre><tt>@errors</tt></pre>', array( - '@errors' => $errors, - )); - continue; - } - - // Replace remote data with local data. - $relative_path = advagg_get_relative_path($full_filename); - $key = $urls[$value->options['filename']]; - $js = advagg_relocate_key_rename($js, array($relative_path => $key)); - $js[$relative_path]['pre_relocate_data'] = $js[$relative_path]['data']; - $js[$relative_path]['data'] = $relative_path; - $js[$relative_path]['type'] = 'file'; - if (defined('ADVAGG_MOD_JS_PREPROCESS') && variable_get('advagg_mod_js_preprocess', ADVAGG_MOD_JS_PREPROCESS) && empty($js[$relative_path]['preprocess_lock'])) { - $js[$relative_path]['preprocess'] = TRUE; - } - - // Check for any .write( statements in the JS code. - if (strpos($value->data, '.write(') !== FALSE) { - if ((!isset($js[$relative_path]['noasync']) || $js[$relative_path]['noasync'] !== FALSE) - || (!isset($js[$relative_path]['nodefer']) || $js[$relative_path]['nodefer'] !== FALSE) - ) { - $js[$relative_path]['async'] = FALSE; - $js[$relative_path]['defer'] = FALSE; - $js[$relative_path]['noasync'] = TRUE; - $js[$relative_path]['nodefer'] = TRUE; - } - } - - // Handle domain prefectch. - $parse = @parse_url($key); - $key_host = ''; - if (!empty($parse['host'])) { - $key_host = $parse['host']; - } - if (!empty($key_host) && !empty($js[$relative_path]['dns_prefetch'])) { - foreach ($js[$relative_path]['dns_prefetch'] as $key => $domain_name) { - if (strpos($domain_name, $key_host) !== FALSE && strpos($value->data, $key_host)) { - unset($js[$relative_path]['dns_prefetch'][$key]); - } - } - } - - // Make sure the external reference has been removed. - $schemes = array('//', 'http', 'https'); - foreach ($schemes as $scheme) { - $parse['scheme'] = $scheme; - $key = advagg_glue_url($parse); - if (isset($js[$key])) { - unset($js[$key]); - } - } - - // List of files that need the cache cleared. - if ($saved) { - $filenames[] = (!is_null($s3fs_no_rewrite_cssjs) && empty($s3fs_no_rewrite_cssjs)) ? $full_filename : $relative_path; - } - } - - if (!empty($filenames)) { - module_load_include('inc', 'advagg'); - module_load_include('cache.inc', 'advagg'); - $files = advagg_get_info_on_files($filenames, TRUE); - advagg_push_new_changes($files); - } -} - -/** - * Implements hook_advagg_current_hooks_hash_array_alter(). - */ -function advagg_relocate_advagg_current_hooks_hash_array_alter(&$aggregate_settings) { - $aggregate_settings['variables']['advagg_relocate_css_inline_import'] = variable_get('advagg_relocate_css_inline_import', ADVAGG_RELOCATE_CSS_INLINE_IMPORT); - - $defaults = array( - 'woff2' => 'woff2', - 'woff' => 'woff', - 'ttf' => 'ttf', - ); - $aggregate_settings['variables']['advagg_relocate_css_inline_import_browsers'] = variable_get('advagg_relocate_css_inline_import_browsers', $defaults); - - $aggregate_settings['variables']['advagg_relocate_css_file_settings'] = variable_get('advagg_relocate_css_file_settings', array()); - - $aggregate_settings['variables']['advagg_relocate_css_font_domains'] = variable_get('advagg_relocate_css_font_domains', ADVAGG_RELOCATE_CSS_FONT_DOMAINS); - - $types = array('css' => 'CSS', 'js' => 'JS'); - foreach ($types as $type_lower => $type_upper) { - $domains_blacklist = array_filter(array_map('trim', explode("\n", variable_get("advagg_relocate_{$type_lower}_domains_blacklist", constant("ADVAGG_RELOCATE_{$type_upper}_DOMAINS_BLACKLIST"))))); - $aggregate_settings['variables']["advagg_relocate_{$type_lower}_domains_blacklist"] = $domains_blacklist; - - $files_blacklist = array_filter(array_map('trim', explode("\n", variable_get("advagg_relocate_{$type_lower}_files_blacklist", constant("ADVAGG_RELOCATE_{$type_upper}_FILES_BLACKLIST"))))); - $aggregate_settings['variables']["advagg_relocate_{$type_lower}_files_blacklist"] = $files_blacklist; - } - - $aggregate_settings['variables']['advagg_relocate_css_inline_external'] = variable_get('advagg_relocate_css_inline_external', ADVAGG_RELOCATE_CSS_INLINE_EXTERNAL); - - $aggregate_settings['variables']['advagg_relocate_css'] = variable_get('advagg_relocate_css', ADVAGG_RELOCATE_CSS); - - $aggregate_settings['variables']['advagg_relocate_js'] = variable_get('advagg_relocate_js', ADVAGG_RELOCATE_JS); - $aggregate_settings['variables']['advagg_relocate_js_ga_local'] = variable_get('advagg_relocate_js_ga_local', ADVAGG_RELOCATE_JS_GA_LOCAL); - $aggregate_settings['variables']['advagg_relocate_js_gtm_local'] = variable_get('advagg_relocate_js_gtm_local', ADVAGG_RELOCATE_JS_GTM_LOCAL); - $aggregate_settings['variables']['advagg_relocate_js_fbds_local'] = variable_get('advagg_relocate_js_fbds_local', ADVAGG_RELOCATE_JS_FBDS_LOCAL); - $aggregate_settings['variables']['advagg_relocate_js_fbevents_local'] = variable_get('advagg_relocate_js_fbevents_local', ADVAGG_RELOCATE_JS_FBEVENTS_LOCAL); - $aggregate_settings['variables']['advagg_relocate_js_piwik_local'] = variable_get('advagg_relocate_js_piwik_local', ADVAGG_RELOCATE_JS_PIWIK_LOCAL); - $aggregate_settings['variables']['advagg_relocate_js_perfectaudience_local'] = variable_get('advagg_relocate_js_perfectaudience_local', ADVAGG_RELOCATE_JS_PERFECTAUDIENCE_LOCAL); - $aggregate_settings['variables']['advagg_relocate_js_twitter_uwt_local'] = variable_get('advagg_relocate_js_twitter_uwt_local', ADVAGG_RELOCATE_JS_TWITTER_UWT_LOCAL); - $aggregate_settings['variables']['advagg_relocate_js_linkedin_insight_local'] = variable_get('advagg_relocate_js_linkedin_insight_local', ADVAGG_RELOCATE_JS_LINKEDIN_INSIGHT_LOCAL); - -} - -/** - * @} End of "addtogroup advagg_hooks". - */ - -/** - * Convert local @import statements to external. - * - * @param array $matches - * Array of matched items from preg_replace_callback(). - * @param string $url - * URL of where the original CSS is located. - * - * @return string - * New import statement. - */ -function advagg_relocate_load_stylesheet_local(array $matches, $url = '') { - $_url = &drupal_static(__FUNCTION__, ''); - // Store base path for preg_replace_callback. - if (!empty($url)) { - $_url = $url; - } - // Short circuit if no matches were passed in. - if (empty($matches)) { - return ''; - } - - $css_url = $_url . $matches[1]; - return "@import \"{$css_url}\";"; -} - -/** - * Convert external @import statements to be local. - * - * @param array $matches - * Array of matched items from preg_replace_callback(). - * - * @return string - * Contents of the import statement or new import statement. - */ -function advagg_relocate_load_stylesheet_external(array $matches) { - // Build css array. - $css = array( - $matches[1] => array( - 'type' => 'external', - 'data' => $matches[1], - ), - ); - // Recursively pull in imported fonts. - advagg_relocate_css_alter($css); - - // Remove '../' segments where possible. - $values = reset($css); - if ($values['type'] !== 'inline') { - $last = ''; - $url = $values['data']; - while ($url != $last) { - $last = $url; - $url = preg_replace('`(^|/)(?!\.\./)([^/]+)/\.\./`', '$1', $url); - } - // Build css array. - $css = array( - $url => array( - 'type' => $values['type'], - 'data' => $url, - ), - ); - } - // Recursively pull in external references. - advagg_relocate_css_post_alter($css); - - $values = reset($css); - $key = key($css); - if ($values['type'] === 'inline') { - return "/* Contents of $key */\n{$values['data']}"; - } - else { - if (!advagg_is_external($values['data'])) { - $dir = variable_get('advagg_relocate_directory', ADVAGG_RELOCATE_DIRECTORY); - $path = advagg_get_relative_path($dir) . '/'; - $values['data'] = str_replace($path, '', $values['data']); - } - return "@import \"{$values['data']}\";"; - } -} - -/** - * Return a filename => url array for external assets. - * - * @param array $data - * CSS or JS data array. - * @param string $type - * Either css or js. - * @param array $aggregate_settings - * Array of settings. - * - * @return array - * Array of external assets to be served locally. - */ -function _advagg_relocate_get_urls(array $data, $type, array $aggregate_settings) { - $domains_blacklist = $aggregate_settings['variables']["advagg_relocate_{$type}_domains_blacklist"]; - $files_blacklist = $aggregate_settings['variables']["advagg_relocate_{$type}_files_blacklist"]; - $urls = array(); - foreach ($data as $key => $value) { - // Get all external js files. - if ($value['type'] !== 'external') { - continue; - } - // If no_relocate=TRUE, do not move it to be local. - if (!empty($value['no_relocate'])) { - continue; - } - if (empty($value['data'])) { - $value['data'] = $key; - } - $host = parse_url($value['data'], PHP_URL_HOST); - if (!empty($domains_blacklist)) { - foreach ($domains_blacklist as $domain) { - if ($domain === $host) { - continue 2; - } - } - } - if (!empty($files_blacklist)) { - foreach ($files_blacklist as $file) { - if (strpos($file, $host) !== FALSE) { - continue 2; - } - } - } - - // Encode the URL into a filename. Force HTTPS. - $filename = advagg_url_to_filename(advagg_force_https_path($value['data'])); - // Make sure it ends with .css or .js. - if (stripos(strrev($filename), strrev($type)) !== 0) { - $filename .= ".$type"; - } - $urls[$filename] = $key; - } - return $urls; -} - -/** - * Verifies that the external CSS file is from a domain we allow for inlining. - * - * @param string $url - * The full URL of the css file. - * @param array $aggregate_settings - * Array of settings. - * - * @return bool - * TRUE if the URL can be inlined. - */ -function advagg_relocate_check_domain_of_font_url($url, array $aggregate_settings) { - // Bail if the host or path and query string are empty. - $parse = @parse_url($url); - if (empty($parse) - || empty($parse['host']) - || (empty($parse['path']) && empty($parse['query'])) - ) { - return FALSE; - } - - // Bail if the host doesn't match one of the listed domains. - if (!isset($aggregate_settings['variables']['advagg_relocate_css_font_domains'])) { - $aggregate_settings['variables']['advagg_relocate_css_font_domains'] = variable_get('advagg_relocate_css_font_domains', ADVAGG_RELOCATE_CSS_FONT_DOMAINS); - } - if (strpos($aggregate_settings['variables']['advagg_relocate_css_font_domains'], $parse['host']) === FALSE) { - return FALSE; - } - return TRUE; -} - -/** - * Replace JS key with another key. - * - * @param array $input - * Input array. - * @param array $replacements - * Key value pair; key is the new key, value is the old key. - */ -function advagg_relocate_key_rename(array $input, array $replacements) { - $output = array(); - foreach ($input as $k => $v) { - $replacement_key = array_search($k, $replacements, TRUE); - if ($replacement_key !== FALSE) { - $output[$replacement_key] = $v; - } - else { - $output[$k] = $v; - } - } - return $output; -} - -/** - * Perform a cache flush of the advagg relocate module. - */ -function advagg_relocate_flush_cache_button() { - cache_clear_all('advagg_relocate_', 'cache_advagg_info', TRUE); - drupal_set_message(t('AdvAgg Relocate Cache Cleared.')); -} diff --git a/sites/all/modules/contrib/advagg/advagg_sri/advagg_sri.admin.inc b/sites/all/modules/contrib/advagg/advagg_sri/advagg_sri.admin.inc deleted file mode 100644 index d971fbfbc8..0000000000 --- a/sites/all/modules/contrib/advagg/advagg_sri/advagg_sri.admin.inc +++ /dev/null @@ -1,55 +0,0 @@ -<?php - -/** - * @file - * Admin page callbacks for the advagg sri module. - */ - -/** - * Form builder; Configure advagg settings. - * - * @ingroup advagg_forms - * - * @see system_settings_form() - */ -function advagg_sri_admin_settings_form() { - drupal_set_title(t('AdvAgg: Subresource Integrity')); - advagg_display_message_if_requirements_not_met(); - - $form = array(); - $form['advagg_sri'] = array( - '#type' => 'radios', - '#title' => t('Subresource Integrity Level'), - '#default_value' => variable_get('advagg_sri', ADVAGG_SRI), - '#options' => array( - 0 => t('Disabled'), - 1 => t('SHA-256'), - 2 => t('SHA-384'), - 3 => t('SHA-512'), - ), - ); - - if (module_exists('httprl')) { - $form['advagg_sri_file_generation'] = array( - '#type' => 'checkbox', - '#title' => t('Always output the page with the subresource integrity attribute.'), - '#default_value' => variable_get('advagg_sri_file_generation', ADVAGG_SRI_FILE_GENERATION), - '#description' => t('If checked - background processes will not usually be used when generating aggregated files; sometimes resulting in a slower page load. Noted though that the page cache is disabled if the all the aggregates do not have the integrity attribute.'), - ); - } - - // Clear the cache bins on submit. - $form['#submit'][] = 'advagg_sri_admin_settings_form_submit'; - - return system_settings_form($form); -} - -/** - * Submit callback, clears out the advagg cache bin. - * - * @ingroup advagg_forms_callback - */ -function advagg_sri_admin_settings_form_submit($form, &$form_state) { - // Clear caches. - advagg_cache_clear_admin_submit(); -} diff --git a/sites/all/modules/contrib/advagg/advagg_sri/advagg_sri.advagg.inc b/sites/all/modules/contrib/advagg/advagg_sri/advagg_sri.advagg.inc deleted file mode 100644 index 16ca56ad6a..0000000000 --- a/sites/all/modules/contrib/advagg/advagg_sri/advagg_sri.advagg.inc +++ /dev/null @@ -1,240 +0,0 @@ -<?php - -/** - * @file - * Advanced aggregation sri module. - */ - -/** - * @addtogroup advagg_hooks - * @{ - */ - -/** - * Implements hook_advagg_save_aggregate_alter(). - * - * Save the hash of the file. - */ -function advagg_sri_advagg_save_aggregate_alter(array &$files_to_save, array $aggregate_settings, array $other_parameters) { - // * @param array $files_to_save - // * Array($uri => $contents). - // * @param array $aggregate_settings - // * Array of settings. - // * @param array $other_parameters - // * Array of containing $files and $type. - foreach ($files_to_save as $uri => $contents) { - // Skip gzip/brotli files. - $ext = strtolower(pathinfo($uri, PATHINFO_EXTENSION)); - if ($ext === 'gz' || $ext === 'br') { - continue; - } - - $hashes = advagg_sri_compute_hashes($contents); - - // Save to the database. - $filename = basename($uri); - advagg_sri_set_filename_hashes($filename, $hashes); - } -} - -/** - * Implements hook_advagg_build_aggregate_plans_post_alter(). - */ -function advagg_sri_advagg_build_aggregate_plans_post_alter(array &$plans) { - // * @param array $plans - // * Array of aggregate files. - $advagg_sri = variable_get('advagg_sri', ADVAGG_SRI); - if (empty($advagg_sri)) { - return; - } - if ($advagg_sri == 1) { - $sha_bits = 'sha256'; - } - if ($advagg_sri == 2) { - $sha_bits = 'sha384'; - } - if ($advagg_sri == 3) { - $sha_bits = 'sha512'; - } - - // Get all aggregates. - $files = array(); - $filenames = array(); - foreach ($plans as $key => $values) { - if ($values['type'] !== 'file' || empty($values['cache'])) { - continue; - } - $files[$values['filename']] = $key; - $filenames[$values['filepath']] = $values['filename']; - } - - // Lookup hashes. - $hashes = array(); - if (!empty($filenames)) { - $hashes = advagg_sri_get_filenames_hashes($filenames); - } - - // Set attributes. - foreach ($hashes as $filename => $hash) { - if (isset($files[$filename]) && isset($plans[$files[$filename]])) { - $plans[$files[$filename]]['attributes']['integrity'] = $sha_bits . '-' . $hash[$sha_bits]; - } - } -} - -/** - * Implements hook_advagg_build_aggregate_plans_alter(). - */ -function advagg_sri_advagg_build_aggregate_plans_alter() { - if (module_exists('httprl') && variable_get('advagg_use_httprl', ADVAGG_USE_HTTPRL) && variable_get('advagg_sri_file_generation', ADVAGG_SRI_FILE_GENERATION)) { - $GLOBALS['conf']['advagg_use_httprl'] = FALSE; - } -} - -/** - * Implements hook_advagg_missing_root_file(). - */ -function advagg_sri_advagg_missing_root_file($aggregate_filename, $filename, $cache) { - // Remove entries from the DB. - if (!empty($aggregate_filename) && !empty($cache)) { - advagg_sri_del_filename_hashes($aggregate_filename); - } - // Remove entries from the Cache. - $ext = strtolower(pathinfo($aggregate_filename, PATHINFO_EXTENSION)); - $cid = "advagg:$ext:"; - cache_clear_all($cid, 'cache_advagg_aggregates', TRUE); -} - -/** - * Implements hook_advagg_removed_aggregates(). - */ -function advagg_sri_advagg_removed_aggregates($kill_list) { - foreach ($kill_list as $uri) { - if (is_object($uri) && property_exists($uri, 'uri')) { - $temp = $uri->uri; - unset($uri); - $uri = $temp; - } - // Skip gzip/brotli files. - $ext = strtolower(pathinfo($uri, PATHINFO_EXTENSION)); - if ($ext === 'gz' || $ext === 'br') { - continue; - } - $filename = basename($uri); - advagg_sri_del_filename_hashes($filename); - } -} - -/** - * @} End of "addtogroup advagg_hooks". - */ - -/** - * Store settings associated with hash. - * - * @param string $filename - * The name of the aggregated filename. - * @param array $hashes - * An array of SHA hashes of the contents of this filename. - * - * @return MergeQuery - * value from db_merge - */ -function advagg_sri_set_filename_hashes($filename, array $hashes) { - return db_merge('advagg_sri') - ->key(array('filename' => $filename)) - ->fields(array( - 'filename' => $filename, - 'hashes' => serialize($hashes), - )) - ->execute(); -} - -/** - * Store settings associated with hash. - * - * @param string $filename - * The name of the aggregated filename. - * - * @return DeleteQuery - * value from db_delete - */ -function advagg_sri_del_filename_hashes($filename) { - return db_delete('advagg_sri') - ->condition('filename', $filename) - ->execute(); -} - -/** - * Returns the hashes settings. - * - * @param array $filenames - * An array of filenames. - * - * @return array - * The hashes for the given filenames. - */ -function advagg_sri_get_filenames_hashes(array $filenames) { - $hashes = array(); - // Do not use the DB if in development mode. - if (variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL) < 0) { - $rows = db_select('advagg_sri', 'advagg_sri') - ->fields('advagg_sri', array('filename', 'hashes')) - ->condition('filename', $filenames, 'IN') - ->execute(); - foreach ($rows as $row) { - $hashes[$row->filename] = unserialize($row->hashes); - } - } - - // If the hash is not in the database, generate it on demand. - $db_filenames = array_keys($hashes); - $not_in_db = array_diff($filenames, $db_filenames); - foreach ($not_in_db as $file) { - $filepath = array_search($file, $filenames); - if (is_readable($filepath)) { - // Do not use advagg_file_get_contents here. - $contents = (string) @file_get_contents($filepath); - if (!empty($contents)) { - $hashes[$file] = advagg_sri_compute_hashes($contents); - advagg_sri_set_filename_hashes($file, $hashes[$file]); - } - } - } - - // Check to make sure we have all hashes. - $all_hashes = array_keys($hashes); - $not_hashed = array_diff($filenames, $all_hashes); - if (!empty($not_hashed)) { - drupal_page_is_cacheable(FALSE); - // Disable saving to the cache if a sri is missing. - if (variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL) > 1) { - $GLOBALS['conf']['advagg_cache_level'] = 0; - } - - if (!module_exists('httprl') || !variable_get('advagg_use_httprl', ADVAGG_USE_HTTPRL)) { - watchdog('advagg_sri', 'The subresource integrity hashes could not be generated for these files: %files', array('%files' => print_r($not_hashed, TRUE))); - } - } - - return $hashes; -} - -/** - * Returns an array of hashes. - * - * @param string $contents - * The string to hash. - * - * @return array - * The hashes for the given string. - */ -function advagg_sri_compute_hashes($contents) { - // Generate hashes. - $hashes = array( - 'sha512' => base64_encode(hash('sha512', $contents, TRUE)), - 'sha384' => base64_encode(hash('sha384', $contents, TRUE)), - 'sha256' => base64_encode(hash('sha256', $contents, TRUE)), - ); - return $hashes; -} diff --git a/sites/all/modules/contrib/advagg/advagg_sri/advagg_sri.info b/sites/all/modules/contrib/advagg/advagg_sri/advagg_sri.info deleted file mode 100644 index 2289cb8026..0000000000 --- a/sites/all/modules/contrib/advagg/advagg_sri/advagg_sri.info +++ /dev/null @@ -1,13 +0,0 @@ -name = AdvAgg Subresource Integrity -description = Provide the integrity attribute for CSS and JS files. -package = Advanced CSS/JS Aggregation -core = 7.x -dependencies[] = advagg - -configure = admin/config/development/performance/advagg/sri - -; Information added by Drupal.org packaging script on 2020-11-19 -version = "7.x-2.35" -core = "7.x" -project = "advagg" -datestamp = "1605792717" diff --git a/sites/all/modules/contrib/advagg/advagg_sri/advagg_sri.install b/sites/all/modules/contrib/advagg/advagg_sri/advagg_sri.install deleted file mode 100644 index c95c1e9dc3..0000000000 --- a/sites/all/modules/contrib/advagg/advagg_sri/advagg_sri.install +++ /dev/null @@ -1,75 +0,0 @@ -<?php - -/** - * @file - * Handles Advanced Aggregation installation and upgrade tasks. - */ - -/** - * @addtogroup hooks - * @{ - */ - -/** - * Implements hook_install(). - */ -function advagg_sri_install() { - module_load_include('install', 'advagg', 'advagg'); - $tables = array( - 'advagg_sri' => array( - 'filename', - ), - ); - - $schema = advagg_sri_schema(); - foreach ($tables as $table => $fields) { - // Change utf8_bin to ascii_bin. - advagg_install_change_table_collation($table, $fields, 'ascii_bin', $schema[$table]['fields']); - } -} - -/** - * Implements hook_uninstall(). - */ -function advagg_sri_uninstall() { - // Remove variables. - db_delete('variable') - ->condition('name', 'advagg_sri%', 'LIKE') - ->execute(); -} - -/** - * Implements hook_schema(). - */ -function advagg_sri_schema() { - $schema = array(); - - // Copy the variable table and change a couple of things. - $schema['advagg_sri'] = drupal_get_schema_unprocessed('system', 'variable'); - - // Create the filename field. - $schema['advagg_sri']['fields']['filename'] = $schema['advagg_sri']['fields']['name']; - $schema['advagg_sri']['fields']['filename']['length'] = 143; - $schema['advagg_sri']['fields']['filename']['description'] = 'The name of the aggregate.'; - $schema['advagg_sri']['fields']['filename']['binary'] = TRUE; - $schema['advagg_sri']['fields']['filename']['collation'] = 'ascii_bin'; - $schema['advagg_sri']['fields']['filename']['charset'] = 'ascii'; - $schema['advagg_sri']['fields']['filename']['mysql_character_set'] = 'ascii'; - - // Create the hashes field. - $schema['advagg_sri']['fields']['hashes'] = $schema['advagg_sri']['fields']['value']; - $schema['advagg_sri']['fields']['hashes']['description'] = 'The hashes associated with this filename.'; - - // Set primary key and table description. - $schema['advagg_sri']['description'] = 'Stores sha hashes of this file.'; - $schema['advagg_sri']['primary key'][0] = 'filename'; - - // Remove the name and value fileds. - unset($schema['advagg_sri']['fields']['name'], $schema['advagg_sri']['fields']['value']); - - return $schema; -} - -/** - * @} End of "addtogroup hooks". - */ diff --git a/sites/all/modules/contrib/advagg/advagg_sri/advagg_sri.module b/sites/all/modules/contrib/advagg/advagg_sri/advagg_sri.module deleted file mode 100644 index 2bd92e18c2..0000000000 --- a/sites/all/modules/contrib/advagg/advagg_sri/advagg_sri.module +++ /dev/null @@ -1,80 +0,0 @@ -<?php - -/** - * @file - * Advanced aggregation sri module. - */ - -/** - * @addtogroup default_variables - * @{ - */ - -/** - * Default value of the SHA hash level. - */ -define('ADVAGG_SRI', 0); - -/** - * Default value to force SRI to always be generated. - */ -define('ADVAGG_SRI_FILE_GENERATION', FALSE); - -/** - * @} End of "addtogroup default_variables". - */ - -/** - * @addtogroup hooks - * @{ - */ - -/** - * Implements hook_menu(). - */ -function advagg_sri_menu() { - $file_path = drupal_get_path('module', 'advagg_sri'); - $config_path = advagg_admin_config_root_path(); - - $items[$config_path . '/advagg/sri'] = array( - 'title' => 'Subresource Integrity', - 'description' => 'Hash aggregated files.', - 'page callback' => 'drupal_get_form', - 'page arguments' => array('advagg_sri_admin_settings_form'), - 'type' => MENU_LOCAL_TASK, - 'access arguments' => array('administer site configuration'), - 'file path' => $file_path, - 'file' => 'advagg_sri.admin.inc', - 'weight' => 10, - ); - - return $items; -} - -/** - * Implements hook_module_implements_alter(). - */ -function advagg_sri_module_implements_alter(&$implementations, $hook) { - // Move advagg_sri to the bottom. - if ($hook === 'advagg_save_aggregate_alter' && array_key_exists('advagg_sri', $implementations)) { - $item = $implementations['advagg_sri']; - unset($implementations['advagg_sri']); - $implementations['advagg_sri'] = $item; - } -} - -/** - * Implements hook_form_FORM_ID_alter(). - */ -function advagg_sri_form_advagg_admin_settings_form_alter(&$form, $form_state) { - // Disable httprl if SRI is set to always on. - if (variable_get('advagg_sri_file_generation', ADVAGG_SRI_FILE_GENERATION)) { - $form['global']['advagg_use_httprl']['#default_value'] = FALSE; - $form['global']['advagg_use_httprl']['#disabled'] = TRUE; - $form['global']['advagg_use_httprl']['#description'] = t('The Subresource Integrity submodule has disabled httprl usage. This is the "Always output the page with the subresource integrity attribute" checkbox.'); - } -} - -/** - * @} End of "addtogroup hooks". - */ diff --git a/sites/all/modules/contrib/advagg/advagg_validator/advagg_validator.admin.inc b/sites/all/modules/contrib/advagg/advagg_validator/advagg_validator.admin.inc index af591211e8..eb4024164e 100644 --- a/sites/all/modules/contrib/advagg/advagg_validator/advagg_validator.admin.inc +++ b/sites/all/modules/contrib/advagg/advagg_validator/advagg_validator.admin.inc @@ -5,26 +5,14 @@ * Admin page callbacks for the advagg validator module. */ -/** - * @addtogroup default_variables - * @{ - */ - -/** - * @} End of "addtogroup default_variables". - */ - /** * Form builder; CSS validator via w3. * * @return array * form array. - * - * @ingroup advagg_forms */ function advagg_validator_admin_css_w3_form() { drupal_set_title(t('AdvAgg: CSS Validator using w3.org')); - advagg_display_message_if_requirements_not_met(); $form = advagg_validator_admin_form_generator('css', FALSE); @@ -42,46 +30,19 @@ function advagg_validator_admin_css_w3_form() { * * @return array * form array. - * - * @ingroup advagg_forms */ function advagg_validator_admin_css_lint_form() { drupal_set_title(t('AdvAgg: CSS Validator using CSSLINT')); - advagg_display_message_if_requirements_not_met(); - // Tell user to update library if a new version is available. - $module_name = 'advagg_validator'; - $lib_name = 'csslint'; - list($description) = advagg_get_version_description($lib_name, $module_name, TRUE); - $form = array(); - if (!empty($description)) { - $form['advagg_version_msg'] = array( - '#markup' => "<p>{$description}</p>", - ); - } + $form = advagg_validator_admin_form_generator('css', TRUE); - $form += advagg_validator_admin_form_generator('css', TRUE); - $library = advagg_get_library('csslint', 'advagg_validator'); - if (!empty($library['installed'])) { - $form['#attached']['libraries_load'][] = array('csslint'); - } - else { - $form['#attached']['js'] = $library['variants']['external']['files']['js']; - } $form['#attached']['js'][] = array( - 'data' => drupal_get_path('module', 'advagg_validator') . '/advagg_validator.js', - 'type' => 'file', + 'data' => '//rawgithub.com/stubbornella/csslint/master/release/csslint.js', + 'type' => 'external', ); - - // Comma separated code. - // https://github.com/CSSLint/csslint/wiki/Command-line-interface#--ignore - $ignore_list = variable_get('advagg_validator_csslint_ignore', ADVAGG_VALIDATOR_CSSLINT_IGNORE); - if (is_array($ignore_list)) { - $ignore_list = implode(',', $ignore_list); - } $form['#attached']['js'][] = array( - 'data' => array('csslint' => array('ignore' => $ignore_list)), - 'type' => 'setting', + 'data' => drupal_get_path('module', 'advagg_validator') . '/advagg_validator.js', + 'type' => 'file', ); return $form; @@ -92,34 +53,16 @@ function advagg_validator_admin_css_lint_form() { * * @return array * form array. - * - * @ingroup advagg_forms */ function advagg_validator_admin_js_hint_form() { drupal_set_title(t('AdvAgg: JavaScript Validator using JSHINT')); - advagg_display_message_if_requirements_not_met(); - - // Tell user to update library if a new version is available. - $module_name = 'advagg_validator'; - $lib_name = 'jshint'; - $form = array(); - list($description) = advagg_get_version_description($lib_name, $module_name, TRUE); - if (!empty($description)) { - $form['advagg_version_msg'] = array( - '#markup' => "<p>{$description}</p>", - ); - } $form = advagg_validator_admin_form_generator('js', TRUE); - $library = advagg_get_library('jshint', 'advagg_validator'); - if (!empty($library['installed'])) { - libraries_load('jshint'); - $form['#attached']['libraries_load'][] = array('jshint'); - } - else { - $form['#attached']['js'] = $library['variants']['external']['files']['js']; - } + $form['#attached']['js'][] = array( + 'data' => '//rawgithub.com/jshint/jshint/master/dist/jshint.js', + 'type' => 'external', + ); $form['#attached']['js'][] = array( 'data' => drupal_get_path('module', 'advagg_validator') . '/advagg_validator.js', 'type' => 'file', @@ -150,15 +93,6 @@ function advagg_validator_admin_js_hint_form() { 'CKEDITOR' => FALSE, ), ); - - // Comma separated code. - // https://jslinterrors.com/ - $ignore_list = variable_get('advagg_validator_jshint_ignore', ADVAGG_VALIDATOR_JSHINT_IGNORE); - if (is_array($ignore_list)) { - $ignore_list = implode(',', $ignore_list); - } - $settings['ignore'] = $ignore_list; - $form['#attached']['js'][] = array( 'data' => array('jshint' => $settings), 'type' => 'setting', @@ -192,7 +126,7 @@ function advagg_validator_admin_form_generator($type, $run_client_side) { $point = &$form; $built = array(); foreach ($levels as $key => $value) { - // Build direcotry structure. + // Build direcotry structure, $built[] = $value; $point = &$point[$value]; if (!is_array($point)) { @@ -298,18 +232,14 @@ function advagg_validator_admin_form_generator($type, $run_client_side) { return $form; } -/** - * @addtogroup advagg_forms_callback - * @{ - */ +// Submit callbacks. /** - * Submit callback, display file info in a drupal message. + * Display file info in a drupal message. */ function advagg_validator_admin_test_advagg_css_submit($form, &$form_state) { module_load_include('inc', 'advagg_validator', 'advagg_validator'); - // @codingStandardsIgnoreLine if (!empty($form_state['input']['ajax_page_state'])) { return; } @@ -352,7 +282,6 @@ function advagg_validator_admin_test_advagg_css_callback($form, &$form_state) { * Display file info in a drupal message. */ function advagg_validator_admin_test_advagg_css_directory_submit($form, &$form_state) { - // @codingStandardsIgnoreLine if (!empty($form_state['input']['ajax_page_state']) || empty($form_state['values']['op']) || strpos($form_state['values']['op'], t('Check this Directory:')) !== 0) { return; } @@ -427,7 +356,6 @@ function advagg_validator_admin_test_advagg_css_directory_callback($form, &$form * Display file info in a drupal message. */ function advagg_validator_admin_test_advagg_css_subdirectory_submit($form, &$form_state) { - // @codingStandardsIgnoreLine if (!empty($form_state['input']['ajax_page_state']) || empty($form_state['values']['op']) || strpos($form_state['values']['op'], t('Check this Directory and all Subdirectories:')) !== 0) { return; } @@ -502,7 +430,6 @@ function advagg_validator_admin_test_advagg_css_subdirectory_callback($form, &$f function advagg_validator_admin_test_all_css_submit($form, &$form_state) { module_load_include('inc', 'advagg_validator', 'advagg_validator'); - // @codingStandardsIgnoreLine if (!empty($form_state['input']['ajax_page_state'])) { return; } @@ -541,10 +468,6 @@ function advagg_validator_admin_test_all_css_callback($form, &$form_state) { return '<div id="advagg-validator-css-validator-ajax">' . $output . '</div>'; } -/** - * @} End of "addtogroup advagg_forms_callback". - */ - /** * Do not display info on a file if it is valid. * diff --git a/sites/all/modules/contrib/advagg/advagg_validator/advagg_validator.advagg.inc b/sites/all/modules/contrib/advagg/advagg_validator/advagg_validator.advagg.inc deleted file mode 100644 index e5d58fc897..0000000000 --- a/sites/all/modules/contrib/advagg/advagg_validator/advagg_validator.advagg.inc +++ /dev/null @@ -1,31 +0,0 @@ -<?php - -/** - * @file - * Advanced CSS/JS aggregation validator module. - */ - -/** - * @addtogroup advagg_hooks - * @{ - */ - -/** - * Implements hook_advagg_update_github_versions_alter(). - */ -function advagg_validator_advagg_update_github_versions_alter(&$projects) { - $projects['csslint'] = array( - 'callback' => 'advagg_get_github_version', - 'url' => 'https://cdn.jsdelivr.net/gh/CSSLint/csslint@master/package.json', - 'variable_name' => 'advagg_validator_github_version_csslint', - ); - $projects['jshint'] = array( - 'callback' => 'advagg_get_github_version', - 'url' => 'https://cdn.jsdelivr.net/gh/jshint/jshint@master/package.json', - 'variable_name' => 'advagg_validator_github_version_jshint', - ); -} - -/** - * @} End of "addtogroup advagg_hooks". - */ diff --git a/sites/all/modules/contrib/advagg/advagg_validator/advagg_validator.inc b/sites/all/modules/contrib/advagg/advagg_validator/advagg_validator.inc index e4177899ee..9686d53e84 100644 --- a/sites/all/modules/contrib/advagg/advagg_validator/advagg_validator.inc +++ b/sites/all/modules/contrib/advagg/advagg_validator/advagg_validator.inc @@ -16,7 +16,7 @@ function advagg_validator_test_advagg_css($options = array()) { $query_files = db_select('advagg_files', 'af') ->fields('af', array('filename_hash', 'filename')) ->condition('af.filetype', 'css') - ->orderBy('filename', 'ASC') + ->orderBy('filename', 'DESC') ->execute() ->fetchAllKeyed(); $files = array_values($query_files); @@ -74,7 +74,7 @@ function advagg_validator_test_css_files($files, $options = array()) { continue; } - $file_contents = (string) @advagg_file_get_contents($filename); + $file_contents = file_get_contents($filename); $lines = file($filename); $filename_hash = drupal_hash_base64($filename); $content_hash = drupal_hash_base64($file_contents); @@ -134,7 +134,7 @@ function advagg_validator_test_css_files($files, $options = array()) { db_merge('advagg_validator') ->key(array( 'filename_hash' => $record['filename_hash'], - )) + )) ->fields($record) ->execute(); } @@ -153,9 +153,9 @@ function advagg_validator_test_css_files($files, $options = array()) { * @return array * Info from the w3c server. */ -function advagg_validator_test_css_file_w3c($filename, array &$validator_options = array()) { +function advagg_validator_test_css_file_w3c($filename, &$validator_options = array()) { // Get CSS files contents. - $validator_options['text'] = (string) @advagg_file_get_contents($filename); + $validator_options['text'] = file_get_contents($filename); // Add in defaults. $validator_options += array( @@ -175,7 +175,7 @@ function advagg_validator_test_css_file_w3c($filename, array &$validator_options // Send request. $result = drupal_http_request($url); if (!empty($result->data) && $result->code == 200) { - // Parse XML and return info. + // Parse XML & return info. $return = advagg_validator_parse_soap_response_w3c($result->data); $return['filename'] = $filename; if (isset($validator_options['text'])) { @@ -298,7 +298,7 @@ function advagg_validator_dom_extractor($dom) { * An associative array (keyed on the chosen key) of objects with 'uri', * 'filename', and 'name' members corresponding to the matching files. */ -function advagg_validator_file_scan_directory($dir, $mask, array $options = array(), $depth = 0) { +function advagg_validator_file_scan_directory($dir, $mask, $options = array(), $depth = 0) { // Merge in defaults. $options += array( 'nomask' => '/(\.\.?|CVS)$/', diff --git a/sites/all/modules/contrib/advagg/advagg_validator/advagg_validator.info b/sites/all/modules/contrib/advagg/advagg_validator/advagg_validator.info index 82a1c39b63..5845cb7c89 100644 --- a/sites/all/modules/contrib/advagg/advagg_validator/advagg_validator.info +++ b/sites/all/modules/contrib/advagg/advagg_validator/advagg_validator.info @@ -1,14 +1,14 @@ name = AdvAgg CSS/JS Validator -description = Validate the CSS and JS files used in Aggregation for syntax errors. +description = Validate the CSS & JS files used in Aggregation for syntax errors. package = Advanced CSS/JS Aggregation core = 7.x dependencies[] = advagg -recommends[] = libraries configure = admin/config/development/performance/advagg/validator -; Information added by Drupal.org packaging script on 2020-11-19 -version = "7.x-2.35" +; Information added by Drupal.org packaging script on 2015-04-14 +version = "7.x-2.8" core = "7.x" project = "advagg" -datestamp = "1605792717" +datestamp = "1429049283" + diff --git a/sites/all/modules/contrib/advagg/advagg_validator/advagg_validator.install b/sites/all/modules/contrib/advagg/advagg_validator/advagg_validator.install index e513487d27..618b4e91e0 100644 --- a/sites/all/modules/contrib/advagg/advagg_validator/advagg_validator.install +++ b/sites/all/modules/contrib/advagg/advagg_validator/advagg_validator.install @@ -5,69 +5,6 @@ * Handles AdvAgg Validator installation and upgrade tasks. */ -/** - * @addtogroup hooks - * @{ - */ - -/** - * Implements hook_install(). - */ -function advagg_validator_install() { - module_load_include('install', 'advagg', 'advagg'); - $tables = array( - 'advagg_validator' => array( - 'filename_hash', - 'content_hash', - ), - ); - - $schema = advagg_validator_schema(); - foreach ($tables as $table => $fields) { - // Change utf8_bin to ascii_bin. - advagg_install_change_table_collation($table, $fields, 'ascii_bin', $schema[$table]['fields']); - } -} - -/** - * Implements hook_requirements(). - */ -function advagg_validator_requirements($phase) { - $requirements = array(); - // Ensure translations don't break at install time. - $t = get_t(); - - // If not at runtime, return here. - if ($phase !== 'runtime') { - return $requirements; - } - - // Check version. - $module_name = 'advagg_validator'; - $lib_name = 'csslint'; - list($description, $info) = advagg_get_version_description($lib_name, $module_name, TRUE); - if (!empty($description)) { - $requirements["{$module_name}_{$lib_name}_updates"] = array( - 'title' => $t('@module_name', array('@module_name' => $info['name'])), - 'severity' => REQUIREMENT_WARNING, - 'value' => $t('The @name library needs to be updated.', array('@name' => $lib_name)), - 'description' => $description, - ); - } - $lib_name = 'jshint'; - list($description, $info) = advagg_get_version_description($lib_name, $module_name, TRUE); - if (!empty($description)) { - $requirements["{$module_name}_{$lib_name}_updates"] = array( - 'title' => $t('@module_name', array('@module_name' => $info['name'])), - 'severity' => REQUIREMENT_WARNING, - 'value' => $t('The @name library needs to be updated.', array('@name' => $lib_name)), - 'description' => $description, - ); - } - - return $requirements; -} - /** * Implements hook_schema(). */ @@ -83,24 +20,20 @@ function advagg_validator_schema() { 'not null' => TRUE, ), 'filename_hash' => array( - 'description' => 'Hash of path and filename. Used to join tables and for lookup.', - 'type' => 'char', + 'description' => 'Hash of path and filename. Used to join tables & for lookup.', + 'type' => 'varchar', 'length' => 43, 'not null' => TRUE, 'default' => '', 'binary' => TRUE, - 'collation' => 'ascii_bin', - 'charset' => 'ascii', ), 'content_hash' => array( 'description' => 'Hash of the file content. Used to see if the file has changed.', - 'type' => 'char', + 'type' => 'varchar', 'length' => 43, 'not null' => TRUE, 'default' => '', 'binary' => TRUE, - 'collation' => 'ascii_bin', - 'charset' => 'ascii', ), 'filetype' => array( 'description' => 'Filetype.', @@ -130,6 +63,8 @@ function advagg_validator_schema() { } /** + * Implements hook_update_N(). + * * Update the schema making the varchar columns utf8_bin in MySQL. */ function advagg_validator_update_7201(&$sandbox) { @@ -151,30 +86,3 @@ function advagg_validator_update_7201(&$sandbox) { return t('The following columns inside of these database tables where converted to utf8_bin: <br />@data', array('@data' => print_r($tables_altered, TRUE))); } - -/** - * Update schema making the varchar columns char. Change utf8_bin to ascii_bin. - */ -function advagg_validator_update_7202(&$sandbox) { - module_load_include('install', 'advagg', 'advagg'); - $tables = array( - 'advagg_validator' => array( - 'filename_hash', - 'content_hash', - ), - ); - - $schema = advagg_validator_schema(); - foreach ($tables as $table => $fields) { - foreach ($fields as $field) { - // Change varchar to char. - db_change_field($table, $field, $field, $schema[$table]['fields'][$field]); - } - // Change utf8_bin to ascii_bin. - advagg_install_change_table_collation($table, $fields, 'ascii_bin', $schema[$table]['fields']); - } -} - -/** - * @} End of "addtogroup hooks". - */ diff --git a/sites/all/modules/contrib/advagg/advagg_validator/advagg_validator.js b/sites/all/modules/contrib/advagg/advagg_validator/advagg_validator.js index cf86cd6d71..a6d2a3a24d 100644 --- a/sites/all/modules/contrib/advagg/advagg_validator/advagg_validator.js +++ b/sites/all/modules/contrib/advagg/advagg_validator/advagg_validator.js @@ -1,18 +1,18 @@ -/** - * @file - * Run JSHINT in the browser against the servers JS. - */ - /* global jQuery:false */ /* global Drupal:false */ /* global JSHINT:false */ /* global CSSLint:false */ +/** + * @file + * Run JSHINT in the browser against the servers JS. + */ + /** * Have clicks to advagg_validator_js classes run JSHINT clientside. */ (function ($) { - 'use strict'; + "use strict"; Drupal.behaviors.advagg_validator_js_simple = { attach: function (context, settings) { $('.advagg_validator_js', context).click(function (context) { @@ -21,7 +21,7 @@ // Clear out the results. $(results).html(''); // Loop over each filename. - $.each($(this).siblings('.filenames'), function () { + $.each($(this).siblings('.filenames'), function() { var filename = $(this).val(); if (filename) { try { @@ -31,14 +31,14 @@ dataType: 'text', async: false }); - if (!JSHINT(x.responseText, Drupal.settings.jshint, Drupal.settings.jshint.predef)) { - $(results).append('<p><h4>' + filename + '</h4><ul>'); + if (JSHINT(x.responseText, Drupal.settings.jshint, Drupal.settings.jshint.predef)) { + $(results).append('<h4>' + filename + ' Passed!</h4>'); + } + else { + $(results).append('<p><h4>' + filename + ' Failed!</h4>'); + $(results).append('<ul>'); for (var i = 0; i < JSHINT.errors.length; i++) { - var ignore = (Drupal.settings.jshint && Drupal.settings.jshint.ignore) ? Drupal.settings.jshint.ignore.split(',') : []; - if (ignore.indexOf(JSHINT.errors[i].code) === -1) { - var w = JSHINT.errors[i].reason + ' (line ' + JSHINT.errors[i].line + ', col ' + JSHINT.errors[i].character + ', rule ' + JSHINT.errors[i].code + ')'; - $(results).append('<li class="' + JSHINT.errors[i].id.replace(/[()]/g, '') + '">' + w.replace(/ /g, ' ') + '</li>'); - } + $(results).append('<li><b>' + JSHINT.errors[i].line + ':</b> ' + JSHINT.errors[i].reason + '</li>'); } $(results).append('</ul></p>'); } @@ -59,7 +59,7 @@ * Have clicks to advagg_validator_recursive_js classes run JSHINT clientside. */ (function ($) { - 'use strict'; + "use strict"; Drupal.behaviors.advagg_validator_js_recursive = { attach: function (context, settings) { $('.advagg_validator_recursive_js', context).click(function (context) { @@ -68,7 +68,7 @@ // Clear out the results. $(results).html(''); // Loop over each filename. - $.each($(this).parent().find('.filenames'), function () { + $.each($(this).parent().find('.filenames'), function() { var filename = $(this).val(); if (filename) { try { @@ -78,14 +78,13 @@ dataType: 'text', async: false }); - if (!JSHINT(x.responseText, Drupal.settings.jshint, Drupal.settings.jshint.predef)) { - $(results).append('<p><h4>' + filename + '</h4><ul>'); + if (JSHINT(x.responseText, Drupal.settings.jshint, Drupal.settings.jshint.predef)) { + $(results).append('<h4>' + filename + ' Passed!</h4>'); + } else { + $(results).append('<p><h4>' + filename + ' Failed!</h4>'); + $(results).append('<ul>'); for (var i = 0; i < JSHINT.errors.length; i++) { - var ignore = (Drupal.settings.jshint && Drupal.settings.jshint.ignore) ? Drupal.settings.jshint.ignore.split(',') : []; - if (ignore.indexOf(JSHINT.errors[i].code) === -1) { - var w = JSHINT.errors[i].reason + ' (line ' + JSHINT.errors[i].line + ', col ' + JSHINT.errors[i].character + ', rule ' + JSHINT.errors[i].code + ')'; - $(results).append('<li class="' + JSHINT.errors[i].id.replace(/[()]/g, '') + '">' + w.replace(/ /g, ' ') + '</li>'); - } + $(results).append('<li><b>' + JSHINT.errors[i].line + ':</b> ' + JSHINT.errors[i].reason + '</li>'); } $(results).append('</ul></p>'); } @@ -106,7 +105,7 @@ * Have clicks to advagg_validator_css classes run CSSLint clientside. */ (function ($) { - 'use strict'; + "use strict"; Drupal.behaviors.advagg_validator_css_simple = { attach: function (context, settings) { $('.advagg_validator_css', context).click(function (context) { @@ -115,7 +114,7 @@ // Clear out the results. $(results).html(''); // Loop over each filename. - $.each($(this).siblings('.filenames'), function () { + $.each($(this).siblings('.filenames'), function() { var filename = $(this).val(); if (filename) { try { @@ -128,13 +127,11 @@ var y = CSSLint.verify(x.responseText); var z = y.messages; - $(results).append('<p><h4>' + filename + '</h4><ul>'); + $(results).append('<p><h4>' + filename + '</h4>'); + $(results).append('<ul>'); for (var i = 0, len = z.length; i < len; i++) { - var ignore = (Drupal.settings.csslint && Drupal.settings.csslint.ignore) ? Drupal.settings.csslint.ignore.split(',') : []; - if (ignore.indexOf(z[i].rule.id) === -1) { - var w = z[i].message + ' (line ' + z[i].line + ', col ' + z[i].col + ', rule ' + z[i].rule.id + ')'; - $(results).append('<li class="' + z[i].type + '">' + w.replace(/ /g, ' ') + '</li>'); - } + var w = z[i].message + ' (line ' + z[i].line + ', col ' + z[i].col + ')'; + $(results).append('<li class="' + z[i].type + '">' + w.replace(/ /g, ' ') + '</li>'); } $(results).append('</ul></p>'); } @@ -154,7 +151,7 @@ * Have clicks to advagg_validator_recursive_css classes run CSSLint clientside. */ (function ($) { - 'use strict'; + "use strict"; Drupal.behaviors.advagg_validator_css_recursive = { attach: function (context, settings) { $('.advagg_validator_recursive_css', context).click(function (context) { @@ -163,7 +160,7 @@ // Clear out the results. $(results).html(''); // Loop over each filename. - $.each($(this).parent().find('.filenames'), function () { + $.each($(this).parent().find('.filenames'), function() { var filename = $(this).val(); if (filename) { try { @@ -176,13 +173,11 @@ var y = CSSLint.verify(x.responseText); var z = y.messages; - $(results).append('<p><h4>' + filename + '</h4><ul>'); + $(results).append('<p><h4>' + filename + '</h4>'); + $(results).append('<ul>'); for (var i = 0, len = z.length; i < len; i++) { - var ignore = (Drupal.settings.csslint && Drupal.settings.csslint.ignore) ? Drupal.settings.csslint.ignore.split(',') : []; - if (ignore.indexOf(z[i].rule.id) === -1) { - var w = z[i].message + ' (line ' + z[i].line + ', col ' + z[i].col + ', rule ' + z[i].rule.id + ')'; - $(results).append('<li class="' + z[i].type + '">' + w.replace(/ /g, ' ') + '</li>'); - } + var w = z[i].message + ' (line ' + z[i].line + ', col ' + z[i].col + ')'; + $(results).append('<li class="' + z[i].type + '">' + w.replace(/ /g, ' ') + '</li>'); } $(results).append('</ul></p>'); } diff --git a/sites/all/modules/contrib/advagg/advagg_validator/advagg_validator.module b/sites/all/modules/contrib/advagg/advagg_validator/advagg_validator.module index 724f75d89a..b6c48db377 100644 --- a/sites/all/modules/contrib/advagg/advagg_validator/advagg_validator.module +++ b/sites/all/modules/contrib/advagg/advagg_validator/advagg_validator.module @@ -5,40 +5,12 @@ * Advanced aggregation validation module. */ -/** - * @addtogroup default_variables - * @{ - */ - -/** - * Default css lint ignore settings. - */ -define('ADVAGG_VALIDATOR_CSSLINT_IGNORE', ''); - -/** - * Default js hint ignore settings. - */ -define('ADVAGG_VALIDATOR_JSHINT_IGNORE', ''); - -/** - * @} End of "addtogroup default_variables". - */ - -/** - * @addtogroup hooks - * @{ - */ - /** * Implements hook_menu(). */ function advagg_validator_menu() { - // Make sure the advagg_admin_config_root_path() function is available. - drupal_load('module', 'advagg'); - $file_path = drupal_get_path('module', 'advagg_validator'); $config_path = advagg_admin_config_root_path(); - $items = array(); $items[$config_path . '/advagg/validate_css_w3'] = array( 'title' => 'Validate CSS via W3', @@ -76,129 +48,3 @@ function advagg_validator_menu() { return $items; } - -/** - * Implements hook_menu_alter(). - */ -function advagg_validator_menu_alter(&$items) { - // Make sure the advagg_admin_config_root_path() function is available. - drupal_load('module', 'advagg'); - $config_path = advagg_admin_config_root_path(); - - if (!isset($items[$config_path . '/advagg'])) { - // If the advagg module is not enabled, redirect the /advagg path to - // /advagg/validate_css_w3. - $items[$config_path . '/advagg'] = array( - 'title' => 'Advanced CSS/JS Aggregation', - 'type' => MENU_LOCAL_TASK, - 'weight' => 1, - 'description' => $items[$config_path . '/advagg/validate_css_w3']['description'], - 'page callback' => 'drupal_goto', - 'page arguments' => array($config_path . '/advagg/validate_css_w3'), - 'access arguments' => array('administer site configuration'), - ); - } - if (!isset($items[$config_path . '/default'])) { - // Make sure the performance page has a default path. - $items[$config_path . '/default'] = array( - 'title' => 'Performance', - 'type' => MENU_DEFAULT_LOCAL_TASK, - 'file path' => drupal_get_path('module', 'system'), - 'weight' => -10, - ); - } -} - -/** - * @} End of "addtogroup hooks". - */ - -/** - * @addtogroup 3rd_party_hooks - * @{ - */ - -/** - * Implements hook_libraries_info(). - */ -function advagg_validator_libraries_info() { - $libraries['csslint'] = array( - 'name' => 'csslint', - 'vendor url' => 'https://github.com/CSSLint/csslint', - 'download url' => 'https://github.com/CSSLint/csslint/archive/master.zip', - 'version arguments' => array( - 'file' => 'package.json', - 'pattern' => '/"version":\\s+"([0-9\.]+)"/', - 'lines' => 10, - ), - 'remote' => array( - 'callback' => 'advagg_get_github_version_json', - 'url' => 'https://cdn.jsdelivr.net/gh/CSSLint/csslint@master/package.json', - ), - 'files' => array( - 'js' => array( - 'dist/csslint.js' => array( - 'type' => 'file', - ), - ), - ), - 'variants' => array(), - ); - // Get the latest tagged version for external file loading. - $version = advagg_get_remote_libraries_version('csslint', $libraries['csslint']); - $libraries['csslint']['variants'] += array( - 'external' => array( - 'files' => array( - 'js' => array( - "https://cdn.jsdelivr.net/gh/CSSLint/csslint@v{$version}/dist/csslint.js" => array( - 'type' => 'external', - 'data' => "https://cdn.jsdelivr.net/gh/CSSLint/csslint@v{$version}/dist/csslint.js", - ), - ), - ), - ), - ); - - $libraries['jshint'] = array( - 'name' => 'jshint', - 'vendor url' => 'https://github.com/jshint/jshint', - 'download url' => 'https://github.com/jshint/jshint/archive/master.zip', - 'version arguments' => array( - 'file' => 'package.json', - 'pattern' => '/"version":\\s+"([0-9\.]+)"/', - 'lines' => 10, - ), - 'remote' => array( - 'callback' => 'advagg_get_github_version_json', - 'url' => 'https://cdn.jsdelivr.net/gh/jshint/jshint@master/package.json', - ), - 'files' => array( - 'js' => array( - 'dist/jshint.js' => array( - 'type' => 'file', - ), - ), - ), - 'variants' => array(), - ); - // Get the latest tagged version for external file loading. - $version = advagg_get_remote_libraries_version('jshint', $libraries['jshint']); - $libraries['jshint']['variants'] += array( - 'external' => array( - 'files' => array( - 'js' => array( - "https://cdn.jsdelivr.net/gh/jshint/jshint@{$version}/dist/jshint.js" => array( - 'type' => 'external', - 'data' => "https://cdn.jsdelivr.net/gh/jshint/jshint@{$version}/dist/jshint.js", - ), - ), - ), - ), - ); - - return $libraries; -} - -/** - * @} End of "addtogroup 3rd_party_hooks". - */ diff --git a/sites/all/modules/contrib/advagg/composer.json b/sites/all/modules/contrib/advagg/composer.json deleted file mode 100644 index ee61c50dcb..0000000000 --- a/sites/all/modules/contrib/advagg/composer.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "name": "drupal/advagg", - "description": "Improved aggregation of CSS/JS files to speed up page load times and prevent 404s.", - "type": "drupal-module", - "license": "GPL-2.0+", - "homepage": "https://drupal.org/project/advagg", - "authors": [ - { - "name": "Mike Carper (mikeytown2)", - "homepage": "https://www.drupal.org/u/mikeytown2", - "role": "Creator, Maintainer" - } - ], - "support": { - "issues": "https://drupal.org/project/issues/advagg", - "irc": "irc://irc.freenode.org/drupal-contribute", - "source": "https://cgit.drupalcode.org/advagg" - } -} diff --git a/sites/all/modules/contrib/advagg/tests/advagg.test b/sites/all/modules/contrib/advagg/tests/advagg.test index 4b67fd7e24..da9227354e 100644 --- a/sites/all/modules/contrib/advagg/tests/advagg.test +++ b/sites/all/modules/contrib/advagg/tests/advagg.test @@ -6,22 +6,7 @@ */ /** - * @defgroup advagg_tests Advanced Aggregates Tests - * - * @{ - * Advanced Aggregates testing functionality. - */ - -// Include all files in the project for simple sanity checking. -$files = file_scan_directory(drupal_get_path('module', 'advagg'), "/.*\.(inc|module|install|php)$/", array( - 'nomask' => '/(\\.\\.?|CVS|tpl\.php)$/', -)); -foreach ($files as $file) { - include_once DRUPAL_ROOT . '/' . $file->uri; -} - -/** - * Resets static variables related to adding CSS or JS to a page. + * Resets static variables related to adding CSS to a page. */ function advagg_test_reset_statics() { drupal_static_reset('drupal_add_css'); @@ -29,68 +14,6 @@ function advagg_test_reset_statics() { drupal_static_reset('drupal_get_library'); drupal_static_reset('drupal_add_js'); drupal_static_reset('drupal_add_js:jquery_added'); - drupal_static_reset('advagg_get_js'); -} - -/** - * Generates a large CSS string. - * - * @param int $selector_count - * The number of selectors to generate. - * @param int $denominator - * The max string length of the selector names. - * - * @return string - * Generated CSS string. - */ -function advagg_test_generate_selector_css($selector_count, $denominator = 5) { - static $count = 0; - $pool = array_merge(range('a', 'z'), range('A', 'Z')); - $selector_count = 10000; - $css = ''; - while ($selector_count > 0) { - $rand_string = advagg_test_randon_string(($selector_count % $denominator) + 3, $pool); - $css .= ".{$rand_string}, "; - --$selector_count; - } - $css .= "#last$count {z-index: 2; margin-left: -1px; content: \" \"; display: table;}"; - - ++$count; - return $css; -} - -/** - * Generates random string. - * - * @param int $length - * How many characters will this string contain. - * @param array $pool - * Array of characters to use. - * - * @return string - * Random string. - */ -function advagg_test_randon_string($length, array $pool) { - $string = ''; - $count = count($pool); - for ($i = 0; $i < $length; $i++) { - $string .= $pool[mt_rand(0, $count - 1)]; - } - return $string; -} - -/** - * Strip the codingStandardsIgnoreFile string from the input. - * - * @param string $input - * The input string. - * - * @return string - * The input string with codingStandardsIgnoreFile removed from it. - */ -function advagg_test_remove_sniffer_comments($input) { - $string = "/* @codingStandardsIgnoreFile */\n"; - return str_replace($string, '', $input); } /** @@ -99,25 +22,9 @@ function advagg_test_remove_sniffer_comments($input) { class AdvAggCascadingStylesheetsTestCase extends DrupalWebTestCase { /** * Store configured value for CSS preprocessing. - * - * @var bool */ protected $preprocessCss = NULL; - /** - * Create user. - * - * @var object - */ - protected $bigUser; - - /** - * Theme settings. - * - * @var array - */ - protected $themes; - /** * Provide information to the UI for this test. */ @@ -135,43 +42,21 @@ class AdvAggCascadingStylesheetsTestCase extends DrupalWebTestCase { public function setUp() { // Enable any modules required for the test. This should be an array of // module names. - parent::setUp(array( - 'advagg', - 'php', - 'locale', - 'common_test', - 'menu_test', - 'color', - )); + parent::setUp(array('advagg', 'php', 'locale', 'common_test', 'menu_test')); // Include the advagg.module file. drupal_load('module', 'advagg'); module_load_include('inc', 'advagg', 'advagg'); + // Reset drupal_add_css() before each test. + advagg_test_reset_statics(); // Set settings for testing. $GLOBALS['conf']['advagg_convert_absolute_to_relative_path'] = FALSE; $GLOBALS['conf']['advagg_convert_absolute_to_protocol_relative_path'] = FALSE; $GLOBALS['conf']['advagg_force_https_path'] = FALSE; - $GLOBALS['conf']['advagg_skip_file_create_url_inside_css'] = FALSE; // Disable CSS preprocessing. $this->preprocessCss = variable_get('preprocess_css', 0); variable_set('preprocess_css', 0); - - // Create users. - $this->bigUser = $this->drupalCreateUser(array('administer themes')); - - // This tests the color module in both Bartik and Garland. - $this->themes = array( - 'bartik' => array( - 'palette_input' => 'palette[bg]', - 'scheme' => 'slate', - 'scheme_color' => '#3b3b3b', - ), - ); - theme_enable(array_keys($this->themes)); - - // Reset drupal_add_css() before each test. - advagg_test_reset_statics(); } /** @@ -187,34 +72,6 @@ class AdvAggCascadingStylesheetsTestCase extends DrupalWebTestCase { * Tests rendering the stylesheets. */ public function testRenderFile() { - foreach ($this->themes as $theme => $test_values) { - variable_set('theme_default', $theme); - $settings_path = 'admin/appearance/settings/' . $theme; - - $this->drupalLogin($this->bigUser); - $this->drupalGet($settings_path); - $this->assertResponse(200); - $edit['scheme'] = ''; - $edit[$test_values['palette_input']] = '#123456'; - $this->drupalPost($settings_path, $edit, t('Save configuration')); - - // Reset drupal_add_css() before each test. - $GLOBALS['conf']['advagg_convert_absolute_to_relative_path'] = FALSE; - $GLOBALS['conf']['advagg_convert_absolute_to_protocol_relative_path'] = FALSE; - advagg_test_reset_statics(); - // Add the css file. - $stylesheets = variable_get('color_' . $theme . '_stylesheets', array()); - drupal_add_css($stylesheets[0]); - $css = file_create_url($stylesheets[0]); - // Get the render array. - $full_css = advagg_get_css(); - $styles = drupal_render($full_css); - $this->assertTrue(strpos($styles, $css) !== FALSE, "Rendered CSS includes the added stylesheet ($css)."); - } - - // Reset drupal_add_css() before each test. - advagg_test_reset_statics(); - // Add the css file. $css = drupal_get_path('module', 'simpletest') . '/simpletest.css'; drupal_add_css($css); @@ -222,7 +79,7 @@ class AdvAggCascadingStylesheetsTestCase extends DrupalWebTestCase { $full_css = advagg_get_css(); // Render the CSS. $styles = drupal_render($full_css); - $this->assertTrue(strpos($styles, $css) > 0, "Rendered CSS includes the added stylesheet ($css)."); + $this->assertTrue(strpos($styles, $css) > 0, 'Rendered CSS includes the added stylesheet.'); // Verify that newlines are properly added inside style tags. $query_string = variable_get('css_js_query_string', '0'); $css_processed = "<style type=\"text/css\" media=\"all\">\n@import url(\"" . check_plain(file_create_url($css)) . "?" . $query_string . "\");\n</style>"; @@ -355,28 +212,13 @@ class AdvAggCascadingStylesheetsTestCase extends DrupalWebTestCase { global $language; $language->direction = LANGUAGE_RTL; $path = drupal_get_path('module', 'system'); - drupal_add_css($path . '/system.base.css', array('group' => CSS_SYSTEM)); - drupal_add_css($path . '/system.menus.css', array('group' => CSS_SYSTEM)); - drupal_add_css($path . '/system.theme.css', array('group' => CSS_SYSTEM)); + drupal_add_css($path . '/system.base.css'); // Get the render array. $full_css = advagg_get_css(); // Render the CSS. $styles = drupal_render($full_css); // Check to see if system.base-rtl.css was also added. - $base_pos = strpos($styles, $path . '/system.base.css'); - $base_rtl_pos = strpos($styles, $path . '/system.base-rtl.css'); - $this->assert($base_rtl_pos !== FALSE, 'CSS is alterable as right to left overrides are added.'); - $this->assert($base_pos < $base_rtl_pos, 'system.base-rtl.css is added after system.base.css.'); - // Check to see if system.menus-rtl.css was also added. - $menus_pos = strpos($styles, $path . '/system.menus.css'); - $menus_rtl_pos = strpos($styles, $path . '/system.menus-rtl.css'); - $this->assert($menus_rtl_pos !== FALSE, 'CSS is alterable as right to left overrides are added.'); - $this->assert($menus_pos < $menus_rtl_pos, 'system.menus-rtl.css is added after system.menus.css.'); - // Check to see if system.theme-rtl.css was also added. - $theme_pos = strpos($styles, $path . '/system.theme.css'); - $theme_rtl_pos = strpos($styles, $path . '/system.theme-rtl.css'); - $this->assert($theme_rtl_pos !== FALSE, 'CSS is alterable as right to left overrides are added.'); - $this->assert($theme_pos < $theme_rtl_pos, 'system.theme-rtl.css is added after system.theme.css.'); + $this->assert(strpos($styles, $path . '/system.base-rtl.css') !== FALSE, 'CSS is alterable as right to left overrides are added.'); // Change the language back to left to right. $language->direction = LANGUAGE_LTR; @@ -501,56 +343,6 @@ class AdvAggCascadingStylesheetsTestCase extends DrupalWebTestCase { $this->assertText('Custom theme: seven. Actual theme: seven.', 'The result of hook_custom_theme() does not override what was set in a theme callback.'); $this->assertRaw('seven/style.css', "The Seven theme's CSS appears on the page."); - // - // Test css split file processing. - // Generate a massive css file. - $css_string = advagg_test_generate_selector_css(1000); - $css_string .= '@media print {' . advagg_test_generate_selector_css(1000) . '}'; - $css_string .= advagg_test_generate_selector_css(1000); - $css_string .= '@media screen {' . advagg_test_generate_selector_css(1000) . '}'; - $css_string .= advagg_test_generate_selector_css(1000); - $css_string .= '@media print {' . advagg_test_generate_selector_css(1000) . '}'; - $css_string .= advagg_test_generate_selector_css(9000); - $css_string .= '@media print {' . advagg_test_generate_selector_css(9000) . '}'; - $css_string .= advagg_test_generate_selector_css(9000); - $css_string .= '@media screen {' . advagg_test_generate_selector_css(9000) . '}'; - $css_string .= '@media print {' . advagg_test_generate_selector_css(50) . '}'; - $css_string .= '@media screen {' . advagg_test_generate_selector_css(50) . '}'; - $css_string .= advagg_test_generate_selector_css(10); - $css_string .= '@media print {' . advagg_test_generate_selector_css(50) . '}'; - $css_string .= '@media screen {' . advagg_test_generate_selector_css(50) . '}'; - $css_string .= '@media print {' . advagg_test_generate_selector_css(50) . '}'; - $css_string .= '@media screen {' . advagg_test_generate_selector_css(50) . '}'; - $css_string .= advagg_test_generate_selector_css(10); - $css_string .= advagg_test_generate_selector_css(10); - $css_string .= advagg_test_generate_selector_css(10); - $css_string .= advagg_test_generate_selector_css(10); - $css_string .= advagg_test_generate_selector_css(15000); - $css_string .= '@media print {' . advagg_test_generate_selector_css(15000) . '}'; - $css_string .= advagg_test_generate_selector_css(10); - $css_string .= advagg_test_generate_selector_css(10); - $css_string .= advagg_test_generate_selector_css(10); - $css_string .= advagg_test_generate_selector_css(10); - $css_string .= advagg_test_generate_selector_css(10); - $file_info = array( - // Use a file that exists but isn't actually being used here. - 'data' => drupal_get_path('module', 'advagg') . '/tests/css_test_files/advagg.css', - ); - $before_selector_count = advagg_count_css_selectors($css_string); - // Split the huge css file. - $parts = advagg_split_css_file($file_info, $css_string); - $after = ''; - foreach ($parts as $part) { - // Get written file. - $after .= "\n" . (string) @advagg_file_get_contents($part['data']); - // Cleanup. - unlink($part['data']); - } - // Note that a diff of the text can not be used for this test. Counting - // selectors is close enough for now. - $after_selector_count = advagg_count_css_selectors($after); - $this->assertEqual($before_selector_count, $after_selector_count, t('Expected %before selectors, got %after.', array('%before' => $before_selector_count, '%after' => $after_selector_count))); - // // Test css file processing. advagg_test_reset_statics(); @@ -589,22 +381,20 @@ class AdvAggCascadingStylesheetsTestCase extends DrupalWebTestCase { $file_path = $path . '/' . $file; $file_url = $GLOBALS['base_url'] . '/' . $file_path; - $expected = advagg_file_get_contents($file_path . '.unoptimized.css'); + $expected = file_get_contents($file_path . '.unoptimized.css'); $unoptimized_output = advagg_load_stylesheet($file_path, FALSE); $this->assertEqual($unoptimized_output, $expected, format_string('Unoptimized CSS file has expected contents (@file)', array('@file' => $file))); - $expected = advagg_file_get_contents($file_path . '.optimized.css'); - $expected = advagg_test_remove_sniffer_comments($expected); + $expected = file_get_contents($file_path . '.optimized.css'); $optimized_output = advagg_load_stylesheet($file_path, TRUE); $this->assertEqual($optimized_output, $expected, format_string('Optimized CSS file has expected contents (@file)', array('@file' => $file))); // Repeat the tests by accessing the stylesheets by URL. - $expected = advagg_file_get_contents($file_path . '.unoptimized.css'); + $expected = file_get_contents($file_path . '.unoptimized.css'); $unoptimized_output_url = advagg_load_stylesheet($file_url, FALSE); $this->assertEqual($unoptimized_output_url, $expected, format_string('Unoptimized CSS file (loaded from an URL) has expected contents (@file)', array('@file' => $file))); - $expected = advagg_file_get_contents($file_path . '.optimized.css'); - $expected = advagg_test_remove_sniffer_comments($expected); + $expected = file_get_contents($file_path . '.optimized.css'); $optimized_output_url = advagg_load_stylesheet($file_url, TRUE); $this->assertEqual($optimized_output_url, $expected, format_string('Optimized CSS file (loaded from an URL) has expected contents (@file)', array('@file' => $file))); } @@ -617,121 +407,54 @@ class AdvAggCascadingStylesheetsTestCase extends DrupalWebTestCase { 'charset_newline.css', 'charset_sameline.css', ); - $path = drupal_get_path('module', 'advagg') . '/tests/css_test_files'; foreach ($testfiles as $file) { $file_path = $path . '/' . $file; $file_url = $GLOBALS['base_url'] . '/' . $file_path; - $expected = advagg_file_get_contents($file_path . '.optimized.css'); - $expected = advagg_test_remove_sniffer_comments($expected); + $expected = file_get_contents($file_path . '.optimized.css'); $optimized_output = advagg_load_stylesheet($file_path, TRUE); $this->assertEqual($optimized_output, $expected, format_string('Optimized CSS file has expected contents (@file)', array('@file' => $file))); - $expected = advagg_file_get_contents($file_path . '.optimized.css'); - $expected = advagg_test_remove_sniffer_comments($expected); + $expected = file_get_contents($file_path . '.optimized.css'); $optimized_output_url = advagg_load_stylesheet($file_url, TRUE); $this->assertEqual($optimized_output_url, $expected, format_string('Optimized CSS file (loaded from an URL) has expected contents (@file)', array('@file' => $file))); } - // Set all to FALSE. - $GLOBALS['conf']['advagg_convert_absolute_to_relative_path'] = FALSE; - $GLOBALS['conf']['advagg_convert_absolute_to_protocol_relative_path'] = FALSE; - $GLOBALS['conf']['advagg_force_https_path'] = FALSE; - $GLOBALS['conf']['advagg_skip_file_create_url_inside_css'] = FALSE; - $settings_to_change = array( - '' => '', - 'advagg_skip_file_create_url_inside_css' => TRUE, - 'advagg_convert_absolute_to_relative_path' => TRUE, - 'advagg_convert_absolute_to_protocol_relative_path' => TRUE, - 'advagg_force_https_path' => TRUE, + // File. Tests: advagg.css + // - Various url() tests. + // https://www.drupal.org/node/1514182 + // https://www.drupal.org/node/1961340 + // https://www.drupal.org/node/2362643 + // https://www.drupal.org/node/2112067 + $testfiles = array( + 'advagg.css', ); - $advagg_path = drupal_get_path('module', 'advagg'); - $path = $advagg_path . '/tests/css_test_files'; - foreach ($settings_to_change as $name => $value) { - $before = ''; - if (!empty($name)) { - $before = variable_get($name, defined(strtoupper($name)) ? constant(strtoupper($name)) : NULL); - $GLOBALS['conf'][$name] = $value; - } - - // File. Tests: advagg.css - // - Various url() tests. - // https://www.drupal.org/node/1514182 - // https://www.drupal.org/node/1961340 - // https://www.drupal.org/node/2362643 - // https://www.drupal.org/node/2112067 - $testfiles = array( - 'advagg.css', + foreach ($testfiles as $file) { + $file_path = $path . '/' . $file; + $file_url = $GLOBALS['base_url'] . '/' . $file_path; + $aggregate_settings = array( + 'variables' => array( + 'is_https' => FALSE, + 'base_path' => ($GLOBALS['base_path'] === '/checkout/') ? $GLOBALS['base_path'] : $GLOBALS['base_path'] . 'advagg_base_path_test/', + 'advagg_convert_absolute_to_relative_path' => TRUE, + 'advagg_convert_absolute_to_protocol_relative_path' => FALSE, + 'advagg_force_https_path' => FALSE, + ), ); - - foreach ($testfiles as $testfile) { - $base_url_before = $GLOBALS['base_url']; - $GLOBALS['base_url'] = advagg_force_http_path($GLOBALS['base_url']); - - $aggregate_settings = array( - 'variables' => array( - 'is_https' => FALSE, - 'base_path' => ($GLOBALS['base_path'] === '/checkout/') ? $GLOBALS['base_path'] : $GLOBALS['base_path'] . 'advagg_base_path_test/', - 'advagg_convert_absolute_to_relative_path' => $GLOBALS['conf']['advagg_convert_absolute_to_relative_path'], - 'advagg_convert_absolute_to_protocol_relative_path' => $GLOBALS['conf']['advagg_convert_absolute_to_protocol_relative_path'], - 'advagg_force_https_path' => $GLOBALS['conf']['advagg_force_https_path'], - 'advagg_skip_file_create_url_inside_css' => $GLOBALS['conf']['advagg_skip_file_create_url_inside_css'], - ), - ); - if (module_exists('cdn')) { - $aggregate_settings['variables'][CDN_MODE_VARIABLE] = CDN_DISABLED; - $aggregate_settings['variables'][CDN_STATUS_VARIABLE] = CDN_DISABLED; - } - - $file_path = $path . '/' . $testfile; - $files = array( - 'file' => $file_path, - 'external' => $GLOBALS['base_url'] . '/' . $file_path, - ); - $expected = advagg_test_remove_sniffer_comments(advagg_file_get_contents($file_path . '.optimized.css')); - foreach ($files as $type => $file) { - $optimized_output = advagg_load_css_stylesheet($file, TRUE, $aggregate_settings); - $mode = 0; - if ($aggregate_settings['variables']['advagg_skip_file_create_url_inside_css'] && $type !== 'external') { - $mode = "01"; - $optimized_output = str_replace($aggregate_settings['variables']['base_path'] . $advagg_path . '/', '', $optimized_output); - } - elseif (!$aggregate_settings['variables']['advagg_convert_absolute_to_relative_path'] - && !$aggregate_settings['variables']['advagg_convert_absolute_to_protocol_relative_path'] - && !$aggregate_settings['variables']['advagg_force_https_path'] - ) { - $mode = 4; - $optimized_output = str_replace('http://' . $_SERVER['HTTP_HOST'] . $aggregate_settings['variables']['base_path'] . $advagg_path . '/', '', $optimized_output); - } - elseif ($aggregate_settings['variables']['advagg_convert_absolute_to_protocol_relative_path']) { - $mode = 2; - $optimized_output = str_replace('//' . $_SERVER['HTTP_HOST'] . $aggregate_settings['variables']['base_path'] . $advagg_path . '/', '', $optimized_output); - } - elseif ($aggregate_settings['variables']['advagg_force_https_path']) { - $mode = 3; - $optimized_output = str_replace('https://' . $_SERVER['HTTP_HOST'] . $aggregate_settings['variables']['base_path'] . $advagg_path . '/', '', $optimized_output); - } - else { - $mode = 1; - $optimized_output = str_replace($aggregate_settings['variables']['base_path'] . $advagg_path . '/', '', $optimized_output); - } - $this->assertEqual($optimized_output, $expected, format_string("Optimized CSS file has expected contents (@file). Setting tested: @name; value before: @before, value after: @after.<br>mode: @mode. <p>!replacements</p> <p><code>!debug</code></p>", array( - '@file' => $file, - '@name' => $name, - '@before' => (is_bool($before) || strlen((string) $before) == 0) ? strtoupper(var_export($before, TRUE)) : $before, - '@after' => (is_bool($value) || strlen((string) $value) == 0) ? strtoupper(var_export($value, TRUE)) : $value, - '@mode' => $mode, - '!replacements' => "1: {$aggregate_settings['variables']['base_path']}{$advagg_path}/ <br> 2: //{$_SERVER['HTTP_HOST']}{$aggregate_settings['variables']['base_path']}{$advagg_path}/ <br> 3: https://{$_SERVER['HTTP_HOST']}{$aggregate_settings['variables']['base_path']}{$advagg_path}/ <br> 4: http://{$_SERVER['HTTP_HOST']}{$aggregate_settings['variables']['base_path']}{$advagg_path}/", - '!debug' => nl2br(str_replace(' ', ' ', $optimized_output)), - ))); - - $GLOBALS['base_url'] = $base_url_before; - } + if (module_exists('cdn')) { + $aggregate_settings['variables'][CDN_MODE_VARIABLE] = CDN_DISABLED; + $aggregate_settings['variables'][CDN_STATUS_VARIABLE] = CDN_DISABLED; } - if (!empty($name)) { - $GLOBALS['conf'][$name] = $before; - } + $expected = file_get_contents($file_path . '.optimized.css'); + $optimized_output = advagg_load_css_stylesheet($file_path, TRUE, $aggregate_settings); + $optimized_output = str_replace($aggregate_settings['variables']['base_path'] . drupal_get_path('module', 'advagg') . '/', '', $optimized_output); + $this->assertEqual($optimized_output, $expected, format_string('Optimized CSS file has expected contents (@file)', array('@file' => $file)) . "<br>\n<br>\n" . $path . "<br>\n<br>\n" . $file_path . "<br>\n<br>\n" . $file_url . "<br>\n<br>\n" . drupal_get_path('module', 'advagg') . "<br>\n<br>\n" . file_create_url($file_path) . "<br>\n<br>\n" . $optimized_output . "<br>\n<br>\n" . $GLOBALS['base_url'] . "<br>\n<br>\n" . $GLOBALS['base_path'] . "<br>\n<br>\n" . $GLOBALS['base_root'] . "<br>\n<br>\n" . $aggregate_settings['variables']['base_path']); + + $expected = file_get_contents($file_path . '.optimized.css'); + $optimized_output_url = advagg_load_css_stylesheet($file_url, TRUE, $aggregate_settings); + $optimized_output_url = str_replace($aggregate_settings['variables']['base_path'] . drupal_get_path('module', 'advagg') . '/', '', $optimized_output_url); + $this->assertEqual($optimized_output_url, $expected, format_string('Optimized CSS file (loaded from an URL) has expected contents (@file)', array('@file' => $file))); } } @@ -743,8 +466,6 @@ class AdvAggCascadingStylesheetsTestCase extends DrupalWebTestCase { class AdvAggJavaScriptTestCase extends DrupalWebTestCase { /** * Store configured value for JavaScript preprocessing. - * - * @var bool */ protected $preprocessJs = NULL; @@ -766,7 +487,6 @@ class AdvAggJavaScriptTestCase extends DrupalWebTestCase { // Enable Locale and SimpleTest in the test environment. parent::setUp(array( 'locale', - 'locale_test', 'simpletest', 'common_test', 'form_test', @@ -851,7 +571,7 @@ class AdvAggJavaScriptTestCase extends DrupalWebTestCase { // 'javascript_always_use_jquery' variable is set to FALSE, drupal_get_js() // should not return jQuery and other standard scripts and settings, nor // should it return the requested settings (since they cannot actually be - // added to the page without jQuery). + // addded to the page without jQuery). advagg_test_reset_statics(); variable_set('javascript_always_use_jquery', FALSE); drupal_add_js(array('testJavaScriptSetting' => 'test'), 'setting'); @@ -1035,8 +755,7 @@ class AdvAggJavaScriptTestCase extends DrupalWebTestCase { "-8 #2", "-8 #3", "-8 #4", - // -5 #1: The external script. - "-5 #1", + "-5 #1", // The external script. "0 #1", "0 #2", "0 #3", @@ -1240,153 +959,6 @@ class AdvAggJavaScriptTestCase extends DrupalWebTestCase { $this->assertTrue(strpos($scripts, $child_js), 'The child #attached JavaScript was included when loading from cache.'); $this->assertTrue(strpos($scripts, $subchild_js), 'The subchild #attached JavaScript was included when loading from cache.'); $_SERVER['REQUEST_METHOD'] = $request_method; - - // - // Tests the localisation of JavaScript libraries. Verifies that the - // datepicker can be localized. - advagg_test_reset_statics(); - drupal_add_library('system', 'ui.datepicker'); - $GLOBALS['conf']['advagg_mod_js_footer'] = 0; - $render_array = advagg_get_js(); - $javascript = drupal_render($render_array); - $this->assertTrue(strpos($javascript, 'locale.datepicker.js'), 'locale.datepicker.js added to scripts.'); - - // - // Functional tests for JavaScript parsing for translatable strings. - // Tests parsing js files for translatable strings. - advagg_test_reset_statics(); - $filename = drupal_get_path('module', 'locale_test') . '/locale_test.js'; - // Parse the file to look for source strings. - _locale_parse_js_file($filename); - - // Get all of the source strings that were found. - $source_strings = db_select('locales_source', 's') - ->fields('s', array('source', 'context')) - ->condition('s.location', $filename) - ->execute() - ->fetchAllKeyed(); - - // List of all strings that should be in the file. - $test_strings = array( - "Standard Call t" => '', - "Whitespace Call t" => '', - - "Single Quote t" => '', - "Single Quote \\'Escaped\\' t" => '', - "Single Quote Concat strings t" => '', - - "Double Quote t" => '', - "Double Quote \\\"Escaped\\\" t" => '', - "Double Quote Concat strings t" => '', - - "Context !key Args t" => "Context string", - - "Context Unquoted t" => "Context string unquoted", - "Context Single Quoted t" => "Context string single quoted", - "Context Double Quoted t" => "Context string double quoted", - - "Standard Call plural" => '', - "Standard Call @count plural" => '', - "Whitespace Call plural" => '', - "Whitespace Call @count plural" => '', - - "Single Quote plural" => '', - "Single Quote @count plural" => '', - "Single Quote \\'Escaped\\' plural" => '', - "Single Quote \\'Escaped\\' @count plural" => '', - - "Double Quote plural" => '', - "Double Quote @count plural" => '', - "Double Quote \\\"Escaped\\\" plural" => '', - "Double Quote \\\"Escaped\\\" @count plural" => '', - - "Context !key Args plural" => "Context string", - "Context !key Args @count plural" => "Context string", - - "Context Unquoted plural" => "Context string unquoted", - "Context Unquoted @count plural" => "Context string unquoted", - "Context Single Quoted plural" => "Context string single quoted", - "Context Single Quoted @count plural" => "Context string single quoted", - "Context Double Quoted plural" => "Context string double quoted", - "Context Double Quoted @count plural" => "Context string double quoted", - ); - - // Assert that all strings were found properly. - foreach ($test_strings as $str => $context) { - $args = array('%source' => $str, '%context' => $context); - - // Make sure that the string was found in the file. - $this->assertTrue(isset($source_strings[$str]), format_string('Found source string: %source', $args)); - - // Make sure that the proper context was matched. - $this->assertTrue(isset($source_strings[$str]) && $source_strings[$str] === $context, strlen($context) > 0 ? format_string('Context for %source is %context', $args) : format_string('Context for %source is blank', $args)); - } - $this->assertEqual(count($source_strings), count($test_strings), 'Found correct number of source strings.'); - - // - // Adds a language and checks that the JavaScript translation files are - // properly created and rebuilt on deletion. - $user = $this->drupalCreateUser(array( - 'translate interface', - 'administer languages', - 'access administration pages', - )); - $this->drupalLogin($user); - - $langcode = 'xx'; - // The English name for the language. This will be translated. - $name = $this->randomName(16); - // The native name for the language. - $native = $this->randomName(16); - // The domain prefix. - $prefix = $langcode; - - // Add custom language. - $edit = array( - 'langcode' => $langcode, - 'name' => $name, - 'native' => $native, - 'prefix' => $prefix, - 'direction' => '0', - ); - $this->drupalPost('admin/config/regional/language/add', $edit, t('Add custom language')); - drupal_static_reset('language_list'); - - // Build the JavaScript translation file. - $this->drupalGet('admin/config/regional/translate/translate'); - - // Retrieve the id of the first string available in the {locales_source} - // table and translate it. - $query = db_select('locales_source', 'l'); - $query->addExpression('min(l.lid)', 'lid'); - $result = $query->condition('l.location', '%.js%', 'LIKE') - ->condition('l.textgroup', 'default') - ->execute(); - $url = 'admin/config/regional/translate/edit/' . $result->fetchObject()->lid; - $edit = array('translations[' . $langcode . ']' => $this->randomName()); - $this->drupalPost($url, $edit, t('Save translations')); - - // Trigger JavaScript translation parsing and building. - require_once DRUPAL_ROOT . '/includes/locale.inc'; - _locale_rebuild_js($langcode); - - // Retrieve the JavaScript translation hash code for the custom language to - // check that the translation file has been properly built. - $file = db_select('languages', 'l') - ->fields('l', array('javascript')) - ->condition('language', $langcode) - ->execute() - ->fetchObject(); - $js_file = 'public://' . variable_get('locale_js_directory', 'languages') . '/' . $langcode . '_' . $file->javascript . '.js'; - $this->assertTrue($result = file_exists($js_file), format_string('JavaScript file created: %file', array('%file' => $result ? $js_file : 'not found'))); - - // Test JavaScript translation rebuilding. - file_unmanaged_delete($js_file); - $this->assertTrue($result = !file_exists($js_file), format_string('JavaScript file deleted: %file', array('%file' => $result ? $js_file : 'found'))); - cache_clear_all(); - _locale_rebuild_js($langcode); - $this->assertTrue($result = file_exists($js_file), format_string('JavaScript file rebuilt: %file', array('%file' => $result ? $js_file : 'not found'))); - } } @@ -1401,8 +973,8 @@ class AdvAggThemeTestCase extends AJAXTestCase { */ public static function getInfo() { return array( - 'name' => 'Theme API and AJAX', - 'description' => 'Test low-level theme functions and AJAX framework functions and commands.', + 'name' => 'Theme API & AJAX', + 'description' => 'Test low-level theme functions and AJAX framework functions & commands.', 'group' => 'AdvAgg', ); } @@ -1429,7 +1001,7 @@ class AdvAggThemeTestCase extends AJAXTestCase { } /** - * Check theme and ajax functions and commands. + * Check theme and ajax functions & commands. */ public function testCssOverride() { // Ensures a theme's .info file is able to override a module CSS file from @@ -1452,7 +1024,7 @@ class AdvAggThemeTestCase extends AJAXTestCase { variable_set('preprocess_css', 0); // - // Test ajax and js settings. + // Test ajax & js settings. advagg_test_reset_statics(); $commands = $this->drupalGetAJAX('ajax-test/render'); // Verify that there is a command to load settings added with @@ -1512,7 +1084,7 @@ class AdvAggThemeTestCase extends AJAXTestCase { 'preprocess' => TRUE, 'data' => $expected['css'], 'browsers' => array('IE' => TRUE, '!IE' => TRUE), - ), + ) ), TRUE); $expected_css_html = drupal_render($expected_css_render_array); $expected_js_render_array = advagg_get_js('header', array($expected['js'] => drupal_js_defaults($expected['js'])), TRUE); @@ -1715,7 +1287,3 @@ class AdvAggThemeTestCase extends AJAXTestCase { } } - -/** - * @} End of "defgroup advagg_tests". - */ diff --git a/sites/all/modules/contrib/advagg/tests/css_test_files/advagg.css b/sites/all/modules/contrib/advagg/tests/css_test_files/advagg.css index b2aee57100..9a570b5d66 100644 --- a/sites/all/modules/contrib/advagg/tests/css_test_files/advagg.css +++ b/sites/all/modules/contrib/advagg/tests/css_test_files/advagg.css @@ -1,4 +1,3 @@ -/* @codingStandardsIgnoreFile */ /* https://www.drupal.org/node/1514182#comment-9532431 */ home { background: url('geo-md-webfont.eot?#iefix'); @@ -15,22 +14,6 @@ home { home { background: url( geo-md-webfont.eot?#iefix ); } -/* https://www.drupal.org/node/2685177 */ -home { - background: url('fostbook-webfont.svg#Foundry Sterling Book'); -} -home { - background: url( 'fostbook-webfont.svg#Foundry Sterling Book' ); -} -home { - background: url("fostbook-webfont.svg#Foundry Sterling Book"); -} -home { - background: url(fostbook-webfont.svg#Foundry Sterling Book); -} -home { - background: url( fostbook-webfont.svg#Foundry Sterling Book ); -} /* https://www.drupal.org/node/2362643 */ home { background: url(#SVGID_1_); @@ -55,35 +38,4 @@ html { 5%200h2000v21h-2000V0z%22%2F%3E%3C%2Fsvg%3E'); } -/* Example from https://www.w3.org/TR/CSS2/syndata.html#rule-sets */ -q[example="public class foo\ -{\ - private int x;\ -\ - foo(int x) {\ - this.x = x;\ - }\ -\ -}"] { color: red } - -/* A pseudo selector with essential whitespace wrapped in quotes. */ -q[style*="quotes: none"] { - quotes: none; -} - -q[style*='quotes: none'] { - quotes: none; -} - -q:after { - content: ": colon & escaped double \" quotes \"."; -} - -q:after { - content: ' (brackets & escaped single \' quotes \') '; -} - -q:after { - content: "I'm Quote"; -} diff --git a/sites/all/modules/contrib/advagg/tests/css_test_files/advagg.css.optimized.css b/sites/all/modules/contrib/advagg/tests/css_test_files/advagg.css.optimized.css index 64b75975d4..9368ec596b 100644 --- a/sites/all/modules/contrib/advagg/tests/css_test_files/advagg.css.optimized.css +++ b/sites/all/modules/contrib/advagg/tests/css_test_files/advagg.css.optimized.css @@ -1,11 +1,2 @@ -/* @codingStandardsIgnoreFile */ -home{background:url(tests/css_test_files/geo-md-webfont.eot?#iefix);}home{background:url(tests/css_test_files/geo-md-webfont.eot?#iefix);}home{background:url(tests/css_test_files/geo-md-webfont.eot?#iefix);}home{background:url(tests/css_test_files/geo-md-webfont.eot?#iefix);}home{background:url(tests/css_test_files/geo-md-webfont.eot?#iefix);}home{background:url(tests/css_test_files/fostbook-webfont.svg#Foundry Sterling Book);}home{background:url(tests/css_test_files/fostbook-webfont.svg#Foundry Sterling Book);}home{background:url(tests/css_test_files/fostbook-webfont.svg#Foundry Sterling Book);}home{background:url(tests/css_test_files/fostbook-webfont.svg#Foundry Sterling Book);}home{background:url(tests/css_test_files/fostbook-webfont.svg#Foundry Sterling Book);}home{background:url(#SVGID_1_);}home{background:url(#a);}home{background:url('#a');}home{background:url("#a");}home{background:url("#a");}home{background:url(#a);}html{background-image:url('data:image/svg+xml;charset=US-ASCII,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22utf-8%22%3F%3E%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%222000%22%20height%3D%2221%22%20viewBox%3D%22-423.5%200%202000%2021%22%20enable-background%3D%22new%20-423.5%200%202000%2021%22%3E%3ClinearGradient%20id%3D%22SVGID_1_%22%20gradientUnits%3D%22userSpaceOnUse%22%20x1%3D%221393.788%22%20y1%3D%22404.834%22%20x2%3D%221395.121%22%20y2%3D%22404.834%22%20gradientTransform%3D%22matrix(1500%200%200%20-538%20-2091105%20217811)%22%3E%3Cstop%20offset%3D%220%22%20stop-color%3D%22%23fff%22%20stop-opacity%3D%220%22%2F%3E%3Cstop%20offset%3D%220%22%20stop-color%3D%22%23fff%22%2F%3E%3Cstop%20offset%3D%22.55%22%20stop-color%3D%22%23fff%22%2F%3E%3Cstop%20offset%3D%22.8%22%20stop-color%3D%22%23fff%22%20stop-opacity%3D%220%22%2F%3E%3Cstop%20offset%3D%221%22%20stop-color%3D%22%23fff%22%20stop-opacity%3D%220%22%2F%3E%3C%2FlinearGradient%3E%3Cpath%20fill%3D%22url(%23SVGID_1_)%22%20d%3D%22M-423. -5%200h2000v21h-2000V0z%22%2F%3E%3C%2Fsvg%3E');}q[example="public class foo\ -{\ - private int x;\ -\ - foo(int x) {\ - this.x = x;\ - }\ -\ -}"]{color:red}q[style*="quotes: none"]{quotes:none;}q[style*='quotes: none']{quotes:none;}q:after{content:": colon & escaped double \" quotes \".";}q:after{content:' (brackets & escaped single \' quotes \') ';}q:after{content:"I'm Quote";} \ No newline at end of file +home{background:url(tests/css_test_files/geo-md-webfont.eot?#iefix);}home{background:url(tests/css_test_files/geo-md-webfont.eot?#iefix);}home{background:url(tests/css_test_files/geo-md-webfont.eot?#iefix);}home{background:url(tests/css_test_files/geo-md-webfont.eot?#iefix);}home{background:url(tests/css_test_files/geo-md-webfont.eot?#iefix);}home{background:url(#SVGID_1_);}home{background:url(#a);}home{background:url('#a');}home{background:url("#a");}home{background:url("#a");}home{background:url(#a);}html{background-image:url('data:image/svg+xml;charset=US-ASCII,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22utf-8%22%3F%3E%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%222000%22%20height%3D%2221%22%20viewBox%3D%22-423.5%200%202000%2021%22%20enable-background%3D%22new%20-423.5%200%202000%2021%22%3E%3ClinearGradient%20id%3D%22SVGID_1_%22%20gradientUnits%3D%22userSpaceOnUse%22%20x1%3D%221393.788%22%20y1%3D%22404.834%22%20x2%3D%221395.121%22%20y2%3D%22404.834%22%20gradientTransform%3D%22matrix(1500%200%200%20-538%20-2091105%20217811)%22%3E%3Cstop%20offset%3D%220%22%20stop-color%3D%22%23fff%22%20stop-opacity%3D%220%22%2F%3E%3Cstop%20offset%3D%220%22%20stop-color%3D%22%23fff%22%2F%3E%3Cstop%20offset%3D%22.55%22%20stop-color%3D%22%23fff%22%2F%3E%3Cstop%20offset%3D%22.8%22%20stop-color%3D%22%23fff%22%20stop-opacity%3D%220%22%2F%3E%3Cstop%20offset%3D%221%22%20stop-color%3D%22%23fff%22%20stop-opacity%3D%220%22%2F%3E%3C%2FlinearGradient%3E%3Cpath%20fill%3D%22url(%23SVGID_1_)%22%20d%3D%22M-423. +5%200h2000v21h-2000V0z%22%2F%3E%3C%2Fsvg%3E');} \ No newline at end of file diff --git a/sites/all/modules/contrib/advagg/tests/css_test_files/charset.css b/sites/all/modules/contrib/advagg/tests/css_test_files/charset.css index 78ae95151c..ba7696ddf4 100644 --- a/sites/all/modules/contrib/advagg/tests/css_test_files/charset.css +++ b/sites/all/modules/contrib/advagg/tests/css_test_files/charset.css @@ -1,7 +1 @@ -/* @codingStandardsIgnoreFile */ @charset "UTF-8";html{font-family:"sans-serif";} - -/* Test data with multi-byte characters */ -.copyright-©, a-with-tilde-ã, plus-minus-± { - font-weight: bold; -} \ No newline at end of file diff --git a/sites/all/modules/contrib/advagg/tests/css_test_files/charset.css.optimized.css b/sites/all/modules/contrib/advagg/tests/css_test_files/charset.css.optimized.css index f21c548d35..4d35c9ab34 100644 --- a/sites/all/modules/contrib/advagg/tests/css_test_files/charset.css.optimized.css +++ b/sites/all/modules/contrib/advagg/tests/css_test_files/charset.css.optimized.css @@ -1,2 +1 @@ -/* @codingStandardsIgnoreFile */ -html{font-family:"sans-serif";}.copyright-©,a-with-tilde-ã,plus-minus-±{font-weight:bold;} \ No newline at end of file +html{font-family:"sans-serif";} \ No newline at end of file diff --git a/sites/all/modules/contrib/advagg/tests/css_test_files/charset_newline.css b/sites/all/modules/contrib/advagg/tests/css_test_files/charset_newline.css index ad411b3f2b..68f3f42b2c 100644 --- a/sites/all/modules/contrib/advagg/tests/css_test_files/charset_newline.css +++ b/sites/all/modules/contrib/advagg/tests/css_test_files/charset_newline.css @@ -1,3 +1,2 @@ -/* @codingStandardsIgnoreFile */ @charset 'UTF-8'; html{font-family:"sans-serif";} diff --git a/sites/all/modules/contrib/advagg/tests/css_test_files/charset_newline.css.optimized.css b/sites/all/modules/contrib/advagg/tests/css_test_files/charset_newline.css.optimized.css index 374b7c65d0..4d35c9ab34 100644 --- a/sites/all/modules/contrib/advagg/tests/css_test_files/charset_newline.css.optimized.css +++ b/sites/all/modules/contrib/advagg/tests/css_test_files/charset_newline.css.optimized.css @@ -1,2 +1 @@ -/* @codingStandardsIgnoreFile */ html{font-family:"sans-serif";} \ No newline at end of file diff --git a/sites/all/modules/contrib/advagg/tests/css_test_files/charset_sameline.css b/sites/all/modules/contrib/advagg/tests/css_test_files/charset_sameline.css index 9a19284b9a..ba7696ddf4 100644 --- a/sites/all/modules/contrib/advagg/tests/css_test_files/charset_sameline.css +++ b/sites/all/modules/contrib/advagg/tests/css_test_files/charset_sameline.css @@ -1,2 +1 @@ -/* @codingStandardsIgnoreFile */ @charset "UTF-8";html{font-family:"sans-serif";} diff --git a/sites/all/modules/contrib/advagg/tests/css_test_files/charset_sameline.css.optimized.css b/sites/all/modules/contrib/advagg/tests/css_test_files/charset_sameline.css.optimized.css index 374b7c65d0..4d35c9ab34 100644 --- a/sites/all/modules/contrib/advagg/tests/css_test_files/charset_sameline.css.optimized.css +++ b/sites/all/modules/contrib/advagg/tests/css_test_files/charset_sameline.css.optimized.css @@ -1,2 +1 @@ -/* @codingStandardsIgnoreFile */ html{font-family:"sans-serif";} \ No newline at end of file diff --git a/sites/all/modules/contrib/advagg/tests/css_test_files/comment_hacks.css b/sites/all/modules/contrib/advagg/tests/css_test_files/comment_hacks.css index f27a0b87f1..7d0dd44118 100644 --- a/sites/all/modules/contrib/advagg/tests/css_test_files/comment_hacks.css +++ b/sites/all/modules/contrib/advagg/tests/css_test_files/comment_hacks.css @@ -1,5 +1,3 @@ -/* @codingStandardsIgnoreFile */ - /* * A sample css file, designed to test the effectiveness and stability * of function <code>drupal_load_stylesheet_content()</code>. @@ -9,11 +7,6 @@ A large comment block to test for segfaults and speed. This is 60K a's. Extreme but useful to demonstrate flaws in comment striping regexp. aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa*/ .test1 { display:block;} -/* A pseudo selector with essential whitespace wrapped in quotes. */ -img[style*="float: right"] { - padding-left:5px; -} - /* A multiline IE-mac hack (v.2) taken from Zen theme*/ /* Hides from IE-mac \*/ html .clear-block { diff --git a/sites/all/modules/contrib/advagg/tests/css_test_files/comment_hacks.css.optimized.css b/sites/all/modules/contrib/advagg/tests/css_test_files/comment_hacks.css.optimized.css index bd49738994..1207be33f1 100644 --- a/sites/all/modules/contrib/advagg/tests/css_test_files/comment_hacks.css.optimized.css +++ b/sites/all/modules/contrib/advagg/tests/css_test_files/comment_hacks.css.optimized.css @@ -1,2 +1 @@ -/* @codingStandardsIgnoreFile */ -.test1{display:block;}img[style*="float: right"]{padding-left:5px;}html .clear-block{height:1%;}.clear-block{display:block;font:italic bold 12px/30px Georgia,serif;}.test2{display:block;}.bkslshv1{background-color:#c00;}.test3{display:block;}.test4{display:block;}.comment-in-double-quotes:before{content:"/* ";}.this_rule_must_stay{color:#f00;background-color:#fff;}.comment-in-double-quotes:after{content:" */";}.comment-in-single-quotes:before{content:'/*';}.this_rule_must_stay{color:#f00;background-color:#fff;}.comment-in-single-quotes:after{content:'*/';}.comment-in-mixed-quotes:before{content:'"/*"';}.this_rule_must_stay{color:#f00;background-color:#fff;}.comment-in-mixed-quotes:after{content:"'*/'";}.comment-in-quotes-with-escaped:before{content:'/* \" \' */';}.this_rule_must_stay{color:#f00;background-color:#fff;}.comment-in-quotes-with-escaped:after{content:"*/ \" \ '";} \ No newline at end of file +.test1{display:block;}html .clear-block{height:1%;}.clear-block{display:block;font:italic bold 12px/30px Georgia,serif;}.test2{display:block;}.bkslshv1{background-color:#c00;}.test3{display:block;}.test4{display:block;}.comment-in-double-quotes:before{content:"/* ";}.this_rule_must_stay{color:#f00;background-color:#fff;}.comment-in-double-quotes:after{content:" */";}.comment-in-single-quotes:before{content:'/*';}.this_rule_must_stay{color:#f00;background-color:#fff;}.comment-in-single-quotes:after{content:'*/';}.comment-in-mixed-quotes:before{content:'"/*"';}.this_rule_must_stay{color:#f00;background-color:#fff;}.comment-in-mixed-quotes:after{content:"'*/'";}.comment-in-quotes-with-escaped:before{content:'/* \" \' */';}.this_rule_must_stay{color:#f00;background-color:#fff;}.comment-in-quotes-with-escaped:after{content:"*/ \" \ '";} \ No newline at end of file diff --git a/sites/all/modules/contrib/advagg/tests/css_test_files/comment_hacks.css.unoptimized.css b/sites/all/modules/contrib/advagg/tests/css_test_files/comment_hacks.css.unoptimized.css index f27a0b87f1..7d0dd44118 100644 --- a/sites/all/modules/contrib/advagg/tests/css_test_files/comment_hacks.css.unoptimized.css +++ b/sites/all/modules/contrib/advagg/tests/css_test_files/comment_hacks.css.unoptimized.css @@ -1,5 +1,3 @@ -/* @codingStandardsIgnoreFile */ - /* * A sample css file, designed to test the effectiveness and stability * of function <code>drupal_load_stylesheet_content()</code>. @@ -9,11 +7,6 @@ A large comment block to test for segfaults and speed. This is 60K a's. Extreme but useful to demonstrate flaws in comment striping regexp. aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa*/ .test1 { display:block;} -/* A pseudo selector with essential whitespace wrapped in quotes. */ -img[style*="float: right"] { - padding-left:5px; -} - /* A multiline IE-mac hack (v.2) taken from Zen theme*/ /* Hides from IE-mac \*/ html .clear-block { diff --git a/sites/all/modules/contrib/advagg/tests/css_test_files/css_input_with_import.css b/sites/all/modules/contrib/advagg/tests/css_test_files/css_input_with_import.css index cd23bd87d6..34270ee696 100644 --- a/sites/all/modules/contrib/advagg/tests/css_test_files/css_input_with_import.css +++ b/sites/all/modules/contrib/advagg/tests/css_test_files/css_input_with_import.css @@ -1,4 +1,4 @@ -/* @codingStandardsIgnoreFile */ + @import "import1.css"; @import "import2.css"; @@ -29,3 +29,4 @@ textarea, select { font: 1em/160% Verdana, sans-serif; color: #494949; } + diff --git a/sites/all/modules/contrib/advagg/tests/css_test_files/css_input_with_import.css.optimized.css b/sites/all/modules/contrib/advagg/tests/css_test_files/css_input_with_import.css.optimized.css index 5f1b3081ed..83f56423d8 100644 --- a/sites/all/modules/contrib/advagg/tests/css_test_files/css_input_with_import.css.optimized.css +++ b/sites/all/modules/contrib/advagg/tests/css_test_files/css_input_with_import.css.optimized.css @@ -1,5 +1,4 @@ -/* @codingStandardsIgnoreFile */ -ul,select,import1{font:1em/160% Verdana,sans-serif;color:#494949;}.ui-icon{background-image:url(images/icon.png);}.data .double-quote{background-image:url("data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7");}.data .single-quote{background-image:url('data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDACAWGBwYFCAcGhwkIiAmMFA0MCwsMGJGSjpQdGZ6eHJmcG6AkLicgIiuim5woNqirr7EztDOfJri8uDI8LjKzsb/2wBDASIkJDAqMF40NF7GhHCExsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsb/wAARCAABAAEDASIAAhEBAxEB/8QAFQABAQAAAAAAAAAAAAAAAAAAAAb/xAAUEAEAAAAAAAAAAAAAAAAAAAAA/8QAFAEBAAAAAAAAAAAAAAAAAAAAAP/EABQRAQAAAAAAAAAAAAAAAAAAAAD/2gAMAwEAAhEDEQA/AKAAH//Z');}.data .no-quote{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACEAAAAEAQAAAAAo/mtHAAAAIElEQVQIHWMRnWHwcRNLN8NZ7QYWwT8PlBlYsgqVBRsAankIMw5MtnoAAAAASUVORK5CYII=);}@import url('https://fonts.googleapis.com/css?family=Ubuntu');p,select,import2{font:1em/160% Verdana,sans-serif;color:#494949;}@import url("http://example.com/style.css");@import url("//example.com/style.css");body{margin:0;padding:0;background:#edf5fa;font:76%/170% Verdana,sans-serif;color:#494949;}.this .is .a .test{font:1em/100% Verdana,sans-serif;color:#494949;}.this +ul,select{font:1em/160% Verdana,sans-serif;color:#494949;}.ui-icon{background-image:url(images/icon.png);}.data .double-quote{background-image:url("data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7");}.data .single-quote{background-image:url('data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDACAWGBwYFCAcGhwkIiAmMFA0MCwsMGJGSjpQdGZ6eHJmcG6AkLicgIiuim5woNqirr7EztDOfJri8uDI8LjKzsb/2wBDASIkJDAqMF40NF7GhHCExsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsb/wAARCAABAAEDASIAAhEBAxEB/8QAFQABAQAAAAAAAAAAAAAAAAAAAAb/xAAUEAEAAAAAAAAAAAAAAAAAAAAA/8QAFAEBAAAAAAAAAAAAAAAAAAAAAP/EABQRAQAAAAAAAAAAAAAAAAAAAAD/2gAMAwEAAhEDEQA/AKAAH//Z');}.data .no-quote{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACEAAAAEAQAAAAAo/mtHAAAAIElEQVQIHWMRnWHwcRNLN8NZ7QYWwT8PlBlYsgqVBRsAankIMw5MtnoAAAAASUVORK5CYII=);}p,select{font:1em/160% Verdana,sans-serif;color:#494949;}@import url("http://example.com/style.css");@import url("//example.com/style.css");body{margin:0;padding:0;background:#edf5fa;font:76%/170% Verdana,sans-serif;color:#494949;}.this .is .a .test{font:1em/100% Verdana,sans-serif;color:#494949;}.this .is .a .test{font:1em/100% Verdana,sans-serif;color:#494949;}textarea,select{font:1em/160% Verdana,sans-serif;color:#494949;} \ No newline at end of file diff --git a/sites/all/modules/contrib/advagg/tests/css_test_files/css_input_with_import.css.unoptimized.css b/sites/all/modules/contrib/advagg/tests/css_test_files/css_input_with_import.css.unoptimized.css index 33a60562e3..c6d26e64d3 100644 --- a/sites/all/modules/contrib/advagg/tests/css_test_files/css_input_with_import.css.unoptimized.css +++ b/sites/all/modules/contrib/advagg/tests/css_test_files/css_input_with_import.css.unoptimized.css @@ -1,8 +1,7 @@ -/* @codingStandardsIgnoreFile */ -/* @codingStandardsIgnoreFile */ -ul, select, import1 { + +ul, select { font: 1em/160% Verdana, sans-serif; color: #494949; } @@ -22,10 +21,8 @@ ul, select, import1 { background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACEAAAAEAQAAAAAo/mtHAAAAIElEQVQIHWMRnWHwcRNLN8NZ7QYWwT8PlBlYsgqVBRsAankIMw5MtnoAAAAASUVORK5CYII=); } -/* @codingStandardsIgnoreFile */ -@import url('https://fonts.googleapis.com/css?family=Ubuntu'); -p, select, import2 { +p, select { font: 1em/160% Verdana, sans-serif; color: #494949; } @@ -57,3 +54,4 @@ textarea, select { font: 1em/160% Verdana, sans-serif; color: #494949; } + diff --git a/sites/all/modules/contrib/advagg/tests/css_test_files/css_input_without_import.css b/sites/all/modules/contrib/advagg/tests/css_test_files/css_input_without_import.css index a6519e6b7b..620360abc5 100644 --- a/sites/all/modules/contrib/advagg/tests/css_test_files/css_input_without_import.css +++ b/sites/all/modules/contrib/advagg/tests/css_test_files/css_input_without_import.css @@ -1,9 +1,9 @@ -/* @codingStandardsIgnoreFile */ /** * @file Basic css that does not use import */ + body { margin: 0; padding: 0; diff --git a/sites/all/modules/contrib/advagg/tests/css_test_files/css_input_without_import.css.optimized.css b/sites/all/modules/contrib/advagg/tests/css_test_files/css_input_without_import.css.optimized.css index d89954f47d..e2c13fd912 100644 --- a/sites/all/modules/contrib/advagg/tests/css_test_files/css_input_without_import.css.optimized.css +++ b/sites/all/modules/contrib/advagg/tests/css_test_files/css_input_without_import.css.optimized.css @@ -1,4 +1,3 @@ -/* @codingStandardsIgnoreFile */ body{margin:0;padding:0;background:#edf5fa;font:76%/170% Verdana,sans-serif;color:#494949;}.this .is .a .test{font:1em/100% Verdana,sans-serif;color:#494949;}.this .is .a diff --git a/sites/all/modules/contrib/advagg/tests/css_test_files/css_input_without_import.css.unoptimized.css b/sites/all/modules/contrib/advagg/tests/css_test_files/css_input_without_import.css.unoptimized.css index a6519e6b7b..620360abc5 100644 --- a/sites/all/modules/contrib/advagg/tests/css_test_files/css_input_without_import.css.unoptimized.css +++ b/sites/all/modules/contrib/advagg/tests/css_test_files/css_input_without_import.css.unoptimized.css @@ -1,9 +1,9 @@ -/* @codingStandardsIgnoreFile */ /** * @file Basic css that does not use import */ + body { margin: 0; padding: 0; diff --git a/sites/all/modules/contrib/advagg/tests/css_test_files/css_subfolder/css_input_with_import.css b/sites/all/modules/contrib/advagg/tests/css_test_files/css_subfolder/css_input_with_import.css index fca7a79ef7..d90ecbcb4f 100644 --- a/sites/all/modules/contrib/advagg/tests/css_test_files/css_subfolder/css_input_with_import.css +++ b/sites/all/modules/contrib/advagg/tests/css_test_files/css_subfolder/css_input_with_import.css @@ -1,4 +1,4 @@ -/* @codingStandardsIgnoreFile */ + @import "../import1.css"; @import "../import2.css"; diff --git a/sites/all/modules/contrib/advagg/tests/css_test_files/css_subfolder/css_input_with_import.css.optimized.css b/sites/all/modules/contrib/advagg/tests/css_test_files/css_subfolder/css_input_with_import.css.optimized.css index 7cb26ed652..5d40017a8f 100644 --- a/sites/all/modules/contrib/advagg/tests/css_test_files/css_subfolder/css_input_with_import.css.optimized.css +++ b/sites/all/modules/contrib/advagg/tests/css_test_files/css_subfolder/css_input_with_import.css.optimized.css @@ -1,5 +1,4 @@ -/* @codingStandardsIgnoreFile */ -ul,select,import1{font:1em/160% Verdana,sans-serif;color:#494949;}.ui-icon{background-image:url(../images/icon.png);}.data .double-quote{background-image:url("data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7");}.data .single-quote{background-image:url('data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDACAWGBwYFCAcGhwkIiAmMFA0MCwsMGJGSjpQdGZ6eHJmcG6AkLicgIiuim5woNqirr7EztDOfJri8uDI8LjKzsb/2wBDASIkJDAqMF40NF7GhHCExsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsb/wAARCAABAAEDASIAAhEBAxEB/8QAFQABAQAAAAAAAAAAAAAAAAAAAAb/xAAUEAEAAAAAAAAAAAAAAAAAAAAA/8QAFAEBAAAAAAAAAAAAAAAAAAAAAP/EABQRAQAAAAAAAAAAAAAAAAAAAAD/2gAMAwEAAhEDEQA/AKAAH//Z');}.data .no-quote{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACEAAAAEAQAAAAAo/mtHAAAAIElEQVQIHWMRnWHwcRNLN8NZ7QYWwT8PlBlYsgqVBRsAankIMw5MtnoAAAAASUVORK5CYII=);}@import url('https://fonts.googleapis.com/css?family=Ubuntu');p,select,import2{font:1em/160% Verdana,sans-serif;color:#494949;}body{margin:0;padding:0;background:#edf5fa;font:76%/170% Verdana,sans-serif;color:#494949;}.this .is .a .test{font:1em/100% Verdana,sans-serif;color:#494949;}.this +ul,select{font:1em/160% Verdana,sans-serif;color:#494949;}.ui-icon{background-image:url(../images/icon.png);}.data .double-quote{background-image:url("data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7");}.data .single-quote{background-image:url('data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDACAWGBwYFCAcGhwkIiAmMFA0MCwsMGJGSjpQdGZ6eHJmcG6AkLicgIiuim5woNqirr7EztDOfJri8uDI8LjKzsb/2wBDASIkJDAqMF40NF7GhHCExsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsb/wAARCAABAAEDASIAAhEBAxEB/8QAFQABAQAAAAAAAAAAAAAAAAAAAAb/xAAUEAEAAAAAAAAAAAAAAAAAAAAA/8QAFAEBAAAAAAAAAAAAAAAAAAAAAP/EABQRAQAAAAAAAAAAAAAAAAAAAAD/2gAMAwEAAhEDEQA/AKAAH//Z');}.data .no-quote{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACEAAAAEAQAAAAAo/mtHAAAAIElEQVQIHWMRnWHwcRNLN8NZ7QYWwT8PlBlYsgqVBRsAankIMw5MtnoAAAAASUVORK5CYII=);}p,select{font:1em/160% Verdana,sans-serif;color:#494949;}body{margin:0;padding:0;background:#edf5fa;font:76%/170% Verdana,sans-serif;color:#494949;}.this .is .a .test{font:1em/100% Verdana,sans-serif;color:#494949;}.this .is .a .test{font:1em/100% Verdana,sans-serif;color:#494949;}textarea,select{font:1em/160% Verdana,sans-serif;color:#494949;} \ No newline at end of file diff --git a/sites/all/modules/contrib/advagg/tests/css_test_files/css_subfolder/css_input_with_import.css.unoptimized.css b/sites/all/modules/contrib/advagg/tests/css_test_files/css_subfolder/css_input_with_import.css.unoptimized.css index 67f4d81c13..6d7151bb38 100644 --- a/sites/all/modules/contrib/advagg/tests/css_test_files/css_subfolder/css_input_with_import.css.unoptimized.css +++ b/sites/all/modules/contrib/advagg/tests/css_test_files/css_subfolder/css_input_with_import.css.unoptimized.css @@ -1,8 +1,7 @@ -/* @codingStandardsIgnoreFile */ -/* @codingStandardsIgnoreFile */ -ul, select, import1 { + +ul, select { font: 1em/160% Verdana, sans-serif; color: #494949; } @@ -22,10 +21,8 @@ ul, select, import1 { background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACEAAAAEAQAAAAAo/mtHAAAAIElEQVQIHWMRnWHwcRNLN8NZ7QYWwT8PlBlYsgqVBRsAankIMw5MtnoAAAAASUVORK5CYII=); } -/* @codingStandardsIgnoreFile */ -@import url('https://fonts.googleapis.com/css?family=Ubuntu'); -p, select, import2 { +p, select { font: 1em/160% Verdana, sans-serif; color: #494949; } diff --git a/sites/all/modules/contrib/advagg/tests/css_test_files/import1.css b/sites/all/modules/contrib/advagg/tests/css_test_files/import1.css index 5f4cd00594..e53d6d52cd 100644 --- a/sites/all/modules/contrib/advagg/tests/css_test_files/import1.css +++ b/sites/all/modules/contrib/advagg/tests/css_test_files/import1.css @@ -1,6 +1,5 @@ -/* @codingStandardsIgnoreFile */ -ul, select, import1 { +ul, select { font: 1em/160% Verdana, sans-serif; color: #494949; } diff --git a/sites/all/modules/contrib/advagg/tests/css_test_files/import2.css b/sites/all/modules/contrib/advagg/tests/css_test_files/import2.css index 9208dc0a3a..367eb57118 100644 --- a/sites/all/modules/contrib/advagg/tests/css_test_files/import2.css +++ b/sites/all/modules/contrib/advagg/tests/css_test_files/import2.css @@ -1,7 +1,5 @@ -/* @codingStandardsIgnoreFile */ -@import url('https://fonts.googleapis.com/css?family=Ubuntu'); -p, select, import2 { +p, select { font: 1em/160% Verdana, sans-serif; color: #494949; } diff --git a/sites/all/modules/contrib/advagg/tpl/imce-page.tpl.php b/sites/all/modules/contrib/advagg/tpl/imce-page.tpl.php deleted file mode 100644 index 3695c76ac8..0000000000 --- a/sites/all/modules/contrib/advagg/tpl/imce-page.tpl.php +++ /dev/null @@ -1,41 +0,0 @@ -<?php - -/** - * @file - * Override imce-page.tpl.php, fixing css/js aggregation issues. - */ - -if (isset($_GET['app'])) { - drupal_add_js(drupal_get_path('module', 'imce') . '/js/imce_set_app.js'); -} -$title = t('File Browser'); -$full_css = advagg_get_css(); -$rendered_css = drupal_render($full_css); -$scripts_header_array = advagg_get_js('header'); -$rendered_scripts_header = drupal_render($scripts_header_array); -$scripts_footer_array = advagg_get_js('footer'); -$rendered_scripts_footer = drupal_render($scripts_footer_array); -$html_head = drupal_get_html_head(); -$status_messages = theme('status_messages'); - -print <<<HTML -<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" - "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> -<html xmlns="http://www.w3.org/1999/xhtml" lang="{$GLOBALS['language']->language}" xml:lang="{$GLOBALS['language']->language}" class="imce"> - -<head> - <title>{$title} - - {$html_head} - {$rendered_css} - {$rendered_scripts_header} - - - - -
{$status_messages}
-{$content} -{$rendered_scripts_footer} - - -HTML; From f91a13cefe38c12600bdccb4bdc00b0395de000c Mon Sep 17 00:00:00 2001 From: Rob Davis Date: Fri, 2 Dec 2022 17:25:45 +0000 Subject: [PATCH 20/22] remove superfluous empty new lines, to be the same as master --- .../scratchpads/scratchpads_backend/scratchpads_backend.module | 3 --- 1 file changed, 3 deletions(-) diff --git a/sites/all/modules/custom/scratchpads/scratchpads_backend/scratchpads_backend.module b/sites/all/modules/custom/scratchpads/scratchpads_backend/scratchpads_backend.module index 0ac274d40b..8877f3cf0d 100644 --- a/sites/all/modules/custom/scratchpads/scratchpads_backend/scratchpads_backend.module +++ b/sites/all/modules/custom/scratchpads/scratchpads_backend/scratchpads_backend.module @@ -314,6 +314,3 @@ function scratchpads_backend_ckeditor_plugin(){ ) ); } - - - From 8378fdd41add08cd80bf46faa52b272e7b5121e9 Mon Sep 17 00:00:00 2001 From: Rob Davis Date: Thu, 22 Dec 2022 12:17:44 +0000 Subject: [PATCH 21/22] vagrant folder removed - should be part of our infrastructure repo I would think --- vagrant/Vagrantfile.full-install | 15 -- vagrant/Vagrantfile.quick | 14 -- vagrant/bootstrap.aegir.sh | 17 -- vagrant/bootstrap.sh | 190 ------------------ .../aegir2-hostmaster_2.1_all_scratchpads.deb | Bin 13702 -> 0 bytes .../aegir2-provision_2.1_all.deb | Bin 109834 -> 0 bytes vagrant/debian-packages/aegir2_2.1_all.deb | Bin 7648 -> 0 bytes 7 files changed, 236 deletions(-) delete mode 100644 vagrant/Vagrantfile.full-install delete mode 100644 vagrant/Vagrantfile.quick delete mode 100755 vagrant/bootstrap.aegir.sh delete mode 100644 vagrant/bootstrap.sh delete mode 100644 vagrant/debian-packages/aegir2-hostmaster_2.1_all_scratchpads.deb delete mode 100644 vagrant/debian-packages/aegir2-provision_2.1_all.deb delete mode 100644 vagrant/debian-packages/aegir2_2.1_all.deb diff --git a/vagrant/Vagrantfile.full-install b/vagrant/Vagrantfile.full-install deleted file mode 100644 index 42aa7c53cc..0000000000 --- a/vagrant/Vagrantfile.full-install +++ /dev/null @@ -1,15 +0,0 @@ -# -*- mode: ruby -*- -# vi: set ft=ruby : - -VAGRANTFILE_API_VERSION = "2" -Vagrant.configure(VAGRANTFILE_API_VERSION) do |config| - config.vm.box = "scratchpads/debian7" - config.vm.network "public_network" - config.vm.provision :shell, path: "vagrant/bootstrap.sh" - config.vm.network "forwarded_port", guest: 80, host: 8888 - - # Virtualbox specific configuration - config.vm.provider "virtualbox" do |vb| - vb.customize ["modifyvm", :id, "--memory", "1024"] - end -end diff --git a/vagrant/Vagrantfile.quick b/vagrant/Vagrantfile.quick deleted file mode 100644 index 1395b5cb4e..0000000000 --- a/vagrant/Vagrantfile.quick +++ /dev/null @@ -1,14 +0,0 @@ -# -*- mode: ruby -*- -# vi: set ft=ruby : - -VAGRANTFILE_API_VERSION = "2" -Vagrant.configure(VAGRANTFILE_API_VERSION) do |config| - config.vm.box = "scratchpads/aegir-scratchpads2" - config.vm.network "public_network" - config.vm.network "forwarded_port", guest: 80, host: 8888 - - # Virtualbox specific configuration - config.vm.provider "virtualbox" do |vb| - vb.customize ["modifyvm", :id, "--memory", "1024"] - end -end diff --git a/vagrant/bootstrap.aegir.sh b/vagrant/bootstrap.aegir.sh deleted file mode 100755 index 18b44d922e..0000000000 --- a/vagrant/bootstrap.aegir.sh +++ /dev/null @@ -1,17 +0,0 @@ -#!/usr/bin/env bash - -# Create the platform in Aegir -drush --root='/var/aegir/platforms/scratchpads-2' provision-save '@platform_scratchpads-2' --context_type='platform' --makefile='/vagrant/scratchpads2.make' -drush --root='/var/aegir/platforms/scratchpads' provision-save '@platform_scratchpads' --context_type='platform' -drush @platform_scratchpads-2 provision-verify -drush @platform_scratchpads provision-verify -drush @hostmaster hosting-import @platform_scratchpads-2 -drush @hostmaster hosting-import @platform_scratchpads - -# Create a site -drush provision-save '@scratchpads.vagrant' --context_type='site' --uri='scratchpads.vagrant' --platform='@platform_scratchpads' --server='@server_master' --db_server='@server_localhost' --profile='scratchpad_2_training' --client_name='admin' -drush @scratchpads.vagrant provision-install -drush @hostmaster hosting-task @platform_scratchpads verify -# Clear the cache on the site, to ensure it's working as expected -drush @scratchpads.vagrant cc all -drush @scratchpads.vagrant cron diff --git a/vagrant/bootstrap.sh b/vagrant/bootstrap.sh deleted file mode 100644 index c9e4bb14ae..0000000000 --- a/vagrant/bootstrap.sh +++ /dev/null @@ -1,190 +0,0 @@ -#!/usr/bin/env bash - -# Add the Aegir package source -echo "deb http://debian.aegirproject.org stable main" | tee -a /etc/apt/sources.list.d/aegir-stable.list -wget -q http://debian.aegirproject.org/key.asc -O- | sudo apt-key add - - -# Update -apt-get update -apt-get upgrade -y -apt-get install debconf-utils -y - -# Set the various options for the packages that aegir will drag in with it (Postfix/MySQL/etc) -# MySQL -echo "mysql-server-5.5 mysql-server/root_password_again password vagrant" | debconf-set-selections -echo "mysql-server-5.5 mysql-server/root_password password vagrant" | debconf-set-selections -# Postfix -echo "postfix postfix/mailname string hostmaster.vagrant"|debconf-set-selections -echo "postfix postfix/main_mailer_type select Internet Site"|debconf-set-selections -echo "postfix postfix/destinations string hostmaster.vagrant, vagrant, localhost.localdomain, localhost"|debconf-set-selections -# Aegir -echo "aegir2-hostmaster aegir/db_password password vagrant"|debconf-set-selections -echo "aegir2-hostmaster aegir/db_user string root"|debconf-set-selections -echo "aegir2-hostmaster aegir/email string aegir@hostmaster.vagrant"|debconf-set-selections -echo "aegir2-hostmaster aegir/db_host string localhost"|debconf-set-selections -echo "aegir2-hostmaster aegir/makefile string "|debconf-set-selections -echo "aegir2-hostmaster aegir/site string hostmaster.vagrant"|debconf-set-selections -echo "aegir2-hostmaster aegir/webserver select apache2"|debconf-set-selections - -# Install dependencies for the aegir2 package -apt-get -y install apache2 drush git-core libapache2-mod-php5 mysql-client mysql-server php5 php5-mysql postfix rsync unzip -# Install additional packages Scratchpads 2 require -apt-get -y install dnsutils memcached solr-tomcat php5-gd php5-curl php5-memcached php5-mcrypt php5-dev php5-gmp varnish - -# Install a couple of PECL modules -pecl install mailparse -pecl install uploadprogress -echo "extension=mailparse.so" > /etc/php5/conf.d/mailparse.ini -echo "extension=uploadprogress.so" > /etc/php5/conf.d/uploadprogress.ini - -# Increase the memory limit for php -sed "s/memory_limit\ =\ 128M/memory_limit = 256M/" -i /etc/php5/apache2/php.ini - -# Convert all of the databases tables to MyISAM -#for i in $(echo "SELECT CONCAT(TABLE_SCHEMA, '.', TABLE_NAME) FROM information_schema.TABLES WHERE ENGINE='InnoDB'"|mysql -pvagrant|grep -v CONCAT) -#do -# echo "ALTER TABLE "$i" ENGINE=MyISAM;" | mysql -pvagrant -#done - -# Tweak MySQL to skip-innodb -echo "[mysqld] -innodb=OFF -default_storage_engine=MyISAM" > /etc/mysql/conf.d/skip-innodb.cnf -service mysql restart - -# Tweak the memcached settings to allow large objects. -sed "s/^-m\s[0-9]*/-m 256/" /etc/memcached.conf -i -echo "# Increase the Maximum size of cached objects --I 16M" >> /etc/memcached.conf -service memcached restart - -# Point web-scratchpad-solr at the local machine so the local solr instance should just work -echo "127.0.0.1 web-scratchpad-solr.nhm.ac.uk" >> /etc/hosts -echo " - ServerName web-scratchpad-solr.nhm.ac.uk - DocumentRoot /var/www - - Order deny,allow - Allow from all - ProxyPass http://localhost:8080/solr nocanon connectiontimeout=30 timeout=60 - ProxyPassReverse http://localhost:8080/solr - -" > /etc/apache2/sites-available/solr -cd /etc/solr/conf -cp /vagrant/sites/all/modules/contrib/apachesolr/solr-conf/solr-3.x/* . -a2ensite solr -a2enmod proxy proxy_http -service apache2 reload -service tomcat6 restart - -# Install our own custom Aegir packages, as there is a bug in the -# standard package that results in the install attempting to ask -# the user for a password. -dpkg -i /vagrant/vagrant/debian-packages/aegir2_2.1_all.deb /vagrant/vagrant/debian-packages/aegir2-hostmaster_2.1_all_scratchpads.deb /vagrant/vagrant/debian-packages/aegir2-provision_2.1_all.deb -apt-get update -y -apt-get upgrade -y - -# Set the password for the admin aegir user -echo "UPDATE users SET pass = MD5('vagrant') WHERE uid = 1;" | mysql -uroot -pvagrant hostmastervagran - -VARNISHSECRET=`cat /etc/varnish/secret` - -# Add a global aegir config file which enables memcache, varnish and other caches -echo " 'default'); -\$conf['cache_backends'][] = 'sites/all/modules/contrib/memcache/memcache.inc'; -\$conf['cache_default_class'] = 'MemCacheDrupal'; - -// Varnish -\$conf['reverse_proxy'] = TRUE; -\$conf['reverse_proxy_addresses'] = array('127.0.0.1'); -\$conf['varnish_flush_cron'] = 0; -\$conf['varnish_version'] = 3; -\$conf['varnish_control_terminal'] = '127.0.0.1:6082'; -\$conf['varnish_control_key'] = '"$VARNISHSECRET"'; -\$conf['varnish_socket_timeout'] = 200; -\$conf['varnish_cache_clear'] = 0; -\$conf['varnish_bantype'] = 0; -\$conf['cache_backends'][] = 'sites/all/modules/contrib/varnish/varnish.cache.inc'; -\$conf['cache_class_cache_page'] = 'VarnishCache'; -\$conf['page_cache_invoke_hooks'] = FALSE; - - -// Not everybody can run updates. -\$update_free_access = 0; - -// Set some PHP settings -@ini_set('pcre.backtrack_limit', 10000000); -@ini_set('pcre.recursion_limit', 10000000); -@ini_set('session.cookie_lifetime', 604800); -@ini_set('session.gc_maxlifetime', 604800); -@ini_set('session.use_cookies', 1); -@ini_set('mysql.default_socket', '/var/lib/mysql/mysql.sock');" > /var/aegir/config/includes/global.inc - -# Create the Scratchpads web folder -cd /var/aegir/platforms -mkdir scratchpads -cd scratchpads -ln -s /vagrant/* /vagrant/.htaccess . -rm sites -mkdir sites -cd sites -ln -s /vagrant/sites/* . -rm all -mkdir all -cd all -ln -s /vagrant/sites/all/* . -rm drush -chown aegir:www-data /var/aegir/platforms/scratchpads/sites -chown aegir:www-data /var/aegir/platforms/scratchpads/sites/all -# Special copy for the scratchpads_twitter.ini file which may be in the /vagrant folder -mkdir -p /usr/local/share/scratchpads-global -cp /vagrant/scratchpads_twitter.ini /usr/local/share/scratchpads-global - -# Restart Apache so that the new global file is read -service apache2 reload - -# Add a record to the hosts file so that the site can access itself -echo "127.0.0.1 scratchpads.vagrant" >> /etc/hosts -# Create the Scratchpads platform -su -c /vagrant/vagrant/bootstrap.aegir.sh aegir - -# Get the external IP address to inform people to add it to their hosts file. -IPADDRESS=`/sbin/ifconfig eth1 | grep 'inet addr:' | cut -d: -f2 | awk '{ print $1}'` - -# Inform the user how they can login to the Aegir site. -echo " - - - - - - - - - - -Add the following entry to your 'hosts' file (http://www.rackspace.com/knowledge_center/article/how-do-i-modify-my-hosts-file) -"$IPADDRESS" scratchpads.vagrant hostmaster.vagrant - -Then login to the Aegir interface: -http://hostmaster.vagrant/ -Username: admin -Password: vagrant" diff --git a/vagrant/debian-packages/aegir2-hostmaster_2.1_all_scratchpads.deb b/vagrant/debian-packages/aegir2-hostmaster_2.1_all_scratchpads.deb deleted file mode 100644 index b2873c72d9fae000055907ab5dca5e29968e505d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 13702 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 diff --git a/vagrant/debian-packages/aegir2-provision_2.1_all.deb b/vagrant/debian-packages/aegir2-provision_2.1_all.deb deleted file mode 100644 index b3f46417c1849ddc0542da256d60e61924e014e5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 109834 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ℑ#&&+p>UjaJ0&9dh7^t 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 FSz3zqzO4WN diff --git a/vagrant/debian-packages/aegir2_2.1_all.deb b/vagrant/debian-packages/aegir2_2.1_all.deb deleted file mode 100644 index d3f666b3bc17845282698dee8e334c7d85032014..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7648 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 From 070ee3fc0aefc5d34760d00c36ab93a053c56caa Mon Sep 17 00:00:00 2001 From: Rob Davis Date: Mon, 9 Jan 2023 14:11:30 +0000 Subject: [PATCH 22/22] Revert "vagrant folder removed - should be part of our infrastructure repo I would think" This reverts commit 8378fdd41add08cd80bf46faa52b272e7b5121e9. --- vagrant/Vagrantfile.full-install | 15 ++ vagrant/Vagrantfile.quick | 14 ++ vagrant/bootstrap.aegir.sh | 17 ++ vagrant/bootstrap.sh | 190 ++++++++++++++++++ .../aegir2-hostmaster_2.1_all_scratchpads.deb | Bin 0 -> 13702 bytes .../aegir2-provision_2.1_all.deb | Bin 0 -> 109834 bytes vagrant/debian-packages/aegir2_2.1_all.deb | Bin 0 -> 7648 bytes 7 files changed, 236 insertions(+) create mode 100644 vagrant/Vagrantfile.full-install create mode 100644 vagrant/Vagrantfile.quick create mode 100755 vagrant/bootstrap.aegir.sh create mode 100644 vagrant/bootstrap.sh create mode 100644 vagrant/debian-packages/aegir2-hostmaster_2.1_all_scratchpads.deb create mode 100644 vagrant/debian-packages/aegir2-provision_2.1_all.deb create mode 100644 vagrant/debian-packages/aegir2_2.1_all.deb diff --git a/vagrant/Vagrantfile.full-install b/vagrant/Vagrantfile.full-install new file mode 100644 index 0000000000..42aa7c53cc --- /dev/null +++ b/vagrant/Vagrantfile.full-install @@ -0,0 +1,15 @@ +# -*- mode: ruby -*- +# vi: set ft=ruby : + +VAGRANTFILE_API_VERSION = "2" +Vagrant.configure(VAGRANTFILE_API_VERSION) do |config| + config.vm.box = "scratchpads/debian7" + config.vm.network "public_network" + config.vm.provision :shell, path: "vagrant/bootstrap.sh" + config.vm.network "forwarded_port", guest: 80, host: 8888 + + # Virtualbox specific configuration + config.vm.provider "virtualbox" do |vb| + vb.customize ["modifyvm", :id, "--memory", "1024"] + end +end diff --git a/vagrant/Vagrantfile.quick b/vagrant/Vagrantfile.quick new file mode 100644 index 0000000000..1395b5cb4e --- /dev/null +++ b/vagrant/Vagrantfile.quick @@ -0,0 +1,14 @@ +# -*- mode: ruby -*- +# vi: set ft=ruby : + +VAGRANTFILE_API_VERSION = "2" +Vagrant.configure(VAGRANTFILE_API_VERSION) do |config| + config.vm.box = "scratchpads/aegir-scratchpads2" + config.vm.network "public_network" + config.vm.network "forwarded_port", guest: 80, host: 8888 + + # Virtualbox specific configuration + config.vm.provider "virtualbox" do |vb| + vb.customize ["modifyvm", :id, "--memory", "1024"] + end +end diff --git a/vagrant/bootstrap.aegir.sh b/vagrant/bootstrap.aegir.sh new file mode 100755 index 0000000000..18b44d922e --- /dev/null +++ b/vagrant/bootstrap.aegir.sh @@ -0,0 +1,17 @@ +#!/usr/bin/env bash + +# Create the platform in Aegir +drush --root='/var/aegir/platforms/scratchpads-2' provision-save '@platform_scratchpads-2' --context_type='platform' --makefile='/vagrant/scratchpads2.make' +drush --root='/var/aegir/platforms/scratchpads' provision-save '@platform_scratchpads' --context_type='platform' +drush @platform_scratchpads-2 provision-verify +drush @platform_scratchpads provision-verify +drush @hostmaster hosting-import @platform_scratchpads-2 +drush @hostmaster hosting-import @platform_scratchpads + +# Create a site +drush provision-save '@scratchpads.vagrant' --context_type='site' --uri='scratchpads.vagrant' --platform='@platform_scratchpads' --server='@server_master' --db_server='@server_localhost' --profile='scratchpad_2_training' --client_name='admin' +drush @scratchpads.vagrant provision-install +drush @hostmaster hosting-task @platform_scratchpads verify +# Clear the cache on the site, to ensure it's working as expected +drush @scratchpads.vagrant cc all +drush @scratchpads.vagrant cron diff --git a/vagrant/bootstrap.sh b/vagrant/bootstrap.sh new file mode 100644 index 0000000000..c9e4bb14ae --- /dev/null +++ b/vagrant/bootstrap.sh @@ -0,0 +1,190 @@ +#!/usr/bin/env bash + +# Add the Aegir package source +echo "deb http://debian.aegirproject.org stable main" | tee -a /etc/apt/sources.list.d/aegir-stable.list +wget -q http://debian.aegirproject.org/key.asc -O- | sudo apt-key add - + +# Update +apt-get update +apt-get upgrade -y +apt-get install debconf-utils -y + +# Set the various options for the packages that aegir will drag in with it (Postfix/MySQL/etc) +# MySQL +echo "mysql-server-5.5 mysql-server/root_password_again password vagrant" | debconf-set-selections +echo "mysql-server-5.5 mysql-server/root_password password vagrant" | debconf-set-selections +# Postfix +echo "postfix postfix/mailname string hostmaster.vagrant"|debconf-set-selections +echo "postfix postfix/main_mailer_type select Internet Site"|debconf-set-selections +echo "postfix postfix/destinations string hostmaster.vagrant, vagrant, localhost.localdomain, localhost"|debconf-set-selections +# Aegir +echo "aegir2-hostmaster aegir/db_password password vagrant"|debconf-set-selections +echo "aegir2-hostmaster aegir/db_user string root"|debconf-set-selections +echo "aegir2-hostmaster aegir/email string aegir@hostmaster.vagrant"|debconf-set-selections +echo "aegir2-hostmaster aegir/db_host string localhost"|debconf-set-selections +echo "aegir2-hostmaster aegir/makefile string "|debconf-set-selections +echo "aegir2-hostmaster aegir/site string hostmaster.vagrant"|debconf-set-selections +echo "aegir2-hostmaster aegir/webserver select apache2"|debconf-set-selections + +# Install dependencies for the aegir2 package +apt-get -y install apache2 drush git-core libapache2-mod-php5 mysql-client mysql-server php5 php5-mysql postfix rsync unzip +# Install additional packages Scratchpads 2 require +apt-get -y install dnsutils memcached solr-tomcat php5-gd php5-curl php5-memcached php5-mcrypt php5-dev php5-gmp varnish + +# Install a couple of PECL modules +pecl install mailparse +pecl install uploadprogress +echo "extension=mailparse.so" > /etc/php5/conf.d/mailparse.ini +echo "extension=uploadprogress.so" > /etc/php5/conf.d/uploadprogress.ini + +# Increase the memory limit for php +sed "s/memory_limit\ =\ 128M/memory_limit = 256M/" -i /etc/php5/apache2/php.ini + +# Convert all of the databases tables to MyISAM +#for i in $(echo "SELECT CONCAT(TABLE_SCHEMA, '.', TABLE_NAME) FROM information_schema.TABLES WHERE ENGINE='InnoDB'"|mysql -pvagrant|grep -v CONCAT) +#do +# echo "ALTER TABLE "$i" ENGINE=MyISAM;" | mysql -pvagrant +#done + +# Tweak MySQL to skip-innodb +echo "[mysqld] +innodb=OFF +default_storage_engine=MyISAM" > /etc/mysql/conf.d/skip-innodb.cnf +service mysql restart + +# Tweak the memcached settings to allow large objects. +sed "s/^-m\s[0-9]*/-m 256/" /etc/memcached.conf -i +echo "# Increase the Maximum size of cached objects +-I 16M" >> /etc/memcached.conf +service memcached restart + +# Point web-scratchpad-solr at the local machine so the local solr instance should just work +echo "127.0.0.1 web-scratchpad-solr.nhm.ac.uk" >> /etc/hosts +echo " + ServerName web-scratchpad-solr.nhm.ac.uk + DocumentRoot /var/www + + Order deny,allow + Allow from all + ProxyPass http://localhost:8080/solr nocanon connectiontimeout=30 timeout=60 + ProxyPassReverse http://localhost:8080/solr + +" > /etc/apache2/sites-available/solr +cd /etc/solr/conf +cp /vagrant/sites/all/modules/contrib/apachesolr/solr-conf/solr-3.x/* . +a2ensite solr +a2enmod proxy proxy_http +service apache2 reload +service tomcat6 restart + +# Install our own custom Aegir packages, as there is a bug in the +# standard package that results in the install attempting to ask +# the user for a password. +dpkg -i /vagrant/vagrant/debian-packages/aegir2_2.1_all.deb /vagrant/vagrant/debian-packages/aegir2-hostmaster_2.1_all_scratchpads.deb /vagrant/vagrant/debian-packages/aegir2-provision_2.1_all.deb +apt-get update -y +apt-get upgrade -y + +# Set the password for the admin aegir user +echo "UPDATE users SET pass = MD5('vagrant') WHERE uid = 1;" | mysql -uroot -pvagrant hostmastervagran + +VARNISHSECRET=`cat /etc/varnish/secret` + +# Add a global aegir config file which enables memcache, varnish and other caches +echo " 'default'); +\$conf['cache_backends'][] = 'sites/all/modules/contrib/memcache/memcache.inc'; +\$conf['cache_default_class'] = 'MemCacheDrupal'; + +// Varnish +\$conf['reverse_proxy'] = TRUE; +\$conf['reverse_proxy_addresses'] = array('127.0.0.1'); +\$conf['varnish_flush_cron'] = 0; +\$conf['varnish_version'] = 3; +\$conf['varnish_control_terminal'] = '127.0.0.1:6082'; +\$conf['varnish_control_key'] = '"$VARNISHSECRET"'; +\$conf['varnish_socket_timeout'] = 200; +\$conf['varnish_cache_clear'] = 0; +\$conf['varnish_bantype'] = 0; +\$conf['cache_backends'][] = 'sites/all/modules/contrib/varnish/varnish.cache.inc'; +\$conf['cache_class_cache_page'] = 'VarnishCache'; +\$conf['page_cache_invoke_hooks'] = FALSE; + + +// Not everybody can run updates. +\$update_free_access = 0; + +// Set some PHP settings +@ini_set('pcre.backtrack_limit', 10000000); +@ini_set('pcre.recursion_limit', 10000000); +@ini_set('session.cookie_lifetime', 604800); +@ini_set('session.gc_maxlifetime', 604800); +@ini_set('session.use_cookies', 1); +@ini_set('mysql.default_socket', '/var/lib/mysql/mysql.sock');" > /var/aegir/config/includes/global.inc + +# Create the Scratchpads web folder +cd /var/aegir/platforms +mkdir scratchpads +cd scratchpads +ln -s /vagrant/* /vagrant/.htaccess . +rm sites +mkdir sites +cd sites +ln -s /vagrant/sites/* . +rm all +mkdir all +cd all +ln -s /vagrant/sites/all/* . +rm drush +chown aegir:www-data /var/aegir/platforms/scratchpads/sites +chown aegir:www-data /var/aegir/platforms/scratchpads/sites/all +# Special copy for the scratchpads_twitter.ini file which may be in the /vagrant folder +mkdir -p /usr/local/share/scratchpads-global +cp /vagrant/scratchpads_twitter.ini /usr/local/share/scratchpads-global + +# Restart Apache so that the new global file is read +service apache2 reload + +# Add a record to the hosts file so that the site can access itself +echo "127.0.0.1 scratchpads.vagrant" >> /etc/hosts +# Create the Scratchpads platform +su -c /vagrant/vagrant/bootstrap.aegir.sh aegir + +# Get the external IP address to inform people to add it to their hosts file. +IPADDRESS=`/sbin/ifconfig eth1 | grep 'inet addr:' | cut -d: -f2 | awk '{ print $1}'` + +# Inform the user how they can login to the Aegir site. +echo " + + + + + + + + + + +Add the following entry to your 'hosts' file (http://www.rackspace.com/knowledge_center/article/how-do-i-modify-my-hosts-file) +"$IPADDRESS" scratchpads.vagrant hostmaster.vagrant + +Then login to the Aegir interface: +http://hostmaster.vagrant/ +Username: admin +Password: vagrant" diff --git a/vagrant/debian-packages/aegir2-hostmaster_2.1_all_scratchpads.deb b/vagrant/debian-packages/aegir2-hostmaster_2.1_all_scratchpads.deb new file mode 100644 index 0000000000000000000000000000000000000000..b2873c72d9fae000055907ab5dca5e29968e505d GIT binary patch literal 13702 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 literal 0 HcmV?d00001 diff --git a/vagrant/debian-packages/aegir2-provision_2.1_all.deb b/vagrant/debian-packages/aegir2-provision_2.1_all.deb new file mode 100644 index 0000000000000000000000000000000000000000..b3f46417c1849ddc0542da256d60e61924e014e5 GIT binary patch literal 109834 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ℑ#&&+p>UjaJ0&9dh7^t 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 FSz3zqzO4WN literal 0 HcmV?d00001 diff --git a/vagrant/debian-packages/aegir2_2.1_all.deb b/vagrant/debian-packages/aegir2_2.1_all.deb new file mode 100644 index 0000000000000000000000000000000000000000..d3f666b3bc17845282698dee8e334c7d85032014 GIT binary patch literal 7648 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 literal 0 HcmV?d00001