@requirement
As part of your subscription, Acquia supports older versions of PHP in an effort to provide additional + time for our customers to make the transition to a supported version. Read our PHP Documentation for more information.
', + [ + '@requirement' => $requirements['php']['description'] ?? '', + ]); } } diff --git a/docroot/modules/contrib/acquia_connector/acquia_connector.links.menu.yml b/docroot/modules/contrib/acquia_connector/acquia_connector.links.menu.yml index 8d80c59550..cb06828bde 100644 --- a/docroot/modules/contrib/acquia_connector/acquia_connector.links.menu.yml +++ b/docroot/modules/contrib/acquia_connector/acquia_connector.links.menu.yml @@ -1,6 +1,6 @@ acquia_connector.settings: title: 'Acquia Connector settings' - description: 'Acquia connector settings.' - parent: system.admin_config_system + description: 'Acquia Connector settings.' + parent: system.admin_config_services route_name: acquia_connector.settings weight: -20 diff --git a/docroot/modules/contrib/acquia_connector/acquia_connector.module b/docroot/modules/contrib/acquia_connector/acquia_connector.module index 9ead69ac3a..815a6ee7ae 100644 --- a/docroot/modules/contrib/acquia_connector/acquia_connector.module +++ b/docroot/modules/contrib/acquia_connector/acquia_connector.module @@ -5,14 +5,8 @@ * Acquia Connector module. */ -use Drupal\acquia_connector\AutoConnector; -use Drupal\acquia_connector\Helper\Storage; -use Drupal\acquia_connector\Subscription; -use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Link; -use Drupal\Core\Routing\RouteMatchInterface; use Drupal\Core\Url; -use Drupal\update\UpdateFetcherInterface; // Version of SPI data format. define('ACQUIA_CONNECTOR_ACQUIA_SPI_DATA_VERSION', 3.1); @@ -29,34 +23,16 @@ define('ACQUIA_CONNECTOR_ACQUIA_SPI_METHOD_INSIGHT', 'insight'); /** * Implements hook_help(). */ -function acquia_connector_help($route_name, RouteMatchInterface $route_match) { +function acquia_connector_help($route_name) { switch ($route_name) { case 'help.page.acquia_connector': $output = '' . t('The Acquia Connector module allows you to connect your site to Acquia.') . '
'; - $output .= '
' . Link::fromTextAndUrl(t('Read more about the installation and use of the Acquia Connector module on the Acquia Library'), Url::fromUri('https://docs.acquia.com/acquia-cloud/insight/install/', []))->toString() . '
'; - - $output .= '' . Link::fromTextAndUrl(t('Read more about the installation and use of the Acquia Connector module on the Acquia Library'), Url::fromUri('https://docs.acquia.com/cloud-platform/onboarding/install/', []))->toString() . '
'; $output .= ''; - $msg .= t('The following Acquia Search Solr index IDs would have worked for your current environment, but could not be found on your Acquia subscription: @list', ['@list' => $list]); - $msg .= '
'; - - } - - $msg .= PHP_EOL . t('To fix this problem, please read our documentation.', [ - '@url' => 'https://docs.acquia.com/acquia-search/multiple-cores', - ]); - - return Markup::create((string) $msg); - -} - -/** - * Returns formatted message about Acquia Search connection details. - * - * @param \Drupal\search_api\Entity\Server $server - * Server entity. - * - * @return \Drupal\Component\Render\MarkupInterface|string - * Renderable array or translatable markup. - * - * @throws \Drupal\search_api\SearchApiException - */ -function acquia_search_get_search_status_message(Server $server) { - /** @var \Drupal\search_api_solr\Plugin\search_api\backend\SearchApiSolrBackend $backend */ - $backend = $server->getBackend(); - $configuration = $backend->getSolrConnector()->getConfiguration(); - - $items[] = acquia_search_get_server_id_message($server->id()); - $items[] = acquia_search_get_server_url_message($configuration); - - // Report on the behavior chosen. - if (isset($configuration['overridden_by_acquia_search'])) { - $items[] = acquia_search_get_overridden_mode_message($configuration['overridden_by_acquia_search']); - } - - $items[] = acquia_search_get_server_availability_message($server); - $items[] = acquia_search_get_server_auth_check_message($server); - - $list = ['#theme' => 'item_list', '#items' => $items]; - $list = \Drupal::service('renderer')->renderRoot($list); - $msg = t('Connection managed by Acquia Search module.') . $list; - - return Markup::create((string) $msg); -} - -/** - * Get text describing the current override mode. - * - * @param int $override - * Override mode. Read only, core auto selected or using existing overrides. - * - * @return array|\Drupal\Core\StringTranslation\TranslatableMarkup - * Renderable array or translatable markup. - */ -function acquia_search_get_overridden_mode_message($override) { - switch ($override) { - case ACQUIA_SEARCH_AUTO_OVERRIDE_READ_ONLY: - return ['#markup' => '' . t('Acquia Search module automatically enforced read-only mode on this connection.') . '']; - - case ACQUIA_SEARCH_OVERRIDE_AUTO_SET: - return t('Acquia Search module automatically selected the proper Solr connection based on the detected environment.'); - - case ACQUIA_SEARCH_EXISTING_OVERRIDE: - return ['#markup' => '' . t('Acquia Search module used overrides set from the acquia_search.settings configuration object instead of automatically selecting an available Acquia Search Solr connection.') . '']; - } -} - -/** - * Get text showing the current URL based on configuration. - * - * @param array $configuration - * A configuration array containing scheme, host, port and path. - * - * @return \Drupal\Core\StringTranslation\TranslatableMarkup - * Translatable markup. - */ -function acquia_search_get_server_url_message(array $configuration) { - $url = $configuration['scheme'] . '://' . $configuration['host'] . ':' . $configuration['port'] . $configuration['path']; - return t('URL: @url', ['@url' => $url]); -} - -/** - * Get text describing current server ID. - * - * @param string $server_id - * Server ID. - * - * @return \Drupal\Core\StringTranslation\TranslatableMarkup - * Translatable markup. - */ -function acquia_search_get_server_id_message($server_id) { - return t('search_api_solr.module server ID: @id', ['@id' => $server_id]); -} - -/** - * Get message describing authentication status for the given server. - * - * @param \Drupal\search_api\Entity\Server $server - * Server entity. - * - * @return array|\Drupal\Core\StringTranslation\TranslatableMarkup - * Renderable array or translatable markup. - * - * @throws \Drupal\search_api\SearchApiException - */ -function acquia_search_get_server_auth_check_message(Server $server) { - if ($server->getBackend()->getSolrConnector()->pingServer()) { - return t('Requests to Solr core are passing authentication checks.'); - } - - return [ - '#markup' => '' . t('Solr core authentication check fails.') . '', - ]; -} - -/** - * Get text describing availability for the given server. - * - * @param \Drupal\search_api\Entity\Server $server - * Server entity. - * - * @return array|\Drupal\Core\StringTranslation\TranslatableMarkup - * Renderable array or translatable markup. - * - * @throws \Drupal\search_api\SearchApiException - */ -function acquia_search_get_server_availability_message(Server $server) { - if ($server->getBackend()->getSolrConnector()->pingCore()) { - return t('Solr core is currently reachable and up.'); - } - - return [ - '#markup' => '' . t('Solr index is currently unreachable.') . '', - ]; -} - -/** - * Instantiates the PreferredSearchCoreService class. - * - * The PreferredSearchCoreService class, helps to determines which search core - * should be used and whether it is available within the subscription. - * - * @return \Drupal\acquia_search\PreferredSearchCoreService - * Preferred Search Core Service. - */ -function acquia_search_get_core_service() { - static $core_service; - - if (isset($core_service)) { - return $core_service; - } - - $storage = new Storage(); - $acquia_identifier = $storage->getIdentifier(); - $ah_env = isset($_ENV['AH_SITE_ENVIRONMENT']) ? $_ENV['AH_SITE_ENVIRONMENT'] : ''; - $ah_site_name = isset($_ENV['AH_SITE_NAME']) ? $_ENV['AH_SITE_NAME'] : ''; - $ah_site_group = isset($_ENV['AH_SITE_GROUP']) ? $_ENV['AH_SITE_GROUP'] : ''; - $conf_path = \Drupal::service('site.path'); - $sites_foldername = substr($conf_path, strrpos($conf_path, '/') + 1); - $ah_db_name = ''; - if ($ah_env && $ah_site_name && $ah_site_group) { - $options = Database::getConnection()->getConnectionOptions(); - $ah_db_name = $options['database']; - } - - $subscription = \Drupal::state()->get('acquia_subscription_data'); - - $available_cores = []; - if (!empty($subscription['heartbeat_data']['search_cores'])) { - $available_cores = $subscription['heartbeat_data']['search_cores']; - } - - $search_v3_cores = acquia_search_get_v3_cores($acquia_identifier); - $available_cores = array_merge($available_cores, $search_v3_cores); - - $core_service = new PreferredSearchCoreService($acquia_identifier, $ah_env, $sites_foldername, $ah_db_name, $available_cores); - return $core_service; -} - -/** - * Implements hook_theme_registry_alter(). - * - * Helps us alter some Search API status pages. - * - * @see acquia_search_theme_search_api_index() - */ -function acquia_search_theme_registry_alter(&$theme_registry) { - $module_handler = \Drupal::moduleHandler(); - $module_path = $module_handler->getModule('acquia_search')->getPath(); - - $theme_registry['search_api_index']['variables']['acquia_search_info_box'] = NULL; - $theme_registry['search_api_index']['path'] = $module_path . '/templates'; - - $theme_registry['search_api_server']['variables']['acquia_search_info_box'] = NULL; - $theme_registry['search_api_server']['path'] = $module_path . '/templates'; -} - -/** - * Implements hook_preprocess_HOOK(). - * - * Theme override for Search API index status page. - * - * @see acquia_search_theme_registry_alter() - */ -function acquia_search_preprocess_search_api_index(&$variables) { - /** @var \Drupal\search_api\Entity\Index $index */ - $index = $variables['index']; - /** @var \Drupal\search_api\Entity\Server $server */ - $server = Server::load($index->get('server')); - - if (!$server || !acquia_search_is_acquia_server($server->getBackendConfig())) { - return; - } - - if (acquia_search_should_set_read_only_mode()) { - acquia_search_server_show_read_only_mode_warning(); - } - - $variables['acquia_search_info_box'] = [ - '#type' => 'fieldset', - '#title' => t('Acquia Search status for this connection'), - '#markup' => acquia_search_get_search_status_message($server), - ]; -} - -/** - * Implements hook_preprocess_HOOK(). - * - * Theme override for Search API server status page. - * - * @see acquia_search_theme_registry_alter() - */ -function acquia_search_preprocess_search_api_server(array &$variables) { - /** @var \Drupal\search_api\Entity\Server $server */ - $server = $variables['server']; - - if (!acquia_search_is_acquia_server($server->getBackendConfig())) { - return; - } - - if (acquia_search_should_set_read_only_mode()) { - acquia_search_server_show_read_only_mode_warning(); - } - - $variables['acquia_search_info_box'] = [ - '#type' => 'fieldset', - '#title' => t('Acquia Search status for this connection'), - '#markup' => acquia_search_get_search_status_message($server), - ]; -} - -/** - * Initializes and returns an instance of AcquiaSearchV3ApiClient. - * - * @return \Drupal\acquia_search\AcquiaSearchV3ApiClient|false - * Acquia search V3 API Client or false on failure. - */ -function acquia_search_get_v3_client() { - $search_v3_host = \Drupal::state()->get('acquia_search.v3_api_host') ? \Drupal::state()->get('acquia_search.v3_api_host') : 'https://api.sr.acquia.com'; - $search_v3_api_key = \Drupal::state()->get('acquia_search.v3_api_key'); - $drupal_http_client = \Drupal::service('http_client'); - $cache = \Drupal::cache(); - - // If any of these variables are empty return FALSE. - if (empty($search_v3_host) || empty($search_v3_api_key)) { - return FALSE; - } - - return new AcquiaSearchV3ApiClient($search_v3_host, $search_v3_api_key, $drupal_http_client, $cache); -} - -/** - * Retrieves list of search v3 cores. - * - * @param string $acquia_identifier - * Acquia identifier (Subscription network id). - * - * @return array - * Array of Acquia search V3 cores. Empty array if cores aren't available. - */ -function acquia_search_get_v3_cores($acquia_identifier) { - $search_v3_enabled = \Drupal::config('acquia_search.settings')->get('search_v3_enabled'); - - if (!$search_v3_enabled) { - return []; - } - - $search_v3_client = acquia_search_get_v3_client(); - if (!$search_v3_client) { - return []; - } - - $search_v3_cores = $search_v3_client->getSearchV3Indexes($acquia_identifier); - if (!is_array($search_v3_cores) || empty($search_v3_cores)) { - return []; - } - - return $search_v3_cores; -} - -/** - * Disable Search API Solr Warning/Error messages regarding EOL. - * @param $variables - */ -function acquia_search_preprocess_status_messages(&$variables) { - if (isset($variables['message_list'])) { - foreach ($variables['message_list'] as $msg_type => $messages) { - foreach ($messages as $message_key => $message) { - if (strpos((string)$message, "Search API Solr 8.x-1.x") !== FALSE) { - unset($variables['message_list'][$msg_type][$message_key]); - } - if (empty($variables['message_list'][$msg_type])) { - unset($variables['message_list'][$msg_type]); - } - } - } - } -} diff --git a/docroot/modules/contrib/acquia_connector/acquia_search/acquia_search.routing.yml b/docroot/modules/contrib/acquia_connector/acquia_search/acquia_search.routing.yml deleted file mode 100644 index ad0efa75d3..0000000000 --- a/docroot/modules/contrib/acquia_connector/acquia_search/acquia_search.routing.yml +++ /dev/null @@ -1,8 +0,0 @@ -entity.acquia_search_server.delete_form: - path: '/admin/config/search/search-api/server/acquia_search_server/delete' - requirements: - _access: 'FALSE' -entity.acquia_search_index.delete_form: - path: '/admin/config/search/search-api/index/acquia_search_index/delete' - requirements: - _access: 'FALSE' diff --git a/docroot/modules/contrib/acquia_connector/acquia_search/composer.json b/docroot/modules/contrib/acquia_connector/acquia_search/composer.json deleted file mode 100644 index 1697e4738a..0000000000 --- a/docroot/modules/contrib/acquia_connector/acquia_search/composer.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "name": "drupal/acquia_search", - "type": "drupal-module", - "description": "Provides integration between your Drupal site and Acquia's hosted search service", - "license": "GPL-2.0-or-later", - "require": { - "drupal/search_api": "^1.17", - "drupal/search_api_solr": "1.7" - }, - "repositories": { - "drupal": { - "type": "composer", - "url": "https://packages.drupal.org/8" - } - } -} diff --git a/docroot/modules/contrib/acquia_connector/acquia_search/config/install/acquia_search.settings.yml b/docroot/modules/contrib/acquia_connector/acquia_search/config/install/acquia_search.settings.yml deleted file mode 100644 index 7046feea58..0000000000 --- a/docroot/modules/contrib/acquia_connector/acquia_search/config/install/acquia_search.settings.yml +++ /dev/null @@ -1,7 +0,0 @@ -host: search.acquia.com -path: '' -derived_key_salt: '' -version: 8.x -default_search_core: null -disable_auto_read_only: false -disable_auto_switch: false diff --git a/docroot/modules/contrib/acquia_connector/acquia_search/config/install/search_api.server.acquia_search_server.yml b/docroot/modules/contrib/acquia_connector/acquia_search/config/install/search_api.server.acquia_search_server.yml deleted file mode 100644 index ab1b0312fc..0000000000 --- a/docroot/modules/contrib/acquia_connector/acquia_search/config/install/search_api.server.acquia_search_server.yml +++ /dev/null @@ -1,23 +0,0 @@ -id: acquia_search_server -name: 'Acquia Search API Solr server' -description: '' -status: false -backend: search_api_solr -backend_config: - site_hash: true - excerpt: false - retrieve_data: true - highlight_data: true - skip_schema_check: false - connector: solr_acquia_connector - connector_config: - scheme: http - timeout: 5 - index_timeout: 10 - optimize_timeout: 15 - solr_version: '' - http_method: AUTO -langcode: en -dependencies: - module: - - acquia_search diff --git a/docroot/modules/contrib/acquia_connector/acquia_search/config/optional/block.block.exposedformacquia_searchpage.yml b/docroot/modules/contrib/acquia_connector/acquia_search/config/optional/block.block.exposedformacquia_searchpage.yml deleted file mode 100644 index 675da1333e..0000000000 --- a/docroot/modules/contrib/acquia_connector/acquia_search/config/optional/block.block.exposedformacquia_searchpage.yml +++ /dev/null @@ -1,22 +0,0 @@ -langcode: en -status: true -dependencies: - config: - - views.view.acquia_search - module: - - views - theme: - - bartik -id: exposedformacquia_searchpage -theme: bartik -region: sidebar_first -weight: -8 -provider: null -plugin: 'views_exposed_filter_block:acquia_search-page' -settings: - id: 'views_exposed_filter_block:acquia_search-page' - label: '' - provider: views - label_display: visible - views_label: '' -visibility: { } diff --git a/docroot/modules/contrib/acquia_connector/acquia_search/config/optional/search_api.index.acquia_search_index.yml b/docroot/modules/contrib/acquia_connector/acquia_search/config/optional/search_api.index.acquia_search_index.yml deleted file mode 100644 index 3ecc77a044..0000000000 --- a/docroot/modules/contrib/acquia_connector/acquia_search/config/optional/search_api.index.acquia_search_index.yml +++ /dev/null @@ -1,49 +0,0 @@ -id: acquia_search_index -name: 'Acquia Search Solr Index' -langcode: en -description: '' -dependencies: - config: - - field.storage.node.body - - search_api.server.acquia_search_server - module: - - search_api - - node -datasource_settings: - 'entity:node': - bundles: - default: true - selected: { } - languages: - default: true - selected: { } -field_settings: - search_api_language: - label: 'Item language' - datasource_id: null - property_path: search_api_language - type: string - body: - label: Body - datasource_id: 'entity:node' - property_path: body - type: text - dependencies: - config: - - field.storage.node.body - title: - label: Title - datasource_id: 'entity:node' - property_path: title - type: text -processor_settings: - add_url: { } - language: { } -options: - index_directly: false - cron_limit: 50 -read_only: false -server: acquia_search_server -status: false -tracker_settings: - default: { } diff --git a/docroot/modules/contrib/acquia_connector/acquia_search/config/optional/views.view.acquia_search.yml b/docroot/modules/contrib/acquia_connector/acquia_search/config/optional/views.view.acquia_search.yml deleted file mode 100644 index 9fe7a2dcba..0000000000 --- a/docroot/modules/contrib/acquia_connector/acquia_search/config/optional/views.view.acquia_search.yml +++ /dev/null @@ -1,237 +0,0 @@ -langcode: en -status: true -dependencies: - config: - - search_api.index.acquia_search_index - - system.menu.main - module: - - search_api - - user -_core: - default_config_hash: g-miB3uO3E3WyjupLgVdEqdOJlePKXyeieMB1TSH4do -id: acquia_search -label: 'Acquia Search' -module: views -description: '' -tag: '' -base_table: search_api_index_acquia_search_index -base_field: search_api_id -core: 8.x -display: - default: - display_plugin: default - id: default - display_title: Master - position: 0 - display_options: - access: - type: perm - options: - perm: 'access content' - cache: - type: tag - options: { } - query: - type: search_views_query - exposed_form: - type: basic - options: - submit_button: Search - reset_button: true - reset_button_label: Reset - exposed_sorts_label: 'Sort by' - expose_sort_order: true - sort_asc_label: Asc - sort_desc_label: Desc - pager: - type: full - options: - items_per_page: 10 - offset: 0 - id: 0 - total_pages: null - expose: - items_per_page: false - items_per_page_label: 'Items per page' - items_per_page_options: '5, 10, 25, 50' - items_per_page_options_all: false - items_per_page_options_all_label: '- All -' - offset: false - offset_label: Offset - tags: - previous: '‹ Previous' - next: 'Next ›' - first: '« First' - last: 'Last »' - quantity: 9 - style: - type: default - row: - type: search_api - options: - view_modes: - 'entity:node': - article: teaser - page: teaser - fields: - search_api_language: - table: search_api_index_acquia_search_index - field: search_api_language - id: search_api_language - entity_type: null - entity_field: null - plugin_id: search_api - relationship: none - group_type: group - admin_label: '' - label: '' - exclude: false - alter: - alter_text: false - text: '' - make_link: false - path: '' - absolute: false - external: false - replace_spaces: false - path_case: none - trim_whitespace: false - alt: '' - rel: '' - link_class: '' - prefix: '' - suffix: '' - target: '' - nl2br: false - max_length: 0 - word_boundary: true - ellipsis: true - more_link: false - more_link_text: '' - more_link_path: '' - strip_tags: false - trim: false - preserve_tags: '' - html: false - element_type: '' - element_class: '' - element_label_type: '' - element_label_class: '' - element_label_colon: true - element_wrapper_type: '' - element_wrapper_class: '' - element_default_classes: true - empty: '' - hide_empty: false - empty_zero: false - hide_alter_empty: true - link_to_item: false - multi_type: separator - multi_separator: ', ' - filters: - search_api_fulltext: - id: search_api_fulltext - table: search_api_index_acquia_search_index - field: search_api_fulltext - relationship: none - group_type: group - admin_label: '' - operator: and - value: '' - group: 1 - exposed: true - expose: - operator_id: search_api_fulltext_op - label: Search - description: '' - use_operator: false - operator: search_api_fulltext_op - identifier: search - required: false - remember: false - multiple: false - remember_roles: - authenticated: authenticated - anonymous: '0' - administrator: '0' - is_grouped: false - group_info: - label: '' - description: '' - identifier: '' - optional: true - widget: select - multiple: false - remember: false - default_group: All - default_group_multiple: { } - group_items: { } - min_length: null - fields: { } - plugin_id: search_api_fulltext - sorts: - search_api_relevance: - id: search_api_relevance - table: search_api_index_acquia_search_index - field: search_api_relevance - relationship: none - group_type: group - admin_label: '' - order: DESC - exposed: false - expose: - label: '' - plugin_id: search_api - title: Search - header: { } - footer: { } - empty: - area: - id: area - table: views - field: area - relationship: none - group_type: group - admin_label: '' - empty: true - tokenize: false - content: - value: 'No results found.' - format: basic_html - plugin_id: text - relationships: { } - arguments: { } - display_extenders: { } - cache_metadata: - max-age: 0 - contexts: - - 'languages:language_interface' - - url.query_args - - user.permissions - tags: { } - page: - display_plugin: page - id: page - display_title: 'Acquia Search Page' - position: 1 - display_options: - display_extenders: { } - path: search - exposed_block: true - menu: - type: normal - title: Search - description: '' - expanded: false - parent: '' - weight: 0 - context: '0' - menu_name: main - display_description: '' - cache_metadata: - max-age: 0 - contexts: - - 'languages:language_interface' - - url.query_args - - user.permissions - tags: { } diff --git a/docroot/modules/contrib/acquia_connector/acquia_search/config/schema/acquia_search.schema.yml b/docroot/modules/contrib/acquia_connector/acquia_search/config/schema/acquia_search.schema.yml deleted file mode 100644 index 9281f3d98f..0000000000 --- a/docroot/modules/contrib/acquia_connector/acquia_search/config/schema/acquia_search.schema.yml +++ /dev/null @@ -1,37 +0,0 @@ -# Schema for the configuration files of the acquia_search module. -acquia_search.settings: - type: config_object - label: 'Acquia search settings' - mapping: - host: - type: string - label: Acquia search host - path: - type: string - label: 'Acquia search path' - derived_key_salt: - type: string - label: 'Acquia derived key salt' - version: - type: string - label: 'Acquia Search version' - default_search_core: - type: string - label: 'String containing desired search core id. The search core should be available via Acquia Search API for the Acquia Subscription' - disable_auto_read_only: - type: boolean - label: 'If TRUE (only when disable_auto_switch is FALSE or not set) then there is no enforcing of read-only mode' - disable_auto_switch: - type: boolean - label: 'If TRUE, completely disables the auto-switching behavior' - -plugin.plugin_configuration.search_api_solr_connector.solr_acquia_connector: - type: plugin.plugin_configuration.search_api_solr_connector.standard - label: Acquia Search Solr connector settings - mapping: - acquia_search_possible_cores: - type: string - label: 'Possible cores' - overridden_by_acquia_search: - type: int - label: 'Overridden by Acquia Search' diff --git a/docroot/modules/contrib/acquia_connector/acquia_search/src/AcquiaSearchV3ApiClient.php b/docroot/modules/contrib/acquia_connector/acquia_search/src/AcquiaSearchV3ApiClient.php deleted file mode 100644 index e59c46e78b..0000000000 --- a/docroot/modules/contrib/acquia_connector/acquia_search/src/AcquiaSearchV3ApiClient.php +++ /dev/null @@ -1,218 +0,0 @@ -searchV3Host = $host; - $this->searchV3ApiKey = $api_key; - $this->httpClient = $http_client; - $this->headers = [ - 'Content-Type' => 'application/json', - 'Accept' => 'application/json', - ]; - $this->cache = $cache; - } - - /** - * Helper function to fetch all search v3 indexes for given network_id. - * - * @param string $network_id - * Subscription network id. - * - * @return array|false - * Response array or FALSE - */ - public function getSearchV3Indexes($network_id) { - $result = []; - if ($cache = $this->cache->get('acquia_search.v3indexes')) { - if (is_array($cache->data) && $cache->expire > time()) { - return $cache->data; - } - } - $indexes = $this->searchRequest('/index/network_id/get_all?network_id=' . $network_id); - if (is_array($indexes)) { - if (!empty($indexes)) { - foreach ($indexes as $index) { - $result[] = [ - 'balancer' => $index['host'], - 'core_id' => $index['name'], - 'version' => 'v3', - ]; - } - } - // Cache will be set in both cases, 1. when search v3 cores are found and - // 2. when there are no search v3 cores but api is reachable. - $this->cache->set('acquia_search.v3indexes', $result, time() + (24 * 60 * 60)); - return $result; - } - else { - // When api is not reachable, cache it for 1 minute. - $this->cache->set('acquia_search.v3keys', $result, time() + (60)); - } - - return FALSE; - } - - /** - * Fetch the search v3 index keys for given core_id and network_id. - * - * @param string $core_id - * Core id. - * @param string $network_id - * Acquia identifier. - * - * @return array|bool|false - * Search v3 index keys. - */ - public function getKeys($core_id, $network_id) { - if ($cache = $this->cache->get('acquia_search.v3keys')) { - if (!empty($cache->data) && $cache->expire > time()) { - return $cache->data; - } - } - - $keys = $this->searchRequest('/index/key?index_name=' . $core_id . '&network_id=' . $network_id); - if ($keys) { - // Cache will be set in both cases, 1. when search v3 cores are found and - // 2. when there are no search v3 cores but api is reachable. - $this->cache->set('acquia_search.v3keys', $keys, time() + (24 * 60 * 60)); - return $keys; - } - else { - // When api is not reachable, cache it for 1 minute. - $this->cache->set('acquia_search.v3keys', $keys, time() + (60)); - } - - return FALSE; - - } - - /** - * Create and send a request to search controller. - * - * @param string $path - * Path to call. - * - * @return array|false - * Response array or FALSE. - */ - public function searchRequest($path) { - $data = [ - 'host' => $this->searchV3Host, - 'headers' => [ - 'x-api-key' => $this->searchV3ApiKey, - ], - ]; - $uri = $data['host'] . $path; - $options = [ - 'headers' => $data['headers'], - 'body' => Json::encode($data), - ]; - - try { - $response = $this->httpClient->get($uri, $options); - if (!$response) { - throw new \Exception('Empty Response'); - } - $stream_size = $response->getBody()->getSize(); - $data = Json::decode($response->getBody()->read($stream_size)); - $status_code = $response->getStatusCode(); - - if ($status_code < 200 || $status_code > 299) { - \Drupal::logger('acquia search')->error("Couldn't connect to search v3 API: @message", - ['@message' => $response->getReasonPhrase()]); - return FALSE; - } - return $data; - } - catch (RequestException $e) { - if ($e->getCode() == 401) { - \Drupal::logger('acquia search')->error("Couldn't connect to search v3 API: - Received a 401 response from the API indicating that credentials are incorrect. - Please validate your credentials. @message", ['@message' => $e->getMessage()]); - } - elseif ($e->getCode() == 404) { - \Drupal::logger('acquia search')->error("Couldn't connect to search v3 API: - Received a 404 response from the API indicating that the api host is incorrect. - Please validate your host. @message", ['@message' => $e->getMessage()]); - } - else { - \Drupal::logger('acquia search')->error("Couldn't connect to search v3 API: Please - validate your api host and credentials. @message", ['@message' => $e->getMessage()]); - } - } - catch (\Exception $e) { - \Drupal::logger('acquia search')->error("Couldn't connect to search v3 API: @message", - ['@message' => $e->getMessage()]); - } - - return FALSE; - } - -} diff --git a/docroot/modules/contrib/acquia_connector/acquia_search/src/EventSubscriber/SearchSubscriber.php b/docroot/modules/contrib/acquia_connector/acquia_search/src/EventSubscriber/SearchSubscriber.php deleted file mode 100644 index 76bad8e007..0000000000 --- a/docroot/modules/contrib/acquia_connector/acquia_search/src/EventSubscriber/SearchSubscriber.php +++ /dev/null @@ -1,333 +0,0 @@ -client->getEventDispatcher(); - $dispatcher->addListener(Events::PRE_EXECUTE_REQUEST, [$this, 'preExecuteRequest']); - $dispatcher->addListener(Events::POST_EXECUTE_REQUEST, [$this, 'postExecuteRequest']); - } - - /** - * Build Acquia Solr Search Authenticator. - * - * @param \Solarium\Core\Event\preExecuteRequest $event - * PreExecuteRequest event. - */ - public function preExecuteRequest(preExecuteRequest $event) { - $request = $event->getRequest(); - $request->addParam('request_id', uniqid(), TRUE); - // If we're hosted on Acquia, and have an Acquia request ID, - // append it to the request so that we map Solr queries to Acquia search - // requests. - if (isset($_ENV['HTTP_X_REQUEST_ID'])) { - $xid = empty($_ENV['HTTP_X_REQUEST_ID']) ? '-' : $_ENV['HTTP_X_REQUEST_ID']; - $request->addParam('x-request-id', $xid); - } - $endpoint = $this->client->getEndpoint(); - $this->uri = $endpoint->getBaseUri() . $request->getUri(); - - $this->nonce = Crypt::randomBytesBase64(24); - $string = $request->getRawData(); - if (!$string) { - $parsed_url = parse_url($this->uri); - $path = isset($parsed_url['path']) ? $parsed_url['path'] : '/'; - $query = isset($parsed_url['query']) ? '?' . $parsed_url['query'] : ''; - // For pings only. - $string = $path . $query; - } - - $cookie = $this->calculateAuthCookie($string, $this->nonce, \Drupal::time()->getRequestTime()); - $request->addHeader('Cookie: ' . $cookie); - $request->addHeader('User-Agent: ' . 'acquia_search/' . \Drupal::config('acquia_search.settings')->get('version')); - } - - /** - * Validate response. - * - * @param \Solarium\Core\Event\postExecuteRequest $event - * postExecuteRequest event. - */ - public function postExecuteRequest(postExecuteRequest $event) { - $response = $event->getResponse(); - if ($response->getStatusCode() != 200) { - throw new HttpException($response->getStatusMessage()); - } - if ($event->getRequest()->getHandler() == 'admin/ping') { - return; - } - $this->authenticateResponse($event->getResponse(), $this->nonce, $this->uri); - } - - /** - * Validate the hmac for the response body. - * - * @param \Solarium\Core\Client\Response $response - * Solarium Response. - * @param string $nonce - * Nonce. - * @param string $url - * Url. - * - * @return \Solarium\Core\Client\Response - * Solarium Response. - * - * @throws \Solarium\Exception\HttpException - */ - protected function authenticateResponse(Response $response, $nonce, $url) { - $hmac = $this->extractHmac($response->getHeaders()); - if (!$this->validateResponse($hmac, $nonce, $response->getBody())) { - throw new HttpException('Authentication of search content failed url: ' . $url); - } - return $response; - } - - /** - * Look in the headers and get the hmac_digest out. - * - * @param mixed $headers - * Headers array. - * - * @return string - * Hmac_digest or empty string. - */ - public function extractHmac($headers) { - $reg = []; - if (is_array($headers)) { - foreach ($headers as $value) { - if (stristr($value, 'pragma') && preg_match("/hmac_digest=([^;]+);/i", $value, $reg)) { - return trim($reg[1]); - } - } - } - return ''; - } - - /** - * Validate the authenticity of returned data using a nonce and HMAC-SHA1. - * - * @param string $hmac - * HMAC. - * @param string $nonce - * Nonce. - * @param string $string - * Data string. - * @param string $derived_key - * Derived key. - * @param string $env_id - * Environment Id. - * - * @return bool - * TRUE if response is valid. - */ - public function validateResponse($hmac, $nonce, $string, $derived_key = NULL, $env_id = NULL) { - if (empty($derived_key)) { - $derived_key = $this->getDerivedKey($env_id); - } - return $hmac == hash_hmac('sha1', $nonce . $string, $derived_key); - } - - /** - * Get the derived key. - * - * Get the derived key for the solr hmac using the information shared with - * acquia.com. - * - * @param string $env_id - * Environment Id. - * - * @return string - * Derived Key. - */ - public function getDerivedKey($env_id = NULL) { - if (empty($env_id)) { - $env_id = $this->client->getEndpoint()->getKey(); - } - - // Get derived key for search v3 core if enabled. - $search_v3_enabled = \Drupal::config('acquia_search.settings')->get('search_v3_enabled'); - if ($search_v3_enabled) { - $search_v3_index = $this->getSearchV3IndexKeys(); - if ($search_v3_index) { - $this->derivedKey[$env_id] = CryptConnector::createDerivedKey($search_v3_index['product_policies']['salt'], $search_v3_index['key'], $search_v3_index['secret_key']); - return $this->derivedKey[$env_id]; - } - } - - if (!isset($this->derivedKey[$env_id])) { - $server = $this->client->getEndpoint(); - - // If derived_key comes from configuration, use that. - // @TODO: make sure the derived_key doesn't make it permanently into the DB! - if (!empty($server->getOption('derived_key'))) { - return $server->getOption('derived_key'); - } - - $acquia_index_id = $server->getOption('index_id'); - $storage = new Storage(); - $key = $storage->getKey(); - - // See if we need to overwrite these values. - // @todo: Implement the derived key per solr environment storage. - // In any case, this is equal for all subscriptions. Also - // even if the search sub is different, the main subscription should be - // active. - $derived_key_salt = $this->getDerivedKeySalt(); - - // We use a salt from acquia.com in key derivation since this is a shared - // value that we could change on the AN side if needed to force any - // or all clients to use a new derived key. We also use a string - // ('solr') specific to the service, since we want each service using a - // derived key to have a separate one. - if (empty($derived_key_salt) || empty($key) || empty($acquia_index_id)) { - // Expired or invalid subscription - don't continue. - $this->derivedKey[$env_id] = ''; - } - elseif (!isset($this->derivedKey[$env_id])) { - $this->derivedKey[$env_id] = CryptConnector::createDerivedKey($derived_key_salt, $acquia_index_id, $key); - } - } - - return $this->derivedKey[$env_id]; - } - - /** - * Returns the subscription's salt used to generate the derived key. - * - * The salt is stored in a system variable so that this module can continue - * connecting to Acquia Search even when the subscription data is not - * available. - * The most common reason for subscription data being unavailable is a failed - * heartbeat connection to rpc.acquia.com. - * - * Acquia Connector versions <= 7.x-2.7 pulled the derived key salt directly - * from the subscription data. In order to allow for seamless upgrades, this - * function checks whether the system variable exists and sets it with the - * data in the subscription if it doesn't. - * - * @return string - * The derived key salt. - * - * @see http://drupal.org/node/1784114 - */ - public function getDerivedKeySalt() { - $salt = \Drupal::config('acquia_search.settings')->get('derived_key_salt'); - if (!$salt) { - // If the variable doesn't exist, set it using the subscription data. - $subscription = \Drupal::state()->get('acquia_subscription_data'); - if (isset($subscription['derived_key_salt'])) { - \Drupal::configFactory()->getEditable('acquia_search.settings')->set('derived_key_salt', $subscription['derived_key_salt'])->save(); - $salt = $subscription['derived_key_salt']; - } - } - return $salt; - } - - /** - * Creates an authenticator based on a data string and HMAC-SHA1. - * - * @param string $string - * Data string. - * @param string $nonce - * Nonce. - * @param int $time - * Request time. - * @param string $derived_key - * Derived key. - * @param string $env_id - * Environment Id. - * - * @return string - * Auth cookie string. - */ - public function calculateAuthCookie($string, $nonce, $time, $derived_key = NULL, $env_id = NULL) { - if (empty($derived_key)) { - $derived_key = $this->getDerivedKey($env_id); - } - if (empty($derived_key)) { - // Expired or invalid subscription - don't continue. - return ''; - } - else { - return 'acquia_solr_time=' . $time . '; acquia_solr_nonce=' . $nonce . '; acquia_solr_hmac=' . hash_hmac('sha1', $time . $nonce . $string, $derived_key) . ';'; - } - } - - /** - * Fetches the search v3 index keys. - * - * @return array|null - * Search v3 index keys, NULL if unavailable. - */ - public function getSearchV3IndexKeys() { - $core_service = acquia_search_get_core_service(); - if (!$core_service->isPreferredCoreAvailable()) { - return; - } - $core = $core_service->getPreferredCore(); - // Check the core version to see if it's v2 or v3 core. - if (empty($core['version']) || $core['version'] !== 'v3') { - return; - } - - $search_v3_client = acquia_search_get_v3_client(); - if (!$search_v3_client) { - return; - } - - $storage = new Storage(); - $acquia_identifier = $storage->getIdentifier(); - $search_v3_index = $search_v3_client->getKeys($core['core_id'], $acquia_identifier); - if (is_array($search_v3_index) && !empty($search_v3_index)) { - return $search_v3_index; - } - - } - -} diff --git a/docroot/modules/contrib/acquia_connector/acquia_search/src/Plugin/SolrConnector/SearchApiSolrAcquiaConnector.php b/docroot/modules/contrib/acquia_connector/acquia_search/src/Plugin/SolrConnector/SearchApiSolrAcquiaConnector.php deleted file mode 100644 index 95a680adae..0000000000 --- a/docroot/modules/contrib/acquia_connector/acquia_search/src/Plugin/SolrConnector/SearchApiSolrAcquiaConnector.php +++ /dev/null @@ -1,273 +0,0 @@ -getCode(); - switch ($response_code) { - case 404: - $description = 'not found'; - break; - - case 401: - case 403: - $description = 'access denied'; - break; - - default: - $description = $e->getMessage(); - } - throw new SearchApiSolrException($this->t('Solr endpoint @endpoint: @description.', [ - '@endpoint' => $endpoint->getBaseUri(), - '@description' => $description, - ]), $response_code, $e); - } - - /** - * {@inheritdoc} - */ - public function defaultConfiguration() { - $configuration = parent::defaultConfiguration(); - $storage = new Storage(); - $configuration['index_id'] = $storage->getIdentifier(); - $configuration['path'] = '/solr/' . $storage->getIdentifier(); - $configuration['host'] = acquia_search_get_search_host(); - $configuration['port'] = '80'; - $configuration['scheme'] = 'http'; - - unset($configuration['overridden_by_acquia_search']); - - // If auto-switch feature is turned off - do not attempt to determine the - // preferred core. - if (acquia_search_is_auto_switch_disabled()) { - return $configuration; - } - - // If the search config is overridden in settings.php, apply this config - // to the Solr connection and don't attempt to determine the preferred - // core. - if (acquia_search_is_connection_config_overridden()) { - $configuration = $this->setOverriddenCore($configuration); - return $configuration; - } - - $preferred_core_service = acquia_search_get_core_service(); - - // If the preferred core available, set it. - if ($preferred_core_service->isPreferredCoreAvailable()) { - $configuration = $this->setPreferredCore($configuration, $preferred_core_service); - } - else { - // This means we can't detect which search core should be used, so we - // need to protect it by setting read-only mode but only if it applies. - if (acquia_search_should_set_read_only_mode()) { - $configuration = $this->setReadOnlyMode($configuration); - } - } - - return $configuration; - } - - /** - * Sets the preferred core in the given Solr config. - * - * @param array $configuration - * Solr connection configuration. - * @param \Drupal\acquia_search\PreferredSearchCoreService $preferred_core_service - * Service for determining the preferred search core. - * - * @return array - * Updated Solr connection configuration. - */ - protected function setPreferredCore(array $configuration, PreferredSearchCoreService $preferred_core_service) { - $configuration['index_id'] = $preferred_core_service->getPreferredCoreId(); - $configuration['path'] = '/solr/' . $preferred_core_service->getPreferredCoreId(); - $configuration['host'] = $preferred_core_service->getPreferredCoreHostname(); - $configuration['overridden_by_acquia_search'] = ACQUIA_SEARCH_OVERRIDE_AUTO_SET; - return $configuration; - } - - /** - * Sets the current connection overrides to the given Solr config. - * - * @param array $configuration - * Solr connection configuration. - * - * @return array - * Updated Solr connection configuration. - */ - protected function setOverriddenCore(array $configuration) { - $override = \Drupal::config('acquia_search.settings')->get('connection_override'); - $configuration['overridden_by_acquia_search'] = ACQUIA_SEARCH_EXISTING_OVERRIDE; - $configuration['path'] = '/solr/' . $override['index_id']; - return array_merge($configuration, $override); - } - - /** - * Sets read-only mode to the given Solr config. - * - * We enforce read-only mode in 2 ways: - * - The module implements hook_search_api_index_load() and alters indexes' - * read-only flag. - * - In this plugin, we "emulate" read-only mode by overriding - * $this->getUpdateQuery() and avoiding all updates just in case something - * is still attempting to directly call a Solr update. - * - * @param array $configuration - * Solr connection configuration. - * - * @return array - * Updated Solr connection configuration. - */ - protected function setReadOnlyMode(array $configuration) { - $configuration['overridden_by_acquia_search'] = ACQUIA_SEARCH_AUTO_OVERRIDE_READ_ONLY; - return $configuration; - } - - /** - * {@inheritdoc} - * - * Acquia-specific: 'admin/info/system' path is protected by Acquia. - * Use admin/system instead. - */ - public function pingServer() { - return $this->doPing(['handler' => 'admin/system'], 'server'); - } - - /** - * {@inheritdoc} - */ - public function buildConfigurationForm(array $form, FormStateInterface $form_state) { - $form = parent::buildConfigurationForm($form, $form_state); - unset($form['host']); - unset($form['port']); - unset($form['path']); - unset($form['core']); - return $form; - } - - /** - * {@inheritdoc} - */ - public function validateConfigurationForm(array &$form, FormStateInterface $form_state) { - // Turn off connection check of parent class. - } - - /** - * {@inheritdoc} - */ - protected function connect() { - if (!$this->solr) { - $this->solr = new Client(); - $this->configuration['port'] = ($this->configuration['scheme'] == 'https') ? 443 : 80; - $this->configuration['key'] = 'core'; - $this->solr->createEndpoint($this->configuration, TRUE); - $this->attachServerEndpoint(); - $this->eventDispatcher = $this->solr->getEventDispatcher(); - $plugin = new SearchSubscriber(); - $this->solr->registerPlugin('acquia_solr_search_subscriber', $plugin); - // Don't use curl. - $this->solr->setAdapter('Solarium\Core\Client\Adapter\Guzzle'); - } - } - - /** - * {@inheritdoc} - */ - protected function getServerUri() { - $this->connect(); - return $this->solr->getEndpoint('core')->getBaseUri(); - } - - /** - * {@inheritdoc} - * - * Avoid providing an valid Update query if module determines this server - * should be locked down (as indicated by the overridden_by_acquia_search - * server option). - * - * @throws \Exception - * If the Search API Server is currently in read-only mode. - */ - public function getUpdateQuery() { - $this->connect(); - $overridden = $this->solr->getEndpoint('server')->getOption('overridden_by_acquia_search'); - if ($overridden === ACQUIA_SEARCH_AUTO_OVERRIDE_READ_ONLY) { - $message = 'The Search API Server serving this index is currently in read-only mode.'; - \Drupal::logger('acquia search')->error($message); - throw new \Exception($message); - } - return $this->solr->createUpdate(); - } - - /** - * {@inheritdoc} - */ - public function getExtractQuery() { - $this->connect(); - $query = $this->solr->createExtract(); - $query->setHandler('extract/tika'); - return $query; - } - - /** - * {@inheritdoc} - */ - public function getMoreLikeThisQuery() { - $this->connect(); - $query = $this->solr->createMoreLikeThis(); - $query->setHandler('select'); - $query->addParam('qt', 'mlt'); - return $query; - } - - /** - * {@inheritdoc} - */ - public function getCoreLink() { - return $this->getServerLink(); - } - - /** - * {@inheritdoc} - */ - public function viewSettings() { - - $uri = Url::fromUri('https://docs.acquia.com/acquia-search/', ['absolute' => TRUE]); - $link = Link::fromTextAndUrl($this->t('Acquia Search'), $uri); - $message = $this->t('Search is provided by @acquia_search.', ['@acquia_search' => $link->toString()]); - - $this->messenger()->addStatus($message); - - return parent::viewSettings(); - } - -} diff --git a/docroot/modules/contrib/acquia_connector/acquia_search/src/PreferredSearchCoreService.php b/docroot/modules/contrib/acquia_connector/acquia_search/src/PreferredSearchCoreService.php deleted file mode 100644 index 21e0b3faa1..0000000000 --- a/docroot/modules/contrib/acquia_connector/acquia_search/src/PreferredSearchCoreService.php +++ /dev/null @@ -1,266 +0,0 @@ - 'useast11-c4.acquia-search.com', - * 'core_id' => 'WXYZ-12345.dev.mysitedev', - * ], - * ]. - */ - public function __construct($acquia_identifier, $ah_env, $sites_folder_name, $ah_db_name, array $available_cores) { - - $this->acquiaIdentifier = $acquia_identifier; - $this->ahEnv = $ah_env; - $this->sitesFolderName = $sites_folder_name; - $this->ahDbName = $ah_db_name; - $this->availableCores = $available_cores; - - } - - /** - * Returns expected core ID based on the current site configs. - * - * @return string - * Core ID. - */ - public function getPreferredCoreId() { - - $core = $this->getPreferredCore(); - - return $core['core_id']; - - } - - /** - * Returns expected core host based on the current site configs. - * - * @return string - * Hostname. - */ - public function getPreferredCoreHostname() { - - $core = $this->getPreferredCore(); - - return $core['balancer']; - } - - /** - * Determines whether the expected core ID matches any available core IDs. - * - * The list of available core IDs is set by Acquia and comes within the - * Acquia Subscription information. - * - * @return bool - * True if the expected core ID available to use with Acquia. - */ - public function isPreferredCoreAvailable() { - - return (bool) $this->getPreferredCore(); - - } - - /** - * Returns the preferred core from the list of available cores. - * - * @return array|null - * NULL or - * [ - * 'balancer' => 'useast11-c4.acquia-search.com', - * 'core_id' => 'WXYZ-12345.dev.mysitedev', - * ]. - */ - public function getPreferredCore() { - static $preferred_core; - - if (!empty($preferred_core)) { - return $preferred_core; - } - - $expected_cores = $this->getListOfPossibleCores(); - $available_cores_sorted = $this->sortCores($this->availableCores); - - foreach ($expected_cores as $expected_core) { - - foreach ($available_cores_sorted as $available_core) { - - if ($expected_core == $available_core['core_id']) { - $preferred_core = $available_core; - return $preferred_core; - } - - } - - } - } - - /** - * Sorts and returns search cores. - * - * It puts v3 cores first. - * - * @param array $cores - * Unsorted array of search cores. - * - * @return array - * Array of search cores. V3 cores in the begging of the result array. - */ - protected function sortCores(array $cores) { - - $v3_cores = array_filter($cores, function ($core) { - return $this->isCoreV3($core); - }); - - $regular_cores = array_filter($cores, function ($core) { - return !$this->isCoreV3($core); - }); - - return array_merge($v3_cores, $regular_cores); - } - - /** - * Determines whether given search core is version 3. - * - * @param array $core - * Search core. - * - * @return bool - * TRUE if the given search core is V3, FALSE otherwise. - */ - protected function isCoreV3(array $core) { - return !empty($core['version']) && $core['version'] === 'v3'; - } - - /** - * Returns URL for the preferred search core. - * - * @return string - * URL string, e.g. - * http://useast1-c1.acquia-search.com/solr/WXYZ-12345.dev.mysitedev - */ - public function getPreferredCoreUrl() { - - $core = $this->getPreferredCore(); - - return 'http://' . $core['balancer'] . '/solr/' . $core['core_id']; - - } - - /** - * Returns a list of all possible search core IDs. - * - * The core IDs are generated based on the current site configuration. - * - * @return array - * E.g. - * [ - * 'WXYZ-12345', - * 'WXYZ-12345.dev.mysitedev_folder1', - * 'WXYZ-12345.dev.mysitedev_db', - * ] - */ - public function getListOfPossibleCores() { - - $possible_core_ids = []; - - // The Acquia Search Solr module tries to use this core before any auto - // detected core in case if it's set in the site configuration. - if ($default_search_core = \Drupal::config('acquia_search.settings')->get('default_search_core')) { - $possible_core_ids[] = $default_search_core; - } - - // In index naming, we only accept alphanumeric chars. - $sites_foldername = preg_replace('/[^a-zA-Z0-9]+/', '', $this->sitesFolderName); - $ah_env = preg_replace('/[^a-zA-Z0-9]+/', '', $this->ahEnv); - - $context = [ - 'ah_env' => $ah_env, - 'ah_db_role' => $this->ahDbName, - 'identifier' => $this->acquiaIdentifier, - 'sites_foldername' => $sites_foldername, - ]; - - if ($ah_env) { - - // When there is an Acquia DB name defined, priority is to pick - // WXYZ-12345.[env].[db_name], then WXYZ-12345.[env].[site_foldername]. - // If we're sure this is prod, then 3rd option is WXYZ-12345. - // @TODO: Support for [id]_[env][sitename] cores? - if ($this->ahDbName) { - $possible_core_ids[] = $this->acquiaIdentifier . '.' . $ah_env . '.' . $this->ahDbName; - } - - $possible_core_ids[] = $this->acquiaIdentifier . '.' . $ah_env . '.' . $sites_foldername; - - } - - // For production-only, we allow auto-connecting to the suffix-less core - // as the fallback. - if (!empty($_SERVER['AH_PRODUCTION']) || !empty($_ENV['AH_PRODUCTION'])) { - $possible_core_ids[] = $this->acquiaIdentifier; - } - - // Let other modules alter the list possible cores. - \Drupal::moduleHandler()->alter('acquia_search_get_list_of_possible_cores', $possible_core_ids, $context); - - return $possible_core_ids; - - } - -} diff --git a/docroot/modules/contrib/acquia_connector/acquia_search/templates/search-api-index.html.twig b/docroot/modules/contrib/acquia_connector/acquia_search/templates/search-api-index.html.twig deleted file mode 100644 index fe94023922..0000000000 --- a/docroot/modules/contrib/acquia_connector/acquia_search/templates/search-api-index.html.twig +++ /dev/null @@ -1,26 +0,0 @@ -{# -/** - * @file - * Default theme implementation for displaying a search index. - * - * Available variables: - * - table: The index info table. - * - description: The index description. - * - index_progress: The index progress. - * - * @see template_preprocess_search_api_index() - * - * @ingroup themeable - */ -#} -{% if acquia_search_info_box %} - {{ acquia_search_info_box }} -{% endif %} -{% if description %} -{{ description|nl2br }}
-{% endif %} -{% if index_progress %} -{{ description|nl2br }}
-{% endif %} -{{ server_info_table }} diff --git a/docroot/modules/contrib/acquia_connector/acquia_search/tests/modules/acquia_search_test/acquia_search_test.info.yml b/docroot/modules/contrib/acquia_connector/acquia_search/tests/modules/acquia_search_test/acquia_search_test.info.yml deleted file mode 100644 index 9638d9078f..0000000000 --- a/docroot/modules/contrib/acquia_connector/acquia_search/tests/modules/acquia_search_test/acquia_search_test.info.yml +++ /dev/null @@ -1,11 +0,0 @@ -type: module -name: 'Acquia Search Test' -description: 'Utility module for Acquia Search tests.' -package: 'Acquia Connector' -core_version_requirement: ^8.8 || ^9 -hidden: true - -# Information added by Drupal.org packaging script on 2021-03-31 -version: '8.x-1.26' -project: 'acquia_connector' -datestamp: 1617213587 diff --git a/docroot/modules/contrib/acquia_connector/acquia_search/tests/modules/acquia_search_test/acquia_search_test.services.yml b/docroot/modules/contrib/acquia_connector/acquia_search/tests/modules/acquia_search_test/acquia_search_test.services.yml deleted file mode 100644 index d17de908c8..0000000000 --- a/docroot/modules/contrib/acquia_connector/acquia_search/tests/modules/acquia_search_test/acquia_search_test.services.yml +++ /dev/null @@ -1,5 +0,0 @@ -services: - acquia_search_test_event_subscriber: - class: Drupal\acquia_search_test\EventSubscriber\AcquiaSearchTestSubscriber - tags: - - {name: event_subscriber} diff --git a/docroot/modules/contrib/acquia_connector/acquia_search/tests/modules/acquia_search_test/src/EventSubscriber/AcquiaSearchTestSubscriber.php b/docroot/modules/contrib/acquia_connector/acquia_search/tests/modules/acquia_search_test/src/EventSubscriber/AcquiaSearchTestSubscriber.php deleted file mode 100644 index ddb902c88b..0000000000 --- a/docroot/modules/contrib/acquia_connector/acquia_search/tests/modules/acquia_search_test/src/EventSubscriber/AcquiaSearchTestSubscriber.php +++ /dev/null @@ -1,48 +0,0 @@ -getRequest()->query->get('env-overrides')) { - $allowed_keys = [ - 'AH_SITE_ENVIRONMENT', - 'AH_SITE_NAME', - 'AH_SITE_GROUP', - 'AH_PRODUCTION', - ]; - foreach ($allowed_keys as $key) { - $value = $event->getRequest()->query->get($key); - if (!empty($value)) { - \Drupal::messenger()->addMessage('acquia_search_test() module set $_ENV[' . $key . '] to ' . $value); - $_ENV[$key] = $value; - } - } - } - } - - /** - * {@inheritdoc} - */ - public static function getSubscribedEvents() { - // Add our event with a high priority (1000) to ensure it runs before - // the Solr connection is decided on. - $events[KernelEvents::REQUEST][] = ['checkForOverrides', 1000]; - return $events; - } - -} diff --git a/docroot/modules/contrib/acquia_connector/acquia_search/tests/src/Functional/AcquiaConnectorSearchOverrideTest.php b/docroot/modules/contrib/acquia_connector/acquia_search/tests/src/Functional/AcquiaConnectorSearchOverrideTest.php deleted file mode 100644 index cf837db03b..0000000000 --- a/docroot/modules/contrib/acquia_connector/acquia_search/tests/src/Functional/AcquiaConnectorSearchOverrideTest.php +++ /dev/null @@ -1,365 +0,0 @@ -id = 'TEST_AcquiaConnectorTestID'; - $this->key = 'TEST_AcquiaConnectorTestKey'; - $this->salt = $this->randomString(32); - $this->server = 'acquia_search_server'; - $this->index = 'acquia_search_index'; - - // Create a new content type. - $content_type = $this->drupalCreateContentType(); - - // Add a node of the new content type. - $node_data = [ - 'type' => $content_type->id(), - ]; - - $this->drupalCreateNode($node_data); - $this->connect(); - $this->setAvailableSearchCores(); - - } - - /** - * Main function that calls the rest of the tests (names start with case*) - */ - public function testOverrides() { - - $this->caseNonAcquiaHosted(); - $this->caseAcquiaHostingEnvironmentDetected(); - $this->caseAcquiaHostingEnvironmentDetectedNoAvailableCores(); - $this->caseAcquiaHostingProdEnvironmentDetectedWithoutProdFlag(); - $this->caseAcquiaHostingProdEnvironmentDetectedWithProdFlag(); - - } - - /** - * No Acquia hosting and DB detected - should override into Readonly. - */ - protected function caseNonAcquiaHosted() { - - $this->drupalGet('/admin/config/search/search-api/server/' . $this->server); - - $this->assertText('automatically enforced read-only mode on this connection.'); - - $delete_btn = $this->xpath('//input[@value="Delete all indexed data on this server"]'); - $this->assertEqual($delete_btn[0]->getAttribute('disabled'), 'disabled'); - - $this->drupalGet('/admin/config/search/search-api/index/' . $this->index); - - $this->assertText('automatically enforced read-only mode on this connection.'); - - } - - /** - * Acquia Dev hosting environment detected. - * - * Configs point to the index on the Dev environment. - */ - protected function caseAcquiaHostingEnvironmentDetected() { - - $overrides = [ - 'env-overrides' => 1, - 'AH_SITE_ENVIRONMENT' => 'dev', - 'AH_SITE_NAME' => 'testsite1dev', - 'AH_SITE_GROUP' => 'testsite1', - ]; - - $this->drupalGet('/admin/config/search/search-api/server/' . $this->server, ['query' => $overrides]); - - $this->assertNoText('automatically enforced read-only mode on this connection.'); - $this->assertNoText('The following Acquia Search Solr index IDs would have worked for your current environment'); - - $delete_btn = $this->xpath('//input[@value="Delete all indexed data on this server"]'); - $this->assertNotEqual($delete_btn[0]->getAttribute('disabled'), 'disabled'); - - $this->drupalGet('/admin/config/search/search-api/index/' . $this->index, ['query' => $overrides]); - - $this->assertNoText('automatically enforced read-only mode on this connection.'); - $this->assertNoText('The following Acquia Search Solr index IDs would have worked for your current environment'); - - } - - /** - * Acquia Test environment and a DB name. - * - * According to the mock, no cores available for the Test environment so it is - * read only. - */ - protected function caseAcquiaHostingEnvironmentDetectedNoAvailableCores() { - - $overrides = [ - 'env-overrides' => 1, - 'AH_SITE_ENVIRONMENT' => 'test', - 'AH_SITE_NAME' => 'testsite1test', - 'AH_SITE_GROUP' => 'testsite1', - ]; - - $this->drupalGet('/admin/config/search/search-api/server/' . $this->server, ['query' => $overrides]); - - $this->assertText('automatically enforced read-only mode on this connection.'); - - $this->assertText('The following Acquia Search Solr index IDs would have worked for your current environment'); - $this->assertText($this->id . '.test.' . $this->getDbName()); - $this->assertText($this->id . '.test.' . $this->getSiteFolderName()); - - $delete_btn = $this->xpath('//input[@value="Delete all indexed data on this server"]'); - $this->assertEqual($delete_btn[0]->getAttribute('disabled'), 'disabled'); - - $this->drupalGet('/admin/config/search/search-api/index/' . $this->index, ['query' => $overrides]); - - // On index edit page, check the read-only mode state. - $this->assertText('automatically enforced read-only mode on this connection.'); - - } - - /** - * Acquia Prod environment and a DB name but AH_PRODUCTION isn't set. - * - * So read only. - */ - protected function caseAcquiaHostingProdEnvironmentDetectedWithoutProdFlag() { - - $overrides = [ - 'env-overrides' => 1, - 'AH_SITE_ENVIRONMENT' => 'prod', - 'AH_SITE_NAME' => 'testsite1prod', - 'AH_SITE_GROUP' => 'testsite1', - ]; - - $this->drupalGet('/admin/config/search/search-api/server/' . $this->server, ['query' => $overrides]); - - $this->assertText('automatically enforced read-only mode on this connection.'); - - $this->assertText('The following Acquia Search Solr index IDs would have worked for your current environment'); - $this->assertText($this->id . '.prod.' . $this->getDbName()); - $this->assertText($this->id . '.prod.' . $this->getSiteFolderName()); - - $delete_btn = $this->xpath('//input[@value="Delete all indexed data on this server"]'); - $this->assertEqual($delete_btn[0]->getAttribute('disabled'), 'disabled'); - - $this->drupalGet('/admin/config/search/search-api/index/' . $this->index, ['query' => $overrides]); - - $this->assertText('automatically enforced read-only mode on this connection.'); - - } - - /** - * Acquia Prod environment and a DB name and AH_PRODUCTION is set. - * - * So it should override to connect to the prod index. - */ - protected function caseAcquiaHostingProdEnvironmentDetectedWithProdFlag() { - - $overrides = [ - 'env-overrides' => 1, - 'AH_SITE_ENVIRONMENT' => 'prod', - 'AH_SITE_NAME' => 'testsite1prod', - 'AH_SITE_GROUP' => 'testsite1', - 'AH_PRODUCTION' => 1, - ]; - - $this->drupalGet('/admin/config/search/search-api/server/' . $this->server, ['query' => $overrides]); - - $this->assertNoText('automatically enforced read-only mode on this connection.'); - $this->assertNoText('The following Acquia Search Solr index IDs would have worked for your current environment'); - - $delete_btn = $this->xpath('//input[@value="Delete all indexed data on this server"]'); - $this->assertNotEqual($delete_btn[0]->getAttribute('disabled'), 'disabled'); - - $this->drupalGet('/admin/config/search/search-api/index/' . $this->index, ['query' => $overrides]); - - $this->assertNoText('automatically enforced read-only mode on this connection.'); - $this->assertNoText('The following Acquia Search Solr index IDs would have worked for your current environment'); - - } - - /** - * Connect to the Acquia Subscription. - */ - protected function connect() { - \Drupal::configFactory()->getEditable('acquia_connector.settings')->set('spi.ssl_verify', FALSE)->save(); - \Drupal::configFactory()->getEditable('acquia_connector.settings')->set('spi.ssl_override', TRUE)->save(); - \Drupal::configFactory()->getEditable('acquia_connector.settings')->set('spi.server', 'http://mock-spi-server')->save(); - - $admin_user = $this->createAdminUser(); - $this->drupalLogin($admin_user); - - $edit_fields = [ - 'acquia_identifier' => $this->id, - 'acquia_key' => $this->key, - ]; - - $submit_button = 'Connect'; - $this->drupalPostForm('admin/config/system/acquia-connector/credentials', $edit_fields, $submit_button); - - \Drupal::service('module_installer')->install(['acquia_search']); - } - - /** - * Creates an admin user. - */ - protected function createAdminUser() { - - $permissions = [ - 'administer site configuration', - 'access administration pages', - 'access toolbar', - 'administer search_api', - ]; - return $this->drupalCreateUser($permissions); - - } - - /** - * Sets available search cores into the subscription heartbeat data. - * - * @param bool $no_db_flag - * Allows to set a limited number of search cores by excluding the one that - * contains the DB name. - */ - protected function setAvailableSearchCores($no_db_flag = FALSE) { - - $acquia_identifier = $this->id; - $solr_hostname = 'mock.acquia-search.com'; - $site_folder = $this->getSiteFolderName(); - $ah_db_name = $this->getDbName(); - - $core_with_folder_name = [ - 'balancer' => $solr_hostname, - 'core_id' => "{$acquia_identifier}.dev.{$site_folder}", - ]; - - $core_with_db_name = [ - 'balancer' => $solr_hostname, - 'core_id' => "{$acquia_identifier}.dev.{$ah_db_name}", - ]; - - $core_with_acquia_identifier = [ - 'balancer' => $solr_hostname, - 'core_id' => "{$acquia_identifier}", - ]; - - if ($no_db_flag) { - $available_cores = [ - $core_with_folder_name, - $core_with_acquia_identifier, - ]; - } - else { - $available_cores = [ - $core_with_folder_name, - $core_with_db_name, - $core_with_acquia_identifier, - ]; - } - - $storage = new Storage(); - $storage->setIdentifier($acquia_identifier); - - $subscription = \Drupal::state()->get('acquia_subscription_data'); - $subscription['heartbeat_data'] = ['search_cores' => $available_cores]; - - \Drupal::state() - ->set('acquia_subscription_data', $subscription); - - } - - /** - * Returns the folder name of the current site folder. - */ - protected function getSiteFolderName() { - - $conf_path = \Drupal::service('site.path'); - return substr($conf_path, strrpos($conf_path, '/') + 1); - - } - - /** - * Returns the current DB name. - */ - protected function getDbName() { - - $db_conn_options = Database::getConnection()->getConnectionOptions(); - return $db_conn_options['database']; - - } - -} diff --git a/docroot/modules/contrib/acquia_connector/acquia_search/tests/src/Functional/AcquiaConnectorSearchTest.php b/docroot/modules/contrib/acquia_connector/acquia_search/tests/src/Functional/AcquiaConnectorSearchTest.php deleted file mode 100644 index 5f31d1c66f..0000000000 --- a/docroot/modules/contrib/acquia_connector/acquia_search/tests/src/Functional/AcquiaConnectorSearchTest.php +++ /dev/null @@ -1,237 +0,0 @@ -id = $this->randomString(10); - $this->key = $this->randomString(32); - $this->salt = $this->randomString(32); - $this->server = 'acquia_search_server'; - $this->index = 'acquia_search_index'; - $this->settingsPath = 'admin/config/search/search-api'; - - // Create a new content type. - $content_type = $this->drupalCreateContentType(); - - // Add a node of the new content type. - $node_data = [ - 'type' => $content_type->id(), - ]; - - $this->drupalCreateNode($node_data); - $this->connect(); - } - - /** - * Connect. - */ - public function connect() { - \Drupal::configFactory()->getEditable('acquia_connector.settings')->set('spi.ssl_verify', FALSE)->save(); - \Drupal::configFactory()->getEditable('acquia_connector.settings')->set('spi.ssl_override', TRUE)->save(); - - $admin_user = $this->createAdminUser(); - $this->drupalLogin($admin_user); - - $edit_fields = [ - 'acquia_identifier' => $this->randomString(8), - 'acquia_key' => $this->randomString(8), - ]; - - $submit_button = 'Connect'; - $this->drupalPostForm('admin/config/system/acquia-connector/credentials', $edit_fields, $submit_button); - - \Drupal::service('module_installer')->install(['acquia_search']); - } - - /** - * Creates an admin user. - */ - public function createAdminUser() { - $permissions = [ - 'administer site configuration', - 'access administration pages', - 'access toolbar', - 'administer search_api', - ]; - return $this->drupalCreateUser($permissions); - } - - /** - * Tests Acquia Search environment creation. - * - * Tests executed: - * - Acquia Search environment is saved and loaded. - * - Acquia Search environment is set as the default environment when created. - * - The service class is set to AcquiaSearchService. - * - The environment's URL is built as expected. - */ - public function testEnvironment() { - // Connect site on key and id. - $this->drupalGet('admin/config/search/search-api'); - $environment = Server::load('acquia_search_server'); - // Check if the environment is a valid variable. - $this->assertTrue($environment, t('Acquia Search environment saved.'), 'Acquia Search'); - } - - /** - * Tests Environment UI. - * - * Tests that the Acquia Search environment shows up in the interface and that - * administrators cannot delete it. - * - * Tests executed: - * - Acquia Search environment is present in the UI. - * - Admin user receives 403 when attempting to delete the environment. - */ - public function testEnvironmentUi() { - $this->drupalGet($this->settingsPath); - // Check the Acquia Search Server is displayed. - $this->assertLinkByHref('/admin/config/search/search-api/server/' . $this->server, 0, t('The Acquia Search Server is displayed in the UI.')); - // Check the Acquia Search Index is displayed. - $this->assertLinkByHref('/admin/config/search/search-api/index/' . $this->index, 0, t('The Acquia Search Index is displayed in the UI.')); - // Delete the environment. - $this->drupalGet('/admin/config/search/search-api/server/' . $this->server . '/edit'); - $this->clickLink('Delete', 0); - $this->assertResponse(403, t('The Acquia Search environment cannot be deleted via the UI.')); - } - - /** - * Tests Acquia Search Server UI. - * - * Test executed: - * - Check backend server - * - Сheck all fields on the existence of - * - Admin user receives 403 when attempting to delete the server. - */ - public function testAcquiaSearchServerUi() { - $settings_path = 'admin/config/search/search-api'; - $this->drupalGet($settings_path); - $this->clickLink('Edit', 0); - // Check backend server. - $this->assertText('Backend', t('The Backend checkbox label exists'), 'Acquia Search'); - $this->assertFieldChecked('edit-backend-config-connector-solr-acquia-connector', t('Is used as a Solr Connector: Acquia'), 'Acquia Search'); - // Check field Solr server URI. - $this->assertText('Solr server URI', t('The Solr server URI label exist'), 'Acquia Search'); - // Check http-protocol. - $this->assertText('HTTP protocol', t('The HTTP protocol label exists'), 'Acquia Search'); - $this->assertOptionSelected('edit-backend-config-connector-config-scheme', 'http', t('By default selected HTTP protocol'), 'Acquia Search'); - // Check Solr host, port, path. - $this->assertNoText('Solr host', t('The Solr host label does not exist'), 'Acquia Search'); - $this->assertNoText('Solr port', t('The Solr port label does not exist'), 'Acquia Search'); - $this->assertNoText('Solr path', t('The Solr path label does not exist'), 'Acquia Search'); - // Check Basic HTTP authentication. - $this->assertNoText('Basic HTTP authentication', t('The basic HTTP authentication label does not exist'), 'Acquia Search'); - // Ckeck Solr version override. - $this->assertText('Solr version override', t('The selectbox "Solr version label" exist'), 'Acquia Search'); - $this->assertOptionByText('edit-backend-config-connector-config-workarounds-solr-version', 'Determine automatically', t('By default selected Solr version "Determine automatically"'), 'Acquia Search'); - // Ckeck HTTP method. - $this->assertText('HTTP method', t('The HTTP method label exist')); - $this->assertOptionSelected('edit-backend-config-connector-config-workarounds-http-method', 'AUTO', t('By default selected AUTO HTTP method'), 'Acquia Search'); - // Server save. - $this->drupalPostForm('/admin/config/search/search-api/server/' . $this->server . '/edit', [], 'Save'); - // Delete server. - $this->drupalGet('/admin/config/search/search-api/server/' . $this->server . '/delete'); - $this->assertResponse(403, t('The Acquia Search Server cannot be deleted via the UI.')); - } - - /** - * Tests Acquia Search Server UI. - * - * Test executed: - * - Сheck all fields on the existence of - * - Check fields used for indexing - * - Check save index - * - Admin user receives 403 when attempting to delete the index. - */ - public function testAcquiaSearchIndexUi() { - $settings_path = 'admin/config/search/search-api'; - $this->drupalGet($settings_path); - $this->clickLink('Edit', 1); - // Check field data types. - $this->assertText('Datasources', t('The Data types label exist'), 'Acquia Search'); - // Check default selected server. - $this->assertFieldChecked('edit-server-acquia-search-server', t('By default selected Acquia Search Server'), 'Acquia Search'); - // Check fields used for indexing. - $this->drupalGet('/admin/config/search/search-api/index/' . $this->index . '/fields'); - $this->assertOptionSelected('edit-fields-body-type', 'text', t('Body used for searching'), t('Acquia Search')); - $this->assertOptionSelected('edit-fields-title-type', 'text', t('Title used for searching'), 'Acquia Search'); - // Save index. - $this->drupalPostForm('/admin/config/search/search-api/index/' . $this->index . '/edit', [], 'Save'); - // Delete index. - $this->drupalGet('/admin/config/search/search-api/index/' . $this->index . '/delete'); - $this->assertResponse(403, t('The Acquia Search Server cannot be deleted via the UI.')); - } - -} diff --git a/docroot/modules/contrib/acquia_connector/acquia_search/tests/src/Kernel/AcquiaSearchOverrideTest.php b/docroot/modules/contrib/acquia_connector/acquia_search/tests/src/Kernel/AcquiaSearchOverrideTest.php deleted file mode 100644 index 49e6a3d2a9..0000000000 --- a/docroot/modules/contrib/acquia_connector/acquia_search/tests/src/Kernel/AcquiaSearchOverrideTest.php +++ /dev/null @@ -1,337 +0,0 @@ -installConfig(['acquia_connector']); - - $guzzle = $this->createMock(Client::class); - $guzzle->expects($this->any()) - ->method('__call') - ->with('get') - ->will($this->returnValue('')); - - $client_factory = $this->getMockBuilder('Drupal\Core\Http\ClientFactory')->disableOriginalConstructor()->getMock(); - $client_factory->expects($this->any()) - ->method('fromOptions') - ->will($this->returnValue($guzzle)); - - $this->container->set('http_client_factory', $client_factory); - - } - - /** - * No Acquia hosting and DB detected - should override into Readonly. - */ - public function testNonAcquiaHosted() { - - $this->setAvailableSearchCores(); - - $solr_connector = new SearchApiSolrAcquiaConnector([], 'foo', ['foo']); - $config = $solr_connector->defaultConfiguration(); - - $this->assertEquals(ACQUIA_SEARCH_AUTO_OVERRIDE_READ_ONLY, $config['overridden_by_acquia_search']); - - $this->assertGetUpdateQueryException($solr_connector); - - } - - /** - * Tests Acquia Dev hosting environment detected. - * - * Configs point to the index on the Dev environment. - */ - public function testAcquiaHostingEnvironmentDetected() { - - $_ENV['AH_SITE_ENVIRONMENT'] = 'dev'; - $_ENV['AH_SITE_NAME'] = 'testsite1dev'; - $_ENV['AH_SITE_GROUP'] = 'testsite1'; - - $this->setAvailableSearchCores(); - - $solr_connector = new SearchApiSolrAcquiaConnector([], 'foo', ['foo']); - $config = $solr_connector->defaultConfiguration(); - - $db_name = $this->getDbName(); - - $this->assertEquals(ACQUIA_SEARCH_OVERRIDE_AUTO_SET, $config['overridden_by_acquia_search']); - $this->assertEquals('WXYZ-12345.dev.' . $db_name, $config['index_id']); - - $this->assertGetUpdateQueryNoException($solr_connector); - - } - - /** - * Tests Acquia Dev hosting environment and search v3 core detected. - * - * Configs point to the index on the Dev environment and host pointing to - * search v3. - */ - public function testAcquiaSearchV3CoreDetected() { - - $_ENV['AH_SITE_ENVIRONMENT'] = 'dev'; - $_ENV['AH_SITE_NAME'] = 'testsite1dev'; - $_ENV['AH_SITE_GROUP'] = 'testsite1'; - - $this->setAvailableSearchCores(); - - $solr_connector = new SearchApiSolrAcquiaConnector([], 'foo', ['foo']); - $config = $solr_connector->defaultConfiguration(); - - $db_name = $this->getDbName(); - - $this->assertEquals(ACQUIA_SEARCH_OVERRIDE_AUTO_SET, $config['overridden_by_acquia_search']); - $this->assertEquals('WXYZ-12345.dev.' . $db_name, $config['index_id']); - $this->assertEquals('sr-dev.acquia.com', $config['host']); - - $this->assertGetUpdateQueryNoException($solr_connector); - - } - - /** - * Tests environment detection without cores available. - * - * Acquia Test environment and a DB name. According to the mock, no cores - * available for the Test environment so it is read only. - */ - public function testAcquiaHostingEnvironmentDetectedNoAvailableCores() { - - $_ENV['AH_SITE_ENVIRONMENT'] = 'test'; - $_ENV['AH_SITE_NAME'] = 'testsite1test'; - $_ENV['AH_SITE_GROUP'] = 'testsite1'; - - $this->setAvailableSearchCores(); - - $solr_connector = new SearchApiSolrAcquiaConnector([], 'foo', ['foo']); - $config = $solr_connector->defaultConfiguration(); - - $this->assertEquals(ACQUIA_SEARCH_AUTO_OVERRIDE_READ_ONLY, $config['overridden_by_acquia_search']); - - $this->assertGetUpdateQueryException($solr_connector); - - } - - /** - * Tests read-only. - * - * Acquia Prod environment and a DB name but AH_PRODUCTION isn't set - so read - * only. - */ - public function testAcquiaHostingProdEnvironmentDetectedWithoutProdFlag() { - - $_ENV['AH_SITE_ENVIRONMENT'] = 'prod'; - $_ENV['AH_SITE_NAME'] = 'testsite1prod'; - $_ENV['AH_SITE_GROUP'] = 'testsite1'; - - $this->setAvailableSearchCores(); - - $solr_connector = new SearchApiSolrAcquiaConnector([], 'foo', ['foo']); - $config = $solr_connector->defaultConfiguration(); - - $this->assertEquals(ACQUIA_SEARCH_AUTO_OVERRIDE_READ_ONLY, $config['overridden_by_acquia_search']); - - $this->assertGetUpdateQueryException($solr_connector); - - } - - /** - * Tests prod environment detection. - * - * Acquia Prod environment and a DB name and AH_PRODUCTION is set - so it - * should override to connect to the prod index. - */ - public function testAcquiaHostingProdEnvironmentDetectedWithProdFlag() { - - $_ENV['AH_SITE_ENVIRONMENT'] = 'prod'; - $_ENV['AH_SITE_NAME'] = 'testsite1prod'; - $_ENV['AH_SITE_GROUP'] = 'testsite1'; - - $_SERVER['AH_PRODUCTION'] = TRUE; - - $this->setAvailableSearchCores(); - - $solr_connector = new SearchApiSolrAcquiaConnector([], 'foo', ['foo']); - $config = $solr_connector->defaultConfiguration(); - - $this->assertEquals(ACQUIA_SEARCH_OVERRIDE_AUTO_SET, $config['overridden_by_acquia_search']); - $this->assertEquals('WXYZ-12345', $config['index_id']); - - $this->assertGetUpdateQueryNoException($solr_connector); - - } - - /** - * Test core override when a core with db name is unavailable. - * - * Tests that it selects the correct preferred search core ID for the - * override URL when limited number of core ID is available. - */ - public function testApacheSolrOverrideWhenCoreWithDbNameNotAvailable() { - - // When the core ID with the DB name in it is not available, it should - // override the URL value with the core ID that has the site folder name - // in it. - $_ENV['AH_SITE_ENVIRONMENT'] = 'dev'; - $_ENV['AH_SITE_NAME'] = 'testsite1dev'; - $_ENV['AH_SITE_GROUP'] = 'testsite1'; - - $this->setAvailableSearchCores(TRUE); - - $solr_connector = new SearchApiSolrAcquiaConnector([], 'foo', ['foo']); - $config = $solr_connector->defaultConfiguration(); - - $site_folder = $this->getSiteFolderName(); - - $this->assertEquals(ACQUIA_SEARCH_OVERRIDE_AUTO_SET, $config['overridden_by_acquia_search']); - $this->assertEquals('WXYZ-12345.dev.' . $site_folder, $config['index_id']); - - $this->assertGetUpdateQueryNoException($solr_connector); - - } - - /** - * Asserts if the Solr Connector getUpdateQuery() method throws exception. - * - * @param \Drupal\acquia_search\Plugin\SolrConnector\SearchApiSolrAcquiaConnector $solr_connector - * SearchApiSolrAcquiaConnector. - * - * @throws \Exception - * If the Search API Server is currently in read-only mode. - */ - protected function assertGetUpdateQueryException(SearchApiSolrAcquiaConnector $solr_connector) { - - // Set the expectation for exception. - $this->expectExceptionMessage('The Search API Server serving this index is currently in read-only mode.'); - - // Run the code that should throw the exception. - // If exception occurred - test passes. If no exception occurred - test - // fails. - $solr_connector->getUpdateQuery(); - - } - - /** - * Helper method to test SearchApiSolrAcquiaConnector::getUpdateQuery. - * - * Asserts if the Solr Connector getUpdateQuery() method doesn't throw - * an exception. - * - * @param \Drupal\acquia_search\Plugin\SolrConnector\SearchApiSolrAcquiaConnector $solr_connector - * SearchApiSolrAcquiaConnector. - */ - protected function assertGetUpdateQueryNoException(SearchApiSolrAcquiaConnector $solr_connector) { - - try { - $solr_connector->getUpdateQuery(); - } - catch (\Exception $e) { - $this->fail('getUpdateQuery() should not throw exception'); - } - - } - - /** - * Sets available search cores into the subscription heartbeat data. - * - * @param bool $no_db_flag - * Allows to set a limited number of search cores by excluding the one that - * contains the DB name. - */ - protected function setAvailableSearchCores($no_db_flag = FALSE) { - - $acquia_identifier = 'WXYZ-12345'; - $solr_hostname = 'mock.acquia-search.com'; - $site_folder = $this->getSiteFolderName(); - $ah_db_name = $this->getDbName(); - - $core_with_folder_name = [ - 'balancer' => $solr_hostname, - 'core_id' => "{$acquia_identifier}.dev.{$site_folder}", - ]; - - $core_with_db_name = [ - 'balancer' => $solr_hostname, - 'core_id' => "{$acquia_identifier}.dev.{$ah_db_name}", - ]; - - $core_with_acquia_identifier = [ - 'balancer' => $solr_hostname, - 'core_id' => "{$acquia_identifier}", - ]; - - $search_v3_core = [ - 'balancer' => 'sr-dev.acquia.com', - 'core_id' => "{$acquia_identifier}.dev.{$ah_db_name}", - 'version' => "v3", - ]; - - if ($no_db_flag) { - $available_cores = [ - $core_with_folder_name, - $core_with_acquia_identifier, - ]; - } - else { - $available_cores = [ - $core_with_folder_name, - $core_with_db_name, - $core_with_acquia_identifier, - $search_v3_core, - ]; - } - - $storage = new Storage(); - $storage->setIdentifier($acquia_identifier); - - \Drupal::state()->set('acquia_subscription_data', [ - 'heartbeat_data' => ['search_cores' => $available_cores], - ]); - - } - - /** - * Returns the folder name of the current site folder. - */ - protected function getSiteFolderName() { - $conf_path = \Drupal::service('site.path'); - return substr($conf_path, strrpos($conf_path, '/') + 1); - } - - /** - * Returns the current DB name. - */ - protected function getDbName() { - $db_conn_options = Database::getConnection()->getConnectionOptions(); - return $db_conn_options['database']; - } - -} diff --git a/docroot/modules/contrib/acquia_connector/acquia_search/tests/src/Unit/AcquiaSearchTest.php b/docroot/modules/contrib/acquia_connector/acquia_search/tests/src/Unit/AcquiaSearchTest.php deleted file mode 100644 index 5e1f38425d..0000000000 --- a/docroot/modules/contrib/acquia_connector/acquia_search/tests/src/Unit/AcquiaSearchTest.php +++ /dev/null @@ -1,182 +0,0 @@ -id = $this->randomMachineName(10); - // Most of the keys and salts have a 32char length. - $this->key = $this->randomMachineName(32); - $this->salt = $this->randomMachineName(32); - - // Include Solarium autoloader. - $dirs = drupal_phpunit_contrib_extension_directory_roots(); - $extensions = []; - foreach ($dirs as $path) { - $extensions += drupal_phpunit_find_extension_directories($path); - } - - unset($extensions); - - $this->searchSubscriber = new SearchSubscriber(); - $this->derivedKey = CryptConnector::createDerivedKey($this->salt, $this->id, $this->key); - } - - /** - * Check createDerivedKey. - */ - public function testCreateDerivedKey() { - // Mimic the hashing code in the API function. - $derivation_string = $this->id . 'solr' . $this->salt; - // str_pad extends the string with the same string in this case - // until it has filled 80 chars. - $derived_key = hash_hmac('sha1', str_pad($derivation_string, 80, $derivation_string), $this->key); - // $this->derivedKey is generated from the API function. - // @see setUp() - $this->assertEquals($derived_key, $this->derivedKey); - } - - /** - * Covers calculateAuthCookie. - * - * @covers ::calculateAuthCookie - */ - public function testCalculateAuthCookie() { - // Generate the expected hash. - $time = 1577635946; - $nonce = $this->randomMachineName(32); - $string = $time . $nonce . $this->randomMachineName(); - $hmac = hash_hmac('sha1', $time . $nonce . $string, $this->derivedKey); - - $calculateAuthCookie = $this->getMockBuilder('Drupal\acquia_search\EventSubscriber\SearchSubscriber') - ->setMethods(['getDerivedKey']) - ->getMock(); - $calculateAuthCookie->expects($this->any()) - ->method('getDerivedKey') - ->willReturn($this->derivedKey); - - $authenticator = $calculateAuthCookie->calculateAuthCookie($string, $nonce, $time, $this->derivedKey, $time); - preg_match('/acquia_solr_hmac=([a-zA-Z0-9]{40});/', $authenticator, $matches); - $this->assertEquals($hmac, $matches[1], 'HMAC API function generates the expected hmac hash.'); - preg_match('/acquia_solr_time=([0-9]{10});/', $authenticator, $matches); - $this->assertNotNull($matches, 'HMAC API function generates a timestamp.', 'Acquia Search'); - preg_match('/acquia_solr_nonce=([a-zA-Z0-9]{32});/', $authenticator, $matches); - $this->assertEquals($nonce, $matches[1], 'HMAC API function generates the expected nonce.'); - } - - /** - * Covers validateResponse. - * - * @covers ::validateResponse - */ - public function testValidResponse() { - // Generate the expected hash. - $nonce = $this->randomMachineName(32); - $string = $this->randomMachineName(32); - $hmac = hash_hmac('sha1', $nonce . $string, $this->derivedKey); - - // Pass the expected hmac digest, API function should return TRUE. - $valid = $this->searchSubscriber->validateResponse($hmac, $nonce, $string, $this->derivedKey); - $this->assertTrue($valid, 'Response flagged as valid when the expected hash is passed.'); - - // Invalidate the hmac digest, API function should return FALSE. - $bad_hmac = $hmac . 'invalidateHash'; - $invalid_hmac = $this->searchSubscriber->validateResponse($bad_hmac, $nonce, $string, $this->derivedKey); - $this->assertFalse($invalid_hmac, 'Response flagged as invalid when a malformed hash is passed.'); - - // Invalidate the nonce, API function should return FALSE. - $bad_nonce = $nonce . 'invalidateString'; - $invalid_nonce = $this->searchSubscriber->validateResponse($hmac, $bad_nonce, $string, $this->derivedKey); - $this->assertFalse($invalid_nonce, 'Response flagged as invalid when a malformed nonce is passed.'); - - // Invalidate the string, API function should return FALSE. - $bad_string = $string . 'invalidateString'; - $invalid_string = $this->searchSubscriber->validateResponse($hmac, $nonce, $bad_string, $this->derivedKey); - $this->assertFalse($invalid_string, 'Response flagged as invalid when a malformed string is passed.'); - - // Invalidate the derived key, API function should return FALSE. - $bad_key = $this->derivedKey . 'invalidateKey'; - $invalid_key = $this->searchSubscriber->validateResponse($hmac, $nonce, $string, $bad_key); - $this->assertFalse($invalid_key, 'Response flagged as invalid when a malformed derived key is passed.'); - } - - /** - * Covers extractHmac. - * - * @covers ::extractHmac - */ - public function testExtractHmacHeader() { - // Generate the expected hash. - $nonce = $this->randomMachineName(32); - $string = $this->randomMachineName(32); - $hmac = hash_hmac('sha1', $nonce . $string, $this->derivedKey); - - // Pass header with an expected pragma. - $headers = ['pragma/hmac_digest=' . $hmac . ';']; - $extracted = $this->searchSubscriber->extractHmac($headers); - $this->assertEquals($hmac, $extracted, 'The HMAC digest was extracted from the response header.'); - - // Pass header with a bad pragma. - $bad_headers1 = ['pragma/' . $this->randomMachineName()]; - $bad_extracted1 = $this->searchSubscriber->extractHmac($bad_headers1); - $this->assertEquals('', $bad_extracted1, 'Empty string returned by HMAC extraction function when an invalid pragma is passed.'); - - // Pass in junk as the header. - $bad_extracted2 = $this->searchSubscriber->extractHmac($this->randomMachineName()); - $this->assertEquals('', $bad_extracted2, 'Empty string returned by HMAC extraction function when an invalid header is passed.'); - } - -} diff --git a/docroot/modules/contrib/acquia_connector/acquia_search/tests/src/Unit/AcquiaSearchV3ApiClientTest.php b/docroot/modules/contrib/acquia_connector/acquia_search/tests/src/Unit/AcquiaSearchV3ApiClientTest.php deleted file mode 100644 index d8e4866bb1..0000000000 --- a/docroot/modules/contrib/acquia_connector/acquia_search/tests/src/Unit/AcquiaSearchV3ApiClientTest.php +++ /dev/null @@ -1,133 +0,0 @@ -searchV3Host = 'https://api.sr-dev.acquia.com'; - $this->searchV3ApiKey = 'XXXXXXXXXXyyyyyyyyyyXXXXXXXXXXyyyyyyyyyy'; - - $path = '/index/network_id/get_all?network_id=WXYZ-12345'; - $data = [ - 'host' => $this->searchV3Host, - 'headers' => [ - 'x-api-key' => $this->searchV3ApiKey, - ], - ]; - $uri = $data['host'] . $path; - $options = [ - 'headers' => $data['headers'], - 'body' => Json::encode($data), - ]; - - $json = '[{"name":"WXYZ-12345.dev.drupal8","host":"test.sr-dev.acquia.com"}]'; - $stream = $this->prophesize('Psr\Http\Message\StreamInterface'); - $stream->getSize()->willReturn(1000); - $stream->read(1000)->willReturn($json); - - $response = $this->prophesize('Psr\Http\Message\ResponseInterface'); - $response->getStatusCode()->willReturn(200); - $response->getBody()->willReturn($stream); - - $this->guzzleClient = $this->prophesize('\GuzzleHttp\Client'); - $this->guzzleClient->get($uri, $options)->willReturn($response); - - $this->cacheBackend = $this->prophesize('\Drupal\Core\Cache\CacheBackendInterface'); - } - - /** - * Tests call to search v3 api. - */ - public function testSearchV3ApiCall() { - $expected = [ - [ - 'balancer' => 'test.sr-dev.acquia.com', - 'core_id' => 'WXYZ-12345.dev.drupal8', - 'version' => 'v3', - ], - ]; - - $client = new AcquiaSearchV3ApiClient($this->searchV3Host, $this->searchV3ApiKey, $this->guzzleClient->reveal(), $this->cacheBackend->reveal()); - $this->assertEquals($expected, $client->getSearchV3Indexes('WXYZ-12345')); - $this->cacheBackend->set('acquia_search.v3indexes', $expected, time() + (24 * 60 * 60))->shouldHaveBeenCalledTimes(1); - } - - /** - * Test to validate cache. - */ - public function testSearchV3ApiCache() { - $expected = [ - [ - 'balancer' => 'test.sr-dev.acquia.com', - 'core_id' => 'WXYZ-12345.dev.drupal8', - 'version' => 'v3', - ], - ]; - $client = new AcquiaSearchV3ApiClient($this->searchV3Host, $this->searchV3ApiKey, $this->guzzleClient->reveal(), $this->cacheBackend->reveal()); - - $fresh_cache = (object) [ - 'data' => $expected, - 'expire' => time() + (24 * 60 * 60), - ]; - $this->cacheBackend->get('acquia_search.v3indexes')->willReturn($fresh_cache); - $client->getSearchV3Indexes('WXYZ-12345'); - - // New cache should not have been set when there is already a valid cache. - $this->cacheBackend->set('acquia_search.v3indexes', $expected, time() + (24 * 60 * 60))->shouldHaveBeenCalledTimes(0); - - $expired_cache = (object) [ - 'data' => $expected, - 'expire' => 0, - ]; - $this->cacheBackend->get('acquia_search.v3indexes')->willReturn($expired_cache); - $client->getSearchV3Indexes('WXYZ-12345'); - - // When the current cache value is expired, it should have set a new one. - $this->cacheBackend->set('acquia_search.v3indexes', $expected, time() + (24 * 60 * 60))->shouldHaveBeenCalledTimes(1); - } - -} diff --git a/docroot/modules/contrib/acquia_connector/bin/travis/script.sh b/docroot/modules/contrib/acquia_connector/bin/travis/script.sh index 69b4eea7b7..5df0408deb 100755 --- a/docroot/modules/contrib/acquia_connector/bin/travis/script.sh +++ b/docroot/modules/contrib/acquia_connector/bin/travis/script.sh @@ -15,4 +15,4 @@ cd ${TRAVIS_BUILD_DIR} if [ "$CUSTOM_TEST" == "css" ]; then csslint --config=vendor/drupal/core/assets/scaffold/files/csslintrc . -fi \ No newline at end of file +fi diff --git a/docroot/modules/contrib/acquia_connector/composer.json b/docroot/modules/contrib/acquia_connector/composer.json index f24d7d0bee..761a4166ea 100644 --- a/docroot/modules/contrib/acquia_connector/composer.json +++ b/docroot/modules/contrib/acquia_connector/composer.json @@ -4,35 +4,16 @@ "description": "Allows Drupal websites to connect with Acquia.", "license": "GPL-2.0-or-later", "require": { - "ext-json": "*", - "drupal/core": "^8.8 || ^9" - }, - "require-dev": { - "acquia/coding-standards": "^0.4.1", - "dealerdirect/phpcodesniffer-composer-installer": "^0.5.0", - "drupal/acquia_search": "*" + "ext-json": "*" }, "extra": { "branch-alias": { - "dev-8.x-1.x": "1.x-dev" + "dev-4.x": "4.x-dev" }, "drush": { "services": { - "drush.services.yml": "^9" + "drush.services.yml": ">=9" } - }, - "phpcodesniffer-search-depth": 4 - }, - "repositories": { - "acquia-search": { - "type": "path", - "url": "./acquia_search" - }, - "drupal": { - "type": "composer", - "url": "https://packages.drupal.org/8" } - }, - "minimum-stability": "dev", - "prefer-stable": true + } } diff --git a/docroot/modules/contrib/acquia_connector/config/install/acquia_connector.settings.yml b/docroot/modules/contrib/acquia_connector/config/install/acquia_connector.settings.yml index 2951871d4c..1109e77852 100644 --- a/docroot/modules/contrib/acquia_connector/config/install/acquia_connector.settings.yml +++ b/docroot/modules/contrib/acquia_connector/config/install/acquia_connector.settings.yml @@ -1,162 +1,5 @@ -subscription_name: '' debug: false cron_interval: 30 cron_interval_override: 0 -banner_service: 'https://insight.acquia.com/system/acquia-banner' hide_signup_messages: 0 -spi: - server: 'https://nspi.acquia.com' - ssl_override: false - ssl_verify: true - admin_priv: 1 - send_node_user: 1 - send_watchdog: 1 - use_cron: 1 - dynamic_banner: 0 - set_variables_override: 0 - set_variables_automatic: - - acquia_spi_set_variables_automatic - - error_level - - preprocess_js - - page_cache_maximum_age - - block_cache - - preprocess_css - - page_compression - - image_allow_insecure_derivatives - - googleanalytics_cache - - acquia_spi_send_node_user - - acquia_spi_admin_priv - - acquia_spi_send_watchdog - - acquia_spi_use_cron - ignored_set_variables: { } - saved_variables: - variables: { } - time: 0 - cron_interval: 28800 -mapping: - acquia_spi_send_node_user: - - acquia_connector.settings - - spi - - send_node_user - acquia_spi_admin_priv: - - acquia_connector.settings - - spi - - admin_priv - acquia_spi_send_watchdog: - - acquia_connector.settings - - spi - - send_watchdog - acquia_spi_use_cron: - - acquia_connector.settings - - spi - - use_cron - cache_backends: { } - cache_default_class: - - cache_classes - - cache - cache_inc: { } - cron_safe_threshold: - - system.cron - - threshold - - autorun - googleanalytics_cache: { } - error_level: - - system.logging - - error_level - preprocess_js: - - system.performance - - js - - preprocess - page_cache_maximum_age: - - system.performance - - cache - - page - - max_age - block_cache: { } - preprocess_css: - - system.performance - - css - - preprocess - page_compression: - - system.performance - - response - - gzip - cron_last: - - state - - system.cron_last - clean_url: { } - redirect_global_clean: { } - theme_zen_settings: { } - site_offline: - - state - - system.maintenance_mode - site_name: - - system.site - - name - user_register: - - user.settings - - register - user_signatures: - - user.settings - - signatures - user_admin_role: - - user.settings - - admin_role - user_email_verification: - - user.settings - - verify_mail - user_cancel_method: - - user.settings - - cancel_method - filter_fallback_format: - - filter.settings - - fallback_format - dblog_row_limit: - - dblog.settings - - row_limit - date_default_timezone: - - system.date - - timezone - - default - file_default_scheme: - - system.file - - default_scheme - install_profile: - - settings - - install_profile - maintenance_mode: - - state - - system.maintenance_mode - update_last_check: - - state - - update.last_check - site_default_country: - - system.date - - country - - default - acquia_spi_saved_variables: - - acquia_connector.settings - - spi - - saved_variables - acquia_spi_set_variables_automatic: - - acquia_connector.settings - - spi - - set_variables_automatic - acquia_spi_ignored_set_variables: - - acquia_connector.settings - - spi - - ignored_set_variables - acquia_spi_set_variables_override: - - acquia_connector.settings - - spi - - set_variables_override - fast_404: - - system.performance - - fast_404 - - enabled - allow_insecure_uploads: - - system.file - - allow_insecure_uploads - http_response_debug_cacheability_headers: - - container_parameter - - http.response.debug_cacheability_headers +third_party_settings: { } diff --git a/docroot/modules/contrib/acquia_connector/config/schema/acquia_connector.schema.yml b/docroot/modules/contrib/acquia_connector/config/schema/acquia_connector.schema.yml index 39e74f121e..a7ddb47578 100644 --- a/docroot/modules/contrib/acquia_connector/config/schema/acquia_connector.schema.yml +++ b/docroot/modules/contrib/acquia_connector/config/schema/acquia_connector.schema.yml @@ -4,9 +4,6 @@ acquia_connector.settings: type: config_object label: 'Acquia connector settings' mapping: - subscription_name: - type: string - label: 'Acquia subscription name' debug: type: boolean label: 'Is debug mode active' @@ -16,84 +13,9 @@ acquia_connector.settings: cron_interval_override: type: integer label: 'Override for cron_interval, minutes' - banner_service: - type: string - label: 'Acquia banner service uri' hide_signup_messages: type: integer label: 'Hide signup messages' - spi: - type: mapping - label: 'SPI' - mapping: - server: - type: string - label: 'Acquia SPI server' - ssl_override: - type: boolean - label: 'Do not require secure connection' - ssl_verify: - type: boolean - label: 'Verify SSL' - admin_priv: - type: integer - label: 'Allow collection of Admin privileges' - send_node_user: - type: integer - label: 'Allow collection of Nodes and users' - send_watchdog: - type: integer - label: 'Allow collection of Watchdog logs' - use_cron: - type: integer - label: 'Send via Drupal cron' - dynamic_banner: - type: integer - label: 'Receive updates from Acquia Subscription' - set_variables_override: - type: integer - label: 'Allow Insight to update list of approved variables' - set_variables_automatic: - type: ignore - ignored_set_variables: - type: sequence - label: 'List of ignored variables' - saved_variables: - type: mapping - label: 'Saved variables' - mapping: - variables: - type: sequence - label: 'Saved variables from the Acquia Subscription' - sequence: - - type: string - label: 'Variable' - time: - type: integer - label: 'Last update of variables' - cron_interval: - type: integer - label: 'Cron interval for failed logins data, sec' - site_environment: - type: string - label: 'Site environment' - site_machine_name: - type: string - label: 'Site machine name' - site_name: - type: string - label: 'Site name' - site_uuid: - type: string - label: 'Site UUID' - env_detection_enabled: - type: boolean - label: 'Is environment detection enabled?' - environment_changes: - type: ignore - environment_changed_action: - type: ignore - blocked: - type: ignore - mapping: + third_party_settings: type: ignore + label: 'Product specific settings' diff --git a/docroot/modules/contrib/acquia_connector/css/acquia_connector.icons.css b/docroot/modules/contrib/acquia_connector/css/acquia_connector.icons.css index e9b6dc40d1..b3f0092b8b 100644 --- a/docroot/modules/contrib/acquia_connector/css/acquia_connector.icons.css +++ b/docroot/modules/contrib/acquia_connector/css/acquia_connector.icons.css @@ -1,7 +1,17 @@ .toolbar-bar .acquia-inactive-subscription:before { - background-image: url(/core/misc/icons/e32700/error.svg); + background-image: url('../images/error.svg'); } .toolbar-bar .acquia-active-subscription:before { - background-image: url(/core/misc/icons/73b355/check.svg); + background-image: url('../images/check.svg'); +} + +.toolbar-bar .acquia-unknown-subscription:before { + background-image: url('../../../../core/misc/icons/e29700/warning.svg'); +} + +.toolbar .toolbar-bar .toolbar-tab .acquia-unknown-subscription, +.toolbar .toolbar-bar .toolbar-tab .acquia-active-subscription, +.toolbar .toolbar-bar .toolbar-tab .acquia-inactive-subscription { + z-index: 0; } diff --git a/docroot/modules/contrib/acquia_connector/drush.services.yml b/docroot/modules/contrib/acquia_connector/drush.services.yml deleted file mode 100644 index 431978b25f..0000000000 --- a/docroot/modules/contrib/acquia_connector/drush.services.yml +++ /dev/null @@ -1,5 +0,0 @@ -services: - acquia_connector.commands: - class: \Drupal\acquia_connector\Commands\AcquiaConnectorCommands - tags: - - { name: drush.command } diff --git a/docroot/modules/contrib/acquia_connector/images/check.svg b/docroot/modules/contrib/acquia_connector/images/check.svg new file mode 100644 index 0000000000..566cbc4c8e --- /dev/null +++ b/docroot/modules/contrib/acquia_connector/images/check.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docroot/modules/contrib/acquia_connector/images/error.svg b/docroot/modules/contrib/acquia_connector/images/error.svg new file mode 100644 index 0000000000..151a1e67c9 --- /dev/null +++ b/docroot/modules/contrib/acquia_connector/images/error.svg @@ -0,0 +1 @@ + diff --git a/docroot/modules/contrib/acquia_connector/migrations/d7_acquia_connector_settings.yml b/docroot/modules/contrib/acquia_connector/migrations/d7_acquia_connector_settings.yml new file mode 100644 index 0000000000..41a9985e9d --- /dev/null +++ b/docroot/modules/contrib/acquia_connector/migrations/d7_acquia_connector_settings.yml @@ -0,0 +1,21 @@ +id: d7_acquia_connector_settings +label: 'Acquia Connector Configurations' +migration_tags: + - Drupal 7 + - Configuration +source: + plugin: variable + variables: + - acquia_agent_debug + - acquia_spi_cron_interval + - acquia_spi_cron_interval_override + - acquia_agent_hide_signup_messages + source_module: acquia_agent +process: + debug: acquia_agent_debug + cron_interval: acquia_spi_cron_interval + cron_interval_override: acquia_spi_cron_interval_override + hide_signup_messages: acquia_agent_hide_signup_messages +destination: + plugin: config + config_name: acquia_connector.settings diff --git a/docroot/modules/contrib/acquia_connector/migrations/d7_acquia_connector_subscription_data.yml b/docroot/modules/contrib/acquia_connector/migrations/d7_acquia_connector_subscription_data.yml new file mode 100644 index 0000000000..029926397c --- /dev/null +++ b/docroot/modules/contrib/acquia_connector/migrations/d7_acquia_connector_subscription_data.yml @@ -0,0 +1,17 @@ +id: d7_acquia_connector_subscription_data +label: 'Acquia Subscription Data' +migration_tags: + - Drupal 7 + - Configuration +source: + plugin: variable + variables: + - acquia_subscription_data + source_module: acquia_agent +process: + acquia_subscription_data: acquia_subscription_data +destination: + plugin: state +migration_dependencies: + required: + - d7_acquia_connector_settings diff --git a/docroot/modules/contrib/acquia_connector/sonar-project.properties b/docroot/modules/contrib/acquia_connector/sonar-project.properties new file mode 100644 index 0000000000..d172ab0296 --- /dev/null +++ b/docroot/modules/contrib/acquia_connector/sonar-project.properties @@ -0,0 +1,8 @@ +sonar.sources=. +sonar.tests=tests/src +sonar.exclusions=tests/src/**/* +# Set to value of CI_WORKSPACE in Dockerfile.ci +sonar.projectBaseDir=/acquia/acquia_connector +sonar.php.file.suffixes=php,inc,module,theme,install,profile,engine +sonar.php.coverage.reportPaths=clover.xml +sonar.php.tests.reportPath=junit.xml diff --git a/docroot/modules/contrib/acquia_connector/src/AcquiaConnectorEvents.php b/docroot/modules/contrib/acquia_connector/src/AcquiaConnectorEvents.php new file mode 100644 index 0000000000..7dfaa654b3 --- /dev/null +++ b/docroot/modules/contrib/acquia_connector/src/AcquiaConnectorEvents.php @@ -0,0 +1,49 @@ +csrfToken = $csrf_token; + $this->clientFactory = $client_factory; + $this->session = $session; + $this->keyValueExpirableFactory = $key_value_expirable_factory; + $this->state = $state; + $this->time = $time; + } + + /** + * Get the authorization URL. + * + * @return \Drupal\Core\Url + * The URL. + */ + public function getAuthUrl(): Url { + $params = [ + 'response_type' => 'code', + 'client_id' => self::CLIENT_ID, + 'redirect_uri' => Url::fromRoute('acquia_connector.auth.return')->setAbsolute()->toString(), + 'state' => $this->getStateToken(), + 'code_challenge' => Crypt::hashBase64($this->getPkceCode()), + 'code_challenge_method' => 'S256', + ]; + $uri = (new Uri()) + ->withScheme('https') + ->withHost(self::getIdpHost()) + ->withPath('/api/auth/oauth/authorize'); + return Url::fromUri( + (string) Uri::withQueryValues($uri, $params) + ); + } + + /** + * Finalizes the OAuth process. + * + * @param string $code + * The authorization code. + * @param string $state + * The state token. + */ + public function finalize(string $code, string $state): void { + if ($state !== $this->getStateToken()) { + throw new \RuntimeException('Could not verify state'); + } + $client = $this->clientFactory->fromOptions([ + 'base_uri' => (new Uri()) + ->withScheme('https') + ->withHost(self::getIdpHost()), + ]); + $response = $client->post('/api/auth/oauth/token', [ + 'json' => [ + 'grant_type' => 'authorization_code', + 'code' => $code, + 'client_id' => self::CLIENT_ID, + 'redirect_uri' => Url::fromRoute('acquia_connector.auth.return')->setAbsolute()->toString(), + 'code_verifier' => $this->getPkceCode(), + ], + ]); + $this->keyValueExpirableFactory->get('acquia_connector')->setWithExpire( + 'oauth', + Json::decode((string) $response->getBody()), + 5400 + ); + $this->session->remove(self::PKCE_KEY); + } + + /** + * Refreshes the access token. + */ + public function refreshAccessToken(): void { + $access_data = $this->getAccessToken(); + $client = $this->clientFactory->fromOptions([ + 'base_uri' => (new Uri()) + ->withScheme('https') + ->withHost(self::getIdpHost()), + ]); + $response = $client->post('/api/auth/oauth/token', [ + 'json' => [ + 'grant_type' => 'refresh_token', + 'refresh_token' => $access_data['refresh_token'] ?? '', + 'client_id' => self::CLIENT_ID, + ], + ]); + $access_data = Json::decode((string) $response->getBody()); + $this->keyValueExpirableFactory->get('acquia_connector')->setWithExpire( + 'oauth', + $access_data, + 5400 + ); + } + + /** + * Gets the access token data. + * + * @phpstan-return array{access_token: string, refresh_token: string, expires: int} + * + * @return array|null + * The access token data, or NULL if not set. + */ + public function getAccessToken(): ?array { + return $this->keyValueExpirableFactory->get('acquia_connector')->get('oauth'); + } + + /** + * Cron refresh of the access token. + */ + public function cronRefresh(): void { + $last_refresh_timestamp = $this->state->get('acquia_connector.oauth_refresh.timestamp', 0); + if ($this->time->getCurrentTime() - $last_refresh_timestamp > 1800) { + try { + $this->refreshAccessToken(); + } + catch (RequestException $exception) { + } finally { + $this->state->set('acquia_connector.oauth_refresh.timestamp', $this->time->getRequestTime()); + } + } + } + + /** + * Gets the state token value used in OAuth authorization. + * + * @return string + * The state token. + */ + private function getStateToken(): string { + return Crypt::hashBase64($this->csrfToken->get(self::CSRF_TOKEN_KEY)); + } + + /** + * Get the PKCE code used in the OAuth authorization. + * + * @return string + * The PKCE code. + */ + private function getPkceCode(): string { + if (!$this->session->has(self::PKCE_KEY)) { + $this->session->set(self::PKCE_KEY, Crypt::randomBytesBase64(64)); + } + return $this->session->get(self::PKCE_KEY); + } + + /** + * Get the identity provider host. + * + * @return string + * The host. + */ + private static function getIdpHost(): string { + return CoreSettings::get('acquia_connector.idp_host', 'accounts.acquia.com'); + } + +} diff --git a/docroot/modules/contrib/acquia_connector/src/AutoConnector.php b/docroot/modules/contrib/acquia_connector/src/AutoConnector.php deleted file mode 100644 index dbb8517581..0000000000 --- a/docroot/modules/contrib/acquia_connector/src/AutoConnector.php +++ /dev/null @@ -1,78 +0,0 @@ -subscription = $subscription; - $this->storage = $storage; - $this->globalConfig = $global_config; - } - - /** - * Ensures a connection to Acquia Subscription. - * - * @return mixed - * False or whatever is returned by Subscription::update. - */ - public function connectToAcquia() { - - if ($this->subscription->hasCredentials()) { - return FALSE; - } - - if (empty($this->globalConfig['ah_network_key'])) { - return FALSE; - } - - if (empty($this->globalConfig['ah_network_identifier'])) { - return FALSE; - } - - $this->storage->setKey($this->globalConfig['ah_network_key']); - $this->storage->setIdentifier($this->globalConfig['ah_network_identifier']); - - return $this->subscription->update(); - - } - -} diff --git a/docroot/modules/contrib/acquia_connector/src/Client.php b/docroot/modules/contrib/acquia_connector/src/Client.php deleted file mode 100644 index 10fc65662a..0000000000 --- a/docroot/modules/contrib/acquia_connector/src/Client.php +++ /dev/null @@ -1,419 +0,0 @@ -config = $config->get('acquia_connector.settings'); - $this->server = $this->config->get('spi.server'); - $this->state = $state; - - $this->headers = [ - 'Content-Type' => 'application/json', - 'Accept' => 'application/json', - ]; - - $this->client = \Drupal::service('http_client_factory')->fromOptions( - [ - 'verify' => (boolean) $this->config->get('spi.ssl_verify'), - 'http_errors' => FALSE, - ] - ); - } - - /** - * Get account settings to use for creating request authorizations. - * - * @param string $email - * Acquia account email. - * @param string $password - * Plain-text password for Acquia account. Will be hashed for communication. - * - * @return array|false - * Credentials array or FALSE. - * - * @throws \Drupal\acquia_connector\ConnectorException - */ - public function getSubscriptionCredentials($email, $password) { - $body = ['email' => $email]; - $authenticator = $this->buildAuthenticator($email, \Drupal::time()->getRequestTime(), ['rpc_version' => ACQUIA_CONNECTOR_ACQUIA_SPI_DATA_VERSION]); - $data = [ - 'body' => $body, - 'authenticator' => $authenticator, - ]; - - // Don't use nspiCall() - key is not defined yet. - $communication_setting = $this->request('POST', '/agent-api/subscription/communication', $data); - if ($communication_setting) { - $crypt_pass = new CryptConnector($communication_setting['algorithm'], $password, $communication_setting['hash_setting'], $communication_setting['extra_md5']); - $pass = $crypt_pass->cryptPass(); - - $body = [ - 'email' => $email, - 'pass' => $pass, - 'rpc_version' => ACQUIA_CONNECTOR_ACQUIA_SPI_DATA_VERSION, - ]; - $authenticator = $this->buildAuthenticator($pass, \Drupal::time()->getRequestTime(), ['rpc_version' => ACQUIA_CONNECTOR_ACQUIA_SPI_DATA_VERSION]); - $data = [ - 'body' => $body, - 'authenticator' => $authenticator, - ]; - - // Don't use nspiCall() - key is not defined yet. - $response = $this->request('POST', '/agent-api/subscription/credentials', $data); - if ($response['body']) { - return $response['body']; - } - } - return FALSE; - } - - /** - * Get Acquia subscription from Acquia. - * - * @param string $id - * Acquia Subscription ID. - * @param string $key - * Acquia Subscription key. - * @param array $body - * Optional. - * - * @return array|false - * Acquia Subscription array or FALSE. - * - * @throws \Exception - */ - public function getSubscription($id, $key, array $body = []) { - $body['identifier'] = $id; - // There is an identifier and key, so attempt communication. - $subscription = []; - $this->state->set('acquia_subscription_data.timestamp', \Drupal::time()->getRequestTime()); - - // Include version number information. - acquia_connector_load_versions(); - if (IS_ACQUIA_DRUPAL) { - $body['version'] = ACQUIA_DRUPAL_VERSION; - $body['series'] = ACQUIA_DRUPAL_SERIES; - $body['branch'] = ACQUIA_DRUPAL_BRANCH; - $body['revision'] = ACQUIA_DRUPAL_REVISION; - } - - // Include Acquia Search for Search API module version number. - if (\Drupal::moduleHandler()->moduleExists('acquia_search')) { - foreach (['acquia_search', 'search_api', 'search_api_solr'] as $name) { - $info = \Drupal::service('extension.list.module')->getExtensionInfo($name); - // Send the version, or at least the core compatibility as a fallback. - $body['search_version'][$name] = isset($info['version']) ? (string) $info['version'] : (string) $info['core_version_requirement']; - } - } - - try { - $response = $this->nspiCall('/agent-api/subscription', $body); - if (!empty($response['result']['authenticator']) && $this->validateResponse($key, $response['result'], $response['authenticator'])) { - $subscription += $response['result']['body']; - // Subscription activated. - if (is_numeric($this->state->get('acquia_subscription_data')) && is_array($response['result']['body'])) { - \Drupal::moduleHandler()->invokeAll('acquia_subscription_status', [$subscription]); - $this->state->set('acquia_subscription_data', $subscription); - } - return $subscription; - } - } - catch (ConnectorException $e) { - $this->messenger()->addError(t('Error occurred while retrieving Acquia subscription information. See logs for details.')); - if ($e->isCustomized()) { - $this->getLogger('acquia connector') - ->error($e->getCustomMessage() . '. Response data: @data', ['@data' => json_encode($e->getAllCustomMessages())]); - } - else { - $this->getLogger('acquia connector')->error($e->getMessage()); - } - throw $e; - } - - return FALSE; - } - - /** - * Get Acquia subscription from Acquia. - * - * @param string $id - * Acquia Subscription ID. - * @param string $key - * Acquia Subscription key. - * @param array $body - * Optional. - * - * @return array|false - * Response result or FALSE. - */ - public function sendNspi($id, $key, array $body = []) { - $body['identifier'] = $id; - - try { - $response = $this->nspiCall('/spi-api/site', $body); - if (!empty($response['result']['authenticator']) && $this->validateResponse($key, $response['result'], $response['authenticator'])) { - return $response['result']; - } - } - catch (ConnectorException $e) { - $this->getLogger('acquia connector')->error('Error: ' . $e->getCustomMessage()); - } - return FALSE; - } - - /** - * Get SPI definition. - * - * @param string $apiEndpoint - * API endpoint. - * - * @return array|bool - * Definition array or FALSE. - */ - public function getDefinition($apiEndpoint) { - try { - return $this->request('GET', $apiEndpoint, []); - } - catch (ConnectorException $e) { - $this->getLogger('acquia connector')->error($e->getCustomMessage()); - } - return FALSE; - } - - /** - * Validate the response authenticator. - * - * @param string $key - * Acquia Subscription key. - * @param array $response - * Response. - * @param array $requestAuthenticator - * Authenticator array. - * - * @return bool - * TRUE if valid response, FALSE otherwise. - */ - protected function validateResponse($key, array $response, array $requestAuthenticator) { - $responseAuthenticator = $response['authenticator']; - if (!($requestAuthenticator['nonce'] === $responseAuthenticator['nonce'] && $requestAuthenticator['time'] < $responseAuthenticator['time'])) { - return FALSE; - } - $hash = $this->hash($key, $responseAuthenticator['time'], $responseAuthenticator['nonce']); - return ($hash === $responseAuthenticator['hash']); - } - - /** - * Create and send a request. - * - * @param string $method - * Method to call. - * @param string $path - * Path to call. - * @param array $data - * Data to send. - * - * @return array|false - * Response array or FALSE. - * - * @throws ConnectorException - */ - protected function request($method, $path, array $data) { - $uri = $this->server . $path; - $options = [ - 'headers' => $this->headers, - 'body' => Json::encode($data), - ]; - - try { - switch ($method) { - case 'GET': - $response = $this->client->get($uri); - $status_code = $response->getStatusCode(); - $stream_size = $response->getBody()->getSize(); - $data = Json::decode($response->getBody()->read($stream_size)); - - if ($status_code < 200 || $status_code > 299) { - throw new ConnectorException($data['message'], $data['code'], $data); - } - - return $data; - - case 'POST': - $response = $this->client->post($uri, $options); - $status_code = $response->getStatusCode(); - $stream_size = $response->getBody()->getSize(); - $data = Json::decode($response->getBody()->read($stream_size)); - - if ($status_code < 200 || $status_code > 299) { - throw new ConnectorException($data['message'], $data['code'], $data); - } - - return $data; - - } - } - catch (RequestException $e) { - throw new ConnectorException($e->getMessage(), $e->getCode()); - } - - return FALSE; - } - - /** - * Build authenticator to sign requests to the Acquia. - * - * @param string $key - * Secret key to use for signing the request. - * @param int $request_time - * Such as from \Drupal::time()->getRequestTime(). - * @param array $params - * Optional parameters to include. - * 'identifier' - Network Identifier. - * - * @return array - * Authenticator array. - */ - protected function buildAuthenticator($key, int $request_time, array $params = []) { - $authenticator = []; - if (isset($params['identifier'])) { - // Put Subscription ID in authenticator but do not use in hash. - $authenticator['identifier'] = $params['identifier']; - unset($params['identifier']); - } - $nonce = $this->getNonce(); - $authenticator['time'] = $request_time; - $authenticator['hash'] = $this->hash($key, $request_time, $nonce); - $authenticator['nonce'] = $nonce; - - return $authenticator; - } - - /** - * Calculates a HMAC-SHA1 according to RFC2104. - * - * @param string $key - * Key. - * @param int $time - * Timestamp. - * @param string $nonce - * Nonce. - * - * @return string - * HMAC-SHA1 hash. - * - * @see http://www.ietf.org/rfc/rfc2104.txt - */ - protected function hash($key, $time, $nonce) { - $string = $time . ':' . $nonce; - return CryptConnector::acquiaHash($key, $string); - } - - /** - * Get a random base 64 encoded string. - * - * @return string - * Random base 64 encoded string. - */ - protected function getNonce() { - return Crypt::hashBase64(uniqid(mt_rand(), TRUE) . random_bytes(55)); - } - - /** - * Prepare and send a REST request to Acquia with an authenticator. - * - * @param string $method - * HTTP method. - * @param array $params - * Parameters to pass to the NSPI. - * @param string $key - * Acquia Key or NULL. - * - * @return array - * NSPI response. - * - * @throws ConnectorException - */ - public function nspiCall($method, array $params, $key = NULL) { - if (empty($key)) { - $storage = new Storage(); - $key = $storage->getKey(); - } - // Used in HMAC validation. - $params['rpc_version'] = ACQUIA_CONNECTOR_ACQUIA_SPI_DATA_VERSION; - $ip = \Drupal::request()->server->get('SERVER_ADDR', ''); - $host = \Drupal::request()->server->get('HTTP_HOST', ''); - $ssl = \Drupal::request()->isSecure(); - $data = [ - 'authenticator' => $this->buildAuthenticator($key, \Drupal::time()->getRequestTime(), $params), - 'ip' => $ip, - 'host' => $host, - 'ssl' => $ssl, - 'body' => $params, - ]; - $data['result'] = $this->request('POST', $method, $data); - return $data; - } - -} diff --git a/docroot/modules/contrib/acquia_connector/src/Client/ClientFactory.php b/docroot/modules/contrib/acquia_connector/src/Client/ClientFactory.php new file mode 100644 index 0000000000..8d7cca8b41 --- /dev/null +++ b/docroot/modules/contrib/acquia_connector/src/Client/ClientFactory.php @@ -0,0 +1,180 @@ +loggerFactory = $logger_factory; + $this->moduleList = $module_list; + $this->time = $date_time; + $this->httpClientFactory = $client_factory; + $this->authService = $auth_service; + $this->stack = $stack; + } + + /** + * Get a client for Cloud API. + * + * @return \GuzzleHttp\Client + * The client. + */ + public function getCloudApiClient(): Client { + if (!$this->authService->getAccessToken()) { + throw new ConnectorException("Missing access token.", 403); + } + + // Do not influence global handler stack. + $stack = clone $this->stack; + $stack->after('prepare_body', Middleware::mapRequest(function (RequestInterface $request) { + $access_data = $this->authService->getAccessToken(); + if (isset($access_data['access_token'])) { + return $request->withHeader('Authorization', 'Bearer ' . $access_data['access_token']); + } + return $request; + })); + $stack->after('prepare_body', function (callable $next) { + return function (RequestInterface $request, array $options = []) use ($next) { + $access_data = $this->authService->getAccessToken(); + if (!isset($access_data['access_token'])) { + return $next($request, $options); + } + + if (!isset($options['retries'])) { + $options['retries'] = 0; + } + return $next($request, $options)->then( + function ($value) use ($next, $request, $options) { + if ($options['retries'] > 0) { + return $value; + } + if (!$value instanceof ResponseInterface) { + return $value; + } + // The status should be 401 for an expired access token, but + // the Cloud API returns 403. We handle both status codes. + // @see https://www.rfc-editor.org/rfc/rfc6750#section-3.1. + if (!in_array($value->getStatusCode(), [401, 403], TRUE)) { + return $value; + } + $this->authService->refreshAccessToken(); + return $next($request, $options); + }, + ); + }; + }); + return $this->httpClientFactory->fromOptions([ + 'base_uri' => (new Uri()) + ->withScheme('https') + ->withHost(CoreSettings::get('acquia_connector.cloud_api_host', 'cloud.acquia.com')), + 'headers' => [ + 'User-Agent' => $this->getClientUserAgent(), + 'Accept' => 'application/json, version=2', + ], + 'handler' => $stack, + ]); + } + + /** + * Returns Client's user agent. + * + * @return string + * User Agent. + */ + protected function getClientUserAgent() { + static $agent; + if ($agent === NULL) { + // Find out the module version in use. + $module_info = $this->moduleList->getExtensionInfo('acquia_connector'); + $module_version = $module_info['version'] ?? '0.0.0'; + $drupal_version = $module_info['core'] ?? '0.0.0'; + + $agent = 'AcquiaConnector/' . $drupal_version . '-' . $module_version; + } + return $agent; + } + +} diff --git a/docroot/modules/contrib/acquia_connector/src/Commands/AcquiaConnectorCommands.php b/docroot/modules/contrib/acquia_connector/src/Commands/AcquiaConnectorCommands.php deleted file mode 100644 index 65f7650333..0000000000 --- a/docroot/modules/contrib/acquia_connector/src/Commands/AcquiaConnectorCommands.php +++ /dev/null @@ -1,162 +0,0 @@ - NULL, 'format' => NULL]) { - - $raw_spi = $this->drushSpiGet(); - switch ($options['format']) { - case 'json': - $spi = Json::encode($raw_spi); - break; - - case 'var_dump': - case 'var_export': - $spi = var_export($raw_spi, TRUE); - break; - - case 'print_r': - default: - $spi = print_r($raw_spi, TRUE); - break; - } - - if (!$options['outfile']) { - $this->output->writeln($spi); - return; - } - - $file = $options['outfile']; - // Path is relative. - if (strpos($file, DIRECTORY_SEPARATOR) !== 0) { - $file = ($_SERVER['PWD'] ?? getcwd()) . DIRECTORY_SEPARATOR . $file; - } - if (file_put_contents($file, $spi)) { - $this->logger->info('SPI Data written to @outfile.', ['@outfile' => realpath($file)]); - } - else { - $this->logger->error('Unable to write SPI Data into @outfile.', ['@outfile' => realpath($file)]); - } - - } - - /** - * A command callback and drush wrapper for custom test validation. - * - * @command acquia:connector:spi-test-validate - * - * @aliases acquia:connector:spi-tv - * - * @usage acquia:connector:spi-test-validate - * Perform a validation check on any modules with Acquia SPI custom tests. - * - * @validate-module-enabled acquia_connector - */ - public function customTestValidate() { - - $modules = \Drupal::moduleHandler()->getImplementations('acquia_connector_spi_test'); - if (!$modules) { - $this->output->writeln((string) dt('No Acquia SPI custom tests were detected.')); - return; - } - - $this->output->writeln((string) dt('Acquia SPI custom tests were detected in: @modules ' . PHP_EOL, ['@modules' => implode(', ', $modules)])); - - $pass = []; - $failure = []; - foreach ($modules as $module) { - $function = $module . '_acquia_connector_spi_test'; - if (!function_exists($function)) { - continue; - } - - $testStatus = new TestStatusController(); - $result = $testStatus->testValidate($function()); - - if (!$result['result']) { - $failure[] = $module; - $this->output->writeln((string) dt("[FAILED] Validation failed for '@module' and has been logged.", ['@module' => $module])); - - foreach ($result['failure'] as $test_name => $test_failures) { - foreach ($test_failures as $test_param => $test_value) { - $variables = [ - '@module_name' => $module, - '@message' => $test_value['message'], - '@param_name' => $test_param, - '@test_name' => $test_name, - '@value' => $test_value['value'], - ]; - $this->output->writeln((string) dt("[DETAILS] @message for parameter '@param_name'; current value '@value'. (Test @test_name in module @module_name)", $variables)); - $this->logger->error("Custom test validation failed: @message for parameter '@param_name'; current value '@value'. (Test '@test_name' in module '@module_name')", $variables); - } - } - } - else { - $pass[] = $module; - $this->output->writeln((string) dt("[PASSED] Validation passed for '@module.'", ['@module' => $module])); - } - - $this->output->writeln(''); - } - - $this->output->writeln((string) dt('Validation checks completed.')); - $variables = []; - if (count($pass) > 0) { - $variables['@passes'] = implode(', ', $pass); - $variables['@pass_count'] = count($pass); - $this->output->writeln((string) dt('@pass_count module(s) passed validation: @passes.'), $variables); - } - - if (count($failure) > 0) { - $variables['@failures'] = implode(', ', $failure); - $variables['@fail_count'] = count($failure); - $this->output->writeln((string) dt('@fail_count module(s) failed validation: @failures.'), $variables); - } - - } - - /** - * Helper method to include acquia_connector.module file. - */ - protected function drushSpiGet() { - global $conf; - $conf['acquia_connector.settings']['spi']['ssl_verify'] = FALSE; - $conf['acquia_connector.settings']['spi']['ssl_override'] = TRUE; - - $client = \Drupal::service('acquia_connector.client'); - $spi = new SpiController($client); - - return $spi->get(); - } - -} diff --git a/docroot/modules/contrib/acquia_connector/src/ConnectorException.php b/docroot/modules/contrib/acquia_connector/src/ConnectorException.php index 295e74bf4e..b5307862ea 100644 --- a/docroot/modules/contrib/acquia_connector/src/ConnectorException.php +++ b/docroot/modules/contrib/acquia_connector/src/ConnectorException.php @@ -3,7 +3,7 @@ namespace Drupal\acquia_connector; /** - * Class ConnectorException. + * Produce an exception for certain cases in the connector. * * @package Drupal\acquia_connector */ diff --git a/docroot/modules/contrib/acquia_connector/src/Controller/AuthController.php b/docroot/modules/contrib/acquia_connector/src/Controller/AuthController.php new file mode 100644 index 0000000000..14d7218815 --- /dev/null +++ b/docroot/modules/contrib/acquia_connector/src/Controller/AuthController.php @@ -0,0 +1,216 @@ +renderer = $renderer; + $this->requestStack = $request_stack; + $this->authService = $auth_service; + $this->messenger = $messenger; + $this->logger = $logger; + $this->moduleList = $module_list; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container): self { + $instance = new self( + $container->get('renderer'), + $container->get('request_stack'), + $container->get('acquia_connector.auth_service'), + $container->get('messenger'), + $container->get('acquia_connector.logger_channel'), + $container->get('extension.list.module') + ); + $instance->setStringTranslation($container->get('string_translation')); + return $instance; + } + + /** + * The setup landing page. + * + * @return array + * The build array. + */ + public function setup(): array { + return [ + '#theme' => 'acquia_connector_banner', + '#attached' => [ + 'library' => [ + 'acquia_connector/acquia_connector.form', + ], + ], + '#attributes' => [ + 'path' => $this->moduleList->getPath('acquia_connector'), + ], + 'actions' => [ + '#type' => 'actions', + '#weight' => 0, + 'continue' => [ + '#type' => 'link', + '#title' => $this->t('Authenticate with Acquia Cloud'), + '#url' => Url::fromRoute('acquia_connector.auth.begin'), + '#cache' => [ + 'max-age' => 0, + ], + '#attributes' => [ + 'class' => ['button', 'button--primary'], + 'id' => 'acquia-connector-oauth', + ], + ], + 'manual' => [ + '#type' => 'link', + '#title' => $this->t('Configure manually'), + '#url' => Url::fromRoute('acquia_connector.setup_manual'), + '#attributes' => [ + 'class' => ['button'], + ], + ], + ], + 'signup' => [ + '#markup' => $this->t('Need a subscription? Get one.', [ + ':url' => Url::fromUri('https://www.acquia.com/acquia-cloud-free')->getUri(), + ]), + ], + ]; + } + + /** + * Begins the OAuth authorization process. + * + * @return \Drupal\Core\Routing\TrustedRedirectResponse + * The redirect response. + */ + public function begin(): TrustedRedirectResponse { + $context = new RenderContext(); + $response = $this->renderer->executeInRenderContext($context, function (): TrustedRedirectResponse { + $url = $this->authService->getAuthUrl(); + $generated = $url->toString(TRUE); + $response = new TrustedRedirectResponse($generated->getGeneratedUrl()); + $response + ->getCacheableMetadata() + ->setCacheMaxAge(0); + $response->addCacheableDependency($generated); + return $response; + }); + assert($response instanceof TrustedRedirectResponse); + if (!$context->isEmpty()) { + $response->addCacheableDependency($context->pop()); + } + return $response; + } + + /** + * Finalizes the OAuth authorization process when the user returns. + * + * @return \Symfony\Component\HttpFoundation\RedirectResponse + * The redirect response. + */ + public function return(): RedirectResponse { + $request = $this->requestStack->getCurrentRequest(); + assert($request !== NULL); + $code = $request->query->get('code', ''); + $state = $request->query->get('state', ''); + + try { + $this->authService->finalize($code, $state); + return new RedirectResponse( + Url::fromRoute('acquia_connector.setup_configure')->toString() + ); + } + catch (\Throwable $e) { + $this->messenger->addError($this->t('We could not retrieve account data, please re-authorize with your Acquia Cloud account. For more information check this link.', [ + ':url' => Url::fromUri('https://docs.acquia.com/cloud-platform/known-issues/#unable-to-log-in-through-acquia-connector')->getUri(), + ])); + $this->logger->error('Unable to finalize OAuth handshake with Acquia Cloud: @error', [ + '@error' => trim($e->getMessage()), + ]); + } + return new RedirectResponse( + Url::fromRoute('acquia_connector.setup_oauth')->toString() + ); + } + +} diff --git a/docroot/modules/contrib/acquia_connector/src/Controller/SecurityReviewController.php b/docroot/modules/contrib/acquia_connector/src/Controller/SecurityReviewController.php deleted file mode 100644 index e25c6e9e56..0000000000 --- a/docroot/modules/contrib/acquia_connector/src/Controller/SecurityReviewController.php +++ /dev/null @@ -1,627 +0,0 @@ -securityReviewGetChecks(); - // Run only specific checks. - $to_check = [ - 'views_access', - 'temporary_files', - 'executable_php', - 'input_formats', - 'admin_permissions', - 'untrusted_php', - 'private_files', - 'upload_extensions', - ]; - foreach ($checklist as $module => $checks) { - foreach ($checks as $check_name => $args) { - if (!in_array($check_name, $to_check)) { - unset($checklist[$module][$check_name]); - } - } - if (empty($checklist[$module])) { - unset($checklist[$module]); - } - } - $checklist_results = $this->securityReviewRun($checklist); - foreach ($checklist_results as $module => $checks) { - foreach ($checks as $check_name => $check) { - // Unset data that does not need to be sent. - if (is_null($check['result'])) { - unset($checklist_results[$module][$check_name]); - } - else { - unset($check['success']); - unset($check['failure']); - $checklist_results[$module][$check_name] = $check; - } - } - if (empty($checklist_results[$module])) { - unset($checklist_results[$module]); - } - } - return $checklist_results; - } - - /** - * Function for running Security Review checklist and returning results. - * - * @param array|null $checklist - * Array of checks to run, indexed by module namespace. - * @param bool $log - * Whether to log check processing using security_review_log. - * @param bool $help - * Whether to load the help file and include in results. - * - * @return array - * Results from running checklist, indexed by module namespace. - */ - private function securityReviewRun(array $checklist = NULL, $log = FALSE, $help = FALSE) { - return $this->getSecurityReviewResults($checklist, $log); - } - - /** - * Private function the review and returns the full results. - * - * @param array $checklist - * Array of checks. - * @param bool $log - * If TRUE logs result. - * - * @return array - * Result. - */ - private function getSecurityReviewResults(array $checklist, $log = FALSE) { - $results = []; - foreach ($checklist as $module => $checks) { - foreach ($checks as $check_name => $arguments) { - $check_result = $this->getSecurityReviewRunCheck($module, $check_name, $arguments, $log); - if (!empty($check_result)) { - $results[$module][$check_name] = $check_result; - } - } - } - return $results; - } - - /** - * Run a single Security Review check. - */ - private function getSecurityReviewRunCheck($module, $check_name, $check, $log, $store = FALSE) { - $return = ['result' => NULL]; - if (isset($check['file'])) { - // Handle Security Review defining checks for other modules. - if (isset($check['module'])) { - $module = $check['module']; - } - module_load_include('inc', $module, $check['file']); - } - $function = $check['callback']; - if (method_exists($this, $function)) { - - $return = call_user_func([ - __NAMESPACE__ . '\SecurityReviewController', - $function, - ]); - - } - $check_result = array_merge($check, $return); - $check_result['lastrun'] = \Drupal::time()->getRequestTime(); - - // Do not log if result is NULL. - if ($log && !is_null($return['result'])) { - $variables = ['@name' => $check_result['title']]; - if ($check_result['result']) { - $this->getSecurityReviewLog($module, $check_name, '@name check passed', $variables, WATCHDOG_INFO); - } - else { - $this->getSecurityReviewLog($module, $check_name, '@name check failed', $variables, WATCHDOG_ERROR); - } - } - return $check_result; - } - - /** - * Log results. - * - * @param string $module - * Module. - * @param string $check_name - * Check name. - * @param string $message - * Message. - * @param array $variables - * Variables. - * @param string $type - * Event type. - */ - private function getSecurityReviewLog($module, $check_name, $message, array $variables, $type) { - $this->moduleHandler() - ->invokeAll('acquia_spi_security_review_log', [ - $module, - $check_name, - $message, - $variables, - $type, - ]); - } - - /** - * Helper function allows for collection of this file's security checks. - */ - private function securityReviewGetChecks() { - // Use Security Review's checks if available. - if ($this->moduleHandler()->moduleExists('security_review') && function_exists('security_review_security_checks')) { - return $this->moduleHandler()->invokeAll('security_checks'); - } - else { - return $this->securityReviewSecurityChecks(); - } - } - - /** - * Checks for acquia_spi_security_review_get_checks(). - * - * @return array - * Result. - */ - private function securityReviewSecurityChecks() { - - $checks['input_formats'] = [ - 'title' => $this->t('Text formats'), - 'callback' => 'checkInputFormats', - 'success' => $this->t('Untrusted users are not allowed to input dangerous HTML tags.'), - 'failure' => $this->t('Untrusted users are allowed to input dangerous HTML tags.'), - ]; - $checks['upload_extensions'] = [ - 'title' => $this->t('Allowed upload extensions'), - 'callback' => 'checkUploadExtensions', - 'success' => $this->t('Only safe extensions are allowed for uploaded files and images.'), - 'failure' => $this->t('Unsafe file extensions are allowed in uploads.'), - ]; - $checks['admin_permissions'] = [ - 'title' => $this->t('Drupal permissions'), - 'callback' => 'checkAdminPermissions', - 'success' => $this->t('Untrusted roles do not have administrative or trusted Drupal permissions.'), - 'failure' => $this->t('Untrusted roles have been granted administrative or trusted Drupal permissions.'), - ]; - // Check dependent on PHP filter being enabled. - if ($this->moduleHandler()->moduleExists('php')) { - $checks['untrusted_php'] = [ - 'title' => $this->t('PHP access'), - 'callback' => 'checkPhpFilter', - 'success' => $this->t('Untrusted users do not have access to use the PHP input format.'), - 'failure' => $this->t('Untrusted users have access to use the PHP input format.'), - ]; - } - $checks['executable_php'] = [ - 'title' => $this->t('Executable PHP'), - 'callback' => 'checkExecutablePhp', - 'success' => $this->t('PHP files in the Drupal files directory cannot be executed.'), - 'failure' => $this->t('PHP files in the Drupal files directory can be executed.'), - ]; - $checks['temporary_files'] = [ - 'title' => $this->t('Temporary files'), - 'callback' => 'checkTemporaryFiles', - 'success' => $this->t('No sensitive temporary files were found.'), - 'failure' => $this->t('Sensitive temporary files were found on your files system.'), - ]; - if ($this->moduleHandler()->moduleExists('views')) { - $checks['views_access'] = [ - 'title' => $this->t('Views access'), - 'callback' => 'checkViewsAccess', - 'success' => $this->t('Views are access controlled.'), - 'failure' => $this->t('There are Views that do not provide any access checks.'), - ]; - } - - return ['security_review' => $checks]; - } - - /** - * Check for sensitive temporary files like settings.php~. - * - * @param int|null $last_check - * Timestamp. - * - * @return array - * Result. - */ - private function checkTemporaryFiles($last_check = NULL) { - $result = TRUE; - $check_result_value = []; - $files = []; - $site_path = \Drupal::service('site.path'); - - $dir = scandir(DRUPAL_ROOT . '/' . $site_path . '/'); - foreach ($dir as $file) { - // Set full path to only files. - if (!is_dir($file)) { - $files[] = DRUPAL_ROOT . '/' . $site_path . '/' . $file; - } - } - $this->moduleHandler()->alter('security_review_temporary_files', $files); - foreach ($files as $path) { - $matches = []; - if (file_exists($path) && preg_match('/.*(~|\.sw[op]|\.bak|\.orig|\.save)$/', $path, $matches) !== FALSE && !empty($matches)) { - $result = FALSE; - $check_result_value[] = $path; - } - } - return ['result' => $result, 'value' => $check_result_value]; - } - - /** - * Check views access. - * - * @param int|null $last_check - * Timestamp. - * - * @return array - * Result. - */ - private function checkViewsAccess($last_check = NULL) { - $result = TRUE; - $check_result_value = []; - // Need review. - $views = Views::getEnabledViews(); - foreach ($views as $view) { - $view_name = $view->get('originalId'); - $view_display = $view->get('display'); - // Access is set in display options of a display. - foreach ($view_display as $display_name => $display) { - if (isset($display['display_options']['access']) && $display['display_options']['access']['type'] == 'none') { - $check_result_value[$view_name][] = $display_name; - } - } - } - if (!empty($check_result_value)) { - $result = FALSE; - } - return ['result' => $result, 'value' => $check_result_value]; - } - - /** - * Check if PHP files written to the files directory can be executed. - */ - private function checkExecutablePhp($last_check = NULL) { - global $base_url; - $result = TRUE; - $check_result_value = []; - - $message = 'Security review test ' . date('Ymdhis'); - $content = "post($base_url . '/' . $directory . $file); - if ($response->getStatusCode() == 200 && $response->getBody()->read(100) === $message) { - $result = FALSE; - $check_result_value[] = 'executable_php'; - } - - } - catch (\Exception $e) { - $response = $e->getResponse(); - } - - if (file_exists('./' . $directory . $file)) { - @unlink('./' . $directory . $file); - } - // Check for presence of the .htaccess file and if the contents are correct. - if (!file_exists($directory . '/.htaccess')) { - $result = FALSE; - $check_result_value[] = 'missing_htaccess'; - } - else { - $contents = file_get_contents($directory . '/.htaccess'); - // Text from includes/file.inc. - $expected = ''; - // Todo: remove conditional when Drupal 8.7 is EOL. - foreach (['\Drupal\Component\FileSecurity\FileSecurity', '\Drupal\Component\PhpStorage\FileStorage'] as $class) { - if (method_exists($class, 'htaccessLines')) { - $expected = $class::htaccessLines(FALSE); - break; - } - } - if ($contents !== $expected) { - $result = FALSE; - $check_result_value[] = 'incorrect_htaccess'; - } - if (is_writable($directory . '/.htaccess')) { - // Don't modify $result. - $check_result_value[] = 'writable_htaccess'; - } - } - - return ['result' => $result, 'value' => $check_result_value]; - } - - /** - * Check upload extensions. - * - * @param int|null $last_check - * Last check. - * - * @return array - * Result. - */ - private function checkUploadExtensions($last_check = NULL) { - $check_result = TRUE; - $check_result_value = []; - $unsafe_extensions = $this->unsafeExtensions(); - $fields = FieldConfig::loadMultiple(); - foreach ($fields as $field) { - $dependencies = $field->get('dependencies'); - if (isset($dependencies) && !empty($dependencies['module'])) { - foreach ($dependencies['module'] as $module) { - if ($module == 'image' || $module == 'file') { - foreach ($unsafe_extensions as $unsafe_extension) { - // Check instance file_extensions. - if (strpos($field->getSetting('file_extensions'), $unsafe_extension) !== FALSE) { - // Found an unsafe extension. - $check_result_value[$field->getName()][$field->getTargetBundle()] = $unsafe_extension; - $check_result = FALSE; - } - } - } - } - } - } - return ['result' => $check_result, 'value' => $check_result_value]; - } - - /** - * Check input formats fo unsafe tags. - * - * Check for formats that either do not have HTML filter that can be used by - * untrusted users, or if they do check if unsafe tags are allowed. - * - * @return array - * Result. - * - * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException - * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException - */ - private function checkInputFormats() { - $result = TRUE; - - /** @var \Drupal\filter\FilterFormatInterface[] $formats */ - $formats = $this->entityTypeManager() - ->getStorage('filter_format') - ->loadByProperties(['status' => TRUE]); - $check_result_value = []; - - // Check formats that are accessible by untrusted users. - // $untrusted_roles = acquia_spi_security_review_untrusted_roles(); - $untrusted_roles = $this->untrustedRoles(); - $untrusted_roles = array_keys($untrusted_roles); - foreach ($formats as $id => $format) { - $format_roles = filter_get_roles_by_format($format); - $intersect = array_intersect(array_keys($format_roles), $untrusted_roles); - if (!empty($intersect)) { - $filters = $formats[$id]->get('filters'); - // Check format for enabled HTML filter. - if (in_array('filter_html', array_keys($filters)) && $filters['filter_html']['status'] == 1) { - $filter = $filters['filter_html']; - // Check for unsafe tags in allowed tags. - $allowed_tags = $filter['settings']['allowed_html']; - $unsafe_tags = $this->unsafeTags(); - foreach ($unsafe_tags as $tag) { - if (strpos($allowed_tags, '<' . $tag . '>') !== FALSE) { - // Found an unsafe tag. - $check_result_value['tags'][$id] = $tag; - } - } - } - elseif (!in_array('filter_html_escape', array_keys($filters)) || !$filters['filter_html_escape']['status'] == 1) { - // Format is usable by untrusted users but does not contain - // the HTML Filter or the HTML escape. - $check_result_value['formats'][$id] = $format; - } - } - } - - if (!empty($check_result_value)) { - $result = FALSE; - } - return ['result' => $result, 'value' => $check_result_value]; - } - - /** - * Look for admin permissions granted to untrusted roles. - */ - private function checkAdminPermissions() { - $result = TRUE; - $check_result_value = []; - $mapping_role = ['anonymous' => 1, 'authenticated' => 2]; - $untrusted_roles = $this->untrustedRoles(); - - // Collect permissions marked as for trusted users only. - $all_permissions = \Drupal::service('user.permissions')->getPermissions(); - $all_keys = array_keys($all_permissions); - - // Get permissions for untrusted roles. - $untrusted_permissions = user_role_permissions(array_keys($untrusted_roles)); - foreach ($untrusted_permissions as $rid => $permissions) { - $intersect = array_intersect($all_keys, $permissions); - foreach ($intersect as $permission) { - if (!empty($all_permissions[$permission]['restrict access'])) { - $check_result_value[$mapping_role[$rid]][] = $permission; - } - } - } - - if (!empty($check_result_value)) { - $result = FALSE; - } - return ['result' => $result, 'value' => $check_result_value]; - } - - /** - * Check if untrusted users can use PHP Filter format. - * - * @return array - * Result. - * - * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException - * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException - */ - protected function checkPhpFilter() { - $result = TRUE; - $check_result_value = []; - /** @var \Drupal\filter\FilterFormatInterface[] $formats */ - $formats = $this->entityTypeManager() - ->getStorage('filter_format') - ->loadByProperties(['status' => TRUE]); - // Check formats that are accessible by untrusted users. - $untrusted_roles = $this->untrustedRoles(); - $untrusted_roles = array_keys($untrusted_roles); - foreach ($formats as $id => $format) { - $format_roles = filter_get_roles_by_format($format); - $intersect = array_intersect(array_keys($format_roles), $untrusted_roles); - if (!empty($intersect)) { - // Untrusted users can use this format. - $filters = $formats[$id]->get('filters'); - // Check format for enabled PHP filter. - if (in_array('php_code', array_keys($filters)) && $filters['php_code']['status'] == 1) { - $result = FALSE; - $check_result_value['formats'][$id] = $format; - } - } - } - - return ['result' => $result, 'value' => $check_result_value]; - } - - /** - * Helper function defines file extensions considered unsafe. - */ - public function unsafeExtensions() { - return [ - 'swf', - 'exe', - 'html', - 'htm', - 'php', - 'phtml', - 'py', - 'js', - 'vb', - 'vbe', - 'vbs', - ]; - } - - /** - * Helper function defines HTML tags that are considered unsafe. - * - * Based on wysiwyg_filter_get_elements_blacklist(). - */ - public function unsafeTags() { - return [ - 'applet', - 'area', - 'audio', - 'base', - 'basefont', - 'body', - 'button', - 'comment', - 'embed', - 'eval', - 'form', - 'frame', - 'frameset', - 'head', - 'html', - 'iframe', - 'image', - 'img', - 'input', - 'isindex', - 'label', - 'link', - 'map', - 'math', - 'meta', - 'noframes', - 'noscript', - 'object', - 'optgroup', - 'option', - 'param', - 'script', - 'select', - 'style', - 'svg', - 'table', - 'td', - 'textarea', - 'title', - 'video', - 'vmlframe', - ]; - } - - /** - * Helper function for user-defined or default untrusted Drupal roles. - * - * @return array - * An associative array with the role id as the key and the role name as - * value. - */ - public function untrustedRoles() { - $defaults = $this->defaultUntrustedRoles(); - $roles = $defaults; - return array_filter($roles); - } - - /** - * Helper function defines the default untrusted Drupal roles. - */ - public function defaultUntrustedRoles() { - $roles = [AccountInterface::ANONYMOUS_ROLE => 'anonymous user']; - // Need set default value. - $user_register = \Drupal::config('user.settings')->get('register'); - // If visitors are allowed to create accounts they are considered untrusted. - if ($user_register != UserInterface::REGISTER_ADMINISTRATORS_ONLY) { - $roles[AccountInterface::AUTHENTICATED_ROLE] = 'authenticated user'; - } - return $roles; - } - -} diff --git a/docroot/modules/contrib/acquia_connector/src/Controller/SpiController.php b/docroot/modules/contrib/acquia_connector/src/Controller/SpiController.php deleted file mode 100644 index d9c07a0c4c..0000000000 --- a/docroot/modules/contrib/acquia_connector/src/Controller/SpiController.php +++ /dev/null @@ -1,1250 +0,0 @@ -client = $client; - $this->configFactory = $config_factory; - } - - /** - * {@inheritdoc} - */ - public static function create(ContainerInterface $container) { - return new static( - $container->get('acquia_connector.client'), - $container->get('config.factory') - ); - } - - /** - * Gather site profile information about this site. - * - * @param string $method - * Optional identifier for the method initiating request. - * Values could be 'cron' or 'menu callback' or 'drush'. - * - * @return array - * An associative array keyed by types of information. - */ - public function get($method = '') { - - $config = $this->configFactory->getEditable('acquia_connector.settings'); - - // Get the Drupal version. - $drupal_version = $this->getVersionInfo(); - - $stored = $this->dataStoreGet(['platform']); - if (!empty($stored['platform'])) { - $platform = $stored['platform']; - } - else { - $platform = $this->getPlatform(); - } - - $acquia_hosted = $this->checkAcquiaHosted(); - $environment = $this->config('acquia_connector.settings')->get('spi.site_environment'); - $env_detection_enabled = $this->config('acquia_connector.settings')->get('spi.env_detection_enabled'); - if ($acquia_hosted) { - if ($environment != $_SERVER['AH_SITE_ENVIRONMENT']) { - $config->set('spi.site_environment', $_SERVER['AH_SITE_ENVIRONMENT']); - $environment = $_SERVER['AH_SITE_ENVIRONMENT']; - if ($env_detection_enabled) { - $config->set('spi.site_machine_name', $this->getAcquiaHostedMachineName()); - } - } - } - else { - if ($environment) { - $config->set('spi.site_environment', NULL); - } - $environment = NULL; - } - - if ($env_detection_enabled === NULL) { - $config->set('spi.env_detection_enabled', TRUE); - } - - $config->save(); - - $spi = [ - // Used in HMAC validation. - 'rpc_version' => ACQUIA_CONNECTOR_ACQUIA_SPI_DATA_VERSION, - // Used in Fix it now feature. - 'spi_data_version' => ACQUIA_CONNECTOR_ACQUIA_SPI_DATA_VERSION, - 'site_key' => sha1(\Drupal::service('private_key')->get()), - 'site_uuid' => $this->config('acquia_connector.settings')->get('spi.site_uuid'), - 'env_changed_action' => $this->config('acquia_connector.settings')->get('spi.environment_changed_action'), - 'acquia_hosted' => $acquia_hosted, - 'name' => $this->config('acquia_connector.settings')->get('spi.site_name'), - 'machine_name' => $this->config('acquia_connector.settings')->get('spi.site_machine_name'), - 'environment' => $environment, - 'modules' => $this->getModules(), - 'platform' => $platform, - 'quantum' => $this->getQuantum(), - 'system_status' => $this->getSystemStatus(), - 'failed_logins' => $this->config('acquia_connector.settings')->get('spi.send_watchdog') ? $this->getFailedLogins() : [], - '404s' => $this->config('acquia_connector.settings')->get('spi.send_watchdog') ? $this->get404s() : [], - 'watchdog_size' => $this->getWatchdogSize(), - 'watchdog_data' => $this->config('acquia_connector.settings')->get('spi.send_watchdog') ? $this->getWatchdogData() : [], - 'last_nodes' => $this->config('acquia_connector.settings')->get('spi.send_node_user') ? $this->getLastNodes() : [], - 'last_users' => $this->config('acquia_connector.settings')->get('spi.send_node_user') ? $this->getLastUsers() : [], - 'extra_files' => $this->checkFilesPresent(), - 'ssl_login' => $this->checkLogin(), - 'distribution' => isset($drupal_version['distribution']) ? $drupal_version['distribution'] : '', - 'base_version' => $drupal_version['base_version'], - 'build_data' => $drupal_version, - 'roles' => Json::encode(user_roles()), - 'uid_0_present' => $this->getUidZeroIsPresent(), - ]; - - $scheme = parse_url($this->config('acquia_connector.settings')->get('spi.server'), PHP_URL_SCHEME); - $via_ssl = (in_array('ssl', stream_get_transports(), TRUE) && $scheme == 'https') ? TRUE : FALSE; - if ($this->config('acquia_connector.settings')->get('spi.ssl_override')) { - $via_ssl = TRUE; - } - - $additional_data = []; - - $security_review = new SecurityReviewController(); - $security_review_results = $security_review->runSecurityReview(); - - // It's worth sending along node access control information even if there - // are no modules implementing it - some alerts are simpler if we know we - // don't have to worry about node access. - // Check for node grants modules. - $additional_data['node_grants_modules'] = $this->moduleHandler()->getImplementations('node_grants'); - - // Check for node access modules. - $additional_data['node_access_modules'] = $this->moduleHandler()->getImplementations('node_access'); - - if (!empty($security_review_results)) { - $additional_data['security_review'] = $security_review_results['security_review']; - } - - // Collect all user-contributed custom tests that pass validation. - $custom_tests_results = $this->testCollect(); - if (!empty($custom_tests_results)) { - $additional_data['custom_tests'] = $custom_tests_results; - } - - $spi_data = $this->moduleHandler()->invokeAll('acquia_connector_spi_get'); - if (!empty($spi_data)) { - foreach ($spi_data as $name => $data) { - if (is_string($name) && is_array($data)) { - $additional_data[$name] = $data; - } - } - } - - include_once "core/includes/update.inc"; - $additional_data['pending_updates'] = (bool) update_get_update_list(); - - if (!empty($additional_data)) { - // JSON encode this additional data. - $spi['additional_data'] = json_encode($additional_data); - } - - if (!empty($method)) { - $spi['send_method'] = $method; - } - - if (!$via_ssl) { - return $spi; - } - else { - $variablesController = new VariablesController(); - // Values returned only over SSL. - $spi_ssl = [ - 'system_vars' => $variablesController->getVariablesData(), - 'settings_ra' => $this->getSettingsPermissions(), - 'admin_count' => $this->config('acquia_connector.settings')->get('spi.admin_priv') ? $this->getAdminCount() : '', - 'admin_name' => $this->config('acquia_connector.settings')->get('spi.admin_priv') ? $this->getSuperName() : '', - ]; - - return array_merge($spi, $spi_ssl); - } - } - - /** - * Collects all user-contributed test results that pass validation. - * - * @return array - * An associative array containing properly formatted user-contributed - * tests. - */ - private function testCollect() { - $custom_data = []; - - // Collect all custom data provided by hook_insight_custom_data(). - $collections = $this->moduleHandler()->invokeAll('acquia_connector_spi_test'); - - foreach ($collections as $test_name => $test_params) { - $status = new TestStatusController(); - $result = $status->testValidate([$test_name => $test_params]); - - if ($result['result']) { - $custom_data[$test_name] = $test_params; - } - } - - return $custom_data; - } - - /** - * Checks to see if SSL login is required. - * - * @return int - * 1 if SSL login is required. - */ - private function checkLogin() { - $login_safe = 0; - - if ($this->moduleHandler()->moduleExists('securelogin')) { - $secureLoginConfig = $this->config('securelogin.settings')->get(); - if ($secureLoginConfig['all_forms']) { - $forms_safe = TRUE; - } - else { - // All the required forms should be enabled. - $required_forms = [ - 'form_user_login_form', - 'form_user_form', - 'form_user_register_form', - 'form_user_pass_reset', - 'form_user_pass', - ]; - $forms_safe = TRUE; - foreach ($required_forms as $form_variable) { - if (!$secureLoginConfig[$form_variable]) { - $forms_safe = FALSE; - break; - } - } - } - // \Drupal::request()->isSecure() ($conf['https'] in D7) should be false - // for expected behavior. - if ($forms_safe && !\Drupal::request()->isSecure()) { - $login_safe = 1; - } - } - - return $login_safe; - } - - /** - * Check to see if the unneeded release files with Drupal are removed. - * - * @return int - * 1 if they are removed, 0 if they aren't. - */ - private function checkFilesPresent() { - - $files_exist = FALSE; - $files_to_remove = [ - 'CHANGELOG.txt', - 'COPYRIGHT.txt', - 'INSTALL.mysql.txt', - 'INSTALL.pgsql.txt', - 'INSTALL.txt', - 'LICENSE.txt', - 'MAINTAINERS.txt', - 'README.txt', - 'UPGRADE.txt', - 'PRESSFLOW.txt', - 'install.php', - ]; - - foreach ($files_to_remove as $file) { - - $path = DRUPAL_ROOT . DIRECTORY_SEPARATOR . $file; - if (file_exists($path)) { - $files_exist = TRUE; - } - } - - return $files_exist ? 1 : 0; - } - - /** - * Attempt to determine if this site is hosted with Acquia. - * - * @return bool - * TRUE if site is hosted with Acquia, otherwise FALSE. - */ - public function checkAcquiaHosted() { - return isset($_SERVER['AH_SITE_ENVIRONMENT'], $_SERVER['AH_SITE_NAME']); - } - - /** - * Generate the name for acquia hosted sites. - * - * @return string - * The Acquia Hosted name. - */ - public function getAcquiaHostedName() { - $subscription_name = $this->config('acquia_connector.settings')->get('subscription_name'); - - if ($this->checkAcquiaHosted() && $subscription_name) { - return $this->config('acquia_connector.settings')->get('subscription_name') . ': ' . $_SERVER['AH_SITE_ENVIRONMENT']; - } - } - - /** - * Generate the machine name for acquia hosted sites. - * - * @return string - * The suggested Acquia Hosted machine name. - */ - public function getAcquiaHostedMachineName() { - $sub_data = $this->state()->get('acquia_subscription_data'); - - if ($this->checkAcquiaHosted() && $sub_data) { - $uuid = new StatusController(); - $sub_uuid = str_replace('-', '_', $uuid->getIdFromSub($sub_data)); - - return $sub_uuid . '__' . $_SERVER['AH_SITE_NAME'] . '__' . uniqid(); - } - } - - /** - * Check if a site environment change has been detected. - * - * @return bool - * TRUE if change detected that needs to be addressed, otherwise FALSE. - */ - public function checkEnvironmentChange() { - $changes = $this->config('acquia_connector.settings')->get('spi.environment_changes'); - $change_action = $this->config('acquia_connector.settings')->get('spi.environment_changed_action'); - - return !empty($changes) && empty($change_action); - } - - /** - * Get last 15 users created. - * - * Useful for determining if your site is compromised. - * - * @return array - * The details of last 15 users created. - */ - private function getLastUsers() { - $last_five_users = []; - $result = Database::getConnection()->select('users_field_data', 'u') - ->fields('u', ['uid', 'name', 'mail', 'created']) - ->condition('u.created', \Drupal::time()->getRequestTime() - 3600, '>') - ->orderBy('created', 'DESC') - ->range(0, 15) - ->execute(); - - $count = 0; - foreach ($result as $record) { - $last_five_users[$count]['uid'] = $record->uid; - $last_five_users[$count]['name'] = $record->name; - $last_five_users[$count]['email'] = $record->mail; - $last_five_users[$count]['created'] = $record->created; - $count++; - } - - return $last_five_users; - } - - /** - * Get last 15 nodes created. - * - * This can be useful to determine if you have some sort of spam on your site. - * - * @return array - * Array of the details of last 15 nodes created. - */ - private function getLastNodes() { - $last_five_nodes = []; - if ($this->moduleHandler()->moduleExists('node')) { - $result = Database::getConnection()->select('node_field_data', 'n') - ->fields('n', ['title', 'type', 'nid', 'created', 'langcode']) - ->condition('n.created', \Drupal::time()->getRequestTime() - 3600, '>') - ->orderBy('n.created', 'DESC') - ->range(0, 15) - ->execute(); - - $count = 0; - foreach ($result as $record) { - $last_five_nodes[$count]['url'] = \Drupal::service('path_alias.manager') - ->getAliasByPath('/node/' . $record->nid, $record->langcode); - $last_five_nodes[$count]['title'] = $record->title; - $last_five_nodes[$count]['type'] = $record->type; - $last_five_nodes[$count]['created'] = $record->created; - $count++; - } - } - - return $last_five_nodes; - } - - /** - * Get the latest (last hour) critical and emergency warnings from watchdog. - * - * These errors are 'severity' 0 and 2. - * - * @return array - * EMERGENCY and CRITICAL watchdog records for last hour. - */ - private function getWatchdogData() { - $wd = []; - if ($this->moduleHandler()->moduleExists('dblog')) { - $result = Database::getConnection()->select('watchdog', 'w') - ->fields('w', ['wid', 'severity', 'type', 'message', 'timestamp']) - ->condition('w.severity', [RfcLogLevel::EMERGENCY, RfcLogLevel::CRITICAL], 'IN') - ->condition('w.timestamp', \Drupal::time()->getRequestTime() - 3600, '>') - ->execute(); - - while ($record = $result->fetchAssoc()) { - $wd[$record['severity']] = $record; - } - } - - return $wd; - } - - /** - * Get the number of rows in watchdog. - * - * @return int - * Number of watchdog records. - */ - private function getWatchdogSize() { - if ($this->moduleHandler()->moduleExists('dblog')) { - return Database::getConnection()->select('watchdog', 'w')->fields('w', ['wid'])->countQuery()->execute()->fetchField(); - } - } - - /** - * Grabs the last 404 errors in logs. - * - * Grabs the last 404 errors in logs, excluding the checks we run for drupal - * files like README. - * - * @return array - * An array of the pages not found and some associated data. - */ - private function get404s() { - $data = []; - $row = 0; - - if ($this->moduleHandler()->moduleExists('dblog')) { - $result = Database::getConnection()->select('watchdog', 'w') - ->fields('w', ['message', 'hostname', 'referer', 'timestamp']) - ->condition('w.type', 'page not found', '=') - ->condition('w.timestamp', \Drupal::time()->getRequestTime() - 3600, '>') - ->condition('w.message', [ - "UPGRADE.txt", - "MAINTAINERS.txt", - "README.txt", - "INSTALL.pgsql.txt", - "INSTALL.txt", - "LICENSE.txt", - "INSTALL.mysql.txt", - "COPYRIGHT.txt", - "CHANGELOG.txt", - ], 'NOT IN') - ->orderBy('w.timestamp', 'DESC') - ->range(0, 10) - ->execute(); - - foreach ($result as $record) { - $data[$row]['message'] = $record->message; - $data[$row]['hostname'] = $record->hostname; - $data[$row]['referer'] = $record->referer; - $data[$row]['timestamp'] = $record->timestamp; - $row++; - } - } - - return $data; - } - - /** - * Get the information on failed logins in the last cron interval. - * - * @return array - * Array of last 10 failed logins. - */ - private function getFailedLogins() { - $last_logins = []; - $cron_interval = $this->config('acquia_connector.settings')->get('spi.cron_interval'); - - if ($this->moduleHandler()->moduleExists('dblog')) { - $result = Database::getConnection()->select('watchdog', 'w') - ->fields('w', ['message', 'variables', 'timestamp']) - ->condition('w.message', 'login attempt failed%', 'LIKE') - ->condition('w.timestamp', \Drupal::time()->getRequestTime() - $cron_interval, '>') - ->condition('w.message', [ - "UPGRADE.txt", - "MAINTAINERS.txt", - "README.txt", - "INSTALL.pgsql.txt", - "INSTALL.txt", - "LICENSE.txt", - "INSTALL.mysql.txt", - "COPYRIGHT.txt", - "CHANGELOG.txt", - ], 'NOT IN') - ->orderBy('w.timestamp', 'DESC') - ->range(0, 10) - ->execute(); - - foreach ($result as $record) { - $variables = unserialize($record->variables); - if (!empty($variables['%user'])) { - $last_logins['failed'][$record->timestamp] = Html::escape($variables['%user']); - } - } - } - return $last_logins; - } - - /** - * This function is a trimmed version of Drupal's system_status function. - * - * @return array - * System status array. - */ - private function getSystemStatus() { - $data = []; - - if (\Drupal::hasContainer()) { - $profile = \Drupal::installProfile(); - } - else { - $profile = BootstrapConfigStorageFactory::getDatabaseStorage()->read('core.extension')['profile']; - } - if ($profile != 'standard') { - $extension_list = \Drupal::service('extension.list.module'); - $info = $extension_list->getExtensionInfo($profile); - $data['install_profile'] = [ - 'title' => 'Install profile', - 'value' => sprintf('%s (%s-%s)', $info['name'], $profile, $info['version']), - ]; - } - $data['php'] = [ - 'title' => 'PHP', - 'value' => phpversion(), - ]; - $conf_dir = TRUE; - $settings = TRUE; - $dir = DrupalKernel::findSitePath(\Drupal::request(), TRUE); - if (is_writable($dir) || is_writable($dir . '/settings.php')) { - $value = 'Not protected'; - if (is_writable($dir)) { - $conf_dir = FALSE; - } - elseif (is_writable($dir . '/settings.php')) { - $settings = FALSE; - } - } - else { - $value = 'Protected'; - } - $data['settings.php'] = [ - 'title' => 'Configuration file', - 'value' => $value, - 'conf_dir' => $conf_dir, - 'settings' => $settings, - ]; - $cron_last = \Drupal::state()->get('system.cron_last'); - if (!is_numeric($cron_last)) { - $cron_last = \Drupal::state()->get('install_time', 0); - } - $data['cron'] = [ - 'title' => 'Cron maintenance tasks', - 'value' => sprintf('Last run %s ago', \Drupal::service('date.formatter')->formatInterval(\Drupal::time()->getRequestTime() - $cron_last)), - 'cron_last' => $cron_last, - ]; - if (!empty(Settings::get('update_free_access'))) { - $data['update access'] = [ - 'value' => 'Not protected', - 'protected' => FALSE, - ]; - } - else { - $data['update access'] = [ - 'value' => 'Protected', - 'protected' => TRUE, - ]; - } - $data['update access']['title'] = 'Access to update.php'; - if (!$this->moduleHandler()->moduleExists('update')) { - $data['update status'] = [ - 'value' => 'Not enabled', - ]; - } - else { - $data['update status'] = [ - 'value' => 'Enabled', - ]; - } - $data['update status']['title'] = 'Update notifications'; - return $data; - } - - /** - * Check the presence of UID 0 in the users table. - * - * @return bool - * Whether UID 0 is present. - */ - private function getUidZeroIsPresent() { - $count = Database::getConnection()->query('SELECT uid FROM {users} WHERE uid = 0')->fetchAll(); - return (boolean) $count; - } - - /** - * The number of users who have admin-level user roles. - * - * @return int - * Count of admin users. - */ - private function getAdminCount() { - $roles_name = []; - $get_roles = Role::loadMultiple(); - unset($get_roles[AccountInterface::ANONYMOUS_ROLE]); - $permission = ['administer permissions', 'administer users']; - foreach ($permission as $value) { - $filtered_roles = array_filter($get_roles, function ($role) use ($value) { - return $role->hasPermission($value); - }); - foreach ($filtered_roles as $role_name => $data) { - $roles_name[] = $role_name; - } - } - - if (!empty($roles_name)) { - $roles_name_unique = array_unique($roles_name); - $query = Database::getConnection()->select('user__roles', 'ur'); - $query->fields('ur', ['entity_id']); - $query->condition('ur.bundle', 'user', '='); - $query->condition('ur.deleted', '0', '='); - $query->condition('ur.roles_target_id', $roles_name_unique, 'IN'); - $count = $query->countQuery()->execute()->fetchField(); - } - - return (isset($count) && is_numeric($count)) ? $count : NULL; - } - - /** - * Determine if the super user has a weak name. - * - * @return int - * 1 if the super user has a weak name, 0 otherwise. - */ - private function getSuperName() { - $result = Database::getConnection()->query("SELECT name FROM {users_field_data} WHERE uid = 1 AND (name LIKE '%admin%' OR name LIKE '%root%') AND LENGTH(name) < 15")->fetchAll(); - return (int) $result; - } - - /** - * Determines if settings.php is read-only. - * - * @return bool - * TRUE if settings.php is read-only, FALSE otherwise. - */ - private function getSettingsPermissions() { - $settings_permissions_read_only = TRUE; - // http://en.wikipedia.org/wiki/File_system_permissions. - $writes = ['2', '3', '6', '7']; - $settings_file = './' . DrupalKernel::findSitePath(\Drupal::request(), TRUE) . '/settings.php'; - $permissions = mb_substr(sprintf('%o', fileperms($settings_file)), -4); - - foreach ($writes as $bit) { - if (strpos($permissions, $bit)) { - $settings_permissions_read_only = FALSE; - break; - } - } - - return $settings_permissions_read_only; - } - - /** - * Determine if a path is a file type we care about for modifications. - */ - private function isManifestType($path) { - $extensions = [ - 'yml' => 1, - 'php' => 1, - 'php4' => 1, - 'php5' => 1, - 'module' => 1, - 'inc' => 1, - 'install' => 1, - 'test' => 1, - 'theme' => 1, - 'engine' => 1, - 'profile' => 1, - 'css' => 1, - 'js' => 1, - 'info' => 1, - 'sh' => 1, - // SSL certificates. - 'pem' => 1, - 'pl' => 1, - 'pm' => 1, - ]; - $pathinfo = pathinfo($path); - return isset($pathinfo['extension']) && isset($extensions[$pathinfo['extension']]); - } - - /** - * Calculate the sha1 hash for a path. - * - * @param string $path - * The name of the file or a directory. - * - * @return string - * base64 encoded sha1 hash. 'hash' is an empty string for directories. - */ - private function hashPath($path = '') { - $hash = ''; - if (file_exists($path)) { - if (!is_dir($path)) { - $string = file_get_contents($path); - // Remove trailing whitespace. - $string = rtrim($string); - // Replace all line endings and CVS/svn Id tags. - $string = preg_replace('/\$Id[^;<>{}\(\)\$]*\$/', 'x$' . 'Id$', $string); - $string = preg_replace('/\r\n|\n|\r/', ' ', $string); - $hash = base64_encode(pack("H*", sha1($string))); - } - } - return $hash; - } - - /** - * Attempt to determine the version of Drupal being used. - * - * Note, there is better information on this in the common.inc file. - * - * @return array - * An array containing some detail about the version - */ - private function getVersionInfo() { - $server = \Drupal::request()->server->all(); - $ver = []; - - $ver['base_version'] = \Drupal::VERSION; - $install_root = $server['DOCUMENT_ROOT'] . base_path(); - $ver['distribution'] = ''; - - // Determine if this puppy is Acquia Drupal. - acquia_connector_load_versions(); - - if (IS_ACQUIA_DRUPAL) { - $ver['distribution'] = 'Acquia Drupal'; - $ver['ad']['version'] = ACQUIA_DRUPAL_VERSION; - $ver['ad']['series'] = ACQUIA_DRUPAL_SERIES; - $ver['ad']['branch'] = ACQUIA_DRUPAL_BRANCH; - $ver['ad']['revision'] = ACQUIA_DRUPAL_REVISION; - } - - // Determine if we are looking at Pressflow. - if (defined('CACHE_EXTERNAL')) { - $ver['distribution'] = 'Pressflow'; - $press_version_file = $install_root . './PRESSFLOW.txt'; - if (is_file($press_version_file)) { - $ver['pr']['version'] = trim(file_get_contents($press_version_file)); - } - } - // Determine if this is Open Atrium. - elseif (is_dir($install_root . '/profiles/openatrium')) { - $ver['distribution'] = 'Open Atrium'; - $version_file = $install_root . 'profiles/openatrium/VERSION.txt'; - if (is_file($version_file)) { - $ver['oa']['version'] = trim(file_get_contents($version_file)); - } - } - // Determine if this is Commons. - elseif (is_dir($install_root . '/profiles/commons')) { - $ver['distribution'] = 'Commons'; - } - // Determine if this is COD. - elseif (is_dir($install_root . '/profiles/cod')) { - $ver['distribution'] = 'COD'; - } - - return $ver; - } - - /** - * Put SPI data in local storage. - * - * @param array $data - * Keyed array of data to store. - * @param int $expire - * Expire time or null to use default of 1 day. - */ - public function dataStoreSet(array $data, $expire = NULL) { - if (is_null($expire)) { - $expire = \Drupal::time()->getRequestTime() + (60 * 60 * 24); - } - foreach ($data as $key => $value) { - \Drupal::cache()->set('acquia.spi.' . $key, $value, $expire); - } - } - - /** - * Get SPI data out of local storage. - * - * @param array $keys - * Array of keys to extract data for. - * - * @return array - * Stored data or false if no data is retrievable from storage. - */ - public function dataStoreGet(array $keys) { - $store = []; - foreach ($keys as $key) { - if ($cache = \Drupal::cache()->get('acquia.spi.' . $key)) { - if (!empty($cache->data)) { - $store[$key] = $cache->data; - } - } - } - return $store; - } - - /** - * Gather platform specific information. - * - * @return array - * An associative array keyed by a platform information type. - */ - public static function getPlatform() { - $server = \Drupal::request()->server; - // Database detection depends on the structure starting with the database. - $db_class = '\Drupal\Core\Database\Driver\\' . Database::getConnection()->driver() . '\Install\Tasks'; - $db_tasks = new $db_class(); - // Webserver detection is based on name being before the slash, and - // version being after the slash. - preg_match('!^([^/]+)(/.+)?$!', $server->get('SERVER_SOFTWARE'), $webserver); - - if (isset($webserver[1]) && stristr($webserver[1], 'Apache') && function_exists('apache_get_version')) { - $webserver[2] = apache_get_version(); - } - - // Get some basic PHP vars. - $php_quantum = [ - 'memory_limit' => ini_get('memory_limit'), - 'register_globals' => 'Off', - 'post_max_size' => ini_get('post_max_size'), - 'max_execution_time' => ini_get('max_execution_time'), - 'upload_max_filesize' => ini_get('upload_max_filesize'), - 'error_log' => ini_get('error_log'), - 'error_reporting' => ini_get('error_reporting'), - 'display_errors' => ini_get('display_errors'), - 'log_errors' => ini_get('log_errors'), - 'session.cookie_domain' => ini_get('session.cookie_domain'), - 'session.cookie_lifetime' => ini_get('session.cookie_lifetime'), - 'newrelic.appname' => ini_get('newrelic.appname'), - 'sapi' => php_sapi_name(), - ]; - - $platform = [ - 'php' => PHP_VERSION, - 'webserver_type' => isset($webserver[1]) ? $webserver[1] : '', - 'webserver_version' => isset($webserver[2]) ? $webserver[2] : '', - 'php_extensions' => get_loaded_extensions(), - 'php_quantum' => $php_quantum, - 'database_type' => (string) $db_tasks->name(), - 'database_version' => Database::getConnection()->version(), - 'system_type' => php_uname('s'), - // php_uname() only accepts one character, so we need to concatenate - // ourselves. - 'system_version' => php_uname('r') . ' ' . php_uname('v') . ' ' . php_uname('m') . ' ' . php_uname('n'), - ]; - - return $platform; - } - - /** - * Gather information about modules on the site. - * - * @return array - * An associative array keyed by filename of associative arrays with - * information on the modules. - */ - private function getModules() { - $modules = \Drupal::service('extension.list.module')->reset()->getList(); - uasort($modules, 'system_sort_modules_by_info_name'); - - $result = []; - $keys_to_send = ['name', 'version', 'package', 'core', 'project']; - foreach ($modules as $module) { - $info = []; - $info['status'] = $module->status; - foreach ($keys_to_send as $key) { - $info[$key] = isset($module->info[$key]) ? $module->info[$key] : ''; - } - $info['filename'] = $module->getPathname(); - if (empty($info['project']) && $module->origin == 'core') { - $info['project'] = 'drupal'; - } - - $result[] = $info; - } - return $result; - } - - /** - * Gather information about nodes, users and comments. - * - * @return array - * An associative array. - */ - private function getQuantum() { - $quantum = []; - - if ($this->moduleHandler()->moduleExists('node')) { - // Get only published nodes. - $quantum['nodes'] = Database::getConnection()->select('node_field_data', 'n') - ->fields('n', ['nid']) - ->condition('n.status', NodeInterface::PUBLISHED) - ->countQuery() - ->execute() - ->fetchField(); - } - - // Get only active users. - $quantum['users'] = Database::getConnection()->select('users_field_data', 'u') - ->fields('u', ['uid']) - ->condition('u.status', 1) - ->countQuery() - ->execute() - ->fetchField(); - - if ($this->moduleHandler()->moduleExists('comment')) { - // Get only active comments. - $quantum['comments'] = Database::getConnection()->select('comment_field_data', 'c') - ->fields('c', ['cid']) - ->condition('c.status', 1) - ->countQuery() - ->execute() - ->fetchField(); - } - - return $quantum; - } - - /** - * Gather full SPI data and send to Acquia. - * - * @param string $method - * Optional identifier for the method initiating request. - * Values could be 'cron' or 'menu callback' or 'drush'. - * - * @return mixed - * FALSE if data is not sent or environment change detected, - * otherwise return NSPI response array. - */ - public function sendFullSpi($method = '') { - $spi = self::get($method); - - if ($this->checkEnvironmentChange()) { - $this->getLogger('acquia spi')->error('SPI data not sent, site environment change detected.'); - $this->messenger()->addError($this->t('SPI data not sent, site environment change detected. Please indicate how you wish to proceed.', [ - '@environment_change' => Url::fromRoute('acquia_connector.environment_change')->toString(), - ])); - return FALSE; - } - - $storage = new Storage(); - $response = $this->client->sendNspi($storage->getIdentifier(), $storage->getKey(), $spi); - - if ($response === FALSE) { - return FALSE; - } - - $this->handleServerResponse($response); - \Drupal::state()->set('acquia_connector.cron_last', \Drupal::time()->getRequestTime()); - - return $response; - } - - /** - * Callback for sending SPI data. - * - * @param \Symfony\Component\HttpFoundation\Request $request - * Request. - * - * @return \Symfony\Component\HttpFoundation\RedirectResponse|\Symfony\Component\HttpFoundation\Response - * Redirect to the destination or return HTTP_BAD_REQUEST|HTTP_OK response. - */ - public function send(Request $request) { - // Mark this page as being uncacheable. - \Drupal::service('page_cache_kill_switch')->trigger(); - $method = ACQUIA_CONNECTOR_ACQUIA_SPI_METHOD_CALLBACK; - - // Insight's set variable feature will pass method insight. - if ($request->query->has('method') && ($request->query->get('method') === ACQUIA_CONNECTOR_ACQUIA_SPI_METHOD_INSIGHT)) { - $method = ACQUIA_CONNECTOR_ACQUIA_SPI_METHOD_INSIGHT; - } - - $response = $this->sendFullSpi($method); - - if ($request->get('destination')) { - $this->spiProcessMessages($response); - $route_match = RouteMatch::createFromRequest($request); - return $this->redirect($route_match->getRouteName(), $route_match->getRawParameters()->all()); - } - - $headers = [ - 'Expires' => 'Mon, 26 Jul 1997 05:00:00 GMT', - 'Content-Type' => 'text/plain', - 'Cache-Control' => 'no-cache', - 'Pragma' => 'no-cache', - ]; - if (empty($response['body'])) { - return new Response('', Response::HTTP_BAD_REQUEST, $headers); - } - - return new Response('', Response::HTTP_OK, $headers); - } - - /** - * Parses and displays messages from the NSPI response. - * - * @param array|mixed $response - * Response array from NSPI. - */ - public function spiProcessMessages($response) { - if (empty($response['body'])) { - $this->messenger()->addError($this->t('Error sending SPI data. Consult the logs for more information.')); - return; - } - - $message_type = 'status'; - - if (isset($response['body']['spi_data_received']) && $response['body']['spi_data_received'] === TRUE) { - $this->messenger()->addStatus($this->t('SPI data sent.')); - } - - if (!empty($response['body']['nspi_messages'])) { - $this->messenger()->addStatus($this->t('Acquia Subscription returned the following messages. Further information may be in the logs.')); - foreach ($response['body']['nspi_messages'] as $nspi_message) { - if (!empty($response['body']['spi_error'])) { - $message_type = $response['body']['spi_error']; - } - $this->messenger()->addMessage(Html::escape($nspi_message), (string) $message_type); - } - } - - if (!empty($response['body']['spi_environment_changes'])) { - $this->configFactory - ->getEditable('acquia_connector.settings') - ->set('spi.environment_changes', Json::decode($response['body']['spi_environment_changes'])) - ->save(); - } - } - - /** - * Act on specific elements of SPI update server response. - * - * @param array $spi_response - * Array response from SpiController->send(). - */ - private function handleServerResponse(array $spi_response) { - - $config_set = $this->configFactory->getEditable('acquia_connector.settings'); - $changed_action = $this->config('acquia_connector.settings')->get('spi.environment_changed_action'); - $config_set->clear('spi.environment_changed_action')->save(); - $site_uuid = $this->config('acquia_connector.settings')->get('spi.site_uuid'); - - // Set site_uuid if it changed or if it hasn't been previously captured. - if (isset($spi_response['body']['site_uuid']) && (is_null($site_uuid) || $spi_response['body']['site_uuid'] != $site_uuid)) { - $config_set->set('spi.site_uuid', $spi_response['body']['site_uuid'])->save(); - } - - // Wipe the site_uuid if it is set locally, but NSPI is trying to create a - // new site. - if (isset($spi_response['body']['site_uuid']) && empty($spi_response['body']['site_uuid']) && !is_null($site_uuid)) { - $config_set->clear('spi.site_uuid')->save(); - } - - $spi_environment_changes = isset($spi_response['body']['spi_environment_changes']) ? Json::decode($spi_response['body']['spi_environment_changes']) : []; - $site_blocked = array_key_exists('blocked', $spi_environment_changes) || !empty($spi_response['site_revoked']); - - // Address any actions taken based on a site environment change. - if (!empty($changed_action) || $site_blocked) { - if ($changed_action == 'create' && isset($spi_response['body']['site_uuid'])) { - $config_set->set('spi.site_uuid', $spi_response['body']['site_uuid'])->save(); - } - elseif (($changed_action == 'block' && array_key_exists('spi_error', $spi_response['body']) && empty($spi_response['body']['spi_error'])) || $site_blocked) { - $config_set->set('spi.blocked', TRUE)->save(); - } - elseif ($changed_action == 'unblock' && array_key_exists('spi_error', $spi_response['body']) && empty($spi_response['body']['spi_error'])) { - $config_set->set('spi.blocked', FALSE)->save(); - } - - // If there were no errors, clear any pending actions. - if (empty($spi_response['body']['spi_error'])) { - $config_set->clear('spi.environment_changes')->save(); - } - } - - // Check result for command to update SPI definition. - $update = isset($spi_response['body']['update_spi_definition']) ? $spi_response['body']['update_spi_definition'] : FALSE; - if ($update === TRUE) { - $this->updateDefinition(); - } - // Check for set_variables command. - $set_variables = isset($spi_response['body']['set_variables']) ? $spi_response['body']['set_variables'] : FALSE; - if ($set_variables !== FALSE) { - $variablesController = new VariablesController(); - $variablesController->setVariables($set_variables); - } - // Log messages. - $messages = isset($spi_response['body']['nspi_messages']) ? $spi_response['body']['nspi_messages'] : FALSE; - if ($messages !== FALSE) { - $this->getLogger('acquia spi')->notice('SPI update server response messages: @messages', ['@messages' => implode(', ', $messages)]); - } - } - - /** - * Checks if NSPI server has an updated SPI data definition. - * - * If it does, then this function updates local copy of SPI definition data. - * - * @return bool - * True if SPI definition data has been updated. - */ - private function updateDefinition() { - $core_version = substr(\Drupal::VERSION, 0, 1); - $spi_def_end_point = '/spi_def/get/' . $core_version; - - $response = $this->client->getDefinition($spi_def_end_point); - - if (!$response) { - $this->getLogger('acquia spi')->error('Failed to obtain latest SPI data definition.'); - return FALSE; - } - else { - $response_data = $response; - $expected_data_types = [ - 'drupal_version' => 'string', - 'timestamp' => 'string', - 'acquia_spi_variables' => 'array', - ]; - // Make sure that $response_data contains everything expected. - foreach ($expected_data_types as $key => $values) { - if (!array_key_exists($key, $response_data) || gettype($response_data[$key]) != $expected_data_types[$key]) { - $this->getLogger('acquia spi') - ->error('Received SPI data definition does not match expected pattern while checking "@key". Received and expected data: @data', [ - '@key' => $key, - '@data' => var_export(array_merge(['expected_data' => $expected_data_types], ['response_data' => $response_data]), TRUE), - ]); - return FALSE; - } - } - if ($response_data['drupal_version'] != $core_version) { - $this->getLogger('acquia spi')->notice('Received SPI data definition does not match with current version of your Drupal installation. Data received for Drupal @version', ['@version' => $response_data['drupal_version']]); - return FALSE; - } - } - - // NSPI response is in expected format. - if ((int) $response_data['timestamp'] > (int) $this->state()->get('acquia_spi_data.def_timestamp', 0)) { - // Compare stored variable names to incoming and report on update. - $old_vars = $this->state()->get('acquia_spi_data.def_vars', []); - $new_vars = $response_data['acquia_spi_variables']; - $new_optional_vars = 0; - foreach ($new_vars as $new_var_name => $new_var) { - // Count if received from NSPI optional variable is not present in old - // local SPI definition or if it already was in old SPI definition, but - // was not optional. - if ($new_var['optional'] && !array_key_exists($new_var_name, $old_vars) || - $new_var['optional'] && isset($old_vars[$new_var_name]) && !$old_vars[$new_var_name]['optional']) { - $new_optional_vars++; - } - } - // Clean up waived vars that are not exposed by NSPI anymore. - $waived_spi_def_vars = $this->state()->get('acquia_spi_data.def_waived_vars', []); - $changed_bool = FALSE; - foreach ($waived_spi_def_vars as $key => $waived_var) { - if (!in_array($waived_var, $new_vars)) { - unset($waived_spi_def_vars[$key]); - $changed_bool = TRUE; - } - } - - if ($changed_bool) { - $this->state()->set('acquia_spi_data.def_waived_vars', $waived_spi_def_vars); - } - // Finally, save SPI definition data. - if ($new_optional_vars > 0) { - $this->state()->set('acquia_spi_data.new_optional_data', 1); - } - $this->state()->set('acquia_spi_data.def_timestamp', (int) $response_data['timestamp']); - $this->state()->set('acquia_spi_data.def_vars', $response_data['acquia_spi_variables']); - return TRUE; - } - return FALSE; - } - - /** - * Access callback check for SPI send independent call. - */ - public function sendAccess() { - $request = \Drupal::request(); - $storage = new Storage(); - $acquia_key = $storage->getKey(); - if (!empty($acquia_key) && $request->get('key')) { - $key = sha1(\Drupal::service('private_key')->get()); - if ($key === $request->get('key')) { - return AccessResultAllowed::allowed(); - } - } - return AccessResultForbidden::forbidden(); - } - -} diff --git a/docroot/modules/contrib/acquia_connector/src/Controller/StartController.php b/docroot/modules/contrib/acquia_connector/src/Controller/StartController.php deleted file mode 100644 index ab253b2436..0000000000 --- a/docroot/modules/contrib/acquia_connector/src/Controller/StartController.php +++ /dev/null @@ -1,87 +0,0 @@ -t('Get an Acquia Cloud Free subscription'); - - $path = drupal_get_path('module', 'acquia_connector'); - - $build['#attached']['library'][] = 'acquia_connector/acquia_connector.form'; - - $banner = [ - '#theme' => 'image', - '#attributes' => [ - 'src' => Url::fromUri('base:' . $path . '/images/action.png', ['absolute' => TRUE])->toString(), - ], - ]; - $uri = Url::fromRoute('acquia_connector.setup', [], ['absolute' => TRUE])->toString(); - $banner = '' . render($banner) . ''; - - $output = '' . $this->t('A suite of products and services to create & maintain killer web experiences built on Drupal') . '
'; - $output .= '' . $this->t("Tap the collective knowledge of Acquia’s technical support team & partners.") . '
'; - $output .= '' . $this->t('Enhance and extend your site with an array of services from Acquia & our partners.', [':services' => 'https://www.acquia.com/products-services/acquia-cloud']) . '
'; - $output .= '' . $this->t("Experienced Drupalists are available to support you whenever you need it.") . '
'; - $output .= 'This form is used to address changes in your site's environment. No changes are currently detected.
"); - return $form; - } - elseif ($blocked) { - $form['env_change_action'] = [ - '#type' => 'checkboxes', - '#title' => $this->t('The Acquia Connector is disabled and is not sending site profile data to Acquia Cloud for evaluation.'), - '#options' => [ - 'unblock' => $this->t('Enable this site and send data to Acquia Cloud.'), - ], - '#required' => TRUE, - ]; - } - else { - $env_changes = $config->get('spi.environment_changes'); - $off_acquia_hosting = array_key_exists('acquia_hosted', $env_changes) && !$acquia_hosted; - - $form['env'] = [ - '#type' => 'fieldset', - '#title' => $this->t('The following changes have been detected in your site environment:'), - '#description' => [ - '#theme' => 'item_list', - '#items' => $env_changes, - ], - ]; - - $form['env_change_action'] = [ - '#type' => 'radios', - '#title' => $this->t('How would you like to proceed?'), - '#options' => [ - 'block' => $this->t('Disable this site from sending profile data to Acquia Cloud.'), - 'update' => $this->t('Update existing site with these changes.'), - 'create' => $this->t('Track this as a new site on Acquia Cloud.'), - ], - '#required' => TRUE, - '#default_value' => $config->get('spi.environment_changed_action'), - ]; - - $form['identification'] = [ - '#type' => 'fieldset', - '#title' => $this->t('Site Identification'), - '#collapsible' => FALSE, - '#states' => [ - 'visible' => [ - ':input[name="env_change_action"]' => ['value' => 'create'], - ], - ], - ]; - - $form['identification']['site'] = [ - '#prefix' => 'A suite of products and services to create & maintain killer web experiences built on Drupal
+Tap the collective knowledge of Acquia’s technical support team & partners.
+Enhance and extend your site with an array of services from Acquia & our partners.
+
+ */
+class CasterTest extends TestCase
+{
+ use VarDumperTestTrait;
+
+ private static $referenceArray = [
+ 'null' => null,
+ 'empty' => false,
+ 'public' => 'pub',
+ "\0~\0virtual" => 'virt',
+ "\0+\0dynamic" => 'dyn',
+ "\0*\0protected" => 'prot',
+ "\0Foo\0private" => 'priv',
+ ];
+
+ /**
+ * @dataProvider provideFilter
+ */
+ public function testFilter($filter, $expectedDiff, $listedProperties = null)
+ {
+ if (null === $listedProperties) {
+ $filteredArray = Caster::filter(self::$referenceArray, $filter);
+ } else {
+ $filteredArray = Caster::filter(self::$referenceArray, $filter, $listedProperties);
+ }
+
+ $this->assertSame($expectedDiff, array_diff_assoc(self::$referenceArray, $filteredArray));
+ }
+
+ public static function provideFilter()
+ {
+ return [
+ [
+ 0,
+ [],
+ ],
+ [
+ Caster::EXCLUDE_PUBLIC,
+ [
+ 'null' => null,
+ 'empty' => false,
+ 'public' => 'pub',
+ ],
+ ],
+ [
+ Caster::EXCLUDE_NULL,
+ [
+ 'null' => null,
+ ],
+ ],
+ [
+ Caster::EXCLUDE_EMPTY,
+ [
+ 'null' => null,
+ 'empty' => false,
+ ],
+ ],
+ [
+ Caster::EXCLUDE_VIRTUAL,
+ [
+ "\0~\0virtual" => 'virt',
+ ],
+ ],
+ [
+ Caster::EXCLUDE_DYNAMIC,
+ [
+ "\0+\0dynamic" => 'dyn',
+ ],
+ ],
+ [
+ Caster::EXCLUDE_PROTECTED,
+ [
+ "\0*\0protected" => 'prot',
+ ],
+ ],
+ [
+ Caster::EXCLUDE_PRIVATE,
+ [
+ "\0Foo\0private" => 'priv',
+ ],
+ ],
+ [
+ Caster::EXCLUDE_VERBOSE,
+ [
+ 'public' => 'pub',
+ "\0*\0protected" => 'prot',
+ ],
+ ['public', "\0*\0protected"],
+ ],
+ [
+ Caster::EXCLUDE_NOT_IMPORTANT,
+ [
+ 'null' => null,
+ 'empty' => false,
+ "\0~\0virtual" => 'virt',
+ "\0+\0dynamic" => 'dyn',
+ "\0Foo\0private" => 'priv',
+ ],
+ ['public', "\0*\0protected"],
+ ],
+ [
+ Caster::EXCLUDE_VIRTUAL | Caster::EXCLUDE_DYNAMIC,
+ [
+ "\0~\0virtual" => 'virt',
+ "\0+\0dynamic" => 'dyn',
+ ],
+ ],
+ [
+ Caster::EXCLUDE_NOT_IMPORTANT | Caster::EXCLUDE_VERBOSE,
+ self::$referenceArray,
+ ['public', "\0*\0protected"],
+ ],
+ [
+ Caster::EXCLUDE_NOT_IMPORTANT | Caster::EXCLUDE_EMPTY,
+ [
+ 'null' => null,
+ 'empty' => false,
+ "\0~\0virtual" => 'virt',
+ "\0+\0dynamic" => 'dyn',
+ "\0*\0protected" => 'prot',
+ "\0Foo\0private" => 'priv',
+ ],
+ ['public', 'empty'],
+ ],
+ [
+ Caster::EXCLUDE_VERBOSE | Caster::EXCLUDE_EMPTY | Caster::EXCLUDE_STRICT,
+ [
+ 'empty' => false,
+ ],
+ ['public', 'empty'],
+ ],
+ ];
+ }
+
+ public function testAnonymousClass()
+ {
+ $c = eval('return new class extends stdClass { private $foo = "foo"; };');
+
+ $this->assertDumpMatchesFormat(
+ <<<'EOTXT'
+stdClass@anonymous {
+ -foo: "foo"
+}
+EOTXT
+ , $c
+ );
+
+ $c = eval('return new class implements \Countable { private $foo = "foo"; public function count(): int { return 0; } };');
+
+ $this->assertDumpMatchesFormat(
+ <<<'EOTXT'
+Countable@anonymous {
+ -foo: "foo"
+}
+EOTXT
+ , $c
+ );
+ }
+
+ public function testTypeErrorInDebugInfo()
+ {
+ $this->assertDumpMatchesFormat('class@anonymous {}', new class() {
+ public function __debugInfo(): array
+ {
+ return ['class' => \get_class(null)];
+ }
+ });
+ }
+}
diff --git a/vendor/symfony/var-dumper/Tests/Caster/DateCasterTest.php b/vendor/symfony/var-dumper/Tests/Caster/DateCasterTest.php
new file mode 100644
index 0000000000..40835671b5
--- /dev/null
+++ b/vendor/symfony/var-dumper/Tests/Caster/DateCasterTest.php
@@ -0,0 +1,409 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\VarDumper\Tests\Caster;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\VarDumper\Caster\Caster;
+use Symfony\Component\VarDumper\Caster\DateCaster;
+use Symfony\Component\VarDumper\Cloner\Stub;
+use Symfony\Component\VarDumper\Test\VarDumperTestTrait;
+use Symfony\Component\VarDumper\Tests\Fixtures\DateTimeChild;
+
+/**
+ * @author Dany Maillard
+ */
+class PdoCasterTest extends TestCase
+{
+ use VarDumperTestTrait;
+
+ /**
+ * @requires extension pdo_sqlite
+ */
+ public function testCastPdo()
+ {
+ $pdo = new \PDO('sqlite::memory:');
+ $pdo->setAttribute(\PDO::ATTR_STATEMENT_CLASS, ['PDOStatement', [$pdo]]);
+ $pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
+
+ $cast = PdoCaster::castPdo($pdo, [], new Stub(), false);
+
+ $this->assertInstanceOf(EnumStub::class, $cast["\0~\0attributes"]);
+
+ $attr = $cast["\0~\0attributes"] = $cast["\0~\0attributes"]->value;
+ $this->assertInstanceOf(ConstStub::class, $attr['CASE']);
+ $this->assertSame('NATURAL', $attr['CASE']->class);
+ $this->assertSame('BOTH', $attr['DEFAULT_FETCH_MODE']->class);
+
+ $xDump = <<<'EODUMP'
+array:2 [
+ "\x00~\x00inTransaction" => false
+ "\x00~\x00attributes" => array:9 [
+ "CASE" => NATURAL
+ "ERRMODE" => EXCEPTION
+ "PERSISTENT" => false
+ "DRIVER_NAME" => "sqlite"
+ "ORACLE_NULLS" => NATURAL
+ "CLIENT_VERSION" => "%s"
+ "SERVER_VERSION" => "%s"
+ "STATEMENT_CLASS" => array:%d [
+ 0 => "PDOStatement"%A
+ ]
+ "DEFAULT_FETCH_MODE" => BOTH
+ ]
+]
+EODUMP;
+
+ $this->assertDumpMatchesFormat($xDump, $cast);
+ }
+}
diff --git a/vendor/symfony/var-dumper/Tests/Caster/RdKafkaCasterTest.php b/vendor/symfony/var-dumper/Tests/Caster/RdKafkaCasterTest.php
new file mode 100644
index 0000000000..65e8ec3b8f
--- /dev/null
+++ b/vendor/symfony/var-dumper/Tests/Caster/RdKafkaCasterTest.php
@@ -0,0 +1,224 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\VarDumper\Tests\Caster;
+
+use PHPUnit\Framework\TestCase;
+use RdKafka\Conf;
+use RdKafka\KafkaConsumer;
+use RdKafka\Producer;
+use RdKafka\TopicConf;
+use Symfony\Component\VarDumper\Test\VarDumperTestTrait;
+
+/**
+ * @requires extension rdkafka
+ *
+ * @group integration
+ */
+class RdKafkaCasterTest extends TestCase
+{
+ use VarDumperTestTrait;
+
+ private const TOPIC = 'test-topic';
+ private const GROUP_ID = 'test-group-id';
+
+ private $hasBroker = false;
+ private $broker;
+
+ protected function setUp(): void
+ {
+ if (!$this->hasBroker && getenv('KAFKA_BROKER')) {
+ $this->broker = getenv('KAFKA_BROKER');
+ $this->hasBroker = true;
+ }
+ }
+
+ public function testDumpConf()
+ {
+ $conf = new Conf();
+ $conf->setErrorCb(function ($kafka, $err, $reason) {});
+ $conf->setDrMsgCb(function () {});
+ $conf->setRebalanceCb(function () {});
+
+ // BC with earlier version of extension rdkafka
+ foreach (['setLogCb', 'setOffsetCommitCb', 'setStatsCb', 'setConsumeCb'] as $method) {
+ if (method_exists($conf, $method)) {
+ $conf->{$method}(function () {});
+ }
+ }
+
+ $expectedDump = <<
+ *
+ * @requires extension redis
+ *
+ * @group integration
+ */
+class RedisCasterTest extends TestCase
+{
+ use VarDumperTestTrait;
+
+ public function testNotConnected()
+ {
+ $redis = new \Redis();
+
+ $xCast = <<<'EODUMP'
+Redis {
+ isConnected: false
+}
+EODUMP;
+
+ $this->assertDumpMatchesFormat($xCast, $redis);
+ }
+
+ public function testConnected()
+ {
+ $redisHost = explode(':', getenv('REDIS_HOST')) + [1 => 6379];
+ $redis = new \Redis();
+ try {
+ $redis->connect(...$redisHost);
+ } catch (\Exception $e) {
+ self::markTestSkipped($e->getMessage());
+ }
+
+ $xCast = <<
+ */
+class ReflectionCasterTest extends TestCase
+{
+ use VarDumperTestTrait;
+
+ public function testReflectionCaster()
+ {
+ $var = new \ReflectionClass(\ReflectionClass::class);
+
+ $this->assertDumpMatchesFormat(
+ <<<'EOTXT'
+ReflectionClass {
+ +name: "ReflectionClass"
+%Aimplements: array:%d [
+%A]
+ constants: array:%d [
+ 0 => ReflectionClassConstant {
+ +name: "IS_IMPLICIT_ABSTRACT"
+ +class: "ReflectionClass"
+ modifiers: "public"
+ value: 16
+ }
+ 1 => ReflectionClassConstant {
+ +name: "IS_EXPLICIT_ABSTRACT"
+ +class: "ReflectionClass"
+ modifiers: "public"
+ value: %d
+ }
+ 2 => ReflectionClassConstant {
+ +name: "IS_FINAL"
+ +class: "ReflectionClass"
+ modifiers: "public"
+ value: %d
+ }
+%A]
+ properties: array:%d [
+ "name" => ReflectionProperty {
+%A +name: "name"
+ +class: "ReflectionClass"
+%A modifiers: "public"
+ }
+%A]
+ methods: array:%d [
+%A
+ "__construct" => ReflectionMethod {
+ +name: "__construct"
+ +class: "ReflectionClass"
+%A parameters: {
+ $%s: ReflectionParameter {
+%A position: 0
+%A
+}
+EOTXT
+ , $var
+ );
+ }
+
+ public function testClosureCaster()
+ {
+ $a = $b = 123;
+ $var = function ($x) use ($a, &$b) {};
+
+ $this->assertDumpMatchesFormat(
+ <<<'EOTXT'
+Closure($x) {
+%Ause: {
+ $a: 123
+ $b: & 123
+ }
+ file: "%sReflectionCasterTest.php"
+ line: "88 to 88"
+}
+EOTXT
+ , $var
+ );
+ }
+
+ public function testFromCallableClosureCaster()
+ {
+ $var = [
+ (new \ReflectionMethod($this, __FUNCTION__))->getClosure($this),
+ (new \ReflectionMethod(__CLASS__, 'stub'))->getClosure(),
+ ];
+
+ $this->assertDumpMatchesFormat(
+ <<
+ */
+class VarClonerTest extends TestCase
+{
+ public function testMaxIntBoundary()
+ {
+ $data = [\PHP_INT_MAX => 123];
+
+ $cloner = new VarCloner();
+ $clone = $cloner->cloneVar($data);
+
+ $expected = <<
+ CliDescriptorTest.php on line 30
+
+
+
+
+
+ */
+class CliDumperTest extends TestCase
+{
+ use VarDumperTestTrait;
+
+ public function testGet()
+ {
+ require __DIR__.'/../Fixtures/dumb-var.php';
+
+ $dumper = new CliDumper('php://output');
+ $dumper->setColors(false);
+ $cloner = new VarCloner();
+ $cloner->addCasters([
+ ':stream' => function ($res, $a) {
+ unset($a['uri'], $a['wrapper_data']);
+
+ return $a;
+ },
+ 'Symfony\Component\VarDumper\Tests\Fixture\DumbFoo' => function ($foo, $a) {
+ $a['foo'] = new CutStub($a['foo']);
+
+ return $a;
+ },
+ ]);
+ $data = $cloner->cloneVar($var);
+
+ ob_start();
+ $dumper->dump($data);
+ $out = ob_get_clean();
+ $out = preg_replace('/[ \t]+$/m', '', $out);
+ $intMax = \PHP_INT_MAX;
+ $res = (int) $var['res'];
+
+ $this->assertStringMatchesFormat(
+ <<
+ */
+class HtmlDumperTest extends TestCase
+{
+ public function testGet()
+ {
+ if (\ini_get('xdebug.file_link_format') || get_cfg_var('xdebug.file_link_format')) {
+ $this->markTestSkipped('A custom file_link_format is defined.');
+ }
+
+ require __DIR__.'/../Fixtures/dumb-var.php';
+
+ $dumper = new HtmlDumper('php://output');
+ $dumper->setDumpHeader('-
+
+ -
+
+
+
+ $
bin/phpunit
+
+ GET
http://localhost/foo