core/core.services.yml | 22 + core/includes/common.inc | 841 +------------------- core/includes/install.core.inc | 4 + .../Core/Asset/AssetCollectionGrouperInterface.php | 25 + .../Asset/AssetCollectionOptimizerInterface.php | 25 + .../Asset/AssetCollectionRendererInterface.php | 25 + core/lib/Drupal/Core/Asset/AssetDumper.php | 52 ++ .../lib/Drupal/Core/Asset/AssetDumperInterface.php | 27 + .../Drupal/Core/Asset/AssetOptimizerInterface.php | 25 + .../lib/Drupal/Core/Asset/CssCollectionGrouper.php | 97 +++ .../Drupal/Core/Asset/CssCollectionOptimizer.php | 179 +++++ .../Drupal/Core/Asset/CssCollectionRenderer.php | 177 ++++ core/lib/Drupal/Core/Asset/CssOptimizer.php | 217 +++++ core/lib/Drupal/Core/Asset/JsCollectionGrouper.php | 80 ++ .../Drupal/Core/Asset/JsCollectionOptimizer.php | 167 ++++ .../lib/Drupal/Core/Asset/JsCollectionRenderer.php | 97 +++ core/lib/Drupal/Core/Asset/JsOptimizer.php | 31 + core/modules/color/color.module | 9 +- .../Drupal/layout/Tests/LayoutDerivativesTest.php | 2 +- .../comment_hacks.css.unoptimized.css | 80 -- .../css_input_with_import.css.unoptimized.css | 30 - .../css_input_without_import.css.unoptimized.css | 65 -- .../Tests/Common/CascadingStylesheetsTest.php | 8 +- .../Tests/Common/CascadingStylesheetsTest.php.rej | 20 + .../Tests/Common/CascadingStylesheetsUnitTest.php | 63 -- .../Drupal/system/Tests/Common/JavaScriptTest.php | 49 +- core/modules/system/system.module | 4 - .../Core/Asset/CssCollectionGrouperUnitTest.php | 216 +++++ .../Core/Asset/CssCollectionRendererUnitTest.php | 572 +++++++++++++ .../Tests/Core/Asset/CssOptimizerUnitTest.php | 227 ++++++ .../Core/Asset}/css_test_files/comment_hacks.css | 0 .../css_test_files/comment_hacks.css.optimized.css | 0 .../css_test_files/css_input_with_import.css | 0 .../css_input_with_import.css.optimized.css | 0 .../css_test_files/css_input_without_import.css | 0 .../css_input_without_import.css.optimized.css | 0 .../Tests/Core/Asset}/css_test_files/import1.css | 0 .../Tests/Core/Asset}/css_test_files/import2.css | 0 38 files changed, 2357 insertions(+), 1079 deletions(-) diff --git a/core/core.services.yml b/core/core.services.yml index d915e10..8f561ca 100644 --- a/core/core.services.yml +++ b/core/core.services.yml @@ -559,3 +559,25 @@ services: tags: - { name: event_subscriber } arguments: ['@authentication'] + asset.css.collection_renderer: + class: Drupal\Core\Asset\CssCollectionRenderer + asset.css.collection_optimizer: + class: Drupal\Core\Asset\CssCollectionOptimizer + arguments: [ '@asset.css.collection_grouper', '@asset.css.optimizer', '@asset.css.dumper', '@state' ] + asset.css.optimizer: + class: Drupal\Core\Asset\CssOptimizer + asset.css.collection_grouper: + class: Drupal\Core\Asset\CssCollectionGrouper + asset.css.dumper: + class: Drupal\Core\Asset\AssetDumper + asset.js.collection_renderer: + class: Drupal\Core\Asset\JsCollectionRenderer + asset.js.collection_optimizer: + class: Drupal\Core\Asset\JsCollectionOptimizer + arguments: [ '@asset.js.collection_grouper', '@asset.js.optimizer', '@asset.js.dumper', '@state' ] + asset.js.optimizer: + class: Drupal\Core\Asset\JsOptimizer + asset.js.collection_grouper: + class: Drupal\Core\Asset\JsCollectionGrouper + asset.js.dumper: + class: Drupal\Core\Asset\AssetDumper diff --git a/core/includes/common.inc b/core/includes/common.inc index fb0abac..d503a25 100644 --- a/core/includes/common.inc +++ b/core/includes/common.inc @@ -26,6 +26,15 @@ use Zend\Feed\Writer\Writer; use Zend\Feed\Reader\Reader; +use Drupal\Core\Asset\CssCollectionRenderer; +use Drupal\Core\Asset\CssCollectionOptimizer; +use Drupal\Core\Asset\CssCollectionGrouper; +use Drupal\Core\Asset\CssOptimizer; +use Drupal\Core\Asset\JsCollectionRenderer; +use Drupal\Core\Asset\JsCollectionOptimizer; +use Drupal\Core\Asset\JsCollectionGrouper; +use Drupal\Core\Asset\AssetDumper; + /** * @file * Common functions that many Drupal modules will need to reference. @@ -1800,154 +1809,6 @@ function drupal_sort_css_js($a, $b) { } /** - * Grouping callback: Groups CSS items by their types, media, and browsers. - * - * This function arranges the CSS items that are in the #items property of the - * styles element into groups. Arranging the CSS items into groups serves two - * purposes. When aggregation is enabled, files within a group are aggregated - * into a single file, significantly improving page loading performance by - * minimizing network traffic overhead. When aggregation is disabled, grouping - * allows multiple files to be loaded from a single STYLE tag, enabling sites - * with many modules enabled or a complex theme being used to stay within IE's - * 31 CSS inclusion tag limit: http://drupal.org/node/228818. - * - * This function puts multiple items into the same group if they are groupable - * and if they are for the same 'media' and 'browsers'. Items of the 'file' type - * are groupable if their 'preprocess' flag is TRUE, items of the 'inline' type - * are always groupable, and items of the 'external' type are never groupable. - * This function also ensures that the process of grouping items does not change - * their relative order. This requirement may result in multiple groups for the - * same type, media, and browsers, if needed to accommodate other items in - * between. - * - * @param $css - * An array of CSS items, as returned by drupal_add_css(), but after - * alteration performed by drupal_get_css(). - * - * @return - * An array of CSS groups. Each group contains the same keys (e.g., 'media', - * 'data', etc.) as a CSS item from the $css parameter, with the value of - * each key applying to the group as a whole. Each group also contains an - * 'items' key, which is the subset of items from $css that are in the group. - * - * @see drupal_pre_render_styles() - * @see system_element_info() - */ -function drupal_group_css($css) { - $groups = array(); - // If a group can contain multiple items, we track the information that must - // be the same for each item in the group, so that when we iterate the next - // item, we can determine if it can be put into the current group, or if a - // new group needs to be made for it. - $current_group_keys = NULL; - // When creating a new group, we pre-increment $i, so by initializing it to - // -1, the first group will have index 0. - $i = -1; - foreach ($css as $item) { - // The browsers for which the CSS 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 - // that's different is that order. - ksort($item['browsers']); - - // If the item can be grouped with other items, set $group_keys to an array - // of information that must be the same for all items in its group. If the - // item can't be grouped with other items, set $group_keys to FALSE. We - // put items into a group that can be aggregated together: whether they will - // be aggregated is up to the _drupal_css_aggregate() function or an - // override of that function specified in hook_css_alter(), but regardless - // of the details of that function, a group represents items that can be - // aggregated. Since a group may be rendered with a single HTML tag, all - // items in the group must share the same information that would need to be - // part of that HTML tag. - switch ($item['type']) { - case 'file': - // Group file items if their 'preprocess' flag is TRUE. - // 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_css() for details about that. - $group_keys = $item['preprocess'] ? array($item['type'], $item['group'], $item['every_page'], $item['media'], $item['browsers']) : FALSE; - break; - case 'inline': - // Always group inline items. - $group_keys = array($item['type'], $item['media'], $item['browsers']); - break; - case 'external': - // Do not group external items. - $group_keys = FALSE; - break; - } - - // If the group keys don't match the most recent group we're working with, - // then a new group must be made. - if ($group_keys !== $current_group_keys) { - $i++; - // Initialize the new group with the same properties as the first item - // being placed into it. The item's 'data' and 'weight' properties are - // unique to the item and should not be carried over to the group. - $groups[$i] = $item; - unset($groups[$i]['data'], $groups[$i]['weight']); - $groups[$i]['items'] = array(); - $current_group_keys = $group_keys ? $group_keys : NULL; - } - - // Add the item to the current group. - $groups[$i]['items'][] = $item; - } - return $groups; -} - -/** - * Aggregation callback: Aggregates CSS files and inline content. - * - * Having the browser load fewer CSS files results in much faster page loads - * than when it loads many small files. This function aggregates files within - * the same group into a single file unless the site-wide setting to do so is - * disabled (commonly the case during site development). To optimize download, - * it also compresses the aggregate files by removing comments, whitespace, and - * other unnecessary content. Additionally, this functions aggregates inline - * content together, regardless of the site-wide aggregation setting. - * - * @param $css_groups - * An array of CSS groups as returned by drupal_group_css(). This function - * modifies the group's 'data' property for each group that is aggregated. - * - * @see drupal_group_css() - * @see drupal_pre_render_styles() - * @see system_element_info() - */ -function drupal_aggregate_css(&$css_groups) { - // Only aggregate during normal site operation. - if (defined('MAINTENANCE_MODE')) { - $preprocess_css = FALSE; - } - else { - $config = config('system.performance'); - $preprocess_css = $config->get('css.preprocess'); - } - - // For each group that needs aggregation, aggregate its items. - foreach ($css_groups as $key => $group) { - switch ($group['type']) { - // 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'] && $preprocess_css) { - $css_groups[$key]['data'] = drupal_build_css_cache($group['items']); - } - break; - // Aggregate all inline CSS content into the group's data property. - case 'inline': - $css_groups[$key]['data'] = ''; - foreach ($group['items'] as $item) { - $css_groups[$key]['data'] .= drupal_load_stylesheet_content($item['data'], $item['preprocess']); - } - break; - } - } -} - -/** * Pre-render callback: Adds the elements needed for CSS tags to be rendered. * * For production websites, LINK tags are preferable to STYLE tags with @import @@ -1997,11 +1858,6 @@ function drupal_aggregate_css(&$css_groups) { * 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 * A render array that will render to a string of XHTML CSS tags. @@ -2009,393 +1865,13 @@ function drupal_aggregate_css(&$css_groups) { * @see drupal_get_css() */ function drupal_pre_render_styles($elements) { - // Group and aggregate the items. - if (isset($elements['#group_callback'])) { - $elements['#groups'] = $elements['#group_callback']($elements['#items']); - } - if (isset($elements['#aggregate_callback'])) { - $elements['#aggregate_callback']($elements['#groups']); - } - - // 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. - $query_string = variable_get('css_js_query_string', '0'); - - // For inline CSS to validate as XHTML, all CSS containing XHTML needs to be - // wrapped in CDATA. To make that backwards compatible with HTML 4, we need to - // comment out the CDATA-tag. - $embed_prefix = "\n/* */\n"; - - // Defaults for LINK and STYLE elements. - $link_element_defaults = array( - '#type' => 'html_tag', - '#tag' => 'link', - '#attributes' => array( - 'rel' => 'stylesheet', - ), - ); - $style_element_defaults = array( - '#type' => 'html_tag', - '#tag' => 'style', - ); + $css_assets = $elements['#items']; - // Loop through each group. - foreach ($elements['#groups'] as $group) { - switch ($group['type']) { - // For file items, there are three possibilites. - // - The group has been aggregated: in this case, output a LINK tag for - // the aggregate file. - // - The group can be aggregated but has not been (most likely because - // the site administrator disabled the site-wide setting): in this case, - // output as few STYLE tags for the group as possible, using @import - // statement for each file in the group. This enables us to stay within - // IE's limit of 31 total CSS inclusion tags. - // - The group contains items not eligible for aggregation (their - // 'preprocess' flag has been set to FALSE): in this case, output a LINK - // tag for each file. - case 'file': - // The group has been aggregated into a single file: output a LINK tag - // for the aggregate file. - if (isset($group['data'])) { - $element = $link_element_defaults; - $element['#attributes']['href'] = file_create_url($group['data']); - $element['#attributes']['media'] = $group['media']; - $element['#browsers'] = $group['browsers']; - $elements[] = $element; - } - // The group can be aggregated, but hasn't been: combine multiple items - // into as few STYLE tags as possible. - elseif ($group['preprocess']) { - $import = array(); - foreach ($group['items'] as $item) { - // 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 - // group on the STYLE tag. - $import[] = '@import url("' . check_plain(file_create_url($item['data']) . '?' . $query_string) . '");'; - } - // In addition to IE's limit of 31 total CSS inclusion tags, it also - // has a limit of 31 @import statements per STYLE tag. - while (!empty($import)) { - $import_batch = array_slice($import, 0, 31); - $import = array_slice($import, 31); - $element = $style_element_defaults; - // This simplifies the JavaScript regex, allowing each line - // (separated by \n) to be treated as a completely different string. - // This means that we can use ^ and $ on one line at a time, and not - // worry about style tags since they'll never match the regex. - $element['#value'] = "\n" . implode("\n", $import_batch) . "\n"; - $element['#attributes']['media'] = $group['media']; - $element['#browsers'] = $group['browsers']; - $elements[] = $element; - } - } - // The group contains items ineligible for aggregation: output a LINK - // tag for each file. - else { - foreach ($group['items'] as $item) { - $element = $link_element_defaults; - // The dummy query string needs to be added to the URL to control - // browser-caching. - $query_string_separator = (strpos($item['data'], '?') !== FALSE) ? '&' : '?'; - $element['#attributes']['href'] = file_create_url($item['data']) . $query_string_separator . $query_string; - $element['#attributes']['media'] = $item['media']; - $element['#browsers'] = $group['browsers']; - $elements[] = $element; - } - } - break; - // For inline content, the 'data' property contains the CSS content. If - // the group's 'data' property is set, then output it in a single STYLE - // tag. Otherwise, output a separate STYLE tag for each item. - case 'inline': - if (isset($group['data'])) { - $element = $style_element_defaults; - $element['#value'] = $group['data']; - $element['#value_prefix'] = $embed_prefix; - $element['#value_suffix'] = $embed_suffix; - $element['#attributes']['media'] = $group['media']; - $element['#browsers'] = $group['browsers']; - $elements[] = $element; - } - else { - foreach ($group['items'] as $item) { - $element = $style_element_defaults; - $element['#value'] = $item['data']; - $element['#value_prefix'] = $embed_prefix; - $element['#value_suffix'] = $embed_suffix; - $element['#attributes']['media'] = $item['media']; - $element['#browsers'] = $group['browsers']; - $elements[] = $element; - } - } - break; - // Output a LINK tag for each external item. The item's 'data' property - // contains the full URL. - case 'external': - foreach ($group['items'] as $item) { - $element = $link_element_defaults; - $element['#attributes']['href'] = $item['data']; - $element['#attributes']['media'] = $item['media']; - $element['#browsers'] = $group['browsers']; - $elements[] = $element; - } - break; - } + // Aggregate the CSS if necessary, but only during normal site operation. + if (!defined('MAINTENANCE_MODE') && config('system.performance')->get('css.preprocess')) { + $css_assets = \Drupal::service('asset.css.collection_optimizer')->optimize($css_assets); } - - return $elements; -} - -/** - * Aggregates and optimizes CSS files into a cache file in the files directory. - * - * The file name for the CSS cache file is generated from the hash of the - * aggregated contents of the files in $css. This forces proxies and browsers - * to download new CSS when the CSS changes. - * - * The cache file name is retrieved on a page load via a lookup variable that - * contains an associative array. The array key is the hash of the file names - * in $css while the value is the cache file name. The cache file is generated - * in two cases. First, if there is no file name value for the key, which will - * happen if a new file name has been added to $css or after the lookup - * variable is emptied to force a rebuild of the cache. Second, the cache file - * is generated if it is missing on disk. Old cache files are not deleted - * immediately when the lookup variable is emptied, but are deleted after a set - * period by drupal_delete_file_if_stale(). This ensures that files referenced - * by a cached page will still be available. - * - * @param $css - * An array of CSS files to aggregate and compress into one file. - * - * @return - * The URI of the CSS cache file, or FALSE if the file could not be saved. - */ -function drupal_build_css_cache($css) { - $data = ''; - $uri = ''; - $map = Drupal::state()->get('drupal_css_cache_files') ?: array(); - // Create a new array so that only the file names are used to create the hash. - // This prevents new aggregates from being created unnecessarily. - $css_data = array(); - foreach ($css as $css_file) { - $css_data[] = $css_file['data']; - } - $key = hash('sha256', serialize($css_data)); - if (isset($map[$key])) { - $uri = $map[$key]; - } - - if (empty($uri) || !file_exists($uri)) { - // Build aggregate CSS file. - foreach ($css as $stylesheet) { - // Only 'file' stylesheets can be aggregated. - if ($stylesheet['type'] == 'file') { - $contents = drupal_load_stylesheet($stylesheet['data'], TRUE); - - // Get the parent directory of this file, relative to the Drupal root. - $css_base_url = substr($stylesheet['data'], 0, strrpos($stylesheet['data'], '/')); - _drupal_build_css_path(NULL, $css_base_url . '/'); - // Anchor all paths in the CSS with its base URL, ignoring external and absolute paths. - $data .= preg_replace_callback('/url\(\s*[\'"]?(?![a-z]+:|\/+)([^\'")]+)[\'"]?\s*\)/i', '_drupal_build_css_path', $contents); - } - } - - // Per the W3C specification at http://www.w3.org/TR/REC-CSS2/cascade.html#at-import, - // @import rules must proceed any other style, so we move those to the top. - $regexp = '/@import[^;]+;/i'; - preg_match_all($regexp, $data, $matches); - $data = preg_replace($regexp, '', $data); - $data = implode('', $matches[0]) . $data; - - // Prefix filename to prevent blocking by firewalls which reject files - // starting with "ad*". - $filename = 'css_' . Crypt::hashBase64($data) . '.css'; - // Create the css/ within the files folder. - $csspath = 'public://css'; - $uri = $csspath . '/' . $filename; - // Create the CSS file. - file_prepare_directory($csspath, FILE_CREATE_DIRECTORY); - if (!file_exists($uri) && !file_unmanaged_save_data($data, $uri, FILE_EXISTS_REPLACE)) { - return FALSE; - } - // If CSS gzip compression is enabled and the zlib extension is available - // then create a gzipped version of this file. This file is served - // conditionally to browsers that accept gzip using .htaccess rules. - // It's possible that the rewrite rules in .htaccess aren't working on this - // server, but there's no harm (other than the time spent generating the - // file) in generating the file anyway. Sites on servers where rewrite rules - // aren't working can set css.gzip to FALSE in order to skip - // generating a file that won't be used. - if (config('system.performance')->get('css.gzip') && extension_loaded('zlib')) { - if (!file_exists($uri . '.gz') && !file_unmanaged_save_data(gzencode($data, 9, FORCE_GZIP), $uri . '.gz', FILE_EXISTS_REPLACE)) { - return FALSE; - } - } - // Save the updated map. - $map[$key] = $uri; - Drupal::state()->set('drupal_css_cache_files', $map); - } - return $uri; -} - -/** - * Prefixes all paths within a CSS file for drupal_build_css_cache(). - */ -function _drupal_build_css_path($matches, $base = NULL) { - $_base = &drupal_static(__FUNCTION__); - // Store base path for preg_replace_callback. - if (isset($base)) { - $_base = $base; - } - - // Prefix with base and remove '../' segments where possible. - $path = $_base . $matches[1]; - $last = ''; - while ($path != $last) { - $last = $path; - $path = preg_replace('`(^|/)(?!\.\./)([^/]+)/\.\./`', '$1', $path); - } - return 'url(' . file_create_url($path) . ')'; -} - -/** - * Loads the stylesheet and resolves all @import commands. - * - * Loads a stylesheet and replaces @import commands with the contents of the - * imported file. Use this instead of file_get_contents when processing - * stylesheets. - * - * The returned contents are compressed removing white space and comments only - * when CSS aggregation is enabled. This optimization will not apply for - * color.module enabled themes with CSS aggregation turned off. - * - * @param $file - * Name of the stylesheet to be processed. - * @param $optimize - * Defines if CSS contents should be compressed or not. - * @param $reset_basepath - * Used internally to facilitate recursive resolution of @import commands. - * - * @return - * Contents of the stylesheet, including any resolved @import commands. - */ -function drupal_load_stylesheet($file, $optimize = NULL, $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. - if (isset($optimize)) { - $_optimize = $optimize; - } - - // Stylesheets are relative one to each other. Start by adding a base path - // prefix provided by the parent stylesheet (if necessary). - if ($basepath && !file_uri_scheme($file)) { - $file = $basepath . '/' . $file; - } - $basepath = dirname($file); - - // Load the CSS stylesheet. We suppress errors because themes may specify - // stylesheets in their .info.yml file that don't exist in the theme's path, - // but are merely there to disable certain module CSS files. - if ($contents = @file_get_contents($file)) { - // Return the processed stylesheet. - return drupal_load_stylesheet_content($contents, $_optimize); - } - - return ''; -} - -/** - * Processes the contents of a stylesheet for aggregation. - * - * @param $contents - * The contents of the stylesheet. - * @param $optimize - * (optional) Boolean whether CSS contents should be minified. Defaults to - * FALSE. - * - * @return - * Contents of the stylesheet including the imported stylesheets. - */ -function drupal_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 ($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. - $single_quot = "'[^'\\\\]*(?:\\\\.[^'\\\\]*)*'"; - // Strip all comment blocks, but keep double/single quoted strings. - $contents = preg_replace( - "<($double_quot|$single_quot)|$comment>Ss", - "$1", - $contents - ); - // Remove certain whitespace. - // There are different conditions for removing leading and trailing - // whitespace. - // @see http://php.net/manual/regexp.reference.subpatterns.php - $contents = preg_replace('< - # Strip leading and trailing whitespace. - \s*([@{};,])\s* - # Strip only leading whitespace from: - # - Closing parenthesis: Retain "@media (bar) and foo". - | \s+([\)]) - # Strip only trailing whitespace from: - # - Opening parenthesis: Retain "@media (bar) and foo". - # - Colon: Retain :pseudo-selectors. - | ([\(:])\s+ - >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. - '$1$2$3', - $contents - ); - // End the file with a new line. - $contents = trim($contents); - $contents .= "\n"; - } - - // 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*;/', '_drupal_load_stylesheet', $contents); - return $contents; -} - -/** - * Loads stylesheets recursively and returns contents with corrected paths. - * - * This function is used for recursive loading of stylesheets and - * returns the stylesheet content with all url() paths corrected. - */ -function _drupal_load_stylesheet($matches) { - $filename = $matches[1]; - // Load the imported stylesheet and replace @import commands in there as well. - $file = drupal_load_stylesheet($filename, NULL, FALSE); - - // Determine the file's directory. - $directory = dirname($filename); - // If the file is in the current directory, make sure '.' doesn't appear in - // the url() path. - $directory = $directory == '.' ? '' : $directory .'/'; - - // Alter all internal url() paths. Leave external paths alone. We don't need - // to normalize absolute paths here (i.e. remove folder/... segments) because - // that will be done later. - return preg_replace('/url\(\s*([\'"]?)(?![a-z]+:|\/+)/i', 'url(\1'. $directory, $file); + return \Drupal::service('asset.css.collection_renderer')->render($css_assets); } /** @@ -3017,214 +2493,14 @@ function drupal_merge_js_settings($settings_items) { * @see drupal_get_js() */ function drupal_pre_render_scripts($elements) { - // Group and aggregate the items. - if (isset($elements['#group_callback'])) { - $elements['#groups'] = $elements['#group_callback']($elements['#items']); - } - if (isset($elements['#aggregate_callback'])) { - $elements['#aggregate_callback']($elements['#groups']); - } - - // 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. Files that should not be cached (see drupal_add_js()) - // get REQUEST_TIME as query-string instead, to enforce reload on every - // page request. - $default_query_string = variable_get('css_js_query_string', '0'); - - // For inline JavaScript to validate as XHTML, all JavaScript containing - // XHTML needs to be wrapped in CDATA. To make that backwards compatible - // with HTML 4, we need to comment out the CDATA-tag. - $embed_prefix = "\n\n"; - - // Since JavaScript may look for arguments in the URL and act on them, some - // third-party code might require the use of a different query string. - $js_version_string = variable_get('drupal_js_version_query_string', 'v='); - - // Defaults for each SCRIPT element. - $element_defaults = array( - '#type' => 'html_tag', - '#tag' => 'script', - '#value' => '', - ); - - // Loop through each group. - foreach ($elements['#groups'] as $group) { - // If a group of files has been aggregated into a single file, - // $group['data'] contains the URI of the aggregate file. Add a single - // script element for this file. - if ($group['type'] == 'file' && isset($group['data'])) { - $element = $element_defaults; - $element['#attributes']['src'] = file_create_url($group['data']); - $element['#browsers'] = $group['browsers']; - $elements[] = $element; - } - // For non-file types, and non-aggregated files, add a script element per - // item. - else { - foreach ($group['items'] as $item) { - // Element properties that do not depend on item type. - $element = $element_defaults; - $element['#browsers'] = $item['browsers']; - - // Element properties that depend on item type. - switch ($item['type']) { - case 'setting': - $element['#value_prefix'] = $embed_prefix; - $element['#value'] = 'var drupalSettings = ' . drupal_json_encode(drupal_merge_js_settings($item['data'])) . ";"; - $element['#value_suffix'] = $embed_suffix; - break; - - case 'inline': - $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) ? '&' : '?'; - $element['#attributes']['src'] = file_create_url($item['data']) . $query_string_separator . ($item['cache'] ? $query_string : REQUEST_TIME); - break; - - case 'external': - $element['#attributes']['src'] = $item['data']; - break; - } + $js_assets = $elements['#items']; - // Attributes may only be set if this script is output independently. - if (!empty($element['#attributes']['src']) && !empty($item['attributes'])) { - $element['#attributes'] += $item['attributes']; - } - - $elements[] = $element; - } - } - } - - return $elements; -} - -/** - * Default callback to group JavaScript items. - * - * This function arranges the JavaScript items that are in the #items property - * of the scripts element into groups. When aggregation is enabled, files within - * a group are aggregated into a single file, significantly improving page - * loading performance by minimizing network traffic overhead. - * - * This function puts multiple items into the same group if they are groupable - * and if they are for the same browsers. Items of the 'file' type are groupable - * if their 'preprocess' flag is TRUE. Items of the 'inline', 'settings', or - * 'external' type are not groupable. - * - * This function also ensures that the process of grouping items does not change - * their relative order. This requirement may result in multiple groups for the - * same type and browsers, if needed to accommodate other items in - * between. - * - * @param $javascript - * An array of JavaScript items, as returned by drupal_add_js(), but after - * alteration performed by drupal_get_js(). - * - * @return - * An array of JavaScript groups. Each group contains the same keys (e.g., - * 'data', etc.) as a JavaScript item from the $javascript parameter, with the - * value of each key applying to the group as a whole. Each group also - * contains an 'items' key, which is the subset of items from $javascript that - * are in the group. - * - * @see drupal_pre_render_scripts() - */ -function drupal_group_js($javascript) { - $groups = array(); - // If a group can contain multiple items, we track the information that must - // be the same for each item in the group, so that when we iterate the next - // item, we can determine if it can be put into the current group, or if a - // new group needs to be made for it. - $current_group_keys = NULL; - $index = -1; - foreach ($javascript as $item) { - // 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 - // that's different is that order. - ksort($item['browsers']); - - switch ($item['type']) { - case 'file': - // Group file items if their 'preprocess' flag is TRUE. - // 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($item['type'], $item['group'], $item['every_page'], $item['browsers']) : FALSE; - break; - - case 'external': - case 'setting': - case 'inline': - // Do not group external, settings, and inline items. - $group_keys = FALSE; - break; - } - - // If the group keys don't match the most recent group we're working with, - // then a new group must be made. - if ($group_keys !== $current_group_keys) { - $index++; - // Initialize the new group with the same properties as the first item - // being placed into it. The item's 'data' and 'weight' properties are - // unique to the item and should not be carried over to the group. - $groups[$index] = $item; - unset($groups[$index]['data'], $groups[$index]['weight']); - $groups[$index]['items'] = array(); - $current_group_keys = $group_keys ? $group_keys : NULL; - } - - // Add the item to the current group. - $groups[$index]['items'][] = $item; - } - - return $groups; -} - -/** - * Default callback to aggregate JavaScript files. - * - * Having the browser load fewer JavaScript files results in much faster page - * loads than when it loads many small files. This function aggregates files - * within the same group into a single file unless the site-wide setting to do - * so is disabled (commonly the case during site development). To optimize - * download, it also compresses the aggregate files by removing comments, - * whitespace, and other unnecessary content. - * - * @param $js_groups - * An array of JavaScript groups as returned by drupal_group_js(). For each - * group that is aggregated, this function sets the value of the group's - * 'data' key to the URI of the aggregate file. - * - * @see drupal_group_js() - * @see drupal_pre_render_scripts() - */ -function drupal_aggregate_js(&$js_groups) { - // Only aggregate during normal site operation. - if (defined('MAINTENANCE_MODE')) { - $preprocess_js = FALSE; - } - else { - $config = config('system.performance'); - $preprocess_js = $config->get('js.preprocess'); - } - - if ($preprocess_js) { - foreach ($js_groups as $key => $group) { - if ($group['type'] == 'file' && $group['preprocess']) { - $js_groups[$key]['data'] = drupal_build_js_cache($group['items']); - } - } + // Aggregate the JavaScript if necessary, but only during normal site + // operation. + if (!defined('MAINTENANCE_MODE') && config('system.performance')->get('js.preprocess')) { + $js_assets = \Drupal::service('asset.js.collection_optimizer')->optimize($js_assets); } + return \Drupal::service('asset.js.collection_renderer')->render($js_assets); } /** @@ -3727,83 +3003,6 @@ function drupal_add_tabledrag($table_id, $action, $relationship, $group, $subgro } /** - * Aggregates JavaScript files into a cache file in the files directory. - * - * The file name for the JavaScript cache file is generated from the hash of - * the aggregated contents of the files in $files. This forces proxies and - * browsers to download new JavaScript when the JavaScript changes. - * - * The cache file name is retrieved on a page load via a lookup variable that - * contains an associative array. The array key is the hash of the names in - * $files while the value is the cache file name. The cache file is generated - * in two cases. First, if there is no file name value for the key, which will - * happen if a new file name has been added to $files or after the lookup - * variable is emptied to force a rebuild of the cache. Second, the cache file - * is generated if it is missing on disk. Old cache files are not deleted - * immediately when the lookup variable is emptied, but are deleted after a set - * period by drupal_delete_file_if_stale(). This ensures that files referenced - * by a cached page will still be available. - * - * @param $files - * An array of JavaScript files to aggregate and compress into one file. - * - * @return - * The URI of the cache file, or FALSE if the file could not be saved. - */ -function drupal_build_js_cache($files) { - $contents = ''; - $uri = ''; - $map = Drupal::state()->get('system.js_cache_files') ?: array(); - // Create a new array so that only the file names are used to create the hash. - // This prevents new aggregates from being created unnecessarily. - $js_data = array(); - foreach ($files as $file) { - $js_data[] = $file['data']; - } - $key = hash('sha256', serialize($js_data)); - if (isset($map[$key])) { - $uri = $map[$key]; - } - - if (empty($uri) || !file_exists($uri)) { - // Build aggregate JS file. - foreach ($files as $info) { - if ($info['preprocess']) { - // Append a ';' and a newline after each JS file to prevent them from running together. - $contents .= file_get_contents($info['data']) . ";\n"; - } - } - // Prefix filename to prevent blocking by firewalls which reject files - // starting with "ad*". - $filename = 'js_' . Crypt::hashBase64($contents) . '.js'; - // Create the js/ within the files folder. - $jspath = 'public://js'; - $uri = $jspath . '/' . $filename; - // Create the JS file. - file_prepare_directory($jspath, FILE_CREATE_DIRECTORY); - if (!file_exists($uri) && !file_unmanaged_save_data($contents, $uri, FILE_EXISTS_REPLACE)) { - return FALSE; - } - // If JS gzip compression is enabled and the zlib extension is available - // then create a gzipped version of this file. This file is served - // conditionally to browsers that accept gzip using .htaccess rules. - // It's possible that the rewrite rules in .htaccess aren't working on this - // server, but there's no harm (other than the time spent generating the - // file) in generating the file anyway. Sites on servers where rewrite rules - // aren't working can set js.gzip to FALSE in order to skip - // generating a file that won't be used. - if (config('system.performance')->get('js.gzip') && extension_loaded('zlib')) { - if (!file_exists($uri . '.gz') && !file_unmanaged_save_data(gzencode($contents, 9, FORCE_GZIP), $uri . '.gz', FILE_EXISTS_REPLACE)) { - return FALSE; - } - } - $map[$key] = $uri; - Drupal::state()->set('system.js_cache_files', $map); - } - return $uri; -} - -/** * Deletes old cached JavaScript files and variables. */ function drupal_clear_js_cache() { diff --git a/core/includes/install.core.inc b/core/includes/install.core.inc index ee5c0a4..4d0d6da 100644 --- a/core/includes/install.core.inc +++ b/core/includes/install.core.inc @@ -436,6 +436,10 @@ function install_begin_request(&$install_state) { $container->register('url_generator', 'Drupal\Core\Routing\NullGenerator'); + // Register the CSS and JavaScript asset collection renderers. + $container->register('asset.css.collection_renderer', 'Drupal\Core\Asset\CssCollectionRenderer'); + $container->register('asset.js.collection_renderer', 'Drupal\Core\Asset\JsCollectionRenderer'); + Drupal::setContainer($container); } diff --git a/core/lib/Drupal/Core/Asset/AssetCollectionGrouperInterface.php b/core/lib/Drupal/Core/Asset/AssetCollectionGrouperInterface.php new file mode 100644 index 0000000..94f9e85 --- /dev/null +++ b/core/lib/Drupal/Core/Asset/AssetCollectionGrouperInterface.php @@ -0,0 +1,25 @@ +get($file_extension . '.gzip') && extension_loaded('zlib')) { + if (!file_exists($uri . '.gz') && !file_unmanaged_save_data(gzencode($data, 9, FORCE_GZIP), $uri . '.gz', FILE_EXISTS_REPLACE)) { + return FALSE; + } + } + return $uri; + } + +} diff --git a/core/lib/Drupal/Core/Asset/AssetDumperInterface.php b/core/lib/Drupal/Core/Asset/AssetDumperInterface.php new file mode 100644 index 0000000..5f70aa6 --- /dev/null +++ b/core/lib/Drupal/Core/Asset/AssetDumperInterface.php @@ -0,0 +1,27 @@ +grouper = $grouper; + $this->optimizer = $optimizer; + $this->dumper = $dumper; + $this->state = $state; + } + + /** + * {@inheritdoc} + * + * The cache file name is retrieved on a page load via a lookup variable that + * contains an associative array. The array key is the hash of the file names + * in $css while the value is the cache file name. The cache file is generated + * in two cases. First, if there is no file name value for the key, which will + * happen if a new file name has been added to $css or after the lookup + * variable is emptied to force a rebuild of the cache. Second, the cache file + * is generated if it is missing on disk. Old cache files are not deleted + * immediately when the lookup variable is emptied, but are deleted after a + * set period by drupal_delete_file_if_stale(). This ensures that files + * referenced by a cached page will still be available. + */ + public function optimize(array $css_assets) { + // Group the assets. + $css_groups = $this->grouper->group($css_assets); + + // Now optimize (concatenate + minify) and dump each asset group, unless + // that was already done, in which case it should appear in + // drupal_css_cache_files. + // Drupal contrib can override this default CSS aggregator to keep the same + // grouping, optimizing and dumping, but change the strategy that is used to + // determine when the aggregate should be rebuilt (e.g. mtime, HTTPS …). + $map = $this->state->get('drupal_css_cache_files') ?: array(); + $css_assets = array(); + foreach ($css_groups as $order => $css_group) { + // We have to return a single asset, not a group of assets. It is now up + // to one of the pieces of code in the switch statement below to set the + // 'data' property to the appropriate value. + $css_assets[$order] = $css_group; + unset($css_assets[$order]['items']); + + switch ($css_group['type']) { + case 'file': + // No preprocessing, single CSS asset: just use the existing URI. + if (!$css_group['preprocess']) { + $uri = $css_group['items'][0]['data']; + $css_assets[$order]['data'] = $uri; + } + // Preprocess (aggregate), unless the aggregate file already exists. + else { + $key = $this->generateHash($css_group); + $uri = ''; + if (isset($map[$key])) { + $uri = $map[$key]; + } + if (empty($uri) || !file_exists($uri)) { + // Optimize each asset within the group. + $data = ''; + foreach ($css_group['items'] as $css_asset) { + $data .= $this->optimizer->optimize($css_asset); + } + // Per the W3C specification at + // http://www.w3.org/TR/REC-CSS2/cascade.html#at-import, @import + // rules must proceed any other style, so we move those to the + // top. + $regexp = '/@import[^;]+;/i'; + preg_match_all($regexp, $data, $matches); + $data = preg_replace($regexp, '', $data); + $data = implode('', $matches[0]) . $data; + // Dump the optimized CSS for this group into an aggregate file. + $uri = $this->dumper->dump($data, 'css'); + // Set the URI for this group's aggregate file. + $css_assets[$order]['data'] = $uri; + // Persist the URI for this aggregate file. + $map[$key] = $uri; + $this->state->set('drupal_css_cache_files', $map); + } + else { + // Use the persisted URI for the optimized CSS file. + $css_assets[$order]['data'] = $uri; + } + $css_assets[$order]['preprocessed'] = TRUE; + } + break; + + case 'inline': + // We don't do any caching for inline CSS assets. + $data = ''; + foreach ($css_group['items'] as $css_asset) { + $data .= $this->optimizer->optimize($css_asset); + } + unset($css_assets[$order]['data']['items']); + $css_assets[$order]['data'] = $data; + break; + + case 'external': + // We don't do any aggregation and hence also no caching for external + // CSS assets. + $uri = $css_group['items'][0]['data']; + $css_assets[$order]['data'] = $uri; + break; + } + } + + return $css_assets; + } + + /** + * Generate a hash for a given group of CSS assets. + * + * @param array $css_group + * A group of CSS assets. + * + * @return string + * A hash to uniquely identify the given group of CSS assets. + */ + protected function generateHash(array $css_group) { + $css_data = array(); + foreach ($css_group['items'] as $css_file) { + $css_data[] = $css_file['data']; + } + return hash('sha256', serialize($css_data)); + } +} diff --git a/core/lib/Drupal/Core/Asset/CssCollectionRenderer.php b/core/lib/Drupal/Core/Asset/CssCollectionRenderer.php new file mode 100644 index 0000000..6d36ede --- /dev/null +++ b/core/lib/Drupal/Core/Asset/CssCollectionRenderer.php @@ -0,0 +1,177 @@ + 'html_tag', + '#tag' => 'link', + '#attributes' => array( + 'rel' => 'stylesheet', + ), + ); + $style_element_defaults = array( + '#type' => 'html_tag', + '#tag' => 'style', + ); + + // For filthy IE hack. + $current_ie_group_keys = NULL; + $get_ie_group_key = function ($css_asset) { + return array($css_asset['type'], $css_asset['preprocess'], $css_asset['group'], $css_asset['every_page'], $css_asset['media'], $css_asset['browsers']); + }; + + // Loop through all CSS assets, by key, to allow for the special IE + // workaround. + $css_assets_keys = array_keys($css_assets); + for ($i = 0; $i < count($css_assets_keys); $i++) { + $css_asset = $css_assets[$css_assets_keys[$i]]; + switch ($css_asset['type']) { + // For file items, there are three possibilites. + // - There are up to 31 CSS assets on the page (some of which may be + // aggregated). In this case, output a LINK tag for file CSS assets. + // - There are more than 31 CSS assets on the page, yet we must stay + // below IE<10's limit of 31 total CSS inclussion tags, we handle this + // in two ways: + // - file CSS assets that are not eligible for aggregation (their + // 'preprocess' flag has been set to FALSE): in this case, output a + // LINK tag. + // - file CSS assets that can be aggregated (and possibly have been): + // in this case, figure out which subsequent file CSS assets share + // the same key properties ('group', 'every_page', 'media' and + // 'browsers') and output this group into as few STYLE tags as + // possible (a STYLE tag may contain only 31 @import statements). + case 'file': + // The dummy query string needs to be added to the URL to control + // browser-caching. + $query_string_separator = (strpos($css_asset['data'], '?') !== FALSE) ? '&' : '?'; + + // As long as the current page will not run into IE's limit for CSS + // assets: output a LINK tag for a file CSS asset. + if (count($css_assets) <= 31) { + $element = $link_element_defaults; + $element['#attributes']['href'] = file_create_url($css_asset['data']) . $query_string_separator . $query_string;; + $element['#attributes']['media'] = $css_asset['media']; + $element['#browsers'] = $css_asset['browsers']; + $elements[] = $element; + } + // The current page will run into IE's limits for CSS assets: work + // around these limits by performing a light form of grouping. + // Once Drupal only needs to support IE10 and later, we can drop this. + else { + // The file CSS asset is ineligible for aggregation: output it in a + // LINK tag. + if (!$css_asset['preprocess']) { + $element = $link_element_defaults; + $element['#attributes']['href'] = file_create_url($css_asset['data']) . $query_string_separator . $query_string; + $element['#attributes']['media'] = $css_asset['media']; + $element['#browsers'] = $css_asset['browsers']; + $elements[] = $element; + } + // The file CSS asset can be aggregated, but hasn't been: combine + // multiple items into as few STYLE tags as possible. + else { + $import = array(); + // Start with the current CSS asset, iterate over subsequent CSS + // assets and find which ones have the same 'type', 'group', + // 'every_page', 'preprocess', 'media' and 'browsers' properties. + $j = $i; + $next_css_asset = $css_asset; + $current_ie_group_key = $get_ie_group_key($css_asset); + do { + // 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 group on the STYLE tag. + $import[] = '@import url("' . String::checkPlain(file_create_url($next_css_asset['data']) . '?' . $query_string) . '");'; + // Move the outer for loop skip the next item, since we + // processed it here. + $i = $j; + // Retrieve next CSS asset, unless there is none: then break. + if ($j + 1 < count($css_assets_keys)) { + $j++; + $next_css_asset = $css_assets[$css_assets_keys[$j]]; + } + else { + break; + } + } while ($get_ie_group_key($next_css_asset) == $current_ie_group_key); + + // In addition to IE's limit of 31 total CSS inclusion tags, it + // also has a limit of 31 @import statements per STYLE tag. + while (!empty($import)) { + $import_batch = array_slice($import, 0, 31); + $import = array_slice($import, 31); + $element = $style_element_defaults; + // This simplifies the JavaScript regex, allowing each line + // (separated by \n) to be treated as a completely different + // string. This means that we can use ^ and $ on one line at a + // time, and not worry about style tags since they'll never + // match the regex. + $element['#value'] = "\n" . implode("\n", $import_batch) . "\n"; + $element['#attributes']['media'] = $css_asset['media']; + $element['#browsers'] = $css_asset['browsers']; + $elements[] = $element; + } + } + } + break; + + // Output a STYLE tag for an inline CSS asset. The asset's 'data' + // property contains the CSS content. + case 'inline': + $element = $style_element_defaults; + $element['#value'] = $css_asset['data']; + $element['#attributes']['media'] = $css_asset['media']; + $element['#browsers'] = $css_asset['browsers']; + // For inline CSS to validate as XHTML, all CSS containing XHTML needs + // to be wrapped in CDATA. To make that backwards compatible with HTML + // 4, we need to comment out the CDATA-tag. + $element['#value_prefix'] = "\n/* */\n"; + $elements[] = $element; + break; + + // Output a LINK tag for an external CSS asset. The asset's 'data' + // property contains the full URL. + case 'external': + $element = $link_element_defaults; + $element['#attributes']['href'] = $css_asset['data']; + $element['#attributes']['media'] = $css_asset['media']; + $element['#browsers'] = $css_asset['browsers']; + $elements[] = $element; + break; + + default: + throw new \Exception('Invalid CSS asset type.'); + } + } + + return $elements; + } + +} diff --git a/core/lib/Drupal/Core/Asset/CssOptimizer.php b/core/lib/Drupal/Core/Asset/CssOptimizer.php new file mode 100644 index 0000000..8865623 --- /dev/null +++ b/core/lib/Drupal/Core/Asset/CssOptimizer.php @@ -0,0 +1,217 @@ +processFile($css_asset); + } + else { + return $this->processCss($css_asset['data'], $css_asset['preprocess']); + } + } + + /** + * Build aggregate CSS file. + */ + protected function processFile($css_asset) { + $contents = $this->loadFile($css_asset['data'], TRUE); + + // Get the parent directory of this file, relative to the Drupal root. + $css_base_url = substr($css_asset['data'], 0, strrpos($css_asset['data'], '/')); + $this->rewriteFileURI(NULL, $css_base_url . '/'); + + // Anchor all paths in the CSS with its base URL, ignoring external and absolute paths. + return preg_replace_callback('/url\(\s*[\'"]?(?![a-z]+:|\/+)([^\'")]+)[\'"]?\s*\)/i', array($this, 'rewriteFileURI'), $contents); + } + + /** + * Loads the stylesheet and resolves all @import commands. + * + * Loads a stylesheet and replaces @import commands with the contents of the + * imported file. Use this instead of file_get_contents when processing + * stylesheets. + * + * The returned contents are compressed removing white space and comments only + * when CSS aggregation is enabled. This optimization will not apply for + * color.module enabled themes with CSS aggregation turned off. + * + * Note: the only reason this method is public is so color.module can call it; + * it is not on the AssetOptimizerInterface, so future refactorings can make + * it protected. + * + * @param $file + * Name of the stylesheet to be processed. + * @param $optimize + * Defines if CSS contents should be compressed or not. + * @param $reset_basepath + * Used internally to facilitate recursive resolution of @import commands. + * + * @return + * Contents of the stylesheet, including any resolved @import commands. + */ + public function loadFile($file, $optimize = NULL, $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. + if (isset($optimize)) { + $_optimize = $optimize; + } + + // Stylesheets are relative one to each other. Start by adding a base path + // prefix provided by the parent stylesheet (if necessary). + if ($basepath && !file_uri_scheme($file)) { + $file = $basepath . '/' . $file; + } + $basepath = dirname($file); + + // Load the CSS stylesheet. We suppress errors because themes may specify + // stylesheets in their .info.yml file that don't exist in the theme's path, + // but are merely there to disable certain module CSS files. + if ($contents = @file_get_contents($file)) { + // Return the processed stylesheet. + return $this->processCss($contents, $_optimize); + } + + return ''; + } + + /** + * Loads stylesheets recursively and returns contents with corrected paths. + * + * This function is used for recursive loading of stylesheets and + * returns the stylesheet content with all url() paths corrected. + * + * @see Drupal\Core\Asset\AssetOptimizerInterface::loadFile() + */ + protected function loadNestedFile($matches) { + $filename = $matches[1]; + // Load the imported stylesheet and replace @import commands in there as + // well. + $file = $this->loadFile($filename, NULL, FALSE); + + // Determine the file's directory. + $directory = dirname($filename); + // If the file is in the current directory, make sure '.' doesn't appear in + // the url() path. + $directory = $directory == '.' ? '' : $directory .'/'; + + // Alter all internal url() paths. Leave external paths alone. We don't need + // to normalize absolute paths here (i.e. remove folder/... segments) + // because that will be done later. + return preg_replace('/url\(\s*([\'"]?)(?![a-z]+:|\/+)/i', 'url(\1'. $directory, $file); + } + + /** + * Processes the contents of a stylesheet for aggregation. + * + * @param $contents + * The contents of the stylesheet. + * @param $optimize + * (optional) Boolean whether CSS contents should be minified. Defaults to + * FALSE. + * + * @return + * Contents of the stylesheet including the imported stylesheets. + */ + protected function processCss($contents, $optimize = FALSE) { + // 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. + $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( + "<($double_quot|$single_quot)|$comment>Ss", + "$1", + $contents + ); + // Remove certain whitespace. + // There are different conditions for removing leading and trailing + // whitespace. + // @see http://php.net/manual/regexp.reference.subpatterns.php + $contents = preg_replace('< + # Strip leading and trailing whitespace. + \s*([@{};,])\s* + # Strip only leading whitespace from: + # - Closing parenthesis: Retain "@media (bar) and foo". + | \s+([\)]) + # Strip only trailing whitespace from: + # - Opening parenthesis: Retain "@media (bar) and foo". + # - Colon: Retain :pseudo-selectors. + | ([\(:])\s+ + >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. + '$1$2$3', + $contents + ); + // End the file with a new line. + $contents = trim($contents); + $contents .= "\n"; + } + + // 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*;/', array($this, 'loadNestedFile'), $contents); + + return $contents; + } + + /** + * Prefixes all paths within a CSS file for aggregateFile(). + * + * Note: the only reason this method is public is so color.module can call it; + * it is not on the AssetOptimizerInterface, so future refactorings can make + * it protected. + */ + public function rewriteFileURI($matches, $base = NULL) { + static $_base; + // Store base path for preg_replace_callback. + if (isset($base)) { + $_base = $base; + } + + // Prefix with base and remove '../' segments where possible. + $path = $_base . $matches[1]; + $last = ''; + while ($path != $last) { + $last = $path; + $path = preg_replace('`(^|/)(?!\.\./)([^/]+)/\.\./`', '$1', $path); + } + return 'url(' . file_create_url($path) . ')'; + } + +} diff --git a/core/lib/Drupal/Core/Asset/JsCollectionGrouper.php b/core/lib/Drupal/Core/Asset/JsCollectionGrouper.php new file mode 100644 index 0000000..9f0aed0 --- /dev/null +++ b/core/lib/Drupal/Core/Asset/JsCollectionGrouper.php @@ -0,0 +1,80 @@ +grouper = $grouper; + $this->optimizer = $optimizer; + $this->dumper = $dumper; + $this->state = $state; + } + + /** + * {@inheritdoc} + * + * The cache file name is retrieved on a page load via a lookup variable that + * contains an associative array. The array key is the hash of the names in + * $files while the value is the cache file name. The cache file is generated + * in two cases. First, if there is no file name value for the key, which will + * happen if a new file name has been added to $files or after the lookup + * variable is emptied to force a rebuild of the cache. Second, the cache file + * is generated if it is missing on disk. Old cache files are not deleted + * immediately when the lookup variable is emptied, but are deleted after a + * set period by drupal_delete_file_if_stale(). This ensures that files + * referenced by a cached page will still be available. + */ + public function optimize(array $js_assets) { + // Group the assets. + $js_groups = $this->grouper->group($js_assets); + + // Now optimize (concatenate, not minify) and dump each asset group, unless + // that was already done, in which case it should appear in + // system.js_cache_files. + // Drupal contrib can override this default JS aggregator to keep the same + // grouping, optimizing and dumping, but change the strategy that is used to + // determine when the aggregate should be rebuilt (e.g. mtime, HTTPS …). + $map = $this->state->get('system.js_cache_files') ?: array(); + $js_assets = array(); + foreach ($js_groups as $order => $js_group) { + // We have to return a single asset, not a group of assets. It is now up + // to one of the pieces of code in the switch statement below to set the + // 'data' property to the appropriate value. + $js_assets[$order] = $js_group; + unset($js_assets[$order]['items']); + + switch ($js_group['type']) { + case 'file': + // No preprocessing, single JS asset: just use the existing URI. + if (!$js_group['preprocess']) { + $uri = $js_group['items'][0]['data']; + $js_assets[$order]['data'] = $uri; + } + // Preprocess (aggregate), unless the aggregate file already exists. + else { + $key = $this->generateHash($js_group); + $uri = ''; + if (isset($map[$key])) { + $uri = $map[$key]; + } + if (empty($uri) || !file_exists($uri)) { + // Concatenate each asset within the group. + $data = ''; + foreach ($js_group['items'] as $js_asset) { + $data .= $this->optimizer->optimize($js_asset); + // Append a ';' and a newline after each JS file to prevent them + // from running together. + $data .= ";\n"; + } + // Dump the optimized JS for this group into an aggregate file. + $uri = $this->dumper->dump($data, 'js'); + // Set the URI for this group's aggregate file. + $js_assets[$order]['data'] = $uri; + // Persist the URI for this aggregate file. + $map[$key] = $uri; + $this->state->set('system.js_cache_files', $map); + } + else { + // Use the persisted URI for the optimized JS file. + $js_assets[$order]['data'] = $uri; + } + $js_assets[$order]['preprocessed'] = TRUE; + } + break; + + case 'external': + case 'setting': + case 'inline': + // We don't do any aggregation and hence also no caching for external, + // setting or inline JS assets. + $uri = $js_group['items'][0]['data']; + $js_assets[$order]['data'] = $uri; + break; + } + } + + return $js_assets; + } + + /** + * Generate a hash for a given group of JavaScript assets. + * + * @param array $js_group + * A group of JavaScript assets. + * + * @return string + * A hash to uniquely identify the given group of JavaScript assets. + */ + protected function generateHash(array $js_group) { + $js_data = array(); + foreach ($js_group['items'] as $js_file) { + $js_data[] = $js_file['data']; + } + return hash('sha256', serialize($js_data)); + } +} diff --git a/core/lib/Drupal/Core/Asset/JsCollectionRenderer.php b/core/lib/Drupal/Core/Asset/JsCollectionRenderer.php new file mode 100644 index 0000000..b9f6582 --- /dev/null +++ b/core/lib/Drupal/Core/Asset/JsCollectionRenderer.php @@ -0,0 +1,97 @@ +\n"; + + // Since JavaScript may look for arguments in the URL and act on them, some + // third-party code might require the use of a different query string. + $js_version_string = variable_get('drupal_js_version_query_string', 'v='); + + // Defaults for each SCRIPT element. + $element_defaults = array( + '#type' => 'html_tag', + '#tag' => 'script', + '#value' => '', + ); + + // Loop through all JS assets. + foreach ($js_assets as $js_asset) { + // Element properties that do not depend on JS asset type. + $element = $element_defaults; + $element['#browsers'] = $js_asset['browsers']; + + // Element properties that depend on item type. + switch ($js_asset['type']) { + case 'setting': + $element['#value_prefix'] = $embed_prefix; + $element['#value'] = 'var drupalSettings = ' . drupal_json_encode(drupal_merge_js_settings($js_asset['data'])) . ";"; + $element['#value_suffix'] = $embed_suffix; + break; + + case 'inline': + $element['#value_prefix'] = $embed_prefix; + $element['#value'] = $js_asset['data']; + $element['#value_suffix'] = $embed_suffix; + break; + + case 'file': + $query_string = empty($js_asset['version']) ? $default_query_string : $js_version_string . $js_asset['version']; + $query_string_separator = (strpos($js_asset['data'], '?') !== FALSE) ? '&' : '?'; + $element['#attributes']['src'] = file_create_url($js_asset['data']); + // Only add the cache-busting query string if this isn't an aggregate + // file. + if (!isset($js_asset['preprocessed'])) { + $element['#attributes']['src'] .= $query_string_separator . ($js_asset['cache'] ? $query_string : REQUEST_TIME); + } + break; + + case 'external': + $element['#attributes']['src'] = $js_asset['data']; + break; + + default: + throw new \Exception('Invalid JS asset type.'); + } + + // Attributes may only be set if this script is output independently. + if (!empty($element['#attributes']['src']) && !empty($js_asset['attributes'])) { + $element['#attributes'] += $js_asset['attributes']; + } + + $elements[] = $element; + } + + return $elements; + } + +} diff --git a/core/lib/Drupal/Core/Asset/JsOptimizer.php b/core/lib/Drupal/Core/Asset/JsOptimizer.php new file mode 100644 index 0000000..e6ad293 --- /dev/null +++ b/core/lib/Drupal/Core/Asset/JsOptimizer.php @@ -0,0 +1,31 @@ +loadFile($paths['source'] . $file, FALSE); // Return the path to where this CSS file originated from, stripping // off the name of the file at the end of the path. $base = base_path() . dirname($paths['source'] . $file) . '/'; - _drupal_build_css_path(NULL, $base); + $css_optimizer->rewriteFileURI(NULL, $base); // Prefix all paths within this CSS file, ignoring absolute paths. - $style = preg_replace_callback('/url\([\'"]?(?![a-z]+:|\/+)([^\'")]+)[\'"]?\)/i', '_drupal_build_css_path', $style); + $style = preg_replace_callback('/url\([\'"]?(?![a-z]+:|\/+)([^\'")]+)[\'"]?\)/i', array($css_optimizer, 'rewriteFileURI'), $style); // Rewrite stylesheet with new colors. $style = _color_rewrite_stylesheet($theme, $info, $paths, $palette, $style); diff --git a/core/modules/layout/lib/Drupal/layout/Tests/LayoutDerivativesTest.php b/core/modules/layout/lib/Drupal/layout/Tests/LayoutDerivativesTest.php index f41460c..287fb8f 100644 --- a/core/modules/layout/lib/Drupal/layout/Tests/LayoutDerivativesTest.php +++ b/core/modules/layout/lib/Drupal/layout/Tests/LayoutDerivativesTest.php @@ -105,6 +105,6 @@ function testPageLayout() { $this->assertRaw('
'); // Ensure the CSS was added. - $this->assertRaw('@import url("' . url('', array('absolute' => TRUE)) . drupal_get_path('theme', 'layout_test_theme') . '/layouts/static/two-col/two-col.css'); + $this->assertRaw(url('', array('absolute' => TRUE)) . drupal_get_path('theme', 'layout_test_theme') . '/layouts/static/two-col/two-col.css'); } } diff --git a/core/modules/simpletest/files/css_test_files/comment_hacks.css.unoptimized.css b/core/modules/simpletest/files/css_test_files/comment_hacks.css.unoptimized.css deleted file mode 100644 index 8b12d6c..0000000 --- a/core/modules/simpletest/files/css_test_files/comment_hacks.css.unoptimized.css +++ /dev/null @@ -1,80 +0,0 @@ -/* -* A sample css file, designed to test the effectiveness and stability -* of function drupal_load_stylesheet_content(). -* -*/ -/* -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 multiline IE-mac hack (v.2) taken fron Zen theme*/ -/* Hides from IE-mac \*/ -html .clear-block { - height: 1%; -} -.clear-block { - display: block; - font:italic bold 12px/30px Georgia, serif; -} - -/* End hide from IE-mac */ -.test2 { display:block; } - -/* v1 of the commented backslash hack. This \ character between rules appears to have the effect -that macIE5 ignores the following rule. Odd, but extremely useful. */ -.bkslshv1 { background-color: #c00; } -.test3 { display:block; } - -/**************** A multiline, multistar comment *************** -****************************************************************/ -.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: "*/ \" \ '"; -} -/************************************/ -/* -"This has to go" -'This has to go' -*/ diff --git a/core/modules/simpletest/files/css_test_files/css_input_with_import.css.unoptimized.css b/core/modules/simpletest/files/css_test_files/css_input_with_import.css.unoptimized.css deleted file mode 100644 index 4c905f5..0000000 --- a/core/modules/simpletest/files/css_test_files/css_input_with_import.css.unoptimized.css +++ /dev/null @@ -1,30 +0,0 @@ - - - - - -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; -} - diff --git a/core/modules/simpletest/files/css_test_files/css_input_without_import.css.unoptimized.css b/core/modules/simpletest/files/css_test_files/css_input_without_import.css.unoptimized.css deleted file mode 100644 index 118dfa4..0000000 --- a/core/modules/simpletest/files/css_test_files/css_input_without_import.css.unoptimized.css +++ /dev/null @@ -1,65 +0,0 @@ - -/** - * @file Basic css that does not use import - */ - - -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; -} - -/** - * CSS spec says that all whitespace is valid whitespace, so this selector - * should be just as good as the one above. - */ -.this -.is -.a -.test { -font: 1em/100% Verdana, sans-serif; -color: #494949; -} - -some :pseudo .thing { - border-radius: 3px; -} - -::-moz-selection { - background: #000; - color:#fff; - -} -::selection { - background: #000; - color: #fff; -} - -@media print { - * { - background: #000 !important; - color: #fff !important; - } - @page { - margin: 0.5cm; - } -} - -@media screen and (max-device-width: 480px) { - background: #000; - color: #fff; -} - -textarea, select { - font: 1em/160% Verdana, sans-serif; - color: #494949; -} - diff --git a/core/modules/system/lib/Drupal/system/Tests/Common/CascadingStylesheetsTest.php b/core/modules/system/lib/Drupal/system/Tests/Common/CascadingStylesheetsTest.php index d7fbdbf..54abcba 100644 --- a/core/modules/system/lib/Drupal/system/Tests/Common/CascadingStylesheetsTest.php +++ b/core/modules/system/lib/Drupal/system/Tests/Common/CascadingStylesheetsTest.php @@ -79,7 +79,7 @@ function testRenderFile() { $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 = ""; + $css_processed = ''; $this->assertEqual(trim($styles), $css_processed, 'Rendered CSS includes newlines inside style tags for JavaScript use.'); } @@ -99,8 +99,12 @@ function testRenderExternal() { * Tests rendering inline stylesheets with preprocessing on. */ function testRenderInlinePreprocess() { + // Turn on CSS aggregation to allow for preprocessing. + $config = $this->container->get('config.factory')->get('system.performance'); + $config->set('css.preprocess', 1); + $css = 'body { padding: 0px; }'; - $css_preprocessed = ''; + $css_preprocessed = ''; drupal_add_css($css, array('type' => 'inline')); $styles = drupal_get_css(); $this->assertEqual(trim($styles), $css_preprocessed, 'Rendering preprocessed inline CSS adds it to the page.'); diff --git a/core/modules/system/lib/Drupal/system/Tests/Common/CascadingStylesheetsTest.php.rej b/core/modules/system/lib/Drupal/system/Tests/Common/CascadingStylesheetsTest.php.rej new file mode 100644 index 0000000..2f11901 --- /dev/null +++ b/core/modules/system/lib/Drupal/system/Tests/Common/CascadingStylesheetsTest.php.rej @@ -0,0 +1,20 @@ +diff a/core/modules/system/lib/Drupal/system/Tests/Common/CascadingStylesheetsTest.php b/core/modules/system/lib/Drupal/system/Tests/Common/CascadingStylesheetsTest.php (rejected hunks) +@@ -123,9 +127,6 @@ function testRenderInlineFullPage() { + module_enable(array('php')); + + $css = 'body { font-size: 254px; }'; +- // Inline CSS is minified unless 'preprocess' => FALSE is passed as a +- // drupal_add_css() option. +- $expected = 'body{font-size:254px;}'; + + // Create Basic page node type. + $this->drupalCreateContentType(array('type' => 'page', 'name' => 'Basic page')); +@@ -146,7 +147,7 @@ function testRenderInlineFullPage() { + + // Fetch the page. + $this->drupalGet('node/' . $node->nid); +- $this->assertRaw($expected, 'Inline stylesheets appear in the full page rendering.'); ++ $this->assertRaw($css, 'Inline stylesheets appear in the full page rendering.'); + } + + /** diff --git a/core/modules/system/lib/Drupal/system/Tests/Common/CascadingStylesheetsUnitTest.php b/core/modules/system/lib/Drupal/system/Tests/Common/CascadingStylesheetsUnitTest.php deleted file mode 100644 index 5ab3c0a..0000000 --- a/core/modules/system/lib/Drupal/system/Tests/Common/CascadingStylesheetsUnitTest.php +++ /dev/null @@ -1,63 +0,0 @@ - 'CSS Unit Tests', - 'description' => 'Unit tests on CSS functions like aggregation.', - 'group' => 'Common', - ); - } - - /** - * Tests CSS loading via drupal_load_stylesheet(). - * - * This test loads CSS files with and without CSS optimization. - * Known tests: - * - Retain white-space in selectors. (http://drupal.org/node/472820) - * - Proper URLs in imported files. (http://drupal.org/node/265719) - * - Retain pseudo-selectors. (http://drupal.org/node/460448) - */ - function testLoadCssBasic() { - // Array of files to test living in 'simpletest/files/css_test_files/'. - // - Original: name.css - // - Unoptimized expected content: name.css.unoptimized.css - // - Optimized expected content: name.css.optimized.css - $testfiles = array( - 'css_input_without_import.css', - 'css_input_with_import.css', - 'comment_hacks.css' - ); - $path = drupal_get_path('module', 'simpletest') . '/files/css_test_files'; - foreach ($testfiles as $file) { - $expected = file_get_contents("$path/$file.unoptimized.css"); - $unoptimized_output = drupal_load_stylesheet("$path/$file.unoptimized.css", FALSE); - $this->assertEqual($unoptimized_output, $expected, format_string('Unoptimized CSS file has expected contents (@file)', array('@file' => $file))); - - $expected = file_get_contents("$path/$file.optimized.css"); - $optimized_output = drupal_load_stylesheet("$path/$file", 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("$path/$file.unoptimized.css"); - $unoptimized_output_url = drupal_load_stylesheet($GLOBALS['base_url'] . "/$path/$file.unoptimized.css", FALSE); - $this->assertEqual($unoptimized_output, $expected, format_string('Unoptimized CSS file (loaded from an URL) has expected contents (@file)', array('@file' => $file))); - - $expected = file_get_contents("$path/$file.optimized.css"); - $optimized_output = drupal_load_stylesheet($GLOBALS['base_url'] . "/$path/$file", TRUE); - $this->assertEqual($optimized_output, $expected, format_string('Optimized CSS file (loaded from an URL) has expected contents (@file)', array('@file' => $file))); - } - } -} diff --git a/core/modules/system/lib/Drupal/system/Tests/Common/JavaScriptTest.php b/core/modules/system/lib/Drupal/system/Tests/Common/JavaScriptTest.php index 099d911..4bd53ff 100644 --- a/core/modules/system/lib/Drupal/system/Tests/Common/JavaScriptTest.php +++ b/core/modules/system/lib/Drupal/system/Tests/Common/JavaScriptTest.php @@ -8,6 +8,7 @@ namespace Drupal\system\Tests\Common; use Drupal\simpletest\WebTestBase; +use Drupal\Component\Utility\Crypt; /** * Tests the JavaScript system. @@ -345,8 +346,8 @@ function testAggregation() { $js_items = drupal_add_js(); $javascript = drupal_get_js(); $expected = implode("\n", array( - '', - '', + '', + '', )); $this->assertTrue(strpos($javascript, $expected) !== FALSE, 'JavaScript is aggregated in the expected groups and order.'); } @@ -365,10 +366,14 @@ function testAggregationOrder() { drupal_add_js('core/misc/autocomplete.js'); $js_items = drupal_add_js(); - drupal_build_js_cache(array( - 'core/misc/ajax.js' => $js_items['core/misc/ajax.js'], - 'core/misc/autocomplete.js' => $js_items['core/misc/autocomplete.js'] - )); + $scripts_html = array( + '#type' => 'scripts', + '#items' => array( + 'core/misc/ajax.js' => $js_items['core/misc/ajax.js'], + 'core/misc/autocomplete.js' => $js_items['core/misc/autocomplete.js'] + ) + ); + drupal_render($scripts_html); // Store the expected key for the first item in the cache. $cache = array_keys(\Drupal::state()->get('system.js_cache_files') ?: array()); @@ -384,10 +389,14 @@ function testAggregationOrder() { // Rebuild the cache. $js_items = drupal_add_js(); - drupal_build_js_cache(array( - 'core/misc/ajax.js' => $js_items['core/misc/ajax.js'], - 'core/misc/autocomplete.js' => $js_items['core/misc/autocomplete.js'] - )); + $scripts_html = array( + '#type' => 'scripts', + '#items' => array( + 'core/misc/ajax.js' => $js_items['core/misc/ajax.js'], + 'core/misc/autocomplete.js' => $js_items['core/misc/autocomplete.js'] + ) + ); + drupal_render($scripts_html); // Compare the expected key for the first file to the current one. $cache = array_keys(\Drupal::state()->get('system.js_cache_files') ?: array()); @@ -562,4 +571,24 @@ function testAddJsFileWithQueryString() { $query_string = variable_get('css_js_query_string', '0'); $this->assertRaw(drupal_get_path('module', 'node') . '/node.js?' . $query_string, 'Query string was appended correctly to js.'); } + + /** + * Calculates the aggregated file URI of a group of JavaScript assets. + * + * @param array $js_assets + * A group of JavaScript assets. + * @return string + * A file URI. + * + * @see testAggregation() + * @see testAggregationOrder() + */ + protected function calculateAggregateFilename($js_assets) { + $data = ''; + foreach ($js_assets as $js_asset) { + $data .= file_get_contents($js_asset['data']) . ";\n"; + } + return file_create_url('public://js/js_' . Crypt::hashBase64($data) . '.js'); + } + } diff --git a/core/modules/system/system.module b/core/modules/system/system.module index 87fe50d..01e9207 100644 --- a/core/modules/system/system.module +++ b/core/modules/system/system.module @@ -284,14 +284,10 @@ function system_element_info() { $types['styles'] = array( '#items' => array(), '#pre_render' => array('drupal_pre_render_styles'), - '#group_callback' => 'drupal_group_css', - '#aggregate_callback' => 'drupal_aggregate_css', ); $types['scripts'] = array( '#items' => array(), '#pre_render' => array('drupal_pre_render_scripts'), - '#group_callback' => 'drupal_group_js', - '#aggregate_callback' => 'drupal_aggregate_js', ); // Input elements. diff --git a/core/tests/Drupal/Tests/Core/Asset/CssCollectionGrouperUnitTest.php b/core/tests/Drupal/Tests/Core/Asset/CssCollectionGrouperUnitTest.php new file mode 100644 index 0000000..adf5e88 --- /dev/null +++ b/core/tests/Drupal/Tests/Core/Asset/CssCollectionGrouperUnitTest.php @@ -0,0 +1,216 @@ + 'CSS asset collection grouper functionality', + 'description' => 'Tests the CSS asset collection grouper.', + 'group' => 'Asset handling', + ); + } + + function setUp() { + parent::setUp(); + + $this->grouper = new CssCollectionGrouper(); + } + + /** + * Tests \Drupal\Core\Asset\CssCollectionGrouper. + */ + function testGrouper() { + $css_assets = array( + 'system.base.css' => array( + 'group' => -100, + 'every_page' => TRUE, + 'type' => 'file', + 'weight' => 0.012, + 'media' => 'all', + 'preprocess' => TRUE, + 'data' => 'core/modules/system/system.base.css', + 'browsers' => array('IE' => TRUE, '!IE' => TRUE), + 'basename' => 'system.base.css', + ), + 'system.theme.css' => array( + 'group' => -100, + 'every_page' => TRUE, + 'type' => 'file', + 'weight' => 0.013, + 'media' => 'all', + 'preprocess' => TRUE, + 'data' => 'core/modules/system/system.theme.css', + 'browsers' => array('IE' => TRUE, '!IE' => TRUE), + 'basename' => 'system.theme.css', + ), + 'jquery.ui.core.css' => array( + 'group' => -100, + 'type' => 'file', + 'weight' => 0.004, + 'every_page' => FALSE, + 'media' => 'all', + 'preprocess' => TRUE, + 'data' => 'core/misc/ui/themes/base/jquery.ui.core.css', + 'browsers' => array('IE' => TRUE, '!IE' => TRUE), + 'basename' => 'jquery.ui.core.css', + ), + 0 => array( + 'type' => 'inline', + 'group' => 0, + 'weight' => 0.007, + 'every_page' => FALSE, + 'media' => 'all', + 'preprocess' => TRUE, + 'data' => 'body { padding: 0px; }', + 'browsers' => array('IE' => TRUE, '!IE' => TRUE), + ), + 1 => array( + 'type' => 'inline', + 'group' => 0, + 'weight' => 0.007, + 'every_page' => FALSE, + 'media' => 'all', + 'preprocess' => FALSE, + 'data' => 'body { margin: 0px; }', + 'browsers' => array('IE' => TRUE, '!IE' => TRUE), + ), + 'field.css' => array( + 'every_page' => TRUE, + 'group' => 0, + 'type' => 'file', + 'weight' => 0.011, + 'media' => 'all', + 'preprocess' => TRUE, + 'data' => 'core/modules/field/theme/field.css', + 'browsers' => array('IE' => TRUE, '!IE' => TRUE), + 'basename' => 'field.css', + ), + 'external.css' => array( + 'every_page' => FALSE, + 'group' => 0, + 'type' => 'external', + 'weight' => 0.009, + 'media' => 'all', + 'preprocess' => TRUE, + 'data' => 'http://example.com/external.css', + 'browsers' => array('IE' => TRUE, '!IE' => TRUE), + 'basename' => 'external.css', + ), + 'style.css' => array( + 'group' => 100, + 'every_page' => TRUE, + 'media' => 'all', + 'type' => 'file', + 'weight' => 0.001, + 'preprocess' => TRUE, + 'data' => 'core/themes/bartik/css/style.css', + 'browsers' => array('IE' => TRUE, '!IE' => TRUE), + 'basename' => 'style.css', + ), + 'print.css' => array( + 'group' => 100, + 'every_page' => TRUE, + 'media' => 'print', + 'type' => 'file', + 'weight' => 0.003, + 'preprocess' => TRUE, + 'data' => 'core/themes/bartik/css/print.css', + 'browsers' => array('IE' => TRUE, '!IE' => TRUE), + 'basename' => 'print.css', + ), + ); + + $groups = $this->grouper->group($css_assets); + + $this->assertSame(count($groups), 7, "7 groups created."); + + // Check group 1. + $this->assertSame($groups[0]['group'], -100); + $this->assertSame($groups[0]['every_page'], TRUE); + $this->assertSame($groups[0]['type'], 'file'); + $this->assertSame($groups[0]['media'], 'all'); + $this->assertSame($groups[0]['preprocess'], TRUE); + $this->assertSame(count($groups[0]['items']), 2); + $this->assertContains($css_assets['system.base.css'], $groups[0]['items']); + $this->assertContains($css_assets['system.theme.css'], $groups[0]['items']); + + // Check group 2. + $this->assertSame($groups[1]['group'], -100); + $this->assertSame($groups[1]['every_page'], FALSE); + $this->assertSame($groups[1]['type'], 'file'); + $this->assertSame($groups[1]['media'], 'all'); + $this->assertSame($groups[1]['preprocess'], TRUE); + $this->assertSame(count($groups[1]['items']), 1); + $this->assertContains($css_assets['jquery.ui.core.css'], $groups[1]['items']); + + // Check group 3. + $this->assertSame($groups[2]['group'], 0); + $this->assertSame($groups[2]['every_page'], FALSE); + $this->assertSame($groups[2]['type'], 'inline'); + $this->assertSame($groups[2]['media'], 'all'); + $this->assertSame($groups[2]['preprocess'], TRUE); + $this->assertSame(count($groups[2]['items']), 2); + $this->assertContains($css_assets[0], $groups[2]['items']); + $this->assertContains($css_assets[1], $groups[2]['items']); + + // Check group 4. + $this->assertSame($groups[3]['group'], 0); + $this->assertSame($groups[3]['every_page'], TRUE); + $this->assertSame($groups[3]['type'], 'file'); + $this->assertSame($groups[3]['media'], 'all'); + $this->assertSame($groups[3]['preprocess'], TRUE); + $this->assertSame(count($groups[3]['items']), 1); + $this->assertContains($css_assets['field.css'], $groups[3]['items']); + + // Check group 5. + $this->assertSame($groups[4]['group'], 0); + $this->assertSame($groups[4]['every_page'], FALSE); + $this->assertSame($groups[4]['type'], 'external'); + $this->assertSame($groups[4]['media'], 'all'); + $this->assertSame($groups[4]['preprocess'], TRUE); + $this->assertSame(count($groups[4]['items']), 1); + $this->assertContains($css_assets['external.css'], $groups[4]['items']); + + // Check group 6. + $this->assertSame($groups[5]['group'], 100); + $this->assertSame($groups[5]['every_page'], TRUE); + $this->assertSame($groups[5]['type'], 'file'); + $this->assertSame($groups[5]['media'], 'all'); + $this->assertSame($groups[5]['preprocess'], TRUE); + $this->assertSame(count($groups[5]['items']), 1); + $this->assertContains($css_assets['style.css'], $groups[5]['items']); + + // Check group 7. + $this->assertSame($groups[6]['group'], 100); + $this->assertSame($groups[6]['every_page'], TRUE); + $this->assertSame($groups[6]['type'], 'file'); + $this->assertSame($groups[6]['media'], 'print'); + $this->assertSame($groups[6]['preprocess'], TRUE); + $this->assertSame(count($groups[6]['items']), 1); + $this->assertContains($css_assets['print.css'], $groups[6]['items']); + } + +} diff --git a/core/tests/Drupal/Tests/Core/Asset/CssCollectionRendererUnitTest.php b/core/tests/Drupal/Tests/Core/Asset/CssCollectionRendererUnitTest.php new file mode 100644 index 0000000..cd97a61 --- /dev/null +++ b/core/tests/Drupal/Tests/Core/Asset/CssCollectionRendererUnitTest.php @@ -0,0 +1,572 @@ + 'CSS asset collection renderer functionality', + 'description' => 'Tests the CSS asset collection renderer.', + 'group' => 'Asset handling', + ); + } + + function setUp() { + parent::setUp(); + + $this->renderer = new CssCollectionRenderer(); + $this->file_css_group = array( + 'group' => -100, + 'every_page' => TRUE, + 'type' => 'file', + 'media' => 'all', + 'preprocess' => TRUE, + 'browsers' => array('IE' => TRUE, '!IE' => TRUE), + 'items' => array( + 0 => array( + 'group' => -100, + 'every_page' => TRUE, + 'type' => 'file', + 'weight' => 0.012, + 'media' => 'all', + 'preprocess' => TRUE, + 'data' => 'tests/Drupal/Tests/Core/Asset/foo.css', + 'browsers' => array('IE' => TRUE, '!IE' => TRUE), + 'basename' => 'foo.css', + ), + 1 => array( + 'group' => -100, + 'every_page' => TRUE, + 'type' => 'file', + 'weight' => 0.013, + 'media' => 'all', + 'preprocess' => TRUE, + 'data' => 'tests/Drupal/Tests/Core/Asset/bar.css', + 'browsers' => array('IE' => TRUE, '!IE' => TRUE), + 'basename' => 'bar.css', + ), + ), + ); + $this->inline_css_group = array( + 'group' => 0, + 'every_page' => FALSE, + 'type' => 'inline', + 'media' => 'all', + 'preprocess' => TRUE, + 'browsers' => array('IE' => TRUE, '!IE' => TRUE), + 'items' => array( + 0 => array( + 'group' => 0, + 'every_page' => FALSE, + 'type' => 'inline', + 'weight' => 0.012, + 'media' => 'all', + 'preprocess' => TRUE, + 'data' => '.girlfriend { display: none; }', + 'browsers' => array('IE' => TRUE, '!IE' => TRUE), + ), + 1 => array( + 'group' => 0, + 'every_page' => FALSE, + 'type' => 'file', + 'weight' => 0.013, + 'media' => 'all', + 'preprocess' => FALSE, + 'data' => '#home body { position: fixed; }', + 'browsers' => array('IE' => TRUE, '!IE' => TRUE), + ), + ), + ); + } + + /** + * Provides data for the CSS asset rendering test. + * + * @see testRender + */ + function testRenderProvider() { + // Default for 'browsers' key in CSS asset. + $browsers_default = array('IE' => TRUE, '!IE' => TRUE); + + // Defaults for LINK and STYLE elements. + $link_element_defaults = array( + + ); + $style_element_defaults = array( + '#type' => 'html_tag', + '#tag' => 'style', + ); + + $create_link_element = function($href, $media = 'all', $browsers = array()) { + return array( + '#type' => 'html_tag', + '#tag' => 'link', + '#attributes' => array( + 'rel' => 'stylesheet', + 'href' => $href, + 'media' => $media, + ), + '#browsers' => $browsers, + ); + }; + $create_style_element = function($value, $media, $browsers = array(), $wrap_in_cdata = FALSE) { + $style_element = array( + '#type' => 'html_tag', + '#tag' => 'style', + '#value' => $value, + '#attributes' => array( + 'media' => $media + ), + '#browsers' => $browsers, + ); + if ($wrap_in_cdata) { + $style_element['#value_prefix'] = "\n/* */\n"; + } + return $style_element; + }; + + $create_file_css_asset = function($data, $media = 'all', $preprocess = TRUE) { + return array('group' => 0, 'every_page' => FALSE, 'type' => 'file', 'media' => $media, 'preprocess' => $preprocess, 'data' => $data, 'browsers' => array()); + }; + + return array( + // Single external CSS asset. + 0 => array( + // CSS assets. + array( + 0 => array('group' => 0, 'every_page' => TRUE, 'type' => 'external', 'media' => 'all', 'preprocess' => TRUE, 'data' => 'http://example.com/popular.js', 'browsers' => array()), + ), + // Render elements. + array( + 0 => $create_link_element('http://example.com/popular.js', 'all'), + ), + ), + // Single inline CSS asset. + 1 => array( + array( + 0 => array('group' => 0, 'every_page' => FALSE, 'type' => 'inline', 'media' => 'all', 'preprocess' => FALSE, 'data' => '.girlfriend { display: none; }', 'browsers' => array()), + ), + array( + 0 => $create_style_element('.girlfriend { display: none; }', 'all', array(), TRUE), + ), + ), + // Single file CSS asset. + 2 => array( + array( + 0 => array('group' => 0, 'every_page' => TRUE, 'type' => 'file', 'media' => 'all', 'preprocess' => TRUE, 'data' => 'public://css/file-every_page-all', 'browsers' => array()), + ), + array( + 0 => $create_link_element(file_create_url('public://css/file-every_page-all') . '?0', 'all'), + ), + ), + // 31 file CSS assets: expect 31 link elements. + 3 => array( + array( + 0 => $create_file_css_asset('public://css/1.css'), + 1 => $create_file_css_asset('public://css/2.css'), + 2 => $create_file_css_asset('public://css/3.css'), + 3 => $create_file_css_asset('public://css/4.css'), + 4 => $create_file_css_asset('public://css/5.css'), + 5 => $create_file_css_asset('public://css/6.css'), + 6 => $create_file_css_asset('public://css/7.css'), + 7 => $create_file_css_asset('public://css/8.css'), + 8 => $create_file_css_asset('public://css/9.css'), + 9 => $create_file_css_asset('public://css/10.css'), + 10 => $create_file_css_asset('public://css/11.css'), + 11 => $create_file_css_asset('public://css/12.css'), + 12 => $create_file_css_asset('public://css/13.css'), + 13 => $create_file_css_asset('public://css/14.css'), + 14 => $create_file_css_asset('public://css/15.css'), + 15 => $create_file_css_asset('public://css/16.css'), + 16 => $create_file_css_asset('public://css/17.css'), + 17 => $create_file_css_asset('public://css/18.css'), + 18 => $create_file_css_asset('public://css/19.css'), + 19 => $create_file_css_asset('public://css/20.css'), + 20 => $create_file_css_asset('public://css/21.css'), + 21 => $create_file_css_asset('public://css/22.css'), + 22 => $create_file_css_asset('public://css/23.css'), + 23 => $create_file_css_asset('public://css/24.css'), + 24 => $create_file_css_asset('public://css/25.css'), + 25 => $create_file_css_asset('public://css/26.css'), + 26 => $create_file_css_asset('public://css/27.css'), + 27 => $create_file_css_asset('public://css/28.css'), + 28 => $create_file_css_asset('public://css/29.css'), + 29 => $create_file_css_asset('public://css/30.css'), + 30 => $create_file_css_asset('public://css/31.css'), + ), + array( + 0 => $create_link_element(file_create_url('public://css/1.css') . '?0'), + 1 => $create_link_element(file_create_url('public://css/2.css') . '?0'), + 2 => $create_link_element(file_create_url('public://css/3.css') . '?0'), + 3 => $create_link_element(file_create_url('public://css/4.css') . '?0'), + 4 => $create_link_element(file_create_url('public://css/5.css') . '?0'), + 5 => $create_link_element(file_create_url('public://css/6.css') . '?0'), + 6 => $create_link_element(file_create_url('public://css/7.css') . '?0'), + 7 => $create_link_element(file_create_url('public://css/8.css') . '?0'), + 8 => $create_link_element(file_create_url('public://css/9.css') . '?0'), + 9 => $create_link_element(file_create_url('public://css/10.css') . '?0'), + 10 => $create_link_element(file_create_url('public://css/11.css') . '?0'), + 11 => $create_link_element(file_create_url('public://css/12.css') . '?0'), + 12 => $create_link_element(file_create_url('public://css/13.css') . '?0'), + 13 => $create_link_element(file_create_url('public://css/14.css') . '?0'), + 14 => $create_link_element(file_create_url('public://css/15.css') . '?0'), + 15 => $create_link_element(file_create_url('public://css/16.css') . '?0'), + 16 => $create_link_element(file_create_url('public://css/17.css') . '?0'), + 17 => $create_link_element(file_create_url('public://css/18.css') . '?0'), + 18 => $create_link_element(file_create_url('public://css/19.css') . '?0'), + 19 => $create_link_element(file_create_url('public://css/20.css') . '?0'), + 20 => $create_link_element(file_create_url('public://css/21.css') . '?0'), + 21 => $create_link_element(file_create_url('public://css/22.css') . '?0'), + 22 => $create_link_element(file_create_url('public://css/23.css') . '?0'), + 23 => $create_link_element(file_create_url('public://css/24.css') . '?0'), + 24 => $create_link_element(file_create_url('public://css/25.css') . '?0'), + 25 => $create_link_element(file_create_url('public://css/26.css') . '?0'), + 26 => $create_link_element(file_create_url('public://css/27.css') . '?0'), + 27 => $create_link_element(file_create_url('public://css/28.css') . '?0'), + 28 => $create_link_element(file_create_url('public://css/29.css') . '?0'), + 29 => $create_link_element(file_create_url('public://css/30.css') . '?0'), + 30 => $create_link_element(file_create_url('public://css/31.css') . '?0'), + ), + ), + // 32 file CSS assets with the same properties: expect 2 style elements. + 4 => array( + array( + 0 => $create_file_css_asset('public://css/1.css'), + 1 => $create_file_css_asset('public://css/2.css'), + 2 => $create_file_css_asset('public://css/3.css'), + 3 => $create_file_css_asset('public://css/4.css'), + 4 => $create_file_css_asset('public://css/5.css'), + 5 => $create_file_css_asset('public://css/6.css'), + 6 => $create_file_css_asset('public://css/7.css'), + 7 => $create_file_css_asset('public://css/8.css'), + 8 => $create_file_css_asset('public://css/9.css'), + 9 => $create_file_css_asset('public://css/10.css'), + 10 => $create_file_css_asset('public://css/11.css'), + 11 => $create_file_css_asset('public://css/12.css'), + 12 => $create_file_css_asset('public://css/13.css'), + 13 => $create_file_css_asset('public://css/14.css'), + 14 => $create_file_css_asset('public://css/15.css'), + 15 => $create_file_css_asset('public://css/16.css'), + 16 => $create_file_css_asset('public://css/17.css'), + 17 => $create_file_css_asset('public://css/18.css'), + 18 => $create_file_css_asset('public://css/19.css'), + 19 => $create_file_css_asset('public://css/20.css'), + 20 => $create_file_css_asset('public://css/21.css'), + 21 => $create_file_css_asset('public://css/22.css'), + 22 => $create_file_css_asset('public://css/23.css'), + 23 => $create_file_css_asset('public://css/24.css'), + 24 => $create_file_css_asset('public://css/25.css'), + 25 => $create_file_css_asset('public://css/26.css'), + 26 => $create_file_css_asset('public://css/27.css'), + 27 => $create_file_css_asset('public://css/28.css'), + 28 => $create_file_css_asset('public://css/29.css'), + 29 => $create_file_css_asset('public://css/30.css'), + 30 => $create_file_css_asset('public://css/31.css'), + 31 => $create_file_css_asset('public://css/32.css'), + ), + array( + 0 => $create_style_element(' +@import url("' . file_create_url('public://css/1.css') . '?0"); +@import url("' . file_create_url('public://css/2.css') . '?0"); +@import url("' . file_create_url('public://css/3.css') . '?0"); +@import url("' . file_create_url('public://css/4.css') . '?0"); +@import url("' . file_create_url('public://css/5.css') . '?0"); +@import url("' . file_create_url('public://css/6.css') . '?0"); +@import url("' . file_create_url('public://css/7.css') . '?0"); +@import url("' . file_create_url('public://css/8.css') . '?0"); +@import url("' . file_create_url('public://css/9.css') . '?0"); +@import url("' . file_create_url('public://css/10.css') . '?0"); +@import url("' . file_create_url('public://css/11.css') . '?0"); +@import url("' . file_create_url('public://css/12.css') . '?0"); +@import url("' . file_create_url('public://css/13.css') . '?0"); +@import url("' . file_create_url('public://css/14.css') . '?0"); +@import url("' . file_create_url('public://css/15.css') . '?0"); +@import url("' . file_create_url('public://css/16.css') . '?0"); +@import url("' . file_create_url('public://css/17.css') . '?0"); +@import url("' . file_create_url('public://css/18.css') . '?0"); +@import url("' . file_create_url('public://css/19.css') . '?0"); +@import url("' . file_create_url('public://css/20.css') . '?0"); +@import url("' . file_create_url('public://css/21.css') . '?0"); +@import url("' . file_create_url('public://css/22.css') . '?0"); +@import url("' . file_create_url('public://css/23.css') . '?0"); +@import url("' . file_create_url('public://css/24.css') . '?0"); +@import url("' . file_create_url('public://css/25.css') . '?0"); +@import url("' . file_create_url('public://css/26.css') . '?0"); +@import url("' . file_create_url('public://css/27.css') . '?0"); +@import url("' . file_create_url('public://css/28.css') . '?0"); +@import url("' . file_create_url('public://css/29.css') . '?0"); +@import url("' . file_create_url('public://css/30.css') . '?0"); +@import url("' . file_create_url('public://css/31.css') . '?0"); +', 'all'), + 1 => $create_style_element(' +@import url("' . file_create_url('public://css/32.css') . '?0"); +', 'all'), + ), + ), + // 32 file CSS assets with the same properties, except for the 10th and + // 20th files, they have different 'media' properties. Expect 5 style + // elements. + 5 => array( + array( + 0 => $create_file_css_asset('public://css/1.css'), + 1 => $create_file_css_asset('public://css/2.css'), + 2 => $create_file_css_asset('public://css/3.css'), + 3 => $create_file_css_asset('public://css/4.css'), + 4 => $create_file_css_asset('public://css/5.css'), + 5 => $create_file_css_asset('public://css/6.css'), + 6 => $create_file_css_asset('public://css/7.css'), + 7 => $create_file_css_asset('public://css/8.css'), + 8 => $create_file_css_asset('public://css/9.css'), + 9 => $create_file_css_asset('public://css/10.css', 'screen'), + 10 => $create_file_css_asset('public://css/11.css'), + 11 => $create_file_css_asset('public://css/12.css'), + 12 => $create_file_css_asset('public://css/13.css'), + 13 => $create_file_css_asset('public://css/14.css'), + 14 => $create_file_css_asset('public://css/15.css'), + 15 => $create_file_css_asset('public://css/16.css'), + 16 => $create_file_css_asset('public://css/17.css'), + 17 => $create_file_css_asset('public://css/18.css'), + 18 => $create_file_css_asset('public://css/19.css'), + 19 => $create_file_css_asset('public://css/20.css', 'print'), + 20 => $create_file_css_asset('public://css/21.css'), + 21 => $create_file_css_asset('public://css/22.css'), + 22 => $create_file_css_asset('public://css/23.css'), + 23 => $create_file_css_asset('public://css/24.css'), + 24 => $create_file_css_asset('public://css/25.css'), + 25 => $create_file_css_asset('public://css/26.css'), + 26 => $create_file_css_asset('public://css/27.css'), + 27 => $create_file_css_asset('public://css/28.css'), + 28 => $create_file_css_asset('public://css/29.css'), + 29 => $create_file_css_asset('public://css/30.css'), + 30 => $create_file_css_asset('public://css/31.css'), + 31 => $create_file_css_asset('public://css/32.css'), + ), + array( + 0 => $create_style_element(' +@import url("' . file_create_url('public://css/1.css') . '?0"); +@import url("' . file_create_url('public://css/2.css') . '?0"); +@import url("' . file_create_url('public://css/3.css') . '?0"); +@import url("' . file_create_url('public://css/4.css') . '?0"); +@import url("' . file_create_url('public://css/5.css') . '?0"); +@import url("' . file_create_url('public://css/6.css') . '?0"); +@import url("' . file_create_url('public://css/7.css') . '?0"); +@import url("' . file_create_url('public://css/8.css') . '?0"); +@import url("' . file_create_url('public://css/9.css') . '?0"); +', 'all'), + 1 => $create_style_element(' +@import url("' . file_create_url('public://css/10.css') . '?0"); +', 'screen'), + 2 => $create_style_element(' +@import url("' . file_create_url('public://css/11.css') . '?0"); +@import url("' . file_create_url('public://css/12.css') . '?0"); +@import url("' . file_create_url('public://css/13.css') . '?0"); +@import url("' . file_create_url('public://css/14.css') . '?0"); +@import url("' . file_create_url('public://css/15.css') . '?0"); +@import url("' . file_create_url('public://css/16.css') . '?0"); +@import url("' . file_create_url('public://css/17.css') . '?0"); +@import url("' . file_create_url('public://css/18.css') . '?0"); +@import url("' . file_create_url('public://css/19.css') . '?0"); +', 'all'), + 3 => $create_style_element(' +@import url("' . file_create_url('public://css/20.css') . '?0"); +', 'print'), + 4 => $create_style_element(' +@import url("' . file_create_url('public://css/21.css') . '?0"); +@import url("' . file_create_url('public://css/22.css') . '?0"); +@import url("' . file_create_url('public://css/23.css') . '?0"); +@import url("' . file_create_url('public://css/24.css') . '?0"); +@import url("' . file_create_url('public://css/25.css') . '?0"); +@import url("' . file_create_url('public://css/26.css') . '?0"); +@import url("' . file_create_url('public://css/27.css') . '?0"); +@import url("' . file_create_url('public://css/28.css') . '?0"); +@import url("' . file_create_url('public://css/29.css') . '?0"); +@import url("' . file_create_url('public://css/30.css') . '?0"); +@import url("' . file_create_url('public://css/31.css') . '?0"); +@import url("' . file_create_url('public://css/32.css') . '?0"); +', 'all'), + ), + ), + // 32 file CSS assets with the same properties, except for the 15th, which + // has 'preprocess' = FALSE. Expect 1 link element and 2 style elements. + 6 => array( + array( + 0 => $create_file_css_asset('public://css/1.css'), + 1 => $create_file_css_asset('public://css/2.css'), + 2 => $create_file_css_asset('public://css/3.css'), + 3 => $create_file_css_asset('public://css/4.css'), + 4 => $create_file_css_asset('public://css/5.css'), + 5 => $create_file_css_asset('public://css/6.css'), + 6 => $create_file_css_asset('public://css/7.css'), + 7 => $create_file_css_asset('public://css/8.css'), + 8 => $create_file_css_asset('public://css/9.css'), + 9 => $create_file_css_asset('public://css/10.css'), + 10 => $create_file_css_asset('public://css/11.css'), + 11 => $create_file_css_asset('public://css/12.css'), + 12 => $create_file_css_asset('public://css/13.css'), + 13 => $create_file_css_asset('public://css/14.css'), + 14 => $create_file_css_asset('public://css/15.css', 'all', FALSE), + 15 => $create_file_css_asset('public://css/16.css'), + 16 => $create_file_css_asset('public://css/17.css'), + 17 => $create_file_css_asset('public://css/18.css'), + 18 => $create_file_css_asset('public://css/19.css'), + 19 => $create_file_css_asset('public://css/20.css'), + 20 => $create_file_css_asset('public://css/21.css'), + 21 => $create_file_css_asset('public://css/22.css'), + 22 => $create_file_css_asset('public://css/23.css'), + 23 => $create_file_css_asset('public://css/24.css'), + 24 => $create_file_css_asset('public://css/25.css'), + 25 => $create_file_css_asset('public://css/26.css'), + 26 => $create_file_css_asset('public://css/27.css'), + 27 => $create_file_css_asset('public://css/28.css'), + 28 => $create_file_css_asset('public://css/29.css'), + 29 => $create_file_css_asset('public://css/30.css'), + 30 => $create_file_css_asset('public://css/31.css'), + 31 => $create_file_css_asset('public://css/32.css'), + ), + array( + 0 => $create_style_element(' +@import url("' . file_create_url('public://css/1.css') . '?0"); +@import url("' . file_create_url('public://css/2.css') . '?0"); +@import url("' . file_create_url('public://css/3.css') . '?0"); +@import url("' . file_create_url('public://css/4.css') . '?0"); +@import url("' . file_create_url('public://css/5.css') . '?0"); +@import url("' . file_create_url('public://css/6.css') . '?0"); +@import url("' . file_create_url('public://css/7.css') . '?0"); +@import url("' . file_create_url('public://css/8.css') . '?0"); +@import url("' . file_create_url('public://css/9.css') . '?0"); +@import url("' . file_create_url('public://css/10.css') . '?0"); +@import url("' . file_create_url('public://css/11.css') . '?0"); +@import url("' . file_create_url('public://css/12.css') . '?0"); +@import url("' . file_create_url('public://css/13.css') . '?0"); +@import url("' . file_create_url('public://css/14.css') . '?0"); +', 'all'), + 1 => $create_link_element(file_create_url('public://css/15.css') . '?0'), + 2 => $create_style_element(' +@import url("' . file_create_url('public://css/16.css') . '?0"); +@import url("' . file_create_url('public://css/17.css') . '?0"); +@import url("' . file_create_url('public://css/18.css') . '?0"); +@import url("' . file_create_url('public://css/19.css') . '?0"); +@import url("' . file_create_url('public://css/20.css') . '?0"); +@import url("' . file_create_url('public://css/21.css') . '?0"); +@import url("' . file_create_url('public://css/22.css') . '?0"); +@import url("' . file_create_url('public://css/23.css') . '?0"); +@import url("' . file_create_url('public://css/24.css') . '?0"); +@import url("' . file_create_url('public://css/25.css') . '?0"); +@import url("' . file_create_url('public://css/26.css') . '?0"); +@import url("' . file_create_url('public://css/27.css') . '?0"); +@import url("' . file_create_url('public://css/28.css') . '?0"); +@import url("' . file_create_url('public://css/29.css') . '?0"); +@import url("' . file_create_url('public://css/30.css') . '?0"); +@import url("' . file_create_url('public://css/31.css') . '?0"); +@import url("' . file_create_url('public://css/32.css') . '?0"); +', 'all'), + ), + ), + ); + } + + /** + * Tests CSS asset rendering. + * + * @dataProvider testRenderProvider + */ + function testRender(array $css_assets, array $render_elements) { + $this->assertSame($render_elements, $this->renderer->render($css_assets)); + } + + /** + * Tests a CSS asset group with the invalid 'type' => 'internal'. + */ + function testRenderInvalidType() { + $this->setExpectedException('Exception', 'Invalid CSS asset type.'); + + $css_group = array( + 'group' => 0, + 'every_page' => TRUE, + 'type' => 'internal', + 'media' => 'all', + 'preprocess' => TRUE, + 'browsers' => array(), + 'data' => 'http://example.com/popular.js' + ); + $this->renderer->render($css_group); + } +} +} diff --git a/core/tests/Drupal/Tests/Core/Asset/CssOptimizerUnitTest.php b/core/tests/Drupal/Tests/Core/Asset/CssOptimizerUnitTest.php new file mode 100644 index 0000000..41eb529 --- /dev/null +++ b/core/tests/Drupal/Tests/Core/Asset/CssOptimizerUnitTest.php @@ -0,0 +1,227 @@ + 'CSS asset optimizer functionality', + 'description' => 'Tests the CSS asset optimizer.', + 'group' => 'Asset handling', + ); + } + + function setUp() { + parent::setUp(); + + $this->optimizer = new CssOptimizer(); + } + + /** + * Provides data for the CSS asset optimizing test. + */ + function testOptimizeProvider() { + $path = dirname(__FILE__) . '/css_test_files/'; + return array( + // File. Tests: + // - Stripped comments and white-space. + // - Retain white-space in selectors. (http://drupal.org/node/472820) + // - Retain pseudo-selectors. (http://drupal.org/node/460448) + 0 => array( + array( + 'group' => -100, + 'every_page' => TRUE, + 'type' => 'file', + 'weight' => 0.012, + 'media' => 'all', + 'preprocess' => TRUE, + 'data' => $path . 'css_input_without_import.css', + 'browsers' => array('IE' => TRUE, '!IE' => TRUE), + 'basename' => 'css_input_without_import.css', + ), + file_get_contents($path . 'css_input_without_import.css.optimized.css'), + ), + // File. Tests: + // - Proper URLs in imported files. (http://drupal.org/node/265719) + // - A background image with relative paths, which must be rewritten. + // - The rewritten background image path must also be passed through + // file_create_url(). (https://drupal.org/node/1961340) + 1 => array( + array( + 'group' => -100, + 'every_page' => TRUE, + 'type' => 'file', + 'weight' => 0.013, + 'media' => 'all', + 'preprocess' => TRUE, + 'data' => $path . 'css_input_with_import.css', + 'browsers' => array('IE' => TRUE, '!IE' => TRUE), + 'basename' => 'css_input_with_import.css', + ), + str_replace('url(images/icon.png)', 'url(' . file_create_url($path . 'images/icon.png') . ')', file_get_contents($path . 'css_input_with_import.css.optimized.css')), + ), + // File. Tests: + // - Retain comment hacks. + 2 => array( + array( + 'group' => -100, + 'every_page' => TRUE, + 'type' => 'file', + 'weight' => 0.013, + 'media' => 'all', + 'preprocess' => TRUE, + 'data' => $path . 'comment_hacks.css', + 'browsers' => array('IE' => TRUE, '!IE' => TRUE), + 'basename' => 'comment_hacks.css', + ), + file_get_contents($path . 'comment_hacks.css.optimized.css'), + ), + // Inline. Preprocessing enabled. + 3 => array( + array( + 'group' => 0, + 'every_page' => FALSE, + 'type' => 'inline', + 'weight' => 0.012, + 'media' => 'all', + 'preprocess' => TRUE, + 'data' => '.girlfriend { display: none; }', + 'browsers' => array('IE' => TRUE, '!IE' => TRUE), + ), + ".girlfriend{display:none;}\n", + ), + // Inline. Preprocessing disabled. + 4 => array( + array( + 'group' => 0, + 'every_page' => FALSE, + 'type' => 'inline', + 'weight' => 0.013, + 'media' => 'all', + 'preprocess' => FALSE, + 'data' => '#home body { position: fixed; }', + 'browsers' => array('IE' => TRUE, '!IE' => TRUE), + ), + '#home body { position: fixed; }', + ) + ); + } + + /** + * Tests optimizing a CSS asset group containing 'type' => 'file'. + * + * @dataProvider testOptimizeProvider + */ + function testOptimize($css_asset, $expected) { + $this->assertEquals($expected, $this->optimizer->optimize($css_asset), 'Group of file CSS assets optimized correctly.'); + } + + /** + * Tests a file CSS asset with preprocessing disabled. + */ + function testTypeFilePreprocessingDisabled() { + $this->setExpectedException('Exception', 'Only file CSS assets with preprocessing enabled can be optimized.'); + + $css_asset = array( + 'group' => -100, + 'every_page' => TRUE, + 'type' => 'file', + 'weight' => 0.012, + 'media' => 'all', + // Preprocessing disabled. + 'preprocess' => FALSE, + 'data' => 'tests/Drupal/Tests/Core/Asset/foo.css', + 'browsers' => array('IE' => TRUE, '!IE' => TRUE), + 'basename' => 'foo.css', + ); + $this->optimizer->optimize($css_asset); + } + + /** + * Tests a CSS asset with 'type' => 'external'. + */ + function testTypeExternal() { + $this->setExpectedException('Exception', 'Only file or inline CSS assets can be optimized.'); + + $css_asset = array( + 'group' => -100, + 'every_page' => TRUE, + // Type external. + 'type' => 'external', + 'weight' => 0.012, + 'media' => 'all', + 'preprocess' => TRUE, + 'data' => 'http://example.com/foo.js', + 'browsers' => array('IE' => TRUE, '!IE' => TRUE), + ); + $this->optimizer->optimize($css_asset); + } + +} +} diff --git a/core/modules/simpletest/files/css_test_files/comment_hacks.css b/core/tests/Drupal/Tests/Core/Asset/css_test_files/comment_hacks.css similarity index 100% rename from core/modules/simpletest/files/css_test_files/comment_hacks.css rename to core/tests/Drupal/Tests/Core/Asset/css_test_files/comment_hacks.css diff --git a/core/modules/simpletest/files/css_test_files/comment_hacks.css.optimized.css b/core/tests/Drupal/Tests/Core/Asset/css_test_files/comment_hacks.css.optimized.css similarity index 100% rename from core/modules/simpletest/files/css_test_files/comment_hacks.css.optimized.css rename to core/tests/Drupal/Tests/Core/Asset/css_test_files/comment_hacks.css.optimized.css diff --git a/core/modules/simpletest/files/css_test_files/css_input_with_import.css b/core/tests/Drupal/Tests/Core/Asset/css_test_files/css_input_with_import.css similarity index 100% rename from core/modules/simpletest/files/css_test_files/css_input_with_import.css rename to core/tests/Drupal/Tests/Core/Asset/css_test_files/css_input_with_import.css diff --git a/core/modules/simpletest/files/css_test_files/css_input_with_import.css.optimized.css b/core/tests/Drupal/Tests/Core/Asset/css_test_files/css_input_with_import.css.optimized.css similarity index 100% rename from core/modules/simpletest/files/css_test_files/css_input_with_import.css.optimized.css rename to core/tests/Drupal/Tests/Core/Asset/css_test_files/css_input_with_import.css.optimized.css diff --git a/core/modules/simpletest/files/css_test_files/css_input_without_import.css b/core/tests/Drupal/Tests/Core/Asset/css_test_files/css_input_without_import.css similarity index 100% rename from core/modules/simpletest/files/css_test_files/css_input_without_import.css rename to core/tests/Drupal/Tests/Core/Asset/css_test_files/css_input_without_import.css diff --git a/core/modules/simpletest/files/css_test_files/css_input_without_import.css.optimized.css b/core/tests/Drupal/Tests/Core/Asset/css_test_files/css_input_without_import.css.optimized.css similarity index 100% rename from core/modules/simpletest/files/css_test_files/css_input_without_import.css.optimized.css rename to core/tests/Drupal/Tests/Core/Asset/css_test_files/css_input_without_import.css.optimized.css diff --git a/core/modules/simpletest/files/css_test_files/import1.css b/core/tests/Drupal/Tests/Core/Asset/css_test_files/import1.css similarity index 100% rename from core/modules/simpletest/files/css_test_files/import1.css rename to core/tests/Drupal/Tests/Core/Asset/css_test_files/import1.css diff --git a/core/modules/simpletest/files/css_test_files/import2.css b/core/tests/Drupal/Tests/Core/Asset/css_test_files/import2.css similarity index 100% rename from core/modules/simpletest/files/css_test_files/import2.css rename to core/tests/Drupal/Tests/Core/Asset/css_test_files/import2.css