diff --git a/composer.json b/composer.json index f8ce40571b..60908cec58 100644 --- a/composer.json +++ b/composer.json @@ -9,7 +9,7 @@ "drupal/core-recommended": "^9.2", "drupal/stage_file_proxy": "^1.1.0", "drupal/google_analytics": "^3.1.0", - "drupal/acquia_connector": "^1.25.0", + "drupal/acquia_connector": "^4.0", "drupal/youtube": "^1.2.0", "drupal/bootstrap": "^3.23", "acquia/cohesion": "^6.9", diff --git a/composer.lock b/composer.lock index 73ea276847..3df9a19b18 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "d14c0ff2b4557fca62a9d757b5f0c743", + "content-hash": "362c271a4b69efaea012636825dc702d", "packages": [ { "name": "acquia/cohesion", @@ -1374,48 +1374,40 @@ }, { "name": "drupal/acquia_connector", - "version": "1.26.0", + "version": "4.0.4", "source": { "type": "git", "url": "https://git.drupalcode.org/project/acquia_connector.git", - "reference": "8.x-1.26" + "reference": "4.0.4" }, "dist": { "type": "zip", - "url": "https://ftp.drupal.org/files/projects/acquia_connector-8.x-1.26.zip", - "reference": "8.x-1.26", - "shasum": "632932100c3d2946a11055c472c7e6dbfc1269bd" + "url": "https://ftp.drupal.org/files/projects/acquia_connector-4.0.4.zip", + "reference": "4.0.4", + "shasum": "4c37d429a11c2121df00c4ff4dccead354754f0c" }, "require": { - "drupal/core": "^8.8 || ^9", + "drupal/core": ">=8.9 <11.0.0-stable", "ext-json": "*" }, - "require-dev": { - "acquia/coding-standards": "^0.4.1", - "dealerdirect/phpcodesniffer-composer-installer": "^0.5.0", - "drupal/acquia_search": "*", - "drupal/search_api": "^1.17", - "drupal/search_api_solr": "~1.0" - }, "type": "drupal-module", "extra": { "drupal": { - "version": "8.x-1.26", - "datestamp": "1617213585", + "version": "4.0.4", + "datestamp": "1680704017", "security-coverage": { "status": "covered", "message": "Covered by Drupal's security advisory policy" } }, "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 + } }, "notification-url": "https://packages.drupal.org/8/downloads", "license": [ @@ -2928,16 +2920,16 @@ }, { "name": "guzzlehttp/psr7", - "version": "1.9.0", + "version": "1.9.1", "source": { "type": "git", "url": "https://github.com/guzzle/psr7.git", - "reference": "e98e3e6d4f86621a9b75f623996e6bbdeb4b9318" + "reference": "e4490cabc77465aaee90b20cfc9a770f8c04be6b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/psr7/zipball/e98e3e6d4f86621a9b75f623996e6bbdeb4b9318", - "reference": "e98e3e6d4f86621a9b75f623996e6bbdeb4b9318", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/e4490cabc77465aaee90b20cfc9a770f8c04be6b", + "reference": "e4490cabc77465aaee90b20cfc9a770f8c04be6b", "shasum": "" }, "require": { @@ -2956,11 +2948,6 @@ "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.9-dev" - } - }, "autoload": { "files": [ "src/functions_include.php" @@ -3018,7 +3005,7 @@ ], "support": { "issues": "https://github.com/guzzle/psr7/issues", - "source": "https://github.com/guzzle/psr7/tree/1.9.0" + "source": "https://github.com/guzzle/psr7/tree/1.9.1" }, "funding": [ { @@ -3034,7 +3021,7 @@ "type": "tidelift" } ], - "time": "2022-06-20T21:43:03+00:00" + "time": "2023-04-17T16:00:37+00:00" }, { "name": "laminas/laminas-diactoros", @@ -3973,16 +3960,16 @@ }, { "name": "pear/pear-core-minimal", - "version": "v1.10.11", + "version": "v1.10.13", "source": { "type": "git", "url": "https://github.com/pear/pear-core-minimal.git", - "reference": "68d0d32ada737153b7e93b8d3c710ebe70ac867d" + "reference": "aed862e95fd286c53cc546734868dc38ff4b5b1d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/pear/pear-core-minimal/zipball/68d0d32ada737153b7e93b8d3c710ebe70ac867d", - "reference": "68d0d32ada737153b7e93b8d3c710ebe70ac867d", + "url": "https://api.github.com/repos/pear/pear-core-minimal/zipball/aed862e95fd286c53cc546734868dc38ff4b5b1d", + "reference": "aed862e95fd286c53cc546734868dc38ff4b5b1d", "shasum": "" }, "require": { @@ -4017,7 +4004,7 @@ "issues": "http://pear.php.net/bugs/search.php?cmd=display&package_name[]=PEAR", "source": "https://github.com/pear/pear-core-minimal" }, - "time": "2021-08-10T22:31:03+00:00" + "time": "2023-04-19T19:15:47+00:00" }, { "name": "pear/pear_exception", @@ -4287,21 +4274,21 @@ }, { "name": "psr/http-factory", - "version": "1.0.1", + "version": "1.0.2", "source": { "type": "git", "url": "https://github.com/php-fig/http-factory.git", - "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be" + "reference": "e616d01114759c4c489f93b099585439f795fe35" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-factory/zipball/12ac7fcd07e5b077433f5f2bee95b3a771bf61be", - "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/e616d01114759c4c489f93b099585439f795fe35", + "reference": "e616d01114759c4c489f93b099585439f795fe35", "shasum": "" }, "require": { "php": ">=7.0.0", - "psr/http-message": "^1.0" + "psr/http-message": "^1.0 || ^2.0" }, "type": "library", "extra": { @@ -4321,7 +4308,7 @@ "authors": [ { "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" + "homepage": "https://www.php-fig.org/" } ], "description": "Common interfaces for PSR-7 HTTP message factories", @@ -4336,9 +4323,9 @@ "response" ], "support": { - "source": "https://github.com/php-fig/http-factory/tree/master" + "source": "https://github.com/php-fig/http-factory/tree/1.0.2" }, - "time": "2019-04-30T12:38:16+00:00" + "time": "2023-04-10T20:10:41+00:00" }, { "name": "psr/http-message", @@ -7029,16 +7016,16 @@ }, { "name": "symfony/var-dumper", - "version": "v5.4.21", + "version": "v5.4.22", "source": { "type": "git", "url": "https://github.com/symfony/var-dumper.git", - "reference": "6c5ac3a1be8b849d59a1a77877ee110e1b55eb74" + "reference": "e2edac9ce47e6df07e38143c7cfa6bdbc1a6dcc4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/6c5ac3a1be8b849d59a1a77877ee110e1b55eb74", - "reference": "6c5ac3a1be8b849d59a1a77877ee110e1b55eb74", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/e2edac9ce47e6df07e38143c7cfa6bdbc1a6dcc4", + "reference": "e2edac9ce47e6df07e38143c7cfa6bdbc1a6dcc4", "shasum": "" }, "require": { @@ -7098,7 +7085,7 @@ "dump" ], "support": { - "source": "https://github.com/symfony/var-dumper/tree/v5.4.21" + "source": "https://github.com/symfony/var-dumper/tree/v5.4.22" }, "funding": [ { @@ -7114,7 +7101,7 @@ "type": "tidelift" } ], - "time": "2023-02-23T10:00:28+00:00" + "time": "2023-03-25T09:27:28+00:00" }, { "name": "symfony/yaml", diff --git a/docroot/modules/contrib/acquia_connector/.gitignore b/docroot/modules/contrib/acquia_connector/.gitignore index f53009582c..5d871155e3 100644 --- a/docroot/modules/contrib/acquia_connector/.gitignore +++ b/docroot/modules/contrib/acquia_connector/.gitignore @@ -1,2 +1,4 @@ -vendor -.phpcs-cache \ No newline at end of file +.DS_Store + .idea/ + .php_cache + vendor/ diff --git a/docroot/modules/contrib/acquia_connector/README.md b/docroot/modules/contrib/acquia_connector/README.md new file mode 100644 index 0000000000..1267ad71d2 --- /dev/null +++ b/docroot/modules/contrib/acquia_connector/README.md @@ -0,0 +1,31 @@ +# Acquia Connector + +[Acquia cloud](https://docs.acquia.com/cloud-platform/onboarding) [1] enhances the Drupal experience by providing the support +and network services to operate a trouble-free Drupal website. Acquia cloud +subscribers gain access to remote network services and documentation. Premium +subscriptions provide web-based support ticket management, as well as email and +telephone support. + +These modules allow you to connect any Drupal 8.x site to Acquia Insight. + +[1] https://docs.acquia.com/cloud-platform/onboarding + +## Installation + +Consult the [online documentation](https://docs.acquia.com/cloud-platform/onboarding/install) [2] for installation +instructions. + +[2] https://docs.acquia.com/cloud-platform/onboarding/install + +## Maintainers + +These modules are maintained by developers at Acquia. For more information on +the company and our offerings, see + +## Issues + +Contact Acquia Support if you have support questions regarding your site. + +If you have issues with the submodules included in the Acquia +Connector package, you are also welcome to submit issues at + (all submitted issues are public). diff --git a/docroot/modules/contrib/acquia_connector/README.txt b/docroot/modules/contrib/acquia_connector/README.txt deleted file mode 100644 index 73cb3d2cab..0000000000 --- a/docroot/modules/contrib/acquia_connector/README.txt +++ /dev/null @@ -1,43 +0,0 @@ -Acquia Connector modules -================================================================================ - -Acquia Insight [1] enhances the Drupal experience by providing the support -and network services to operate a trouble-free Drupal website. Acquia Insight -subscribers gain access to remote network services and documentation. Premium -subscriptions provide web-based support ticket management, as well as email and -telephone support. - -These modules allow you to connect any Drupal 8.x site to Acquia Insight. - -[1] https://www.acquia.com/products-services/acquia-insight-subscription - -Modules in this project --------------------------------------------------------------------------------- - -Acquia Search: Provides integration between your Drupal site and Acquia's -hosted search service. Requires Apache Solr Search Integration module. - - NOTE: See acquia_search/README.txt for more information for this module. - -Installation --------------------------------------------------------------------------------- - -Consult the online documentation at -https://docs.acquia.com/acquia-cloud/insight/install/ for installation -instructions. - - -Maintainers --------------------------------------------------------------------------------- - -These modules are maintained by developers at Acquia. For more information on -the company and our offerings, see https://www.acquia.com/ - -Issues --------------------------------------------------------------------------------- - -Contact Acquia Support if you have support questions regarding your site. - -If you have issues with the submodules included in the Acquia -Connector package, you are also welcome to submit issues at -https://drupal.org/project/acquia_connector (all submitted issues are public). diff --git a/docroot/modules/contrib/acquia_connector/acquia_connector.api.php b/docroot/modules/contrib/acquia_connector/acquia_connector.api.php deleted file mode 100644 index aea471b71f..0000000000 --- a/docroot/modules/contrib/acquia_connector/acquia_connector.api.php +++ /dev/null @@ -1,75 +0,0 @@ - array(). - */ -function hook_acquia_connector_spi_get() { - $data['example'] = [ - 'result' => TRUE, - 'value' => '9000', - ]; - return $data; -} - -/** - * Include data to be sent to Acquia Insight as part of the SPI process. - * - * This data will be stored on Acquia's servers in an unencrypted database, so - * be careful not to send sensitive information in any field. Multiple tests can - * also be added per callback provided that each test has a unique identifier. - * - * @return array - * An array of user-contributed test data keyed by unique identifier. - * - (string) description: Detailed information regarding test, its impact, - * and other relevant details. Cannot exceed 1024 characters. - * - (string) solved_message: The message to display when the test has - * succeeded. Cannot exceed 1024 characters. - * - (string) failed_message: The message to display when the test has - * failed. Cannot exceed 1024 characters. - * - (boolean) solved: A flag indicating whether or not the test was - * successful. - * - (string) fix_details: Information on how to fix or resolve the test if - * failed. Cannot exceed 1024 characters. - * - (string) category: The category to place the test within. Must be either - * 'performance', 'security, or 'best_practices'. - * - (int) severity: The priority level of the custom test. Must be either - * 0, 1, 2, 4, 8, 16, 32, 64, or 128. Higher severities impact - * the Insight score proportionally. - */ -function hook_acquia_connector_spi_test() { - return [ - 'unique_example' => [ - 'description' => 'This example test is useful.', - 'solved_message' => 'The test was successful', - 'failed_message' => 'The test has failed', - 'solved' => TRUE, - 'fix_details' => 'Please resolve this issue using this fix information.', - 'category' => 'best_practices', - 'severity' => 0, - ], - ]; -} diff --git a/docroot/modules/contrib/acquia_connector/acquia_connector.info.yml b/docroot/modules/contrib/acquia_connector/acquia_connector.info.yml index b76f6c8a34..38cf065145 100644 --- a/docroot/modules/contrib/acquia_connector/acquia_connector.info.yml +++ b/docroot/modules/contrib/acquia_connector/acquia_connector.info.yml @@ -1,12 +1,14 @@ name: Acquia Connector type: module -description: 'Allows Drupal to securely communicate with the Acquia Subscription, and checks for updates to Acquia Drupal.' +description: 'Allows Drupal to securely communicate with Acquia.' package: Acquia -core: 8.x -core_version_requirement: ^8 || ^9 +core_version_requirement: '>=8.9 <11.0.0-stable' configure: acquia_connector.settings -# Information added by Drupal.org packaging script on 2021-03-31 -version: '8.x-1.26' +dependencies: + - drupal:path_alias + +# Information added by Drupal.org packaging script on 2023-04-05 +version: '4.0.4' project: 'acquia_connector' -datestamp: 1617213587 +datestamp: 1680704021 diff --git a/docroot/modules/contrib/acquia_connector/acquia_connector.install b/docroot/modules/contrib/acquia_connector/acquia_connector.install index d1fd33764d..afb2f2b7b8 100644 --- a/docroot/modules/contrib/acquia_connector/acquia_connector.install +++ b/docroot/modules/contrib/acquia_connector/acquia_connector.install @@ -5,17 +5,15 @@ * Install, update, and uninstall functions for the Acquia Connector module. */ -use Drupal\acquia_connector\Controller\TestStatusController; -use Drupal\acquia_connector\Helper\Storage; -use Drupal\acquia_connector\Subscription; use Drupal\Core\Url; /** * Implements hook_uninstall(). */ function acquia_connector_uninstall() { - $storage = new Storage(); - $storage->deleteAllData(); + $subscription = \Drupal::service('acquia_connector.subscription'); + $settings = $subscription->getSettings(); + $settings->deleteAllData(); } /** @@ -24,300 +22,72 @@ function acquia_connector_uninstall() { function acquia_connector_requirements($phase) { $requirements = []; - switch ($phase) { - case 'runtime': - // Unsupported Warning to upgrade to 3.x - if (strtotime('1 May 2021') < time()) { - $ret['acquia_connector_1_eol'] = [ - 'title' => t('Acquia Connector 8.x-1.x'), - 'value' => t('The 1.x version reached EOL at 2021-05-01!'), - 'description' => t('This version of connector is unsupported. Upgrade to 3.x! This version is fully backwards compatible and works with all versions of Acquia Search'), - 'severity' => REQUIREMENT_ERROR, - ]; - } - elseif (strtotime('1 May 2021') > time()) { - $ret['acquia_connector_1_eol'] = [ - 'title' => t('Acquia Connector 8.x-1.x'), - 'value' => t('This version will be EOL on 2021-05-01!'), - 'description' => t('This version of connector will soon be unsupported. Upgrade to 3.x. It is fully backwards compatible and works with all versions of Acquia Search'), - 'severity' => REQUIREMENT_WARNING, - ]; - } - acquia_connector_load_versions(); - $config = \Drupal::config('acquia_connector.settings'); - $use_cron = $config->get('spi.use_cron'); - $last_sent = \Drupal::state()->get('acquia_connector.cron_last', 0); - $subscription = new Subscription(); - $has_credentials = $subscription->hasCredentials(); - - if ($has_credentials) { - $key = sha1(Drupal::service('private_key')->get()); - $description = ''; - $ago = \Drupal::time()->getRequestTime() - $last_sent; - - $blocked = $config->get('spi.blocked'); - $environment_change = \Drupal::service('acquia_connector.spi')->checkEnvironmentChange(); - $is_acquia_hosted = \Drupal::service('acquia_connector.spi')->checkAcquiaHosted(); - $name_required = is_null($config->get('spi.site_name')) && is_null($config->get('spi.site_machine_name')) && !$is_acquia_hosted; - $interval = $config->get('cron_interval'); - if ($config->get('cron_interval_override')) { - $interval = $config->get('cron_interval_override'); - } - - $variables = [ - ':spi-send' => Url::fromRoute('acquia_connector.send', [], [ - 'query' => [ - 'destination' => Url::fromRoute('system.status')->toString(), - 'key' => $key, - ], - 'absolute' => TRUE, - ])->toString(), - '@interval' => $interval, - ]; - - // Default status. - $severity = REQUIREMENT_OK; - $value = t('Last sent @time ago', ['@time' => \Drupal::service('date.formatter')->formatInterval($ago)]); - $description = t('SPI data will be sent once every @interval minutes once cron is called. You can manually send SPI data.', $variables); - - // Requirement 'value' indicates when or if data was last sent. - if ($last_sent == 0) { - $severity = REQUIREMENT_WARNING; - $value = t('SPI data has not been sent'); - } - // 1.5 days ago. - elseif ($ago >= 60 * 60 * 36) { - $severity = REQUIREMENT_WARNING; - $value = t('SPI data has not been reported to Acquia for more than a day.'); - } - - // Requirement 'description' indicates potential configuration problems. - if ($blocked) { - $severity = REQUIREMENT_WARNING; - $config_url = Url::fromRoute('acquia_connector.environment_change')->toString(); - $variables[':config-page'] = $config_url; - $description = t('This site has been disabled from sending profile data to Acquia. Enable this site.', $variables); - } - elseif ($environment_change) { - $severity = REQUIREMENT_ERROR; - $config_url = Url::fromRoute('acquia_connector.environment_change')->toString(); - $variables[':config-page'] = $config_url; - $description = t('A change in your site\'s environment has been detected. SPI data cannot be submitted until this is resolved. Please confirm the action you wish to take.', $variables); - } - elseif ($name_required) { - $severity = REQUIREMENT_ERROR; - $config_url = Url::fromRoute('acquia_connector.settings')->toString(); - $variables[':config-page'] = $config_url; - $description = t('You are not currently sending site profile data to Acquia. Please provide a site name.', $variables); - } - elseif (!$use_cron) { - $config_url = Url::fromRoute('acquia_connector.settings')->toString(); - $variables[':config-page'] = $config_url; - $description = t('You are not sending SPI data via Drupal\'s cron system. View Acquia Subscription configuration for details.
You can manually send SPI data.', $variables); - } - - $requirements['acquia_spi'] = [ - 'title' => t('Acquia Subscription SPI'), - 'severity' => $severity, - 'value' => $value, - 'description' => $description, - ]; - } - - // Inform users on subscription status. Either we know they are active, - // or we know they have credentials but not active (not set up yet) or - // we have credentials but an inactive subscription (either bad - // credentials or expired subscription). - if ($subscription->isActive()) { - $requirements['acquia_subscription_status'] = [ - 'title' => t('Acquia Subscription status'), - 'severity' => REQUIREMENT_OK, - 'value' => t('Active'), - 'description' => t('You can manually refresh the subscription status.', [ - ':refresh-status' => Url::fromRoute('acquia_connector.refresh_status', [], ['absolute' => TRUE]) - ->toString(), - ]), - ]; - } - elseif (!$has_credentials) { - $requirements['acquia_subscription_status'] = [ - 'title' => t('Acquia Subscription status'), - 'severity' => REQUIREMENT_WARNING, - 'value' => t('Unknown'), - 'description' => t('You did not complete your signup to Acquia. You can provide the subscription identifier and the subscription key at the Acquia settings page or try to manually refresh the subscription status.', [':settings' => Url::fromRoute('acquia_connector.settings')->toString(), ':refresh-status' => Url::fromRoute('acquia_connector.refresh_status')->toString()]), - ]; - } - else { - $subscription = \Drupal::state()->get('acquia_subscription_data'); - $href = isset($subscription['uuid']) ? 'https://cloud.acquia.com/app/develop/applications/' . $subscription['uuid'] : 'https://cloud.acquia.com'; - $requirements['acquia_subscription_status'] = [ - 'title' => t('Acquia Subscription status'), - 'severity' => REQUIREMENT_WARNING, - 'value' => t('Inactive'), - 'description' => t('Your subscription is expired or you are using an invalid identifier and key pair. You can check the subscription identifier and the subscription key at the Acquia settings page. Check your subscription on the Acquia Subscription for further status information.', [':settings' => Url::fromRoute('acquia_connector.settings')->toString(), ':acquia-network' => $href]), - ]; - } - - // Acquia SPI custom tests status. - $variables = [ - ':help' => Url::fromUri('base:admin/help/acquia_connector', ['absolute' => TRUE]) - ->toString(), - ':validate' => Url::fromRoute('acquia_connector.test_validate', [], ['absolute' => TRUE]) - ->toString(), - ]; - - $modules = \Drupal::moduleHandler()->getImplementations('acquia_connector_spi_test'); - if (empty($modules)) { - $description = t('No custom tests were detected in any module.'); - $value = t('Not implemented (more information)', $variables); - $severity = REQUIREMENT_OK; - } - else { - $status = new TestStatusController(); - $result = $status->testStatus(); - - if (!empty($result)) { - $variables['%modules'] = implode(', ', array_keys($result)); - $description = t('Custom tests within the following module(s) have failed validation and will not be sent: %modules.
Please check the error logs for more information regarding how to pass validation or perform another validation check. A validation check can also be performed via the Drush command, "spi-test-validate".', $variables); - $value = t('Failed (more information)', $variables); - $severity = REQUIREMENT_ERROR; - } - else { - $variables['%modules'] = implode(', ', $modules); - $description = t('Custom test data is structured properly and is sending from: %modules', $variables); - $value = t('Passed'); - $severity = REQUIREMENT_OK; - } - - } - - $requirements['acquia_spi_test'] = [ - 'title' => t('Acquia Subscription SPI Custom Tests'), - 'description' => $description, - 'value' => $value, - 'severity' => $severity, - ]; - break; - } - - return $requirements; -} - -/** - * Rebuild all the menu data. - */ -function acquia_connector_update_8001() { - \Drupal::service('router.builder')->rebuild(); -} - -/** - * Add mapping for the http_response_debug_cacheability_headers variable. - */ -function acquia_connector_update_8002() { - $config_factory = \Drupal::configFactory(); - $config = $config_factory->getEditable('acquia_connector.settings'); - $mapping = $config->get('mapping'); - $mapping['http_response_debug_cacheability_headers'] = ['container_parameter', 'http.response.debug_cacheability_headers']; - $config->set('mapping', $mapping); - $config->save(TRUE); -} - -/** - * Remove cache and cache_lifetime from the mapping. - */ -function acquia_connector_update_8003() { - $config_factory = \Drupal::configFactory(); - $config = $config_factory->getEditable('acquia_connector.settings'); - $config->clear('mapping.cache'); - $config->clear('mapping.cache_lifetime'); - - $variables_automatic = $config->get('spi.set_variables_automatic'); - - foreach (['cache_lifetime', 'cache'] as $variable) { - if (($key = array_search($variable, $variables_automatic)) !== FALSE) { - unset($variables_automatic[$key]); - } + if ($phase !== 'runtime') { + return $requirements; } - $config->set('spi.set_variables_automatic', array_values($variables_automatic)); - $config->save(TRUE); -} - -/** - * Delete the multisite related settings as it is not currently supported. - */ -function acquia_connector_update_8004() { - $config_factory = \Drupal::configFactory(); - $config = $config_factory->getEditable('acquia_connector.settings'); - - $config->clear('spi.multisite_identifier'); - $config->clear('spi.is_multisite'); - $config->clear('spi.machine_multisite_identifier'); - - $config->save(TRUE); -} - -/** - * Move Acquia Subscription credentials into State. - * - * Remove Acquia Subscription credentials from the config and place it into the - * State storage. For more info see https://www.drupal.org/node/2635138. - */ -function acquia_connector_update_8005() { - $config = \Drupal::configFactory()->getEditable('acquia_connector.settings'); - $storage = new Storage(); - - if ($config->get('key')) { - $storage->setKey($config->get('key')); - $config->clear('key'); + $subscription = \Drupal::service('acquia_connector.subscription'); + + $has_credentials = $subscription->hasCredentials(); + + // Inform users on subscription status. Either we know they are active, + // or we know they have credentials but not active (not set up yet) or + // we have credentials but an inactive subscription (either bad + // credentials or expired subscription). + if ($subscription->isActive()) { + $requirements['acquia_subscription_status'] = [ + 'title' => t('Acquia Subscription status'), + 'severity' => REQUIREMENT_OK, + 'value' => t('Active'), + 'description' => t('You can manually refresh the subscription status.', [ + ':refresh-status' => Url::fromRoute('acquia_connector.refresh_status', [], ['absolute' => TRUE]) + ->toString(), + ]), + ]; } - - if ($config->get('identifier')) { - $storage->setIdentifier($config->get('identifier')); - $config->clear('identifier'); + elseif (!$has_credentials) { + $requirements['acquia_subscription_status'] = [ + 'title' => t('Acquia Subscription status'), + 'severity' => REQUIREMENT_WARNING, + 'value' => t('Unknown'), + 'description' => t('You did not complete your signup to Acquia. You can provide the subscription identifier and the subscription key at the Acquia settings page or try to manually refresh the subscription status.', [ + ':settings' => Url::fromRoute('acquia_connector.settings') + ->toString(), + ':refresh-status' => Url::fromRoute('acquia_connector.refresh_status') + ->toString(), + ]), + ]; } - - $config->save(TRUE); -} - -/** - * Delete the spi module diff data settings. - */ -function acquia_connector_update_8006() { - $config_factory = \Drupal::configFactory(); - $config = $config_factory->getEditable('acquia_connector.settings'); - - if ($config->get('spi.module_diff_data')) { - $config->clear('spi.module_diff_data'); + else { + // Should get cached data if it exists. + $subscription = $subscription->getSubscription(); + $href = isset($subscription['uuid']) ? 'https://loud.acquia.com/app/develop/applications/' . $subscription['uuid'] : 'https://cloud.acquia.com'; + $requirements['acquia_subscription_status'] = [ + 'title' => t('Acquia Subscription status'), + 'severity' => REQUIREMENT_WARNING, + 'value' => t('Inactive'), + 'description' => t('Your subscription is expired or you are using an invalid identifier and key pair. You can check the subscription identifier and the subscription key at the Acquia settings page. Check your subscription on the Acquia Subscription for further status information.', [ + ':settings' => Url::fromRoute('acquia_connector.settings') + ->toString(), + ':acquia-network' => $href, + ]), + ]; } - $config->save(TRUE); + return $requirements; } /** - * Move subscription data to state. + * Implements hook_requirements_alter(). */ -function acquia_connector_update_8007() { - $config = \Drupal::configFactory()->getEditable('acquia_connector.settings'); - - // Handle subscription data first. - $subscription_data = $config->get('subscription_data'); - if ($subscription_data) { - \Drupal::state()->set('acquia_subscription_data', $subscription_data); - $config->clear('subscription_data')->save(); - } - - // Now handle SPI vars. - $spi_moved_keys = [ - 'def_vars', - 'def_waived_vars', - 'def_timestamp', - 'new_optional_data', - ]; - foreach ($spi_moved_keys as $key) { - $data = $config->get("spi.$key"); - if ($data) { - \Drupal::state()->set("acquia_spi_data.$key", $data); - $config->clear("spi.$key")->save(); - } +function acquia_connector_requirements_alter(array &$requirements): void { + $php_severity = $requirements['php']['severity'] ?? NULL; + // Customers always use a supported version of PHP on the Acquia Platform. + if ($php_severity === REQUIREMENT_ERROR) { + $requirements['php']['severity'] = REQUIREMENT_WARNING; + $requirements['php']['description'] = t('

@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('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 .= '
'; - $output .= '
' . t('Acquia SPI Custom Tests') . '
'; - $output .= '
' . t('Acquia Insight supports custom tests for your site. See acquia_connector.api.php for information on the custom test hook and validate your tests for inclusion in outgoing SPI data with the Drush command, spi-test-validate.') . '
'; - - $output .= '
' . t('Acquia Search') . '
'; - $output .= '
' . t("Provides authentication service to the Apache Solr Search Integration module to enable use of Acquia's hosted Solr search indexes.") . '
'; - $output .= '
'; - - $output .= '

' . t('Configuration settings') . '

'; - $output .= '
'; - $output .= '
' . t('Data collection and examination') . '
'; - $output .= '
' . t('Upon cron (or if configured to run manually) information about your site will be sent and analyzed as part of the Acquia Insight service. You can optionally exclude information about admin privileges, content and user count, and watchdog logs.') . '
'; - - $output .= '
' . t('Source code analysis') . '
'; - $output .= '
' . t('If your site supports external SSL connections, Acquia Insight will examine the source code of your site to detect alterations and provide code diffs and update recommendations.') . '
'; + $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 .= '
' . t('Receive updates from Acquia Subscription') . '
'; $output .= '
' . t('Receive dynamic updates on the Network Settings page from Acquia.com about your subscription and new features.') . '
'; - $output .= '
' . t('Allow Insight to update list of approved variables.') . '
'; - $output .= '
' . t('As part of the Acquia Insight service, some variables can be corrected to their recommended settings from within the Insight system. The list of variables that can be corrected can also be updated at your discretion.') . '
'; $output .= '
'; return $output; @@ -64,54 +40,25 @@ function acquia_connector_help($route_name, RouteMatchInterface $route_match) { } /** - * Implements hook_form_FORM_ID_alter(). - */ -function acquia_connector_form_system_modules_alter(&$form, FormStateInterface $form_state) { - if (isset($form['modules']['Acquia']['acquia_search']['description']['#markup'])) { - $subscription = \Drupal::state()->get('acquia_subscription_data'); - - if (!\Drupal::moduleHandler()->moduleExists('acquia_search') && empty($subscription['active'])) { - $form['modules']['Acquia']['acquia_search']['enable']['#disabled'] = TRUE; - $message = t('Acquia Subscription (inactive)', ['@network-url' => 'http://acquia.com/products-services/acquia-search']); - $form['modules']['Acquia']['acquia_search']['#requires']['acquia_subscription'] = $message; - } - } -} - -/** - * Implements hook_cron(). + * Implements hook_theme(). */ -function acquia_connector_cron() { - - $config = \Drupal::config('acquia_connector.settings'); - // Don't send data if site is blocked or missing components. - if ($config->get('spi.blocked') || (is_null($config->get('spi.site_name')) && is_null($config->get('spi.site_machine_name')))) { - return; - } - - // Check subscription and send a heartbeat to Acquia. - $subscription = new Subscription(); - $subscription->update(); - - // Get the last time we processed data. - $last = \Drupal::state()->get('acquia_connector.cron_last', 0); - - // 30 minute interval for sending site profile. - $interval = $config->get('cron_interval'); - if ($config->get('cron_interval_override')) { - $interval = $config->get('cron_interval_override'); - } - - // Determine if the required interval has passed. - if ($config->get('spi.use_cron') && ((\Drupal::time()->getRequestTime() - $last) > ($interval * 60))) { - \Drupal::service('acquia_connector.spi')->sendFullSpi(ACQUIA_CONNECTOR_ACQUIA_SPI_METHOD_CRON); - } +function acquia_connector_theme() { + return [ + 'acquia_connector_banner' => [ + 'render element' => 'form', + 'template' => 'acquia_connector_banner', + ], + ]; } /** * Implements hook_toolbar(). */ function acquia_connector_toolbar() { + if (!\Drupal::currentUser()->hasPermission('view acquia connector toolbar')) { + return []; + } + $link = [ '#type' => 'link', '#attributes' => [ @@ -121,22 +68,21 @@ function acquia_connector_toolbar() { ], ]; - $subscription = new Subscription(); + /** @var \Drupal\acquia_connector\Subscription $subscription */ + $subscription = Drupal::service('acquia_connector.subscription'); if ($subscription->isActive()) { - $subscription_data = \Drupal::state()->get('acquia_subscription_data'); - if (is_array($subscription_data['expiration_date']) && isset($subscription_data['active']) && $subscription_data['active'] !== FALSE) { - $link['#title'] = t('Subscription active (expires @date)', [ - '@date' => \Drupal::service('date.formatter')->format(strtotime($subscription_data['expiration_date']['value']), 'custom', 'Y/n/j'), - ]); + $subscription_data = $subscription->getSubscription(); + if (isset($subscription_data['active']) && $subscription_data['active'] !== FALSE) { + $link['#title'] = t('Subscription active'); $link['#attributes']['class'][] = 'acquia-active-subscription'; - $link['#url'] = Url::fromUri('https://cloud.acquia.com/app/develop/applications/' . $subscription_data['uuid']); } + $link['#url'] = Url::fromUri('https://cloud.acquia.com/app/develop/applications/' . $subscription->getSettings()->getApplicationUuid()); } if (empty($link['#url'])) { $link['#title'] = t('Subscription not active'); $link['#attributes']['class'][] = 'acquia-inactive-subscription'; - $link['#url'] = Url::fromUri('https://cloud.acquia.com'); + $link['#url'] = Url::fromRoute('acquia_connector.setup_oauth'); } return [ @@ -145,9 +91,7 @@ function acquia_connector_toolbar() { 'tab' => $link, '#weight' => 200, '#cache' => [ - 'contexts' => [ - 'user.roles:authenticated', - ], + 'tags' => ['acquia_connector_subscription'], ], '#attached' => [ 'library' => [ @@ -159,221 +103,60 @@ function acquia_connector_toolbar() { } /** - * Implements hook_update_status_alter(). + * Implements hook_modules_installed(). */ -function acquia_connector_update_status_alter(&$projects) { - if (!$subscription = acquia_connector_has_update_service()) { - // Get subscription data or return if the service is not enabled. - return; - } - - acquia_connector_load_versions(); - - foreach ($projects as $project => $project_info) { - if ($project == 'drupal') { - if (isset($subscription['update'])) { - $projects[$project]['status'] = isset($subscription['update']['status']) ? $subscription['update']['status'] : t('Unknown'); - $projects[$project]['releases'] = isset($subscription['update']['releases']) ? $subscription['update']['releases'] : []; - $projects[$project]['recommended'] = isset($subscription['update']['recommended']) ? $subscription['update']['recommended'] : ''; - $projects[$project]['latest_version'] = isset($subscription['update']['latest_version']) ? $subscription['update']['latest_version'] : ''; - // Security updates are a separate piece of data. If we leave it, then - // core security warnings from drupal.org will also be displayed on the - // update page. - unset($projects[$project]['security updates']); - } - else { - $projects[$project]['status'] = UpdateFetcherInterface::NOT_CHECKED; - $projects[$project]['reason'] = t('No information available from Acquia.'); - unset($projects[$project]['releases']); - unset($projects[$project]['recommended']); - } - $projects[$project]['link'] = 'http://acquia.com/products-services/acquia-drupal'; - $projects[$project]['title'] = 'Acquia Drupal'; - $projects[$project]['existing_version'] = ACQUIA_DRUPAL_VERSION; - $projects[$project]['install_type'] = 'official'; - unset($projects[$project]['extra']); - } - elseif ($project_info['datestamp'] == 'acquia drupal') { - $projects['drupal']['includes'][$project] = !empty($project_info['title']) ? $project_info['title'] : ''; - unset($projects[$project]); - } +function acquia_connector_modules_installed(array $modules) { + /** @var \Drupal\acquia_connector\EventSubscriber\KernelTerminate\AcquiaTelemetry $telemetry_service */ + $telemetry_service = \Drupal::service('acquia_connector.telemetry'); + $installed_acquia_extensions = array_intersect($modules, $telemetry_service->getAcquiaExtensionNames()); + if ($installed_acquia_extensions) { + $event_properties = ['installed_extensions' => array_values($installed_acquia_extensions)]; + $telemetry_service->sendTelemetry('Acquia extensions installed', $event_properties); } -} -/** - * API function used by others to ensure version information is loaded. - * - * Saves us some cycles to not load it each time, when it is actually - * not needed. We store this in a separate file, so that the Acquia - * build process only needs to alter that file instead of the main - * module file. - */ -function acquia_connector_load_versions() { - // Include version number information. - include_once 'acquia_connector_drupal_version.inc'; -} - -/** - * Returns the stored subscription data. - * - * @return mixed - * Returns the stored subscription data if update service is enabled or FALSE - * otherwise. - */ -function acquia_connector_has_update_service() { - // Include version number information. - acquia_connector_load_versions(); - $subscription = Drupal::state()->get('acquia_subscription_data'); - if (!IS_ACQUIA_DRUPAL || empty($subscription['active']) || (isset($subscription['update_service']) && empty($subscription['update_service']))) { - // We don't have update service if (1) this is not Acquia Drupal, (2) there - // is no subscription or (3) the update service was disabled on acquia.com. - // Requiring the update_service key and checking its value separately is - // important for backwards compatibility. Isset & empty tells us - // that the web service willingly told us to not do update notifications. - return FALSE; + $acquia_modules = array_filter($modules, static function (string $name) { + return str_starts_with($name, 'acquia_'); + }); + if (count($acquia_modules) > 0) { + \Drupal::service('acquia_connector.subscription')->getSubscription(TRUE); } - - return $subscription; -} - -/** - * Set error message. - * - * @param mixed|int $code - * The Exception code. - * @param string $message - * The Exception message. - */ -function acquia_connector_report_restapi_error($code, $message) { - \Drupal::messenger()->addError(t('Error: @message (@errno)', ['@message' => $message, '@errno' => $code])); } /** - * Return an error message by the error code. - * - * Returns an error message for the most recent (failed) attempt to connect - * to the Acquia during the current page request. If there were no failed - * attempts, returns FALSE. - * - * This function assumes that the most recent error came from the Acquia; - * otherwise, it will not work correctly. - * - * @param int $errno - * Error code defined by the module. - * - * @return mixed - * The error message string or FALSE. + * Implements hook_modules_uninstalled(). */ -function acquia_connector_connection_error_message($errno) { - if ($errno) { - switch ($errno) { - case Subscription::NOT_FOUND: - return t('The identifier you have provided does not exist at Acquia or is expired. Please make sure you have used the correct value and try again.'); - - case Subscription::EXPIRED: - return t('Your Acquia Subscription subscription has expired. Please renew your subscription so that you can resume using Acquia services.'); - - case Subscription::MESSAGE_FUTURE: - return t('Your server is unable to communicate with Acquia due to a problem with your clock settings. For security reasons, we reject messages that are more than @time ahead of the actual time recorded by our servers. Please fix the clock on your server and try again.', ['@time' => \Drupal::service('date.formatter')->formatInterval(Subscription::MESSAGE_LIFETIME)]); - - case Subscription::MESSAGE_EXPIRED: - return t('Your server is unable to communicate with Acquia due to a problem with your clock settings. For security reasons, we reject messages that are more than @time older than the actual time recorded by our servers. Please fix the clock on your server and try again.', ['@time' => \Drupal::service('date.formatter')->formatInterval(Subscription::MESSAGE_LIFETIME)]); - - case Subscription::VALIDATION_ERROR: - return t('The identifier and key you have provided for the Acquia Subscription do not match. Please make sure you have used the correct values and try again.'); - - default: - return t('There is an error communicating with the Acquia Subscription at this time. Please check your identifier and key and try again.'); - } +function acquia_connector_modules_uninstalled(array $modules) { + /** @var \Drupal\acquia_connector\EventSubscriber\KernelTerminate\AcquiaTelemetry $telemetry_service */ + $telemetry_service = \Drupal::service('acquia_connector.telemetry'); + $uninstalled_acquia_extensions = array_intersect($modules, $telemetry_service->getAcquiaExtensionNames()); + if ($uninstalled_acquia_extensions) { + $event_properties = ['uninstalled_extensions' => array_values($uninstalled_acquia_extensions)]; + $telemetry_service->sendTelemetry('Acquia extensions uninstalled', $event_properties); } - return FALSE; -} -/** - * Implements hook_modules_installed(). - */ -function acquia_connector_modules_installed($modules) { - foreach ($modules as $module) { - if (function_exists($module . '_acquia_connector_spi_test')) { - \Drupal::messenger()->addStatus(t("A new invocation of hook_acquia_connector_spi_test() has been detected in @module.", ['@module' => $module])); - \Drupal::logger('acquia connector spi test')->info("A new invocation of hook_acquia_connector_spi_test() has been detected in @module.", ['@module' => $module]); - } + $acquia_modules = array_filter($modules, static function (string $name) { + return str_starts_with($name, 'acquia_'); + }); + if (count($acquia_modules) > 0) { + \Drupal::service('acquia_connector.subscription')->getSubscription(TRUE); } } /** - * Displays promo DSM for Acquia Cloud Free offering. + * Implements hook_cron(). */ -function acquia_connector_show_free_tier_promo() { - - $subscription = new Subscription(); - - if (PHP_SAPI == 'cli') { - return; - } - // Check that there's no form submission in progress. - if (\Drupal::request()->server->get('REQUEST_METHOD') == 'POST') { - return; - } - // Check that we're not on an AJAX request. - if (\Drupal::request()->isXmlHttpRequest()) { - return; - } - - // Check that we're not serving a private file or image. - $controller_name = \Drupal::request()->attributes->get('_controller'); - if (strpos($controller_name, 'FileDownloadController') !== FALSE || strpos($controller_name, 'ImageStyleDownloadController') !== FALSE) { - return; - } - - $ac_config = \Drupal::configFactory()->get('acquia_connector.settings'); - - if ($ac_config->get('hide_signup_messages')) { - return; - } - - // Check that we're not on one of our own config pages, all of which are - // prefixed with admin/config/system/acquia-connector. - $current_path = \Drupal::Request()->attributes->get('_system_path'); - if (\Drupal::service('path.matcher')->matchPath($current_path, 'admin/config/system/acquia-connector/*')) { - return; - } - - // Check that the user has 'administer site configuration' permission. - if (!\Drupal::currentUser()->hasPermission('administer site configuration')) { - return; - } - - // Check that there are no Acquia credentials currently set up. - if ($subscription->hasCredentials()) { - return; - } - - // Display the promo message. - $message = t('Sign up for Acquia Cloud Free, a free Drupal sandbox to experiment with new features, test your code quality, and apply continuous integration best practices. Check out the epic set of dev features and tools that come with your free subscription.
If you have an Acquia Subscription, connect now. Otherwise, you can turn this message off by disabling the Acquia Connector modules.', [ - '@acquia-free' => Url::fromUri('https://www.acquia.com/acquia-cloud-free')->getUri(), - '@settings' => Url::fromRoute('acquia_connector.setup')->toString(), - ]); - - \Drupal::messenger()->addWarning($message); - +function acquia_connector_cron() { + $auth_service = \Drupal::service('acquia_connector.auth_service'); + $auth_service->cronRefresh(); } /** - * Auto-connects the site to Acquia. + * Implements hook_module_implements_alter(). */ -function acquia_connector_auto_connect() { - $subscription = new Subscription(); - $storage = new Storage(); - $user = \Drupal::currentUser(); - global $config; - - $auto_connector = new AutoConnector($subscription, $storage, $config); - $connected = $auto_connector->connectToAcquia(); - - if ($connected && $user->hasPermission('administer site configuration')) { - $url = Url::fromRoute('acquia_connector.setup')->toString(); - $text = t('Your site has been automatically connected to Acquia. Change subscription', [':url' => $url]); - \Drupal::messenger()->addStatus($text); +function acquia_connector_module_implements_alter(&$implementations, $hook) { + // The acquia_telemetry is deprecated, if the module could not be uninstalled + // while upgrading to acquia_connector:4.0.0. + if (isset($implementations['acquia_telemetry'])) { + unset($implementations['acquia_telemetry']); } } diff --git a/docroot/modules/contrib/acquia_connector/acquia_connector.permissions.yml b/docroot/modules/contrib/acquia_connector/acquia_connector.permissions.yml new file mode 100644 index 0000000000..4a16067545 --- /dev/null +++ b/docroot/modules/contrib/acquia_connector/acquia_connector.permissions.yml @@ -0,0 +1,2 @@ +view acquia connector toolbar: + title: 'View Acquia Connector Toolbar' diff --git a/docroot/modules/contrib/acquia_connector/acquia_connector.post_update.php b/docroot/modules/contrib/acquia_connector/acquia_connector.post_update.php new file mode 100644 index 0000000000..b5dcc6cf38 --- /dev/null +++ b/docroot/modules/contrib/acquia_connector/acquia_connector.post_update.php @@ -0,0 +1,76 @@ +moduleExists('acquia_telemetry')) { + $debug = \Drupal::state()->get('acquia_telemetry.loud'); + if ($debug) { + \Drupal::state()->set('acquia_connector.telemetry.loud', TRUE); + } + /** @var \Drupal\Core\Extension\ModuleInstallerInterface $module_installer */ + $module_installer = \Drupal::service('module_installer'); + try { + $module_installer->uninstall(['acquia_telemetry'], FALSE); + } + catch (ModuleUninstallValidatorException $e) { + // Do nothing, versions of acquia_cms_common and lightning_core declared + // acquia_telemetry as a dependency, and we cannot automatically uninstall + // the module. + } + } +} + +/** + * Ensure old Amplitude API key is removed from config. + */ +function acquia_connector_post_update_remove_amplitude_keys() { + $acquia_connector_config = \Drupal::configFactory()->getEditable('acquia_connector.settings'); + if ($acquia_connector_config->get('spi.amplitude_api_key')) { + $acquia_connector_config->clear('spi.amplitude_api_key'); + // Anything left in SPI should be in state, not config. + $acquia_connector_config->clear('spi'); + $acquia_connector_config->save(); + } +} + +/** + * Rebuild a simple acquia connector config object. + */ +function acquia_connector_post_update_deprecated_variables() { + $acquia_connector_config = \Drupal::configFactory()->getEditable('acquia_connector.settings'); + + $variables = [ + 'debug', + 'cron_interval', + 'cron_interval_override', + 'hide_signup_messages', + 'third_party_settings', + ]; + $data = []; + foreach ($variables as $var) { + $data[$var] = $acquia_connector_config->get($var); + } + $acquia_connector_config->setData($data); + $acquia_connector_config->save(); + + // Migrate any existing subscription data from v3 to the new location. + if ($acquia_subscription_data = \Drupal::state()->get('acquia_subscription_data')) { + \Drupal::state()->delete('acquia_subscription_data'); + \Drupal::state()->set('acquia_connector.subscription_data', $acquia_subscription_data); + } + // Get subscription data from V4 location, and set uuid properly. + $acquia_subscription_data = \Drupal::state()->get('acquia_connector.subscription_data'); + \Drupal::state()->set('acquia_connector.application_uuid', $acquia_subscription_data['uuid']); + + // Flush caches when upgrading from 3.0.x to 4.0.x. + drupal_flush_all_caches(); +} diff --git a/docroot/modules/contrib/acquia_connector/acquia_connector.routing.yml b/docroot/modules/contrib/acquia_connector/acquia_connector.routing.yml index 6dea5ba3d9..15fd6a6ab9 100644 --- a/docroot/modules/contrib/acquia_connector/acquia_connector.routing.yml +++ b/docroot/modules/contrib/acquia_connector/acquia_connector.routing.yml @@ -1,30 +1,31 @@ acquia_connector.settings: - path: '/admin/config/system/acquia-connector' + path: '/admin/config/services/acquia-connector' defaults: _form: '\Drupal\acquia_connector\Form\SettingsForm' requirements: _permission: 'administer site configuration' -acquia_connector.setup: - path: '/admin/config/system/acquia-connector/setup' +acquia_connector.setup_manual: + path: '/admin/config/services/acquia-connector/manual' defaults: - _form: '\Drupal\acquia_connector\Form\SetupForm' - _title: 'Acquia Subscription automatic setup' + _form: '\Drupal\acquia_connector\Form\CredentialForm' + _title: 'Acquia Subscription credentials manual entry' requirements: _permission: 'administer site configuration' -acquia_connector.start: - path: '/admin/config/system/acquia-connector/start' +acquia_connector.setup_oauth: + path: '/admin/config/services/acquia-connector/login' defaults: - _controller: '\Drupal\acquia_connector\Controller\StartController::info' + _controller: Drupal\acquia_connector\Controller\AuthController::setup + _title: 'Connect to Acquia Cloud' requirements: _permission: 'administer site configuration' -acquia_connector.credentials: - path: '/admin/config/system/acquia-connector/credentials' +acquia_connector.setup_configure: + path: '/admin/config/services/acquia-connector/configure' defaults: - _form: '\Drupal\acquia_connector\Form\CredentialForm' - _title: 'Acquia Subscription credentials' + _form: Drupal\acquia_connector\Form\ConfigureApplicationForm + _title: 'Select application' requirements: _permission: 'administer site configuration' @@ -38,7 +39,7 @@ acquia_connector.status: _maintenance_access: TRUE acquia_connector.refresh_status: - path: '/admin/config/system/acquia-connector/refresh-status' + path: '/admin/config/services/acquia-connector/refresh-status' defaults: _controller: '\Drupal\acquia_connector\Controller\StatusController::refresh' _title: 'Manual update check' @@ -46,27 +47,21 @@ acquia_connector.refresh_status: _csrf_token: 'TRUE' _permission: 'administer site configuration' -acquia_connector.send: - path: '/system/acquia-spi-send' - defaults: - _controller: '\Drupal\acquia_connector\Controller\SpiController::send' - _title: 'Acquia SPI send' - requirements: - _custom_access: '\Drupal\acquia_connector\Controller\SpiController::sendAccess' - -acquia_connector.test_validate: - path: '/system/acquia-connector-test-validate' +acquia_connector.auth.begin: + path: '/acquia-connector/auth/begin' defaults: - _controller: '\Drupal\acquia_connector\Controller\TestStatusController::testStatus' - _title: 'Acquia SPI Custom Test Validation' - log: TRUE + _controller: Drupal\acquia_connector\Controller\AuthController::begin requirements: - _permission: 'access site reports' + _csrf_token: 'TRUE' + _permission: 'administer site configuration' + options: + no_cache: TRUE -acquia_connector.environment_change: - path: '/admin/config/system/acquia-connector/environment-change' +acquia_connector.auth.return: + path: '/acquia-connector/auth/return' defaults: - _form: '\Drupal\acquia_connector\Form\SpiChangeForm' - _title: 'Acquia SPI Environment Change Actions' + _controller: Drupal\acquia_connector\Controller\AuthController::return requirements: _permission: 'administer site configuration' + options: + no_cache: TRUE diff --git a/docroot/modules/contrib/acquia_connector/acquia_connector.services.yml b/docroot/modules/contrib/acquia_connector/acquia_connector.services.yml index 86aa8efa27..341d00c613 100644 --- a/docroot/modules/contrib/acquia_connector/acquia_connector.services.yml +++ b/docroot/modules/contrib/acquia_connector/acquia_connector.services.yml @@ -1,16 +1,52 @@ services: - acquia_connector.init_subscriber: - class: Drupal\acquia_connector\EventSubscriber\InitSubscriber - arguments: ['@config.factory', '@state', '@cache.default'] + acquia_connector.kernel_view.codestudio_message: + class: Drupal\acquia_connector\EventSubscriber\KernelView\CodeStudioMessage + arguments: ['@messenger'] tags: - {name: event_subscriber} - acquia_connector.client: - class: Drupal\acquia_connector\Client - arguments: ['@config.factory', '@state'] - acquia_connector.spi: - class: Drupal\acquia_connector\Controller\SpiController - arguments: ['@acquia_connector.client', '@config.factory'] - logger.acquia_connector: - class: Drupal\acquia_connector\CronService + acquia_connector.telemetry: + class: Drupal\acquia_connector\EventSubscriber\KernelTerminate\AcquiaTelemetry + arguments: ['@extension.list.module', '@http_client', '@config.factory', '@state', '@datetime.time'] tags: - - { name: logger } + - { name: event_subscriber } + acquia_connector.client.factory: + class: Drupal\acquia_connector\Client\ClientFactory + arguments: ['@logger.factory', '@extension.list.module', '@http_client_factory', '@datetime.time', '@acquia_connector.auth_service', '@http_handler_stack'] + acquia_connector.site_profile: + class: Drupal\acquia_connector\SiteProfile\SiteProfile + arguments: ['@request_stack'] + acquia_connector.subscription: + class: Drupal\acquia_connector\Subscription + arguments: ['@event_dispatcher', '@acquia_connector.client.factory', '@state', '@config.factory'] + acquia_connector.logger_channel: + parent: logger.channel_base + arguments: ['acquia_connector'] + acquia_connector.settings.acquiacloud: + class: Drupal\acquia_connector\EventSubscriber\GetSettings\FromAcquiaCloud + arguments: ['@acquia_connector.logger_channel', '@messenger', '@state'] + tags: + - { name: event_subscriber } + acquia_connector.settings.core: + class: Drupal\acquia_connector\EventSubscriber\GetSettings\FromCoreSettings + tags: + - { name: event_subscriber } + acquia_connector.settings.state: + class: Drupal\acquia_connector\EventSubscriber\GetSettings\FromCoreState + arguments: ['@state'] + tags: + - { name: event_subscriber } + + acquia_connector.auth_service: + class: Drupal\acquia_connector\AuthService + arguments: ['@csrf_token', '@http_client_factory', '@session', '@keyvalue.expirable', '@state', '@datetime.time'] + + acquia_connector.connector_settings_subscriber: + class: Drupal\acquia_connector\EventSubscriber\ConfigSubscriber + arguments: ['@acquia_connector.subscription'] + tags: + - { name: event_subscriber } + + acquia_connector.polyfill.requirements_alter: + class: Drupal\acquia_connector\Polyfill\RequirementsAlter + parent: system.manager + decorates: system.manager diff --git a/docroot/modules/contrib/acquia_connector/acquia_connector_drupal_version.inc b/docroot/modules/contrib/acquia_connector/acquia_connector_drupal_version.inc deleted file mode 100644 index 1241a45586..0000000000 --- a/docroot/modules/contrib/acquia_connector/acquia_connector_drupal_version.inc +++ /dev/null @@ -1,23 +0,0 @@ - 'http', - 'host' => 'somehostname.acquia-search.com', - 'index_id' => 'ABCD-12345.prod.mysite', - 'port' => 80, - 'derived_key' => 'asdfasdfasdfasdfasdfasdfasdfasdf - ]; diff --git a/docroot/modules/contrib/acquia_connector/acquia_search/acquia_search.api.php b/docroot/modules/contrib/acquia_connector/acquia_search/acquia_search.api.php deleted file mode 100644 index af8a34d037..0000000000 --- a/docroot/modules/contrib/acquia_connector/acquia_search/acquia_search.api.php +++ /dev/null @@ -1,43 +0,0 @@ - 'dev', // string|null - * 'ah_db_role' => 'SomeDb1, // string - * 'identifier' => 'WXYZ-12345', // string, may be empty - * 'sites_foldername' => 'default', // string - * ]; - * @endcode - */ -function hook_acquia_search_get_list_of_possible_cores_alter(array &$possible_core_ids, array $context) { - if (empty($context['ah_env'])) { - $possible_core_ids[] = 'WXYZ-12345.dev.mysitedev_db'; - } -} - -/** - * @} End of "addtogroup hooks". - */ diff --git a/docroot/modules/contrib/acquia_connector/acquia_search/acquia_search.info.yml b/docroot/modules/contrib/acquia_connector/acquia_search/acquia_search.info.yml deleted file mode 100644 index bd56228c58..0000000000 --- a/docroot/modules/contrib/acquia_connector/acquia_search/acquia_search.info.yml +++ /dev/null @@ -1,18 +0,0 @@ -name: Acquia Search -type: module -description: Provides integration between your Drupal site and Acquia's hosted search service. -package: Acquia -core: 8.x -core_version_requirement: ^8 || ^9 -configure: search_api.overview -dependencies: - - acquia_connector:acquia_connector - - search_api_solr:search_api_solr (8.x-1.x) - - search_api:search_api (>=8.x-1.17) - - drupal:views - - drupal:node - -# 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/acquia_search.install b/docroot/modules/contrib/acquia_connector/acquia_search/acquia_search.install deleted file mode 100644 index 35de0ea473..0000000000 --- a/docroot/modules/contrib/acquia_connector/acquia_search/acquia_search.install +++ /dev/null @@ -1,145 +0,0 @@ -reset(); - - _acquia_search_set_version(); - $subscription = new Subscription(); - $subscription->update(); -} - -/** - * Implements hook_requirements(). - */ -function acquia_search_requirements($phase) { - $requirements = []; - // Ensure translations don't break at install time - // Skip install checks if install.php is running. The weak install profile - // API means install.php calls hook_requirements for every module in - // a profile. - if ($phase == 'install' && (!defined('MAINTENANCE_MODE') || MAINTENANCE_MODE != 'install')) { - if (class_exists('Drupal\acquia_connector\Subscription')) { - $subscription = new Subscription(); - if ($subscription->hasCredentials()) { - $severity = REQUIREMENT_OK; - } - else { - $severity = REQUIREMENT_ERROR; - } - $requirements['acquia_search_credentials'] = [ - 'description' => t('In order to use Acquia search module you must have an Acquia Subscription. Please enter your Acquia Subscription keys.'), - 'severity' => $severity, - 'value' => '', - ]; - } - else { - $severity = REQUIREMENT_ERROR; - $requirements['acquia_search_credentials'] = [ - 'description' => t('In order to use Acquia search module you must enable and configure the Acquia Connector module.'), - 'severity' => $severity, - 'value' => '', - ]; - } - - } - if ($phase == 'runtime') { - // Check SSL support. - if (in_array('ssl', stream_get_transports(), TRUE)) { - $severity = REQUIREMENT_OK; - $requirements['acquia_search_ssl'] = [ - 'description' => t('The Acquia Search module is using SSL to protect the privacy of your content.'), - ]; - } - else { - $severity = REQUIREMENT_WARNING; - $requirements['acquia_search_ssl'] = [ - 'description' => t('In order to protect the privacy of your content with the Acquia Search module you must have SSL support enabled in PHP on your host.'), - ]; - } - $requirements['acquia_search_ssl']['title'] = t('Acquia Search security'); - $requirements['acquia_search_ssl']['severity'] = $severity; - $requirements['acquia_search_ssl']['value'] = ''; - - $servers = Server::loadMultiple(); - - $acquia_servers = array_filter($servers, function ($server) { - return acquia_search_is_acquia_server($server->getBackendConfig()); - }); - - // Show available Acquia search indexes. - foreach ($acquia_servers as $server_id => $server) { - $requirements['acquia_search_status_' . $server_id] = [ - 'title' => t('Acquia Search connection status'), - 'severity' => REQUIREMENT_OK, - 'description' => ['#markup' => acquia_search_get_search_status_message($server)], - ]; - } - - // Flag when read-only mode was forced because of not finding the right - // index. - if (acquia_search_should_set_read_only_mode()) { - $requirements['acquia_search_read_only'] = [ - 'title' => t('Acquia Search read-only warning'), - 'severity' => REQUIREMENT_WARNING, - 'value' => acquia_search_get_read_only_mode_warning(), - ]; - } - - // Flag if acquia_search_multi_subs module is enabled. - if (\Drupal::moduleHandler()->moduleExists('acquia_search_multi_subs')) { - $requirements['acquia_search_asms'] = [ - 'title' => t('Acquia Search module warning'), - 'severity' => REQUIREMENT_WARNING, - 'description' => t( - 'Warning: acquia_search_multi_subs.module is enabled, but most of its functionality is now included in the Acquia Search module. Please read our documentation.', - ['@url' => 'https://docs.acquia.com/acquia-search/multiple-cores/'] - ), - ]; - } - } - // Update the cached version whenever we may be updating the module. - if ($phase == 'runtime' || $phase == 'update') { - _acquia_search_set_version(); - } - - return $requirements; -} - -/** - * Helper function to cache the Acquia Search version. - */ -function _acquia_search_set_version() { - // Cache the version in a variable so we can send it at not extra cost. - $version = \Drupal::config('acquia_search.settings')->get('version'); - $info = \Drupal::service('extension.list.module')->getExtensionInfo('acquia_search'); - // Send the version, or at least the core compatibility as a fallback. - $new_version = isset($info['version']) ? (string) $info['version'] : (string) \Drupal::VERSION; - if ($version != $new_version) { - \Drupal::configFactory()->getEditable('acquia_search.settings')->set('version', $new_version)->save(); - } -} - -/** - * Implements hook_uninstall(). - */ -function acquia_search_uninstall() { - \Drupal::state()->deleteMultiple([ - 'acquia_search.v3_api_host', - 'acquia_search.v3_api_key', - ]); -} diff --git a/docroot/modules/contrib/acquia_connector/acquia_search/acquia_search.module b/docroot/modules/contrib/acquia_connector/acquia_search/acquia_search.module deleted file mode 100644 index 7c6cfe33ef..0000000000 --- a/docroot/modules/contrib/acquia_connector/acquia_search/acquia_search.module +++ /dev/null @@ -1,722 +0,0 @@ -get('derived_key_salt'); - if (isset($subscription['derived_key_salt']) && $salt != $subscription['derived_key_salt']) { - \Drupal::configFactory()->getEditable('acquia_search.settings') - ->set('derived_key_salt', $subscription['derived_key_salt']) - ->save(); - } - - // Enable search. - /** @var \Drupal\search_api\Entity\Server $server */ - if ($server = Server::load('acquia_search_server')) { - $server->set('status', TRUE); - $server->save(); - } - - /** @var \Drupal\search_api\Entity\Index $index */ - if ($index = Index::load('acquia_search_index')) { - $index->set('status', TRUE); - $index->save(); - } - } - elseif (is_int($subscription)) { - // @todo: Maybe we don't want to switch off search/index because there could - // be an override in place. But perhaps we need to log it or show a message. - switch ($subscription) { - case Subscription::NOT_FOUND: - case Subscription::EXPIRED: - // Disable search. - /** @var \Drupal\search_api\Entity\Server $server */ - if ($server = Server::load('acquia_search_server')) { - $server->set('status', FALSE); - $server->save(); - } - /** @var \Drupal\search_api\Entity\Index $index */ - if ($index = Index::load('acquia_search_index')) { - $index->set('status', FALSE); - $index->save(); - } - break; - } - } -} - -/** - * Acquia Search helper function. Returns search host. - * - * @param array $subscription - * Acquia Subscription. - * - * @return string - * Search server url. - */ -function acquia_search_get_search_host(array $subscription = []) { - if (empty($subscription)) { - $subscription = \Drupal::state()->get('acquia_subscription_data'); - } - - $search_host = \Drupal::config('acquia_search.settings')->get('host'); - // Adding the subscription specific colony to the heartbeat data. - if (!empty($subscription['heartbeat_data']['search_service_colony'])) { - $search_host = $subscription['heartbeat_data']['search_service_colony']; - } - // Check if we are on Acquia Cloud hosting. @see NN-2503. - if (!empty($_ENV['AH_SITE_ENVIRONMENT']) && !empty($_ENV['AH_CURRENT_REGION'])) { - if ($_ENV['AH_CURRENT_REGION'] == 'us-east-1' && $search_host == 'search.acquia.com') { - $search_host = 'internal-search.acquia.com'; - } - elseif (strpos($search_host, 'search-' . $_ENV['AH_CURRENT_REGION']) === 0) { - $search_host = 'internal-' . $search_host; - } - } - return $search_host; -} - -/** - * Implements hook_entity_operation_alter(). - * - * Don't allow delete default server and index. - */ -function acquia_search_entity_operation_alter(array &$operations, EntityInterface $entity) { - if (empty($operations['delete'])) { - return; - } - $do_not_delete = [ - 'acquia_search_server', - 'acquia_search_index', - ]; - if (array_search($entity->id(), $do_not_delete) !== FALSE) { - unset($operations['delete']); - } -} - -/** - * Determine whether search core auto switch functionality is disabled. - * - * @return bool - * TRUE if the auto switch feature disabled via configuration, FALSE - * otherwise. - */ -function acquia_search_is_auto_switch_disabled() { - return !empty(\Drupal::config('acquia_search.settings')->get('disable_auto_switch')); -} - -/** - * Determine whether search config has been overridden via settings.php. - * - * @return bool - * TRUE if a search config has been overridden, FALSE otherwise. - */ -function acquia_search_is_connection_config_overridden() { - $overrides = \Drupal::config('acquia_search.settings')->get('connection_override'); - - if (!$overrides) { - return FALSE; - } - - $is_overridden_and_valid = - !empty($overrides['host']) && - !empty($overrides['scheme']) && - !empty($overrides['port']) && - !empty($overrides['index_id']) && - !empty($overrides['derived_key']); - - if ($is_overridden_and_valid) { - return TRUE; - } - - \Drupal::logger('acquia search')->notice("Invalid config override detected for - acquia_search.settings.connection_override. It should include host, index_id, - scheme, port and derived_key."); - - return FALSE; -} - -/** - * Determine if we should enforce read-only mode. - * - * @return bool - * TRUE if acquia_search module should enforce the read-only mode, FALSE - * otherwise. - */ -function acquia_search_should_set_read_only_mode() { - - // If search config is overridden in settings.php we can't enforce anything. - if (acquia_search_is_connection_config_overridden()) { - return FALSE; - } - - // Check if auto-switch or read-only modes are disabled in settings. - $auto_switch_disabled = \Drupal::config('acquia_search.settings')->get('disable_auto_switch'); - $disable_auto_read_only = \Drupal::config('acquia_search.settings')->get('disable_auto_read_only'); - if ($auto_switch_disabled || $disable_auto_read_only) { - return FALSE; - } - - // If subscription is expired, then DO enforce read-only mode. - $subscription = new Subscription(); - if (!$subscription->isActive()) { - return TRUE; - } - - // If there is no preferred core, then DO enforce read-only mode. - $core_service = acquia_search_get_core_service(); - if (!$core_service->isPreferredCoreAvailable()) { - return TRUE; - } - - return FALSE; -} - -/** - * Implements hook_search_api_server_load(). - * - * Flag when a certain server should be enforcing read-only mode. - * - * @throws \Drupal\search_api\SearchApiException - */ -function acquia_search_search_api_server_load($entities) { - $acquia_servers = array_filter($entities, function ($server) { - /** @var \Drupal\search_api\Entity\Server $server */ - return acquia_search_is_acquia_server($server->getBackendConfig()); - }); - - $core_service = acquia_search_get_core_service(); - - /** @var \Drupal\search_api\Entity\Server $server */ - foreach ($acquia_servers as $server) { - /** @var \Drupal\search_api_solr\Plugin\search_api\backend\SearchApiSolrBackend $backend */ - $backend = $server->getBackend(); - $connector_config = $backend->getSolrConnector()->getConfiguration(); - // Set a list of eligible cores. - $connector_config['acquia_search_possible_cores'] = $core_service->getListOfPossibleCores(); - unset($connector_config['overridden_by_acquia_search']); - - if (acquia_search_should_set_read_only_mode()) { - $connector_config['overridden_by_acquia_search'] = ACQUIA_SEARCH_AUTO_OVERRIDE_READ_ONLY; - } - - $backend->getSolrConnector()->setConfiguration($connector_config); - } -} - -/** - * Determine whether given server config belongs to an Acquia search server. - * - * @param array $backend_config - * An array of data obtained from - * \Drupal\search_api\Entity\Server->getBackendConfig() - * - * @return bool - * TRUE if the provided config belongs to an Acquia Search server. - */ -function acquia_search_is_acquia_server(array $backend_config) { - return !empty($backend_config['connector']) && $backend_config['connector'] === 'solr_acquia_connector'; -} - -/** - * Implements hook_search_api_index_load(). - * - * This takes care of marking indexes as read-only mode under the right - * conditions (@see acquia_search_search_api_server_load()). - */ -function acquia_search_search_api_index_load($entities) { - // Loop through the Index entities. - foreach ($entities as &$index) { - - // Check for server-less indexes. - // @see https://www.drupal.org/project/acquia_connector/issues/2956737 - $serverId = $index->getServerId(); - if (!isset($serverId) || $serverId == '') { - continue; - } - - // Checking for serverless indexes. - $serverId = $index->getServerId(); - if (!$serverId) { - continue; - } - - // Check for non-existent servers. - /** @var \Drupal\search_api\Entity\Index $index */ - $server = Server::load($serverId); - - if (!$server) { - continue; - } - - if (!acquia_search_is_acquia_server($server->getBackendConfig())) { - continue; - } - - // Reset the overridden_by_acquia_search option. - $options = $index->getOptions(); - if (!empty($options['overridden_by_acquia_search'])) { - unset($options['overridden_by_acquia_search']); - $index->setOptions($options); - } - - if (acquia_search_should_set_read_only_mode()) { - // Set this index to read-only mode. - $index->set('read_only', TRUE); - // Flag this index as having been altered by this module. - \Drupal::state()->set('acquia_search_index.' . $index->id() . '.overridden_by_acquia_search', ACQUIA_SEARCH_AUTO_OVERRIDE_READ_ONLY); - } - } -} - -/** - * Implements hook_form_FORM_ID_alter(). - * - * Alters the Search API server's status form and displays a warning. - */ -function acquia_search_form_search_api_server_status_alter(&$form) { - $server = !empty($form['#server']) ? $form['#server'] : NULL; - - if (!is_object($server) || get_class($server) !== 'Drupal\search_api\Entity\Server') { - return; - } - - if (!acquia_search_is_acquia_server($server->getBackendConfig())) { - return; - } - - if (!acquia_search_should_set_read_only_mode()) { - return; - } - - // Show read-only warning and disable the "Delete all indexed - // data on this server" action. - acquia_search_server_show_read_only_mode_warning(); - - $form['actions']['clear']['#disabled'] = TRUE; -} - -/** - * Implements hook_form_FORM_ID_alter(). - * - * Display the read-only warning. - */ -function acquia_search_form_search_api_server_edit_form_alter(&$form) { - $server = Server::load($form['id']['#default_value']); - - if (!$server) { - return; - } - - if (!acquia_search_is_acquia_server($server->getBackendConfig())) { - return; - } - - if (!acquia_search_should_set_read_only_mode()) { - return; - } - - acquia_search_server_show_read_only_mode_warning(); -} - -/** - * Implements hook_form_FORM_ID_alter(). - * - * Shows message if we are editing a Search API server's configuration. - */ -function acquia_search_form_search_api_index_edit_form_alter(&$form) { - /** @var \Drupal\search_api\Entity\Server $server */ - $server = Server::load($form['server']['#default_value']); - - if (!$server) { - return; - } - - if (!acquia_search_is_acquia_server($server->getBackendConfig())) { - return; - } - - if (!acquia_search_should_set_read_only_mode()) { - return; - } - - acquia_search_server_show_read_only_mode_warning(); - $form['options']['read_only']['#disabled'] = TRUE; -} - -/** - * Generates DSM with read-only message warning. - */ -function acquia_search_server_show_read_only_mode_warning() { - $message = acquia_search_get_read_only_mode_warning(); - \Drupal::messenger()->addWarning($message); -} - -/** - * Returns formatted message about read-only mode. - * - * @return \Drupal\Component\Render\MarkupInterface|string - * Renderable array or translatable markup. - */ -function acquia_search_get_read_only_mode_warning() { - - $msg = t('To protect your data, the Acquia Search module is enforcing - read-only mode on the Search API indexes, because it could not figure out - what Acquia-hosted Solr index to connect to. This helps you avoid writing to - a production index if you copy your site to a development or other - environment(s).'); - - $core_service = acquia_search_get_core_service(); - - if ($core_service->getListOfPossibleCores()) { - - $item_list = [ - '#theme' => 'item_list', - '#items' => $core_service->getListOfPossibleCores(), - ]; - $list = render($item_list); - - $msg .= '

'; - $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 %} -

{{ 'Index status'|t }}

-
{{ index_progress }}
-{% endif %} -{{ table }} diff --git a/docroot/modules/contrib/acquia_connector/acquia_search/templates/search-api-server.html.twig b/docroot/modules/contrib/acquia_connector/acquia_search/templates/search-api-server.html.twig deleted file mode 100644 index 22a0661f91..0000000000 --- a/docroot/modules/contrib/acquia_connector/acquia_search/templates/search-api-server.html.twig +++ /dev/null @@ -1,22 +0,0 @@ -{# -/** - * @file - * Default theme implementation for displaying a search server. - * - * Available variables: - * - server: The server entity. - * - server_info_table: The server info table. - * - description: The server description. - * - * @see template_preprocess_search_api_server() - * - * @ingroup themeable - */ -#} -{% if acquia_search_info_box %} - {{ acquia_search_info_box }} -{% endif %} -{% if description %} -

{{ 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 = '
'; - $output .= '
'; - $output .= '
'; - $output .= '

' . $this->t('Acquia Subscription', ['@acquia-network' => 'https://www.acquia.com/customer-success']) . '

'; - $output .= '

' . $this->t('A suite of products and services to create & maintain killer web experiences built on Drupal') . '

'; - $output .= '
'; - $output .= '
'; - $output .= '

' . $this->t('Answers you need') . '

'; - $image = [ - '#theme' => 'image', - '#attributes' => [ - 'src' => Url::fromUri('base:' . $path . '/images/icon-library.png', ['absolute' => TRUE])->toString(), - ], - ]; - $output .= '' . render($image) . ''; - $output .= '

' . $this->t("Tap the collective knowledge of Acquia’s technical support team & partners.") . '

'; - $output .= '
'; - $output .= '
'; - $output .= '

' . $this->t('Tools to extend your site') . '

'; - $image = [ - '#theme' => 'image', - '#attributes' => [ - 'src' => Url::fromUri('base:' . $path . '/images/icon-tools.png', ['absolute' => TRUE])->toString(), - ], - ]; - $output .= '' . render($image) . ''; - $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 .= '
'; - $output .= '
'; - $output .= '

' . $this->t('Support when you want it') . '

'; - $image = [ - '#theme' => 'image', - '#attributes' => [ - 'src' => Url::fromUri('base:' . $path . '/images/icon-support.png', ['absolute' => TRUE])->toString(), - ], - ]; - $output .= '' . render($image) . ''; - $output .= '

' . $this->t("Experienced Drupalists are available to support you whenever you need it.") . '

'; - $output .= '
'; - $output .= '
'; - $output .= '
'; - $output .= '
'; - $output .= $banner; - $output .= '
'; - $output .= '
'; - $output .= '
'; - $build['output'] = [ - '#markup' => $output, - ]; - - return $build; - } - -} diff --git a/docroot/modules/contrib/acquia_connector/src/Controller/StatusController.php b/docroot/modules/contrib/acquia_connector/src/Controller/StatusController.php index 1af224d3f6..c45536e172 100644 --- a/docroot/modules/contrib/acquia_connector/src/Controller/StatusController.php +++ b/docroot/modules/contrib/acquia_connector/src/Controller/StatusController.php @@ -3,26 +3,74 @@ namespace Drupal\acquia_connector\Controller; use Drupal\acquia_connector\Subscription; -use Drupal\Component\Utility\UrlHelper; -use Drupal\Core\Access\AccessResultAllowed; -use Drupal\Core\Access\AccessResultForbidden; +use Drupal\Core\Access\AccessResult; use Drupal\Core\Controller\ControllerBase; +use Drupal\Core\Extension\ModuleHandlerInterface; +use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\HttpFoundation\JsonResponse; +use Symfony\Component\HttpFoundation\RequestStack; /** - * Class StatusController. + * Checks the current status of the Acquia Service. */ class StatusController extends ControllerBase { /** - * Menu callback for 'admin/config/system/acquia-agent/refresh-status'. + * Acquia Subscription Service. + * + * @var \Drupal\acquia_connector\Subscription + */ + protected $subscription; + + /** + * The request stack. + * + * @var \Symfony\Component\HttpFoundation\RequestStack + */ + protected $requestStack; + + /** + * The module handler. + * + * @var \Drupal\Core\Extension\ModuleHandlerInterface + */ + protected $moduleHandler; + + /** + * WebhooksSettingsForm constructor. + * + * @param \Drupal\acquia_connector\Subscription $subscription + * The event dispatcher. + * @param \Symfony\Component\HttpFoundation\RequestStack $request_stack + * The request stack. + * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler + * The module handler. + */ + public function __construct(Subscription $subscription, RequestStack $request_stack, ModuleHandlerInterface $module_handler) { + $this->subscription = $subscription; + $this->requestStack = $request_stack; + $this->moduleHandler = $module_handler; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static( + $container->get('acquia_connector.subscription'), + $container->get('request_stack'), + $container->get('module_handler') + ); + } + + /** + * Menu callback for 'admin/config/services/acquia-agent/refresh-status'. */ public function refresh() { // Refresh subscription information, so we are sure about our update status. // We send a heartbeat here so that all of our status information gets // updated locally via the return data. - $subscription = new Subscription(); - $subscription->update(); + $this->subscription->getSubscription(TRUE); // Return to the setting pages (or destination). return $this->redirect('system.status'); @@ -34,16 +82,11 @@ public function refresh() { * Used by Acquia uptime monitoring. */ public function json() { - // We don't want this page cached. - \Drupal::service('page_cache_kill_switch')->trigger(); - - $performance_config = $this->config('system.performance'); - $data = [ 'version' => '1.0', 'data' => [ 'maintenance_mode' => (bool) $this->state()->get('system.maintenance_mode'), - 'cache' => $performance_config->get('cache.page.use_internal'), + 'cache' => $this->moduleHandler->moduleExists('page_cache'), 'block_cache' => FALSE, ], ]; @@ -55,25 +98,26 @@ public function json() { * Access callback for json() callback. */ public function access() { - $request = \Drupal::request(); + $request = $this->requestStack->getCurrentRequest(); + assert($request !== NULL); $nonce = $request->get('nonce', FALSE); $connector_config = $this->config('acquia_connector.settings'); // If we don't have all the query params, leave now. if (!$nonce) { - return AccessResultForbidden::forbidden(); + return AccessResult::forbidden('Missing nonce.'); } - $sub_data = $this->state()->get('acquia_subscription_data'); - $sub_uuid = $this->getIdFromSub($sub_data); - - if (!empty($sub_uuid)) { - $expected_hash = hash('sha1', "{$sub_uuid}:{$nonce}"); + $sub_data = $this->subscription->getSubscription(); + if (empty($sub_data['uuid'])) { + return AccessResult::forbidden('Missing application UUID.'); + } + $sub_uuid = $sub_data['uuid']; - // If the generated hash matches the hash from $_GET['key'], we're good. - if ($request->get('key', FALSE) === $expected_hash) { - return AccessResultAllowed::allowed(); - } + $expected_hash = hash('sha1', "{$sub_uuid}:{$nonce}"); + // If the generated hash matches the hash from $_GET['key'], we're good. + if ($request->get('key', FALSE) === $expected_hash) { + return AccessResult::allowed(); } // Log the request if validation failed and debug is enabled. @@ -90,32 +134,7 @@ public function access() { $this->getLogger('acquia_agent')->notice('Site status request: @data', ['@data' => var_export($info, TRUE)]); } - return AccessResultForbidden::forbidden(); - } - - /** - * Gets the subscription UUID from subscription data. - * - * @param array $sub_data - * An array of subscription data. - * - * @see acquia_agent_settings('acquia_subscription_data') - * - * @return string - * The UUID taken from the subscription data. - */ - public function getIdFromSub(array $sub_data) { - if (!empty($sub_data['uuid'])) { - return $sub_data['uuid']; - } - - // Otherwise, get this form the sub url. - $url = UrlHelper::parse($sub_data['href']); - $parts = explode('/', $url['path']); - // Remove '/dashboard'. - array_pop($parts); - - return end($parts); + return AccessResult::forbidden('Could not validate key.'); } } diff --git a/docroot/modules/contrib/acquia_connector/src/Controller/TestStatusController.php b/docroot/modules/contrib/acquia_connector/src/Controller/TestStatusController.php deleted file mode 100644 index 26fcfaf0fb..0000000000 --- a/docroot/modules/contrib/acquia_connector/src/Controller/TestStatusController.php +++ /dev/null @@ -1,144 +0,0 @@ -moduleHandler()->getImplementations('acquia_connector_spi_test') as $module) { - $function = $module . '_acquia_connector_spi_test'; - if (function_exists($function)) { - - $result = $this->testValidate($function()); - if (!$result['result']) { - $custom_data[$module] = $result; - - foreach ($result['failure'] as $test_name => $test_failures) { - foreach ($test_failures as $test_param => $test_value) { - $variables = [ - '@module' => $module, - '@message' => $test_value['message'], - '@param_name' => $test_param, - '@test' => $test_name, - '@value' => $test_value['value'], - ]; - // Only log if we're performing a full validation check. - if ($log) { - $this->messenger()->addError($this->t("Custom test validation failed for @test in @module and has been logged: @message for parameter '@param_name'; current value '@value'.", $variables)); - $this->getLogger('acquia spi test')->notice("Custom test validation failed: @message for parameter '@param_name'; current value '@value'. (Test '@test_name' in module '@module_name')", $variables); - } - } - } - } - } - } - - // If a full validation check is being performed, go to the status page to - // show the results. - if ($log) { - return $this->redirect('system.status'); - } - - return $custom_data; - } - - /** - * Validates data from custom test callbacks. - * - * @param array $collection - * An associative array containing a collection of user-contributed tests. - * - * @return array - * An associative array containing the validation result of the given tests, - * along with any failed parameters. - */ - public function testValidate(array $collection) { - $result = TRUE; - $check_result_value = []; - - // Load valid categories and severities. - $categories = ['performance', 'security', 'best_practices']; - $severities = [0, 1, 2, 4, 8, 16, 32, 64, 128]; - - foreach ($collection as $machine_name => $tests) { - foreach ($tests as $check_name => $check_value) { - $fail_value = ''; - $message = ''; - - $check_name = strtolower($check_name); - $check_value = (is_string($check_value)) ? strtolower($check_value) : $check_value; - - // Validate the data inputs for each check. - switch ($check_name) { - case 'category': - if (!is_string($check_value) || !in_array($check_value, $categories)) { - $type = gettype($check_value); - $fail_value = "$check_value ($type)"; - $message = 'Value must be a string and one of ' . implode(', ', $categories); - } - break; - - case 'solved': - if (!is_bool($check_value)) { - $type = gettype($check_value); - $fail_value = "$check_value ($type)"; - $message = 'Value must be a boolean'; - } - break; - - case 'severity': - if (!is_int($check_value) || !in_array($check_value, $severities)) { - $type = gettype($check_value); - $fail_value = "$check_value ($type)"; - $message = 'Value must be an integer and set to one of ' . implode(', ', $severities); - } - break; - - default: - if (!is_string($check_value) || strlen($check_value) > 1024) { - $type = gettype($check_value); - $fail_value = "$check_value ($type)"; - $message = 'Value must be a string and no more than 1024 characters'; - } - break; - } - - if (!empty($fail_value) && !empty($message)) { - $check_result_value['failed'][$machine_name][$check_name]['value'] = $fail_value; - $check_result_value['failed'][$machine_name][$check_name]['message'] = $message; - } - } - } - - // If there were any failures, the test has failed. Into exile it must go. - if (!empty($check_result_value)) { - $result = FALSE; - } - - return [ - 'result' => $result, - 'failure' => (isset($check_result_value['failed'])) ? $check_result_value['failed'] : [], - ]; - } - -} diff --git a/docroot/modules/contrib/acquia_connector/src/Controller/VariablesController.php b/docroot/modules/contrib/acquia_connector/src/Controller/VariablesController.php deleted file mode 100644 index e934d266d5..0000000000 --- a/docroot/modules/contrib/acquia_connector/src/Controller/VariablesController.php +++ /dev/null @@ -1,275 +0,0 @@ -mapping = \Drupal::config('acquia_connector.settings')->get('mapping'); - } - - /** - * Load configs for all enabled modules. - * - * @return array - * Array of Drupal configs. - */ - public function getAllConfigs() { - if (!is_null($this->configs)) { - return $this->configs; - } - - $this->configs = []; - $names = \Drupal::configFactory()->listAll(); - foreach ($names as $config_name) { - $this->configs[$config_name] = \Drupal::config($config_name)->get(); - } - - return $this->configs; - } - - /** - * Get a variable value by the variable name. - * - * @param string $var - * Variable name. - * - * @return mixed - * Variable value. - * - * @throws \UnexpectedValueException - */ - public function getVariableValue($var) { - - // We have no mapping for the variable. - if (empty($this->mapping[$var])) { - throw new \UnexpectedValueException($var); - } - - // Variable type (for state, setting and container parameter only). - // Holds Config name for the configuration object variables. - $var_type = $this->mapping[$var][0]; - // Variable machine name (for state, settings and container parameter only). - $var_name = !empty($this->mapping[$var][1]) ? $this->mapping[$var][1] : NULL; - - // Variable is Drupal state. - if ($var_type == 'state') { - return \Drupal::state()->get($var_name); - } - - // Variable is Drupal setting. - if ($var_type == 'settings') { - return Settings::get($var_name); - } - - // Variable is Container Parameter. - if ($var_type == 'container_parameter') { - if (\Drupal::hasContainer()) { - try { - return \Drupal::getContainer()->getParameter($var_name); - } - catch (ParameterNotFoundException $e) { - // Parameter not found. - } - } - throw new \UnexpectedValueException($var); - } - - // Variable is data from Configuration object (D7 Variable). - // We can not detect this variable type so we're processing it in last turn. - $key_exists = NULL; - $config = self::getAllConfigs(); - $value = NestedArray::getValue($config, $this->mapping[$var], $key_exists); - if ($key_exists) { - return $value; - } - - throw new \UnexpectedValueException($var); - - } - - /** - * Get all system variables. - * - * @return string - * Variables values keyed by the variable name. - */ - public function getVariablesData() { - // Send SPI definition timestamp to see if the site needs updates. - $data = [ - 'acquia_spi_def_timestamp' => $this->state()->get('acquia_spi_data.def_timestamp', 0), - ]; - $variables = [ - 'acquia_spi_send_node_user', - 'acquia_spi_admin_priv', - 'acquia_spi_send_watchdog', - 'acquia_spi_use_cron', - 'cache_backends', - 'cache_default_class', - 'cache_inc', - 'cron_safe_threshold', - 'googleanalytics_cache', - 'error_level', - 'preprocess_js', - 'page_cache_maximum_age', - 'block_cache', - 'preprocess_css', - 'page_compression', - 'cron_last', - 'clean_url', - 'redirect_global_clean', - 'theme_zen_settings', - 'site_offline', - 'site_name', - 'user_register', - 'user_signatures', - 'user_admin_role', - 'user_email_verification', - 'user_cancel_method', - 'filter_fallback_format', - 'dblog_row_limit', - 'date_default_timezone', - 'file_default_scheme', - 'install_profile', - 'maintenance_mode', - 'update_last_check', - 'site_default_country', - 'acquia_spi_saved_variables', - 'acquia_spi_set_variables_automatic', - 'acquia_spi_ignored_set_variables', - 'acquia_spi_set_variables_override', - 'http_response_debug_cacheability_headers', - ]; - - $spi_def_vars = $this->state()->get('acquia_spi_data.def_vars', []); - $waived_spi_def_vars = $this->state()->get('acquia_spi_data.def_waived_vars', []); - // Merge hard coded $variables with vars from SPI definition. - foreach ($spi_def_vars as $var_name => $var) { - if (!in_array($var_name, $waived_spi_def_vars) && !in_array($var_name, $variables)) { - $variables[] = $var_name; - } - } - - foreach ($variables as $name) { - try { - $data[$name] = $this->getVariableValue($name); - } - catch (\UnexpectedValueException $e) { - // Variable does not exist. - } - } - - // Unset waived vars so they won't be sent to NSPI. - foreach ($data as $var_name => $var) { - if (in_array($var_name, $waived_spi_def_vars)) { - unset($data[$var_name]); - } - } - - // Collapse to JSON string to simplify transport. - return Json::encode($data); - } - - /** - * Set variables from NSPI response. - * - * @param array|bool $set_variables - * Variables to be set. - */ - public function setVariables($set_variables) { - $this->getLogger('acquia spi') - ->notice('SPI set variables: @messages', ['@messages' => implode(', ', $set_variables)]); - if (empty($set_variables)) { - return; - } - $saved = []; - $ignored = \Drupal::config('acquia_connector.settings')->get('spi.ignored_set_variables'); - - if (!\Drupal::config('acquia_connector.settings')->get('spi.set_variables_override')) { - $ignored[] = 'acquia_spi_set_variables_automatic'; - } - // Some variables can never be set. - $ignored = array_merge($ignored, [ - 'drupal_private_key', - 'site_mail', - 'site_name', - 'maintenance_mode', - 'user_register', - ]); - // Variables that can be automatically set. - $whitelist = \Drupal::config('acquia_connector.settings')->get('spi.set_variables_automatic'); - foreach ($set_variables as $key => $value) { - // Approved variables get set immediately unless ignored. - if (in_array($key, $whitelist) && !in_array($key, $ignored)) { - if (!empty($this->mapping[$key])) { - // State. - if ($this->mapping[$key][0] == 'state' and !empty($this->mapping[$key][1])) { - \Drupal::state()->set($this->mapping[$key][1], $value); - $saved[] = $key; - } - elseif ($this->mapping[$key][0] == 'settings') { - // No setter for Settings. - } - // Variable. - else { - $mapping_row_copy = $this->mapping[$key]; - $config_name = array_shift($mapping_row_copy); - $variable_name = implode('.', $mapping_row_copy); - \Drupal::configFactory()->getEditable($config_name)->set($variable_name, $value); - \Drupal::configFactory()->getEditable($config_name)->save(); - $saved[] = $key; - } - } - // D8 implementation "config.name:variable.name". - elseif (preg_match('/^([^\s]+):([^\s]+)$/ui', $key, $regs)) { - $config_name = $regs[1]; - $variable_name = $regs[2]; - \Drupal::configFactory()->getEditable($config_name)->set($variable_name, $value); - \Drupal::configFactory()->getEditable($config_name)->save(); - $saved[] = $key; - } - else { - $this->getLogger('acquia spi')->notice('Variable is not implemented: ' . $key); - } - } - } - if (!empty($saved)) { - \Drupal::configFactory() - ->getEditable('acquia_connector.settings') - ->set('spi.saved_variables', ['variables' => $saved, 'time' => time()]); - \Drupal::configFactory()->getEditable('acquia_connector.settings')->save(); - $this->getLogger('acquia spi') - ->notice('Saved variables from the Acquia: @variables', ['@variables' => implode(', ', $saved)]); - } - else { - $this->getLogger('acquia spi')->notice('Did not save any variables from Acquia.'); - } - } - -} diff --git a/docroot/modules/contrib/acquia_connector/src/CronService.php b/docroot/modules/contrib/acquia_connector/src/CronService.php deleted file mode 100644 index cf94485e61..0000000000 --- a/docroot/modules/contrib/acquia_connector/src/CronService.php +++ /dev/null @@ -1,32 +0,0 @@ -get('acquia_subscription_data.timestamp', FALSE); - if (!$last_update_attempt || ((\Drupal::time()->getRequestTime() - $last_update_attempt) >= 60 * 60)) { - $subscription = new Subscription(); - $subscription->update(); - } - } - } - -} diff --git a/docroot/modules/contrib/acquia_connector/src/CryptConnector.php b/docroot/modules/contrib/acquia_connector/src/CryptConnector.php deleted file mode 100644 index a9ad3c1888..0000000000 --- a/docroot/modules/contrib/acquia_connector/src/CryptConnector.php +++ /dev/null @@ -1,103 +0,0 @@ -generateSalt(). - * - * @var string - */ - private $setting; - - /** - * CryptConnector constructor. - * - * @param string $algo - * The string name of a hashing algorithm usable by hash(), like 'sha256'. - * @param string $password - * Plain-text password up to 512 bytes (128 to 512 UTF-8 characters) to - * hash. - * @param string $setting - * An existing hash or the output of $this->generateSalt(). Must be at least - * 12 characters (the settings and salt). - * @param mixed $extra_md5 - * (Deprecated) If not empty password needs to be hashed with MD5 first. - */ - public function __construct($algo, $password, $setting, $extra_md5) { - $this->algo = $algo; - $this->password = $password; - $this->setting = $setting; - } - - /** - * Crypt pass. - * - * @return string - * Crypt password. - */ - public function cryptPass() { - $crypt_pass = $this->crypt($this->algo, $this->password, $this->setting); - - return $crypt_pass; - } - - /** - * Helper function. Calculate sha1 hash. - * - * @param string $key - * Acquia Subscription Key. - * @param string $string - * String to calculate hash. - * - * @return string - * Sha1 string. - */ - public static function acquiaHash($key, $string) { - return sha1((str_pad($key, 64, chr(0x00)) ^ (str_repeat(chr(0x5c), 64))) . pack("H*", sha1((str_pad($key, 64, chr(0x00)) ^ (str_repeat(chr(0x36), 64))) . $string))); - } - - /** - * Derive a key for the solr hmac using a salt, id and key. - * - * @param string $salt - * Salt. - * @param string $id - * Acquia Subscription ID. - * @param string $key - * Acquia Subscription Key. - * - * @return string - * Derived Key. - */ - public static function createDerivedKey($salt, $id, $key) { - $derivation_string = $id . 'solr' . $salt; - return hash_hmac('sha1', str_pad($derivation_string, 80, $derivation_string), $key); - } - -} diff --git a/docroot/modules/contrib/acquia_connector/src/Event/AcquiaProductSettingsEvent.php b/docroot/modules/contrib/acquia_connector/src/Event/AcquiaProductSettingsEvent.php new file mode 100644 index 0000000000..599a85bf34 --- /dev/null +++ b/docroot/modules/contrib/acquia_connector/src/Event/AcquiaProductSettingsEvent.php @@ -0,0 +1,129 @@ +form = $form; + $this->formState = $form_state; + $this->subscription = $subscription; + } + + /** + * Gets the form attached to the Event. + * + * @return array + * The settings form. + */ + public function getForm() { + return $this->form; + } + + /** + * Gets the form state values attached to the Event. + * + * @return array + * The settings form_state values. + */ + public function getFormState() { + return $this->formState->getValues(); + } + + /** + * Sets the 'product settings' key for the Connector form. + * + * @param string $product + * Product Name. + * @param string $product_machine_name + * Product Machine Name. + * @param array $product_setting_form + * Product Settings form. + */ + public function setProductSettings($product, $product_machine_name, array $product_setting_form) { + $this->form['product_settings'][$product_machine_name] = [ + '#type' => 'fieldset', + '#title' => $this->t("%product_name", ['%product_name' => $product]), + '#collapsible' => TRUE, + '#collapsed' => FALSE, + ]; + $this->form['product_settings'][$product_machine_name]['settings'] = $product_setting_form; + } + + /** + * Retreives third party setting from Connector. + * + * @return mixed + * A reference to the value for that property, or NULL if the property does + * not exist. + */ + public function getThirdPartySetting($module_name, $form_value) { + return $this->formState->get([ + 'product_settings', + $module_name, + 'settings', + $form_value, + ]); + } + + /** + * Retrieve the Acquia Subscription. + * + * @return \Drupal\acquia_connector\Subscription + * The Subscription. + */ + public function getSubscription() { + return $this->subscription; + } + + /** + * Alters the 'product settings' submission for the Connector form. + * + * @param array $form_value + * Form State values. + */ + public function alterProductSettingsSubmit(array $form_value) { + $this->formState->setValues($form_value); + } + +} diff --git a/docroot/modules/contrib/acquia_connector/src/Event/AcquiaSubscriptionDataEvent.php b/docroot/modules/contrib/acquia_connector/src/Event/AcquiaSubscriptionDataEvent.php new file mode 100644 index 0000000000..f4852b644c --- /dev/null +++ b/docroot/modules/contrib/acquia_connector/src/Event/AcquiaSubscriptionDataEvent.php @@ -0,0 +1,107 @@ + 'Acquia Network', + ]; + + /** + * Config Factory for events to fetch their own configs. + * + * @var \Drupal\Core\Config\ConfigFactoryInterface + */ + protected $configFactory; + + /** + * Pass in connector config by default to all events. + * + * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory + * Acquia Connector settings. + * @param array $subscription_data + * Raw Subscription Data. + */ + public function __construct(ConfigFactoryInterface $config_factory, array $subscription_data) { + $this->configFactory = $config_factory; + $this->subscriptionData = $subscription_data; + } + + /** + * Gets the Acquia Connector settings object. + * + * @return array + * The Acquia Subscription data. + */ + public function getData() { + return $this->subscriptionData; + } + + /** + * Gets product specific subscription data. + * + * @return array + * The Acquia Subscription data. + */ + public function getProductData() { + return $this->productData; + } + + /** + * Return static config for an event subscriber. + * + * @return \Drupal\Core\Config\Config + * The Config Object. + */ + public function getConfig($config_settings) { + return $this->configFactory->get($config_settings); + } + + /** + * Set the subscription data. + * + * Event subscribers to this event should be mindful to use the + * NestedArray::mergeDeepArray() method to merge data together and not + * overwrite other event subscriber's data. + * + * @param array $data + * Data to set. + */ + public function setData(array $data): void { + $this->subscriptionData = $data; + } + + /** + * Set Acquia Product Data. + * + * This event is preferable to use over the setData method, which overwrites + * all data. This limits the scope of data to a specific product array key. + * + * @param string $product + * Acquia Product to set data to. + * @param array $data + * Data to set. + */ + public function setProductData(string $product, array $data): void { + $this->productData[$product] = $data; + } + +} diff --git a/docroot/modules/contrib/acquia_connector/src/Event/AcquiaSubscriptionSettingsEvent.php b/docroot/modules/contrib/acquia_connector/src/Event/AcquiaSubscriptionSettingsEvent.php new file mode 100644 index 0000000000..1fd4dc5707 --- /dev/null +++ b/docroot/modules/contrib/acquia_connector/src/Event/AcquiaSubscriptionSettingsEvent.php @@ -0,0 +1,94 @@ +config = $config_factory->getEditable('acquia_connector.settings'); + } + + /** + * Gets the Acquia Connector settings object. + * + * @return \Drupal\acquia_connector\Settings + * The Acquia settings. + */ + public function getSettings() { + return $this->settings; + } + + /** + * Return the static Acquia Settings config array. + * + * @return \Drupal\Core\Config\Config + * The Config Object. + */ + public function getConfig() { + return $this->config; + } + + /** + * Set the Acquia settings object. + * + * @param \Drupal\acquia_connector\Settings $settings + * The client settings. + */ + public function setSettings(Settings $settings) { + $this->settings = $settings; + } + + /** + * Gets the providers of the settings object. + * + * @return string + * The Provider. + */ + public function getProvider() { + return $this->provider; + } + + /** + * Sets the provider of the settings object. + * + * @param string $provider + * The Provider. + */ + public function setProvider($provider) { + $this->provider = $provider; + } + +} diff --git a/docroot/modules/contrib/acquia_connector/src/Event/EventBase.php b/docroot/modules/contrib/acquia_connector/src/Event/EventBase.php new file mode 100644 index 0000000000..c2313a939d --- /dev/null +++ b/docroot/modules/contrib/acquia_connector/src/Event/EventBase.php @@ -0,0 +1,23 @@ +subscription = $subscription; + } + + /** + * {@inheritdoc} + */ + public static function getSubscribedEvents() { + return [ + ConfigEvents::SAVE => 'onSave', + ]; + } + + /** + * Config save event handler. + * + * @param \Drupal\Core\Config\ConfigCrudEvent $event + * The event. + */ + public function onSave(ConfigCrudEvent $event) { + if ($event->getConfig()->getName() === 'acquia_connector.settings' + && $event->isChanged('third_party_settings') + ) { + $this->subscription->getSubscription(TRUE); + } + } + +} diff --git a/docroot/modules/contrib/acquia_connector/src/EventSubscriber/GetSettings/FromAcquiaCloud.php b/docroot/modules/contrib/acquia_connector/src/EventSubscriber/GetSettings/FromAcquiaCloud.php new file mode 100644 index 0000000000..e9f6deb5c5 --- /dev/null +++ b/docroot/modules/contrib/acquia_connector/src/EventSubscriber/GetSettings/FromAcquiaCloud.php @@ -0,0 +1,130 @@ +logger = $logger; + $this->messenger = $messenger; + $this->state = $state; + } + + /** + * {@inheritdoc} + */ + public static function getSubscribedEvents() { + $events[AcquiaConnectorEvents::GET_SETTINGS][] = ['onGetSettings', 100]; + return $events; + } + + /** + * Extract settings from environment and create a Settings object. + * + * @param \Drupal\acquia_connector\Event\AcquiaSubscriptionSettingsEvent $event + * The dispatched event. + * + * @see \Acquia\ContentHubClient\Settings + */ + public function onGetSettings(AcquiaSubscriptionSettingsEvent $event) { + $metadata = []; + foreach (self::ENVIRONMENT_VARIABLES as $var) { + if (!empty(getenv($var))) { + $metadata[$var] = getenv($var); + } + } + + // If the expected Acquia cloud environment variables are missing, return. + if (count($metadata) !== count(self::ENVIRONMENT_VARIABLES)) { + return; + } + // Cloud IDE environments do not have network information injected. + if (preg_match('/^(ide|ode\d*)$/', getenv('AH_SITE_ENVIRONMENT') ?: '') !== 0) { + return; + } + + // Store the default Cloud settings in the metadata storage. + global $config; + $metadata['ah_network_identifier'] = CoreSettings::get('ah_network_identifier') ?? $config['ah_network_identifier']; + $metadata['ah_network_key'] = CoreSettings::get('ah_network_key') ?? $config['ah_network_key']; + + // Use the state service since customers can override subscription data. + $state = $this->state->getMultiple([ + 'acquia_connector.key', + 'acquia_connector.identifier', + 'acquia_connector.application_uuid', + 'spi.site_name', + 'spi.site_machine_name', + ]); + + $settings = new Settings( + $event->getConfig(), + $state['acquia_connector.identifier'] ?? $metadata['ah_network_identifier'], + $state['acquia_connector.key'] ?? $metadata['ah_network_key'], + $state['acquia_connector.application_uuid'] ?? $metadata['AH_APPLICATION_UUID'], + $metadata + ); + + $event->setProvider('acquia_cloud'); + $event->setSettings($settings); + // @phpstan-ignore-next-line + $event->stopPropagation(); + } + +} diff --git a/docroot/modules/contrib/acquia_connector/src/EventSubscriber/GetSettings/FromCoreSettings.php b/docroot/modules/contrib/acquia_connector/src/EventSubscriber/GetSettings/FromCoreSettings.php new file mode 100644 index 0000000000..ec53dee71c --- /dev/null +++ b/docroot/modules/contrib/acquia_connector/src/EventSubscriber/GetSettings/FromCoreSettings.php @@ -0,0 +1,42 @@ +setSettings($settings); + $event->setProvider('core_settings'); + // @phpstan-ignore-next-line + $event->stopPropagation(); + } + } + +} diff --git a/docroot/modules/contrib/acquia_connector/src/EventSubscriber/GetSettings/FromCoreState.php b/docroot/modules/contrib/acquia_connector/src/EventSubscriber/GetSettings/FromCoreState.php new file mode 100644 index 0000000000..d7b9570696 --- /dev/null +++ b/docroot/modules/contrib/acquia_connector/src/EventSubscriber/GetSettings/FromCoreState.php @@ -0,0 +1,70 @@ +state = $state; + } + + /** + * {@inheritdoc} + */ + public static function getSubscribedEvents() { + $events[AcquiaConnectorEvents::GET_SETTINGS][] = ['onGetSettings', 0]; + return $events; + } + + /** + * Extract settings from configuration and create a Settings object. + * + * @param \Drupal\acquia_connector\Event\AcquiaSubscriptionSettingsEvent $event + * The dispatched event. + * + * @see \Drupal\acquia_connector\Settings + */ + public function onGetSettings(AcquiaSubscriptionSettingsEvent $event) { + $state = $this->state->getMultiple([ + 'acquia_connector.key', + 'acquia_connector.identifier', + 'acquia_connector.application_uuid', + ]); + + $settings = new Settings( + $event->getConfig(), + $state['acquia_connector.identifier'] ?? '', + $state['acquia_connector.key'] ?? '', + $state['acquia_connector.application_uuid'] ?? '', + ); + + $settings->setReadOnly(FALSE); + $event->setSettings($settings); + $event->setProvider('core_state'); + // @phpstan-ignore-next-line + $event->stopPropagation(); + } + +} diff --git a/docroot/modules/contrib/acquia_connector/src/EventSubscriber/InitSubscriber.php b/docroot/modules/contrib/acquia_connector/src/EventSubscriber/InitSubscriber.php deleted file mode 100644 index ad6d928f3e..0000000000 --- a/docroot/modules/contrib/acquia_connector/src/EventSubscriber/InitSubscriber.php +++ /dev/null @@ -1,115 +0,0 @@ -configFactory = $config_factory; - $this->state = $state; - $this->cache = $cache; - } - - /** - * Display a message asking the user to connect to Acquia. - * - * @param \Symfony\Component\HttpKernel\Event\GetResponseEvent $event - * Event. - */ - public function onKernelRequest(GetResponseEvent $event) { - - acquia_connector_auto_connect(); - - acquia_connector_show_free_tier_promo(); - - } - - /** - * Refresh subscription information. - * - * @param \Symfony\Component\HttpKernel\Event\FilterControllerEvent $event - * Event. - */ - public function onKernelController(FilterControllerEvent $event) { - if ($event->getRequest()->attributes->get('_route') != 'update.manual_status') { - return; - } - - $controller = $event->getController(); - /* - * $controller passed can be either a class or a Closure. - * This is not usual in Symfony but it may happen. - * If it is a class, it comes in array format - */ - if (!is_array($controller)) { - return; - } - - if ($controller[0] instanceof UpdateController) { - // Refresh subscription information, so we are sure about our update - // status. We send a heartbeat here so that all of our status information - // gets updated locally via the return data. - $subscription = new Subscription(); - $subscription->update(); - } - } - - /** - * {@inheritdoc} - */ - public static function getSubscribedEvents() { - $events[KernelEvents::REQUEST][] = ['onKernelRequest']; - $events[KernelEvents::CONTROLLER][] = ['onKernelController']; - return $events; - } - -} diff --git a/docroot/modules/contrib/acquia_connector/src/EventSubscriber/KernelTerminate/AcquiaTelemetry.php b/docroot/modules/contrib/acquia_connector/src/EventSubscriber/KernelTerminate/AcquiaTelemetry.php new file mode 100644 index 0000000000..8e00d96cc3 --- /dev/null +++ b/docroot/modules/contrib/acquia_connector/src/EventSubscriber/KernelTerminate/AcquiaTelemetry.php @@ -0,0 +1,285 @@ +moduleList = $module_list; + $this->httpClient = $http_client; + $this->configFactory = $config_factory; + $this->state = $state; + $this->time = $time; + } + + /** + * {@inheritdoc} + */ + public static function getSubscribedEvents() { + $events[KernelEvents::TERMINATE][] = ['onTerminateResponse']; + return $events; + } + + /** + * Sends Telemetry on a daily basis. This occurs after the response is sent. + * + * @param \Symfony\Component\HttpKernel\Event\KernelEvent $event + * The event. + */ + public function onTerminateResponse(KernelEvent $event) { + $send_timestamp = $this->state->get('acquia_connector.telemetry.timestamp'); + if ($this->time->getCurrentTime() - $send_timestamp > 86400) { + $this->sendTelemetry("Drupal Module Statistics"); + $this->state->set('acquia_connector.telemetry.timestamp', $this->time->getCurrentTime()); + } + } + + /** + * Returns the Amplitude API key. + * + * This is not intended to be private. It is typically included in client + * side code. Fetching data requires an additional API secret. + * + * @see https://developers.amplitude.com/#http-api + * + * @return string + * The Amplitude API key. + */ + private function getApiKey() { + return Settings::get('acquia_connector.telemetry.key', 'f32aacddde42ad34f5a3078a621f37a9'); + } + + /** + * Sends an event to Amplitude. + * + * @param array $event + * The Amplitude event. + * + * @throws \GuzzleHttp\Exception\GuzzleException + * + * @see https://developers.amplitude.com/#http-api + */ + private function sendEvent(array $event) { + $this->httpClient->request('POST', $this->apiUrl, [ + 'form_params' => [ + 'api_key' => $this->getApiKey(), + 'event' => Json::encode($event), + ], + ]); + } + + /** + * Creates and sends an event to Amplitude. + * + * @param string $event_type + * The event type. This accepts any string that is not reserved. Reserved + * event types include: "[Amplitude] Start Session", "[Amplitude] End + * Session", "[Amplitude] Revenue", "[Amplitude] Revenue (Verified)", + * "[Amplitude] Revenue (Unverified)", and "[Amplitude] Merged User". + * @param array $event_properties + * (optional) Event properties. + * + * @return bool + * TRUE if event was successfully sent, otherwise FALSE. + * + * @throws \Exception + * Thrown if state key acquia_telemetry.loud is TRUE and request fails. + * + * @see https://amplitude.zendesk.com/hc/en-us/articles/204771828#keys-for-the-event-argument + */ + public function sendTelemetry($event_type, array $event_properties = []) { + $event = $this->createEvent($event_type, $event_properties); + + // Failure to send Telemetry should never cause a user facing error or + // interrupt a process. Telemetry failure should be graceful and quiet. + try { + $this->sendEvent($event); + return TRUE; + } + catch (\Exception $e) { + if ($this->state->get('acquia_connector.telemetry.loud')) { + throw new \Exception($e->getMessage(), $e->getCode(), $e); + } + return FALSE; + } + } + + /** + * Get an array of information about Lightning extensions. + * + * @return array + * An array of extension info keyed by the extensions machine name. E.g., + * ['lightning_layout' => ['version' => '8.2.0', 'status' => 'enabled']]. + */ + private function getExtensionInfo() { + $all_modules = $this->moduleList->getAllAvailableInfo(); + $installed_modules = $this->moduleList->getAllInstalledInfo(); + $extension_info = []; + + foreach ($all_modules as $name => $extension) { + // Remove all custom modules from reporting. + if (strpos($this->moduleList->getPath($name), '/custom/') !== FALSE) { + continue; + } + + // Tag all core modules in use. If the version matches the core + // Version, assume it is a core module. + $core_comparison = [ + $extension['version'], + $extension['core_version_requirement'], + \Drupal::VERSION, + ]; + if (count(array_unique($core_comparison)) === 1) { + if (array_key_exists($name, $installed_modules)) { + $extension_info['core'][$name] = 'enabled'; + } + continue; + } + + // Version is unset for dev versions. In order to generate reports, we + // need some value for version, even if it is just the major version. + $extension_info['contrib'][$name]['version'] = $extension['version'] ?? 'dev'; + + // Check if module is installed. + $extension_info['contrib'][$name]['status'] = array_key_exists($name, $installed_modules) ? 'enabled' : 'disabled'; + } + + return $extension_info; + } + + /** + * Creates an Amplitude event. + * + * @param string $type + * The event type. + * @param array $properties + * The event properties. + * + * @return array + * An Amplitude event with basic info already populated. + */ + private function createEvent($type, array $properties) { + $modules = $this->getExtensionInfo(); + $default_properties = [ + 'extensions' => $modules['contrib'], + 'php' => [ + 'version' => phpversion(), + ], + 'drupal' => [ + 'version' => \Drupal::VERSION, + 'core_enabled' => $modules['core'], + ], + ]; + + return [ + 'event_type' => $type, + 'user_id' => $this->getUserId(), + 'event_properties' => NestedArray::mergeDeep($default_properties, $properties), + ]; + } + + /** + * Gets a unique ID for this application. "User ID" is an Amplitude term. + * + * @return string + * Returns a hashed site uuid. + */ + private function getUserId() { + return Crypt::hashBase64($this->configFactory->get('system.site')->get('uuid')); + } + + /** + * Gets an array of all Acquia Drupal extensions. + * + * @return array + * A flat array of all Acquia Drupal extensions. + */ + public function getAcquiaExtensionNames() { + $module_names = array_keys($this->moduleList->getAllAvailableInfo()); + + return array_values(array_filter($module_names, function ($name) { + return $name === 'cohesion' || strpos($name, 'acquia') !== FALSE || + strpos($name, 'lightning_') !== FALSE || + strpos($name, 'acms') !== FALSE; + })); + } + +} diff --git a/docroot/modules/contrib/acquia_connector/src/EventSubscriber/KernelView/CodeStudioMessage.php b/docroot/modules/contrib/acquia_connector/src/EventSubscriber/KernelView/CodeStudioMessage.php new file mode 100644 index 0000000000..7241b2d49c --- /dev/null +++ b/docroot/modules/contrib/acquia_connector/src/EventSubscriber/KernelView/CodeStudioMessage.php @@ -0,0 +1,93 @@ +messenger = $messenger; + } + + /** + * {@inheritdoc} + */ + public static function getSubscribedEvents() { + $events[KernelEvents::VIEW][] = ['onViewRenderArray', 100]; + return $events; + } + + /** + * {@inheritdoc} + */ + public function onViewRenderArray(KernelEvent $event) { + // Only load script in CD Environment (née ODE). + $ah_env = getenv('AH_SITE_ENVIRONMENT'); + if (!$this->isOdeEnvironment($ah_env)) { + return; + } + $required_variables = [ + 'CODE_STUDIO_CI_PROJECT_ID', + 'CODE_STUDIO_CI_MERGE_REQUEST_IID', + 'CODE_STUDIO_CI_PROJECT_ID', + 'CODE_STUDIO_CI_PROJECT_PATH', + ]; + foreach ($required_variables as $required_variable) { + if (!getenv($required_variable)) { + // Exit early if a required variable is missing. + return; + } + } + + $this->messenger->addStatus($this->t('This Acquia Continuous Delivery Environment (CDE) was automatically created by Acquia Code Studio for merge request !@merge_request_iid for @project_path. It will be destroyed when the merge request is closed or merged.', [ + ':code_studio_url' => getenv('CODE_STUDIO_CI_SERVER_URL'), + '@merge_request_iid' => getenv('CODE_STUDIO_CI_MERGE_REQUEST_IID'), + ':merge_request_url' => getenv('CODE_STUDIO_CI_SERVER_URL') . '/' . getenv('CODE_STUDIO_CI_PROJECT_PATH') . '/-/merge_requests/' . getenv('CODE_STUDIO_CI_MERGE_REQUEST_IID'), + '@project_path' => getenv('CODE_STUDIO_CI_PROJECT_PATH'), + ':project_url' => getenv('CODE_STUDIO_CI_SERVER_URL') . '/' . getenv('CODE_STUDIO_CI_PROJECT_PATH'), + ])); + } + + /** + * Is AH ODE. + * + * @param string $ah_env + * Environment machine name. + * + * @return false|int + * TRUE if ODE, FALSE otherwise. + */ + protected function isOdeEnvironment($ah_env) { + // CDEs (formerly 'ODEs') can be 'ode1', 'ode2', ... + return (preg_match('/^ode\d*$/', $ah_env)); + } + +} diff --git a/docroot/modules/contrib/acquia_connector/src/Form/ConfigureApplicationForm.php b/docroot/modules/contrib/acquia_connector/src/Form/ConfigureApplicationForm.php new file mode 100644 index 0000000000..72624c2c81 --- /dev/null +++ b/docroot/modules/contrib/acquia_connector/src/Form/ConfigureApplicationForm.php @@ -0,0 +1,217 @@ +state = $state; + $this->siteProfile = $site_profile; + $this->clientFactory = $client_factory; + $this->subscription = $subscription; + $this->logger = $logger; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container): self { + $instance = new self( + $container->get('state'), + $container->get('acquia_connector.site_profile'), + $container->get('acquia_connector.client.factory'), + $container->get('acquia_connector.subscription'), + $container->get('acquia_connector.logger_channel') + ); + $instance->setStringTranslation($container->get('string_translation')); + return $instance; + } + + /** + * {@inheritdoc} + */ + public function getFormId(): string { + return 'acquia_connector_configure_application_form'; + } + + /** + * {@inheritdoc} + */ + public function buildForm(array $form, FormStateInterface $form_state) { + try { + $response = $this->clientFactory->getCloudApiClient()->get('/api/applications'); + $data = Json::decode((string) $response->getBody()); + $applications = []; + foreach ($data['_embedded']['items'] as $key => $item) { + if (trim($item['subscription']['name']) !== trim($item['name'])) { + // Format for ACSF Sites. + if (preg_match('/.+?(?= - ACSF)/', trim($item['name']), $sub_name)) { + // For ACSF sites, remove the duplicate name in the subscription. + $applications[$item['uuid']] = $sub_name[0] . ': ' . substr($item['name'], strlen($sub_name[0]) + 2); + } + else { + $applications[$item['uuid']] = trim($item['subscription']['name'] . ': ' . $item['name']); + } + } + else { + $applications[$item['uuid']] = trim($item['name']); + } + } + asort($applications); + } + 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 get applications from Acquia Cloud: @error', [ + '@error' => $e->getMessage(), + ]); + return new RedirectResponse(Url::fromRoute('acquia_connector.setup_oauth')->toString()); + } + // Set some defaults if we're hosted on Acquia Cloud. + $default_uuid = ''; + if ($this->subscription->getProvider() === 'acquia_cloud') { + $default_uuid = $this->subscription->getSettings()->getMetadata('AH_APPLICATION_UUID'); + } + + if (count($applications) === 0) { + $this->messenger()->addError($this->t('No subscriptions were found for your account.')); + return new RedirectResponse(Url::fromRoute('acquia_connector.setup_oauth')->toString()); + } + if (count($applications) === 1) { + // Don't allow users on cloud to inadvertently override the default sub. + if ($default_uuid && key($applications) !== $default_uuid) { + $this->messenger()->addError($this->t("Unable to set Subscription: User does not have access to this application.")); + return new RedirectResponse(Url::fromRoute('acquia_connector.setup_oauth')->toString()); + } + $form_state->setValue('application', key($applications)); + $this->submitForm($form, $form_state); + $redirect = $form_state->getRedirect(); + if ($redirect instanceof Url) { + return new RedirectResponse($redirect->setAbsolute()->toString(), Response::HTTP_SEE_OTHER); + } + } + + $form['application'] = [ + '#type' => 'select', + '#required' => TRUE, + '#options' => $applications, + '#default_value' => $default_uuid, + '#title' => $this->t('Application'), + ]; + + $form['actions'] = [ + '#type' => 'actions', + 'submit' => [ + '#type' => 'submit', + '#button_type' => 'primary', + '#value' => $this->t('Set application'), + ], + ]; + + return $form; + } + + /** + * {@inheritdoc} + */ + public function submitForm(array &$form, FormStateInterface $form_state): void { + $values = $form_state->getValues(); + $application_uuid = $values['application']; + $client = $this->clientFactory->getCloudApiClient(); + try { + $keys = $client->get("/api/applications/$application_uuid/settings/keys"); + $data_keys = Json::decode((string) $keys->getBody()); + } + catch (\Throwable $e) { + $this->messenger()->addError('We encountered an error when retrieving information for the selected application. Try logging into Acquia Cloud again.'); + $form_state->setRedirect('acquia_connector.setup_oauth'); + return; + } + + // Setup form uses the state system, update state. + $this->state->set('acquia_connector.identifier', $data_keys['acquia_connector']['identifier']); + $this->state->set('acquia_connector.key', $data_keys['acquia_connector']['key']); + $this->state->set('acquia_connector.application_uuid', $application_uuid); + $this->state->set('spi.site_name', $this->siteProfile->getSiteName($application_uuid)); + + $this->subscription->populateSettings(); + $subscription_data = $this->subscription->getSubscription(TRUE); + + if ($subscription_data) { + $form_state->setRedirect('acquia_connector.settings'); + } + + if ($subscription_data['active']) { + $this->messenger()->addStatus($this->t('

Connection successful!

You are now connected to Acquia Cloud.')); + } + } + +} diff --git a/docroot/modules/contrib/acquia_connector/src/Form/CredentialForm.php b/docroot/modules/contrib/acquia_connector/src/Form/CredentialForm.php index 212a0d3b99..72b423ee39 100644 --- a/docroot/modules/contrib/acquia_connector/src/Form/CredentialForm.php +++ b/docroot/modules/contrib/acquia_connector/src/Form/CredentialForm.php @@ -2,39 +2,54 @@ namespace Drupal\acquia_connector\Form; -use Drupal\acquia_connector\Client; -use Drupal\acquia_connector\ConnectorException; -use Drupal\acquia_connector\Helper\Storage; +use Drupal\acquia_connector\Client\ClientFactory; use Drupal\acquia_connector\Subscription; -use Drupal\Core\Config\ConfigFactoryInterface; -use Drupal\Core\Form\ConfigFormBase; +use Drupal\Core\Form\FormBase; use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\State\StateInterface; use Drupal\Core\Url; use Symfony\Component\DependencyInjection\ContainerInterface; /** - * Class CredentialForm. + * Form for Acquia Credentials. */ -class CredentialForm extends ConfigFormBase { +class CredentialForm extends FormBase { /** * The Acquia client. * - * @var \Drupal\acquia_connector\Client + * @var \Drupal\acquia_connector\Client\ClientFactory */ - protected $client; + protected $clientFactory; + + /** + * Drupal State Service. + * + * @var \Drupal\Core\State\StateInterface + */ + protected $state; + + /** + * Acquia Connector Subscription service. + * + * @var \Drupal\acquia_connector\Subscription + */ + protected $subscription; /** * Constructs a \Drupal\system\ConfigFormBase object. * - * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory - * The factory for configuration objects. - * @param \Drupal\acquia_connector\Client $client + * @param \Drupal\acquia_connector\Client\ClientFactory $client_factory * The Acquia client. + * @param \Drupal\Core\State\StateInterface $state + * Drupal State Service. + * @param \Drupal\acquia_connector\Subscription $subscription + * Acquia Connector Subscription service. */ - public function __construct(ConfigFactoryInterface $config_factory, Client $client) { - $this->configFactory = $config_factory; - $this->client = $client; + public function __construct(ClientFactory $client_factory, StateInterface $state, Subscription $subscription) { + $this->clientFactory = $client_factory; + $this->state = $state; + $this->subscription = $subscription; } /** @@ -42,18 +57,12 @@ public function __construct(ConfigFactoryInterface $config_factory, Client $clie */ public static function create(ContainerInterface $container) { return new static( - $container->get('config.factory'), - $container->get('acquia_connector.client') + $container->get('acquia_connector.client.factory'), + $container->get('state'), + $container->get('acquia_connector.subscription') ); } - /** - * {@inheritdoc} - */ - protected function getEditableConfigNames() { - return ['acquia_connector.settings', 'acquia_search.settings']; - } - /** * {@inheritdoc} */ @@ -66,22 +75,27 @@ public function getFormId() { */ public function buildForm(array $form, FormStateInterface $form_state) { - $storage = new Storage(); - $form['#prefix'] = $this->t('Enter your product keys from your application overview or log in to connect your site to Acquia Insight.', [ + $form['#prefix'] = $this->t('Enter your product keys from your application overview or log in to connect your site to Acquia Cloud.', [ ':net' => Url::fromUri('https://cloud.acquia.com')->getUri(), - ':url' => Url::fromRoute('acquia_connector.setup')->toString(), + ':url' => Url::fromRoute('acquia_connector.setup_oauth')->toString(), ]); - $form['acquia_identifier'] = [ + $form['identifier'] = [ '#type' => 'textfield', '#title' => $this->t('Identifier'), - '#default_value' => $storage->getIdentifier(), + '#default_value' => '', '#required' => TRUE, ]; - $form['acquia_key'] = [ + $form['key'] = [ '#type' => 'textfield', '#title' => $this->t('Network key'), - '#default_value' => $storage->getKey(), + '#default_value' => '', + '#required' => TRUE, + ]; + $form['application_uuid'] = [ + '#type' => 'textfield', + '#title' => $this->t('Application UUID'), + '#default_value' => '', '#required' => TRUE, ]; $form['actions'] = ['#type' => 'actions']; @@ -98,67 +112,25 @@ public function buildForm(array $form, FormStateInterface $form_state) { return $form; } - /** - * {@inheritdoc} - */ - public function validateForm(array &$form, FormStateInterface $form_state) { - try { - $response = $this->client->nspiCall( - '/agent-api/subscription', - ['identifier' => trim($form_state->getValue('acquia_identifier'))], - trim($form_state->getValue('acquia_key'))); - } - catch (ConnectorException $e) { - // Set form error to prevent switching to the next page. - if ($e->isCustomized()) { - // Allow to connect with expired subscription. - if ($e->getCustomMessage('code') == Subscription::EXPIRED) { - $form_state->setValue('subscription', 'Expired subscription.'); - return; - } - acquia_connector_report_restapi_error($e->getCustomMessage('code'), $e->getCustomMessage()); - $form_state->setErrorByName(''); - } - else { - $form_state->setErrorByName('', $this->t('Server error, please submit again.')); - } - return; - } - - $response = $response['result']; - - if (empty($response['body']['subscription_name'])) { - $form_state->setErrorByName('acquia_identifier', $this->t('No subscriptions were found.')); - } - else { - $form_state->setValue('subscription', $response['body']['subscription_name']); - } - } - /** * {@inheritdoc} */ public function submitForm(array &$form, FormStateInterface $form_state) { - $config = $this->config('acquia_connector.settings'); - - $config->set('subscription_name', $form_state->getValue('subscription')) - ->save(); - - $storage = new Storage(); - $storage->setKey($form_state->getValue('acquia_key')); - $storage->setIdentifier($form_state->getValue('acquia_identifier')); + // Save Credentials to state, only if a new value was added. + $values = ['identifier', 'key', 'application_uuid']; + foreach ($values as $value) { + $this->state->set('acquia_connector.' . $value, $form_state->getValue($value)); + } - // Check subscription and send a heartbeat to Acquia via XML-RPC. // Our status gets updated locally via the return data. - $subscription = new Subscription(); - $subscription_data = $subscription->update(); + // Don't use dependency injection here because we just created the sub. + $subscription = \Drupal::service('acquia_connector.subscription'); + $subscription->populateSettings(); // Redirect to the path without the suffix. $form_state->setRedirect('acquia_connector.settings'); - drupal_flush_all_caches(); - - if ($subscription_data['active']) { + if ($subscription->isActive()) { $this->messenger()->addStatus($this->t('

Connection successful!

You are now connected to Acquia Cloud. Please enter a name for your site to begin sending profile data.')); } } diff --git a/docroot/modules/contrib/acquia_connector/src/Form/ResetConfirmationForm.php b/docroot/modules/contrib/acquia_connector/src/Form/ResetConfirmationForm.php new file mode 100644 index 0000000000..8a75463029 --- /dev/null +++ b/docroot/modules/contrib/acquia_connector/src/Form/ResetConfirmationForm.php @@ -0,0 +1,46 @@ +t('Are you sure you want to reset your credentials to default values?'); + } + + /** + * {@inheritdoc} + */ + public function getCancelUrl() { + return new Url('acquia_connector.settings'); + } + + /** + * {@inheritdoc} + */ + public function getFormId() { + return 'connector_reset_confirmation_form'; + } + + /** + * Reset's the credentials by deleting the override from state. + * + * Note, this method is implemented in submitForm on the Settings Form. + * See @Drupal\acquia_connector\Form\SettingsForm. + */ + public function submitForm(array &$form, FormStateInterface $form_state) { + } + +} diff --git a/docroot/modules/contrib/acquia_connector/src/Form/SettingsForm.php b/docroot/modules/contrib/acquia_connector/src/Form/SettingsForm.php index 1e7272af39..4116bae5db 100644 --- a/docroot/modules/contrib/acquia_connector/src/Form/SettingsForm.php +++ b/docroot/modules/contrib/acquia_connector/src/Form/SettingsForm.php @@ -2,25 +2,35 @@ namespace Drupal\acquia_connector\Form; -use Drupal\acquia_connector\Client; -use Drupal\acquia_connector\ConnectorException; -use Drupal\acquia_connector\Helper\Storage; +use Drupal\acquia_connector\AcquiaConnectorEvents; +use Drupal\acquia_connector\Event\AcquiaProductSettingsEvent; +use Drupal\acquia_connector\SiteProfile\SiteProfile; +use Drupal\acquia_connector\Subscription; use Drupal\Core\Config\ConfigFactoryInterface; use Drupal\Core\Extension\ModuleHandlerInterface; use Drupal\Core\Form\ConfigFormBase; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\PrivateKey; +use Drupal\Core\State\StateInterface; use Drupal\Core\Url; use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\HttpFoundation\RedirectResponse; /** - * Class SettingsForm. + * Acquia Connector Settings. * * @package Drupal\acquia_connector\Form */ class SettingsForm extends ConfigFormBase { + /** + * The event dispatcher. + * + * @var \Symfony\Component\EventDispatcher\EventDispatcherInterface + */ + protected $dispatcher; + /** * The config factory interface. * @@ -45,12 +55,33 @@ class SettingsForm extends ConfigFormBase { /** * The Acquia connector client. * - * @var \Drupal\acquia_connector\Client + * @var \Drupal\acquia_connector\Subscription + */ + protected $subscription; + + /** + * The state service. + * + * @var \Drupal\Core\State\StateInterface + */ + protected $state; + + /** + * Site Profile Service. + * + * @var \Drupal\acquia_connector\SiteProfile\SiteProfile + */ + protected $siteProfile; + + /** + * Reset to Defaults data. + * + * @var array */ - protected $client; + protected $resetData; /** - * Constructs a \Drupal\aggregator\SettingsForm object. + * Acquia Connector Settings Form Constructor. * * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory * The factory for configuration objects. @@ -58,15 +89,24 @@ class SettingsForm extends ConfigFormBase { * The module handler. * @param \Drupal\Core\PrivateKey $private_key * The private key. - * @param \Drupal\acquia_connector\Client $client - * The Acquia client. + * @param \Drupal\acquia_connector\Subscription $subscription + * The Acquia subscription service. + * @param \Drupal\Core\State\StateInterface $state + * The State handler. + * @param \Drupal\acquia_connector\SiteProfile\SiteProfile $site_profile + * Connector Site Profile Service. + * @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $dispatcher + * Event Dispatcher Service. */ - public function __construct(ConfigFactoryInterface $config_factory, ModuleHandlerInterface $module_handler, PrivateKey $private_key, Client $client) { + public function __construct(ConfigFactoryInterface $config_factory, ModuleHandlerInterface $module_handler, PrivateKey $private_key, Subscription $subscription, StateInterface $state, SiteProfile $site_profile, EventDispatcherInterface $dispatcher) { parent::__construct($config_factory); $this->moduleHandler = $module_handler; $this->privateKey = $private_key; - $this->client = $client; + $this->subscription = $subscription; + $this->state = $state; + $this->siteProfile = $site_profile; + $this->dispatcher = $dispatcher; } /** @@ -77,7 +117,10 @@ public static function create(ContainerInterface $container) { $container->get('config.factory'), $container->get('module_handler'), $container->get('private_key'), - $container->get('acquia_connector.client') + $container->get('acquia_connector.subscription'), + $container->get('state'), + $container->get('acquia_connector.site_profile'), + $container->get('event_dispatcher') ); } @@ -85,7 +128,7 @@ public static function create(ContainerInterface $container) { * {@inheritdoc} */ protected function getEditableConfigNames() { - return ['acquia_connector.settings', 'acquia_search.settings']; + return ['acquia_connector.settings']; } /** @@ -99,38 +142,48 @@ public function getFormId() { * {@inheritdoc} */ public function buildForm(array $form, FormStateInterface $form_state) { - - $config = $this->config('acquia_connector.settings'); - $storage = new Storage(); - $identifier = $storage->getIdentifier(); - $key = $storage->getKey(); - $subscription = $config->get('subscription_name'); - - if (empty($identifier) && empty($key)) { - return new RedirectResponse((string) \Drupal::service('url_generator')->generateFromRoute('acquia_connector.start')); + // Redirect to Connector Setup if no subscription is active. + if (!$this->subscription->isActive()) { + return new RedirectResponse( + Url::fromRoute('acquia_connector.setup_oauth')->toString() + ); } - // Check our connection to the Acquia and validate credentials. - try { - $this->client->getSubscription($identifier, $key); - } - catch (ConnectorException $e) { - $error_message = acquia_connector_connection_error_message($e->getCustomMessage('code', FALSE)); - $ssl_available = in_array('ssl', stream_get_transports(), TRUE) && !defined('ACQUIA_CONNECTOR_TEST_ACQUIA_DEVELOPMENT_NOSSL') && $config->get('spi.ssl_verify'); - if (empty($error_message) && $ssl_available) { - $error_message = $this->t('There was an error in validating your subscription credentials. You may want to try disabling SSL peer verification by setting the variable acquia_connector.settings:spi.ssl_verify to false.'); - } - $this->messenger()->addError($error_message); + // Redirect to confirmation form when resetting network ID. + if ($this->resetData) { + return \Drupal::formBuilder()->getForm('Drupal\acquia_connector\Form\ResetConfirmationForm'); } + // Start with an empty subscription. + $subscription = $this->subscription->getSubscription(TRUE); + $form['connected'] = [ '#markup' => $this->t('

Connected to Acquia

'), ]; + if (!empty($subscription)) { $form['subscription'] = [ - '#markup' => $this->t('Subscription: @sub change', [ - '@sub' => $subscription, - ':url' => (string) \Drupal::service('url_generator')->generateFromRoute('acquia_connector.setup'), + '#markup' => $this->t('Subscription: @sub change
', [ + '@sub' => $subscription['subscription_name'], + ':url' => Url::fromRoute('acquia_connector.setup_configure')->toString(), + ]), + ]; + if (isset($subscription['application'])) { + $form['app_name'] = [ + '#markup' => $this->t('Application Name: @app_name
', [ + '@app_name' => $subscription['application']['name'], + ]), + ]; + } + $form['identifier'] = [ + '#markup' => $this->t('Identifier: @identifier @overridden
', [ + '@identifier' => $this->subscription->getSettings()->getIdentifier(), + '@overridden' => $this->isCloudOverridden() ? '(Overridden)' : '', + ]), + ]; + $form['app_uuid'] = [ + '#markup' => $this->t('Application UUID: @app_uuid', [ + '@app_uuid' => $this->subscription->getSettings()->getApplicationUuid(), ]), ]; } @@ -141,7 +194,7 @@ public function buildForm(array $form, FormStateInterface $form_state) { '#collapsible' => FALSE, ]; - $form['identification']['description']['#markup'] = $this->t('Provide a name for this site to uniquely identify it on Acquia Cloud.'); + $form['identification']['description']['#markup'] = $this->t('This is the unique string used to identify this site on Acquia Cloud.'); $form['identification']['description']['#weight'] = -2; $form['identification']['site'] = [ @@ -155,12 +208,15 @@ public function buildForm(array $form, FormStateInterface $form_state) { '#title' => $this->t('Name'), '#maxlength' => 255, '#required' => TRUE, - '#default_value' => $config->get('spi.site_name') ?: \Drupal::service('acquia_connector.spi')->getAcquiaHostedName(), + '#disabled' => TRUE, + '#default_value' => $this->siteProfile->getSiteName($this->subscription->getSettings()->getApplicationUuid()), ]; - $acquia_hosted = \Drupal::service('acquia_connector.spi')->checkAcquiaHosted(); + if (!empty($form['identification']['site']['name']['#default_value']) && $this->siteProfile->checkAcquiaHosted()) { + $form['identification']['site']['name']['#disabled'] = TRUE; + } - if ($acquia_hosted) { + if ($this->siteProfile->checkAcquiaHosted()) { $form['identification']['#description'] = $this->t('Acquia hosted sites are automatically provided with a machine name.'); } @@ -173,89 +229,46 @@ public function buildForm(array $form, FormStateInterface $form_state) { 'exists' => [$this, 'exists'], 'source' => ['identification', 'site', 'name'], ], - '#default_value' => $config->get('spi.site_machine_name'), + '#default_value' => $this->siteProfile->getMachineName($subscription['uuid']), ]; - if ($acquia_hosted) { - $form['identification']['site']['machine_name']['#default_value'] = $this->config('acquia_connector.settings')->get('spi.site_machine_name') ?: \Drupal::service('acquia_connector.spi')->getAcquiaHostedMachineName(); - $form['identification']['site']['machine_name']['#disabled'] = TRUE; - } + $form['identification']['site']['machine_name']['#disabled'] = TRUE; - $form['connection'] = [ - '#type' => 'fieldset', - '#title' => $this->t('Acquia Subscription Settings'), - '#collapsible' => FALSE, - ]; + // Get product settings + // Refresh the subscription from Acquia + // Allow other modules to add metadata to the subscription. + $event = new AcquiaProductSettingsEvent($form, $form_state, $this->subscription); - // Help documentation is local unless the Help module is disabled. - if ($this->moduleHandler->moduleExists('help')) { - $help_url = Url::fromRoute('help.page', ['name' => 'acquia_connector'])->toString(); + // @todo Remove after dropping support for Drupal 8. + if (version_compare(\Drupal::VERSION, '9.0', '>=')) { + $this->dispatcher->dispatch($event, AcquiaConnectorEvents::ACQUIA_PRODUCT_SETTINGS); } else { - $help_url = Url::fromUri('https://docs.acquia.com/acquia-cloud/insight/install/')->getUri(); + // @phpstan-ignore-next-line + $this->dispatcher->dispatch(AcquiaConnectorEvents::ACQUIA_PRODUCT_SETTINGS, $event); } - if (!empty($identifier) && !empty($key)) { - $form['connection']['spi'] = [ - '#prefix' => '
', - '#suffix' => '
', - '#weight' => 0, - ]; - - $form['connection']['description']['#markup'] = $this->t('Allow collection and examination of the following items. Learn more.', [':url' => $help_url]); - $form['connection']['description']['#weight'] = '-1'; - - $form['connection']['spi']['admin_priv'] = [ - '#type' => 'checkbox', - '#title' => $this->t('Admin privileges'), - '#default_value' => $config->get('spi.admin_priv'), - ]; - $form['connection']['spi']['send_node_user'] = [ - '#type' => 'checkbox', - '#title' => $this->t('Nodes and users'), - '#default_value' => $config->get('spi.send_node_user'), - ]; - $form['connection']['spi']['send_watchdog'] = [ - '#type' => 'checkbox', - '#title' => $this->t('Watchdog logs'), - '#default_value' => $config->get('spi.send_watchdog'), - ]; - $form['connection']['acquia_dynamic_banner'] = [ - '#type' => 'checkbox', - '#title' => $this->t('Receive updates from Acquia Subscription'), - '#default_value' => $config->get('spi.dynamic_banner'), - ]; - $form['connection']['alter_variables'] = [ - '#type' => 'checkbox', - '#title' => $this->t('Allow Insight to update list of approved variables.'), - '#default_value' => (int) $config->get('spi.set_variables_override'), - '#description' => $this->t('Insight can set variables on your site to recommended values at your approval, but only from a specific list of variables. Check this box to allow Insight to update the list of approved variables. Learn more.', [':url' => $help_url]), - ]; - - $use_cron = $config->get('spi.use_cron'); + $form = $event->getForm(); + if (isset($form['product_settings'])) { + $form['product_settings']['#type'] = 'fieldset'; + $form['product_settings']['#title'] = $this->t("Product Specific Settings"); + $form['product_settings']['#collapsible'] = FALSE; + $form['product_settings']['#tree'] = TRUE; + } - $form['connection']['use_cron'] = [ - '#type' => 'checkbox', - '#title' => $this->t('Send via Drupal cron'), - '#default_value' => $use_cron, - ]; + $form = parent::buildForm($form, $form_state); - $form['#attached']['library'][] = 'acquia_connector/acquia_connector.form'; - $key = sha1($this->privateKey->get()); - $url = Url::fromRoute('acquia_connector.send', [], ['query' => ['key' => $key], 'absolute' => TRUE])->toString(); - - $form['connection']['use_cron_url'] = [ - '#type' => 'container', - '#children' => $this->t("Enter the following URL in your server's crontab to send SPI data:
:url", [':url' => $url]), - '#states' => [ - 'visible' => [ - ':input[name="use_cron"]' => ['checked' => FALSE], - ], - ], + // Allow customers to reset the connection if it mismatches hosting. + if ($this->isCloudOverridden()) { + $form['actions']['reset'] = [ + '#type' => 'submit', + '#value' => $this->t('Reset to default'), + '#button_type' => 'danger', + '#submit' => [[$this, 'submitReset']], ]; } - return parent::buildForm($form, $form_state); + return $form; } /** @@ -268,30 +281,86 @@ public function exists() { return FALSE; } + /** + * Submit handler for the Reset Defaults button. + */ + public function submitReset(array &$form, FormStateInterface $form_state) { + $form_state->setRebuild(); + $this->resetData = $form_state->getValues(); + } + /** * {@inheritdoc} */ public function submitForm(array &$form, FormStateInterface $form_state) { - $config = \Drupal::configFactory()->getEditable('acquia_connector.settings'); + // If we're just resetting the values, do it first. + if ($form_state->getValue('confirm')) { + $this->state->deleteMultiple([ + 'acquia_connector.identifier', + 'acquia_connector.key', + 'acquia_connector.application_uuid', + ]); + + // Repopulate Settings and reset the subscription data. + $this->subscription->populateSettings(); + $this->subscription->getSubscription(TRUE); + $this->messenger()->addStatus($this->t('Successfully reset Acquia Connector Identifier and Key.')); + return; + } + $event = new AcquiaProductSettingsEvent($form, $form_state, $this->subscription); + // @todo Remove after dropping support for Drupal 8. + if (version_compare(\Drupal::VERSION, '9.0', '>=')) { + $this->dispatcher->dispatch($event, AcquiaConnectorEvents::ALTER_PRODUCT_SETTINGS_SUBMIT); + } + else { + // @phpstan-ignore-next-line + $this->dispatcher->dispatch(AcquiaConnectorEvents::ALTER_PRODUCT_SETTINGS_SUBMIT, $event); + } + $values = $form_state->getValues(); - $config->set('spi.site_name', $values['name']) - ->set('spi.dynamic_banner', $values['acquia_dynamic_banner']) - ->set('spi.admin_priv', $values['admin_priv']) - ->set('spi.send_node_user', $values['send_node_user']) - ->set('spi.send_watchdog', $values['send_watchdog']) - ->set('spi.use_cron', $values['use_cron']) - ->set('spi.set_variables_override', $values['alter_variables']) - ->save(); - - // If the machine name changed, send information so we know if it is a dupe. - if ($values['machine_name'] != $this->config('acquia_connector.settings')->get('spi.site_machine_name')) { - $config->set('spi.site_machine_name', $values['machine_name'])->save(); - - $response = \Drupal::service('acquia_connector.spi')->sendFullSpi(ACQUIA_CONNECTOR_ACQUIA_SPI_METHOD_CREDS); - \Drupal::service('acquia_connector.spi')->spiProcessMessages($response); + $this->state->set('spi.site_name', $values['name']); + + $config = $this->config('acquia_connector.settings'); + // Save individual product settings within connector config. + if (!empty($values['product_settings'])) { + // Loop through each product. + foreach ($values['product_settings'] as $product_name => $settings) { + // Only set the setting if it changed. + foreach ($settings['settings'] as $key => $value) { + // Don't change the settings if the existing value matches. + if ($form['product_settings'][$product_name]['settings'][$key]['#default_value'] === $value) { + continue; + } + // Delete the setting if the value is null. + if (empty($value)) { + $config->clear('third_party_settings.' . $product_name . '.' . $key); + continue; + } + // Save the setting if it's not empty. + $config->set('third_party_settings.' . $product_name . '.' . $key, $value); + } + } } + $config->save(); parent::submitForm($form, $form_state); } + /** + * Checks if Acquia Cloud's values are overridden. + * + * @return bool + * Determines whether the subscription matches AH values. + */ + protected function isCloudOverridden() { + if ($this->subscription->getProvider() !== 'acquia_cloud') { + return FALSE; + } + $metadata = $this->subscription->getSettings()->getMetadata(); + $settings = $this->subscription->getSettings(); + return $metadata['ah_network_identifier'] !== $settings->getIdentifier() || + $metadata['ah_network_key'] !== $settings->getSecretKey() || + $metadata['AH_APPLICATION_UUID'] !== $settings->getApplicationUuid(); + } + } diff --git a/docroot/modules/contrib/acquia_connector/src/Form/SetupForm.php b/docroot/modules/contrib/acquia_connector/src/Form/SetupForm.php deleted file mode 100644 index 8e386fdcd6..0000000000 --- a/docroot/modules/contrib/acquia_connector/src/Form/SetupForm.php +++ /dev/null @@ -1,250 +0,0 @@ -configFactory = $config_factory; - $this->client = $client; - } - - /** - * {@inheritdoc} - */ - public static function create(ContainerInterface $container) { - return new static( - $container->get('config.factory'), - $container->get('acquia_connector.client') - ); - } - - /** - * {@inheritdoc} - */ - protected function getEditableConfigNames() { - return ['acquia_connector.settings', 'acquia_search.settings']; - } - - /** - * {@inheritdoc} - */ - public function getFormId() { - return 'acquia_connector_automatic_setup_form'; - } - - /** - * {@inheritdoc} - */ - public function buildForm(array $form, FormStateInterface $form_state) { - $storage = $form_state->getStorage(); - if (empty($storage['choose'])) { - return $this->buildSetupForm($form_state); - } - else { - return $this->buildChooseForm($form_state); - } - } - - /** - * Build setup form. - * - * @param \Drupal\Core\Form\FormStateInterface $form_state - * Form state. - * - * @return array - * Form. - */ - protected function buildSetupForm(FormStateInterface &$form_state) { - $form = [ - '#prefix' => $this->t('Log in or configure manually to connect your site to the Acquia Subscription.', [':url' => Url::fromRoute('acquia_connector.credentials')->toString()]), - 'email' => [ - '#type' => 'textfield', - '#title' => $this->t('Enter the email address you use to login to the Acquia Subscription:'), - '#required' => TRUE, - ], - 'pass' => [ - '#type' => 'password', - '#title' => $this->t('Enter your Acquia Subscription password:'), - '#description' => $this->t('Your password will not be stored locally and will be sent securely to Acquia.com. Forgot password?', [ - ':url' => Url::fromUri('https://accounts.acquia.com/user/password')->getUri(), - ]), - '#size' => 32, - '#required' => TRUE, - ], - 'actions' => [ - '#type' => 'actions', - 'continue' => [ - '#type' => 'submit', - '#value' => $this->t('Next'), - ], - 'signup' => [ - '#markup' => $this->t('Need a subscription? Get one.', [ - ':url' => Url::fromUri('https://www.acquia.com/acquia-cloud-free')->getUri(), - ]), - ], - ], - ]; - return $form; - } - - /** - * Build choose form. - * - * @param \Drupal\Core\Form\FormStateInterface $form_state - * Form state. - * - * @return array - * Form. - */ - protected function buildChooseForm(FormStateInterface &$form_state) { - $options = []; - $storage = $form_state->getStorage(); - foreach ($storage['response']['subscription'] as $credentials) { - $options[] = $credentials['name']; - } - asort($options); - - $form = [ - '#prefix' => $this->t('You have multiple subscriptions available.'), - 'subscription' => [ - '#type' => 'select', - '#title' => $this->t('Available subscriptions'), - '#options' => $options, - '#description' => $this->t('Choose from your available subscriptions.'), - '#required' => TRUE, - ], - 'continue' => [ - '#type' => 'submit', - '#value' => $this->t('Submit'), - ], - ]; - - return $form; - } - - /** - * {@inheritdoc} - */ - public function validateForm(array &$form, FormStateInterface $form_state) { - $storage = $form_state->getStorage(); - if (!isset($storage['choose'])) { - try { - $response = $this->client->getSubscriptionCredentials($form_state->getValue('email'), $form_state->getValue('pass')); - } - catch (ConnectorException $e) { - // Set form error to prevent switching to the next page. - if ($e->isCustomized()) { - $form_state->setErrorByName('', $e->getCustomMessage()); - } - else { - \Drupal::logger('acquia connector')->error($e->getMessage()); - $form_state->setErrorByName('', $this->t("Can't connect to the Acquia Subscription.")); - } - } - if (!empty($response)) { - $storage['response'] = $response; - } - } - - $form_state->setStorage($storage); - } - - /** - * {@inheritdoc} - */ - public function submitForm(array &$form, FormStateInterface $form_state) { - $form_data = $form_state->getStorage(); - if (isset($form_data['choose']) && isset($form_data['response']['subscription'][$form_state->getValue('subscription')])) { - $config = $this->config('acquia_connector.settings'); - $sub = $form_data['response']['subscription'][$form_state->getValue('subscription')]; - $config->set('subscription_name', $sub['name'])->save(); - - $storage = new Storage(); - $storage->setKey($sub['key']); - $storage->setIdentifier($sub['identifier']); - } - else { - $this->automaticStartSubmit($form_state); - } - - // Don't set message or redirect if multistep. - if (!$form_state->getErrors() && empty($form_data['rebuild'])) { - // Check subscription and send a heartbeat to Acquia via XML-RPC. - // Our status gets updated locally via the return data. - $subscription = new Subscription(); - $subscription_data = $subscription->update(); - - // Redirect to the path without the suffix. - if ($subscription_data) { - $form_state->setRedirect('acquia_connector.settings'); - } - - if ($subscription_data['active']) { - $this->messenger()->addStatus($this->t('

Connection successful!

You are now connected to Acquia Cloud. Please enter a name for your site to begin sending profile data.')); - drupal_flush_all_caches(); - } - } - } - - /** - * Submit automatically if one subscription found. - * - * @param \Drupal\Core\Form\FormStateInterface $form_state - * Form state. - */ - protected function automaticStartSubmit(FormStateInterface &$form_state) { - $config = $this->config('acquia_connector.settings'); - $storage = $form_state->getStorage(); - if (empty($storage['response']['subscription'])) { - $this->messenger()->addError($this->t('No subscriptions were found for your account.')); - } - elseif (count($storage['response']['subscription']) > 1) { - // Multistep form for choosing from available subscriptions. - $storage['choose'] = TRUE; - // Force rebuild with next step. - $form_state->setRebuild(TRUE); - $form_state->setStorage($storage); - } - else { - // One subscription so set id/key pair. - $sub = $storage['response']['subscription'][0]; - $config->set('subscription_name', $sub['name'])->save(); - - $storage = new Storage(); - $storage->setKey($sub['key']); - $storage->setIdentifier($sub['identifier']); - } - } - -} diff --git a/docroot/modules/contrib/acquia_connector/src/Form/SpiChangeForm.php b/docroot/modules/contrib/acquia_connector/src/Form/SpiChangeForm.php deleted file mode 100644 index c47700ff4d..0000000000 --- a/docroot/modules/contrib/acquia_connector/src/Form/SpiChangeForm.php +++ /dev/null @@ -1,171 +0,0 @@ -config('acquia_connector.settings'); - $blocked = $config->get('spi.blocked'); - $acquia_hosted = \Drupal::service('acquia_connector.spi')->checkAcquiaHosted(); - $environment_change = \Drupal::service('acquia_connector.spi')->checkEnvironmentChange(); - - if (!$environment_change && !$blocked) { - $form['#markup'] = $this->t("

No changes detected

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' => '
', - '#suffix' => '
', - '#weight' => -2, - ]; - - $form['identification']['site']['name'] = [ - '#type' => 'textfield', - '#title' => $this->t('Name'), - '#maxlength' => 255, - '#required' => TRUE, - '#default_value' => $config->get('spi.site_name'), - ]; - - $form['identification']['site']['machine_name'] = [ - '#type' => 'machine_name', - '#title' => $this->t('Machine name'), - '#maxlength' => 255, - '#required' => TRUE, - '#machine_name' => [ - 'exists' => [$this, 'exists'], - 'source' => ['identification', 'site', 'name'], - ], - '#default_value' => $config->get('spi.site_machine_name'), - ]; - - if ($acquia_hosted) { - $form['identification']['site']['machine_name']['#disabled'] = TRUE; - $form['identification']['site']['machine_name']['#default_value'] = \Drupal::service('acquia_connector.spi')->getAcquiaHostedMachineName(); - } - elseif ($off_acquia_hosting) { - unset($form['env_change_action']['#options']['block']); - unset($form['env_change_action']['#options']['update']); - unset($form['env_change_action']['#states']); - unset($form['identification']['site']['name']['#default_value']); - unset($form['identification']['site']['machine_name']['#default_value']); - $form['env_change_action']['#default_value'] = 'create'; - $form['env_change_action']['#access'] = FALSE; - } - - } - - return parent::buildForm($form, $form_state); - } - - /** - * Determines if the machine name already exists. - * - * @return bool - * FALSE. - */ - public function exists() { - return FALSE; - } - - /** - * {@inheritdoc} - */ - public function submitForm(array &$form, FormStateInterface $form_state) { - - $values = $form_state->getValues(); - $config = \Drupal::configFactory()->getEditable('acquia_connector.settings'); - - if (isset($values['env_change_action']['unblock']) && $values['env_change_action']['unblock'] == 'unblock') { - $config->set('spi.environment_changed_action', $values['env_change_action']['unblock'])->save(); - } - else { - $config->set('spi.environment_changed_action', $values['env_change_action'])->save(); - } - - if ($values['env_change_action'] == 'create') { - $config->set('spi.site_name', $values['name']) - ->set('spi.site_machine_name', $values['machine_name']) - ->save(); - } - parent::submitForm($form, $form_state); - - // Send information as soon as the key/identifier pair is submitted. - $response = \Drupal::service('acquia_connector.spi')->sendFullSpi(ACQUIA_CONNECTOR_ACQUIA_SPI_METHOD_CREDS); - \Drupal::service('acquia_connector.spi')->spiProcessMessages($response); - $form_state->setRedirect('system.status'); - } - -} diff --git a/docroot/modules/contrib/acquia_connector/src/Helper/Storage.php b/docroot/modules/contrib/acquia_connector/src/Helper/Storage.php deleted file mode 100644 index c21978c5cc..0000000000 --- a/docroot/modules/contrib/acquia_connector/src/Helper/Storage.php +++ /dev/null @@ -1,65 +0,0 @@ -get('acquia_connector.identifier'); - } - - /** - * Returns Acquia Subscription key. - * - * @return mixed - * Acquia Subscription key. - */ - public function getKey() { - return \Drupal::state()->get('acquia_connector.key'); - } - - /** - * Updates Acquia Subscription identifier. - * - * @param string $value - * Acquia Subscription identifier. - */ - public function setIdentifier($value) { - \Drupal::state()->set('acquia_connector.identifier', $value); - } - - /** - * Updates Acquia Subscription key. - * - * @param string $value - * Acquia Subscription key. - */ - public function setKey($value) { - \Drupal::state()->set('acquia_connector.key', $value); - } - - /** - * Deletes all stored data. - */ - public function deleteAllData() { - \Drupal::state()->deleteMultiple([ - 'acquia_connector.key', - 'acquia_connector.identifier', - ]); - } - -} diff --git a/docroot/modules/contrib/acquia_connector/src/Plugin/migrate/destination/State.php b/docroot/modules/contrib/acquia_connector/src/Plugin/migrate/destination/State.php new file mode 100644 index 0000000000..b8b71dc26a --- /dev/null +++ b/docroot/modules/contrib/acquia_connector/src/Plugin/migrate/destination/State.php @@ -0,0 +1,163 @@ +get('my_key.my_state1') === 1); + * assert(\Drupal::state()->get('my_key.my_state2') === [1, 2]); + * @endcode + * + * Without setting "state_prefix" to "my_key" or simply omitting this + * option, keys in the state will not have the "my_key." prefix. + * + * @MigrateDestination( + * id = "state", + * ) + */ +class State extends DestinationBase implements ContainerFactoryPluginInterface, ConfigurableInterface { + + /** + * The state service. + * + * @var \Drupal\Core\State\StateInterface + */ + protected $state; + + /** + * {@inheritdoc} + */ + public function __construct(array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration, StateInterface $state) { + parent::__construct($configuration, $plugin_id, $plugin_definition, $migration); + $this->setConfiguration($configuration); + $this->state = $state; + $this->supportsRollback = TRUE; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration = NULL) { + return new static($configuration, + $plugin_id, + $plugin_definition, + $migration, + $container->get('state')); + } + + /** + * {@inheritdoc} + */ + public function getIds() { + return [ + 'state_names' => [ + 'type' => 'string', + ], + ]; + } + + /** + * {@inheritdoc} + */ + public function fields(MigrationInterface $migration = NULL) { + // State API doesn't use fields. + return []; + } + + /** + * {@inheritdoc} + */ + public function import(Row $row, array $old_destination_id_values = []) { + $ids = []; + foreach ($row->getDestination() as $key => $value) { + $key = $this->configuration['state_prefix'] . $key; + $this->state->set($key, $value); + $ids[] = $key; + } + + // Contrary to configuration entities, states can not be nested, + // so every state must be stored separately from others. + // To be able to migrate several states in one migrate source row, + // combine their names and send as one ID. + return [implode(',', $ids)]; + } + + /** + * {@inheritdoc} + */ + public function rollback(array $destination_identifier) { + // Destination identifier is a comma-concatenated string of state names. + // This identifier is the comma separated return value from ::import(). + foreach (explode(',', $destination_identifier['state_names']) as $key) { + $this->state->delete($key); + } + } + + /** + * {@inheritdoc} + */ + public function defaultConfiguration() { + return [ + 'state_prefix' => '', + ]; + } + + /** + * {@inheritdoc} + */ + public function getConfiguration() { + return $this->configuration; + } + + /** + * {@inheritdoc} + */ + public function setConfiguration(array $configuration) { + $this->configuration = NestedArray::mergeDeepArray([ + $this->defaultConfiguration(), + $configuration, + ], TRUE); + } + +} diff --git a/docroot/modules/contrib/acquia_connector/src/Polyfill/RequirementsAlter.php b/docroot/modules/contrib/acquia_connector/src/Polyfill/RequirementsAlter.php new file mode 100644 index 0000000000..ea8c686776 --- /dev/null +++ b/docroot/modules/contrib/acquia_connector/src/Polyfill/RequirementsAlter.php @@ -0,0 +1,27 @@ +moduleHandler->alter('requirements', $requirements); + } + return $requirements; + } + +} diff --git a/docroot/modules/contrib/acquia_connector/src/Settings.php b/docroot/modules/contrib/acquia_connector/src/Settings.php new file mode 100644 index 0000000000..0679cce5ed --- /dev/null +++ b/docroot/modules/contrib/acquia_connector/src/Settings.php @@ -0,0 +1,186 @@ +config = $config; + $this->identifier = $network_id ?? ''; + $this->secretKey = $secret_key ?? ''; + $this->applicationUuid = $application_uuid ?? ''; + $this->metadata = $metadata ?? []; + } + + /** + * Returns Acquia Subscription identifier. + * + * @return mixed + * Acquia Subscription identifier. + */ + public function getIdentifier() { + return $this->identifier ?? NULL; + } + + /** + * Returns Acquia Subscription key. + * + * @return mixed + * Acquia Subscription key. + */ + public function getSecretKey() { + return $this->secretKey ?? NULL; + } + + /** + * Returns Acquia Subscription Application UUID. + * + * @return mixed + * Acquia Application UUID identifier. + */ + public function getApplicationUuid() { + return $this->applicationUuid ?? NULL; + } + + /** + * Returns static connector config settings. + * + * @return \Drupal\Core\Config\Config + * Acquia Connector Config Object. + */ + public function getConfig() { + return $this->config; + } + + /** + * Returns the metadata array, or a specific piece of metadata if it exists. + * + * @param string|null $key + * Metadata key. + * + * @return mixed + * The Metadata. + */ + public function getMetadata(string $key = NULL) { + if (isset($key) && isset($this->metadata[$key])) { + return $this->metadata[$key]; + } + elseif (isset($key)) { + return []; + } + else { + return $this->metadata; + } + } + + /** + * Deletes all stored data. + */ + public function deleteAllData() { + \Drupal::state()->deleteMultiple([ + 'acquia_connector.key', + 'acquia_connector.identifier', + 'acquia_connector.application_uuid', + 'spi.site_name', + 'spi.site_machine_name', + 'acquia_subscription_data', + ]); + } + + /** + * Gets readonly status for the settings object. + * + * @return bool + * Readonly Status. + */ + public function isReadonly() { + return $this->readonly; + } + + /** + * Sets readonly status for the settings object. + */ + public function setReadOnly($readonly) { + $this->readonly = $readonly; + } + +} diff --git a/docroot/modules/contrib/acquia_connector/src/SiteProfile/SiteProfile.php b/docroot/modules/contrib/acquia_connector/src/SiteProfile/SiteProfile.php new file mode 100644 index 0000000000..a891552659 --- /dev/null +++ b/docroot/modules/contrib/acquia_connector/src/SiteProfile/SiteProfile.php @@ -0,0 +1,89 @@ +requestStack = $request_stack; + } + + /** + * 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 $this->requestStack->getCurrentRequest()->server->has('AH_SITE_ENVIRONMENT') + && $this->requestStack->getCurrentRequest()->server->has('AH_SITE_NAME'); + } + + /** + * Returns a unique string built on the current domain. + * + * @return string + * The Site ID when not hosted on Acquia. + */ + private function getNonAcquiaSiteId() { + $base_url = $this->requestStack->getCurrentRequest()->getHost(); + return $base_url . '_' . substr(md5(uniqid(mt_rand(), TRUE)), 0, 8); + } + + /** + * Generate the site name for connector. + * + * @param string $application_uuid + * The Application UUID. + */ + public function getSiteName($application_uuid) { + $prefix = substr($application_uuid, '-12'); + + // Acquia Hosted. + if ($this->checkAcquiaHosted()) { + return $prefix . ': ' . $this->requestStack->getCurrentRequest()->server->get('AH_SITE_ENVIRONMENT'); + } + // Locally Hosted. + return $prefix . ': ' . $this->getNonAcquiaSiteId(); + + } + + /** + * Generate the machine name for connector. + * + * @param string $application_uuid + * The Application UUID. + * + * @return string + * The suggested Acquia Hosted machine name. + */ + public function getMachineName($application_uuid): string { + $prefix = substr($application_uuid, '-12'); + + if ($this->checkAcquiaHosted()) { + return $prefix . '__' . $this->requestStack->getCurrentRequest()->server->get('AH_SITE_ENVIRONMENT') . '__' . uniqid(); + } + return $prefix . '__' . str_replace(['.', '-'], '_', $this->getNonAcquiaSiteId()); + } + +} diff --git a/docroot/modules/contrib/acquia_connector/src/Subscription.php b/docroot/modules/contrib/acquia_connector/src/Subscription.php index 9305c0cab3..5d79364a89 100644 --- a/docroot/modules/contrib/acquia_connector/src/Subscription.php +++ b/docroot/modules/contrib/acquia_connector/src/Subscription.php @@ -2,10 +2,26 @@ namespace Drupal\acquia_connector; -use Drupal\acquia_connector\Helper\Storage; +use Drupal\acquia_connector\Client\ClientFactory; +use Drupal\acquia_connector\Event\AcquiaSubscriptionDataEvent; +use Drupal\acquia_connector\Event\AcquiaSubscriptionSettingsEvent; +use Drupal\Component\EventDispatcher\ContainerAwareEventDispatcher; +use Drupal\Component\Serialization\Json; +use Drupal\Core\Cache\Cache; +use Drupal\Core\Config\ConfigFactoryInterface; +use Drupal\Core\State\StateInterface; +use GuzzleHttp\Exception\RequestException; /** - * Class Subscription. + * Acquia Subscription service. + * + * The Acquia Subscription service is the public way other items can access + * Acquia's services via connector. There is a settings object that is invoked + * via an Event Subscriber, to fetch settings from envvars, settings.php or the + * state system. + * + * Acquia Subscription data is always stored in state, and is not part of the + * settings object. * * @package Drupal\acquia_connector. */ @@ -32,68 +48,225 @@ class Subscription { const MESSAGE_LIFETIME = 900; /** - * Get subscription status from Acquia, and store the result. + * Event Dispatcher Service. * - * This check also sends a heartbeat to Acquia unless - * $params['no_heartbeat'] == 1. + * @var \Drupal\Component\EventDispatcher\ContainerAwareEventDispatcher + */ + protected $dispatcher; + + /** + * Settings Provider. * - * @param array $params - * Optional parameters for \Drupal\acquia_connector\Client::getSubscription. + * @var string + */ + protected $settingsProvider; + + /** + * Settings object. * - * @return mixed - * FALSE, integer (error number), or subscription data. + * @var \Drupal\acquia_connector\Settings + */ + protected $settings; + + /** + * Raw Acquia subscription data. + * + * @var array + */ + protected $subscriptionData; + + /** + * Connector Client. + * + * @var \Drupal\acquia_connector\Client\ClientFactory + */ + protected $clientFactory; + + /** + * Drupal config. + * + * @var \Drupal\Core\Config\ConfigFactoryInterface + */ + protected $configFactory; + + /** + * Drupal State Service. + * + * @var \Drupal\Core\State\StateInterface */ - public function update(array $params = []) { - $config = \Drupal::configFactory()->getEditable('acquia_connector.settings'); - $current_subscription = \Drupal::state()->get('acquia_subscription_data'); - $subscription = FALSE; + protected $state; - if (!self::hasCredentials()) { - // If there is not an identifier or key, delete any old subscription data. - \Drupal::state()->delete('acquia_subscription_data'); - \Drupal::state()->set('acquia_subscription_data', ['active' => FALSE]); + /** + * Acquia Subscription Constructor. + * + * @param \Drupal\Component\EventDispatcher\ContainerAwareEventDispatcher $dispatcher + * The event dispatcher. + * @param \Drupal\acquia_connector\Client\ClientFactory $client_factory + * The acquia connector client factory. + * @param \Drupal\Core\State\StateInterface $state + * Drupal State System. + * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory + * Config Factory. + */ + public function __construct(ContainerAwareEventDispatcher $dispatcher, ClientFactory $client_factory, StateInterface $state, ConfigFactoryInterface $config_factory) { + $this->dispatcher = $dispatcher; + $this->state = $state; + $this->clientFactory = $client_factory; + $this->configFactory = $config_factory; + $this->populateSettings(); + } + + /** + * Call the event to populate Acquia Connector settings. + */ + public function populateSettings() { + $event = new AcquiaSubscriptionSettingsEvent($this->configFactory); + + // @todo Remove after dropping support for Drupal 8. + if (version_compare(\Drupal::VERSION, '9.0', '>=')) { + $this->dispatcher->dispatch($event, AcquiaConnectorEvents::GET_SETTINGS); } else { - // Get our subscription data. - try { - $storage = new Storage(); - $key = $storage->getKey(); - $identifier = $storage->getIdentifier(); - $subscription = \Drupal::service('acquia_connector.client')->getSubscription($identifier, $key, $params); - } - catch (ConnectorException $e) { - switch ($e->getCustomMessage('code')) { - case self::NOT_FOUND: - case self::EXPIRED: - // Fall through since these values are stored and used by - // acquia_search_acquia_subscription_status() - $subscription = $e->getCustomMessage('code'); - break; - - default: - // Likely server error (503) or connection timeout (-110) so leave - // current subscription in place. _acquia_agent_request() logged an - // error message. - return $current_subscription; - } - } - if ($subscription) { - \Drupal::moduleHandler()->invokeAll('acquia_subscription_status', [$subscription]); - if ($subscription != $current_subscription) { - \Drupal::state()->set('acquia_subscription_data', $subscription); - } - } + // @phpstan-ignore-next-line + $this->dispatcher->dispatch(AcquiaConnectorEvents::GET_SETTINGS, $event); } - return $subscription; + $this->settings = $event->getSettings(); + $this->settingsProvider = $event->getProvider(); + } + + /** + * Retreives the stored subscription. + * + * @return \Drupal\acquia_connector\Settings|false + * The Connector Settings Object. + */ + public function getSettings() { + return $this->settings ?? FALSE; + } + + /** + * Gets the subscription provider from the subscription event for settings. + * + * @return string + * The name of settings' provider. + */ + public function getProvider() { + return $this->settingsProvider; + } + + /** + * Retrieve the Acquia Subscription. + * + * @return array + * The Raw Subscription Data. + */ + public function getSubscription($refresh = NULL, $body = []) { + // If Settings do not exist, we have no subscription to fetch. + if (!$this->hasCredentials()) { + // Ensure subscription data is scrubbed. + $this->state->delete('acquia_connector.subscription_data'); + Cache::invalidateTags(['acquia_connector_subscription']); + return ['active' => FALSE]; + } + // Used the cached data if refresh is NULL or FALSE. + if (isset($this->subscriptionData) && $refresh !== TRUE) { + return $this->subscriptionData; + } + $subscriptionData = $this->state->get('acquia_connector.subscription_data', []); + // Handle edge cases where acquia_connector.subscription_data is set wrong. + if (!is_array($subscriptionData)) { + $subscriptionData = []; + } + if ($subscriptionData !== [] && $refresh !== TRUE) { + // Ensure the legacy location of UUID is up-to-date. + $subscriptionData['uuid'] = $this->settings->getApplicationUuid(); + return $subscriptionData; + } + // If there is no local subscription data, retrieve it. + $subscriptionData += $this->getDefaultSubscriptionData(); + try { + $client = $this->clientFactory->getCloudApiClient(); + $application_response = $client->get('/api/applications/' . $this->settings->getApplicationUuid()); + $application_data = Json::decode((string) $application_response->getBody()); + $subscription_uuid = $application_data['subscription']['uuid']; + $subscription_response = $client->get('/api/subscriptions/' . $subscription_uuid); + $subscription_info = Json::decode((string) $subscription_response->getBody()); + + $subscriptionData['active'] = $subscription_info['flags']['active']; + $subscriptionData['application'] = $application_data; + $subscriptionData['subscription_name'] = $subscription_info['name']; + // Expiration Date may have been a string, ensure its set as an array. + $subscriptionData['expiration_date'] = [ + 'value' => $subscription_info['expire_at'], + ]; + } + catch (RequestException | ConnectorException $e) { + } + + // If subscription expiration date passed, set gratis value to TRUE. + if (isset($subscriptionData['expiration_date']['value'])) { + $subscriptionData['gratis'] = strtotime($subscriptionData['expiration_date']['value']) < strtotime(date("m/d/Y")); + } + // Allow other modules to add metadata to the subscription. + $event = new AcquiaSubscriptionDataEvent($this->configFactory, $subscriptionData); + // @todo Remove after dropping support for Drupal 8. + if (version_compare(\Drupal::VERSION, '9.0', '>=')) { + $this->dispatcher->dispatch($event, AcquiaConnectorEvents::GET_SUBSCRIPTION); + } + else { + // @phpstan-ignore-next-line + $this->dispatcher->dispatch(AcquiaConnectorEvents::GET_SUBSCRIPTION, $event); + } + // Get data from subscription event. + $this->subscriptionData = $event->getData(); + + // Get product data from subscription event. + $this->subscriptionData['product'] = $event->getProductData(); + + // Save subscription data to state. + $this->state->set('acquia_connector.subscription_data', $this->subscriptionData); + Cache::invalidateTags(['acquia_connector_subscription']); + + return $this->subscriptionData; + } + + /** + * Build a subscription data object to mimic legacy NSPI responses. + * + * @return array + * The subscription data. + */ + private function getDefaultSubscriptionData() { + if (!$this->hasCredentials()) { + return ['active' => FALSE]; + } + + return [ + 'active' => TRUE, + 'gratis' => FALSE, + 'href' => "", + 'uuid' => $this->settings->getApplicationUuid(), + 'subscription_name' => "", + "expiration_date" => "", + "search_service_enabled" => 1, + ]; + } + + /** + * Delete any subscription data held in the database. + */ + public function delete() { + $this->state->set('acquia_connector.subscription_data', ['active' => FALSE]); + $this->state->delete('spi.site_name'); + $this->state->delete('spi.site_machine_name'); } /** * Helper function to check if an identifier and key exist. */ public function hasCredentials() { - $storage = new Storage(); - return $storage->getIdentifier() && $storage->getKey(); + return $this->settings->getIdentifier() && $this->settings->getSecretKey() && $this->settings->getApplicationUuid(); } /** @@ -102,18 +275,21 @@ public function hasCredentials() { public function isActive() { $active = FALSE; // Subscription cannot be active if we have no credentials. - if (self::hasCredentials()) { - $config = \Drupal::config('acquia_connector.settings'); - $subscription = \Drupal::state()->get('acquia_subscription_data'); + if ($this->hasCredentials()) { + $data = $this->state->get('acquia_connector.subscription_data'); + if ($data !== NULL) { + if (is_array($data)) { + return !empty($data['active']); + } + } + // Only retrieve cached subscription at this time. + $subscription = $this->getSubscription(FALSE); - $subscription_timestamp = \Drupal::state()->get('acquia_subscription_data.timestamp'); - // Make sure we have data at least once per day. - if (isset($subscription_timestamp) && (time() - $subscription_timestamp > 60 * 60 * 24)) { + // If we don't have a timestamp, or timestamp is less than a day, fetch. + if (!isset($subscription['timestamp']) || (isset($subscription['timestamp']) && (time() - $subscription['timestamp'] > 60 * 60 * 24))) { try { - $storage = new Storage(); - $key = $storage->getKey(); - $identifier = $storage->getIdentifier(); - $subscription = \Drupal::service('acquia_connector.client')->getSubscription($identifier, $key, ['no_heartbeat' => 1]); + $subscription = $this->getSubscription(TRUE, ['no_heartbeat' => 1]); + $this->state->set('acquia_connector.subscription_data', $subscription); } catch (ConnectorException $e) { } @@ -123,4 +299,45 @@ public function isActive() { return $active; } + /** + * Return an error message by the error code. + * + * Returns an error message for the most recent (failed) attempt to connect + * to the Acquia during the current page request. If there were no failed + * attempts, returns FALSE. + * + * This function assumes that the most recent error came from the Acquia; + * otherwise, it will not work correctly. + * + * @param int $errno + * Error code defined by the module. + * + * @return mixed + * The error message string or FALSE. + */ + public function connectionErrorMessage($errno) { + if ($errno) { + switch ($errno) { + case self::NOT_FOUND: + return t('The identifier you have provided does not exist at Acquia or is expired. Please make sure you have used the correct value and try again.'); + + case self::EXPIRED: + return t('Your Acquia Subscription subscription has expired. Please renew your subscription so that you can resume using Acquia services.'); + + case self::MESSAGE_FUTURE: + return t('Your server is unable to communicate with Acquia due to a problem with your clock settings. For security reasons, we reject messages that are more than @time ahead of the actual time recorded by our servers. Please fix the clock on your server and try again.', ['@time' => \Drupal::service('date.formatter')->formatInterval(Subscription::MESSAGE_LIFETIME)]); + + case self::MESSAGE_EXPIRED: + return t('Your server is unable to communicate with Acquia due to a problem with your clock settings. For security reasons, we reject messages that are more than @time older than the actual time recorded by our servers. Please fix the clock on your server and try again.', ['@time' => \Drupal::service('date.formatter')->formatInterval(Subscription::MESSAGE_LIFETIME)]); + + case self::VALIDATION_ERROR: + return t('The identifier and key you have provided for the Acquia Subscription do not match. Please make sure you have used the correct values and try again.'); + + default: + return t('There is an error communicating with the Acquia Subscription at this time. Please check your identifier and key and try again.'); + } + } + return FALSE; + } + } diff --git a/docroot/modules/contrib/acquia_connector/templates/acquia_connector_banner.html.twig b/docroot/modules/contrib/acquia_connector/templates/acquia_connector_banner.html.twig new file mode 100644 index 0000000000..0294d066fa --- /dev/null +++ b/docroot/modules/contrib/acquia_connector/templates/acquia_connector_banner.html.twig @@ -0,0 +1,23 @@ +
+
+
+

Acquia Subscription

+

A suite of products and services to create & maintain killer web experiences built on Drupal

+
+
+

Answers you need

+ +

Tap the collective knowledge of Acquia’s technical support team & partners.

+
+

Tools to extend your site

+ +

Enhance and extend your site with an array of services from Acquia & our partners.

+

Support when you want it

+ +

Experienced Drupalists are available to support you whenever you need it.

+
+
+
+
+
+{{ form }} \ No newline at end of file diff --git a/docroot/modules/contrib/acquia_connector/tests/modules/acquia_connector_subdata_test/acquia_connector_subdata_test.info.yml b/docroot/modules/contrib/acquia_connector/tests/modules/acquia_connector_subdata_test/acquia_connector_subdata_test.info.yml new file mode 100644 index 0000000000..f5cf354437 --- /dev/null +++ b/docroot/modules/contrib/acquia_connector/tests/modules/acquia_connector_subdata_test/acquia_connector_subdata_test.info.yml @@ -0,0 +1,10 @@ +name: Acquia Connector subscription data test +type: module +package: Testing +description: Provides test integration for AcquiaConnectorEvents::GET_SUBSCRIPTION. +hidden: true + +# Information added by Drupal.org packaging script on 2023-04-05 +version: '4.0.4' +project: 'acquia_connector' +datestamp: 1680704021 diff --git a/docroot/modules/contrib/acquia_connector/tests/modules/acquia_connector_subdata_test/acquia_connector_subdata_test.services.yml b/docroot/modules/contrib/acquia_connector/tests/modules/acquia_connector_subdata_test/acquia_connector_subdata_test.services.yml new file mode 100644 index 0000000000..26779446fb --- /dev/null +++ b/docroot/modules/contrib/acquia_connector/tests/modules/acquia_connector_subdata_test/acquia_connector_subdata_test.services.yml @@ -0,0 +1,5 @@ +services: + acquia_connector_subdata_test.subscription_data: + class: Drupal\acquia_connector_subdata_test\EventSubscriber\AcquiaSubscriptionData\SubscriptionData + tags: + - { name: 'event_subscriber' } diff --git a/docroot/modules/contrib/acquia_connector/tests/modules/acquia_connector_subdata_test/src/EventSubscriber/AcquiaSubscriptionData/SubscriptionData.php b/docroot/modules/contrib/acquia_connector/tests/modules/acquia_connector_subdata_test/src/EventSubscriber/AcquiaSubscriptionData/SubscriptionData.php new file mode 100644 index 0000000000..12915d507e --- /dev/null +++ b/docroot/modules/contrib/acquia_connector/tests/modules/acquia_connector_subdata_test/src/EventSubscriber/AcquiaSubscriptionData/SubscriptionData.php @@ -0,0 +1,40 @@ + ['onGetSubscriptionData', 100], + ]; + } + + /** + * Gets a prebuilt Settings object from Drupal's settings file. + * + * @param \Drupal\acquia_connector\Event\AcquiaSubscriptionDataEvent $event + * The dispatched event. + */ + public function onGetSubscriptionData(AcquiaSubscriptionDataEvent $event) { + $subscription_data = $event->getData(); + $product_data = [ + 'foo' => 'bar', + 'data_from_subscription' => $subscription_data['uuid'], + ]; + $event->setProductData('acquia_subdata_product', $product_data); + } + +} diff --git a/docroot/modules/contrib/acquia_connector/tests/modules/acquia_connector_test.info.yml b/docroot/modules/contrib/acquia_connector/tests/modules/acquia_connector_test/acquia_connector_test.info.yml similarity index 57% rename from docroot/modules/contrib/acquia_connector/tests/modules/acquia_connector_test.info.yml rename to docroot/modules/contrib/acquia_connector/tests/modules/acquia_connector_test/acquia_connector_test.info.yml index d2eb706a6b..4547960e45 100644 --- a/docroot/modules/contrib/acquia_connector/tests/modules/acquia_connector_test.info.yml +++ b/docroot/modules/contrib/acquia_connector/tests/modules/acquia_connector_test/acquia_connector_test.info.yml @@ -1,10 +1,10 @@ name: Acquia Connector test type: module -description: Support module for the Acquia Connector module tests. -core: 8.x package: Testing +description: Support module for the Acquia Connector module tests. +hidden: true -# Information added by Drupal.org packaging script on 2021-03-31 -version: '8.x-1.26' +# Information added by Drupal.org packaging script on 2023-04-05 +version: '4.0.4' project: 'acquia_connector' -datestamp: 1617213587 +datestamp: 1680704021 diff --git a/docroot/modules/contrib/acquia_connector/tests/modules/acquia_connector_test.module b/docroot/modules/contrib/acquia_connector/tests/modules/acquia_connector_test/acquia_connector_test.module similarity index 100% rename from docroot/modules/contrib/acquia_connector/tests/modules/acquia_connector_test.module rename to docroot/modules/contrib/acquia_connector/tests/modules/acquia_connector_test/acquia_connector_test.module diff --git a/docroot/modules/contrib/acquia_connector/tests/modules/acquia_connector_test.services.yml b/docroot/modules/contrib/acquia_connector/tests/modules/acquia_connector_test/acquia_connector_test.services.yml similarity index 86% rename from docroot/modules/contrib/acquia_connector/tests/modules/acquia_connector_test.services.yml rename to docroot/modules/contrib/acquia_connector/tests/modules/acquia_connector_test/acquia_connector_test.services.yml index 0347bb8f41..1befd87081 100644 --- a/docroot/modules/contrib/acquia_connector/tests/modules/acquia_connector_test.services.yml +++ b/docroot/modules/contrib/acquia_connector/tests/modules/acquia_connector_test/acquia_connector_test.services.yml @@ -1,5 +1,6 @@ services: acquia_connector_test.client_middleware: class: Drupal\acquia_connector_test\AcquiaConnectorMiddleware + arguments: ['@state'] tags: - { name: http_client_middleware } diff --git a/docroot/modules/contrib/acquia_connector/tests/modules/acquia_connector_test/src/AcquiaConnectorMiddleware.php b/docroot/modules/contrib/acquia_connector/tests/modules/acquia_connector_test/src/AcquiaConnectorMiddleware.php new file mode 100644 index 0000000000..9e885a225e --- /dev/null +++ b/docroot/modules/contrib/acquia_connector/tests/modules/acquia_connector_test/src/AcquiaConnectorMiddleware.php @@ -0,0 +1,283 @@ +state = $state; + } + + /** + * Invoked method that returns a promise. + */ + public function __invoke() { + return function ($handler) { + return function (RequestInterface $request, array $options) use ($handler) { + $uri = $request->getUri(); + if ($uri->getHost() === 'accounts.acquia.com') { + $oauth_content = Json::decode((string) $request->getBody()); + if ($oauth_content['grant_type'] === 'authorization_code' && $oauth_content['code'] === 'AUTHORIZATION_SUCCESSFUL') { + return new FulfilledPromise( + new Response( + 200, + [], + Json::encode([ + 'access_token' => 'ACCESS_TOKEN', + 'refresh_token' => 'REFRESH_TOKEN', + ]) + ) + ); + } + if ($oauth_content['grant_type'] === 'authorization_code' && $oauth_content['code'] === 'AUTHORIZATION_ERROR') { + return new FulfilledPromise( + new Response( + 400, + [], + json_encode([ + 'error' => 'invalid_grant', + 'error_description' => 'Authorization code doesn\'t exist or is invalid for the client', + ]) + ) + ); + } + if ($oauth_content['grant_type'] === 'refresh_token') { + return new FulfilledPromise( + new Response( + 200, + [], + Json::encode([ + 'access_token' => 'ACCESS_TOKEN_REFRESHED', + 'refresh_token' => 'REFRESH_TOKEN_REFRESHED', + ]) + ) + ); + } + } + + if ($uri->getHost() === 'cloud.acquia.com') { + $authorization = $request->getHeaderLine('Authorization'); + if ($authorization === '') { + return new FulfilledPromise( + new Response( + 403, + [], + '' + ) + ); + } + if ($uri->getPath() === '/api/applications') { + if ($authorization === 'Bearer ACCESS_TOKEN_NO_APPLICATIONS') { + return new FulfilledPromise( + new Response( + 200, + [], + Json::encode([ + 'total' => 0, + '_embedded' => [ + 'items' => [], + ], + ]) + ) + ); + } + if ($authorization === 'Bearer ACCESS_TOKEN_ONE_APPLICATION') { + return new FulfilledPromise( + new Response( + 200, + [], + Json::encode([ + 'total' => 0, + '_embedded' => [ + 'items' => [ + [ + 'id' => 1234, + 'uuid' => 'a47ac10b-58cc-4372-a567-0e02b2c3d470', + 'name' => 'Sample application 1', + 'subscription' => [ + 'uuid' => 'f47ac10b-58cc-4372-a567-0e02b2c3d470', + 'name' => 'Sample subscription', + ], + ], + ], + ], + ]) + ) + ); + } + if ($authorization === 'Bearer ACCESS_TOKEN_ERROR_GETTING_APPLICATION_KEYS') { + return new FulfilledPromise( + new Response( + 200, + [], + Json::encode([ + 'total' => 0, + '_embedded' => [ + 'items' => [ + [ + 'id' => 1234, + 'uuid' => '647061f7-9971-4b24-9ebb-59eea154d507', + 'name' => 'Sample application 1', + 'subscription' => [ + 'uuid' => 'f47ac10b-58cc-4372-a567-0e02b2c3d470', + 'name' => 'Sample subscription', + ], + ], + ], + ], + ]) + ) + ); + } + if ($authorization === 'Bearer ACCESS_TOKEN_MULTIPLE_APPLICATIONS') { + return new FulfilledPromise( + new Response( + 200, + [], + Json::encode([ + 'total' => 0, + '_embedded' => [ + 'items' => [ + [ + 'id' => 1234, + 'uuid' => 'a47ac10b-58cc-4372-a567-0e02b2c3d470', + 'name' => 'Sample application 1', + 'subscription' => [ + 'uuid' => 'f47ac10b-58cc-4372-a567-0e02b2c3d470', + 'name' => 'Sample subscription', + ], + ], + [ + 'id' => 5678, + 'uuid' => 'a47ac10b-58cc-4372-a567-0e02b2c3d471', + 'name' => 'Sample application 2', + 'subscription' => [ + 'uuid' => 'f47ac10b-58cc-4372-a567-0e02b2c3d470', + 'name' => 'Sample subscription', + ], + ], + ], + ], + ]) + ) + ); + } + } + if ($uri->getPath() === '/api/applications/647061f7-9971-4b24-9ebb-59eea154d507/settings/keys') { + return new FulfilledPromise( + new Response( + 500, + [], + '' + ) + ); + } + if ($uri->getPath() === '/api/applications/a47ac10b-58cc-4372-a567-0e02b2c3d470/settings/keys') { + return new FulfilledPromise( + new Response( + 200, + [], + Json::encode([ + 'acquia_connector' => [ + 'identifier' => 'ABCD-12345', + 'key' => '12345678f5325ea35d63a6c3debcd225', + ], + ]) + ) + ); + } + if ($uri->getPath() === '/api/applications/a47ac10b-58cc-4372-a567-0e02b2c3d470') { + return new FulfilledPromise( + new Response( + 200, + [], + Json::encode([ + 'id' => 1234, + 'uuid' => 'a47ac10b-58cc-4372-a567-0e02b2c3d470', + 'name' => 'Sample application 1', + 'subscription' => [ + 'uuid' => 'f47ac10b-58cc-4372-a567-0e02b2c3d470', + 'name' => 'Sample subscription', + ], + ]) + ) + ); + } + if ($uri->getPath() === '/api/subscriptions/f47ac10b-58cc-4372-a567-0e02b2c3d470') { + return new FulfilledPromise( + new Response( + 200, + [], + Json::encode([ + 'id' => 329876, + 'uuid' => 'f47ac10b-58cc-4372-a567-0e02b2c3d470', + 'name' => 'Sample subscription', + 'expire_at' => '2030-05-12T00:00:00', + 'flags' => [ + 'active' => TRUE, + 'expired' => FALSE, + ], + ]) + ) + ); + } + if ($uri->getPath() === '/test-retry-middleware') { + if ($authorization === 'Bearer ACCESS_TOKEN_RETRY_MIDDLEWARE') { + return new FulfilledPromise( + new Response( + 401, + [], + '' + ) + ); + } + if ($authorization === 'Bearer ACCESS_TOKEN_REFRESHED') { + return new FulfilledPromise( + new Response( + 200, + [], + '' + ) + ); + } + } + } + + if ($uri->getHost() === 'api.amplitude.com') { + $events = $this->state->get('acquia_connector_test.telemetry_events', []); + $content = (string) $request->getBody(); + $events[] = $content; + $this->state->set('acquia_connector_test.telemetry_events', $events); + + return new FulfilledPromise(new Response(200, [], '')); + } + + // Otherwise, no intervention. We defer to the handler stack. + return $handler($request, $options); + }; + }; + } + +} diff --git a/docroot/modules/contrib/acquia_connector/tests/modules/src/AcquiaConnectorMiddleware.php b/docroot/modules/contrib/acquia_connector/tests/modules/src/AcquiaConnectorMiddleware.php deleted file mode 100644 index ca1e545f91..0000000000 --- a/docroot/modules/contrib/acquia_connector/tests/modules/src/AcquiaConnectorMiddleware.php +++ /dev/null @@ -1,92 +0,0 @@ -getUri(); - - // API requests to NSPI. - if ($uri->getScheme() . '://' . $uri->getHost() === 'http://mock-spi-server') { - return $this->createPromise($request); - } - - // Otherwise, no intervention. We defer to the handler stack. - return $handler($request, $options); - }; - }; - } - - /** - * Creates a promise for the NSPI request. - * - * @param \Psr\Http\Message\RequestInterface $request - * Request interface. - * - * @return \GuzzleHttp\Promise\PromiseInterface - * Promise interface. - * - * @throws \Exception - */ - protected function createPromise(RequestInterface $request) { - $nspiController = new NspiController(); - $path = $request->getUri()->getPath(); - switch ($path) { - case '/agent-api/subscription': - $response = $nspiController->getSubscription($request); - $this->updateRequestCount(); - break; - - case '/spi-api/site': - $response = $nspiController->nspiUpdate($request); - $this->updateRequestCount(); - break; - - case '/agent-api/subscription/communication': - $response = $nspiController->getCommunicationSettings($request); - $this->updateRequestCount(); - break; - - case '/agent-api/subscription/credentials': - $response = $nspiController->getCredentials($request); - $this->updateRequestCount(); - break; - - default: - // Todo: handle this more cleanly, maybe with some sort of native route - // matching and parsing. - if (strstr($path, '/spi_def/get')) { - $parts = explode('/', $path); - $response = $nspiController->spiDefinition($request, $parts[3]); - $this->updateRequestCount(); - } - else { - throw new \Exception("Unhandled path $path"); - } - } - return new FulfilledPromise($response); - } - - /** - * Update request count. - */ - protected function updateRequestCount() { - $requests = \Drupal::state()->get('acquia_connector_test_request_count', 0); - $requests++; - \Drupal::state()->set('acquia_connector_test_request_count', $requests); - } - -} diff --git a/docroot/modules/contrib/acquia_connector/tests/modules/src/Controller/NspiController.php b/docroot/modules/contrib/acquia_connector/tests/modules/src/Controller/NspiController.php deleted file mode 100644 index 9f34d8d119..0000000000 --- a/docroot/modules/contrib/acquia_connector/tests/modules/src/Controller/NspiController.php +++ /dev/null @@ -1,589 +0,0 @@ -acqtestSiteMachineName = \Drupal::state()->get('acqtest_site_machine_name'); - $this->acquiaHosted = \Drupal::state()->get('acqtest_site_acquia_hosted'); - } - - /** - * SPI API site update. - * - * @param \GuzzleHttp\Psr7\Request $request - * Request. - * - * @return \GuzzleHttp\Psr7\Response - * JsonResponse. - */ - public function nspiUpdate(Request $request) { - $data = json_decode($request->getBody(), TRUE); - - $fields = [ - 'time' => 'is_numeric', - 'nonce' => 'is_string', - 'hash' => 'is_string', - ]; - $result = $this->basicAuthenticator($fields, $data); - - if (!empty($result['error'])) { - return new Response(200, [], json_encode($result)); - } - if (!empty($data['authenticator']['identifier'])) { - if ($data['authenticator']['identifier'] != self::ACQTEST_ID && $data['authenticator']['identifier'] != self::ACQTEST_ERROR_ID) { - return new Response(self::ACQTEST_SUBSCRIPTION_SERVICE_UNAVAILABLE, [], json_encode($this->errorResponse(self::ACQTEST_SUBSCRIPTION_VALIDATION_ERROR, $this->t('Subscription not found')))); - } - if ($data['authenticator']['identifier'] == self::ACQTEST_ERROR_ID) { - return new Response(); - } - else { - $result = $this->validateAuthenticator($data); - // Needs for update definition. - $data['body']['spi_def_update'] = TRUE; - $spi_data = $data['body']; - - $result['body'] = ['spi_data_received' => TRUE]; - if (isset($spi_data['spi_def_update'])) { - $result['body']['update_spi_definition'] = TRUE; - } - - // Reflect send_method as nspi_messages if set. - if (isset($spi_data['send_method'])) { - $result['body']['nspi_messages'][] = $spi_data['send_method']; - } - $result['authenticator']['hash'] = CryptConnector::acquiaHash($result['secret']['key'], $result['authenticator']['time'] . ':' . $result['authenticator']['nonce']); - if (isset($spi_data['test_validation_error'])) { - // Force a validation fail. - $result['authenticator']['nonce'] = 'TEST'; - } - - $site_action = $spi_data['env_changed_action']; - // First connection. - if (empty($spi_data['site_uuid'])) { - $site_action = 'create'; - } - - switch ($site_action) { - case 'create': - $result['body']['site_uuid'] = self::ACQTEST_SITE_UUID; - // Set machine name. - \Drupal::state()->set('acqtest_site_machine_name', $spi_data['machine_name']); - // Set name. - \Drupal::state()->set('acqtest_site_name', $spi_data['name']); - $acquia_hosted = (int) filter_var($spi_data['acquia_hosted'], FILTER_VALIDATE_BOOLEAN); - \Drupal::state()->set('acqtest_site_acquia_hosted', $acquia_hosted); - - $result['body']['nspi_messages'][] = $this->t('This is the first connection from this site, it may take awhile for it to appear.'); - return new Response(200, [], json_encode($result)); - - case 'update': - $update = $this->updateNspiSite($spi_data); - $result['body']['nspi_messages'][] = $update; - break; - - case 'unblock': - \Drupal::state()->delete('acqtest_site_blocked'); - $result['body']['spi_error'] = ''; - $result['body']['nspi_messages'][] = $this->t('Your site has been enabled and is sending data to Acquia Cloud.'); - return new Response(200, [], json_encode($result)); - - case 'block': - \Drupal::state()->set('acqtest_site_blocked', TRUE); - $result['body']['spi_error'] = ''; - $result['body']['nspi_messages'][] = $this->t('You have disabled your site from sending data to Acquia Cloud.'); - return new Response(200, [], json_encode($result)); - - } - - // Update site name if it has changed. - $tacqtest_site_name = \Drupal::state()->get('acqtest_site_name'); - if (isset($spi_data['name']) && $spi_data['name'] != $tacqtest_site_name) { - if (!empty($tacqtest_site_name)) { - $name_update_message = $this->t('Site name updated (from @old_name to @new_name).', [ - '@old_name' => $tacqtest_site_name, - '@new_name' => $spi_data['name'], - ]); - - \Drupal::state()->set('acqtest_site_name', $spi_data['name']); - } - $result['body']['nspi_messages'][] = $name_update_message; - } - - // Detect Changes. - if ($changes = $this->detectChanges($spi_data)) { - $result['body']['nspi_messages'][] = $changes['response']; - $result['body']['spi_error'] = TRUE; - $result['body']['spi_environment_changes'] = json_encode($changes['changes']); - return new Response(200, [], json_encode($result)); - } - - unset($result['secret']); - return new Response(200, [], json_encode($result)); - } - } - else { - return new Response(self::ACQTEST_SUBSCRIPTION_SERVICE_UNAVAILABLE, [], json_encode($this->errorResponse(self::ACQTEST_SUBSCRIPTION_VALIDATION_ERROR, $this->t('Invalid arguments')))); - } - } - - /** - * Detect potential environment changes. - * - * @param array $spi_data - * SPI data array. - * - * @return array|bool - * FALSE or changes message. - */ - public function detectChanges(array $spi_data) { - $changes = []; - $site_blocked = \Drupal::state()->get('acqtest_site_blocked'); - - if ($site_blocked) { - $changes['changes']['blocked'] = (string) $this->t('Your site has been enabled.'); - } - else { - - if ($this->checkAcquiaHostedStatusChanged($spi_data) && !is_null($this->acquiaHosted)) { - if ($spi_data['acquia_hosted']) { - $changes['changes']['acquia_hosted'] = (string) $this->t('Your site is now Acquia hosted.'); - } - else { - $changes['changes']['acquia_hosted'] = (string) $this->t('Your site is no longer Acquia hosted.'); - } - } - - if ($this->checkMachineNameStatusChanged($spi_data)) { - $changes['changes']['machine_name'] = (string) $this->t('Your site machine name changed from @old_machine_name to @new_machine_name.', [ - '@old_machine_name' => $this->acqtestSiteMachineName, - '@new_machine_name' => $spi_data['machine_name'], - ]); - } - - } - - if (empty($changes)) { - return FALSE; - } - - $changes['response'] = (string) $this->t('A change has been detected in your site environment. Please check the Acquia SPI status on your Status Report page for more information.'); - - return $changes; - } - - /** - * Save changes to the site entity. - * - * @param array $spi_data - * SPI data array. - * - * @return string - * Message string. - */ - public function updateNspiSite(array $spi_data) { - $message = ''; - - if ($this->checkMachineNameStatusChanged($spi_data)) { - if (!empty($this->acqtestSiteMachineName)) { - $message = (string) $this->t('Updated site machine name from @old_machine_name to @new_machine_name.', ['@old_machine_name' => $this->acqtestSiteMachineName, '@new_machine_name' => $spi_data['machine_name']]); - } - else { - $message = (string) $this->t('Site machine name set to to @new_machine_name.', ['@new_machine_name' => $spi_data['machine_name']]); - } - - \Drupal::state()->set('acqtest_site_machine_name', $spi_data['machine_name']); - $this->acqtestSiteMachineName = $spi_data['machine_name']; - } - - if ($this->checkAcquiaHostedStatusChanged($spi_data)) { - if (!is_null($this->acquiaHosted)) { - $hosted_message = $spi_data['acquia_hosted'] ? (string) $this->t('site is now Acquia hosted') : (string) $this->t('site is no longer Acquia hosted'); - $message = (string) $this->t('Updated Acquia hosted status (@hosted_message).', ['@hosted_message' => $hosted_message]); - } - - $acquia_hosted = (int) filter_var($spi_data['acquia_hosted'], FILTER_VALIDATE_BOOLEAN); - \Drupal::state()->set('acqtest_site_acquia_hosted', $acquia_hosted); - $this->acquiaHosted = $acquia_hosted; - } - - return $message; - } - - /** - * Detect if machine name changed. - * - * @param array $spi_data - * SPI data. - * - * @return bool - * TRUE if machine name was changed. - */ - public function checkMachineNameStatusChanged(array $spi_data) { - return isset($spi_data['machine_name']) && $spi_data['machine_name'] != $this->acqtestSiteMachineName; - } - - /** - * Detect if Acquia hosted changed. - * - * @param array $spi_data - * SPI data. - * - * @return bool - * TRUE if site is Acquia Hosted. - */ - public function checkAcquiaHostedStatusChanged(array $spi_data) { - return isset($spi_data['acquia_hosted']) && (bool) $spi_data['acquia_hosted'] != (bool) $this->acquiaHosted; - } - - /** - * Return spi definition. - * - * @param \GuzzleHttp\Psr7\Request $request - * Request. - * @param string $version - * Version. - * - * @return \GuzzleHttp\Psr7\Response - * JsonResponse. - */ - public function spiDefinition(Request $request, $version) { - $vars = [ - 'test_variable_1' => [ - 'optional' => FALSE, - 'description' => 'test_variable_1', - ], - 'test_variable_2' => [ - 'optional' => TRUE, - 'description' => 'test_variable_2', - ], - 'test_variable_3' => [ - 'optional' => TRUE, - 'description' => 'test_variable_3', - ], - ]; - $data = [ - 'drupal_version' => (string) $version, - 'timestamp' => (string) (\Drupal::time()->getRequestTime() + 9), - 'acquia_spi_variables' => $vars, - ]; - return new Response(200, [], json_encode($data)); - } - - /** - * Test return communication settings for an account. - * - * @param \GuzzleHttp\Psr7\Request $request - * Request. - * - * @return \GuzzleHttp\Psr7\Response - * JsonResponse. - */ - public function getCommunicationSettings(Request $request) { - $data = json_decode($request->getBody(), TRUE); - $fields = [ - 'time' => 'is_numeric', - 'nonce' => 'is_string', - 'hash' => 'is_string', - ]; - - // Authenticate. - $result = $this->basicAuthenticator($fields, $data); - if (!empty($result['error'])) { - return new Response(200, [], json_encode($result)); - } - - if (!isset($data['body']) || !isset($data['body']['email'])) { - return new Response(self::ACQTEST_SUBSCRIPTION_SERVICE_UNAVAILABLE, [], json_encode($this->errorResponse(self::ACQTEST_SUBSCRIPTION_VALIDATION_ERROR, $this->t('Invalid arguments')))); - } - $account = user_load_by_mail($data['body']['email']); - if (empty($account) || $account->isAnonymous()) { - return new Response(self::ACQTEST_SUBSCRIPTION_SERVICE_UNAVAILABLE, [], json_encode($this->errorResponse(self::ACQTEST_SUBSCRIPTION_VALIDATION_ERROR, $this->t('Account not found')))); - } - $result = [ - 'algorithm' => 'sha512', - 'hash_setting' => substr($account->getPassword(), 0, 12), - 'extra_md5' => FALSE, - ]; - return new Response(200, [], json_encode($result)); - } - - /** - * Basic authenticator. - * - * @param array $fields - * Fields array. - * @param array $data - * Data array. - * - * @return array - * Result array. - */ - protected function basicAuthenticator(array $fields, array $data) { - $result = []; - foreach ($fields as $field => $type) { - if (empty($data['authenticator'][$field]) || !$type($data['authenticator'][$field])) { - return $this->errorResponse(self::ACQTEST_SUBSCRIPTION_MESSAGE_INVALID, $this->t('Authenticator field @field is missing or invalid.', ['@field' => $field])); - } - } - $now = \Drupal::time()->getRequestTime(); - if ($data['authenticator']['time'] > ($now + self::ACQTEST_SUBSCRIPTION_MESSAGE_LIFETIME)) { - return $this->errorResponse(self::ACQTEST_SUBSCRIPTION_MESSAGE_FUTURE, $this->t('Message time ahead of server time.')); - } - else { - if ($data['authenticator']['time'] < ($now - self::ACQTEST_SUBSCRIPTION_MESSAGE_LIFETIME)) { - return $this->errorResponse(self::ACQTEST_SUBSCRIPTION_MESSAGE_EXPIRED, $this->t('Message is too old.')); - } - } - - $result['error'] = FALSE; - return $result; - } - - /** - * Test returns subscriptions for an email. - * - * @param \GuzzleHttp\Psr7\Request $request - * Request. - * - * @return \GuzzleHttp\Psr7\Response - * JsonResponse. - */ - public function getCredentials(Request $request) { - $data = json_decode($request->getBody(), TRUE); - - $fields = [ - 'time' => 'is_numeric', - 'nonce' => 'is_string', - 'hash' => 'is_string', - ]; - $result = $this->basicAuthenticator($fields, $data); - if (!empty($result['error'])) { - return new Response(self::ACQTEST_SUBSCRIPTION_SERVICE_UNAVAILABLE, [], $result); - } - - if (!empty($data['body']['email'])) { - $account = user_load_by_mail($data['body']['email']); - $this->getLogger('getCredentials password')->debug($account->getPassword()); - if (empty($account) || $account->isAnonymous()) { - return new Response(self::ACQTEST_SUBSCRIPTION_SERVICE_UNAVAILABLE, [], json_encode($this->errorResponse(self::ACQTEST_SUBSCRIPTION_VALIDATION_ERROR, $this->t('Account not found')))); - } - } - else { - return new Response(self::ACQTEST_SUBSCRIPTION_SERVICE_UNAVAILABLE, [], json_encode($this->errorResponse(self::ACQTEST_SUBSCRIPTION_VALIDATION_ERROR, $this->t('Invalid arguments')))); - } - - $hash = CryptConnector::acquiaHash($account->getPassword(), $data['authenticator']['time'] . ':' . $data['authenticator']['nonce']); - if ($hash === $data['authenticator']['hash']) { - $result = []; - $result['is_error'] = FALSE; - $result['body']['subscription'][] = [ - 'identifier' => self::ACQTEST_ID, - 'key' => self::ACQTEST_KEY, - 'name' => self::ACQTEST_ID, - ]; - return new Response(200, [], json_encode($result)); - } - else { - return new Response(self::ACQTEST_SUBSCRIPTION_SERVICE_UNAVAILABLE, [], json_encode($this->errorResponse(self::ACQTEST_SUBSCRIPTION_VALIDATION_ERROR, $this->t('Incorrect password.')))); - } - } - - /** - * Test validates an Acquia subscription. - * - * @param \GuzzleHttp\Psr7\Request $request - * Request. - * - * @return \GuzzleHttp\Psr7\Response - * JsonResponse. - */ - public function getSubscription(Request $request) { - $data = json_decode($request->getBody(), TRUE); - $result = $this->validateAuthenticator($data); - if (empty($result['error'])) { - $result['authenticator']['hash'] = CryptConnector::acquiaHash($result['secret']['key'], $result['authenticator']['time'] . ':' . $result['authenticator']['nonce']); - unset($result['secret']); - return new Response(200, [], json_encode($result)); - } - unset($result['secret']); - return new Response(self::ACQTEST_SUBSCRIPTION_SERVICE_UNAVAILABLE, [], json_encode($result)); - } - - /** - * Test validates an Acquia authenticator. - * - * @param array $data - * Data to validate. - * - * @return array - * Result array. - */ - protected function validateAuthenticator(array $data) { - $fields = [ - 'time' => 'is_numeric', - 'identifier' => 'is_string', - 'nonce' => 'is_string', - 'hash' => 'is_string', - ]; - - $result = $this->basicAuthenticator($fields, $data); - if (!empty($result['error'])) { - return $result; - } - - if (strpos($data['authenticator']['identifier'], 'TEST_') !== 0) { - return $this->errorResponse(self::ACQTEST_SUBSCRIPTION_NOT_FOUND, $this->t('Subscription not found')); - } - - switch ($data['authenticator']['identifier']) { - case self::ACQTEST_ID: - $key = self::ACQTEST_KEY; - break; - - case self::ACQTEST_EXPIRED_ID: - $key = self::ACQTEST_EXPIRED_KEY; - break; - - case self::ACQTEST_503_ID: - $key = self::ACQTEST_503_KEY; - break; - - default: - $key = self::ACQTEST_ERROR_KEY; - break; - } - - $hash = CryptConnector::acquiaHash($key, $data['authenticator']['time'] . ':' . $data['authenticator']['nonce']); - $hash_simple = CryptConnector::acquiaHash($key, $data['authenticator']['time'] . ':' . $data['authenticator']['nonce']); - - if (($hash !== $data['authenticator']['hash']) && ($hash_simple != $data['authenticator']['hash'])) { - return $this->errorResponse(self::ACQTEST_SUBSCRIPTION_VALIDATION_ERROR, $this->t('HMAC validation error: @expected != @actual', [ - '@expected' => $hash, - '@actual' => $data['authenticator']['hash'], - ])); - } - - if ($key === self::ACQTEST_EXPIRED_KEY) { - return $this->errorResponse(self::ACQTEST_SUBSCRIPTION_EXPIRED, $this->t('Subscription expired.')); - } - - // Record connections. - $connections = \Drupal::state()->get('test_connections' . $data['authenticator']['identifier']); - $connections++; - \Drupal::state()->set('test_connections' . $data['authenticator']['identifier'], $connections); - if ($connections == 3 && $data['authenticator']['identifier'] == self::ACQTEST_503_ID) { - // Trigger a 503 response on 3rd call to this (1st is - // acquia.agent.subscription and 2nd is acquia.agent.validate) - return $this->errorResponse(self::ACQTEST_SUBSCRIPTION_SERVICE_UNAVAILABLE, 'Subscription service unavailable.'); - } - $result['error'] = FALSE; - $result['body']['subscription_name'] = 'TEST_AcquiaConnectorTestID'; - $result['body']['active'] = 1; - $result['body']['href'] = 'http://acquia.com/network'; - $result['body']['expiration_date']['value'] = '2023-10-08T06:30:00'; - $result['body']['product'] = '91990'; - $result['body']['derived_key_salt'] = $data['authenticator']['identifier'] . '_KEY_SALT'; - $result['body']['update_service'] = 1; - $result['body']['search_service_enabled'] = 1; - $result['body']['uuid'] = self::ACQTEST_UUID; - if (isset($data['body']['rpc_version'])) { - $result['body']['rpc_version'] = $data['body']['rpc_version']; - } - $result['secret']['data'] = $data; - $result['secret']['nid'] = '91990'; - $result['secret']['node'] = $data['authenticator']['identifier'] . '_NODE'; - $result['secret']['key'] = $key; - // $result['secret']['nonce'] = '';. - $result['authenticator'] = $data['authenticator']; - $result['authenticator']['hash'] = ''; - $result['authenticator']['time'] += 1; - $result['authenticator']['nonce'] = $data['authenticator']['nonce']; - return $result; - } - - /** - * Access callback. - * - * @return \Drupal\Core\Access\AccessResultAllowed - * The access result. - */ - public function access() { - return AccessResultAllowed::allowed(); - } - - /** - * Format the error response. - * - * @param mixed $code - * Error code. - * @param string $message - * Error message. - * - * @return array - * Error response. - */ - protected function errorResponse($code, $message) { - return [ - 'code' => $code, - 'message' => $message, - 'error' => TRUE, - ]; - } - -} diff --git a/docroot/modules/contrib/acquia_connector/tests/src/Functional/AcquiaConnectorModuleTest.php b/docroot/modules/contrib/acquia_connector/tests/src/Functional/AcquiaConnectorModuleTest.php deleted file mode 100644 index d1a20d6959..0000000000 --- a/docroot/modules/contrib/acquia_connector/tests/src/Functional/AcquiaConnectorModuleTest.php +++ /dev/null @@ -1,623 +0,0 @@ -privilegedUser = $this->drupalCreateUser([ - 'administer site configuration', - 'access administration pages', - 'access toolbar', - ]); - $this->drupalLogin($this->privilegedUser); - - // Create a user that has a Network subscription. - $this->networkUser = $this->drupalCreateUser(); - $this->networkUser->mail = $this->acqtestEmail; - $this->networkUser->pass = $this->acqtestPass; - $this->networkUser->save(); - // $this->drupalLogin($this->network_user); - // Setup variables. - $this->cloudFreeUrl = 'https://www.acquia.com/acquia-cloud-free'; - $this->setupPath = 'admin/config/system/acquia-connector/setup'; - $this->credentialsPath = 'admin/config/system/acquia-connector/credentials'; - $this->settingsPath = 'admin/config/system/acquia-connector'; - $this->environmentChangePath = '/admin/config/system/acquia-connector/environment-change'; - $this->statusReportUrl = 'admin/reports/status'; - - \Drupal::configFactory()->getEditable('acquia_connector.settings')->set('spi.server', 'http://mock-spi-server')->save(); - \Drupal::configFactory()->getEditable('acquia_connector.settings')->set('spi.ssl_verify', FALSE)->save(); - \Drupal::configFactory()->getEditable('acquia_connector.settings')->set('spi.ssl_override', TRUE)->save(); - } - - /** - * Helper function for storing UI strings. - */ - private function acquiaConnectorStrings($id) { - switch ($id) { - case 'free': - return 'Sign up for Acquia Cloud Free, a free Drupal sandbox to experiment with new features, test your code quality, and apply continuous integration best practices.'; - - case 'get-connected': - return 'If you have an Acquia Subscription, connect now. Otherwise, you can turn this message off by disabling the Acquia Connector modules.'; - - case 'enter-email': - return 'Enter the email address you use to login to the Acquia Subscription'; - - case 'enter-password': - return 'Enter your Acquia Subscription password'; - - case 'account-not-found': - return 'Account not found'; - - case 'id-key': - return 'Enter your product keys from your application overview or log in to connect your site to Acquia Insight.'; - - case 'enter-key': - return 'Network key'; - - case 'subscription-not-found': - return 'Error: Subscription not found (1000)'; - - case 'saved': - return 'The configuration options have been saved.'; - - case 'subscription': - // Assumes subscription name is same as id. - return 'Subscription: ' . $this->acqtestId; - - case 'menu-active': - return 'Subscription active (expires 2023/10/8)'; - - case 'menu-inactive': - return 'Subscription not active'; - - case 'site-name-required': - return 'Name field is required.'; - - case 'site-machine-name-required': - return 'Machine name field is required.'; - - case 'first-connection': - return 'This is the first connection from this site, it may take awhile for it to appear.'; - } - } - - /** - * Test get connected. - */ - public function testAcquiaConnectorGetConnectedTests() { - // Check for call to get connected. - $this->drupalGet('admin'); - $this->assertText($this->acquiaConnectorStrings('free'), 'The explanation of services text exists'); - $this->assertLinkByHref($this->cloudFreeUrl, 0, 'Link to Acquia.com Cloud Services exists'); - $this->assertText($this->acquiaConnectorStrings('get-connected'), 'The call-to-action to connect text exists'); - $this->assertLink('connect now', 0, 'The "connect now" link exists'); - // Check connection setup page. - $this->drupalGet($this->setupPath); - $this->assertText($this->acquiaConnectorStrings('enter-email'), 'The email address field label exists'); - $this->assertText($this->acquiaConnectorStrings('enter-password'), 'The password field label exists'); - $this->assertLinkByHref($this->cloudFreeUrl, 0, 'Link to Acquia.com free signup exists'); - // Check errors on automatic setup page. - $edit_fields = [ - 'email' => $this->randomString(), - 'pass' => $this->randomString(), - ]; - $submit_button = 'Next'; - $this->drupalPostForm($this->setupPath, $edit_fields, $submit_button); - $this->assertText($this->acquiaConnectorStrings('account-not-found'), 'Account not found for random automatic setup attempt'); - $this->assertText($this->acquiaConnectorStrings('menu-inactive'), 'Subscription not active menu message appears'); - // Check manual connection. - $this->drupalGet($this->credentialsPath); - $this->assertText($this->acquiaConnectorStrings('id-key'), 'The network key and id description exists'); - $this->assertText($this->acquiaConnectorStrings('enter-key'), 'The network key field label exists'); - $this->assertLinkByHref($this->cloudFreeUrl, 0, 'Link to Acquia.com free signup exists'); - // Check errors on connection page. - $edit_fields = [ - 'acquia_identifier' => $this->randomString(), - 'acquia_key' => $this->randomString(), - ]; - $submit_button = 'Connect'; - $this->drupalPostForm($this->credentialsPath, $edit_fields, $submit_button); - $this->assertText($this->acquiaConnectorStrings('subscription-not-found'), 'Subscription not found for random credentials'); - $this->assertText($this->acquiaConnectorStrings('menu-inactive'), 'Subscription not active menu message appears'); - // Connect site on key and id. - $edit_fields = [ - 'acquia_identifier' => $this->acqtestId, - 'acquia_key' => $this->acqtestKey, - ]; - $submit_button = 'Connect'; - $this->drupalPostForm($this->credentialsPath, $edit_fields, $submit_button); - $this->drupalGet($this->settingsPath); - $this->assertText($this->acquiaConnectorStrings('subscription'), 'Subscription connected with key and identifier'); - $this->assertLinkByHref($this->setupPath, 0, 'Link to change subscription exists'); - - $this->disconnectSite(); - - // Connect via automatic setup. - $edit_fields = [ - 'email' => $this->acqtestEmail, - 'pass' => $this->acqtestPass, - ]; - $submit_button = 'Next'; - $this->drupalPostForm($this->setupPath, $edit_fields, $submit_button); - $this->drupalGet($this->setupPath); - $this->drupalGet($this->settingsPath); - $this->assertText($this->acquiaConnectorStrings('subscription'), 'Subscription connected with credentials'); - - // Confirm menu reports active subscription. - $this->drupalGet('admin'); - $this->assertText($this->acquiaConnectorStrings('menu-active'), 'Subscription active menu message appears'); - // Check errors if name or machine name empty. - $submit_button = 'Save configuration'; - $this->drupalPostForm($this->settingsPath, [], $submit_button); - $this->assertText($this->acquiaConnectorStrings('site-name-required'), 'Name is required message appears'); - $this->assertText($this->acquiaConnectorStrings('site-machine-name-required'), 'Machine name is required message appears'); - - // Acquia hosted sites. - $edit_fields = [ - 'acquia_dynamic_banner' => TRUE, - 'name' => 'test_name', - 'machine_name' => 'test_name', - ]; - $submit_button = 'Save configuration'; - $this->drupalPostForm($this->settingsPath, $edit_fields, $submit_button); - $this->assertFieldChecked('edit-acquia-dynamic-banner', '"Receive updates from Acquia" option stays saved'); - // Test acquia hosted site. - $settings['_SERVER']['AH_SITE_NAME'] = (object) [ - 'value' => 'acqtest_drupal', - 'required' => TRUE, - ]; - $settings['_SERVER']['AH_SITE_ENVIRONMENT'] = (object) [ - 'value' => 'dev', - 'required' => TRUE, - ]; - $this->writeSettings($settings); - sleep(10); - $this->drupalGet($this->settingsPath); - $elements = $this->xpath('//input[@name=:name]', [':name' => 'machine_name']); - foreach ($elements as $element) { - $this->assertIdentical($element->getAttribute('disabled'), 'disabled', 'Machine name field is disabled.'); - } - - $this->disconnectSite(); - - } - - /** - * Test Connector subscription methods. - */ - public function testAcquiaConnectorSubscriptionTests() { - $subscription = new Subscription(); - // Starts as inactive. - $is_active = $subscription->isActive(); - $this->assertFalse($is_active, 'Subscription is not currently active.'); - // Confirm HTTP request count is 0 because without credentials no request - // should have been made. - $this->assertIdentical(\Drupal::state()->get('acquia_connector_test_request_count', 0), 0); - $check_subscription = $subscription->update(); - \Drupal::state()->resetCache(); - $this->assertFalse($check_subscription, 'Subscription is currently false.'); - // Confirm HTTP request count is still 0. - $this->assertIdentical(\Drupal::state()->get('acquia_connector_test_request_count', 0), 0); - // Fail a connection. - $random_id = $this->randomString(); - $edit_fields = [ - 'acquia_identifier' => $random_id, - 'acquia_key' => $this->randomString(), - ]; - $submit_button = 'Connect'; - $this->drupalPostForm($this->credentialsPath, $edit_fields, $submit_button); - // Confirm HTTP request count is 1. - $this->assertIdentical(\Drupal::state()->get('acquia_connector_test_request_count', 0), 1, 'Made 1 HTTP request in attempt to connect subscription.'); - $is_active = $subscription->isActive(); - $this->assertFalse($is_active, 'Subscription is not active after failed attempt to connect.'); - $this->assertIdentical(\Drupal::state()->get('acquia_connector_test_request_count', 0), 1, 'Still have made only 1 HTTP request'); - $check_subscription = $subscription->update(); - \Drupal::state()->resetCache(); - $this->assertFalse($check_subscription, 'Subscription is false after failed attempt to connect.'); - $this->assertIdentical(\Drupal::state()->get('acquia_connector_test_request_count', 0), 1, 'Still have made only 1 HTTP request'); - // Test default from acquia_agent_settings(). - $stored = \Drupal::config('acquia_connector.settings'); - $current_subscription = \Drupal::state()->get('acquia_subscription_data'); - // Not identical since acquia_agent_has_credentials() causes stored to be - // deleted. - $this->assertNotIdentical($check_subscription, $current_subscription, 'Stored subscription data not same before connected subscription.'); - $this->assertTrue($current_subscription['active'] === FALSE, 'Default is inactive.'); - // Reset HTTP request counter;. - \Drupal::state()->set('acquia_connector_test_request_count', 0); - // Connect. - $edit_fields = [ - 'acquia_identifier' => $this->acqtestId, - 'acquia_key' => $this->acqtestKey, - ]; - $this->drupalPostForm($this->credentialsPath, $edit_fields, $submit_button); - // HTTP requests should now be 3 (acquia.agent.subscription.name and - // acquia.agent.subscription and acquia.agent.validate. - $this->assertIdentical(\Drupal::state()->get('acquia_connector_test_request_count', 0), 3, '3 HTTP requests were made during first connection.'); - $is_active = $subscription->isActive(); - $this->assertTrue($is_active, 'Subscription is active after successful connection.'); - $check_subscription = $subscription->update(); - \Drupal::state()->resetCache(); - $this->assertTrue(is_array($check_subscription), 'Subscription is array after successful connection.'); - // Now stored subscription data should match. - $stored = \Drupal::config('acquia_connector.settings'); - $this->assertIdentical(\Drupal::state()->get('acquia_connector_test_request_count', 0), 4, '1 additional HTTP request made via acquia_agent_check_subscription().'); - $this->drupalGet($this->baseUrl); - $this->drupalGet('admin'); - $this->assertIdentical(\Drupal::state()->get('acquia_connector_test_request_count', 0), 4, 'No extra requests made during visits to other pages.'); - // Reset HTTP request counter;. - \Drupal::state()->set('acquia_connector_test_request_count', 0); - // Connect on expired subscription. - $edit_fields = [ - 'acquia_identifier' => $this->acqtestExpiredId, - 'acquia_key' => $this->acqtestExpiredKey, - ]; - $this->drupalPostForm($this->credentialsPath, $edit_fields, $submit_button); - $this->assertIdentical(\Drupal::state()->get('acquia_connector_test_request_count', 0), 3, '3 HTTP requests were made during expired connection attempt.'); - $is_active = $subscription->isActive(); - $this->assertFalse($is_active, 'Subscription is not active after connection with expired subscription.'); - $this->assertIdentical(\Drupal::state()->get('acquia_connector_test_request_count', 0), 3, 'No additional HTTP requests made via acquia_agent_subscription_is_active().'); - $this->drupalGet($this->baseUrl); - $this->drupalGet('admin'); - $this->assertIdentical(\Drupal::state()->get('acquia_connector_test_request_count', 0), 3, 'No HTTP requests made during visits to other pages.'); - // Stored subscription data will now be the expired integer. - $check_subscription = $subscription->update(); - \Drupal::state()->resetCache(); - $this->assertIdentical($check_subscription, 1200, 'Subscription is expired after connection with expired subscription.'); - $this->assertIdentical(\Drupal::state()->get('acquia_connector_test_request_count', 0), 4, '1 additional request made via acquia_agent_check_subscription().'); - $stored = \Drupal::config('acquia_connector.settings'); - $current_subscription = \Drupal::state()->get('acquia_subscription_data'); - $this->assertIdentical($check_subscription, $current_subscription, 'Stored expected subscription data.'); - // Reset HTTP request counter;. - \Drupal::state()->set('acquia_connector_test_request_count', 0); - // Connect on subscription that will trigger a 503 response.. - $edit_fields = [ - 'acquia_identifier' => $this->acqtest503Id, - 'acquia_key' => $this->acqtest503Key, - ]; - $this->drupalPostForm($this->credentialsPath, $edit_fields, $submit_button); - $is_active = $subscription->isActive(); - $this->assertTrue($is_active, 'Subscription is active after successful connection.'); - // Make another request which will trigger 503 server error. - $check_subscription = $subscription->update(); - \Drupal::state()->resetCache(); - // Hold onto subcription data for comparison. - $stored = \Drupal::config('acquia_connector.settings'); - $this->assertNotIdentical($check_subscription, '503', 'Subscription is not storing 503.'); - $this->assertTrue(is_array($check_subscription), 'Storing subscription array data.'); - $this->assertIdentical(\Drupal::state()->get('acquia_connector_test_request_count', 0), 4, 'Have made 4 HTTP requests so far.'); - } - - /** - * Tests the site status callback. - */ - public function testAcquiaConnectorSiteStatusTests() { - $uuid = '0dee0d07-4032-44ea-a2f2-84182dc10d54'; - $test_url = "https://insight.acquia.com/node/uuid/{$uuid}/dashboard"; - $test_data = [ - 'active' => 1, - 'href' => $test_url, - ]; - // Set some sample test data. - \Drupal::state()->set('acquia_subscription_data', $test_data); - // Test StatusControllerTest::getIdFromSub. - $getIdFromSub = new StatusController(); - $key = $getIdFromSub->getIdFromSub($test_data); - $this->assertIdentical($key, $uuid); - // Add a 'uuid' key to the data and make sure that is returned. - $test_data['uuid'] = $uuid; - $test_data['href'] = 'http://example.com'; - $key = $getIdFromSub->getIdFromSub($test_data); - $this->assertIdentical($key, $uuid); - $query = [ - 'key' => hash('sha1', "{$key}:test"), - 'nonce' => 'test', - ]; - $json = json_decode($this->drupalGet('system/acquia-connector-status', ['query' => $query]), TRUE); - // Test the version. - $this->assertIdentical($json['version'], '1.0', 'Correct API version found.'); - // Test invalid query string parameters for access. - // A random key value should fail. - $query['key'] = $this->randomString(16); - $this->drupalGet('system/acquia-connector-status', ['query' => $query]); - $this->assertResponse(403); - } - - /** - * Tests the SPI change form. - * - * This should be a separate test. - */ - public function testSpiChangeFormTests() { - // Connect site on key and id. - $edit_fields = [ - 'acquia_identifier' => $this->acqtestId, - 'acquia_key' => $this->acqtestKey, - ]; - $submit_button = 'Connect'; - $this->drupalPostForm($this->credentialsPath, $edit_fields, $submit_button); - $this->drupalGet($this->settingsPath); - $this->assertText($this->acquiaConnectorStrings('subscription'), 'Subscription connected with key and identifier'); - // No changes detected. - $edit_fields = [ - 'acquia_dynamic_banner' => TRUE, - 'name' => $this->acqtestName, - 'machine_name' => $this->acqtestMachineName, - ]; - $submit_button = 'Save configuration'; - $this->drupalPostForm($this->settingsPath, $edit_fields, $submit_button); - $this->assertText($this->acquiaConnectorStrings('saved'), 'The configuration options have been saved.'); - $this->drupalGet($this->statusReportUrl); - $this->clickLink('manually send SPI data'); - $this->drupalGet($this->environmentChangePath); - $this->assertText('No changes detected', 'No changes are currently detected.'); - // Detect Changes. - $edit_fields = [ - 'acquia_dynamic_banner' => TRUE, - 'name' => $this->acqtestName, - 'machine_name' => $this->acqtestMachineName . '_change', - ]; - $submit_button = 'Save configuration'; - $this->drupalPostForm($this->settingsPath, $edit_fields, $submit_button); - $this->assertText($this->acquiaConnectorStrings('saved'), 'The configuration options have been saved.'); - $this->assertText('A change has been detected in your site environment. Please check the Acquia SPI status on your Status Report page for more information', 'Changes have been detected'); - $this->drupalGet($this->environmentChangePath); - // Check environment change action. - $elements = $this->xpath('//input[@name=:name]', [':name' => 'env_change_action']); - $expected_values = ['block', 'update', 'create']; - foreach ($elements as $element) { - $expected = array_shift($expected_values); - $this->assertIdentical($element->getAttribute('value'), $expected); - } - // Test "block" the connector from sending data to NSPI. - $edit_fields = [ - 'env_change_action' => 'block', - ]; - $submit_button = 'Save configuration'; - $this->drupalPostForm($this->environmentChangePath, $edit_fields, $submit_button); - $this->assertText('This site has been disabled from sending profile data to Acquia.'); - $this->assertText('You have disabled your site from sending data to Acquia Cloud.'); - // Test unblock site. - $this->clickLink('Enable this site'); - $this->assertText('The Acquia Connector is disabled and is not sending site profile data to Acquia Cloud for evaluation.'); - $edit_fields = [ - 'env_change_action[unblock]' => TRUE, - ]; - $submit_button = 'Save configuration'; - $this->drupalPostForm($this->environmentChangePath, $edit_fields, $submit_button); - $this->assertText('Your site has been enabled and is sending data to Acquia Cloud.'); - $this->clickLink('manually send SPI data'); - $this->assertText('A change has been detected in your site environment. Please check the Acquia SPI status on your Status Report page for more information.'); - // Test update existing site. - $this->clickLink('confirm the action you wish to take'); - $edit_fields = [ - 'env_change_action' => 'update', - ]; - $submit_button = 'Save configuration'; - $this->drupalPostForm($this->environmentChangePath, $edit_fields, $submit_button); - // Test new site in Acquia Cloud. - $edit_fields = [ - 'acquia_dynamic_banner' => TRUE, - 'name' => $this->acqtestName, - 'machine_name' => $this->acqtestMachineName, - ]; - $submit_button = 'Save configuration'; - $this->drupalPostForm($this->settingsPath, $edit_fields, $submit_button); - $this->assertText($this->acquiaConnectorStrings('saved'), 'The configuration options have been saved.'); - $this->assertText('A change has been detected in your site environment. Please check the Acquia SPI status on your Status Report page for more information.'); - $this->drupalGet($this->statusReportUrl); - $this->clickLink('confirm the action you wish to take'); - $edit_fields = [ - 'env_change_action' => 'create', - 'name' => '', - 'machine_name' => '', - ]; - $submit_button = 'Save configuration'; - $this->drupalPostForm($this->environmentChangePath, $edit_fields, $submit_button); - $this->assertText($this->acquiaConnectorStrings('site-name-required'), 'Name field is required.'); - $this->assertText($this->acquiaConnectorStrings('site-machine-name-required'), 'Machine name field is required.'); - $edit_fields = [ - 'env_change_action' => 'create', - 'name' => $this->acqtestName, - 'machine_name' => $this->acqtestMachineName, - ]; - $submit_button = 'Save configuration'; - $this->drupalPostForm($this->environmentChangePath, $edit_fields, $submit_button); - $this->assertText($this->acquiaConnectorStrings('first-connection'), 'First connection from this site'); - } - - /** - * Clear the connection data thus simulating a disconnected site. - */ - protected function disconnectSite() { - $config = \Drupal::configFactory()->getEditable('acquia_connector.settings'); - \Drupal::state()->delete('acquia_subscription_data'); - \Drupal::state()->set('acquia_subscription_data', ['active' => FALSE]); - $config->save(); - - $storage = new Storage(); - $storage->setKey(''); - $storage->setIdentifier(''); - - \Drupal::state()->set('acquia_connector_test_request_count', 0); - \Drupal::state()->resetCache(); - } - -} diff --git a/docroot/modules/contrib/acquia_connector/tests/src/Functional/AcquiaConnectorSpiTest.php b/docroot/modules/contrib/acquia_connector/tests/src/Functional/AcquiaConnectorSpiTest.php deleted file mode 100644 index 7855b33b3b..0000000000 --- a/docroot/modules/contrib/acquia_connector/tests/src/Functional/AcquiaConnectorSpiTest.php +++ /dev/null @@ -1,637 +0,0 @@ -randomString(); - } - parent::setUp(); - - // Enable any modules required for the test - // Create and log in our privileged user. - $this->privilegedUser = $this->drupalCreateUser([ - 'administer site configuration', - 'access administration pages', - ]); - $this->drupalLogin($this->privilegedUser); - - // Setup variables. - $this->environmentChangePath = '/admin/config/system/acquia-connector/environment-change'; - $this->credentialsPath = 'admin/config/system/acquia-connector/credentials'; - $this->settingsPath = 'admin/config/system/acquia-connector'; - $this->statusReportUrl = 'admin/reports/status'; - - // Local env. - $config = \Drupal::configFactory()->getEditable('acquia_connector.settings'); - $config->set('spi.server', 'http://mock-spi-server'); - $config->set('spi.ssl_verify', FALSE); - $config->set('spi.ssl_override', TRUE); - // Set mapping for the test variables. - $mapping = $config->get('mapping'); - $mapping['test_variable_1'] = ['state', 'test_variable_1']; - $mapping['test_variable_2'] = ['state', 'test_variable_2']; - $mapping['test_variable_3'] = ['state', 'test_variable_3']; - $config->set('mapping', $mapping); - $config->save(TRUE); - - // Set values for test variables. - \Drupal::state()->set('test_variable_1', 1); - \Drupal::state()->set('test_variable_2', 2); - \Drupal::state()->set('test_variable_3', 3); - - } - - /** - * Helper function for storing UI strings. - * - * @param string $id - * String ID. - * - * @return string - * UI message. - * - * @throws \Exception - */ - private function acquiaSpiStrings($id) { - switch ($id) { - case 'spi-status-text': - return 'SPI data will be sent once every 30 minutes once cron is called'; - - case 'spi-not-sent'; - return 'SPI data has not been sent'; - - case 'spi-send-text'; - return 'manually send SPI data'; - - case 'spi-data-sent': - return 'SPI data sent'; - - case 'spi-data-sent-error': - return 'Error sending SPI data. Consult the logs for more information.'; - - case 'spi-new-def': - return 'There are new checks that will be performed on your site by the Acquia Connector'; - - case 'provide-site-name': - return 'provide a site name'; - - case 'change-env-detected': - return 'A change in your site\'s environment has been detected. SPI data cannot be submitted until this is resolved.'; - - case 'confirm-action': - return 'confirm the action you wish to take'; - - case 'block-site-message': - return 'This site has been disabled from sending profile data to Acquia.'; - - case 'unblock-site': - return 'Enable this site'; - - case 'acquia-hosted': - return 'Your site is now Acquia hosted.'; - - case 'no-acquia-hosted': - return 'Your site is no longer Acquia hosted.'; - - default: - throw new \Exception("Invalid id $id"); - } - } - - /** - * Test Acquia SPI UI. - * - * @throws \Exception - */ - public function testAcquiaSpiUiTests() { - $this->drupalGet($this->statusReportUrl); - $this->assertNoText($this->acquiaSPIStrings('spi-status-text')); - // Connect site on key and id that will error. - $edit_fields = [ - 'acquia_identifier' => $this->acqtestErrorId, - 'acquia_key' => $this->acqtestErrorKey, - ]; - $submit_button = 'Connect'; - $this->drupalPostForm($this->credentialsPath, $edit_fields, $submit_button); - // Even though the credentials are invalid, they should still be set and the - // connection successful. - $this->assertText("Connection successful"); - - // If name and machine name are empty. - $this->drupalGet($this->statusReportUrl); - $this->assertText($this->acquiaSPIStrings('spi-not-sent')); - $this->assertText($this->acquiaSPIStrings('provide-site-name')); - - $edit_fields = [ - 'name' => $this->acqtestName, - 'machine_name' => $this->acqtestMachineName, - ]; - $submit_button = 'Save configuration'; - $this->drupalPostForm($this->settingsPath, $edit_fields, $submit_button); - - // Send SPI data. - $this->drupalGet($this->statusReportUrl); - $this->assertText($this->acquiaSPIStrings('spi-status-text')); - $this->clickLink($this->acquiaSPIStrings('spi-send-text')); - $this->assertNoText($this->acquiaSPIStrings('spi-data-sent')); - $this->assertText($this->acquiaSPIStrings('spi-data-sent-error')); - - // Connect site on non-error key and id. - $this->connectSite(); - $this->drupalGet($this->statusReportUrl); - $this->clickLink($this->acquiaSPIStrings('spi-send-text')); - $this->assertText($this->acquiaSPIStrings('spi-data-sent')); - $this->assertNoText($this->acquiaSPIStrings('spi-not-sent')); - $this->assertText('This is the first connection from this site, it may take awhile for it to appear.'); - - // Machine name change. - $edit_fields = [ - 'name' => $this->acqtestName, - 'machine_name' => $this->acqtestMachineName . '_change', - ]; - $submit_button = 'Save configuration'; - $this->drupalPostForm($this->settingsPath, $edit_fields, $submit_button); - $this->assertText('A change has been detected in your site environment. Please check the Acquia SPI status on your Status Report page for more information.'); - $this->drupalGet($this->statusReportUrl); - $this->clickLink($this->acquiaSPIStrings('confirm-action')); - $this->assertText('Your site machine name changed from ' . $this->acqtestMachineName . ' to ' . $this->acqtestMachineName . '_change' . '.'); - - // Block site. - $edit_fields = [ - 'env_change_action' => 'block', - ]; - - $submit_button = 'Save configuration'; - $this->drupalPostForm($this->environmentChangePath, $edit_fields, $submit_button); - $this->assertText($this->acquiaSPIStrings('block-site-message')); - $this->clickLink($this->acquiaSPIStrings('unblock-site')); - - // Unblock site. - $edit_fields = [ - 'env_change_action[unblock]' => TRUE, - ]; - - $submit_button = 'Save configuration'; - $this->drupalPostForm($this->environmentChangePath, $edit_fields, $submit_button); - $this->assertText('Your site has been enabled and is sending data to Acquia Cloud'); - $this->assertText($this->acquiaSPIStrings('spi-data-sent')); - $this->assertNoText($this->acquiaSPIStrings('spi-not-sent')); - - // Update machine name on existing site. - $this->clickLink($this->acquiaSPIStrings('spi-send-text')); - $this->assertText($this->acquiaSPIStrings('change-env-detected')); - $this->clickLink($this->acquiaSPIStrings('confirm-action')); - - $edit_fields = [ - 'env_change_action' => 'update', - ]; - - $submit_button = 'Save configuration'; - $this->drupalPostForm($this->environmentChangePath, $edit_fields, $submit_button); - - // Name change. - $edit_fields = [ - 'name' => $this->acqtestName . ' change', - 'machine_name' => $this->acqtestMachineName . '_change', - ]; - $submit_button = 'Save configuration'; - $this->drupalPostForm($this->settingsPath, $edit_fields, $submit_button); - $this->drupalGet($this->statusReportUrl); - $this->assertNoText($this->acquiaSPIStrings('spi-not-sent')); - $this->clickLink($this->acquiaSPIStrings('spi-send-text')); - $this->assertText('Site name updated (from ' . $this->acqtestName . ' to ' . $this->acqtestName . ' change).'); - } - - /** - * Test Acquia SPI get. - */ - public function testAcquiaSpiGetTests() { - // Connect site on non-error key and id. - $this->connectSite(); - - $edit_fields = [ - 'name' => $this->acqtestName, - 'machine_name' => $this->acqtestMachineName, - ]; - $submit_button = 'Save configuration'; - $this->drupalPostForm($this->settingsPath, $edit_fields, $submit_button); - - // Test spiControllerTest::get. - $spi = new SpiController(\Drupal::service('acquia_connector.client'), \Drupal::service('config.factory')); - $spi_data = $spi->get(); - $valid = is_array($spi_data); - $this->assertTrue($valid, 'spiController::get returns an array'); - if ($valid) { - foreach ($this->spiDataKeys as $key) { - if (!array_key_exists($key, $spi_data)) { - $valid = FALSE; - break; - } - } - $this->assertTrue($valid, 'Array has expected keys'); - $private_key = \Drupal::service('private_key')->get(); - $this->assertEqual(sha1($private_key), $spi_data['site_key'], 'Site key is sha1 of Drupal private key'); - $this->assertTrue(!empty($spi_data['spi_data_version']), 'SPI data version is set'); - $vars = Json::decode($spi_data['system_vars']); - $this->assertTrue(is_array($vars), 'SPI data system_vars is a JSON-encoded array'); - $this->assertTrue(isset($vars['test_variable_3']), 'test_variable_3 included in SPI data'); - $this->assertTrue(!empty($spi_data['modules']), 'Modules is not empty'); - $modules = [ - 'status', - 'name', - 'version', - 'package', - 'core', - 'project', - 'filename', - 'module_data', - ]; - $diff = array_diff(array_keys($spi_data['modules'][0]), $modules); - $this->assertTrue(empty($diff), 'Module elements have expected keys'); - $diff = array_diff(array_keys($spi_data['platform']), $this->platformKeys); - $this->assertTrue(empty($diff), 'Platform contains expected keys'); - $roles = Json::decode($spi_data['roles']); - $this->assertTrue(is_array($roles), 'Roles is an array'); - $this->assertTrue(isset($roles) && array_key_exists('anonymous', $roles), 'Roles array contains anonymous user'); - } - } - - /** - * Validate Acquia SPI data. - */ - public function testNoObjectInSpiDataTests() { - // Connect site on non-error key and id. - $this->connectSite(); - - $edit_fields = [ - 'name' => $this->acqtestName, - 'machine_name' => $this->acqtestMachineName, - ]; - $submit_button = 'Save configuration'; - $this->drupalPostForm($this->settingsPath, $edit_fields, $submit_button); - - $spi = new SpiController(\Drupal::service('acquia_connector.client'), \Drupal::service('config.factory')); - $spi_data = $spi->get(); - - $this->assertFalse($this->isContainObjects($spi_data), 'SPI data does not contain PHP objects.'); - } - - /** - * Test Acquia SPI send. - */ - public function testAcquiaSpiSendTests() { - // Connect site on invalid credentials. - $edit_fields = [ - 'acquia_identifier' => $this->acqtestErrorId, - 'acquia_key' => $this->acqtestErrorKey, - ]; - $submit_button = 'Connect'; - $this->drupalPostForm($this->credentialsPath, $edit_fields, $submit_button); - - // Attempt to send something. - $client = \Drupal::service('acquia_connector.client'); - // Connect site on valid credentials. - $this->connectSite(); - - // Check that result is an array. - $spi = new SpiController(\Drupal::service('acquia_connector.client'), \Drupal::service('config.factory')); - $spi_data = $spi->get(); - unset($spi_data['spi_def_update']); - $result = $client->sendNspi($this->acqtestId, $this->acqtestKey, $spi_data); - $this->assertTrue(is_array($result), 'SPI update result is an array'); - - // Trigger a validation error on response. - $spi_data['test_validation_error'] = TRUE; - unset($spi_data['spi_def_update']); - $result = $client->sendNspi($this->acqtestId, $this->acqtestKey, $spi_data); - $this->assertFalse($result, 'SPI result is false if validation error.'); - unset($spi_data['test_validation_error']); - - // Trigger a SPI definition update response. - $spi_data['spi_def_update'] = TRUE; - $result = $client->sendNspi($this->acqtestId, $this->acqtestKey, $spi_data); - $this->assertTrue(!empty($result['body']['update_spi_definition']), 'SPI result array has expected "update_spi_definition" key.'); - } - - /** - * Test Acquia SPI update response. - */ - public function testAcquiaSpiUpdateResponseTests() { - $this->connectSite(); - - $edit_fields = [ - 'name' => $this->acqtestName, - 'machine_name' => $this->acqtestMachineName, - ]; - $submit_button = 'Save configuration'; - $this->drupalPostForm($this->settingsPath, $edit_fields, $submit_button); - - $def_timestamp = \Drupal::state()->get('acquia_spi_data.def_timestamp', 0); - $this->assertNotEqual($def_timestamp, 0, 'SPI definition timestamp set'); - $def_vars = \Drupal::state()->get('acquia_spi_data.def_vars', []); - $this->assertTrue(!empty($def_vars), 'SPI definition variable set'); - \Drupal::state()->set('acquia_spi_data.def_waived_vars', ['test_variable_3']); - // Test that new variables are in SPI data. - $spi = new SpiController(\Drupal::service('acquia_connector.client'), \Drupal::service('config.factory')); - $spi_data = $spi->get(); - $vars = Json::decode($spi_data['system_vars']); - $this->assertTrue(!empty($vars['test_variable_1']), 'New variables included in SPI data'); - $this->assertTrue(!isset($vars['test_variable_3']), 'test_variable_3 not included in SPI data'); - } - - /** - * Test Acquia SPI set variables. - */ - public function testAcquiaSpiSetVariablesTests() { - // Connect site on non-error key and id. - $this->connectSite(); - - $edit_fields = [ - 'name' => $this->acqtestName, - 'machine_name' => $this->acqtestMachineName, - ]; - $submit_button = 'Save configuration'; - $this->drupalPostForm($this->settingsPath, $edit_fields, $submit_button); - - $spi = new SpiController(\Drupal::service('acquia_connector.client'), \Drupal::service('config.factory')); - $spi_data = $spi->get(); - $vars = Json::decode($spi_data['system_vars']); - $this->assertTrue(empty($vars['acquia_spi_saved_variables']['variables']), 'Have not saved any variables'); - // Set error reporting so variable is saved. - $edit = [ - 'error_level' => 'verbose', - ]; - $this->drupalPostForm('admin/config/development/logging', $edit, 'Save configuration'); - - // Turn off error reporting. - $set_variables = ['error_level' => 'hide']; - $variables = new VariablesController(); - $variables->setVariables($set_variables); - - $new = \Drupal::config('system.logging')->get('error_level'); - $this->assertTrue($new === 'hide', 'Set error reporting to log only'); - $vars = Json::decode($variables->getVariablesData()); - $this->assertTrue(in_array('error_level', $vars['acquia_spi_saved_variables']['variables']), 'SPI data reports error level was saved'); - $this->assertTrue(isset($vars['acquia_spi_saved_variables']['time']), 'Set time for saved variables'); - - // Attemp to set variable that is not whitelisted. - $current = \Drupal::config('system.site')->get('name'); - $set_variables = ['site_name' => 0]; - $variables->setVariables($set_variables); - $after = \Drupal::config('system.site')->get('name'); - $this->assertIdentical($current, $after, 'Non-whitelisted variable cannot be automatically set'); - $vars = Json::decode($variables->getVariablesData()); - $this->assertFalse(in_array('site_name', $vars['acquia_spi_saved_variables']['variables']), 'SPI data does not include anything about trying to save clean url'); - - // Test override of approved variable list. - \Drupal::configFactory()->getEditable('acquia_connector.settings')->set('spi.set_variables_override', FALSE)->save(); - // Variables controller stores old config. - $variables = new VariablesController(); - $set_variables = ['acquia_spi_set_variables_automatic' => 'test_variable']; - $variables->setVariables($set_variables); - $vars = Json::decode($variables->getVariablesData()); - $this->assertFalse(isset($vars['test_variable']), 'Using default list of approved list of variables'); - \Drupal::configFactory()->getEditable('acquia_connector.settings')->set('spi.set_variables_override', TRUE)->save(); - // Variables controller stores old config. - $variables = new VariablesController(); - $set_variables = ['acquia_spi_set_variables_automatic' => 'test_variable']; - $variables->setVariables($set_variables); - $vars = Json::decode($variables->getVariablesData()); - $this->assertIdentical($vars['acquia_spi_set_variables_automatic'], 'test_variable', 'Altered approved list of variables that can be set'); - } - - /** - * Helper function determines whether given array contains PHP object. - */ - protected function isContainObjects($arr) { - foreach ($arr as $item) { - if (is_object($item)) { - return TRUE; - } - if (is_array($item) && $this->isContainObjects($item)) { - return TRUE; - } - } - return FALSE; - } - - /** - * Helper function connects to valid subscription. - */ - protected function connectSite() { - $edit_fields = [ - 'acquia_identifier' => $this->acqtestId, - 'acquia_key' => $this->acqtestKey, - ]; - $submit_button = 'Connect'; - $this->drupalPostForm($this->credentialsPath, $edit_fields, $submit_button); - } - -} diff --git a/docroot/modules/contrib/acquia_connector/tests/src/Kernel/AcquiaConnectorTestBase.php b/docroot/modules/contrib/acquia_connector/tests/src/Kernel/AcquiaConnectorTestBase.php new file mode 100644 index 0000000000..3553620ae0 --- /dev/null +++ b/docroot/modules/contrib/acquia_connector/tests/src/Kernel/AcquiaConnectorTestBase.php @@ -0,0 +1,154 @@ +installEntitySchema('user'); + // Burn uid:1. + $this->createUser(); + } + + /** + * {@inheritdoc} + */ + protected function tearDown(): void { + foreach ($this->modifiedEnv as $key) { + putenv("$key="); + } + parent::tearDown(); + } + + /** + * Put an environment variable. + * + * @param string $key + * The key. + * @param string|int $value + * The value. + */ + protected function putEnv(string $key, $value): void { + $this->modifiedEnv[] = $key; + putenv("$key=$value"); + } + + /** + * Creates a user, its session, and sets it as the current user. + * + * @return \Drupal\user\UserInterface + * The user. + */ + protected function createUserWithSession(): UserInterface { + $this->container->get('session_manager.metadata_bag')->stampNew(); + $user = $this->createUser(['administer site configuration']); + self::assertNotFalse($user); + $this->container->get('current_user')->setAccount($user); + return $user; + } + + /** + * Passes a request to the HTTP kernel and returns a response. + * + * @param \Symfony\Component\HttpFoundation\Request $request + * The request. + * + * @return \Symfony\Component\HttpFoundation\Response + * The response. + * + * @throws \Exception + */ + protected function doRequest(Request $request): Response { + $response = $this->container->get('http_kernel')->handle($request); + $content = $response->getContent(); + self::assertNotFalse($content); + $this->setRawContent($content); + return $response; + } + + /** + * Get the string URL for a CSRF protected route. + * + * @param \Drupal\Core\Url $url + * The URL. + * + * @return string + * The URL string. + */ + protected function getCsrfUrlString(Url $url): string { + $context = new RenderContext(); + $url = $this->container->get('renderer')->executeInRenderContext($context, function () use ($url) { + return $url->toString(); + }); + $bubbleable_metadata = $context->pop(); + assert($bubbleable_metadata instanceof BubbleableMetadata); + $build = [ + '#plain_text' => $url, + ]; + $bubbleable_metadata->applyTo($build); + return (string) $this->container->get('renderer')->renderPlain($build); + } + + /** + * Populates the oAuth settings with dummy data. + * + * @param array $data + * oAuth settings data. + */ + protected function populateOauthSettings(array $data = []): void { + if (!$data) { + $data = [ + 'access_token' => 'ACCESS_TOKEN', + 'refresh_token' => 'REFRESH_TOKEN', + ]; + } + + $this->container + ->get('keyvalue.expirable') + ->get('acquia_connector') + ->setWithExpire( + 'oauth', + $data, + 5400 + ); + } + +} diff --git a/docroot/modules/contrib/acquia_connector/tests/src/Kernel/AcquiaTelemetryIntegrationTest.php b/docroot/modules/contrib/acquia_connector/tests/src/Kernel/AcquiaTelemetryIntegrationTest.php new file mode 100644 index 0000000000..3c5a68f022 --- /dev/null +++ b/docroot/modules/contrib/acquia_connector/tests/src/Kernel/AcquiaTelemetryIntegrationTest.php @@ -0,0 +1,60 @@ +hasDefinition('acquia.telemetry')) { + $mocked_telemetry = $this->createMock(Telemetry::class); + $mocked_telemetry->expects($this->never())->method('sendTelemetry'); + $mocked_telemetry->expects($this->never())->method('getAcquiaExtensionNames'); + $container->set('acquia.telemetry', $mocked_telemetry); + } + } + + /** + * {@inheritdoc} + */ + protected function setUp(): void { + $this->checkRequirements(); + parent::setUp(); + $this->installSchema('user', ['users_data']); + } + + /** + * Verifies acquia_telemetry's hooks are disabled. + * + * The telemetry service is mocked to not expect to be called, invoking the + * hooks provide the assertion. + */ + public function testHooksDisabled(): void { + $module_handler = $this->container->get('module_handler'); + + $module_handler->invokeAll('cron'); + $modules = ['acquia_foo', 'lightning_bar']; + $module_handler->invokeAll('modules_installed', [$modules]); + $module_handler->invokeAll('modules_uninstalled', [$modules]); + } + +} diff --git a/docroot/modules/contrib/acquia_connector/tests/src/Kernel/AuthServiceTest.php b/docroot/modules/contrib/acquia_connector/tests/src/Kernel/AuthServiceTest.php new file mode 100644 index 0000000000..1824bf30d6 --- /dev/null +++ b/docroot/modules/contrib/acquia_connector/tests/src/Kernel/AuthServiceTest.php @@ -0,0 +1,148 @@ +container->get('acquia_connector.auth_service'); + $url = UrlHelper::parse($sut->getAuthUrl()->toString()); + self::assertEquals( + 'https://accounts.acquia.com/api/auth/oauth/authorize', + $url['path'] + ); + self::assertEquals([ + 'response_type', + 'client_id', + 'redirect_uri', + 'state', + 'code_challenge', + 'code_challenge_method', + ], array_keys($url['query'])); + self::assertEquals('code', $url['query']['response_type']); + self::assertEquals('38357830-bacd-4b4d-a356-f508c6ddecf8', $url['query']['client_id']); + self::assertEquals( + Url::fromRoute('acquia_connector.auth.return') + ->setAbsolute() + ->toString(), + $url['query']['redirect_uri']); + self::assertEquals('S256', $url['query']['code_challenge_method']); + } + + /** + * Tests finalize with a bad state value. + * + * @covers ::finalize + * @covers ::getStateToken + */ + public function testFinalizeBadState(): void { + $this->expectException(\RuntimeException::class); + $this->expectExceptionMessage('Could not verify state'); + $sut = $this->container->get('acquia_connector.auth_service'); + $sut->finalize('FOO', 'BAR'); + } + + /** + * Tests finalize. + * + * @covers ::finalize + * @covers ::getAccessToken + * @covers ::getPkceCode + * @covers ::getStateToken + * + * @dataProvider finalizeData + */ + public function testFinalize(string $code, ?array $access_token): void { + if ($access_token === NULL) { + $this->expectException(ClientException::class); + $this->expectExceptionMessage('Client error: `POST https://accounts.acquia.com/api/auth/oauth/token` resulted in a `400 Bad Request` response: +{"error":"invalid_grant","error_description":"Authorization code doesn\'t exist or is invalid for the client"}'); + } + + $sut = $this->container->get('acquia_connector.auth_service'); + $session_metadata = $this->container->get('session_manager.metadata_bag'); + $session_metadata->stampNew(); + $csrf_token_seed = $session_metadata->getCsrfTokenSeed(); + // Generating this URL clears out the session seed for some reason. + $url = UrlHelper::parse($sut->getAuthUrl()->toString()); + $state = $url['query']['state']; + + $session_metadata->setCsrfTokenSeed($csrf_token_seed); + $sut->finalize($code, $state); + self::assertEquals( + $access_token, + $sut->getAccessToken() + ); + } + + /** + * Test data for ::finalize. + */ + public function finalizeData() { + yield 'success' => [ + 'AUTHORIZATION_SUCCESSFUL', + [ + 'access_token' => 'ACCESS_TOKEN', + 'refresh_token' => 'REFRESH_TOKEN', + ], + ]; + yield 'error' => [ + 'AUTHORIZATION_ERROR', + NULL, + ]; + } + + /** + * Tests cron refresh of access token. + * + * @covers ::cronRefresh + */ + public function testCron(): void { + $this->container + ->get('keyvalue.expirable') + ->get('acquia_connector') + ->setWithExpire( + 'oauth', + [ + 'access_token' => 'ACCESS_TOKEN', + 'refresh_token' => 'REFRESH_TOKEN', + ], + 5400 + ); + + $request_timestamp = $this->container->get('datetime.time')->getRequestTime(); + + $last_refresh_timestamp = $this->container->get('state')->get('acquia_connector.oauth_refresh.timestamp', 0); + self::assertEquals(0, $last_refresh_timestamp); + $sut = $this->container->get('acquia_connector.auth_service'); + $sut->cronRefresh(); + $last_refresh_timestamp = $this->container->get('state')->get('acquia_connector.oauth_refresh.timestamp', 0); + self::assertEquals($request_timestamp, $last_refresh_timestamp); + + self::assertEquals( + [ + 'access_token' => 'ACCESS_TOKEN_REFRESHED', + 'refresh_token' => 'REFRESH_TOKEN_REFRESHED', + ], + $sut->getAccessToken() + ); + } + +} diff --git a/docroot/modules/contrib/acquia_connector/tests/src/Kernel/Client/ClientFactoryTest.php b/docroot/modules/contrib/acquia_connector/tests/src/Kernel/Client/ClientFactoryTest.php new file mode 100644 index 0000000000..b798d266a9 --- /dev/null +++ b/docroot/modules/contrib/acquia_connector/tests/src/Kernel/Client/ClientFactoryTest.php @@ -0,0 +1,50 @@ +populateOauthSettings(); + $client = $this->container->get('acquia_connector.client.factory')->getCloudApiClient(); + $config = $client->getConfig(); + self::assertEquals('https://cloud.acquia.com', (string) $config['base_uri']); + self::assertStringContainsString('AcquiaConnector/', $config['headers']['User-Agent']); + self::assertEquals('application/json, version=2', $config['headers']['Accept']); + } + + /** + * Tests the refresh token and retry middleware. + */ + public function testRefreshRetryMiddleware(): void { + $this->populateOauthSettings([ + 'access_token' => 'ACCESS_TOKEN_RETRY_MIDDLEWARE', + 'refresh_token' => 'REFRESH_TOKEN', + ]); + $client = $this->container->get('acquia_connector.client.factory')->getCloudApiClient(); + $response = $client->get('/test-retry-middleware'); + self::assertEquals(200, $response->getStatusCode()); + } + + /** + * Tests exception if we don't have a token set. + */ + public function testConnectorException(): void { + $this->expectException(ConnectorException::class); + $this->expectExceptionMessage("Missing access token."); + $this->container->get('acquia_connector.client.factory')->getCloudApiClient(); + } + +} diff --git a/docroot/modules/contrib/acquia_connector/tests/src/Kernel/Controller/AuthControllerTest.php b/docroot/modules/contrib/acquia_connector/tests/src/Kernel/Controller/AuthControllerTest.php new file mode 100644 index 0000000000..4db255f037 --- /dev/null +++ b/docroot/modules/contrib/acquia_connector/tests/src/Kernel/Controller/AuthControllerTest.php @@ -0,0 +1,202 @@ +register('testing.acquia_conector_logger', self::class) + ->addTag('logger'); + $container->set('testing.acquia_conector_logger', $this); + } + + /** + * {@inheritdoc} + */ + public function log($level, $message, array $context = []): void { + $message_placeholders = $this->container + ->get('logger.log_message_parser') + ->parseMessagePlaceholders($message, $context); + $message = empty($message_placeholders) ? $message : strtr($message, $message_placeholders); + + $entry = strtr('!severity|!type|!message', [ + '!type' => $context['channel'], + '!request_uri' => $context['request_uri'], + '!severity' => $level, + '!uid' => $context['uid'], + '!message' => strip_tags($message), + ]); + $this->logs[] = $entry; + } + + /** + * {@inheritdoc} + */ + protected function setUp(): void { + parent::setUp(); + $this->createUserWithSession(); + } + + /** + * Tests the ::setup method. + */ + public function testSetup(): void { + $request = Request::create( + Url::fromRoute('acquia_connector.setup_oauth')->toString() + ); + $response = $this->doRequest($request); + self::assertEquals(200, $response->getStatusCode()); + // @note: cannot use generated URL to due generated CSRF token. + $this->assertStringContainsString( + $this->getCsrfUrlString(Url::fromRoute('acquia_connector.auth.begin')), + $this->getRawContent() + ); + $this->assertStringContainsString( + Url::fromRoute('acquia_connector.setup_manual')->toString(), + $this->getRawContent() + ); + } + + /** + * Tests the ::begin method. + */ + public function testBegin(): void { + $request = Request::create( + $this->getCsrfUrlString(Url::fromRoute('acquia_connector.auth.begin')) + ); + $response = $this->doRequest($request); + self::assertEquals(302, $response->getStatusCode()); + self::assertTrue($response->headers->has('Location')); + $url = UrlHelper::parse($response->headers->get('Location')); + self::assertEquals( + 'https://accounts.acquia.com/api/auth/oauth/authorize', + $url['path'] + ); + self::assertEquals([ + 'response_type', + 'client_id', + 'redirect_uri', + 'state', + 'code_challenge', + 'code_challenge_method', + ], array_keys($url['query'])); + self::assertEquals('code', $url['query']['response_type']); + self::assertEquals('38357830-bacd-4b4d-a356-f508c6ddecf8', $url['query']['client_id']); + self::assertEquals( + Url::fromRoute('acquia_connector.auth.return') + ->setAbsolute() + ->toString(), + $url['query']['redirect_uri']); + self::assertEquals('S256', $url['query']['code_challenge_method']); + } + + /** + * Tests the ::return method. + * + * @dataProvider authorizationReturnData + */ + public function testReturn(string $code, string $error = ''): void { + $request = Request::create( + $this->getCsrfUrlString(Url::fromRoute('acquia_connector.auth.begin')) + ); + $response = $this->doRequest($request); + $location = UrlHelper::parse($response->headers->get('Location')); + $state = $location['query']['state'] ?? ''; + + $request = Request::create( + Url::fromRoute('acquia_connector.auth.return')->toString(), + 'GET', + ['code' => $code, 'state' => $state] + ); + $response = $this->doRequest($request); + self::assertEquals(302, $response->getStatusCode()); + self::assertTrue($response->headers->has('Location')); + if ($error === '') { + self::assertEquals( + Url::fromRoute('acquia_connector.setup_configure')->toString(), + $response->headers->get('Location') + ); + } + else { + self::assertEquals( + Url::fromRoute('acquia_connector.setup_oauth')->toString(), + $response->headers->get('Location') + ); + self::assertEquals( + ['We could not retrieve account data, please re-authorize with your Acquia Cloud account. For more information check this link.'], + $this->container->get('messenger')->messagesByType('error') + ); + self::assertEquals( + [$error], + $this->logs + ); + } + } + + public function authorizationReturnData() { + yield 'success' => ['AUTHORIZATION_SUCCESSFUL']; + yield 'error' => [ + 'AUTHORIZATION_ERROR', + '3|acquia_connector|Unable to finalize OAuth handshake with Acquia Cloud: Client error: `POST https://accounts.acquia.com/api/auth/oauth/token` resulted in a `400 Bad Request` response: +{"error":"invalid_grant","error_description":"Authorization code doesn\'t exist or is invalid for the client"}', + ]; + } + + /** + * Tests that ::return fails if the state parameter does not match. + */ + public function testReturnInvalidState(): void { + $request = Request::create( + Url::fromRoute('acquia_connector.auth.return')->toString(), + 'GET', + ['code' => 'AUTHORIZATION_SUCCESSFUL', 'state' => 'foo'] + ); + $response = $this->doRequest($request); + self::assertEquals(302, $response->getStatusCode()); + self::assertEquals( + Url::fromRoute('acquia_connector.setup_oauth')->toString(), + $response->headers->get('Location') + ); + self::assertEquals( + ['We could not retrieve account data, please re-authorize with your Acquia Cloud account. For more information check this link.'], + $this->container->get('messenger')->messagesByType('error') + ); + self::assertEquals( + [ + '3|acquia_connector|Unable to finalize OAuth handshake with Acquia Cloud: Could not verify state', + ], + $this->logs + ); + } + +} diff --git a/docroot/modules/contrib/acquia_connector/tests/src/Kernel/Controller/StatusControllerTest.php b/docroot/modules/contrib/acquia_connector/tests/src/Kernel/Controller/StatusControllerTest.php new file mode 100644 index 0000000000..f240939a2d --- /dev/null +++ b/docroot/modules/contrib/acquia_connector/tests/src/Kernel/Controller/StatusControllerTest.php @@ -0,0 +1,131 @@ +createUserWithSession(); + + $url = $this->getCsrfUrlString(Url::fromRoute('acquia_connector.refresh_status')); + $request = Request::create($url); + $response = $this->doRequest($request); + self::assertEquals(302, $response->getStatusCode()); + self::assertEquals( + Url::fromRoute('system.status')->setAbsolute()->toString(), + $response->headers->get('Location') + ); + } + + /** + * Tests status route. + * + * @param bool $with_page_cache + * Test with page_cache installed or not. + * + * @dataProvider withPageCache + */ + public function testJson(bool $with_page_cache): void { + if ($with_page_cache) { + $this->container->get('module_installer')->install(['page_cache']); + } + $uuid = (new PhpUuid())->generate(); + $state = $this->container->get('state'); + $state->set('acquia_connector.key', $this->randomMachineName()); + $state->set('acquia_connector.identifier', 'ABC-1234'); + $state->set('acquia_connector.application_uuid', $uuid); + + $url = Url::fromRoute('acquia_connector.status', [], [ + 'query' => [ + 'nonce' => 'f00bar', + 'key' => hash('sha1', "$uuid:f00bar"), + ], + ]); + $request = Request::create($url->toString()); + $response = $this->doRequest($request); + self::assertInstanceOf(JsonResponse::class, $response); + self::assertEquals( + [ + 'version' => '1.0', + 'data' => [ + 'maintenance_mode' => FALSE, + 'cache' => $with_page_cache, + 'block_cache' => FALSE, + ], + ], + Json::decode((string) $response->getContent()) + ); + self::assertEquals('must-revalidate, no-cache, private', $response->headers->get('Cache-Control')); + } + + /** + * Data for testing the status response. + * + * @return \Generator + * The test data. + */ + public function withPageCache() { + yield 'page_cache installed' => [TRUE]; + yield 'page_cache uninstalled' => [FALSE]; + } + + /** + * Tests the access method. + * + * @dataProvider accessData + */ + public function testAccess(string $uuid, string $nonce, string $key, AccessResultInterface $result): void { + $state = $this->container->get('state'); + $state->set('acquia_connector.key', $this->randomMachineName()); + $state->set('acquia_connector.identifier', 'ABC-1234'); + $state->set('acquia_connector.application_uuid', $uuid); + + $url = Url::fromRoute('acquia_connector.status', [], [ + 'query' => [ + 'nonce' => $nonce, + 'key' => $key, + ], + ]); + $request = Request::create($url->toString()); + $this->container->get('request_stack')->push($request); + $sut = $this->container->get('class_resolver') + ->getInstanceFromDefinition(StatusController::class); + assert($sut instanceof StatusController); + self::assertEquals($result, $sut->access()); + } + + /** + * Data for access check test. + * + * @return \Generator + * The test data. + */ + public function accessData() { + $uuid = (new PhpUuid())->generate(); + yield 'missing nonce' => [$uuid, '', '', AccessResult::forbidden('Missing nonce.')]; + yield 'missing uuid' => ['', 'f00Bar', '', AccessResult::forbidden('Missing application UUID.')]; + yield 'missing key' => [$uuid, 'f00Bar', '', AccessResult::forbidden('Could not validate key.')]; + yield 'invalid key' => [$uuid, 'f00Bar', 'ddsdfdsfdsdf', AccessResult::forbidden('Could not validate key.')]; + yield 'okay' => [$uuid, 'f00Bar', hash('sha1', "$uuid:f00Bar"), AccessResult::allowed()]; + } + +} diff --git a/docroot/modules/contrib/acquia_connector/tests/src/Kernel/EventSubscriber/AcquiaTelemetryTest.php b/docroot/modules/contrib/acquia_connector/tests/src/Kernel/EventSubscriber/AcquiaTelemetryTest.php new file mode 100644 index 0000000000..6a96dcc301 --- /dev/null +++ b/docroot/modules/contrib/acquia_connector/tests/src/Kernel/EventSubscriber/AcquiaTelemetryTest.php @@ -0,0 +1,122 @@ +config('system.site') + ->set('uuid', (new PhpUuid())->generate()) + ->save(); + } + + /** + * Tests the telemetry events sent. + */ + public function testTelemetry(): void { + $state = $this->container->get('state'); + + $request = Request::create('/'); + $this->container->get('http_kernel')->terminate( + $request, + $this->doRequest($request) + ); + + $events = $state->get('acquia_connector_test.telemetry_events', []); + self::assertIsArray($events); + self::assertCount(1, $events); + $payload = []; + parse_str($events[0], $payload); + self::assertIsString($payload['api_key']); + self::assertIsString('event', $payload['event']); + + $data = Json::decode($payload['event']); + self::assertIsArray($data); + self::assertEquals( + ['event_type', 'user_id', 'event_properties'], + array_keys($data), + ); + self::assertEquals( + ['extensions', 'php', 'drupal'], + array_keys($data['event_properties']) + ); + self::assertContains('acquia_connector', array_keys($data['event_properties']['extensions'])); + self::assertEquals(['version'], array_keys($data['event_properties']['php'])); + self::assertEquals(PHP_VERSION, $data['event_properties']['php']['version']); + self::assertEquals(['version', 'core_enabled'], array_keys($data['event_properties']['drupal'])); + self::assertContains('user', array_keys($data['event_properties']['drupal']['core_enabled'])); + } + + /** + * Tests the telemetry threshold period for sending events. + */ + public function testTelemetryThresholdPeriod(): void { + $state = $this->container->get('state'); + $current_time = time(); + $time = $this->createMock(TimeInterface::class); + $time->method('getCurrentTime')->willReturn( + // The first getCurrentTime() for checks. + $current_time, + // The call to getCurrentTime() for setting the timestamp. + $current_time, + // The second getCurrentTime() for checks. + $current_time + 21600, + // The third getCurrentTime() for checks. + $current_time + 86400, + // The fourth getCurrentTime() for checks. + $current_time + 86401, + // The call to getCurrentTime() for setting the timestamp. + $current_time + 86401 + ); + + $sut = new AcquiaTelemetry( + $this->container->get('extension.list.module'), + $this->container->get('http_client'), + $this->container->get('config.factory'), + $state, + $time + ); + $do_terminate = function () use ($sut) { + $sut->onTerminateResponse(new KernelEvent( + $this->container->get('http_kernel'), + Request::create('/'), + 1 + )); + }; + + $do_terminate(); + self::assertEquals($current_time, $state->get('acquia_connector.telemetry.timestamp')); + self::assertCount(1, $state->get('acquia_connector_test.telemetry_events')); + + $do_terminate(); + self::assertEquals($current_time, $state->get('acquia_connector.telemetry.timestamp')); + self::assertCount(1, $state->get('acquia_connector_test.telemetry_events')); + + $do_terminate(); + self::assertEquals($current_time, $state->get('acquia_connector.telemetry.timestamp')); + self::assertCount(1, $state->get('acquia_connector_test.telemetry_events')); + + $do_terminate(); + self::assertEquals($current_time + 86401, $state->get('acquia_connector.telemetry.timestamp')); + self::assertCount(2, $state->get('acquia_connector_test.telemetry_events')); + } + +} diff --git a/docroot/modules/contrib/acquia_connector/tests/src/Kernel/EventSubscriber/CodeStudioMessageTest.php b/docroot/modules/contrib/acquia_connector/tests/src/Kernel/EventSubscriber/CodeStudioMessageTest.php new file mode 100644 index 0000000000..280f961edb --- /dev/null +++ b/docroot/modules/contrib/acquia_connector/tests/src/Kernel/EventSubscriber/CodeStudioMessageTest.php @@ -0,0 +1,113 @@ +putEnv('AH_SITE_ENVIRONMENT', 'foo'); + $sut = $this->container->get('acquia_connector.kernel_view.codestudio_message'); + $sut->onViewRenderArray(new RequestEvent( + $this->container->get('http_kernel'), + Request::createFromGlobals(), + 1, + )); + $messenger = $this->container->get('messenger'); + self::assertEquals([], $messenger->all()); + } + + /** + * Tests CDE environment message. + * + * @param string $ah_site_env + * The site env. + * @param string $project_id + * The project ID. + * @param string $request_iid + * The request IID. + * @param string $project_path + * The project path. + * @param string $server_url + * The server URL. + * @param string $expected_message + * The expected message. + * + * @dataProvider environmentData + */ + public function testCdeEnvironment(string $ah_site_env, string $project_id, string $request_iid, string $project_path, string $server_url, string $expected_message): void { + $this->putEnv('AH_SITE_ENVIRONMENT', $ah_site_env); + $this->putEnv('CODE_STUDIO_CI_PROJECT_ID', $project_id); + $this->putEnv('CODE_STUDIO_CI_MERGE_REQUEST_IID', $request_iid); + $this->putEnv('CODE_STUDIO_CI_PROJECT_PATH', $project_path); + $this->putEnv('CODE_STUDIO_CI_SERVER_URL', $server_url); + + $sut = $this->container->get('acquia_connector.kernel_view.codestudio_message'); + $sut->onViewRenderArray(new RequestEvent( + $this->container->get('http_kernel'), + Request::createFromGlobals(), + 1 + )); + $messenger = $this->container->get('messenger'); + if ($expected_message === '') { + self::assertEquals([], $messenger->all()); + } + else { + self::assertEquals(['status' => [$expected_message]], $messenger->all()); + } + } + + /** + * Test environment data for subscriptions. + * + * @return iterable + * The test data. + */ + public function environmentData(): iterable { + yield 'not ah' => [ + '', + '', + '', + '', + '', + '', + ]; + yield 'standard ah' => [ + 'prod', + '', + '', + '', + '', + '', + ]; + yield 'CDE missing vars' => [ + 'ode1232', + '', + '', + '', + '', + '', + ]; + yield 'CDE ok' => [ + 'ode1232', + 'SAMPLE_PROJECT_ID', + 'ABC123', + 'path/to/project', + 'https://ci_server_url', + 'This Acquia Continuous Delivery Environment (CDE) was automatically created by Acquia Code Studio for merge request !ABC123 for path/to/project. It will be destroyed when the merge request is closed or merged.', + ]; + } + +} diff --git a/docroot/modules/contrib/acquia_connector/tests/src/Kernel/EventSubscriber/GetSettingsTest.php b/docroot/modules/contrib/acquia_connector/tests/src/Kernel/EventSubscriber/GetSettingsTest.php new file mode 100644 index 0000000000..94095f8c62 --- /dev/null +++ b/docroot/modules/contrib/acquia_connector/tests/src/Kernel/EventSubscriber/GetSettingsTest.php @@ -0,0 +1,202 @@ +dispatchEvent(); + self::assertEquals('core_state', $event->getProvider()); + self::assertEquals('', $event->getSettings()->getIdentifier()); + self::assertEquals('', $event->getSettings()->getSecretKey()); + self::assertFalse($event->getSettings()->isReadonly()); + self::assertEquals('', $event->getSettings()->getApplicationUuid()); + self::assertEquals([], $event->getSettings()->getMetadata()); + } + + /** + * Tests settings from state storage. + */ + public function testFromCoreState(): void { + $uuid = (new PhpUuid())->generate(); + $this->container->get('state')->setMultiple([ + 'acquia_connector.identifier' => 'ABC-1234', + 'acquia_connector.key' => 'TEST_KEY', + 'acquia_connector.application_uuid' => $uuid, + ]); + $event = $this->dispatchEvent(); + self::assertEquals('core_state', $event->getProvider()); + self::assertEquals('ABC-1234', $event->getSettings()->getIdentifier()); + self::assertEquals('TEST_KEY', $event->getSettings()->getSecretKey()); + self::assertFalse($event->getSettings()->isReadonly()); + self::assertEquals($uuid, $event->getSettings()->getApplicationUuid()); + self::assertEquals([], $event->getSettings()->getMetadata()); + } + + /** + * Tests from settings override. + */ + public function testFromCoreSettings(): void { + $uuid = (new PhpUuid())->generate(); + $settings = CoreSettings::getAll(); + $settings['acquia_connector.settings'] = new Settings( + // @todo this impossible to set from $settings['acquia_connector.settings']. + $this->config('acquia_connector.settings'), + 'ABC-1234', + 'TEST_KEY', + $uuid, + ); + new CoreSettings($settings); + $event = $this->dispatchEvent(); + self::assertEquals('core_settings', $event->getProvider()); + self::assertEquals('ABC-1234', $event->getSettings()->getIdentifier()); + self::assertEquals('TEST_KEY', $event->getSettings()->getSecretKey()); + self::assertTrue($event->getSettings()->isReadonly()); + self::assertEquals($uuid, $event->getSettings()->getApplicationUuid()); + self::assertEquals([], $event->getSettings()->getMetadata()); + } + + /** + * Tests with environment variables from AH. + */ + public function testFromAcquiaCloud(): void { + $uuid = (new PhpUuid())->generate(); + $this->putEnv('AH_SITE_ENVIRONMENT', 'test'); + $this->putEnv('AH_SITE_NAME', 'foo'); + $this->putEnv('AH_SITE_GROUP', 'bar'); + $this->putEnv('AH_APPLICATION_UUID', $uuid); + + $settings = CoreSettings::getAll(); + $settings['ah_network_identifier'] = 'ABC-1234'; + $settings['ah_network_key'] = 'TEST_KEY'; + new CoreSettings($settings); + + $event = $this->dispatchEvent(); + self::assertEquals('acquia_cloud', $event->getProvider()); + self::assertEquals('ABC-1234', $event->getSettings()->getIdentifier()); + self::assertEquals('TEST_KEY', $event->getSettings()->getSecretKey()); + self::assertTrue($event->getSettings()->isReadonly()); + self::assertEquals($uuid, $event->getSettings()->getApplicationUuid()); + self::assertEquals([ + 'AH_SITE_ENVIRONMENT' => 'test', + 'AH_SITE_NAME' => 'foo', + 'AH_SITE_GROUP' => 'bar', + 'AH_APPLICATION_UUID' => $uuid, + 'ah_network_identifier' => 'ABC-1234', + 'ah_network_key' => 'TEST_KEY', + ], $event->getSettings()->getMetadata()); + } + + /** + * Tests with environment variables from Cloud IDE. + */ + public function testFromAcquiaCloudIde(): void { + $uuid = (new PhpUuid())->generate(); + $this->putEnv('AH_SITE_ENVIRONMENT', 'ide'); + $this->putEnv('AH_SITE_NAME', 'foo'); + $this->putEnv('AH_SITE_GROUP', 'bar'); + $this->putEnv('AH_APPLICATION_UUID', $uuid); + + $event = $this->dispatchEvent(); + self::assertEquals('core_state', $event->getProvider()); + self::assertEquals('', $event->getSettings()->getIdentifier()); + self::assertEquals('', $event->getSettings()->getSecretKey()); + self::assertFalse($event->getSettings()->isReadonly()); + self::assertEquals('', $event->getSettings()->getApplicationUuid()); + self::assertEquals([], $event->getSettings()->getMetadata()); + } + + /** + * Tests with environment variables from on-demand environment. + */ + public function testFromAcquiaCloudOde(): void { + $uuid = (new PhpUuid())->generate(); + $this->putEnv('AH_SITE_ENVIRONMENT', 'ode'); + $this->putEnv('AH_SITE_NAME', 'foo'); + $this->putEnv('AH_SITE_GROUP', 'bar'); + $this->putEnv('AH_APPLICATION_UUID', $uuid); + + $event = $this->dispatchEvent(); + self::assertEquals('core_state', $event->getProvider()); + self::assertEquals('', $event->getSettings()->getIdentifier()); + self::assertEquals('', $event->getSettings()->getSecretKey()); + self::assertFalse($event->getSettings()->isReadonly()); + self::assertEquals('', $event->getSettings()->getApplicationUuid()); + self::assertEquals([], $event->getSettings()->getMetadata()); + } + + /** + * Tests with combination of values. + */ + public function testWithCombination(): void { + $settings = CoreSettings::getAll(); + $settings['acquia_connector.settings'] = new Settings( + // Impossible to set from $settings['acquia_connector.settings']. + $this->config('acquia_connector.settings'), + 'ABC-1234', + 'TEST_KEY', + '', + ); + $settings['ah_network_identifier'] = 'ABC-1234'; + $settings['ah_network_key'] = 'TEST_KEY'; + new CoreSettings($settings); + + $this->putEnv('AH_SITE_ENVIRONMENT', 'test'); + $this->putEnv('AH_SITE_NAME', 'foo'); + $this->putEnv('AH_SITE_GROUP', 'bar'); + $this->putEnv('AH_APPLICATION_UUID', '2847ba56-cb57-4d37-85f1-baa69ff0c604'); + + $this->container->get('state')->setMultiple([ + 'acquia_connector.identifier' => 'ABC-1234', + 'acquia_connector.key' => 'TEST_KEY', + ]); + + $event = $this->dispatchEvent(); + self::assertEquals('acquia_cloud', $event->getProvider()); + self::assertEquals('ABC-1234', $event->getSettings()->getIdentifier()); + self::assertEquals('TEST_KEY', $event->getSettings()->getSecretKey()); + self::assertTrue($event->getSettings()->isReadonly()); + self::assertEquals([ + 'AH_SITE_ENVIRONMENT' => 'test', + 'AH_SITE_NAME' => 'foo', + 'AH_SITE_GROUP' => 'bar', + 'AH_APPLICATION_UUID' => '2847ba56-cb57-4d37-85f1-baa69ff0c604', + 'ah_network_identifier' => 'ABC-1234', + 'ah_network_key' => 'TEST_KEY', + ], $event->getSettings()->getMetadata()); + } + + /** + * Dispatches a settings event. + * + * @return \Drupal\acquia_connector\Event\AcquiaSubscriptionSettingsEvent + * The dispatched event. + */ + private function dispatchEvent(): AcquiaSubscriptionSettingsEvent { + $event = new AcquiaSubscriptionSettingsEvent($this->container->get('config.factory')); + $this->container->get('event_dispatcher')->dispatch($event, AcquiaConnectorEvents::GET_SETTINGS); + return $event; + } + +} diff --git a/docroot/modules/contrib/acquia_connector/tests/src/Kernel/Form/ConfigureApplicationFormTest.php b/docroot/modules/contrib/acquia_connector/tests/src/Kernel/Form/ConfigureApplicationFormTest.php new file mode 100644 index 0000000000..e14fae9489 --- /dev/null +++ b/docroot/modules/contrib/acquia_connector/tests/src/Kernel/Form/ConfigureApplicationFormTest.php @@ -0,0 +1,239 @@ +createUserWithSession(); + + $request = Request::create( + Url::fromRoute('acquia_connector.setup_configure')->toString() + ); + $response = $this->doRequest($request); + self::assertEquals(302, $response->getStatusCode()); + self::assertEquals( + Url::fromRoute('acquia_connector.setup_oauth')->toString(), + $response->headers->get('Location') + ); + self::assertEquals( + ['We could not retrieve account data, please re-authorize with your Acquia Cloud account. For more information check this link.'], + $this->container->get('messenger')->messagesByType('error') + ); + } + + public function testWithErrorGettingApplicationKeys(): void { + $this->createUserWithSession(); + + $this->setAccessToken('ACCESS_TOKEN_ERROR_GETTING_APPLICATION_KEYS'); + + $request = Request::create( + Url::fromRoute('acquia_connector.setup_configure')->toString() + ); + $response = $this->doRequest($request); + self::assertEquals(303, $response->getStatusCode()); + self::assertEquals( + Url::fromRoute('acquia_connector.setup_oauth')->setAbsolute()->toString(), + $response->headers->get('Location') + ); + self::assertEquals( + ['We encountered an error when retrieving information for the selected application. Try logging into Acquia Cloud again.'], + $this->container->get('messenger')->messagesByType('error') + ); + } + + public function testWithNoApplications(): void { + $this->createUserWithSession(); + + $this->setAccessToken('ACCESS_TOKEN_NO_APPLICATIONS'); + + $request = Request::create( + Url::fromRoute('acquia_connector.setup_configure')->toString() + ); + $response = $this->doRequest($request); + self::assertEquals(302, $response->getStatusCode()); + self::assertEquals( + Url::fromRoute('acquia_connector.setup_oauth')->toString(), + $response->headers->get('Location') + ); + self::assertEquals( + ['No subscriptions were found for your account.'], + $this->container->get('messenger')->messagesByType('error') + ); + } + + public function testApplicationCloudMismatch(): void { + $this->setAccessToken('ACCESS_TOKEN_ONE_APPLICATION'); + + // Emulate Acquia Cloud -- Hardcode the uuid to ensure it is a mismatch. + $uuid = 'a6ce3b66-febf-487f-8d35-2802c1964a55'; + $this->putEnv('AH_SITE_ENVIRONMENT', 'test'); + $this->putEnv('AH_SITE_NAME', 'foo'); + $this->putEnv('AH_SITE_GROUP', 'bar'); + $this->putEnv('AH_APPLICATION_UUID', $uuid); + $settings = CoreSettings::getAll(); + $settings['ah_network_identifier'] = 'WRNG-12345'; + $settings['ah_network_key'] = 'TEST_KEY'; + new CoreSettings($settings); + + // User session must be made AFTER we setup cloud environment variables. + $this->createUserWithSession(); + + $request = Request::create( + Url::fromRoute('acquia_connector.setup_configure')->toString() + ); + $response = $this->doRequest($request); + self::assertEquals(302, $response->getStatusCode()); + self::assertEquals( + Url::fromRoute('acquia_connector.setup_oauth')->toString(), + $response->headers->get('Location') + ); + self::assertEquals( + ['Unable to set Subscription: User does not have access to this application.'], + $this->container->get('messenger')->messagesByType('error') + ); + } + + public function testWithOneApplication(): void { + $this->createUserWithSession(); + + $this->setAccessToken('ACCESS_TOKEN_ONE_APPLICATION'); + + $request = Request::create( + Url::fromRoute('acquia_connector.setup_configure')->toString() + ); + $response = $this->doRequest($request); + self::assertEquals(303, $response->getStatusCode()); + self::assertEquals( + Url::fromRoute('acquia_connector.settings')->setAbsolute()->toString(), + $response->headers->get('Location') + ); + self::assertEquals( + ['status' => ['

Connection successful!

You are now connected to Acquia Cloud.']], + $this->container->get('messenger')->all() + ); + + self::assertEquals( + [ + 'active' => TRUE, + 'href' => '', + 'uuid' => 'a47ac10b-58cc-4372-a567-0e02b2c3d470', + 'subscription_name' => 'Sample subscription', + 'expiration_date' => [ + 'value' => '2030-05-12T00:00:00', + ], + 'product' => [ + 'view' => 'Acquia Network', + ], + 'search_service_enabled' => 1, + 'gratis' => FALSE, + 'application' => [ + 'id' => 1234, + 'uuid' => 'a47ac10b-58cc-4372-a567-0e02b2c3d470', + 'name' => 'Sample application 1', + 'subscription' => [ + 'uuid' => 'f47ac10b-58cc-4372-a567-0e02b2c3d470', + 'name' => 'Sample subscription', + ], + ], + ], + $this->container->get('acquia_connector.subscription')->getSubscription() + ); + } + + public function testWithMultipleApplications(): void { + $this->createUserWithSession(); + + $this->setAccessToken('ACCESS_TOKEN_MULTIPLE_APPLICATIONS'); + + $request = Request::create( + Url::fromRoute('acquia_connector.setup_configure')->toString() + ); + $response = $this->doRequest($request); + self::assertEquals(200, $response->getStatusCode()); + $request = Request::create( + Url::fromRoute('acquia_connector.setup_configure')->toString(), + 'POST', + [ + 'application' => 'a47ac10b-58cc-4372-a567-0e02b2c3d470', + // @phpstan-ignore-next-line + 'form_build_id' => (string) $this->cssSelect('input[name="form_build_id"]')[0]->attributes()->value[0], + // @phpstan-ignore-next-line + 'form_token' => (string) $this->cssSelect('input[name="form_token"]')[0]->attributes()->value[0], + // @phpstan-ignore-next-line + 'form_id' => (string) $this->cssSelect('input[name="form_id"]')[0]->attributes()->value[0], + 'op' => 'Save', + ]); + $response = $this->doRequest($request); + self::assertEquals(303, $response->getStatusCode(), $this->content); + self::assertEquals( + Url::fromRoute('acquia_connector.settings')->setAbsolute()->toString(), + $response->headers->get('Location') + ); + self::assertEquals( + ['status' => ['

Connection successful!

You are now connected to Acquia Cloud.']], + $this->container->get('messenger')->all() + ); + + self::assertEquals( + [ + 'active' => TRUE, + 'href' => '', + 'uuid' => 'a47ac10b-58cc-4372-a567-0e02b2c3d470', + 'subscription_name' => 'Sample subscription', + 'expiration_date' => [ + 'value' => '2030-05-12T00:00:00', + ], + 'product' => [ + 'view' => 'Acquia Network', + ], + 'search_service_enabled' => 1, + 'gratis' => FALSE, + 'application' => [ + 'id' => 1234, + 'uuid' => 'a47ac10b-58cc-4372-a567-0e02b2c3d470', + 'name' => 'Sample application 1', + 'subscription' => [ + 'uuid' => 'f47ac10b-58cc-4372-a567-0e02b2c3d470', + 'name' => 'Sample subscription', + ], + ], + ], + $this->container->get('acquia_connector.subscription')->getSubscription() + ); + } + + /** + * Sets the access token. + * + * @param string $token + * The token. + */ + private function setAccessToken(string $token): void { + $this->container + ->get('keyvalue.expirable') + ->get('acquia_connector') + ->setWithExpire( + 'oauth', + [ + 'access_token' => $token, + 'refresh_token' => 'REFRESH_TOKEN', + ], + 5400 + ); + } + +} diff --git a/docroot/modules/contrib/acquia_connector/tests/src/Kernel/Form/CredentialFormTest.php b/docroot/modules/contrib/acquia_connector/tests/src/Kernel/Form/CredentialFormTest.php new file mode 100644 index 0000000000..a5147e6a26 --- /dev/null +++ b/docroot/modules/contrib/acquia_connector/tests/src/Kernel/Form/CredentialFormTest.php @@ -0,0 +1,77 @@ +createUserWithSession(); + $request = Request::create( + Url::fromRoute('acquia_connector.setup_manual')->toString() + ); + $response = $this->doRequest($request); + self::assertEquals(200, $response->getStatusCode()); + + $request = Request::create( + Url::fromRoute('acquia_connector.setup_manual')->toString(), + 'POST', + [ + 'identifier' => $identifier, + 'key' => $key, + 'application_uuid' => $application_uuid, + // @phpstan-ignore-next-line + 'form_build_id' => (string) $this->cssSelect('input[name="form_build_id"]')[0]->attributes()->value[0], + // @phpstan-ignore-next-line + 'form_token' => (string) $this->cssSelect('input[name="form_token"]')[0]->attributes()->value[0], + // @phpstan-ignore-next-line + 'form_id' => (string) $this->cssSelect('input[name="form_id"]')[0]->attributes()->value[0], + 'op' => 'Connect', + ]); + $response = $this->doRequest($request); + self::assertEquals(303, $response->getStatusCode(), var_export($response->getContent(), TRUE)); + self::assertEquals( + Url::fromRoute('acquia_connector.settings')->setAbsolute()->toString(), + $response->headers->get('Location') + ); + self::assertEquals( + ['status' => ['

Connection successful!

You are now connected to Acquia Cloud. Please enter a name for your site to begin sending profile data.']], + $this->container->get('messenger')->all() + ); + } + + /** + * The test data. + * + * @return \Generator + * The data. + */ + public function credentialData() { + yield ['ABC', 'CDE', 'a47ac10b-58cc-4372-a567-0e02b2c3d470']; + } + +} diff --git a/docroot/modules/contrib/acquia_connector/tests/src/Kernel/Form/SettingsFormTest.php b/docroot/modules/contrib/acquia_connector/tests/src/Kernel/Form/SettingsFormTest.php new file mode 100644 index 0000000000..c9e625ccc9 --- /dev/null +++ b/docroot/modules/contrib/acquia_connector/tests/src/Kernel/Form/SettingsFormTest.php @@ -0,0 +1,227 @@ +createUserWithSession(); + } + + /** + * {@inheritdoc} + */ + public function register(ContainerBuilder $container) { + parent::register($container); + $container + ->register('testing.acquia_connector_subscriber', self::class) + ->addTag('event_subscriber'); + $container->set('testing.acquia_connector_subscriber', $this); + + } + + /** + * {@inheritdoc} + */ + public static function getSubscribedEvents() { + return [ + AcquiaConnectorEvents::ACQUIA_PRODUCT_SETTINGS => 'onProductSettings', + AcquiaConnectorEvents::ALTER_PRODUCT_SETTINGS_SUBMIT => 'onAlterProductSettingsSubmit', + AcquiaConnectorEvents::GET_SUBSCRIPTION => 'onGetSubscription', + ]; + } + + /** + * Event handler for ACQUIA_PRODUCT_SETTINGS. + * + * @param \Drupal\acquia_connector\Event\AcquiaProductSettingsEvent $event + * The event. + */ + public function onProductSettings(AcquiaProductSettingsEvent $event) { + $form['api_key'] = [ + '#type' => 'textfield', + '#title' => 'API Key', + '#default_value' => '', + '#required' => TRUE, + ]; + $form['api_url'] = [ + '#type' => 'hidden', + '#title' => 'API URL', + '#default_value' => '', + ]; + $event->setProductSettings( + 'Acquia Example Product', + 'acquia_example_product', + $form + ); + } + + /** + * Event handler for ALTER_PRODUCT_SETTINGS_SUBMIT. + * + * @param \Drupal\acquia_connector\Event\AcquiaProductSettingsEvent $event + * The event. + */ + public function onAlterProductSettingsSubmit(AcquiaProductSettingsEvent $event) { + $form_state = $event->getFormState(); + $form_state['product_settings']['acquia_example_product']['settings']['api_url'] = 'https://example.acquia.com'; + $event->alterProductSettingsSubmit($form_state); + } + + /** + * Event handler for GET_SUBSCRIPTION. + * + * @param \Drupal\acquia_connector\Event\AcquiaSubscriptionDataEvent $event + * The event. + */ + public function onGetSubscription(AcquiaSubscriptionDataEvent $event) { + $config = $event->getConfig('acquia_connector.settings'); + $data = $config->get('third_party_settings.acquia_example_product'); + + $subscription_data = $event->getData(); + $subscription_data['acquia_example_product'] = $data; + $event->setData($subscription_data); + } + + /** + * Tests redirect from settings to auth if no subscription data. + */ + public function testRedirectIfNoActiveSubscription(): void { + $request = Request::create( + Url::fromRoute('acquia_connector.settings')->toString() + ); + $response = $this->doRequest($request); + self::assertEquals(302, $response->getStatusCode()); + self::assertEquals( + Url::fromRoute('acquia_connector.setup_oauth')->toString(), + $response->headers->get('Location') + ); + } + + /** + * Tests the site name generated if Acquia Cloud hosting. + */ + public function testSiteNameGeneratedOnAcquiaCloud(): void { + $this->container->get('state')->setMultiple([ + 'acquia_connector.identifier' => 'ABC', + 'acquia_connector.key' => 'DEF', + 'acquia_connector.application_uuid' => 'a47ac10b-58cc-4372-a567-0e02b2c3d470', + 'spi.site_name' => 'old_stored_site_name', + ]); + $this->container->get('acquia_connector.subscription')->populateSettings(); + + $request = Request::create( + Url::fromRoute('acquia_connector.settings')->toString(), + 'GET', + [], + [], + [], + [ + 'AH_SITE_ENVIRONMENT' => 'test', + 'AH_SITE_NAME' => 'foobar', + ] + ); + $response = $this->doRequest($request); + self::assertEquals(200, $response->getStatusCode()); + $this->assertRaw('

Connected to Acquia

'); + $site_name = (string) $this->cssSelect('input[name="name"]')[0]->attributes()->value[0]; + self::assertStringContainsString('0e02b2c3d470: test', $site_name); + } + + /** + * Tests the form with subscription data available. + */ + public function testWithSubscriptionData(): void { + $this->container->get('state')->setMultiple([ + 'acquia_connector.identifier' => 'ABC', + 'acquia_connector.key' => 'DEF', + 'acquia_connector.application_uuid' => 'a47ac10b-58cc-4372-a567-0e02b2c3d470', + 'spi.site_name' => 'old_stored_site_name', + ]); + $this->container->get('acquia_connector.subscription')->populateSettings(); + + $request = Request::create( + Url::fromRoute('acquia_connector.settings')->toString() + ); + $response = $this->doRequest($request); + self::assertEquals(200, $response->getStatusCode()); + $this->assertRaw('

Connected to Acquia

'); + $site_name = (string) $this->cssSelect('input[name="name"]')[0]->attributes()->value[0]; + self::assertStringContainsString('0e02b2c3d470: localhost_', $site_name); + $machine_name = (string) $this->cssSelect('input[name="machine_name"]')[0]->attributes()->value[0]; + self::assertStringContainsString('0e02b2c3d470__localhost_', $machine_name); + self::assertCount(1, $this->cssSelect('input[name="product_settings[acquia_example_product][settings][api_key]"]'), var_export($this->getRawContent(), TRUE)); + + $request = Request::create( + Url::fromRoute('acquia_connector.settings')->toString(), + 'POST', + [ + 'name' => $site_name, + 'machine_name' => $machine_name, + 'product_settings' => [ + 'acquia_example_product' => [ + 'settings' => [ + 'api_key' => 'ABC123', + ], + ], + ], + // @phpstan-ignore-next-line + 'form_build_id' => (string) $this->cssSelect('input[name="form_build_id"]')[0]->attributes()->value[0], + // @phpstan-ignore-next-line + 'form_token' => (string) $this->cssSelect('input[name="form_token"]')[0]->attributes()->value[0], + // @phpstan-ignore-next-line + 'form_id' => (string) $this->cssSelect('input[name="form_id"]')[0]->attributes()->value[0], + 'op' => 'Connect', + ]); + $response = $this->doRequest($request); + self::assertEquals(303, $response->getStatusCode(), var_export($response->getContent(), TRUE)); + self::assertEquals( + ['status' => ['The configuration options have been saved.']], + $this->container->get('messenger')->all() + ); + $config = $this->config('acquia_connector.settings')->get('third_party_settings'); + self::assertEquals([ + 'acquia_example_product' => [ + 'api_key' => 'ABC123', + 'api_url' => 'https://example.acquia.com', + ], + ], $config); + + self::assertEquals([ + 'active' => TRUE, + 'href' => '', + 'uuid' => 'a47ac10b-58cc-4372-a567-0e02b2c3d470', + 'subscription_name' => '', + 'expiration_date' => '', + 'product' => [ + 'view' => 'Acquia Network', + ], + 'search_service_enabled' => 1, + 'gratis' => FALSE, + 'acquia_example_product' => [ + 'api_key' => 'ABC123', + 'api_url' => 'https://example.acquia.com', + ], + ], $this->container->get('acquia_connector.subscription')->getSubscription()); + } + +} diff --git a/docroot/modules/contrib/acquia_connector/tests/src/Kernel/HelpIntegrationTest.php b/docroot/modules/contrib/acquia_connector/tests/src/Kernel/HelpIntegrationTest.php new file mode 100644 index 0000000000..c88f642305 --- /dev/null +++ b/docroot/modules/contrib/acquia_connector/tests/src/Kernel/HelpIntegrationTest.php @@ -0,0 +1,46 @@ +createMock(RouteMatchInterface::class); + $route_match->method('getRouteName')->willReturn('help.page.acquia_connector'); + $block = new HelpBlock( + [], + 'help_block', + [ + 'provider' => 'help', + ], + Request::create('/'), + $this->container->get('module_handler'), + $route_match + ); + $build = $block->build(); + $output = $this->container->get('renderer')->renderPlain($build); + $this->setRawContent($output); + $this->assertRaw('

Acquia Connector

'); + $this->assertLinkByHref('https://docs.acquia.com/cloud-platform/onboarding/install/'); + } + +} diff --git a/docroot/modules/contrib/acquia_connector/tests/src/Kernel/Migrate/d7/MigrateAcquiaConnectorConfigurationTest.php b/docroot/modules/contrib/acquia_connector/tests/src/Kernel/Migrate/d7/MigrateAcquiaConnectorConfigurationTest.php new file mode 100644 index 0000000000..028bbde594 --- /dev/null +++ b/docroot/modules/contrib/acquia_connector/tests/src/Kernel/Migrate/d7/MigrateAcquiaConnectorConfigurationTest.php @@ -0,0 +1,152 @@ + [ + 'debug' => FALSE, + 'cron_interval' => 30, + 'cron_interval_override' => 0, + 'hide_signup_messages' => 0, + ], + ]; + + /** + * Expected State Variable. + * + * Note, Acquia uses state for site-specific subscription data. However, the + * spi data is dynamically generated and doesn't need migration: + * 'def_vars', + * 'def_waived_vars', + * 'def_timestamp', + * 'new_optional_data'. + * + * @var array[] + */ + protected $expectedState = [ + 'acquia_subscription_data' => [ + 'timestamp' => 1234567890, + 'active' => '1', + 'href' => 'https://insight.acquia.com/node/uuid/1b2c3456-a123-456d-a789-e1234567895d/dashboard', + 'uuid' => '1b2c3456-a123-456d-a789-e1234567895d', + 'subscription_name' => 'Test', + 'expiration_date' => [ + 'value' => '2042-12-30T00:00:00', + ], + 'product' => [ + 'view' => 'Acquia Network', + ], + 'derived_key_salt' => '1234e56789979a1d8ae123cd321a12c7', + 'update_service' => '1', + 'search_service_enabled' => 1, + 'update' => [], + 'heartbeat_data' => [ + 'acquia_lift' => [ + 'status' => FALSE, + 'decision' => [ + 'public_key' => '', + 'private_key' => '', + ], + 'profile' => [ + 'account_name' => '', + 'hostname' => '', + 'public_key' => '', + 'secret_key' => '', + 'js_path' => '', + ], + ], + 'search_service_enabled' => 1, + 'search_cores' => [ + 0 => [ + 'balancer' => 'useast1-c1.acquia-search.com', + 'core_id' => 'TEST-123456', + ], + 1 => [ + 'balancer' => 'useast1-c1.acquia-search.com', + 'core_id' => 'TEST-123456.prod.v2', + ], + 2 => [ + 'balancer' => 'useast1-c1.acquia-search.com', + 'core_id' => 'TEST-123456.test.v2', + ], + 3 => [ + 'balancer' => 'useast1-c1.acquia-search.com', + 'core_id' => 'TEST-123456.dev.v2', + ], + 4 => [ + 'balancer' => 'useast1-c26.acquia-search.com', + 'core_id' => 'TEST-123456.prod.default', + ], + 5 => [ + 'balancer' => 'useast1-c26.acquia-search.com', + 'core_id' => 'TEST-123456.test.default', + ], + 6 => [ + 'balancer' => 'useast1-c26.acquia-search.com', + 'core_id' => 'TEST-123456.dev.default', + ], + ], + 'search_service_colony' => 'useast1-c1.acquia-search.com', + ], + ], + ]; + + /** + * {@inheritdoc} + */ + protected function setUp(): void { + parent::setUp(); + if (version_compare(\Drupal::VERSION, '9.3', '>=')) { + // phpcs:ignore + $path = \Drupal::service('extension.path.resolver')->getPath('module', 'acquia_connector'); + } + else { + // @phpstan-ignore-next-line + $path = drupal_get_path('module', 'acquia_connector'); + } + $this->loadFixture(implode(DIRECTORY_SEPARATOR, [ + DRUPAL_ROOT, + $path, + 'tests', + 'fixtures', + 'drupal7.php', + ])); + + $migrations = [ + 'd7_acquia_connector_settings', + 'd7_acquia_connector_subscription_data', + ]; + $this->executeMigrations($migrations); + } + + /** + * Tests that all expected configuration gets migrated. + */ + public function testConfigurationMigration() { + // Test Config. + foreach ($this->expectedConfig as $config_id => $values) { + $actual = \Drupal::config($config_id)->get(); + $this->assertSame($values, $actual); + } + // Test State. + foreach ($this->expectedState as $state_id => $values) { + $actual = \Drupal::state()->get($state_id); + $this->assertSame($values, $actual); + } + } + +} diff --git a/docroot/modules/contrib/acquia_connector/tests/src/Kernel/RequirementsTest.php b/docroot/modules/contrib/acquia_connector/tests/src/Kernel/RequirementsTest.php new file mode 100644 index 0000000000..4fb9ca15df --- /dev/null +++ b/docroot/modules/contrib/acquia_connector/tests/src/Kernel/RequirementsTest.php @@ -0,0 +1,44 @@ +setParameter('install_profile', 'standard'); + } + + /** + * Test that we never get REQUIREMENT_ERROR for runtime PHP requirement. + */ + public function testPhpEol(): void { + // Preload install files for system and acquia_connector to ensure their + // hooks are discovered. They are not loaded in `drupal_load_updates` due to + // the fact module schema is not registered in Kernel tests. + $module_handler = $this->container->get('module_handler'); + $module_handler->loadInclude('system', 'install'); + $module_handler->loadInclude('acquia_connector', 'install'); + + $requirements = $this->container->get('system.manager')->listRequirements(); + + self::assertArrayHasKey('php', $requirements); + // Severity is only set if there is an issue with PHP. If it is set, ensure + // it is not set to REQUIREMENT_ERROR. + if (isset($requirements['php']['severity'])) { + self::assertNotEquals(REQUIREMENT_ERROR, $requirements['php']['severity']); + } + } + +} diff --git a/docroot/modules/contrib/acquia_connector/tests/src/Kernel/SubscriptionRefreshTest.php b/docroot/modules/contrib/acquia_connector/tests/src/Kernel/SubscriptionRefreshTest.php new file mode 100644 index 0000000000..8cd83da5f0 --- /dev/null +++ b/docroot/modules/contrib/acquia_connector/tests/src/Kernel/SubscriptionRefreshTest.php @@ -0,0 +1,167 @@ +installSchema('user', ['users_data']); + $this->installConfig(['system']); + $this->config('system.site') + ->set('uuid', (new PhpUuid())->generate()) + ->save(); + } + + /** + * Tests acquia_connector_modules_installed(). + */ + public function testModulesInstalled(): void { + $this->container->get('state')->setMultiple([ + 'acquia_connector.identifier' => 'ABC', + 'acquia_connector.key' => 'DEF', + 'acquia_connector.application_uuid' => 'a47ac10b-58cc-4372-a567-0e02b2c3d470', + ]); + $this->container->get('acquia_connector.subscription')->populateSettings(); + + self::assertEquals( + [ + 'active' => TRUE, + 'href' => '', + 'uuid' => 'a47ac10b-58cc-4372-a567-0e02b2c3d470', + 'subscription_name' => '', + 'expiration_date' => '', + 'product' => [ + 'view' => 'Acquia Network', + ], + 'search_service_enabled' => 1, + 'gratis' => FALSE, + ], + $this->container->get('acquia_connector.subscription')->getSubscription() + ); + + $this->container->get('module_installer')->install([ + 'acquia_connector_subdata_test', + ]); + + self::assertEquals( + [ + 'active' => TRUE, + 'href' => '', + 'uuid' => 'a47ac10b-58cc-4372-a567-0e02b2c3d470', + 'subscription_name' => '', + 'expiration_date' => '', + 'product' => [ + 'view' => 'Acquia Network', + 'acquia_subdata_product' => [ + 'foo' => 'bar', + 'data_from_subscription' => 'a47ac10b-58cc-4372-a567-0e02b2c3d470', + ], + ], + 'search_service_enabled' => 1, + 'gratis' => FALSE, + ], + $this->container->get('acquia_connector.subscription')->getSubscription() + ); + } + + /** + * Tests acquia_connector_modules_uninstalled(). + */ + public function testModuleUninstalled(): void { + $this->container->get('state')->setMultiple([ + 'acquia_connector.identifier' => 'ABC', + 'acquia_connector.key' => 'DEF', + 'acquia_connector.application_uuid' => 'a47ac10b-58cc-4372-a567-0e02b2c3d470', + ]); + $this->container->get('acquia_connector.subscription')->populateSettings(); + + $this->container->get('module_installer')->install([ + 'acquia_connector_subdata_test', + ]); + + self::assertEquals( + [ + 'active' => TRUE, + 'href' => '', + 'uuid' => 'a47ac10b-58cc-4372-a567-0e02b2c3d470', + 'subscription_name' => '', + 'expiration_date' => '', + 'product' => [ + 'view' => 'Acquia Network', + 'acquia_subdata_product' => [ + 'foo' => 'bar', + 'data_from_subscription' => 'a47ac10b-58cc-4372-a567-0e02b2c3d470', + ], + ], + 'search_service_enabled' => 1, + 'gratis' => FALSE, + + ], + $this->container->get('acquia_connector.subscription')->getSubscription() + ); + + $this->container->get('module_installer')->uninstall([ + 'acquia_connector_subdata_test', + ]); + + self::assertEquals( + [ + 'active' => TRUE, + 'href' => '', + 'uuid' => 'a47ac10b-58cc-4372-a567-0e02b2c3d470', + 'subscription_name' => '', + 'expiration_date' => '', + 'product' => [ + 'view' => 'Acquia Network', + ], + 'search_service_enabled' => 1, + 'gratis' => FALSE, + ], + $this->container->get('acquia_connector.subscription')->getSubscription() + ); + + } + + /** + * Test getSubscription(). + */ + public function testGetSubscription(): void { + $this->container->get('state')->setMultiple([ + 'acquia_connector.identifier' => 'ABC', + 'acquia_connector.key' => 'DEF', + 'acquia_connector.application_uuid' => 'a47ac10b-58cc-4372-a567-0e02b2c3d470', + 'acquia_connector.subscription_data' => 'bogus_data', + ]); + $this->container->get('acquia_connector.subscription')->populateSettings(); + + // Assert that we don't get data if oAuth data is empty. + $keys = ["subscription_name", "expiration_date"]; + $subscription_data_no_oauth = $this->container->get('acquia_connector.subscription') + ->getSubscription(TRUE); + + foreach ($keys as $key) { + $this->assertEmpty($subscription_data_no_oauth[$key]); + } + + // Assert again with oAuth data set. + $this->populateOauthSettings(); + $subscription_data_with_oauth = $this->container->get('acquia_connector.subscription') + ->getSubscription(TRUE); + + foreach ($keys as $key) { + $this->assertNotEmpty($subscription_data_with_oauth[$key]); + } + } + +} diff --git a/docroot/modules/contrib/acquia_connector/tests/src/Kernel/ToolbarIntegrationTest.php b/docroot/modules/contrib/acquia_connector/tests/src/Kernel/ToolbarIntegrationTest.php new file mode 100644 index 0000000000..8887e25e65 --- /dev/null +++ b/docroot/modules/contrib/acquia_connector/tests/src/Kernel/ToolbarIntegrationTest.php @@ -0,0 +1,90 @@ +createUser(); + self::assertNotFalse($user); + $this->container->get('current_user')->setAccount($user); + self::assertEquals([], acquia_connector_toolbar()); + } + + /** + * Tests the toolbar output with subscription credentials. + * + * @param string $identifier + * The network identifier. + * @param string $key + * The network key. + * @param string $application_uuid + * The application UUID. + * @param string $expected_title + * The expected toolbar item title. + * @param string $expected_url + * The expected toolbar item URL. + * + * @dataProvider credentialData + */ + public function testWithoutSubscription(string $identifier, string $key, string $application_uuid, string $expected_title, string $expected_url): void { + $this->container->get('state')->setMultiple([ + 'acquia_connector.identifier' => $identifier, + 'acquia_connector.key' => $key, + 'acquia_connector.application_uuid' => $application_uuid, + ]); + + $user = $this->createUser(['view acquia connector toolbar']); + self::assertNotFalse($user); + $this->container->get('current_user')->setAccount($user); + $toolbar = acquia_connector_toolbar(); + self::assertArrayHasKey('acquia_connector', $toolbar); + self::assertEquals( + ['tags' => ['acquia_connector_subscription']], + $toolbar['acquia_connector']['#cache'] + ); + self::assertArrayHasKey('tab', $toolbar['acquia_connector']); + $tab = $toolbar['acquia_connector']['tab']; + self::assertEquals($expected_title, (string) $tab['#title']); + self::assertEquals($expected_url, $tab['#url']->toString()); + } + + /** + * The test data. + * + * @return \Generator + * The data. + */ + public function credentialData() { + yield 'no credentials' => [ + '', + '', + '', + 'Subscription not active', + '/admin/config/services/acquia-connector/login', + ]; + yield 'with credentials' => [ + 'ABC', + 'DEF', + 'a47ac10b-58cc-4372-a567-0e02b2c3d470', + 'Subscription active', + 'https://cloud.acquia.com/app/develop/applications/a47ac10b-58cc-4372-a567-0e02b2c3d470', + ]; + } + +} diff --git a/docroot/modules/contrib/acquia_connector/tests/src/Unit/AcquiaConnectorUnitTest.php b/docroot/modules/contrib/acquia_connector/tests/src/Unit/AcquiaConnectorUnitTest.php deleted file mode 100644 index 9d528e132d..0000000000 --- a/docroot/modules/contrib/acquia_connector/tests/src/Unit/AcquiaConnectorUnitTest.php +++ /dev/null @@ -1,108 +0,0 @@ -randomMachineName(); - $key = $this->randomMachineName(); - $params = ['time', 'nonce', 'hash']; - - $client = new ClientTest(); - $result = $client->buildAuthenticator($key, time(), $params); - // Test Client::buildAuthenticator. - $valid = is_array($result); - $this->assertTrue($valid, 'Client::buildAuthenticator returns an array'); - if ($valid) { - foreach ($params as $key) { - if (!array_key_exists($key, $result)) { - $valid = FALSE; - break; - } - } - $this->assertTrue($valid, 'Array has expected keys'); - } - // Test Client::buildAuthenticator. - $result = $client->buildAuthenticator($identifier, time(), []); - $valid = is_array($result); - $this->assertTrue($valid, 'Client::buildAuthenticator returns an array'); - if ($valid) { - foreach ($params as $key) { - if (!array_key_exists($key, $result)) { - $valid = FALSE; - break; - } - } - $this->assertTrue($valid, 'Array has expected keys'); - } - } - - /** - * Test Id From Subscription. - */ - public function testIdFromSub() { - $statusController = new StatusControllerTest(); - $uuid = $statusController->getIdFromSub(['uuid' => 'test']); - $this->assertEquals('test', $uuid, 'UUID property identical'); - $data = ['href' => 'http://example.com/network/uuid/test/dashboard']; - $uuid = $statusController->getIdFromSub($data); - $this->assertEquals('test', $uuid, 'UUID extracted from href'); - } - -} -/** - * {@inheritdoc} - */ -class ClientTest extends Client { - - /** - * Construction method. - */ - public function __construct(){} - - /** - * {@inheritdoc} - */ - public function buildAuthenticator($key, int $request_time, array $params = []) { - - $authenticator = parent::buildAuthenticator($key, $request_time, $params); - - return $authenticator; - - } - -} - -/** - * Class StatusController. - */ -class StatusControllerTest extends StatusController { - - /** - * Construction method. - */ - public function __construct(){} - -} diff --git a/docroot/modules/contrib/acquia_connector/tests/src/Unit/AcquiaTelemetryTest.php b/docroot/modules/contrib/acquia_connector/tests/src/Unit/AcquiaTelemetryTest.php new file mode 100644 index 0000000000..3240758bb5 --- /dev/null +++ b/docroot/modules/contrib/acquia_connector/tests/src/Unit/AcquiaTelemetryTest.php @@ -0,0 +1,57 @@ +createMock(ModuleExtensionList::class); + $module_list->method('getAllAvailableInfo')->willReturn(array_combine($modules, $modules)); + + $sut = new AcquiaTelemetry( + $module_list, + $this->createMock(ClientInterface::class), + $this->createMock(ConfigFactoryInterface::class), + $this->createMock(StateInterface::class), + $this->createMock(TimeInterface::class) + ); + self::assertEquals( + [ + 'acquia_cms_page', + 'acquia_cms_toolbar', + 'acquia_connector', + 'acquia_perz', + 'cohesion', + 'media_acquiadam', + ], + $sut->getAcquiaExtensionNames() + ); + } + +} diff --git a/docroot/modules/contrib/acquia_connector/tests/src/Unit/AutoConnectorTest.php b/docroot/modules/contrib/acquia_connector/tests/src/Unit/AutoConnectorTest.php deleted file mode 100644 index a496b55b72..0000000000 --- a/docroot/modules/contrib/acquia_connector/tests/src/Unit/AutoConnectorTest.php +++ /dev/null @@ -1,100 +0,0 @@ -prophesize(Subscription::CLASS); - $subscription_mock->hasCredentials()->willReturn(FALSE); - $subscription_mock->update()->willReturn(TRUE); - - $storage_mock = $this->prophesize(Storage::CLASS); - - $config = [ - 'ah_network_identifier' => 'WXYZ-12345', - 'ah_network_key' => '12345678901234567890', - ]; - - $auto_connector = new AutoConnector($subscription_mock->reveal(), $storage_mock->reveal(), $config); - - $auto_connected = $auto_connector->connectToAcquia(); - - $this->assertTrue($auto_connected); - - $storage_mock->setKey('12345678901234567890')->shouldHaveBeenCalled(); - $storage_mock->setIdentifier('WXYZ-12345')->shouldHaveBeenCalled(); - $subscription_mock->update()->shouldHaveBeenCalled(); - - } - - /** - * Tests the scenario when the site is already connected to Acquia. - */ - public function testAutoConnectWhenAlreadyConnected() { - - $subscription_mock = $this->prophesize(Subscription::CLASS); - $subscription_mock->hasCredentials()->willReturn(TRUE); - $subscription_mock->update()->shouldNotBeCalled(); - - $storage_mock = $this->prophesize(Storage::CLASS); - $storage_mock->setKey()->shouldNotBeCalled(); - $storage_mock->setIdentifier()->shouldNotBeCalled(); - - $config = [ - 'ah_network_identifier' => 'WXYZ-12345', - 'ah_network_key' => '12345678901234567890', - ]; - - $auto_connector = new AutoConnector($subscription_mock->reveal(), $storage_mock->reveal(), $config); - - $auto_connected = $auto_connector->connectToAcquia(); - - $this->assertFalse($auto_connected); - - } - - /** - * Tests auto connect. - * - * Tests the scenario when the site is not connected but there are no - * credentials provided by the global config. - */ - public function testAutoConnectWhenNoCredsInGlobalConfig() { - - $subscription_mock = $this->prophesize(Subscription::CLASS); - $subscription_mock->hasCredentials()->willReturn(FALSE); - $subscription_mock->update()->shouldNotBeCalled(); - - $storage_mock = $this->prophesize(Storage::CLASS); - $storage_mock->setKey()->shouldNotBeCalled(); - $storage_mock->setIdentifier()->shouldNotBeCalled(); - - $config = []; - - $auto_connector = new AutoConnector($subscription_mock->reveal(), $storage_mock->reveal(), $config); - - $auto_connected = $auto_connector->connectToAcquia(); - - $this->assertFalse($auto_connected); - - } - -} diff --git a/docroot/modules/contrib/acquia_connector/tests/src/Unit/SettingsOverriddenTest.php b/docroot/modules/contrib/acquia_connector/tests/src/Unit/SettingsOverriddenTest.php new file mode 100644 index 0000000000..36ee5ae774 --- /dev/null +++ b/docroot/modules/contrib/acquia_connector/tests/src/Unit/SettingsOverriddenTest.php @@ -0,0 +1,141 @@ +prophesize(Config::class)->reveal(), + $network_id, + $secret_key, + $app_uuid, + $metadata + ); + $subscription = $this->prophesize(Subscription::class); + $subscription + ->getSettings() + ->willReturn($settings); + $subscription + ->getProvider() + ->willReturn('acquia_cloud'); + $settings_form = new SettingsForm( + $this->prophesize(ConfigFactoryInterface::class)->reveal(), + $this->prophesize(ModuleHandlerInterface::class)->reveal(), + $this->prophesize(PrivateKey::class)->reveal(), + $subscription->reveal(), + $this->prophesize(StateInterface::class)->reveal(), + $this->prophesize(SiteProfile::class)->reveal(), + $this->prophesize(EventDispatcherInterface::class)->reveal() + ); + $method_reflection = new \ReflectionMethod($settings_form, 'isCloudOverridden'); + $method_reflection->setAccessible(TRUE); + self::assertEquals($is_overridden, $method_reflection->invoke($settings_form)); + + } + + /** + * Data provider for settings object. + * + * @return iterable + * Data provider. + */ + public function settingsDataProvider(): iterable { + yield [ + 'network_id', + 'secret_key', + 'app_uuid', + [ + 'ah_network_identifier' => 'network_id', + 'ah_network_key' => 'secret_key', + 'AH_APPLICATION_UUID' => 'app_uuid', + ], + FALSE, + ]; + yield [ + 'updated_network_id', + 'secret_key', + 'app_uuid', + [ + 'ah_network_identifier' => 'network_id', + 'ah_network_key' => 'secret_key', + 'AH_APPLICATION_UUID' => 'app_uuid', + ], + TRUE, + ]; + yield [ + 'network_id', + 'updated_secret_key', + 'app_uuid', + [ + 'ah_network_identifier' => 'network_id', + 'ah_network_key' => 'secret_key', + 'AH_APPLICATION_UUID' => 'app_uuid', + ], + TRUE, + ]; + yield [ + 'network_id', + 'secret_key', + 'updated_app_uuid', + [ + 'ah_network_identifier' => 'network_id', + 'ah_network_key' => 'secret_key', + 'AH_APPLICATION_UUID' => 'app_uuid', + ], + TRUE, + ]; + yield [ + 'updated_network_id', + 'updated_secret_key', + 'updated_app_uuid', + [ + 'ah_network_identifier' => 'network_id', + 'ah_network_key' => 'secret_key', + 'AH_APPLICATION_UUID' => 'app_uuid', + ], + TRUE, + ]; + } + +} diff --git a/vendor/composer/ClassLoader.php b/vendor/composer/ClassLoader.php index a72151c77c..afef3fa2ad 100644 --- a/vendor/composer/ClassLoader.php +++ b/vendor/composer/ClassLoader.php @@ -42,9 +42,6 @@ */ class ClassLoader { - /** @var \Closure(string):void */ - private static $includeFile; - /** @var ?string */ private $vendorDir; @@ -109,7 +106,6 @@ class ClassLoader public function __construct($vendorDir = null) { $this->vendorDir = $vendorDir; - self::initializeIncludeClosure(); } /** @@ -429,8 +425,7 @@ public function unregister() public function loadClass($class) { if ($file = $this->findFile($class)) { - $includeFile = self::$includeFile; - $includeFile($file); + includeFile($file); return true; } @@ -560,26 +555,18 @@ private function findFileWithExtension($class, $ext) return false; } +} - /** - * @return void - */ - private static function initializeIncludeClosure() - { - if (self::$includeFile !== null) { - return; - } - - /** - * Scope isolated include. - * - * Prevents access to $this/self from included files. - * - * @param string $file - * @return void - */ - self::$includeFile = \Closure::bind(static function($file) { - include $file; - }, null, null); - } +/** + * Scope isolated include. + * + * Prevents access to $this/self from included files. + * + * @param string $file + * @return void + * @private + */ +function includeFile($file) +{ + include $file; } diff --git a/vendor/composer/autoload_psr4.php b/vendor/composer/autoload_psr4.php index b14767b74f..d598c6cccc 100644 --- a/vendor/composer/autoload_psr4.php +++ b/vendor/composer/autoload_psr4.php @@ -47,7 +47,7 @@ 'Robo\\' => array($vendorDir . '/consolidation/robo/src'), 'Psy\\' => array($vendorDir . '/psy/psysh/src'), 'Psr\\Log\\' => array($vendorDir . '/psr/log/Psr/Log'), - 'Psr\\Http\\Message\\' => array($vendorDir . '/psr/http-factory/src', $vendorDir . '/psr/http-message/src'), + 'Psr\\Http\\Message\\' => array($vendorDir . '/psr/http-message/src', $vendorDir . '/psr/http-factory/src'), 'Psr\\Container\\' => array($vendorDir . '/psr/container/src'), 'Psr\\Cache\\' => array($vendorDir . '/psr/cache/src'), 'PhpParser\\' => array($vendorDir . '/nikic/php-parser/lib/PhpParser'), diff --git a/vendor/composer/autoload_real.php b/vendor/composer/autoload_real.php index 19e0733600..dcf424ad52 100644 --- a/vendor/composer/autoload_real.php +++ b/vendor/composer/autoload_real.php @@ -37,18 +37,25 @@ public static function getLoader() $loader->register(true); - $filesToLoad = \Composer\Autoload\ComposerStaticInit355962ff916b055657c7b3a80cd20fb3::$files; - $requireFile = \Closure::bind(static function ($fileIdentifier, $file) { - if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) { - $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true; - - require $file; - } - }, null, null); - foreach ($filesToLoad as $fileIdentifier => $file) { - $requireFile($fileIdentifier, $file); + $includeFiles = \Composer\Autoload\ComposerStaticInit355962ff916b055657c7b3a80cd20fb3::$files; + foreach ($includeFiles as $fileIdentifier => $file) { + composerRequire355962ff916b055657c7b3a80cd20fb3($fileIdentifier, $file); } return $loader; } } + +/** + * @param string $fileIdentifier + * @param string $file + * @return void + */ +function composerRequire355962ff916b055657c7b3a80cd20fb3($fileIdentifier, $file) +{ + if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) { + $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true; + + require $file; + } +} diff --git a/vendor/composer/autoload_static.php b/vendor/composer/autoload_static.php index cee4478e25..000a28afd9 100644 --- a/vendor/composer/autoload_static.php +++ b/vendor/composer/autoload_static.php @@ -340,8 +340,8 @@ class ComposerStaticInit355962ff916b055657c7b3a80cd20fb3 ), 'Psr\\Http\\Message\\' => array ( - 0 => __DIR__ . '/..' . '/psr/http-factory/src', - 1 => __DIR__ . '/..' . '/psr/http-message/src', + 0 => __DIR__ . '/..' . '/psr/http-message/src', + 1 => __DIR__ . '/..' . '/psr/http-factory/src', ), 'Psr\\Container\\' => array ( diff --git a/vendor/composer/include_paths.php b/vendor/composer/include_paths.php index af33c14914..6bf233c505 100644 --- a/vendor/composer/include_paths.php +++ b/vendor/composer/include_paths.php @@ -8,6 +8,6 @@ return array( $vendorDir . '/pear/archive_tar', $vendorDir . '/pear/console_getopt', - $vendorDir . '/pear/pear-core-minimal/src', $vendorDir . '/pear/pear_exception', + $vendorDir . '/pear/pear-core-minimal/src', ); diff --git a/vendor/composer/installed.json b/vendor/composer/installed.json index 2a30886323..333c3918b5 100644 --- a/vendor/composer/installed.json +++ b/vendor/composer/installed.json @@ -1428,49 +1428,41 @@ }, { "name": "drupal/acquia_connector", - "version": "1.26.0", - "version_normalized": "1.26.0.0", + "version": "4.0.4", + "version_normalized": "4.0.4.0", "source": { "type": "git", "url": "https://git.drupalcode.org/project/acquia_connector.git", - "reference": "8.x-1.26" + "reference": "4.0.4" }, "dist": { "type": "zip", - "url": "https://ftp.drupal.org/files/projects/acquia_connector-8.x-1.26.zip", - "reference": "8.x-1.26", - "shasum": "632932100c3d2946a11055c472c7e6dbfc1269bd" + "url": "https://ftp.drupal.org/files/projects/acquia_connector-4.0.4.zip", + "reference": "4.0.4", + "shasum": "4c37d429a11c2121df00c4ff4dccead354754f0c" }, "require": { - "drupal/core": "^8.8 || ^9", + "drupal/core": ">=8.9 <11.0.0-stable", "ext-json": "*" }, - "require-dev": { - "acquia/coding-standards": "^0.4.1", - "dealerdirect/phpcodesniffer-composer-installer": "^0.5.0", - "drupal/acquia_search": "*", - "drupal/search_api": "^1.17", - "drupal/search_api_solr": "~1.0" - }, "type": "drupal-module", "extra": { "drupal": { - "version": "8.x-1.26", - "datestamp": "1617213585", + "version": "4.0.4", + "datestamp": "1680704017", "security-coverage": { "status": "covered", "message": "Covered by Drupal's security advisory policy" } }, "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 + } }, "installation-source": "dist", "notification-url": "https://packages.drupal.org/8/downloads", @@ -1478,33 +1470,13 @@ "GPL-2.0-or-later" ], "authors": [ - { - "name": "Dane Powell", - "homepage": "https://www.drupal.org/user/339326" - }, - { - "name": "Mark Trapp", - "homepage": "https://www.drupal.org/user/212019" - }, - { - "name": "Stanislav Mixnovich", - "homepage": "https://www.drupal.org/user/2859977" - }, { "name": "acquia", "homepage": "https://www.drupal.org/user/1231722" }, { - "name": "ayang", - "homepage": "https://www.drupal.org/user/1777884" - }, - { - "name": "blueminds", - "homepage": "https://www.drupal.org/user/128652" - }, - { - "name": "grasmash", - "homepage": "https://www.drupal.org/user/455714" + "name": "Dane Powell", + "homepage": "https://www.drupal.org/user/339326" }, { "name": "irek02", @@ -1515,16 +1487,20 @@ "homepage": "https://www.drupal.org/user/45640" }, { - "name": "jmoreira", - "homepage": "https://www.drupal.org/user/2374486" + "name": "kaynen", + "homepage": "https://www.drupal.org/user/733308" }, { "name": "kolafson", "homepage": "https://www.drupal.org/user/822402" }, { - "name": "nerdstein", - "homepage": "https://www.drupal.org/user/1557710" + "name": "Mark Trapp", + "homepage": "https://www.drupal.org/user/212019" + }, + { + "name": "mglaman", + "homepage": "https://www.drupal.org/user/2416470" }, { "name": "vlad.pavlovic", @@ -3049,17 +3025,17 @@ }, { "name": "guzzlehttp/psr7", - "version": "1.9.0", - "version_normalized": "1.9.0.0", + "version": "1.9.1", + "version_normalized": "1.9.1.0", "source": { "type": "git", "url": "https://github.com/guzzle/psr7.git", - "reference": "e98e3e6d4f86621a9b75f623996e6bbdeb4b9318" + "reference": "e4490cabc77465aaee90b20cfc9a770f8c04be6b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/psr7/zipball/e98e3e6d4f86621a9b75f623996e6bbdeb4b9318", - "reference": "e98e3e6d4f86621a9b75f623996e6bbdeb4b9318", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/e4490cabc77465aaee90b20cfc9a770f8c04be6b", + "reference": "e4490cabc77465aaee90b20cfc9a770f8c04be6b", "shasum": "" }, "require": { @@ -3077,14 +3053,9 @@ "suggest": { "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses" }, - "time": "2022-06-20T21:43:03+00:00", + "time": "2023-04-17T16:00:37+00:00", "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.9-dev" - } - }, - "installation-source": "dist", + "installation-source": "source", "autoload": { "files": [ "src/functions_include.php" @@ -3142,7 +3113,7 @@ ], "support": { "issues": "https://github.com/guzzle/psr7/issues", - "source": "https://github.com/guzzle/psr7/tree/1.9.0" + "source": "https://github.com/guzzle/psr7/tree/1.9.1" }, "funding": [ { @@ -4136,17 +4107,17 @@ }, { "name": "pear/pear-core-minimal", - "version": "v1.10.11", - "version_normalized": "1.10.11.0", + "version": "v1.10.13", + "version_normalized": "1.10.13.0", "source": { "type": "git", "url": "https://github.com/pear/pear-core-minimal.git", - "reference": "68d0d32ada737153b7e93b8d3c710ebe70ac867d" + "reference": "aed862e95fd286c53cc546734868dc38ff4b5b1d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/pear/pear-core-minimal/zipball/68d0d32ada737153b7e93b8d3c710ebe70ac867d", - "reference": "68d0d32ada737153b7e93b8d3c710ebe70ac867d", + "url": "https://api.github.com/repos/pear/pear-core-minimal/zipball/aed862e95fd286c53cc546734868dc38ff4b5b1d", + "reference": "aed862e95fd286c53cc546734868dc38ff4b5b1d", "shasum": "" }, "require": { @@ -4156,9 +4127,9 @@ "replace": { "rsky/pear-core-min": "self.version" }, - "time": "2021-08-10T22:31:03+00:00", + "time": "2023-04-19T19:15:47+00:00", "type": "library", - "installation-source": "dist", + "installation-source": "source", "autoload": { "psr-0": { "": "src/" @@ -4468,31 +4439,31 @@ }, { "name": "psr/http-factory", - "version": "1.0.1", - "version_normalized": "1.0.1.0", + "version": "1.0.2", + "version_normalized": "1.0.2.0", "source": { "type": "git", "url": "https://github.com/php-fig/http-factory.git", - "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be" + "reference": "e616d01114759c4c489f93b099585439f795fe35" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-factory/zipball/12ac7fcd07e5b077433f5f2bee95b3a771bf61be", - "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/e616d01114759c4c489f93b099585439f795fe35", + "reference": "e616d01114759c4c489f93b099585439f795fe35", "shasum": "" }, "require": { "php": ">=7.0.0", - "psr/http-message": "^1.0" + "psr/http-message": "^1.0 || ^2.0" }, - "time": "2019-04-30T12:38:16+00:00", + "time": "2023-04-10T20:10:41+00:00", "type": "library", "extra": { "branch-alias": { "dev-master": "1.0.x-dev" } }, - "installation-source": "dist", + "installation-source": "source", "autoload": { "psr-4": { "Psr\\Http\\Message\\": "src/" @@ -4505,7 +4476,7 @@ "authors": [ { "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" + "homepage": "https://www.php-fig.org/" } ], "description": "Common interfaces for PSR-7 HTTP message factories", @@ -4520,7 +4491,7 @@ "response" ], "support": { - "source": "https://github.com/php-fig/http-factory/tree/master" + "source": "https://github.com/php-fig/http-factory/tree/1.0.2" }, "install-path": "../psr/http-factory" }, @@ -7318,17 +7289,17 @@ }, { "name": "symfony/var-dumper", - "version": "v5.4.21", - "version_normalized": "5.4.21.0", + "version": "v5.4.22", + "version_normalized": "5.4.22.0", "source": { "type": "git", "url": "https://github.com/symfony/var-dumper.git", - "reference": "6c5ac3a1be8b849d59a1a77877ee110e1b55eb74" + "reference": "e2edac9ce47e6df07e38143c7cfa6bdbc1a6dcc4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/6c5ac3a1be8b849d59a1a77877ee110e1b55eb74", - "reference": "6c5ac3a1be8b849d59a1a77877ee110e1b55eb74", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/e2edac9ce47e6df07e38143c7cfa6bdbc1a6dcc4", + "reference": "e2edac9ce47e6df07e38143c7cfa6bdbc1a6dcc4", "shasum": "" }, "require": { @@ -7352,12 +7323,12 @@ "ext-intl": "To show region name in time zone dump", "symfony/console": "To use the ServerDumpCommand and/or the bin/var-dump-server script" }, - "time": "2023-02-23T10:00:28+00:00", + "time": "2023-03-25T09:27:28+00:00", "bin": [ "Resources/bin/var-dump-server" ], "type": "library", - "installation-source": "dist", + "installation-source": "source", "autoload": { "files": [ "Resources/functions/dump.php" @@ -7390,7 +7361,7 @@ "dump" ], "support": { - "source": "https://github.com/symfony/var-dumper/tree/v5.4.21" + "source": "https://github.com/symfony/var-dumper/tree/v5.4.22" }, "funding": [ { diff --git a/vendor/composer/installed.php b/vendor/composer/installed.php index 84503aae2d..156124c9ff 100644 --- a/vendor/composer/installed.php +++ b/vendor/composer/installed.php @@ -3,7 +3,7 @@ 'name' => 'acquia/eessmith', 'pretty_version' => 'dev-master', 'version' => 'dev-master', - 'reference' => '4d3387378698284c1916c4891c8116e9fbf151c0', + 'reference' => '6215b2e1a4663bf7396f2de126547c59c5218c65', 'type' => 'project', 'install_path' => __DIR__ . '/../../', 'aliases' => array(), @@ -31,7 +31,7 @@ 'acquia/eessmith' => array( 'pretty_version' => 'dev-master', 'version' => 'dev-master', - 'reference' => '4d3387378698284c1916c4891c8116e9fbf151c0', + 'reference' => '6215b2e1a4663bf7396f2de126547c59c5218c65', 'type' => 'project', 'install_path' => __DIR__ . '/../../', 'aliases' => array(), @@ -206,9 +206,9 @@ 'dev_requirement' => false, ), 'drupal/acquia_connector' => array( - 'pretty_version' => '1.26.0', - 'version' => '1.26.0.0', - 'reference' => '8.x-1.26', + 'pretty_version' => '4.0.4', + 'version' => '4.0.4.0', + 'reference' => '4.0.4', 'type' => 'drupal-module', 'install_path' => __DIR__ . '/../../docroot/modules/contrib/acquia_connector', 'aliases' => array(), @@ -536,9 +536,9 @@ 'dev_requirement' => false, ), 'guzzlehttp/psr7' => array( - 'pretty_version' => '1.9.0', - 'version' => '1.9.0.0', - 'reference' => 'e98e3e6d4f86621a9b75f623996e6bbdeb4b9318', + 'pretty_version' => '1.9.1', + 'version' => '1.9.1.0', + 'reference' => 'e4490cabc77465aaee90b20cfc9a770f8c04be6b', 'type' => 'library', 'install_path' => __DIR__ . '/../guzzlehttp/psr7', 'aliases' => array(), @@ -668,9 +668,9 @@ 'dev_requirement' => false, ), 'pear/pear-core-minimal' => array( - 'pretty_version' => 'v1.10.11', - 'version' => '1.10.11.0', - 'reference' => '68d0d32ada737153b7e93b8d3c710ebe70ac867d', + 'pretty_version' => 'v1.10.13', + 'version' => '1.10.13.0', + 'reference' => 'aed862e95fd286c53cc546734868dc38ff4b5b1d', 'type' => 'library', 'install_path' => __DIR__ . '/../pear/pear-core-minimal', 'aliases' => array(), @@ -735,9 +735,9 @@ ), ), 'psr/http-factory' => array( - 'pretty_version' => '1.0.1', - 'version' => '1.0.1.0', - 'reference' => '12ac7fcd07e5b077433f5f2bee95b3a771bf61be', + 'pretty_version' => '1.0.2', + 'version' => '1.0.2.0', + 'reference' => 'e616d01114759c4c489f93b099585439f795fe35', 'type' => 'library', 'install_path' => __DIR__ . '/../psr/http-factory', 'aliases' => array(), @@ -806,7 +806,7 @@ 'rsky/pear-core-min' => array( 'dev_requirement' => false, 'replaced' => array( - 0 => 'v1.10.11', + 0 => 'v1.10.13', ), ), 'shama/baton' => array( @@ -1113,9 +1113,9 @@ 'dev_requirement' => false, ), 'symfony/var-dumper' => array( - 'pretty_version' => 'v5.4.21', - 'version' => '5.4.21.0', - 'reference' => '6c5ac3a1be8b849d59a1a77877ee110e1b55eb74', + 'pretty_version' => 'v5.4.22', + 'version' => '5.4.22.0', + 'reference' => 'e2edac9ce47e6df07e38143c7cfa6bdbc1a6dcc4', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/var-dumper', 'aliases' => array(), diff --git a/vendor/guzzlehttp/psr7/.editorconfig b/vendor/guzzlehttp/psr7/.editorconfig new file mode 100644 index 0000000000..677e36e295 --- /dev/null +++ b/vendor/guzzlehttp/psr7/.editorconfig @@ -0,0 +1,9 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +indent_size = 4 +indent_style = space +insert_final_newline = true +trim_trailing_whitespace = true diff --git a/vendor/guzzlehttp/psr7/.gitattributes b/vendor/guzzlehttp/psr7/.gitattributes new file mode 100644 index 0000000000..b72a8a9007 --- /dev/null +++ b/vendor/guzzlehttp/psr7/.gitattributes @@ -0,0 +1,7 @@ +/tests export-ignore +.editorconfig export-ignore +.gitattributes export-ignore +.gitignore export-ignore +.travis.yml export-ignore +Makefile export-ignore +phpunit.xml.dist export-ignore diff --git a/vendor/guzzlehttp/psr7/.gitignore b/vendor/guzzlehttp/psr7/.gitignore new file mode 100644 index 0000000000..6a926bd6cb --- /dev/null +++ b/vendor/guzzlehttp/psr7/.gitignore @@ -0,0 +1,7 @@ +artifacts/ +vendor/ +.php_cs +.php_cs.cache +.phpunit.result.cache +composer.lock +phpunit.xml diff --git a/vendor/guzzlehttp/psr7/.travis.yml b/vendor/guzzlehttp/psr7/.travis.yml new file mode 100644 index 0000000000..77d260ecf8 --- /dev/null +++ b/vendor/guzzlehttp/psr7/.travis.yml @@ -0,0 +1,26 @@ +language: php + +matrix: + include: + - php: hhvm-3.24 + dist: trusty + - php: 5.4 + dist: trusty + env: COMPOSER_FLAGS="--prefer-stable --prefer-lowest" + - php: 5.4 + dist: trusty + - php: 5.5.9 + dist: trusty + env: COMPOSER_FLAGS="--prefer-stable --prefer-lowest" + fast_finish: true + +before_install: + - if [[ "$TRAVIS_PHP_VERSION" != "hhvm-3.24" ]]; then echo "xdebug.overload_var_dump = 1" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini; fi + - if [[ "$TRAVIS_PHP_VERSION" == "hhvm-3.24" ]]; then travis_retry composer require "phpunit/phpunit:^5.7.27" --dev --no-update -n; fi + +install: + - if [[ "$TRAVIS_PHP_VERSION" != "nightly" ]]; then travis_retry composer update; fi + - if [[ "$TRAVIS_PHP_VERSION" == "nightly" ]]; then travis_retry composer update --ignore-platform-reqs; fi + +script: + - make test diff --git a/vendor/guzzlehttp/psr7/CHANGELOG.md b/vendor/guzzlehttp/psr7/CHANGELOG.md index b4fdf3c68a..9b2b65cdb8 100644 --- a/vendor/guzzlehttp/psr7/CHANGELOG.md +++ b/vendor/guzzlehttp/psr7/CHANGELOG.md @@ -9,6 +9,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased +## 1.9.1 - 2023-04-17 + +### Fixed + +- Fixed header validation issue + ## 1.9.0 - 2022-06-20 ### Added diff --git a/vendor/guzzlehttp/psr7/Makefile b/vendor/guzzlehttp/psr7/Makefile new file mode 100644 index 0000000000..8b00b43e6a --- /dev/null +++ b/vendor/guzzlehttp/psr7/Makefile @@ -0,0 +1,29 @@ +all: clean test + +test: + vendor/bin/phpunit $(TEST) + +coverage: + vendor/bin/phpunit --coverage-html=artifacts/coverage $(TEST) + +view-coverage: + open artifacts/coverage/index.html + +check-tag: + $(if $(TAG),,$(error TAG is not defined. Pass via "make tag TAG=4.2.1")) + +tag: check-tag + @echo Tagging $(TAG) + chag update $(TAG) + git commit -a -m '$(TAG) release' + chag tag + @echo "Release has been created. Push using 'make release'" + @echo "Changes made in the release commit" + git diff HEAD~1 HEAD + +release: check-tag + git push origin master + git push origin $(TAG) + +clean: + rm -rf artifacts/* diff --git a/vendor/guzzlehttp/psr7/composer.json b/vendor/guzzlehttp/psr7/composer.json index 0e36920dbc..2607f22d48 100644 --- a/vendor/guzzlehttp/psr7/composer.json +++ b/vendor/guzzlehttp/psr7/composer.json @@ -61,11 +61,6 @@ "GuzzleHttp\\Tests\\Psr7\\": "tests/" } }, - "extra": { - "branch-alias": { - "dev-master": "1.9-dev" - } - }, "config": { "preferred-install": "dist", "sort-packages": true, diff --git a/vendor/guzzlehttp/psr7/phpunit.xml.dist b/vendor/guzzlehttp/psr7/phpunit.xml.dist new file mode 100644 index 0000000000..e2819aa1df --- /dev/null +++ b/vendor/guzzlehttp/psr7/phpunit.xml.dist @@ -0,0 +1,24 @@ + + + + + tests/ + tests/Integration + + + tests/Integration + + + + + + src/ + + + diff --git a/vendor/guzzlehttp/psr7/src/MessageTrait.php b/vendor/guzzlehttp/psr7/src/MessageTrait.php index 0ac8663da5..0bbd63e0d6 100644 --- a/vendor/guzzlehttp/psr7/src/MessageTrait.php +++ b/vendor/guzzlehttp/psr7/src/MessageTrait.php @@ -226,12 +226,9 @@ private function assertHeader($header) throw new \InvalidArgumentException('Header name can not be empty.'); } - if (! preg_match('/^[a-zA-Z0-9\'`#$%&*+.^_|~!-]+$/', $header)) { + if (! preg_match('/^[a-zA-Z0-9\'`#$%&*+.^_|~!-]+$/D', $header)) { throw new \InvalidArgumentException( - sprintf( - '"%s" is not valid header name', - $header - ) + sprintf('"%s" is not valid header name.', $header) ); } } @@ -263,8 +260,10 @@ private function assertValue($value) // Clients must not send a request with line folding and a server sending folded headers is // likely very rare. Line folding is a fairly obscure feature of HTTP/1.1 and thus not accepting // folding is not likely to break any legitimate use case. - if (! preg_match('/^[\x20\x09\x21-\x7E\x80-\xFF]*$/', $value)) { - throw new \InvalidArgumentException(sprintf('"%s" is not valid header value', $value)); + if (! preg_match('/^[\x20\x09\x21-\x7E\x80-\xFF]*$/D', $value)) { + throw new \InvalidArgumentException( + sprintf('"%s" is not valid header value.', $value) + ); } } } diff --git a/vendor/guzzlehttp/psr7/tests/AppendStreamTest.php b/vendor/guzzlehttp/psr7/tests/AppendStreamTest.php new file mode 100644 index 0000000000..2f3febc06b --- /dev/null +++ b/vendor/guzzlehttp/psr7/tests/AppendStreamTest.php @@ -0,0 +1,213 @@ +getMockBuilder('Psr\Http\Message\StreamInterface') + ->setMethods(['isReadable']) + ->getMockForAbstractClass(); + $s->expects(self::once()) + ->method('isReadable') + ->will(self::returnValue(false)); + + $this->expectExceptionGuzzle('InvalidArgumentException', 'Each stream must be readable'); + + $a->addStream($s); + } + + public function testValidatesSeekType() + { + $a = new AppendStream(); + + $this->expectExceptionGuzzle('RuntimeException', 'The AppendStream can only seek with SEEK_SET'); + + $a->seek(100, SEEK_CUR); + } + + public function testTriesToRewindOnSeek() + { + $a = new AppendStream(); + $s = $this->getMockBuilder('Psr\Http\Message\StreamInterface') + ->setMethods(['isReadable', 'rewind', 'isSeekable']) + ->getMockForAbstractClass(); + $s->expects(self::once()) + ->method('isReadable') + ->will(self::returnValue(true)); + $s->expects(self::once()) + ->method('isSeekable') + ->will(self::returnValue(true)); + $s->expects(self::once()) + ->method('rewind') + ->will(self::throwException(new \RuntimeException())); + $a->addStream($s); + + $this->expectExceptionGuzzle('RuntimeException', 'Unable to seek stream 0 of the AppendStream'); + + $a->seek(10); + } + + public function testSeeksToPositionByReading() + { + $a = new AppendStream([ + Psr7\Utils::streamFor('foo'), + Psr7\Utils::streamFor('bar'), + Psr7\Utils::streamFor('baz'), + ]); + + $a->seek(3); + self::assertSame(3, $a->tell()); + self::assertSame('bar', $a->read(3)); + + $a->seek(6); + self::assertSame(6, $a->tell()); + self::assertSame('baz', $a->read(3)); + } + + public function testDetachWithoutStreams() + { + $s = new AppendStream(); + $s->detach(); + + self::assertSame(0, $s->getSize()); + self::assertTrue($s->eof()); + self::assertTrue($s->isReadable()); + self::assertSame('', (string) $s); + self::assertTrue($s->isSeekable()); + self::assertFalse($s->isWritable()); + } + + public function testDetachesEachStream() + { + $handle = fopen('php://temp', 'r'); + + $s1 = Psr7\Utils::streamFor($handle); + $s2 = Psr7\Utils::streamFor('bar'); + $a = new AppendStream([$s1, $s2]); + + $a->detach(); + + self::assertSame(0, $a->getSize()); + self::assertTrue($a->eof()); + self::assertTrue($a->isReadable()); + self::assertSame('', (string) $a); + self::assertTrue($a->isSeekable()); + self::assertFalse($a->isWritable()); + + self::assertNull($s1->detach()); + $this->assertInternalTypeGuzzle('resource', $handle, 'resource is not closed when detaching'); + fclose($handle); + } + + public function testClosesEachStream() + { + $handle = fopen('php://temp', 'r'); + + $s1 = Psr7\Utils::streamFor($handle); + $s2 = Psr7\Utils::streamFor('bar'); + $a = new AppendStream([$s1, $s2]); + + $a->close(); + + self::assertSame(0, $a->getSize()); + self::assertTrue($a->eof()); + self::assertTrue($a->isReadable()); + self::assertSame('', (string) $a); + self::assertTrue($a->isSeekable()); + self::assertFalse($a->isWritable()); + + self::assertFalse(is_resource($handle)); + } + + public function testIsNotWritable() + { + $a = new AppendStream([Psr7\Utils::streamFor('foo')]); + self::assertFalse($a->isWritable()); + self::assertTrue($a->isSeekable()); + self::assertTrue($a->isReadable()); + + $this->expectExceptionGuzzle('RuntimeException', 'Cannot write to an AppendStream'); + + $a->write('foo'); + } + + public function testDoesNotNeedStreams() + { + $a = new AppendStream(); + self::assertSame('', (string) $a); + } + + public function testCanReadFromMultipleStreams() + { + $a = new AppendStream([ + Psr7\Utils::streamFor('foo'), + Psr7\Utils::streamFor('bar'), + Psr7\Utils::streamFor('baz'), + ]); + self::assertFalse($a->eof()); + self::assertSame(0, $a->tell()); + self::assertSame('foo', $a->read(3)); + self::assertSame('bar', $a->read(3)); + self::assertSame('baz', $a->read(3)); + self::assertSame('', $a->read(1)); + self::assertTrue($a->eof()); + self::assertSame(9, $a->tell()); + self::assertSame('foobarbaz', (string) $a); + } + + public function testCanDetermineSizeFromMultipleStreams() + { + $a = new AppendStream([ + Psr7\Utils::streamFor('foo'), + Psr7\Utils::streamFor('bar') + ]); + self::assertSame(6, $a->getSize()); + + $s = $this->getMockBuilder('Psr\Http\Message\StreamInterface') + ->setMethods(['isSeekable', 'isReadable']) + ->getMockForAbstractClass(); + $s->expects(self::once()) + ->method('isSeekable') + ->will(self::returnValue(null)); + $s->expects(self::once()) + ->method('isReadable') + ->will(self::returnValue(true)); + $a->addStream($s); + self::assertNull($a->getSize()); + } + + public function testCatchesExceptionsWhenCastingToString() + { + $s = $this->getMockBuilder('Psr\Http\Message\StreamInterface') + ->setMethods(['isSeekable', 'read', 'isReadable', 'eof']) + ->getMockForAbstractClass(); + $s->expects(self::once()) + ->method('isSeekable') + ->will(self::returnValue(true)); + $s->expects(self::once()) + ->method('read') + ->will(self::throwException(new \RuntimeException('foo'))); + $s->expects(self::once()) + ->method('isReadable') + ->will(self::returnValue(true)); + $s->expects(self::any()) + ->method('eof') + ->will(self::returnValue(false)); + $a = new AppendStream([$s]); + self::assertFalse($a->eof()); + self::assertSame('', (string) $a); + } + + public function testReturnsEmptyMetadata() + { + $s = new AppendStream(); + self::assertSame([], $s->getMetadata()); + self::assertNull($s->getMetadata('foo')); + } +} diff --git a/vendor/guzzlehttp/psr7/tests/BaseTest.php b/vendor/guzzlehttp/psr7/tests/BaseTest.php new file mode 100644 index 0000000000..f0ad99c531 --- /dev/null +++ b/vendor/guzzlehttp/psr7/tests/BaseTest.php @@ -0,0 +1,139 @@ +setExpectedException($exception, $message); + } else { + $this->expectException($exception); + if (null !== $message) { + $this->expectExceptionMessage($message); + } + } + } + + public function expectWarningGuzzle() + { + if (method_exists($this, 'expectWarning')) { + $this->expectWarning(); + } elseif (class_exists('PHPUnit\Framework\Error\Warning')) { + $this->expectExceptionGuzzle('PHPUnit\Framework\Error\Warning'); + } else { + $this->expectExceptionGuzzle('PHPUnit_Framework_Error_Warning'); + } + } + + /** + * @param string $type + * @param mixed $input + */ + public function assertInternalTypeGuzzle($type, $input) + { + switch ($type) { + case 'array': + if (method_exists($this, 'assertIsArray')) { + self::assertIsArray($input); + } else { + self::assertInternalType('array', $input); + } + break; + case 'bool': + case 'boolean': + if (method_exists($this, 'assertIsBool')) { + self::assertIsBool($input); + } else { + self::assertInternalType('bool', $input); + } + break; + case 'double': + case 'float': + case 'real': + if (method_exists($this, 'assertIsFloat')) { + self::assertIsFloat($input); + } else { + self::assertInternalType('float', $input); + } + break; + case 'int': + case 'integer': + if (method_exists($this, 'assertIsInt')) { + self::assertIsInt($input); + } else { + self::assertInternalType('int', $input); + } + break; + case 'numeric': + if (method_exists($this, 'assertIsNumeric')) { + self::assertIsNumeric($input); + } else { + self::assertInternalType('numeric', $input); + } + break; + case 'object': + if (method_exists($this, 'assertIsObject')) { + self::assertIsObject($input); + } else { + self::assertInternalType('object', $input); + } + break; + case 'resource': + if (method_exists($this, 'assertIsResource')) { + self::assertIsResource($input); + } else { + self::assertInternalType('resource', $input); + } + break; + case 'string': + if (method_exists($this, 'assertIsString')) { + self::assertIsString($input); + } else { + self::assertInternalType('string', $input); + } + break; + case 'scalar': + if (method_exists($this, 'assertIsScalar')) { + self::assertIsScalar($input); + } else { + self::assertInternalType('scalar', $input); + } + break; + case 'callable': + if (method_exists($this, 'assertIsCallable')) { + self::assertIsCallable($input); + } else { + self::assertInternalType('callable', $input); + } + break; + case 'iterable': + if (method_exists($this, 'assertIsIterable')) { + self::assertIsIterable($input); + } else { + self::assertInternalType('iterable', $input); + } + break; + } + } + + /** + * @param string $needle + * @param string $haystack + */ + public function assertStringContainsStringGuzzle($needle, $haystack) + { + if (method_exists($this, 'assertStringContainsString')) { + self::assertStringContainsString($needle, $haystack); + } else { + self::assertContains($needle, $haystack); + } + } +} diff --git a/vendor/guzzlehttp/psr7/tests/BufferStreamTest.php b/vendor/guzzlehttp/psr7/tests/BufferStreamTest.php new file mode 100644 index 0000000000..804a42609f --- /dev/null +++ b/vendor/guzzlehttp/psr7/tests/BufferStreamTest.php @@ -0,0 +1,63 @@ +isReadable()); + self::assertTrue($b->isWritable()); + self::assertFalse($b->isSeekable()); + self::assertSame(null, $b->getMetadata('foo')); + self::assertSame(10, $b->getMetadata('hwm')); + self::assertSame([], $b->getMetadata()); + } + + public function testRemovesReadDataFromBuffer() + { + $b = new BufferStream(); + self::assertSame(3, $b->write('foo')); + self::assertSame(3, $b->getSize()); + self::assertFalse($b->eof()); + self::assertSame('foo', $b->read(10)); + self::assertTrue($b->eof()); + self::assertSame('', $b->read(10)); + } + + public function testCanCastToStringOrGetContents() + { + $b = new BufferStream(); + $b->write('foo'); + $b->write('baz'); + self::assertSame('foo', $b->read(3)); + $b->write('bar'); + self::assertSame('bazbar', (string) $b); + + $this->expectExceptionGuzzle('RuntimeException', 'Cannot determine the position of a BufferStream'); + + $b->tell(); + } + + public function testDetachClearsBuffer() + { + $b = new BufferStream(); + $b->write('foo'); + $b->detach(); + self::assertTrue($b->eof()); + self::assertSame(3, $b->write('abc')); + self::assertSame('abc', $b->read(10)); + } + + public function testExceedingHighwaterMarkReturnsFalseButStillBuffers() + { + $b = new BufferStream(5); + self::assertSame(3, $b->write('hi ')); + self::assertFalse($b->write('hello')); + self::assertSame('hi hello', (string) $b); + self::assertSame(4, $b->write('test')); + } +} diff --git a/vendor/guzzlehttp/psr7/tests/CachingStreamTest.php b/vendor/guzzlehttp/psr7/tests/CachingStreamTest.php new file mode 100644 index 0000000000..a1fa04aea6 --- /dev/null +++ b/vendor/guzzlehttp/psr7/tests/CachingStreamTest.php @@ -0,0 +1,212 @@ +decorated = Psr7\Utils::streamFor('testing'); + $this->body = new CachingStream($this->decorated); + } + + /** + * @after + */ + public function tearDownTest() + { + $this->decorated->close(); + $this->body->close(); + } + + public function testUsesRemoteSizeIfAvailable() + { + $body = Psr7\Utils::streamFor('test'); + $caching = new CachingStream($body); + self::assertSame(4, $caching->getSize()); + } + + public function testUsesRemoteSizeIfNotAvailable() + { + $body = new Psr7\PumpStream(function () { + return 'a'; + }); + $caching = new CachingStream($body); + self::assertNull($caching->getSize()); + } + + public function testReadsUntilCachedToByte() + { + $this->body->seek(5); + self::assertSame('n', $this->body->read(1)); + $this->body->seek(0); + self::assertSame('t', $this->body->read(1)); + } + + public function testCanSeekNearEndWithSeekEnd() + { + $baseStream = Psr7\Utils::streamFor(implode('', range('a', 'z'))); + $cached = new CachingStream($baseStream); + $cached->seek(-1, SEEK_END); + self::assertSame(25, $baseStream->tell()); + self::assertSame('z', $cached->read(1)); + self::assertSame(26, $cached->getSize()); + } + + public function testCanSeekToEndWithSeekEnd() + { + $baseStream = Psr7\Utils::streamFor(implode('', range('a', 'z'))); + $cached = new CachingStream($baseStream); + $cached->seek(0, SEEK_END); + self::assertSame(26, $baseStream->tell()); + self::assertSame('', $cached->read(1)); + self::assertSame(26, $cached->getSize()); + } + + public function testCanUseSeekEndWithUnknownSize() + { + $baseStream = Psr7\Utils::streamFor('testing'); + $decorated = Psr7\FnStream::decorate($baseStream, [ + 'getSize' => function () { + return null; + } + ]); + $cached = new CachingStream($decorated); + $cached->seek(-1, SEEK_END); + self::assertSame('g', $cached->read(1)); + } + + public function testRewindUsesSeek() + { + $a = Psr7\Utils::streamFor('foo'); + $d = $this->getMockBuilder('GuzzleHttp\Psr7\CachingStream') + ->setMethods(['seek']) + ->setConstructorArgs([$a]) + ->getMock(); + $d->expects(self::once()) + ->method('seek') + ->with(0) + ->will(self::returnValue(true)); + $d->seek(0); + } + + public function testCanSeekToReadBytes() + { + self::assertSame('te', $this->body->read(2)); + $this->body->seek(0); + self::assertSame('test', $this->body->read(4)); + self::assertSame(4, $this->body->tell()); + $this->body->seek(2); + self::assertSame(2, $this->body->tell()); + $this->body->seek(2, SEEK_CUR); + self::assertSame(4, $this->body->tell()); + self::assertSame('ing', $this->body->read(3)); + } + + public function testCanSeekToReadBytesWithPartialBodyReturned() + { + $stream = fopen('php://temp', 'r+'); + fwrite($stream, 'testing'); + fseek($stream, 0); + + $this->decorated = $this->getMockBuilder('\GuzzleHttp\Psr7\Stream') + ->setConstructorArgs([$stream]) + ->setMethods(['read']) + ->getMock(); + + $this->decorated->expects(self::exactly(2)) + ->method('read') + ->willReturnCallback(function ($length) use ($stream) { + return fread($stream, 2); + }); + + $this->body = new CachingStream($this->decorated); + + self::assertSame(0, $this->body->tell()); + $this->body->seek(4, SEEK_SET); + self::assertSame(4, $this->body->tell()); + + $this->body->seek(0); + self::assertSame('test', $this->body->read(4)); + } + + public function testWritesToBufferStream() + { + $this->body->read(2); + $this->body->write('hi'); + $this->body->seek(0); + self::assertSame('tehiing', (string) $this->body); + } + + public function testSkipsOverwrittenBytes() + { + $decorated = Psr7\Utils::streamFor( + implode("\n", array_map(function ($n) { + return str_pad($n, 4, '0', STR_PAD_LEFT); + }, range(0, 25))) + ); + + $body = new CachingStream($decorated); + + self::assertSame("0000\n", Psr7\Utils::readLine($body)); + self::assertSame("0001\n", Psr7\Utils::readLine($body)); + // Write over part of the body yet to be read, so skip some bytes + self::assertSame(5, $body->write("TEST\n")); + self::assertSame(5, Helpers::readObjectAttribute($body, 'skipReadBytes')); + // Read, which skips bytes, then reads + self::assertSame("0003\n", Psr7\Utils::readLine($body)); + self::assertSame(0, Helpers::readObjectAttribute($body, 'skipReadBytes')); + self::assertSame("0004\n", Psr7\Utils::readLine($body)); + self::assertSame("0005\n", Psr7\Utils::readLine($body)); + + // Overwrite part of the cached body (so don't skip any bytes) + $body->seek(5); + self::assertSame(5, $body->write("ABCD\n")); + self::assertSame(0, Helpers::readObjectAttribute($body, 'skipReadBytes')); + self::assertSame("TEST\n", Psr7\Utils::readLine($body)); + self::assertSame("0003\n", Psr7\Utils::readLine($body)); + self::assertSame("0004\n", Psr7\Utils::readLine($body)); + self::assertSame("0005\n", Psr7\Utils::readLine($body)); + self::assertSame("0006\n", Psr7\Utils::readLine($body)); + self::assertSame(5, $body->write("1234\n")); + self::assertSame(5, Helpers::readObjectAttribute($body, 'skipReadBytes')); + + // Seek to 0 and ensure the overwritten bit is replaced + $body->seek(0); + self::assertSame("0000\nABCD\nTEST\n0003\n0004\n0005\n0006\n1234\n0008\n0009\n", $body->read(50)); + + // Ensure that casting it to a string does not include the bit that was overwritten + $this->assertStringContainsStringGuzzle("0000\nABCD\nTEST\n0003\n0004\n0005\n0006\n1234\n0008\n0009\n", (string) $body); + } + + public function testClosesBothStreams() + { + $s = fopen('php://temp', 'r'); + $a = Psr7\Utils::streamFor($s); + $d = new CachingStream($a); + $d->close(); + self::assertFalse(is_resource($s)); + } + + public function testEnsuresValidWhence() + { + $this->expectExceptionGuzzle('InvalidArgumentException'); + + $this->body->seek(10, -123456); + } +} diff --git a/vendor/guzzlehttp/psr7/tests/DroppingStreamTest.php b/vendor/guzzlehttp/psr7/tests/DroppingStreamTest.php new file mode 100644 index 0000000000..15dd3015f1 --- /dev/null +++ b/vendor/guzzlehttp/psr7/tests/DroppingStreamTest.php @@ -0,0 +1,27 @@ +write('hel')); + self::assertSame(2, $drop->write('lo')); + self::assertSame(5, $drop->getSize()); + self::assertSame('hello', $drop->read(5)); + self::assertSame(0, $drop->getSize()); + $drop->write('12345678910'); + self::assertSame(5, $stream->getSize()); + self::assertSame(5, $drop->getSize()); + self::assertSame('12345', (string) $drop); + self::assertSame(0, $drop->getSize()); + $drop->write('hello'); + self::assertSame(0, $drop->write('test')); + } +} diff --git a/vendor/guzzlehttp/psr7/tests/FnStreamTest.php b/vendor/guzzlehttp/psr7/tests/FnStreamTest.php new file mode 100644 index 0000000000..530a816199 --- /dev/null +++ b/vendor/guzzlehttp/psr7/tests/FnStreamTest.php @@ -0,0 +1,98 @@ +expectExceptionGuzzle('BadMethodCallException', 'seek() is not implemented in the FnStream'); + + (new FnStream([]))->seek(1); + } + + public function testProxiesToFunction() + { + $s = new FnStream([ + 'read' => function ($len) { + $this->assertSame(3, $len); + return 'foo'; + } + ]); + + self::assertSame('foo', $s->read(3)); + } + + public function testCanCloseOnDestruct() + { + $called = false; + $s = new FnStream([ + 'close' => function () use (&$called) { + $called = true; + } + ]); + unset($s); + self::assertTrue($called); + } + + public function testDoesNotRequireClose() + { + $s = new FnStream([]); + unset($s); + self::assertTrue(true); // strict mode requires an assertion + } + + public function testDecoratesStream() + { + $a = Psr7\Utils::streamFor('foo'); + $b = FnStream::decorate($a, []); + self::assertSame(3, $b->getSize()); + self::assertSame($b->isWritable(), true); + self::assertSame($b->isReadable(), true); + self::assertSame($b->isSeekable(), true); + self::assertSame($b->read(3), 'foo'); + self::assertSame($b->tell(), 3); + self::assertSame($a->tell(), 3); + self::assertSame('', $a->read(1)); + self::assertSame($b->eof(), true); + self::assertSame($a->eof(), true); + $b->seek(0); + self::assertSame('foo', (string) $b); + $b->seek(0); + self::assertSame('foo', $b->getContents()); + self::assertSame($a->getMetadata(), $b->getMetadata()); + $b->seek(0, SEEK_END); + $b->write('bar'); + self::assertSame('foobar', (string) $b); + $this->assertInternalTypeGuzzle('resource', $b->detach()); + $b->close(); + } + + public function testDecoratesWithCustomizations() + { + $called = false; + $a = Psr7\Utils::streamFor('foo'); + $b = FnStream::decorate($a, [ + 'read' => function ($len) use (&$called, $a) { + $called = true; + return $a->read($len); + } + ]); + self::assertSame('foo', $b->read(3)); + self::assertTrue($called); + } + + public function testDoNotAllowUnserialization() + { + $a = new FnStream([]); + $b = serialize($a); + $this->expectExceptionGuzzle('\LogicException', 'FnStream should never be unserialized'); + unserialize($b); + } +} diff --git a/vendor/guzzlehttp/psr7/tests/HasToString.php b/vendor/guzzlehttp/psr7/tests/HasToString.php new file mode 100644 index 0000000000..7a9d737477 --- /dev/null +++ b/vendor/guzzlehttp/psr7/tests/HasToString.php @@ -0,0 +1,11 @@ +', + 'rel' => 'front', + 'type' => 'image/jpeg', + ], + [ + '', + 'rel' => 'back', + 'type' => 'image/jpeg', + ], + ]; + return [ + [ + '; rel="front"; type="image/jpeg", ; rel=back; type="image/jpeg"', + $res1, + ], + [ + '; rel="front"; type="image/jpeg",; rel=back; type="image/jpeg"', + $res1, + ], + [ + 'foo="baz"; bar=123, boo, test="123", foobar="foo;bar"', + [ + ['foo' => 'baz', 'bar' => '123'], + ['boo'], + ['test' => '123'], + ['foobar' => 'foo;bar'], + ], + ], + [ + '; rel="side"; type="image/jpeg",; rel=side; type="image/jpeg"', + [ + ['', 'rel' => 'side', 'type' => 'image/jpeg'], + ['', 'rel' => 'side', 'type' => 'image/jpeg'], + ], + ], + [ + '', + [], + ], + ]; + } + + /** + * @dataProvider parseParamsProvider + */ + public function testParseParams($header, $result) + { + self::assertSame($result, Psr7\Header::parse($header)); + } + + public function testParsesArrayHeaders() + { + $header = ['a, b', 'c', 'd, e']; + self::assertSame(['a', 'b', 'c', 'd', 'e'], Psr7\Header::normalize($header)); + } +} diff --git a/vendor/guzzlehttp/psr7/tests/Helpers.php b/vendor/guzzlehttp/psr7/tests/Helpers.php new file mode 100644 index 0000000000..6ff31a69dc --- /dev/null +++ b/vendor/guzzlehttp/psr7/tests/Helpers.php @@ -0,0 +1,35 @@ +getProperty($attributeName); + + if (!$attribute || $attribute->isPublic()) { + return $object->$attributeName; + } + + $attribute->setAccessible(true); + + return $attribute->getValue($object); + } catch (\ReflectionException $e) { + // do nothing + } + } while ($reflector = $reflector->getParentClass()); + + throw new \Exception( + sprintf('Attribute "%s" not found in object.', $attributeName) + ); + } +} diff --git a/vendor/guzzlehttp/psr7/tests/InflateStreamTest.php b/vendor/guzzlehttp/psr7/tests/InflateStreamTest.php new file mode 100644 index 0000000000..a97b287447 --- /dev/null +++ b/vendor/guzzlehttp/psr7/tests/InflateStreamTest.php @@ -0,0 +1,51 @@ +getGzipStringWithFilename('test'); + $a = Psr7\Utils::streamFor($content); + $b = new InflateStream($a); + self::assertSame('test', (string) $b); + } + + public function testInflatesStreamsPreserveSeekable() + { + $content = $this->getGzipStringWithFilename('test'); + $seekable = Psr7\Utils::streamFor($content); + $nonSeekable = new NoSeekStream(Psr7\Utils::streamFor($content)); + + self::assertTrue((new InflateStream($seekable))->isSeekable()); + self::assertFalse((new InflateStream($nonSeekable))->isSeekable()); + } + + private function getGzipStringWithFilename($original_string) + { + $gzipped = bin2hex(gzencode($original_string)); + + $header = substr($gzipped, 0, 20); + // set FNAME flag + $header[6]=0; + $header[7]=8; + // make a dummy filename + $filename = '64756d6d7900'; + $rest = substr($gzipped, 20); + + return hex2bin($header . $filename . $rest); + } +} diff --git a/vendor/guzzlehttp/psr7/tests/Integration/ServerRequestFromGlobalsTest.php b/vendor/guzzlehttp/psr7/tests/Integration/ServerRequestFromGlobalsTest.php new file mode 100644 index 0000000000..ff95b73edf --- /dev/null +++ b/vendor/guzzlehttp/psr7/tests/Integration/ServerRequestFromGlobalsTest.php @@ -0,0 +1,45 @@ +getServerUri()) { + self::markTestSkipped(); + } + parent::setUp(); + } + + public function testBodyExists() + { + $curl = curl_init(); + + curl_setopt($curl, CURLOPT_URL, $this->getServerUri()); + curl_setopt($curl, CURLOPT_POST, 1); + curl_setopt($curl, CURLOPT_POSTFIELDS, 'foobar'); + curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); + $response = curl_exec($curl); + curl_close($curl); + + self::assertNotFalse($response); + $data = json_decode($response, true); + self::assertIsArray($data); + self::assertArrayHasKey('method', $data); + self::assertArrayHasKey('uri', $data); + self::assertArrayHasKey('body', $data); + + self::assertEquals('foobar', $data['body']); + } + + private function getServerUri() + { + return isset($_SERVER['TEST_SERVER']) ? $_SERVER['TEST_SERVER'] : false; + } +} diff --git a/vendor/guzzlehttp/psr7/tests/Integration/server.php b/vendor/guzzlehttp/psr7/tests/Integration/server.php new file mode 100644 index 0000000000..9539e626b4 --- /dev/null +++ b/vendor/guzzlehttp/psr7/tests/Integration/server.php @@ -0,0 +1,13 @@ + $request->getMethod(), + 'uri' => $request->getUri()->__toString(), + 'body' => $request->getBody()->__toString(), +]; + +echo json_encode($output); diff --git a/vendor/guzzlehttp/psr7/tests/LazyOpenStreamTest.php b/vendor/guzzlehttp/psr7/tests/LazyOpenStreamTest.php new file mode 100644 index 0000000000..0eca3c5993 --- /dev/null +++ b/vendor/guzzlehttp/psr7/tests/LazyOpenStreamTest.php @@ -0,0 +1,71 @@ +fname = tempnam(sys_get_temp_dir(), 'tfile'); + + if (file_exists($this->fname)) { + unlink($this->fname); + } + } + + /** + * @after + */ + public function tearDownTest() + { + if (file_exists($this->fname)) { + unlink($this->fname); + } + } + + public function testOpensLazily() + { + $l = new LazyOpenStream($this->fname, 'w+'); + $l->write('foo'); + $this->assertInternalTypeGuzzle('array', $l->getMetadata()); + self::assertFileExists($this->fname); + self::assertSame('foo', file_get_contents($this->fname)); + self::assertSame('foo', (string) $l); + } + + public function testProxiesToFile() + { + file_put_contents($this->fname, 'foo'); + $l = new LazyOpenStream($this->fname, 'r'); + self::assertSame('foo', $l->read(4)); + self::assertTrue($l->eof()); + self::assertSame(3, $l->tell()); + self::assertTrue($l->isReadable()); + self::assertTrue($l->isSeekable()); + self::assertFalse($l->isWritable()); + $l->seek(1); + self::assertSame('oo', $l->getContents()); + self::assertSame('foo', (string) $l); + self::assertSame(3, $l->getSize()); + $this->assertInternalTypeGuzzle('array', $l->getMetadata()); + $l->close(); + } + + public function testDetachesUnderlyingStream() + { + file_put_contents($this->fname, 'foo'); + $l = new LazyOpenStream($this->fname, 'r'); + $r = $l->detach(); + $this->assertInternalTypeGuzzle('resource', $r); + fseek($r, 0); + self::assertSame('foo', stream_get_contents($r)); + fclose($r); + } +} diff --git a/vendor/guzzlehttp/psr7/tests/LimitStreamTest.php b/vendor/guzzlehttp/psr7/tests/LimitStreamTest.php new file mode 100644 index 0000000000..62dc0b26ad --- /dev/null +++ b/vendor/guzzlehttp/psr7/tests/LimitStreamTest.php @@ -0,0 +1,166 @@ +decorated = Psr7\Utils::streamFor(fopen(__FILE__, 'r')); + $this->body = new LimitStream($this->decorated, 10, 3); + } + + public function testReturnsSubset() + { + $body = new LimitStream(Psr7\Utils::streamFor('foo'), -1, 1); + self::assertSame('oo', (string) $body); + self::assertTrue($body->eof()); + $body->seek(0); + self::assertFalse($body->eof()); + self::assertSame('oo', $body->read(100)); + self::assertSame('', $body->read(1)); + self::assertTrue($body->eof()); + } + + public function testReturnsSubsetWhenCastToString() + { + $body = Psr7\Utils::streamFor('foo_baz_bar'); + $limited = new LimitStream($body, 3, 4); + self::assertSame('baz', (string) $limited); + } + + public function testReturnsSubsetOfEmptyBodyWhenCastToString() + { + $body = Psr7\Utils::streamFor('01234567891234'); + $limited = new LimitStream($body, 0, 10); + self::assertSame('', (string) $limited); + } + + public function testReturnsSpecificSubsetOBodyWhenCastToString() + { + $body = Psr7\Utils::streamFor('0123456789abcdef'); + $limited = new LimitStream($body, 3, 10); + self::assertSame('abc', (string) $limited); + } + + public function testSeeksWhenConstructed() + { + self::assertSame(0, $this->body->tell()); + self::assertSame(3, $this->decorated->tell()); + } + + public function testAllowsBoundedSeek() + { + $this->body->seek(100); + self::assertSame(10, $this->body->tell()); + self::assertSame(13, $this->decorated->tell()); + $this->body->seek(0); + self::assertSame(0, $this->body->tell()); + self::assertSame(3, $this->decorated->tell()); + try { + $this->body->seek(-10); + self::fail(); + } catch (\RuntimeException $e) { + } + self::assertSame(0, $this->body->tell()); + self::assertSame(3, $this->decorated->tell()); + $this->body->seek(5); + self::assertSame(5, $this->body->tell()); + self::assertSame(8, $this->decorated->tell()); + // Fail + try { + $this->body->seek(1000, SEEK_END); + self::fail(); + } catch (\RuntimeException $e) { + } + } + + public function testReadsOnlySubsetOfData() + { + $data = $this->body->read(100); + self::assertSame(10, strlen($data)); + self::assertSame('', $this->body->read(1000)); + + $this->body->setOffset(10); + $newData = $this->body->read(100); + self::assertSame(10, strlen($newData)); + self::assertNotSame($data, $newData); + } + + public function testThrowsWhenCurrentGreaterThanOffsetSeek() + { + $a = Psr7\Utils::streamFor('foo_bar'); + $b = new NoSeekStream($a); + $c = new LimitStream($b); + $a->getContents(); + + $this->expectExceptionGuzzle('RuntimeException', 'Could not seek to stream offset 2'); + + $c->setOffset(2); + } + + public function testCanGetContentsWithoutSeeking() + { + $a = Psr7\Utils::streamFor('foo_bar'); + $b = new NoSeekStream($a); + $c = new LimitStream($b); + self::assertSame('foo_bar', $c->getContents()); + } + + public function testClaimsConsumedWhenReadLimitIsReached() + { + self::assertFalse($this->body->eof()); + $this->body->read(1000); + self::assertTrue($this->body->eof()); + } + + public function testContentLengthIsBounded() + { + self::assertSame(10, $this->body->getSize()); + } + + public function testGetContentsIsBasedOnSubset() + { + $body = new LimitStream(Psr7\Utils::streamFor('foobazbar'), 3, 3); + self::assertSame('baz', $body->getContents()); + } + + public function testReturnsNullIfSizeCannotBeDetermined() + { + $a = new FnStream([ + 'getSize' => function () { + return null; + }, + 'tell' => function () { + return 0; + }, + ]); + $b = new LimitStream($a); + self::assertNull($b->getSize()); + } + + public function testLengthLessOffsetWhenNoLimitSize() + { + $a = Psr7\Utils::streamFor('foo_bar'); + $b = new LimitStream($a, -1, 4); + self::assertSame(3, $b->getSize()); + } +} diff --git a/vendor/guzzlehttp/psr7/tests/MessageTest.php b/vendor/guzzlehttp/psr7/tests/MessageTest.php new file mode 100644 index 0000000000..339b3ab3b7 --- /dev/null +++ b/vendor/guzzlehttp/psr7/tests/MessageTest.php @@ -0,0 +1,266 @@ + 'bar', + 'Qux' => 'ipsum', + ], 'hello', '1.0'); + self::assertSame( + "PUT /hi?123 HTTP/1.0\r\nHost: foo.com\r\nBaz: bar\r\nQux: ipsum\r\n\r\nhello", + Psr7\Message::toString($request) + ); + } + + public function testConvertsResponsesToStrings() + { + $response = new Psr7\Response(200, [ + 'Baz' => 'bar', + 'Qux' => 'ipsum', + ], 'hello', '1.0', 'FOO'); + self::assertSame( + "HTTP/1.0 200 FOO\r\nBaz: bar\r\nQux: ipsum\r\n\r\nhello", + Psr7\Message::toString($response) + ); + } + + public function testCorrectlyRendersSetCookieHeadersToString() + { + $response = new Psr7\Response(200, [ + 'Set-Cookie' => ['bar','baz','qux'] + ], 'hello', '1.0', 'FOO'); + self::assertSame( + "HTTP/1.0 200 FOO\r\nSet-Cookie: bar\r\nSet-Cookie: baz\r\nSet-Cookie: qux\r\n\r\nhello", + Psr7\Message::toString($response) + ); + } + + public function testRewindsBody() + { + $body = Psr7\Utils::streamFor('abc'); + $res = new Psr7\Response(200, [], $body); + Psr7\Message::rewindBody($res); + self::assertSame(0, $body->tell()); + $body->rewind(); + Psr7\Message::rewindBody($res); + self::assertSame(0, $body->tell()); + } + + public function testThrowsWhenBodyCannotBeRewound() + { + $body = Psr7\Utils::streamFor('abc'); + $body->read(1); + $body = FnStream::decorate($body, [ + 'rewind' => function () { + throw new \RuntimeException('a'); + }, + ]); + $res = new Psr7\Response(200, [], $body); + + $this->expectExceptionGuzzle('RuntimeException'); + + Psr7\Message::rewindBody($res); + } + + public function testParsesRequestMessages() + { + $req = "GET /abc HTTP/1.0\r\nHost: foo.com\r\nFoo: Bar\r\nBaz: Bam\r\nBaz: Qux\r\n\r\nTest"; + $request = Psr7\Message::parseRequest($req); + self::assertSame('GET', $request->getMethod()); + self::assertSame('/abc', $request->getRequestTarget()); + self::assertSame('1.0', $request->getProtocolVersion()); + self::assertSame('foo.com', $request->getHeaderLine('Host')); + self::assertSame('Bar', $request->getHeaderLine('Foo')); + self::assertSame('Bam, Qux', $request->getHeaderLine('Baz')); + self::assertSame('Test', (string)$request->getBody()); + self::assertSame('http://foo.com/abc', (string)$request->getUri()); + } + + public function testParsesRequestMessagesWithHttpsScheme() + { + $req = "PUT /abc?baz=bar HTTP/1.1\r\nHost: foo.com:443\r\n\r\n"; + $request = Psr7\Message::parseRequest($req); + self::assertSame('PUT', $request->getMethod()); + self::assertSame('/abc?baz=bar', $request->getRequestTarget()); + self::assertSame('1.1', $request->getProtocolVersion()); + self::assertSame('foo.com:443', $request->getHeaderLine('Host')); + self::assertSame('', (string)$request->getBody()); + self::assertSame('https://foo.com/abc?baz=bar', (string)$request->getUri()); + } + + public function testParsesRequestMessagesWithUriWhenHostIsNotFirst() + { + $req = "PUT / HTTP/1.1\r\nFoo: Bar\r\nHost: foo.com\r\n\r\n"; + $request = Psr7\Message::parseRequest($req); + self::assertSame('PUT', $request->getMethod()); + self::assertSame('/', $request->getRequestTarget()); + self::assertSame('http://foo.com/', (string)$request->getUri()); + } + + public function testParsesRequestMessagesWithFullUri() + { + $req = "GET https://www.google.com:443/search?q=foobar HTTP/1.1\r\nHost: www.google.com\r\n\r\n"; + $request = Psr7\Message::parseRequest($req); + self::assertSame('GET', $request->getMethod()); + self::assertSame('https://www.google.com:443/search?q=foobar', $request->getRequestTarget()); + self::assertSame('1.1', $request->getProtocolVersion()); + self::assertSame('www.google.com', $request->getHeaderLine('Host')); + self::assertSame('', (string)$request->getBody()); + self::assertSame('https://www.google.com/search?q=foobar', (string)$request->getUri()); + } + + public function testParsesRequestMessagesWithCustomMethod() + { + $req = "GET_DATA / HTTP/1.1\r\nFoo: Bar\r\nHost: foo.com\r\n\r\n"; + $request = Psr7\Message::parseRequest($req); + self::assertSame('GET_DATA', $request->getMethod()); + } + + public function testParsesRequestMessagesWithFoldedHeadersOnHttp10() + { + $req = "PUT / HTTP/1.0\r\nFoo: Bar\r\n Bam\r\n\r\n"; + $request = Psr7\Message::parseRequest($req); + self::assertSame('PUT', $request->getMethod()); + self::assertSame('/', $request->getRequestTarget()); + self::assertSame('Bar Bam', $request->getHeaderLine('Foo')); + } + + public function testRequestParsingFailsWithFoldedHeadersOnHttp11() + { + $this->expectExceptionGuzzle('InvalidArgumentException', 'Invalid header syntax: Obsolete line folding'); + + Psr7\Message::parseResponse("GET_DATA / HTTP/1.1\r\nFoo: Bar\r\n Biz: Bam\r\n\r\n"); + } + + public function testParsesRequestMessagesWhenHeaderDelimiterIsOnlyALineFeed() + { + $req = "PUT / HTTP/1.0\nFoo: Bar\nBaz: Bam\n\n"; + $request = Psr7\Message::parseRequest($req); + self::assertSame('PUT', $request->getMethod()); + self::assertSame('/', $request->getRequestTarget()); + self::assertSame('Bar', $request->getHeaderLine('Foo')); + self::assertSame('Bam', $request->getHeaderLine('Baz')); + } + + public function testValidatesRequestMessages() + { + $this->expectExceptionGuzzle('InvalidArgumentException'); + + Psr7\Message::parseRequest("HTTP/1.1 200 OK\r\n\r\n"); + } + + public function testParsesResponseMessages() + { + $res = "HTTP/1.0 200 OK\r\nFoo: Bar\r\nBaz: Bam\r\nBaz: Qux\r\n\r\nTest"; + $response = Psr7\Message::parseResponse($res); + self::assertSame(200, $response->getStatusCode()); + self::assertSame('OK', $response->getReasonPhrase()); + self::assertSame('1.0', $response->getProtocolVersion()); + self::assertSame('Bar', $response->getHeaderLine('Foo')); + self::assertSame('Bam, Qux', $response->getHeaderLine('Baz')); + self::assertSame('Test', (string)$response->getBody()); + } + + public function testParsesResponseWithoutReason() + { + $res = "HTTP/1.0 200\r\nFoo: Bar\r\nBaz: Bam\r\nBaz: Qux\r\n\r\nTest"; + $response = Psr7\Message::parseResponse($res); + self::assertSame(200, $response->getStatusCode()); + self::assertSame('OK', $response->getReasonPhrase()); + self::assertSame('1.0', $response->getProtocolVersion()); + self::assertSame('Bar', $response->getHeaderLine('Foo')); + self::assertSame('Bam, Qux', $response->getHeaderLine('Baz')); + self::assertSame('Test', (string)$response->getBody()); + } + + public function testParsesResponseWithLeadingDelimiter() + { + $res = "\r\nHTTP/1.0 200\r\nFoo: Bar\r\n\r\nTest"; + $response = Psr7\Message::parseResponse($res); + self::assertSame(200, $response->getStatusCode()); + self::assertSame('OK', $response->getReasonPhrase()); + self::assertSame('1.0', $response->getProtocolVersion()); + self::assertSame('Bar', $response->getHeaderLine('Foo')); + self::assertSame('Test', (string)$response->getBody()); + } + + public function testParsesResponseWithFoldedHeadersOnHttp10() + { + $res = "HTTP/1.0 200\r\nFoo: Bar\r\n Bam\r\n\r\nTest"; + $response = Psr7\Message::parseResponse($res); + self::assertSame(200, $response->getStatusCode()); + self::assertSame('OK', $response->getReasonPhrase()); + self::assertSame('1.0', $response->getProtocolVersion()); + self::assertSame('Bar Bam', $response->getHeaderLine('Foo')); + self::assertSame('Test', (string)$response->getBody()); + } + + public function testResponseParsingFailsWithFoldedHeadersOnHttp11() + { + $this->expectExceptionGuzzle('InvalidArgumentException', 'Invalid header syntax: Obsolete line folding'); + + Psr7\Message::parseResponse("HTTP/1.1 200\r\nFoo: Bar\r\n Biz: Bam\r\nBaz: Qux\r\n\r\nTest"); + } + + public function testParsesResponseWhenHeaderDelimiterIsOnlyALineFeed() + { + $res = "HTTP/1.0 200\nFoo: Bar\nBaz: Bam\n\nTest\n\nOtherTest"; + $response = Psr7\Message::parseResponse($res); + self::assertSame(200, $response->getStatusCode()); + self::assertSame('OK', $response->getReasonPhrase()); + self::assertSame('1.0', $response->getProtocolVersion()); + self::assertSame('Bar', $response->getHeaderLine('Foo')); + self::assertSame('Bam', $response->getHeaderLine('Baz')); + self::assertSame("Test\n\nOtherTest", (string)$response->getBody()); + } + + public function testResponseParsingFailsWithoutHeaderDelimiter() + { + $this->expectExceptionGuzzle('InvalidArgumentException', 'Invalid message: Missing header delimiter'); + + Psr7\Message::parseResponse("HTTP/1.0 200\r\nFoo: Bar\r\n Baz: Bam\r\nBaz: Qux\r\n"); + } + + public function testValidatesResponseMessages() + { + $this->expectExceptionGuzzle('InvalidArgumentException'); + + Psr7\Message::parseResponse("GET / HTTP/1.1\r\n\r\n"); + } + + public function testMessageBodySummaryWithSmallBody() + { + $message = new Psr7\Response(200, [], 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.'); + self::assertSame('Lorem ipsum dolor sit amet, consectetur adipiscing elit.', Psr7\Message::bodySummary($message)); + } + + public function testMessageBodySummaryWithLargeBody() + { + $message = new Psr7\Response(200, [], 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.'); + self::assertSame('Lorem ipsu (truncated...)', Psr7\Message::bodySummary($message, 10)); + } + + public function testMessageBodySummaryWithSpecialUTF8Characters() + { + $message = new Psr7\Response(200, [], '’é€௵ဪ‱'); + self::assertSame('’é€௵ဪ‱', Psr7\Message::bodySummary($message)); + } + + public function testMessageBodySummaryWithEmptyBody() + { + $message = new Psr7\Response(200, [], ''); + self::assertNull(Psr7\Message::bodySummary($message)); + } + + public function testGetResponseBodySummaryOfNonReadableStream() + { + self::assertNull(Psr7\Message::bodySummary(new Psr7\Response(500, [], new ReadSeekOnlyStream()))); + } +} diff --git a/vendor/guzzlehttp/psr7/tests/MimeTypeTest.php b/vendor/guzzlehttp/psr7/tests/MimeTypeTest.php new file mode 100644 index 0000000000..169e20b549 --- /dev/null +++ b/vendor/guzzlehttp/psr7/tests/MimeTypeTest.php @@ -0,0 +1,22 @@ +getBoundary()); + } + + public function testCanProvideBoundary() + { + $b = new MultipartStream([], 'foo'); + self::assertSame('foo', $b->getBoundary()); + } + + public function testIsNotWritable() + { + $b = new MultipartStream(); + self::assertFalse($b->isWritable()); + } + + public function testCanCreateEmptyStream() + { + $b = new MultipartStream(); + $boundary = $b->getBoundary(); + self::assertSame("--{$boundary}--\r\n", $b->getContents()); + self::assertSame(strlen($boundary) + 6, $b->getSize()); + } + + public function testValidatesFilesArrayElement() + { + $this->expectExceptionGuzzle('InvalidArgumentException'); + + new MultipartStream([['foo' => 'bar']]); + } + + public function testEnsuresFileHasName() + { + $this->expectExceptionGuzzle('InvalidArgumentException'); + + new MultipartStream([['contents' => 'bar']]); + } + + public function testSerializesFields() + { + $b = new MultipartStream([ + [ + 'name' => 'foo', + 'contents' => 'bar' + ], + [ + 'name' => 'baz', + 'contents' => 'bam' + ] + ], 'boundary'); + self::assertSame( + "--boundary\r\nContent-Disposition: form-data; name=\"foo\"\r\nContent-Length: 3\r\n\r\n" + . "bar\r\n--boundary\r\nContent-Disposition: form-data; name=\"baz\"\r\nContent-Length: 3" + . "\r\n\r\nbam\r\n--boundary--\r\n", + (string) $b + ); + } + + public function testSerializesNonStringFields() + { + $b = new MultipartStream([ + [ + 'name' => 'int', + 'contents' => (int) 1 + ], + [ + 'name' => 'bool', + 'contents' => (boolean) false + ], + [ + 'name' => 'bool2', + 'contents' => (boolean) true + ], + [ + 'name' => 'float', + 'contents' => (float) 1.1 + ] + ], 'boundary'); + self::assertSame( + "--boundary\r\nContent-Disposition: form-data; name=\"int\"\r\nContent-Length: 1\r\n\r\n" + . "1\r\n--boundary\r\nContent-Disposition: form-data; name=\"bool\"\r\n\r\n\r\n--boundary" + . "\r\nContent-Disposition: form-data; name=\"bool2\"\r\nContent-Length: 1\r\n\r\n" + . "1\r\n--boundary\r\nContent-Disposition: form-data; name=\"float\"\r\nContent-Length: 3" + . "\r\n\r\n1.1\r\n--boundary--\r\n", + (string) $b + ); + } + + public function testSerializesFiles() + { + $f1 = Psr7\FnStream::decorate(Psr7\Utils::streamFor('foo'), [ + 'getMetadata' => function () { + return '/foo/bar.txt'; + } + ]); + + $f2 = Psr7\FnStream::decorate(Psr7\Utils::streamFor('baz'), [ + 'getMetadata' => function () { + return '/foo/baz.jpg'; + } + ]); + + $f3 = Psr7\FnStream::decorate(Psr7\Utils::streamFor('bar'), [ + 'getMetadata' => function () { + return '/foo/bar.gif'; + } + ]); + + $b = new MultipartStream([ + [ + 'name' => 'foo', + 'contents' => $f1 + ], + [ + 'name' => 'qux', + 'contents' => $f2 + ], + [ + 'name' => 'qux', + 'contents' => $f3 + ], + ], 'boundary'); + + $expected = << function () { + return '/foo/bar.txt'; + } + ]); + + $b = new MultipartStream([ + [ + 'name' => 'foo', + 'contents' => $f1, + 'headers' => [ + 'x-foo' => 'bar', + 'content-disposition' => 'custom' + ] + ] + ], 'boundary'); + + $expected = << function () { + return '/foo/bar.txt'; + } + ]); + + $f2 = Psr7\FnStream::decorate(Psr7\Utils::streamFor('baz'), [ + 'getMetadata' => function () { + return '/foo/baz.jpg'; + } + ]); + + $b = new MultipartStream([ + [ + 'name' => 'foo', + 'contents' => $f1, + 'headers' => [ + 'x-foo' => 'bar', + 'content-disposition' => 'custom' + ] + ], + [ + 'name' => 'foo', + 'contents' => $f2, + 'headers' => ['cOntenT-Type' => 'custom'], + ] + ], 'boundary'); + + $expected = <<getMockBuilder('Psr\Http\Message\StreamInterface') + ->setMethods(['isSeekable', 'seek']) + ->getMockForAbstractClass(); + $s->expects(self::never())->method('seek'); + $s->expects(self::never())->method('isSeekable'); + $wrapped = new NoSeekStream($s); + self::assertFalse($wrapped->isSeekable()); + + $this->expectExceptionGuzzle('RuntimeException', 'Cannot seek a NoSeekStream'); + + $wrapped->seek(2); + } + + public function testToStringDoesNotSeek() + { + $s = \GuzzleHttp\Psr7\Utils::streamFor('foo'); + $s->seek(1); + $wrapped = new NoSeekStream($s); + self::assertSame('oo', (string) $wrapped); + + $wrapped->close(); + } +} diff --git a/vendor/guzzlehttp/psr7/tests/PumpStreamTest.php b/vendor/guzzlehttp/psr7/tests/PumpStreamTest.php new file mode 100644 index 0000000000..31d2cba918 --- /dev/null +++ b/vendor/guzzlehttp/psr7/tests/PumpStreamTest.php @@ -0,0 +1,78 @@ + ['foo' => 'bar'], + 'size' => 100 + ]); + + self::assertSame('bar', $p->getMetadata('foo')); + self::assertSame(['foo' => 'bar'], $p->getMetadata()); + self::assertSame(100, $p->getSize()); + } + + public function testCanReadFromCallable() + { + $p = Psr7\Utils::streamFor(function ($size) { + return 'a'; + }); + self::assertSame('a', $p->read(1)); + self::assertSame(1, $p->tell()); + self::assertSame('aaaaa', $p->read(5)); + self::assertSame(6, $p->tell()); + } + + public function testStoresExcessDataInBuffer() + { + $called = []; + $p = Psr7\Utils::streamFor(function ($size) use (&$called) { + $called[] = $size; + return 'abcdef'; + }); + self::assertSame('a', $p->read(1)); + self::assertSame('b', $p->read(1)); + self::assertSame('cdef', $p->read(4)); + self::assertSame('abcdefabc', $p->read(9)); + self::assertSame([1, 9, 3], $called); + } + + public function testInifiniteStreamWrappedInLimitStream() + { + $p = Psr7\Utils::streamFor(function () { + return 'a'; + }); + $s = new LimitStream($p, 5); + self::assertSame('aaaaa', (string) $s); + } + + public function testDescribesCapabilities() + { + $p = Psr7\Utils::streamFor(function () { + }); + self::assertTrue($p->isReadable()); + self::assertFalse($p->isSeekable()); + self::assertFalse($p->isWritable()); + self::assertNull($p->getSize()); + self::assertSame('', $p->getContents()); + self::assertSame('', (string) $p); + $p->close(); + self::assertSame('', $p->read(10)); + self::assertTrue($p->eof()); + + try { + self::assertFalse($p->write('aa')); + self::fail(); + } catch (\RuntimeException $e) { + } + } +} diff --git a/vendor/guzzlehttp/psr7/tests/QueryTest.php b/vendor/guzzlehttp/psr7/tests/QueryTest.php new file mode 100644 index 0000000000..6047fb024c --- /dev/null +++ b/vendor/guzzlehttp/psr7/tests/QueryTest.php @@ -0,0 +1,98 @@ + ['a', 'b']]], + // Can parse multi-valued items that use numeric indices + ['q[0]=a&q[1]=b', ['q[0]' => 'a', 'q[1]' => 'b']], + // Can parse duplicates and does not include numeric indices + ['q[]=a&q[]=b', ['q[]' => ['a', 'b']]], + // Ensures that the value of "q" is an array even though one value + ['q[]=a', ['q[]' => 'a']], + // Does not modify "." to "_" like PHP's parse_str() + ['q.a=a&q.b=b', ['q.a' => 'a', 'q.b' => 'b']], + // Can decode %20 to " " + ['q%20a=a%20b', ['q a' => 'a b']], + // Can parse funky strings with no values by assigning each to null + ['q&a', ['q' => null, 'a' => null]], + // Does not strip trailing equal signs + ['data=abc=', ['data' => 'abc=']], + // Can store duplicates without affecting other values + ['foo=a&foo=b&?µ=c', ['foo' => ['a', 'b'], '?µ' => 'c']], + // Sets value to null when no "=" is present + ['foo', ['foo' => null]], + // Preserves "0" keys. + ['0', ['0' => null]], + // Sets the value to an empty string when "=" is present + ['0=', ['0' => '']], + // Preserves falsey keys + ['var=0', ['var' => '0']], + ['a[b][c]=1&a[b][c]=2', ['a[b][c]' => ['1', '2']]], + ['a[b]=c&a[d]=e', ['a[b]' => 'c', 'a[d]' => 'e']], + // Ensure it doesn't leave things behind with repeated values + // Can parse mult-values items + ['q=a&q=b&q=c', ['q' => ['a', 'b', 'c']]], + ]; + } + + /** + * @dataProvider parseQueryProvider + */ + public function testParsesQueries($input, $output) + { + $result = Psr7\Query::parse($input); + self::assertSame($output, $result); + } + + public function testDoesNotDecode() + { + $str = 'foo%20=bar'; + $data = Psr7\Query::parse($str, false); + self::assertSame(['foo%20' => 'bar'], $data); + } + + /** + * @dataProvider parseQueryProvider + */ + public function testParsesAndBuildsQueries($input) + { + $result = Psr7\Query::parse($input, false); + self::assertSame($input, Psr7\Query::build($result, false)); + } + + public function testEncodesWithRfc1738() + { + $str = Psr7\Query::build(['foo bar' => 'baz+'], PHP_QUERY_RFC1738); + self::assertSame('foo+bar=baz%2B', $str); + } + + public function testEncodesWithRfc3986() + { + $str = Psr7\Query::build(['foo bar' => 'baz+'], PHP_QUERY_RFC3986); + self::assertSame('foo%20bar=baz%2B', $str); + } + + public function testDoesNotEncode() + { + $str = Psr7\Query::build(['foo bar' => 'baz+'], false); + self::assertSame('foo bar=baz+', $str); + } + + public function testCanControlDecodingType() + { + $result = Psr7\Query::parse('var=foo+bar', PHP_QUERY_RFC3986); + self::assertSame('foo+bar', $result['var']); + $result = Psr7\Query::parse('var=foo+bar', PHP_QUERY_RFC1738); + self::assertSame('foo bar', $result['var']); + } +} diff --git a/vendor/guzzlehttp/psr7/tests/ReadSeekOnlyStream.php b/vendor/guzzlehttp/psr7/tests/ReadSeekOnlyStream.php new file mode 100644 index 0000000000..27409ecea1 --- /dev/null +++ b/vendor/guzzlehttp/psr7/tests/ReadSeekOnlyStream.php @@ -0,0 +1,23 @@ +getUri()); + } + + public function testRequestUriMayBeUri() + { + $uri = new Uri('/'); + $r = new Request('GET', $uri); + self::assertSame($uri, $r->getUri()); + } + + public function testValidateRequestUri() + { + $this->expectExceptionGuzzle('InvalidArgumentException'); + + new Request('GET', '///'); + } + + public function testCanConstructWithBody() + { + $r = new Request('GET', '/', [], 'baz'); + self::assertInstanceOf('Psr\Http\Message\StreamInterface', $r->getBody()); + self::assertSame('baz', (string) $r->getBody()); + } + + public function testNullBody() + { + $r = new Request('GET', '/', [], null); + self::assertInstanceOf('Psr\Http\Message\StreamInterface', $r->getBody()); + self::assertSame('', (string) $r->getBody()); + } + + public function testFalseyBody() + { + $r = new Request('GET', '/', [], '0'); + self::assertInstanceOf('Psr\Http\Message\StreamInterface', $r->getBody()); + self::assertSame('0', (string) $r->getBody()); + } + + public function testConstructorDoesNotReadStreamBody() + { + $streamIsRead = false; + $body = Psr7\FnStream::decorate(Psr7\Utils::streamFor(''), [ + '__toString' => function () use (&$streamIsRead) { + $streamIsRead = true; + return ''; + } + ]); + + $r = new Request('GET', '/', [], $body); + self::assertFalse($streamIsRead); + self::assertSame($body, $r->getBody()); + } + + public function testCapitalizesMethod() + { + $r = new Request('get', '/'); + self::assertSame('GET', $r->getMethod()); + } + + public function testCapitalizesWithMethod() + { + $r = new Request('GET', '/'); + self::assertSame('PUT', $r->withMethod('put')->getMethod()); + } + + public function testWithUri() + { + $r1 = new Request('GET', '/'); + $u1 = $r1->getUri(); + $u2 = new Uri('http://www.example.com'); + $r2 = $r1->withUri($u2); + self::assertNotSame($r1, $r2); + self::assertSame($u2, $r2->getUri()); + self::assertSame($u1, $r1->getUri()); + } + + /** + * @dataProvider invalidMethodsProvider + */ + public function testConstructWithInvalidMethods($method) + { + $this->expectExceptionGuzzle('InvalidArgumentException'); + new Request($method, '/'); + } + + /** + * @dataProvider invalidMethodsProvider + */ + public function testWithInvalidMethods($method) + { + $r = new Request('get', '/'); + $this->expectExceptionGuzzle('InvalidArgumentException'); + $r->withMethod($method); + } + + public function invalidMethodsProvider() + { + return [ + [null], + [false], + [['foo']], + [new \stdClass()], + ]; + } + + public function testSameInstanceWhenSameUri() + { + $r1 = new Request('GET', 'http://foo.com'); + $r2 = $r1->withUri($r1->getUri()); + self::assertSame($r1, $r2); + } + + public function testWithRequestTarget() + { + $r1 = new Request('GET', '/'); + $r2 = $r1->withRequestTarget('*'); + self::assertSame('*', $r2->getRequestTarget()); + self::assertSame('/', $r1->getRequestTarget()); + } + + public function testRequestTargetDoesNotAllowSpaces() + { + $this->expectExceptionGuzzle('InvalidArgumentException'); + + $r1 = new Request('GET', '/'); + $r1->withRequestTarget('/foo bar'); + } + + public function testRequestTargetDefaultsToSlash() + { + $r1 = new Request('GET', ''); + self::assertSame('/', $r1->getRequestTarget()); + $r2 = new Request('GET', '*'); + self::assertSame('*', $r2->getRequestTarget()); + $r3 = new Request('GET', 'http://foo.com/bar baz/'); + self::assertSame('/bar%20baz/', $r3->getRequestTarget()); + } + + public function testBuildsRequestTarget() + { + $r1 = new Request('GET', 'http://foo.com/baz?bar=bam'); + self::assertSame('/baz?bar=bam', $r1->getRequestTarget()); + } + + public function testBuildsRequestTargetWithFalseyQuery() + { + $r1 = new Request('GET', 'http://foo.com/baz?0'); + self::assertSame('/baz?0', $r1->getRequestTarget()); + } + + public function testHostIsAddedFirst() + { + $r = new Request('GET', 'http://foo.com/baz?bar=bam', ['Foo' => 'Bar']); + self::assertSame([ + 'Host' => ['foo.com'], + 'Foo' => ['Bar'] + ], $r->getHeaders()); + } + + public function testHeaderValueWithWhitespace() + { + $r = new Request('GET', 'https://example.com/', [ + 'User-Agent' => 'Linux f0f489981e90 5.10.104-linuxkit 1 SMP Wed Mar 9 19:05:23 UTC 2022 x86_64' + ]); + self::assertSame([ + 'Host' => ['example.com'], + 'User-Agent' => ['Linux f0f489981e90 5.10.104-linuxkit 1 SMP Wed Mar 9 19:05:23 UTC 2022 x86_64'] + ], $r->getHeaders()); + } + + public function testCanGetHeaderAsCsv() + { + $r = new Request('GET', 'http://foo.com/baz?bar=bam', [ + 'Foo' => ['a', 'b', 'c'] + ]); + self::assertSame('a, b, c', $r->getHeaderLine('Foo')); + self::assertSame('', $r->getHeaderLine('Bar')); + } + + public function testHostIsNotOverwrittenWhenPreservingHost() + { + $r = new Request('GET', 'http://foo.com/baz?bar=bam', ['Host' => 'a.com']); + self::assertSame(['Host' => ['a.com']], $r->getHeaders()); + $r2 = $r->withUri(new Uri('http://www.foo.com/bar'), true); + self::assertSame('a.com', $r2->getHeaderLine('Host')); + } + + public function testWithUriSetsHostIfNotSet() + { + $r = (new Request('GET', 'http://foo.com/baz?bar=bam'))->withoutHeader('Host'); + self::assertSame([], $r->getHeaders()); + $r2 = $r->withUri(new Uri('http://www.baz.com/bar'), true); + self::assertSame('www.baz.com', $r2->getHeaderLine('Host')); + } + + public function testOverridesHostWithUri() + { + $r = new Request('GET', 'http://foo.com/baz?bar=bam'); + self::assertSame(['Host' => ['foo.com']], $r->getHeaders()); + $r2 = $r->withUri(new Uri('http://www.baz.com/bar')); + self::assertSame('www.baz.com', $r2->getHeaderLine('Host')); + } + + public function testAggregatesHeaders() + { + $r = new Request('GET', '', [ + 'ZOO' => 'zoobar', + 'zoo' => ['foobar', 'zoobar'] + ]); + self::assertSame(['ZOO' => ['zoobar', 'foobar', 'zoobar']], $r->getHeaders()); + self::assertSame('zoobar, foobar, zoobar', $r->getHeaderLine('zoo')); + } + + public function testAddsPortToHeader() + { + $r = new Request('GET', 'http://foo.com:8124/bar'); + self::assertSame('foo.com:8124', $r->getHeaderLine('host')); + } + + public function testAddsPortToHeaderAndReplacePreviousPort() + { + $r = new Request('GET', 'http://foo.com:8124/bar'); + $r = $r->withUri(new Uri('http://foo.com:8125/bar')); + self::assertSame('foo.com:8125', $r->getHeaderLine('host')); + } + + /** + * @dataProvider provideHeaderValuesContainingNotAllowedChars + */ + public function testContainsNotAllowedCharsOnHeaderValue($value) + { + $this->expectExceptionGuzzle('InvalidArgumentException', sprintf('"%s" is not valid header value', $value)); + $r = new Request( + 'GET', + 'http://foo.com/baz?bar=bam', + [ + 'testing' => $value + ] + ); + } + + /** + * @return iterable + */ + public function provideHeaderValuesContainingNotAllowedChars() + { + // Explicit tests for newlines as the most common exploit vector. + $tests = [ + ["new\nline"], + ["new\r\nline"], + ["new\rline"], + // Line folding is technically allowed, but deprecated. + // We don't support it. + ["new\r\n line"], + ["newline\n"], + ["\nnewline"], + ["newline\r\n"], + ["\r\nnewline"], + ]; + + for ($i = 0; $i <= 0xff; $i++) { + if (\chr($i) == "\t") { + continue; + } + if (\chr($i) == " ") { + continue; + } + if ($i >= 0x21 && $i <= 0x7e) { + continue; + } + if ($i >= 0x80) { + continue; + } + + $tests[] = ["foo" . \chr($i) . "bar"]; + $tests[] = ["foo" . \chr($i)]; + } + + return $tests; + } +} diff --git a/vendor/guzzlehttp/psr7/tests/ResponseTest.php b/vendor/guzzlehttp/psr7/tests/ResponseTest.php new file mode 100644 index 0000000000..204015a9e1 --- /dev/null +++ b/vendor/guzzlehttp/psr7/tests/ResponseTest.php @@ -0,0 +1,384 @@ +getStatusCode()); + self::assertSame('1.1', $r->getProtocolVersion()); + self::assertSame('OK', $r->getReasonPhrase()); + self::assertSame([], $r->getHeaders()); + self::assertInstanceOf('Psr\Http\Message\StreamInterface', $r->getBody()); + self::assertSame('', (string) $r->getBody()); + } + + public function testCanConstructWithStatusCode() + { + $r = new Response(404); + self::assertSame(404, $r->getStatusCode()); + self::assertSame('Not Found', $r->getReasonPhrase()); + } + + public function testConstructorDoesNotReadStreamBody() + { + $streamIsRead = false; + $body = Psr7\FnStream::decorate(Psr7\Utils::streamFor(''), [ + '__toString' => function () use (&$streamIsRead) { + $streamIsRead = true; + return ''; + } + ]); + + $r = new Response(200, [], $body); + self::assertFalse($streamIsRead); + self::assertSame($body, $r->getBody()); + } + + public function testStatusCanBeNumericString() + { + $r = new Response('404'); + $r2 = $r->withStatus('201'); + self::assertSame(404, $r->getStatusCode()); + self::assertSame('Not Found', $r->getReasonPhrase()); + self::assertSame(201, $r2->getStatusCode()); + self::assertSame('Created', $r2->getReasonPhrase()); + } + + public function testCanConstructWithHeaders() + { + $r = new Response(200, ['Foo' => 'Bar']); + self::assertSame(['Foo' => ['Bar']], $r->getHeaders()); + self::assertSame('Bar', $r->getHeaderLine('Foo')); + self::assertSame(['Bar'], $r->getHeader('Foo')); + } + + public function testCanConstructWithHeadersAsArray() + { + $r = new Response(200, [ + 'Foo' => ['baz', 'bar'] + ]); + self::assertSame(['Foo' => ['baz', 'bar']], $r->getHeaders()); + self::assertSame('baz, bar', $r->getHeaderLine('Foo')); + self::assertSame(['baz', 'bar'], $r->getHeader('Foo')); + } + + public function testCanConstructWithBody() + { + $r = new Response(200, [], 'baz'); + self::assertInstanceOf('Psr\Http\Message\StreamInterface', $r->getBody()); + self::assertSame('baz', (string) $r->getBody()); + } + + public function testNullBody() + { + $r = new Response(200, [], null); + self::assertInstanceOf('Psr\Http\Message\StreamInterface', $r->getBody()); + self::assertSame('', (string) $r->getBody()); + } + + public function testFalseyBody() + { + $r = new Response(200, [], '0'); + self::assertInstanceOf('Psr\Http\Message\StreamInterface', $r->getBody()); + self::assertSame('0', (string) $r->getBody()); + } + + public function testCanConstructWithReason() + { + $r = new Response(200, [], null, '1.1', 'bar'); + self::assertSame('bar', $r->getReasonPhrase()); + + $r = new Response(200, [], null, '1.1', '0'); + self::assertSame('0', $r->getReasonPhrase(), 'Falsey reason works'); + } + + public function testCanConstructWithProtocolVersion() + { + $r = new Response(200, [], null, '1000'); + self::assertSame('1000', $r->getProtocolVersion()); + } + + public function testWithStatusCodeAndNoReason() + { + $r = (new Response())->withStatus(201); + self::assertSame(201, $r->getStatusCode()); + self::assertSame('Created', $r->getReasonPhrase()); + } + + public function testWithStatusCodeAndReason() + { + $r = (new Response())->withStatus(201, 'Foo'); + self::assertSame(201, $r->getStatusCode()); + self::assertSame('Foo', $r->getReasonPhrase()); + + $r = (new Response())->withStatus(201, '0'); + self::assertSame(201, $r->getStatusCode()); + self::assertSame('0', $r->getReasonPhrase(), 'Falsey reason works'); + } + + public function testWithProtocolVersion() + { + $r = (new Response())->withProtocolVersion('1000'); + self::assertSame('1000', $r->getProtocolVersion()); + } + + public function testSameInstanceWhenSameProtocol() + { + $r = new Response(); + self::assertSame($r, $r->withProtocolVersion('1.1')); + } + + public function testWithBody() + { + $b = Psr7\Utils::streamFor('0'); + $r = (new Response())->withBody($b); + self::assertInstanceOf('Psr\Http\Message\StreamInterface', $r->getBody()); + self::assertSame('0', (string) $r->getBody()); + } + + public function testSameInstanceWhenSameBody() + { + $r = new Response(); + $b = $r->getBody(); + self::assertSame($r, $r->withBody($b)); + } + + public function testWithHeader() + { + $r = new Response(200, ['Foo' => 'Bar']); + $r2 = $r->withHeader('baZ', 'Bam'); + self::assertSame(['Foo' => ['Bar']], $r->getHeaders()); + self::assertSame(['Foo' => ['Bar'], 'baZ' => ['Bam']], $r2->getHeaders()); + self::assertSame('Bam', $r2->getHeaderLine('baz')); + self::assertSame(['Bam'], $r2->getHeader('baz')); + } + + public function testNumericHeaderValue() + { + $r = (new Response())->withHeader('Api-Version', 1); + self::assertSame(['Api-Version' => ['1']], $r->getHeaders()); + } + + public function testWithHeaderAsArray() + { + $r = new Response(200, ['Foo' => 'Bar']); + $r2 = $r->withHeader('baZ', ['Bam', 'Bar']); + self::assertSame(['Foo' => ['Bar']], $r->getHeaders()); + self::assertSame(['Foo' => ['Bar'], 'baZ' => ['Bam', 'Bar']], $r2->getHeaders()); + self::assertSame('Bam, Bar', $r2->getHeaderLine('baz')); + self::assertSame(['Bam', 'Bar'], $r2->getHeader('baz')); + } + + public function testWithHeaderReplacesDifferentCase() + { + $r = new Response(200, ['Foo' => 'Bar']); + $r2 = $r->withHeader('foO', 'Bam'); + self::assertSame(['Foo' => ['Bar']], $r->getHeaders()); + self::assertSame(['foO' => ['Bam']], $r2->getHeaders()); + self::assertSame('Bam', $r2->getHeaderLine('foo')); + self::assertSame(['Bam'], $r2->getHeader('foo')); + } + + public function testWithAddedHeader() + { + $r = new Response(200, ['Foo' => 'Bar']); + $r2 = $r->withAddedHeader('foO', 'Baz'); + self::assertSame(['Foo' => ['Bar']], $r->getHeaders()); + self::assertSame(['Foo' => ['Bar', 'Baz']], $r2->getHeaders()); + self::assertSame('Bar, Baz', $r2->getHeaderLine('foo')); + self::assertSame(['Bar', 'Baz'], $r2->getHeader('foo')); + } + + public function testWithAddedHeaderAsArray() + { + $r = new Response(200, ['Foo' => 'Bar']); + $r2 = $r->withAddedHeader('foO', ['Baz', 'Bam']); + self::assertSame(['Foo' => ['Bar']], $r->getHeaders()); + self::assertSame(['Foo' => ['Bar', 'Baz', 'Bam']], $r2->getHeaders()); + self::assertSame('Bar, Baz, Bam', $r2->getHeaderLine('foo')); + self::assertSame(['Bar', 'Baz', 'Bam'], $r2->getHeader('foo')); + } + + public function testWithAddedHeaderThatDoesNotExist() + { + $r = new Response(200, ['Foo' => 'Bar']); + $r2 = $r->withAddedHeader('nEw', 'Baz'); + self::assertSame(['Foo' => ['Bar']], $r->getHeaders()); + self::assertSame(['Foo' => ['Bar'], 'nEw' => ['Baz']], $r2->getHeaders()); + self::assertSame('Baz', $r2->getHeaderLine('new')); + self::assertSame(['Baz'], $r2->getHeader('new')); + } + + public function testWithoutHeaderThatExists() + { + $r = new Response(200, ['Foo' => 'Bar', 'Baz' => 'Bam']); + $r2 = $r->withoutHeader('foO'); + self::assertTrue($r->hasHeader('foo')); + self::assertSame(['Foo' => ['Bar'], 'Baz' => ['Bam']], $r->getHeaders()); + self::assertFalse($r2->hasHeader('foo')); + self::assertSame(['Baz' => ['Bam']], $r2->getHeaders()); + } + + public function testWithoutHeaderThatDoesNotExist() + { + $r = new Response(200, ['Baz' => 'Bam']); + $r2 = $r->withoutHeader('foO'); + self::assertSame($r, $r2); + self::assertFalse($r2->hasHeader('foo')); + self::assertSame(['Baz' => ['Bam']], $r2->getHeaders()); + } + + public function testSameInstanceWhenRemovingMissingHeader() + { + $r = new Response(); + self::assertSame($r, $r->withoutHeader('foo')); + } + + public function testPassNumericHeaderNameInConstructor() + { + $r = new Response(200, ['Location' => 'foo', '123' => 'bar']); + self::assertSame('bar', $r->getHeaderLine('123')); + } + + /** + * @dataProvider invalidHeaderProvider + */ + public function testConstructResponseInvalidHeader($header, $headerValue, $expectedMessage) + { + $this->expectExceptionGuzzle('InvalidArgumentException', $expectedMessage); + new Response(200, [$header => $headerValue]); + } + + public function invalidHeaderProvider() + { + return [ + ['foo', [], 'Header value can not be an empty array.'], + ['', '', 'Header name can not be empty.'], + ['foo', new \stdClass(), 'Header value must be scalar or null but stdClass provided.'], + ]; + } + + /** + * @dataProvider invalidWithHeaderProvider + */ + public function testWithInvalidHeader($header, $headerValue, $expectedMessage) + { + $r = new Response(); + $this->expectExceptionGuzzle('InvalidArgumentException', $expectedMessage); + $r->withHeader($header, $headerValue); + } + + public function invalidWithHeaderProvider() + { + return array_merge($this->invalidHeaderProvider(), [ + [[], 'foo', 'Header name must be a string but array provided.'], + [false, 'foo', 'Header name must be a string but boolean provided.'], + [new \stdClass(), 'foo', 'Header name must be a string but stdClass provided.'], + ["", 'foo', "Header name can not be empty."], + ["Content-Type\r\n\r\n", 'foo', "\"Content-Type\r\n\r\n\" is not valid header name."], + ["Content-Type\r\n", 'foo', "\"Content-Type\r\n\" is not valid header name."], + ["Content-Type\n", 'foo', "\"Content-Type\n\" is not valid header name."], + ["\r\nContent-Type", 'foo', "\"\r\nContent-Type\" is not valid header name."], + ["\nContent-Type", 'foo', "\"\nContent-Type\" is not valid header name."], + ["\n", 'foo', "\"\n\" is not valid header name."], + ["\r\n", 'foo', "\"\r\n\" is not valid header name."], + ["\t", 'foo', "\"\t\" is not valid header name."], + ]); + } + + public function testHeaderValuesAreTrimmed() + { + $r1 = new Response(200, ['OWS' => " \t \tFoo\t \t "]); + $r2 = (new Response())->withHeader('OWS', " \t \tFoo\t \t "); + $r3 = (new Response())->withAddedHeader('OWS', " \t \tFoo\t \t "); + + foreach ([$r1, $r2, $r3] as $r) { + self::assertSame(['OWS' => ['Foo']], $r->getHeaders()); + self::assertSame('Foo', $r->getHeaderLine('OWS')); + self::assertSame(['Foo'], $r->getHeader('OWS')); + } + } + + public function testWithAddedHeaderArrayValueAndKeys() + { + $message = (new Response())->withAddedHeader('list', ['foo' => 'one']); + $message = $message->withAddedHeader('list', ['foo' => 'two', 'bar' => 'three']); + + $headerLine = $message->getHeaderLine('list'); + self::assertSame('one, two, three', $headerLine); + } + + /** + * @dataProvider nonIntegerStatusCodeProvider + * + * @param mixed $invalidValues + */ + public function testConstructResponseWithNonIntegerStatusCode($invalidValues) + { + $this->expectExceptionGuzzle('InvalidArgumentException', 'Status code must be an integer value.'); + new Response($invalidValues); + } + + /** + * @dataProvider nonIntegerStatusCodeProvider + * + * @param mixed $invalidValues + */ + public function testResponseChangeStatusCodeWithNonInteger($invalidValues) + { + $response = new Response(); + $this->expectExceptionGuzzle('InvalidArgumentException', 'Status code must be an integer value.'); + $response->withStatus($invalidValues); + } + + public function nonIntegerStatusCodeProvider() + { + return [ + ['whatever'], + ['1.01'], + [1.01], + [new \stdClass()], + ]; + } + + /** + * @dataProvider invalidStatusCodeRangeProvider + * + * @param mixed $invalidValues + */ + public function testConstructResponseWithInvalidRangeStatusCode($invalidValues) + { + $this->expectExceptionGuzzle('InvalidArgumentException', 'Status code must be an integer value between 1xx and 5xx.'); + new Response($invalidValues); + } + + /** + * @dataProvider invalidStatusCodeRangeProvider + * + * @param mixed $invalidValues + */ + public function testResponseChangeStatusCodeWithWithInvalidRange($invalidValues) + { + $response = new Response(); + $this->expectExceptionGuzzle('InvalidArgumentException', 'Status code must be an integer value between 1xx and 5xx.'); + $response->withStatus($invalidValues); + } + + public function invalidStatusCodeRangeProvider() + { + return [ + [600], + [99], + ]; + } +} diff --git a/vendor/guzzlehttp/psr7/tests/ServerRequestTest.php b/vendor/guzzlehttp/psr7/tests/ServerRequestTest.php new file mode 100644 index 0000000000..10f46b4a3b --- /dev/null +++ b/vendor/guzzlehttp/psr7/tests/ServerRequestTest.php @@ -0,0 +1,544 @@ + [ + [ + 'file' => [ + 'name' => 'MyFile.txt', + 'type' => 'text/plain', + 'tmp_name' => '/tmp/php/php1h4j1o', + 'error' => '0', + 'size' => '123' + ] + ], + [ + 'file' => new UploadedFile( + '/tmp/php/php1h4j1o', + 123, + UPLOAD_ERR_OK, + 'MyFile.txt', + 'text/plain' + ) + ] + ], + 'Empty file' => [ + [ + 'image_file' => [ + 'name' => '', + 'type' => '', + 'tmp_name' => '', + 'error' => '4', + 'size' => '0' + ] + ], + [ + 'image_file' => new UploadedFile( + '', + 0, + UPLOAD_ERR_NO_FILE, + '', + '' + ) + ] + ], + 'Already Converted' => [ + [ + 'file' => new UploadedFile( + '/tmp/php/php1h4j1o', + 123, + UPLOAD_ERR_OK, + 'MyFile.txt', + 'text/plain' + ) + ], + [ + 'file' => new UploadedFile( + '/tmp/php/php1h4j1o', + 123, + UPLOAD_ERR_OK, + 'MyFile.txt', + 'text/plain' + ) + ] + ], + 'Already Converted array' => [ + [ + 'file' => [ + new UploadedFile( + '/tmp/php/php1h4j1o', + 123, + UPLOAD_ERR_OK, + 'MyFile.txt', + 'text/plain' + ), + new UploadedFile( + '', + 0, + UPLOAD_ERR_NO_FILE, + '', + '' + ) + ], + ], + [ + 'file' => [ + new UploadedFile( + '/tmp/php/php1h4j1o', + 123, + UPLOAD_ERR_OK, + 'MyFile.txt', + 'text/plain' + ), + new UploadedFile( + '', + 0, + UPLOAD_ERR_NO_FILE, + '', + '' + ) + ], + ] + ], + 'Multiple files' => [ + [ + 'text_file' => [ + 'name' => 'MyFile.txt', + 'type' => 'text/plain', + 'tmp_name' => '/tmp/php/php1h4j1o', + 'error' => '0', + 'size' => '123' + ], + 'image_file' => [ + 'name' => '', + 'type' => '', + 'tmp_name' => '', + 'error' => '4', + 'size' => '0' + ] + ], + [ + 'text_file' => new UploadedFile( + '/tmp/php/php1h4j1o', + 123, + UPLOAD_ERR_OK, + 'MyFile.txt', + 'text/plain' + ), + 'image_file' => new UploadedFile( + '', + 0, + UPLOAD_ERR_NO_FILE, + '', + '' + ) + ] + ], + 'Nested files' => [ + [ + 'file' => [ + 'name' => [ + 0 => 'MyFile.txt', + 1 => 'Image.png', + ], + 'type' => [ + 0 => 'text/plain', + 1 => 'image/png', + ], + 'tmp_name' => [ + 0 => '/tmp/php/hp9hskjhf', + 1 => '/tmp/php/php1h4j1o', + ], + 'error' => [ + 0 => '0', + 1 => '0', + ], + 'size' => [ + 0 => '123', + 1 => '7349', + ], + ], + 'nested' => [ + 'name' => [ + 'other' => 'Flag.txt', + 'test' => [ + 0 => 'Stuff.txt', + 1 => '', + ], + ], + 'type' => [ + 'other' => 'text/plain', + 'test' => [ + 0 => 'text/plain', + 1 => '', + ], + ], + 'tmp_name' => [ + 'other' => '/tmp/php/hp9hskjhf', + 'test' => [ + 0 => '/tmp/php/asifu2gp3', + 1 => '', + ], + ], + 'error' => [ + 'other' => '0', + 'test' => [ + 0 => '0', + 1 => '4', + ], + ], + 'size' => [ + 'other' => '421', + 'test' => [ + 0 => '32', + 1 => '0', + ] + ] + ], + ], + [ + 'file' => [ + 0 => new UploadedFile( + '/tmp/php/hp9hskjhf', + 123, + UPLOAD_ERR_OK, + 'MyFile.txt', + 'text/plain' + ), + 1 => new UploadedFile( + '/tmp/php/php1h4j1o', + 7349, + UPLOAD_ERR_OK, + 'Image.png', + 'image/png' + ), + ], + 'nested' => [ + 'other' => new UploadedFile( + '/tmp/php/hp9hskjhf', + 421, + UPLOAD_ERR_OK, + 'Flag.txt', + 'text/plain' + ), + 'test' => [ + 0 => new UploadedFile( + '/tmp/php/asifu2gp3', + 32, + UPLOAD_ERR_OK, + 'Stuff.txt', + 'text/plain' + ), + 1 => new UploadedFile( + '', + 0, + UPLOAD_ERR_NO_FILE, + '', + '' + ), + ] + ] + ] + ] + ]; + } + + /** + * @dataProvider dataNormalizeFiles + */ + public function testNormalizeFiles($files, $expected) + { + $result = ServerRequest::normalizeFiles($files); + + self::assertEquals($expected, $result); + } + + public function testNormalizeFilesRaisesException() + { + $this->expectExceptionGuzzle('InvalidArgumentException', 'Invalid value in files specification'); + ServerRequest::normalizeFiles(['test' => 'something']); + } + + public function dataGetUriFromGlobals() + { + $server = [ + 'REQUEST_URI' => '/blog/article.php?id=10&user=foo', + 'SERVER_PORT' => '443', + 'SERVER_ADDR' => '217.112.82.20', + 'SERVER_NAME' => 'www.example.org', + 'SERVER_PROTOCOL' => 'HTTP/1.1', + 'REQUEST_METHOD' => 'POST', + 'QUERY_STRING' => 'id=10&user=foo', + 'DOCUMENT_ROOT' => '/path/to/your/server/root/', + 'HTTP_HOST' => 'www.example.org', + 'HTTPS' => 'on', + 'REMOTE_ADDR' => '193.60.168.69', + 'REMOTE_PORT' => '5390', + 'SCRIPT_NAME' => '/blog/article.php', + 'SCRIPT_FILENAME' => '/path/to/your/server/root/blog/article.php', + 'PHP_SELF' => '/blog/article.php', + ]; + + return [ + 'HTTPS request' => [ + 'https://www.example.org/blog/article.php?id=10&user=foo', + $server, + ], + 'HTTPS request with different on value' => [ + 'https://www.example.org/blog/article.php?id=10&user=foo', + array_merge($server, ['HTTPS' => '1']), + ], + 'HTTP request' => [ + 'http://www.example.org/blog/article.php?id=10&user=foo', + array_merge($server, ['HTTPS' => 'off', 'SERVER_PORT' => '80']), + ], + 'HTTP_HOST missing -> fallback to SERVER_NAME' => [ + 'https://www.example.org/blog/article.php?id=10&user=foo', + array_merge($server, ['HTTP_HOST' => null]), + ], + 'HTTP_HOST and SERVER_NAME missing -> fallback to SERVER_ADDR' => [ + 'https://217.112.82.20/blog/article.php?id=10&user=foo', + array_merge($server, ['HTTP_HOST' => null, 'SERVER_NAME' => null]), + ], + 'Query string with ?' => [ + 'https://www.example.org/path?continue=https://example.com/path?param=1', + array_merge($server, ['REQUEST_URI' => '/path?continue=https://example.com/path?param=1', 'QUERY_STRING' => '']), + ], + 'No query String' => [ + 'https://www.example.org/blog/article.php', + array_merge($server, ['REQUEST_URI' => '/blog/article.php', 'QUERY_STRING' => '']), + ], + 'Host header with port' => [ + 'https://www.example.org:8324/blog/article.php?id=10&user=foo', + array_merge($server, ['HTTP_HOST' => 'www.example.org:8324']), + ], + 'IPv6 local loopback address' => [ + 'https://[::1]:8000/blog/article.php?id=10&user=foo', + array_merge($server, ['HTTP_HOST' => '[::1]:8000']), + ], + 'Invalid host' => [ + 'https://localhost/blog/article.php?id=10&user=foo', + array_merge($server, ['HTTP_HOST' => 'a:b']), + ], + 'Different port with SERVER_PORT' => [ + 'https://www.example.org:8324/blog/article.php?id=10&user=foo', + array_merge($server, ['SERVER_PORT' => '8324']), + ], + 'REQUEST_URI missing query string' => [ + 'https://www.example.org/blog/article.php?id=10&user=foo', + array_merge($server, ['REQUEST_URI' => '/blog/article.php']), + ], + 'Empty server variable' => [ + 'http://localhost', + [], + ], + ]; + } + + /** + * @dataProvider dataGetUriFromGlobals + */ + public function testGetUriFromGlobals($expected, $serverParams) + { + $_SERVER = $serverParams; + + self::assertEquals(new Uri($expected), ServerRequest::getUriFromGlobals()); + } + + public function testFromGlobals() + { + $_SERVER = [ + 'REQUEST_URI' => '/blog/article.php?id=10&user=foo', + 'SERVER_PORT' => '443', + 'SERVER_ADDR' => '217.112.82.20', + 'SERVER_NAME' => 'www.example.org', + 'SERVER_PROTOCOL' => 'HTTP/1.1', + 'REQUEST_METHOD' => 'POST', + 'QUERY_STRING' => 'id=10&user=foo', + 'DOCUMENT_ROOT' => '/path/to/your/server/root/', + 'CONTENT_TYPE' => 'text/plain', + 'HTTP_HOST' => 'www.example.org', + 'HTTP_ACCEPT' => 'text/html', + 'HTTP_REFERRER' => 'https://example.com', + 'HTTP_USER_AGENT' => 'My User Agent', + 'HTTPS' => 'on', + 'REMOTE_ADDR' => '193.60.168.69', + 'REMOTE_PORT' => '5390', + 'SCRIPT_NAME' => '/blog/article.php', + 'SCRIPT_FILENAME' => '/path/to/your/server/root/blog/article.php', + 'PHP_SELF' => '/blog/article.php', + ]; + + $_COOKIE = [ + 'logged-in' => 'yes!' + ]; + + $_POST = [ + 'name' => 'Pesho', + 'email' => 'pesho@example.com', + ]; + + $_GET = [ + 'id' => 10, + 'user' => 'foo', + ]; + + $_FILES = [ + 'file' => [ + 'name' => 'MyFile.txt', + 'type' => 'text/plain', + 'tmp_name' => '/tmp/php/php1h4j1o', + 'error' => UPLOAD_ERR_OK, + 'size' => 123, + ] + ]; + + $server = ServerRequest::fromGlobals(); + + self::assertSame('POST', $server->getMethod()); + self::assertEquals([ + 'Host' => ['www.example.org'], + 'Content-Type' => ['text/plain'], + 'Accept' => ['text/html'], + 'Referrer' => ['https://example.com'], + 'User-Agent' => ['My User Agent'], + ], $server->getHeaders()); + self::assertSame('', (string) $server->getBody()); + self::assertSame('1.1', $server->getProtocolVersion()); + self::assertSame($_COOKIE, $server->getCookieParams()); + self::assertSame($_POST, $server->getParsedBody()); + self::assertSame($_GET, $server->getQueryParams()); + + self::assertEquals( + new Uri('https://www.example.org/blog/article.php?id=10&user=foo'), + $server->getUri() + ); + + $expectedFiles = [ + 'file' => new UploadedFile( + '/tmp/php/php1h4j1o', + 123, + UPLOAD_ERR_OK, + 'MyFile.txt', + 'text/plain' + ), + ]; + + self::assertEquals($expectedFiles, $server->getUploadedFiles()); + } + + public function testUploadedFiles() + { + $request1 = new ServerRequest('GET', '/'); + + $files = [ + 'file' => new UploadedFile('test', 123, UPLOAD_ERR_OK) + ]; + + $request2 = $request1->withUploadedFiles($files); + + self::assertNotSame($request2, $request1); + self::assertSame([], $request1->getUploadedFiles()); + self::assertSame($files, $request2->getUploadedFiles()); + } + + public function testServerParams() + { + $params = ['name' => 'value']; + + $request = new ServerRequest('GET', '/', [], null, '1.1', $params); + self::assertSame($params, $request->getServerParams()); + } + + public function testCookieParams() + { + $request1 = new ServerRequest('GET', '/'); + + $params = ['name' => 'value']; + + $request2 = $request1->withCookieParams($params); + + self::assertNotSame($request2, $request1); + self::assertEmpty($request1->getCookieParams()); + self::assertSame($params, $request2->getCookieParams()); + } + + public function testQueryParams() + { + $request1 = new ServerRequest('GET', '/'); + + $params = ['name' => 'value']; + + $request2 = $request1->withQueryParams($params); + + self::assertNotSame($request2, $request1); + self::assertEmpty($request1->getQueryParams()); + self::assertSame($params, $request2->getQueryParams()); + } + + public function testParsedBody() + { + $request1 = new ServerRequest('GET', '/'); + + $params = ['name' => 'value']; + + $request2 = $request1->withParsedBody($params); + + self::assertNotSame($request2, $request1); + self::assertEmpty($request1->getParsedBody()); + self::assertSame($params, $request2->getParsedBody()); + } + + public function testAttributes() + { + $request1 = new ServerRequest('GET', '/'); + + $request2 = $request1->withAttribute('name', 'value'); + $request3 = $request2->withAttribute('other', 'otherValue'); + $request4 = $request3->withoutAttribute('other'); + $request5 = $request3->withoutAttribute('unknown'); + + self::assertNotSame($request2, $request1); + self::assertNotSame($request3, $request2); + self::assertNotSame($request4, $request3); + self::assertSame($request5, $request3); + + self::assertSame([], $request1->getAttributes()); + self::assertNull($request1->getAttribute('name')); + self::assertSame( + 'something', + $request1->getAttribute('name', 'something'), + 'Should return the default value' + ); + + self::assertSame('value', $request2->getAttribute('name')); + self::assertSame(['name' => 'value'], $request2->getAttributes()); + self::assertEquals(['name' => 'value', 'other' => 'otherValue'], $request3->getAttributes()); + self::assertSame(['name' => 'value'], $request4->getAttributes()); + } + + public function testNullAttribute() + { + $request = (new ServerRequest('GET', '/'))->withAttribute('name', null); + + self::assertSame(['name' => null], $request->getAttributes()); + self::assertNull($request->getAttribute('name', 'different-default')); + + $requestWithoutAttribute = $request->withoutAttribute('name'); + + self::assertSame([], $requestWithoutAttribute->getAttributes()); + self::assertSame('different-default', $requestWithoutAttribute->getAttribute('name', 'different-default')); + } +} diff --git a/vendor/guzzlehttp/psr7/tests/StreamDecoratorTraitTest.php b/vendor/guzzlehttp/psr7/tests/StreamDecoratorTraitTest.php new file mode 100644 index 0000000000..365e7f3713 --- /dev/null +++ b/vendor/guzzlehttp/psr7/tests/StreamDecoratorTraitTest.php @@ -0,0 +1,147 @@ +c = fopen('php://temp', 'r+'); + fwrite($this->c, 'foo'); + fseek($this->c, 0); + $this->a = Psr7\Utils::streamFor($this->c); + $this->b = new Str($this->a); + } + + public function testCatchesExceptionsWhenCastingToString() + { + $s = $this->getMockBuilder('Psr\Http\Message\StreamInterface') + ->setMethods(['read']) + ->getMockForAbstractClass(); + $s->expects(self::once()) + ->method('read') + ->will(self::throwException(new \Exception('foo'))); + $msg = ''; + set_error_handler(function ($errNo, $str) use (&$msg) { + $msg = $str; + }); + echo new Str($s); + restore_error_handler(); + $this->assertStringContainsStringGuzzle('foo', $msg); + } + + public function testToString() + { + self::assertSame('foo', (string) $this->b); + } + + public function testHasSize() + { + self::assertSame(3, $this->b->getSize()); + } + + public function testReads() + { + self::assertSame('foo', $this->b->read(10)); + } + + public function testCheckMethods() + { + self::assertSame($this->a->isReadable(), $this->b->isReadable()); + self::assertSame($this->a->isWritable(), $this->b->isWritable()); + self::assertSame($this->a->isSeekable(), $this->b->isSeekable()); + } + + public function testSeeksAndTells() + { + $this->b->seek(1); + self::assertSame(1, $this->a->tell()); + self::assertSame(1, $this->b->tell()); + $this->b->seek(0); + self::assertSame(0, $this->a->tell()); + self::assertSame(0, $this->b->tell()); + $this->b->seek(0, SEEK_END); + self::assertSame(3, $this->a->tell()); + self::assertSame(3, $this->b->tell()); + } + + public function testGetsContents() + { + self::assertSame('foo', $this->b->getContents()); + self::assertSame('', $this->b->getContents()); + $this->b->seek(1); + self::assertSame('oo', $this->b->getContents()); + } + + public function testCloses() + { + $this->b->close(); + self::assertFalse(is_resource($this->c)); + } + + public function testDetaches() + { + $this->b->detach(); + self::assertFalse($this->b->isReadable()); + } + + public function testWrapsMetadata() + { + self::assertSame($this->b->getMetadata(), $this->a->getMetadata()); + self::assertSame($this->b->getMetadata('uri'), $this->a->getMetadata('uri')); + } + + public function testWrapsWrites() + { + $this->b->seek(0, SEEK_END); + $this->b->write('foo'); + self::assertSame('foofoo', (string) $this->a); + } + + public function testThrowsWithInvalidGetter() + { + $this->expectExceptionGuzzle('UnexpectedValueException'); + + $this->b->foo; + } + + public function testThrowsWhenGetterNotImplemented() + { + $s = new BadStream(); + + $this->expectExceptionGuzzle('BadMethodCallException'); + + $s->stream; + } +} + +class BadStream +{ + use StreamDecoratorTrait; + + public function __construct() + { + } +} diff --git a/vendor/guzzlehttp/psr7/tests/StreamTest.php b/vendor/guzzlehttp/psr7/tests/StreamTest.php new file mode 100644 index 0000000000..3cd2a5044d --- /dev/null +++ b/vendor/guzzlehttp/psr7/tests/StreamTest.php @@ -0,0 +1,403 @@ +expectExceptionGuzzle('InvalidArgumentException'); + + new Stream(true); + } + + public function testConstructorInitializesProperties() + { + $handle = fopen('php://temp', 'r+'); + fwrite($handle, 'data'); + $stream = new Stream($handle); + self::assertTrue($stream->isReadable()); + self::assertTrue($stream->isWritable()); + self::assertTrue($stream->isSeekable()); + self::assertSame('php://temp', $stream->getMetadata('uri')); + $this->assertInternalTypeGuzzle('array', $stream->getMetadata()); + self::assertSame(4, $stream->getSize()); + self::assertFalse($stream->eof()); + $stream->close(); + } + + public function testConstructorInitializesPropertiesWithRbPlus() + { + $handle = fopen('php://temp', 'rb+'); + fwrite($handle, 'data'); + $stream = new Stream($handle); + self::assertTrue($stream->isReadable()); + self::assertTrue($stream->isWritable()); + self::assertTrue($stream->isSeekable()); + self::assertSame('php://temp', $stream->getMetadata('uri')); + $this->assertInternalTypeGuzzle('array', $stream->getMetadata()); + self::assertSame(4, $stream->getSize()); + self::assertFalse($stream->eof()); + $stream->close(); + } + + public function testStreamClosesHandleOnDestruct() + { + $handle = fopen('php://temp', 'r'); + $stream = new Stream($handle); + unset($stream); + self::assertFalse(is_resource($handle)); + } + + public function testConvertsToString() + { + $handle = fopen('php://temp', 'w+'); + fwrite($handle, 'data'); + $stream = new Stream($handle); + self::assertSame('data', (string) $stream); + self::assertSame('data', (string) $stream); + $stream->close(); + } + + public function testConvertsToStringNonSeekableStream() + { + if (defined('HHVM_VERSION')) { + self::markTestSkipped('This does not work on HHVM.'); + } + + $handle = popen('echo foo', 'r'); + $stream = new Stream($handle); + self::assertFalse($stream->isSeekable()); + self::assertSame('foo', trim((string) $stream)); + } + + public function testConvertsToStringNonSeekablePartiallyReadStream() + { + if (defined('HHVM_VERSION')) { + self::markTestSkipped('This does not work on HHVM.'); + } + + $handle = popen('echo bar', 'r'); + $stream = new Stream($handle); + $firstLetter = $stream->read(1); + self::assertFalse($stream->isSeekable()); + self::assertSame('b', $firstLetter); + self::assertSame('ar', trim((string) $stream)); + } + + public function testGetsContents() + { + $handle = fopen('php://temp', 'w+'); + fwrite($handle, 'data'); + $stream = new Stream($handle); + self::assertSame('', $stream->getContents()); + $stream->seek(0); + self::assertSame('data', $stream->getContents()); + self::assertSame('', $stream->getContents()); + $stream->close(); + } + + public function testChecksEof() + { + $handle = fopen('php://temp', 'w+'); + fwrite($handle, 'data'); + $stream = new Stream($handle); + self::assertSame(4, $stream->tell(), 'Stream cursor already at the end'); + self::assertFalse($stream->eof(), 'Stream still not eof'); + self::assertSame('', $stream->read(1), 'Need to read one more byte to reach eof'); + self::assertTrue($stream->eof()); + $stream->close(); + } + + public function testGetSize() + { + $size = filesize(__FILE__); + $handle = fopen(__FILE__, 'r'); + $stream = new Stream($handle); + self::assertSame($size, $stream->getSize()); + // Load from cache + self::assertSame($size, $stream->getSize()); + $stream->close(); + } + + public function testEnsuresSizeIsConsistent() + { + $h = fopen('php://temp', 'w+'); + self::assertSame(3, fwrite($h, 'foo')); + $stream = new Stream($h); + self::assertSame(3, $stream->getSize()); + self::assertSame(4, $stream->write('test')); + self::assertSame(7, $stream->getSize()); + self::assertSame(7, $stream->getSize()); + $stream->close(); + } + + public function testProvidesStreamPosition() + { + $handle = fopen('php://temp', 'w+'); + $stream = new Stream($handle); + self::assertSame(0, $stream->tell()); + $stream->write('foo'); + self::assertSame(3, $stream->tell()); + $stream->seek(1); + self::assertSame(1, $stream->tell()); + self::assertSame(ftell($handle), $stream->tell()); + $stream->close(); + } + + public function testDetachStreamAndClearProperties() + { + $handle = fopen('php://temp', 'r'); + $stream = new Stream($handle); + self::assertSame($handle, $stream->detach()); + $this->assertInternalTypeGuzzle('resource', $handle, 'Stream is not closed'); + self::assertNull($stream->detach()); + + $this->assertStreamStateAfterClosedOrDetached($stream); + + $stream->close(); + } + + public function testCloseResourceAndClearProperties() + { + $handle = fopen('php://temp', 'r'); + $stream = new Stream($handle); + $stream->close(); + + self::assertFalse(is_resource($handle)); + + $this->assertStreamStateAfterClosedOrDetached($stream); + } + + private function assertStreamStateAfterClosedOrDetached(Stream $stream) + { + self::assertFalse($stream->isReadable()); + self::assertFalse($stream->isWritable()); + self::assertFalse($stream->isSeekable()); + self::assertNull($stream->getSize()); + self::assertSame([], $stream->getMetadata()); + self::assertNull($stream->getMetadata('foo')); + + $throws = function (callable $fn) { + try { + $fn(); + } catch (\Exception $e) { + $this->assertStringContainsStringGuzzle('Stream is detached', $e->getMessage()); + + return; + } + + $this->fail('Exception should be thrown after the stream is detached.'); + }; + + $throws(function () use ($stream) { + $stream->read(10); + }); + $throws(function () use ($stream) { + $stream->write('bar'); + }); + $throws(function () use ($stream) { + $stream->seek(10); + }); + $throws(function () use ($stream) { + $stream->tell(); + }); + $throws(function () use ($stream) { + $stream->eof(); + }); + $throws(function () use ($stream) { + $stream->getContents(); + }); + self::assertSame('', (string) $stream); + } + + public function testStreamReadingWithZeroLength() + { + $r = fopen('php://temp', 'r'); + $stream = new Stream($r); + + self::assertSame('', $stream->read(0)); + + $stream->close(); + } + + public function testStreamReadingWithNegativeLength() + { + $r = fopen('php://temp', 'r'); + $stream = new Stream($r); + + $this->expectExceptionGuzzle('RuntimeException', 'Length parameter cannot be negative'); + + try { + $stream->read(-1); + } catch (\Exception $e) { + $stream->close(); + throw $e; + } + + $stream->close(); + } + + public function testStreamReadingFreadError() + { + self::$isFReadError = true; + $r = fopen('php://temp', 'r'); + $stream = new Stream($r); + + $this->expectExceptionGuzzle('RuntimeException', 'Unable to read from stream'); + + try { + $stream->read(1); + } catch (\Exception $e) { + self::$isFReadError = false; + $stream->close(); + throw $e; + } + + self::$isFReadError = false; + $stream->close(); + } + + /** + * @dataProvider gzipModeProvider + * + * @param string $mode + * @param bool $readable + * @param bool $writable + */ + public function testGzipStreamModes($mode, $readable, $writable) + { + if (defined('HHVM_VERSION')) { + self::markTestSkipped('This does not work on HHVM.'); + } + + $r = gzopen('php://temp', $mode); + $stream = new Stream($r); + + self::assertSame($readable, $stream->isReadable()); + self::assertSame($writable, $stream->isWritable()); + + $stream->close(); + } + + public function gzipModeProvider() + { + return [ + ['mode' => 'rb9', 'readable' => true, 'writable' => false], + ['mode' => 'wb2', 'readable' => false, 'writable' => true], + ]; + } + + /** + * @dataProvider readableModeProvider + * + * @param string $mode + */ + public function testReadableStream($mode) + { + $r = fopen('php://temp', $mode); + $stream = new Stream($r); + + self::assertTrue($stream->isReadable()); + + $stream->close(); + } + + public function readableModeProvider() + { + return [ + ['r'], + ['w+'], + ['r+'], + ['x+'], + ['c+'], + ['rb'], + ['w+b'], + ['r+b'], + ['x+b'], + ['c+b'], + ['rt'], + ['w+t'], + ['r+t'], + ['x+t'], + ['c+t'], + ['a+'], + ['rb+'], + ]; + } + + public function testWriteOnlyStreamIsNotReadable() + { + $r = fopen('php://output', 'w'); + $stream = new Stream($r); + + self::assertFalse($stream->isReadable()); + + $stream->close(); + } + + /** + * @dataProvider writableModeProvider + * + * @param string $mode + */ + public function testWritableStream($mode) + { + $r = fopen('php://temp', $mode); + $stream = new Stream($r); + + self::assertTrue($stream->isWritable()); + + $stream->close(); + } + + public function writableModeProvider() + { + return [ + ['w'], + ['w+'], + ['rw'], + ['r+'], + ['x+'], + ['c+'], + ['wb'], + ['w+b'], + ['r+b'], + ['rb+'], + ['x+b'], + ['c+b'], + ['w+t'], + ['r+t'], + ['x+t'], + ['c+t'], + ['a'], + ['a+'], + ]; + } + + public function testReadOnlyStreamIsNotWritable() + { + $r = fopen('php://input', 'r'); + $stream = new Stream($r); + + self::assertFalse($stream->isWritable()); + + $stream->close(); + } +} + +namespace GuzzleHttp\Psr7; + +use GuzzleHttp\Tests\Psr7\StreamTest; + +function fread($handle, $length) +{ + return StreamTest::$isFReadError ? false : \fread($handle, $length); +} diff --git a/vendor/guzzlehttp/psr7/tests/StreamWrapperTest.php b/vendor/guzzlehttp/psr7/tests/StreamWrapperTest.php new file mode 100644 index 0000000000..e6a8ae6407 --- /dev/null +++ b/vendor/guzzlehttp/psr7/tests/StreamWrapperTest.php @@ -0,0 +1,200 @@ + 0, + 1 => 0, + 2 => 33206, + 3 => 0, + 4 => 0, + 5 => 0, + 6 => 0, + 7 => 6, + 8 => 0, + 9 => 0, + 10 => 0, + 11 => $stBlksize, + 12 => $stBlksize, + 'dev' => 0, + 'ino' => 0, + 'mode' => 33206, + 'nlink' => 0, + 'uid' => 0, + 'gid' => 0, + 'rdev' => 0, + 'size' => 6, + 'atime' => 0, + 'mtime' => 0, + 'ctime' => 0, + 'blksize' => $stBlksize, + 'blocks' => $stBlksize, + ], fstat($handle)); + } + + self::assertTrue(fclose($handle)); + self::assertSame('foobar', (string) $stream); + } + + public function testStreamContext() + { + $stream = Psr7\Utils::streamFor('foo'); + + self::assertSame('foo', file_get_contents('guzzle://stream', false, StreamWrapper::createStreamContext($stream))); + } + + public function testStreamCast() + { + $streams = [ + StreamWrapper::getResource(Psr7\Utils::streamFor('foo')), + StreamWrapper::getResource(Psr7\Utils::streamFor('bar')) + ]; + $write = null; + $except = null; + $this->assertInternalTypeGuzzle('integer', stream_select($streams, $write, $except, 0)); + } + + public function testValidatesStream() + { + $stream = $this->getMockBuilder('Psr\Http\Message\StreamInterface') + ->setMethods(['isReadable', 'isWritable']) + ->getMockForAbstractClass(); + $stream->expects(self::once()) + ->method('isReadable') + ->will(self::returnValue(false)); + $stream->expects(self::once()) + ->method('isWritable') + ->will(self::returnValue(false)); + + $this->expectExceptionGuzzle('InvalidArgumentException'); + + StreamWrapper::getResource($stream); + } + + public function testReturnsFalseWhenStreamDoesNotExist() + { + $this->expectWarningGuzzle(); + + fopen('guzzle://foo', 'r'); + } + + public function testCanOpenReadonlyStream() + { + $stream = $this->getMockBuilder('Psr\Http\Message\StreamInterface') + ->setMethods(['isReadable', 'isWritable']) + ->getMockForAbstractClass(); + $stream->expects(self::once()) + ->method('isReadable') + ->will(self::returnValue(false)); + $stream->expects(self::once()) + ->method('isWritable') + ->will(self::returnValue(true)); + $r = StreamWrapper::getResource($stream); + $this->assertInternalTypeGuzzle('resource', $r); + fclose($r); + } + + public function testUrlStat() + { + StreamWrapper::register(); + + $stBlksize = defined('PHP_WINDOWS_VERSION_BUILD') ? -1 : 0; + + self::assertSame([ + 0 => 0, + 1 => 0, + 2 => 0, + 3 => 0, + 4 => 0, + 5 => 0, + 6 => 0, + 7 => 0, + 8 => 0, + 9 => 0, + 10 => 0, + 11 => $stBlksize, + 12 => $stBlksize, + 'dev' => 0, + 'ino' => 0, + 'mode' => 0, + 'nlink' => 0, + 'uid' => 0, + 'gid' => 0, + 'rdev' => 0, + 'size' => 0, + 'atime' => 0, + 'mtime' => 0, + 'ctime' => 0, + 'blksize' => $stBlksize, + 'blocks' => $stBlksize, + ], stat('guzzle://stream')); + } + + public function testXmlReaderWithStream() + { + if (!class_exists('XMLReader')) { + self::markTestSkipped('XML Reader is not available.'); + } + if (defined('HHVM_VERSION')) { + self::markTestSkipped('This does not work on HHVM.'); + } + + $stream = Psr7\Utils::streamFor(''); + + StreamWrapper::register(); + libxml_set_streams_context(StreamWrapper::createStreamContext($stream)); + $reader = new \XMLReader(); + + self::assertTrue($reader->open('guzzle://stream')); + self::assertTrue($reader->read()); + self::assertSame('foo', $reader->name); + } + + public function testXmlWriterWithStream() + { + if (!class_exists('XMLWriter')) { + self::markTestSkipped('XML Writer is not available.'); + } + if (defined('HHVM_VERSION')) { + self::markTestSkipped('This does not work on HHVM.'); + } + + $stream = Psr7\Utils::streamFor(fopen('php://memory', 'wb')); + + StreamWrapper::register(); + libxml_set_streams_context(StreamWrapper::createStreamContext($stream)); + $writer = new \XMLWriter(); + + self::assertTrue($writer->openURI('guzzle://stream')); + self::assertTrue($writer->startDocument()); + self::assertTrue($writer->writeElement('foo')); + self::assertTrue($writer->endDocument()); + + $stream->rewind(); + self::assertXmlStringEqualsXmlString('', (string) $stream); + } +} diff --git a/vendor/guzzlehttp/psr7/tests/UploadedFileTest.php b/vendor/guzzlehttp/psr7/tests/UploadedFileTest.php new file mode 100644 index 0000000000..d41dee526f --- /dev/null +++ b/vendor/guzzlehttp/psr7/tests/UploadedFileTest.php @@ -0,0 +1,287 @@ +cleanup = []; + } + + /** + * @after + */ + public function tearDownTest() + { + foreach ($this->cleanup as $file) { + if (is_scalar($file) && file_exists($file)) { + unlink($file); + } + } + } + + public function invalidStreams() + { + return [ + 'null' => [null], + 'true' => [true], + 'false' => [false], + 'int' => [1], + 'float' => [1.1], + 'array' => [['filename']], + 'object' => [(object) ['filename']], + ]; + } + + /** + * @dataProvider invalidStreams + */ + public function testRaisesExceptionOnInvalidStreamOrFile($streamOrFile) + { + $this->expectExceptionGuzzle('InvalidArgumentException'); + + new UploadedFile($streamOrFile, 0, UPLOAD_ERR_OK); + } + + public function invalidSizes() + { + return [ + 'null' => [null], + 'float' => [1.1], + 'array' => [[1]], + 'object' => [(object) [1]], + ]; + } + + /** + * @dataProvider invalidSizes + */ + public function testRaisesExceptionOnInvalidSize($size) + { + $this->expectExceptionGuzzle('InvalidArgumentException', 'size'); + + new UploadedFile(fopen('php://temp', 'wb+'), $size, UPLOAD_ERR_OK); + } + + public function invalidErrorStatuses() + { + return [ + 'null' => [null], + 'true' => [true], + 'false' => [false], + 'float' => [1.1], + 'string' => ['1'], + 'array' => [[1]], + 'object' => [(object) [1]], + 'negative' => [-1], + 'too-big' => [9], + ]; + } + + /** + * @dataProvider invalidErrorStatuses + */ + public function testRaisesExceptionOnInvalidErrorStatus($status) + { + $this->expectExceptionGuzzle('InvalidArgumentException', 'status'); + + new UploadedFile(fopen('php://temp', 'wb+'), 0, $status); + } + + public function invalidFilenamesAndMediaTypes() + { + return [ + 'true' => [true], + 'false' => [false], + 'int' => [1], + 'float' => [1.1], + 'array' => [['string']], + 'object' => [(object) ['string']], + ]; + } + + /** + * @dataProvider invalidFilenamesAndMediaTypes + */ + public function testRaisesExceptionOnInvalidClientFilename($filename) + { + $this->expectExceptionGuzzle('InvalidArgumentException', 'filename'); + + new UploadedFile(fopen('php://temp', 'wb+'), 0, UPLOAD_ERR_OK, $filename); + } + + /** + * @dataProvider invalidFilenamesAndMediaTypes + */ + public function testRaisesExceptionOnInvalidClientMediaType($mediaType) + { + $this->expectExceptionGuzzle('InvalidArgumentException', 'media type'); + + new UploadedFile(fopen('php://temp', 'wb+'), 0, UPLOAD_ERR_OK, 'foobar.baz', $mediaType); + } + + public function testGetStreamReturnsOriginalStreamObject() + { + $stream = new Stream(fopen('php://temp', 'r')); + $upload = new UploadedFile($stream, 0, UPLOAD_ERR_OK); + + self::assertSame($stream, $upload->getStream()); + } + + public function testGetStreamReturnsWrappedPhpStream() + { + $stream = fopen('php://temp', 'wb+'); + $upload = new UploadedFile($stream, 0, UPLOAD_ERR_OK); + $uploadStream = $upload->getStream()->detach(); + + self::assertSame($stream, $uploadStream); + } + + public function testGetStreamReturnsStreamForFile() + { + $this->cleanup[] = $stream = tempnam(sys_get_temp_dir(), 'stream_file'); + $upload = new UploadedFile($stream, 0, UPLOAD_ERR_OK); + $uploadStream = $upload->getStream(); + $r = new ReflectionProperty($uploadStream, 'filename'); + $r->setAccessible(true); + + self::assertSame($stream, $r->getValue($uploadStream)); + } + + public function testSuccessful() + { + $stream = \GuzzleHttp\Psr7\Utils::streamFor('Foo bar!'); + $upload = new UploadedFile($stream, $stream->getSize(), UPLOAD_ERR_OK, 'filename.txt', 'text/plain'); + + self::assertSame($stream->getSize(), $upload->getSize()); + self::assertSame('filename.txt', $upload->getClientFilename()); + self::assertSame('text/plain', $upload->getClientMediaType()); + + $this->cleanup[] = $to = tempnam(sys_get_temp_dir(), 'successful'); + $upload->moveTo($to); + self::assertFileExists($to); + self::assertSame($stream->__toString(), file_get_contents($to)); + } + + public function invalidMovePaths() + { + return [ + 'null' => [null], + 'true' => [true], + 'false' => [false], + 'int' => [1], + 'float' => [1.1], + 'empty' => [''], + 'array' => [['filename']], + 'object' => [(object) ['filename']], + ]; + } + + /** + * @dataProvider invalidMovePaths + */ + public function testMoveRaisesExceptionForInvalidPath($path) + { + $stream = \GuzzleHttp\Psr7\Utils::streamFor('Foo bar!'); + $upload = new UploadedFile($stream, 0, UPLOAD_ERR_OK); + + $this->cleanup[] = $path; + + $this->expectExceptionGuzzle('InvalidArgumentException', 'path'); + $upload->moveTo($path); + } + + public function testMoveCannotBeCalledMoreThanOnce() + { + $stream = \GuzzleHttp\Psr7\Utils::streamFor('Foo bar!'); + $upload = new UploadedFile($stream, 0, UPLOAD_ERR_OK); + + $this->cleanup[] = $to = tempnam(sys_get_temp_dir(), 'diac'); + $upload->moveTo($to); + self::assertFileExists($to); + + $this->expectExceptionGuzzle('RuntimeException', 'moved'); + $upload->moveTo($to); + } + + public function testCannotRetrieveStreamAfterMove() + { + $stream = \GuzzleHttp\Psr7\Utils::streamFor('Foo bar!'); + $upload = new UploadedFile($stream, 0, UPLOAD_ERR_OK); + + $this->cleanup[] = $to = tempnam(sys_get_temp_dir(), 'diac'); + $upload->moveTo($to); + self::assertFileExists($to); + + $this->expectExceptionGuzzle('RuntimeException', 'moved'); + $upload->getStream(); + } + + public function nonOkErrorStatus() + { + return [ + 'UPLOAD_ERR_INI_SIZE' => [ UPLOAD_ERR_INI_SIZE ], + 'UPLOAD_ERR_FORM_SIZE' => [ UPLOAD_ERR_FORM_SIZE ], + 'UPLOAD_ERR_PARTIAL' => [ UPLOAD_ERR_PARTIAL ], + 'UPLOAD_ERR_NO_FILE' => [ UPLOAD_ERR_NO_FILE ], + 'UPLOAD_ERR_NO_TMP_DIR' => [ UPLOAD_ERR_NO_TMP_DIR ], + 'UPLOAD_ERR_CANT_WRITE' => [ UPLOAD_ERR_CANT_WRITE ], + 'UPLOAD_ERR_EXTENSION' => [ UPLOAD_ERR_EXTENSION ], + ]; + } + + /** + * @dataProvider nonOkErrorStatus + */ + public function testConstructorDoesNotRaiseExceptionForInvalidStreamWhenErrorStatusPresent($status) + { + $uploadedFile = new UploadedFile('not ok', 0, $status); + self::assertSame($status, $uploadedFile->getError()); + } + + /** + * @dataProvider nonOkErrorStatus + */ + public function testMoveToRaisesExceptionWhenErrorStatusPresent($status) + { + $uploadedFile = new UploadedFile('not ok', 0, $status); + $this->expectExceptionGuzzle('RuntimeException', 'upload error'); + $uploadedFile->moveTo(__DIR__ . '/' . sha1(uniqid('', true))); + } + + /** + * @dataProvider nonOkErrorStatus + */ + public function testGetStreamRaisesExceptionWhenErrorStatusPresent($status) + { + $uploadedFile = new UploadedFile('not ok', 0, $status); + $this->expectExceptionGuzzle('RuntimeException', 'upload error'); + $uploadedFile->getStream(); + } + + public function testMoveToCreatesStreamIfOnlyAFilenameWasProvided() + { + $this->cleanup[] = $from = tempnam(sys_get_temp_dir(), 'copy_from'); + $this->cleanup[] = $to = tempnam(sys_get_temp_dir(), 'copy_to'); + + copy(__FILE__, $from); + + $uploadedFile = new UploadedFile($from, 100, UPLOAD_ERR_OK, basename($from), 'text/plain'); + $uploadedFile->moveTo($to); + + self::assertFileEquals(__FILE__, $to); + } +} diff --git a/vendor/guzzlehttp/psr7/tests/UriComparatorTest.php b/vendor/guzzlehttp/psr7/tests/UriComparatorTest.php new file mode 100644 index 0000000000..ccfdd7b760 --- /dev/null +++ b/vendor/guzzlehttp/psr7/tests/UriComparatorTest.php @@ -0,0 +1,42 @@ +withPath("/$actualEncoding")->withQuery($actualEncoding); + + self::assertSame("/$actualEncoding?$actualEncoding", (string) $uri, 'Not normalized automatically beforehand'); + + $normalizedUri = UriNormalizer::normalize($uri, UriNormalizer::CAPITALIZE_PERCENT_ENCODING); + + self::assertInstanceOf('Psr\Http\Message\UriInterface', $normalizedUri); + self::assertSame("/$expectEncoding?$expectEncoding", (string) $normalizedUri); + } + + /** + * @dataProvider getUnreservedCharacters + */ + public function testDecodeUnreservedCharacters($char) + { + $percentEncoded = '%' . bin2hex($char); + // Add encoded reserved characters to test that those are not decoded and include the percent-encoded + // unreserved character both in lower and upper case to test the decoding is case-insensitive. + $encodedChars = $percentEncoded . '%2F%5B' . strtoupper($percentEncoded); + $uri = (new Uri())->withPath("/$encodedChars")->withQuery($encodedChars); + + self::assertSame("/$encodedChars?$encodedChars", (string) $uri, 'Not normalized automatically beforehand'); + + $normalizedUri = UriNormalizer::normalize($uri, UriNormalizer::DECODE_UNRESERVED_CHARACTERS); + + self::assertInstanceOf('Psr\Http\Message\UriInterface', $normalizedUri); + self::assertSame("/$char%2F%5B$char?$char%2F%5B$char", (string) $normalizedUri); + } + + public function getUnreservedCharacters() + { + $unreservedChars = array_merge(range('a', 'z'), range('A', 'Z'), range('0', '9'), ['-', '.', '_', '~']); + + return array_map(function ($char) { + return [$char]; + }, $unreservedChars); + } + + /** + * @dataProvider getEmptyPathTestCases + */ + public function testConvertEmptyPath($uri, $expected) + { + $normalizedUri = UriNormalizer::normalize(new Uri($uri), UriNormalizer::CONVERT_EMPTY_PATH); + + self::assertInstanceOf('Psr\Http\Message\UriInterface', $normalizedUri); + self::assertSame($expected, (string) $normalizedUri); + } + + public function getEmptyPathTestCases() + { + return [ + ['http://example.org', 'http://example.org/'], + ['https://example.org', 'https://example.org/'], + ['urn://example.org', 'urn://example.org'], + ]; + } + + public function testRemoveDefaultHost() + { + $uri = new Uri('file://localhost/myfile'); + $normalizedUri = UriNormalizer::normalize($uri, UriNormalizer::REMOVE_DEFAULT_HOST); + + self::assertInstanceOf('Psr\Http\Message\UriInterface', $normalizedUri); + self::assertSame('file:///myfile', (string) $normalizedUri); + } + + public function testRemoveDefaultPort() + { + $uri = $this->getMockBuilder('Psr\Http\Message\UriInterface')->getMock(); + $uri->expects(self::any())->method('getScheme')->will(self::returnValue('http')); + $uri->expects(self::any())->method('getPort')->will(self::returnValue(80)); + $uri->expects(self::once())->method('withPort')->with(null)->will(self::returnValue(new Uri('http://example.org'))); + + $normalizedUri = UriNormalizer::normalize($uri, UriNormalizer::REMOVE_DEFAULT_PORT); + + self::assertInstanceOf('Psr\Http\Message\UriInterface', $normalizedUri); + self::assertNull($normalizedUri->getPort()); + } + + public function testRemoveDotSegments() + { + $uri = new Uri('http://example.org/../a/b/../c/./d.html'); + $normalizedUri = UriNormalizer::normalize($uri, UriNormalizer::REMOVE_DOT_SEGMENTS); + + self::assertInstanceOf('Psr\Http\Message\UriInterface', $normalizedUri); + self::assertSame('http://example.org/a/c/d.html', (string) $normalizedUri); + } + + public function testRemoveDotSegmentsOfAbsolutePathReference() + { + $uri = new Uri('/../a/b/../c/./d.html'); + $normalizedUri = UriNormalizer::normalize($uri, UriNormalizer::REMOVE_DOT_SEGMENTS); + + self::assertInstanceOf('Psr\Http\Message\UriInterface', $normalizedUri); + self::assertSame('/a/c/d.html', (string) $normalizedUri); + } + + public function testRemoveDotSegmentsOfRelativePathReference() + { + $uri = new Uri('../c/./d.html'); + $normalizedUri = UriNormalizer::normalize($uri, UriNormalizer::REMOVE_DOT_SEGMENTS); + + self::assertInstanceOf('Psr\Http\Message\UriInterface', $normalizedUri); + self::assertSame('../c/./d.html', (string) $normalizedUri); + } + + public function testRemoveDuplicateSlashes() + { + $uri = new Uri('http://example.org//foo///bar/bam.html'); + $normalizedUri = UriNormalizer::normalize($uri, UriNormalizer::REMOVE_DUPLICATE_SLASHES); + + self::assertInstanceOf('Psr\Http\Message\UriInterface', $normalizedUri); + self::assertSame('http://example.org/foo/bar/bam.html', (string) $normalizedUri); + } + + public function testSortQueryParameters() + { + $uri = new Uri('?lang=en&article=fred'); + $normalizedUri = UriNormalizer::normalize($uri, UriNormalizer::SORT_QUERY_PARAMETERS); + + self::assertInstanceOf('Psr\Http\Message\UriInterface', $normalizedUri); + self::assertSame('?article=fred&lang=en', (string) $normalizedUri); + } + + public function testSortQueryParametersWithSameKeys() + { + $uri = new Uri('?a=b&b=c&a=a&a&b=a&b=b&a=d&a=c'); + $normalizedUri = UriNormalizer::normalize($uri, UriNormalizer::SORT_QUERY_PARAMETERS); + + self::assertInstanceOf('Psr\Http\Message\UriInterface', $normalizedUri); + self::assertSame('?a&a=a&a=b&a=c&a=d&b=a&b=b&b=c', (string) $normalizedUri); + } + + /** + * @dataProvider getEquivalentTestCases + */ + public function testIsEquivalent($uri1, $uri2, $expected) + { + $equivalent = UriNormalizer::isEquivalent(new Uri($uri1), new Uri($uri2)); + + self::assertSame($expected, $equivalent); + } + + public function getEquivalentTestCases() + { + return [ + ['http://example.org', 'http://example.org', true], + ['hTTp://eXaMpLe.org', 'http://example.org', true], + ['http://example.org/path?#', 'http://example.org/path', true], + ['http://example.org:80', 'http://example.org/', true], + ['http://example.org/../a/.././p%61th?%7a=%5e', 'http://example.org/path?z=%5E', true], + ['https://example.org/', 'http://example.org/', false], + ['https://example.org/', '//example.org/', false], + ['//example.org/', '//example.org/', true], + ['file:/myfile', 'file:///myfile', true], + ['file:///myfile', 'file://localhost/myfile', true], + ]; + } +} diff --git a/vendor/guzzlehttp/psr7/tests/UriResolverTest.php b/vendor/guzzlehttp/psr7/tests/UriResolverTest.php new file mode 100644 index 0000000000..6f41409be7 --- /dev/null +++ b/vendor/guzzlehttp/psr7/tests/UriResolverTest.php @@ -0,0 +1,204 @@ +getScheme()); + self::assertSame('user:pass@example.com:8080', $uri->getAuthority()); + self::assertSame('user:pass', $uri->getUserInfo()); + self::assertSame('example.com', $uri->getHost()); + self::assertSame(8080, $uri->getPort()); + self::assertSame('/path/123', $uri->getPath()); + self::assertSame('q=abc', $uri->getQuery()); + self::assertSame('test', $uri->getFragment()); + self::assertSame('https://user:pass@example.com:8080/path/123?q=abc#test', (string) $uri); + } + + public function testCanTransformAndRetrievePartsIndividually() + { + $uri = (new Uri()) + ->withScheme('https') + ->withUserInfo('user', 'pass') + ->withHost('example.com') + ->withPort(8080) + ->withPath('/path/123') + ->withQuery('q=abc') + ->withFragment('test'); + + self::assertSame('https', $uri->getScheme()); + self::assertSame('user:pass@example.com:8080', $uri->getAuthority()); + self::assertSame('user:pass', $uri->getUserInfo()); + self::assertSame('example.com', $uri->getHost()); + self::assertSame(8080, $uri->getPort()); + self::assertSame('/path/123', $uri->getPath()); + self::assertSame('q=abc', $uri->getQuery()); + self::assertSame('test', $uri->getFragment()); + self::assertSame('https://user:pass@example.com:8080/path/123?q=abc#test', (string) $uri); + } + + /** + * @dataProvider getValidUris + */ + public function testValidUrisStayValid($input) + { + $uri = new Uri($input); + + self::assertSame($input, (string) $uri); + } + + /** + * @dataProvider getValidUris + */ + public function testFromParts($input) + { + $uri = Uri::fromParts(parse_url($input)); + + self::assertSame($input, (string) $uri); + } + + public function getValidUris() + { + return [ + ['urn:path-rootless'], + ['urn:path:with:colon'], + ['urn:/path-absolute'], + ['urn:/'], + // only scheme with empty path + ['urn:'], + // only path + ['/'], + ['relative/'], + ['0'], + // same document reference + [''], + // network path without scheme + ['//example.org'], + ['//example.org/'], + ['//example.org?q#h'], + // only query + ['?q'], + ['?q=abc&foo=bar'], + // only fragment + ['#fragment'], + // dot segments are not removed automatically + ['./foo/../bar'], + ]; + } + + /** + * @dataProvider getInvalidUris + */ + public function testInvalidUrisThrowException($invalidUri) + { + $this->expectExceptionGuzzle('InvalidArgumentException', 'Unable to parse URI'); + + new Uri($invalidUri); + } + + public function getInvalidUris() + { + return [ + // parse_url() requires the host component which makes sense for http(s) + // but not when the scheme is not known or different. So '//' or '///' is + // currently invalid as well but should not according to RFC 3986. + ['http://'], + ['urn://host:with:colon'], // host cannot contain ":" + ]; + } + + public function testPortMustBeValid() + { + $this->expectExceptionGuzzle('InvalidArgumentException', 'Must be between 0 and 65535'); + + (new Uri())->withPort(100000); + } + + public function testWithPortCannotBeNegative() + { + $this->expectExceptionGuzzle('InvalidArgumentException', 'Invalid port: -1. Must be between 0 and 65535'); + + (new Uri())->withPort(-1); + } + + public function testParseUriPortCannotBeNegative() + { + $this->expectExceptionGuzzle('InvalidArgumentException', 'Unable to parse URI'); + + new Uri('//example.com:-1'); + } + + public function testSchemeMustHaveCorrectType() + { + $this->expectExceptionGuzzle('InvalidArgumentException'); + + (new Uri())->withScheme([]); + } + + public function testHostMustHaveCorrectType() + { + $this->expectExceptionGuzzle('InvalidArgumentException'); + + (new Uri())->withHost([]); + } + + public function testPathMustHaveCorrectType() + { + $this->expectExceptionGuzzle('InvalidArgumentException'); + + (new Uri())->withPath([]); + } + + public function testQueryMustHaveCorrectType() + { + $this->expectExceptionGuzzle('InvalidArgumentException'); + + (new Uri())->withQuery([]); + } + + public function testFragmentMustHaveCorrectType() + { + $this->expectExceptionGuzzle('InvalidArgumentException'); + + (new Uri())->withFragment([]); + } + + public function testCanParseFalseyUriParts() + { + $uri = new Uri('0://0:0@0/0?0#0'); + + self::assertSame('0', $uri->getScheme()); + self::assertSame('0:0@0', $uri->getAuthority()); + self::assertSame('0:0', $uri->getUserInfo()); + self::assertSame('0', $uri->getHost()); + self::assertSame('/0', $uri->getPath()); + self::assertSame('0', $uri->getQuery()); + self::assertSame('0', $uri->getFragment()); + self::assertSame('0://0:0@0/0?0#0', (string) $uri); + } + + public function testCanConstructFalseyUriParts() + { + $uri = (new Uri()) + ->withScheme('0') + ->withUserInfo('0', '0') + ->withHost('0') + ->withPath('/0') + ->withQuery('0') + ->withFragment('0'); + + self::assertSame('0', $uri->getScheme()); + self::assertSame('0:0@0', $uri->getAuthority()); + self::assertSame('0:0', $uri->getUserInfo()); + self::assertSame('0', $uri->getHost()); + self::assertSame('/0', $uri->getPath()); + self::assertSame('0', $uri->getQuery()); + self::assertSame('0', $uri->getFragment()); + self::assertSame('0://0:0@0/0?0#0', (string) $uri); + } + + /** + * @dataProvider getPortTestCases + */ + public function testIsDefaultPort($scheme, $port, $isDefaultPort) + { + $uri = $this->getMockBuilder('Psr\Http\Message\UriInterface')->getMock(); + $uri->expects(self::any())->method('getScheme')->will(self::returnValue($scheme)); + $uri->expects(self::any())->method('getPort')->will(self::returnValue($port)); + + self::assertSame($isDefaultPort, Uri::isDefaultPort($uri)); + } + + public function getPortTestCases() + { + return [ + ['http', null, true], + ['http', 80, true], + ['http', 8080, false], + ['https', null, true], + ['https', 443, true], + ['https', 444, false], + ['ftp', 21, true], + ['gopher', 70, true], + ['nntp', 119, true], + ['news', 119, true], + ['telnet', 23, true], + ['tn3270', 23, true], + ['imap', 143, true], + ['pop', 110, true], + ['ldap', 389, true], + ]; + } + + public function testIsAbsolute() + { + self::assertTrue(Uri::isAbsolute(new Uri('http://example.org'))); + self::assertFalse(Uri::isAbsolute(new Uri('//example.org'))); + self::assertFalse(Uri::isAbsolute(new Uri('/abs-path'))); + self::assertFalse(Uri::isAbsolute(new Uri('rel-path'))); + } + + public function testIsNetworkPathReference() + { + self::assertFalse(Uri::isNetworkPathReference(new Uri('http://example.org'))); + self::assertTrue(Uri::isNetworkPathReference(new Uri('//example.org'))); + self::assertFalse(Uri::isNetworkPathReference(new Uri('/abs-path'))); + self::assertFalse(Uri::isNetworkPathReference(new Uri('rel-path'))); + } + + public function testIsAbsolutePathReference() + { + self::assertFalse(Uri::isAbsolutePathReference(new Uri('http://example.org'))); + self::assertFalse(Uri::isAbsolutePathReference(new Uri('//example.org'))); + self::assertTrue(Uri::isAbsolutePathReference(new Uri('/abs-path'))); + self::assertTrue(Uri::isAbsolutePathReference(new Uri('/'))); + self::assertFalse(Uri::isAbsolutePathReference(new Uri('rel-path'))); + } + + public function testIsRelativePathReference() + { + self::assertFalse(Uri::isRelativePathReference(new Uri('http://example.org'))); + self::assertFalse(Uri::isRelativePathReference(new Uri('//example.org'))); + self::assertFalse(Uri::isRelativePathReference(new Uri('/abs-path'))); + self::assertTrue(Uri::isRelativePathReference(new Uri('rel-path'))); + self::assertTrue(Uri::isRelativePathReference(new Uri(''))); + } + + public function testIsSameDocumentReference() + { + self::assertFalse(Uri::isSameDocumentReference(new Uri('http://example.org'))); + self::assertFalse(Uri::isSameDocumentReference(new Uri('//example.org'))); + self::assertFalse(Uri::isSameDocumentReference(new Uri('/abs-path'))); + self::assertFalse(Uri::isSameDocumentReference(new Uri('rel-path'))); + self::assertFalse(Uri::isSameDocumentReference(new Uri('?query'))); + self::assertTrue(Uri::isSameDocumentReference(new Uri(''))); + self::assertTrue(Uri::isSameDocumentReference(new Uri('#fragment'))); + + $baseUri = new Uri('http://example.org/path?foo=bar'); + + self::assertTrue(Uri::isSameDocumentReference(new Uri('#fragment'), $baseUri)); + self::assertTrue(Uri::isSameDocumentReference(new Uri('?foo=bar#fragment'), $baseUri)); + self::assertTrue(Uri::isSameDocumentReference(new Uri('/path?foo=bar#fragment'), $baseUri)); + self::assertTrue(Uri::isSameDocumentReference(new Uri('path?foo=bar#fragment'), $baseUri)); + self::assertTrue(Uri::isSameDocumentReference(new Uri('//example.org/path?foo=bar#fragment'), $baseUri)); + self::assertTrue(Uri::isSameDocumentReference(new Uri('http://example.org/path?foo=bar#fragment'), $baseUri)); + + self::assertFalse(Uri::isSameDocumentReference(new Uri('https://example.org/path?foo=bar'), $baseUri)); + self::assertFalse(Uri::isSameDocumentReference(new Uri('http://example.com/path?foo=bar'), $baseUri)); + self::assertFalse(Uri::isSameDocumentReference(new Uri('http://example.org/'), $baseUri)); + self::assertFalse(Uri::isSameDocumentReference(new Uri('http://example.org'), $baseUri)); + + self::assertFalse(Uri::isSameDocumentReference(new Uri('urn:/path'), new Uri('urn://example.com/path'))); + } + + public function testAddAndRemoveQueryValues() + { + $uri = new Uri(); + $uri = Uri::withQueryValue($uri, 'a', 'b'); + $uri = Uri::withQueryValue($uri, 'c', 'd'); + $uri = Uri::withQueryValue($uri, 'e', null); + self::assertSame('a=b&c=d&e', $uri->getQuery()); + + $uri = Uri::withoutQueryValue($uri, 'c'); + self::assertSame('a=b&e', $uri->getQuery()); + $uri = Uri::withoutQueryValue($uri, 'e'); + self::assertSame('a=b', $uri->getQuery()); + $uri = Uri::withoutQueryValue($uri, 'a'); + self::assertSame('', $uri->getQuery()); + } + + public function testNumericQueryValue() + { + $uri = Uri::withQueryValue(new Uri(), 'version', 1); + self::assertSame('version=1', $uri->getQuery()); + } + + public function testWithQueryValues() + { + $uri = new Uri(); + $uri = Uri::withQueryValues($uri, [ + 'key1' => 'value1', + 'key2' => 'value2' + ]); + + self::assertSame('key1=value1&key2=value2', $uri->getQuery()); + } + + public function testWithQueryValuesReplacesSameKeys() + { + $uri = new Uri(); + + $uri = Uri::withQueryValues($uri, [ + 'key1' => 'value1', + 'key2' => 'value2' + ]); + + $uri = Uri::withQueryValues($uri, [ + 'key2' => 'newvalue' + ]); + + self::assertSame('key1=value1&key2=newvalue', $uri->getQuery()); + } + + public function testWithQueryValueReplacesSameKeys() + { + $uri = new Uri(); + $uri = Uri::withQueryValue($uri, 'a', 'b'); + $uri = Uri::withQueryValue($uri, 'c', 'd'); + $uri = Uri::withQueryValue($uri, 'a', 'e'); + self::assertSame('c=d&a=e', $uri->getQuery()); + } + + public function testWithoutQueryValueRemovesAllSameKeys() + { + $uri = (new Uri())->withQuery('a=b&c=d&a=e'); + $uri = Uri::withoutQueryValue($uri, 'a'); + self::assertSame('c=d', $uri->getQuery()); + } + + public function testRemoveNonExistingQueryValue() + { + $uri = new Uri(); + $uri = Uri::withQueryValue($uri, 'a', 'b'); + $uri = Uri::withoutQueryValue($uri, 'c'); + self::assertSame('a=b', $uri->getQuery()); + } + + public function testWithQueryValueHandlesEncoding() + { + $uri = new Uri(); + $uri = Uri::withQueryValue($uri, 'E=mc^2', 'ein&stein'); + self::assertSame('E%3Dmc%5E2=ein%26stein', $uri->getQuery(), 'Decoded key/value get encoded'); + + $uri = new Uri(); + $uri = Uri::withQueryValue($uri, 'E%3Dmc%5e2', 'ein%26stein'); + self::assertSame('E%3Dmc%5e2=ein%26stein', $uri->getQuery(), 'Encoded key/value do not get double-encoded'); + } + + public function testWithoutQueryValueHandlesEncoding() + { + // It also tests that the case of the percent-encoding does not matter, + // i.e. both lowercase "%3d" and uppercase "%5E" can be removed. + $uri = (new Uri())->withQuery('E%3dmc%5E2=einstein&foo=bar'); + $uri = Uri::withoutQueryValue($uri, 'E=mc^2'); + self::assertSame('foo=bar', $uri->getQuery(), 'Handles key in decoded form'); + + $uri = (new Uri())->withQuery('E%3dmc%5E2=einstein&foo=bar'); + $uri = Uri::withoutQueryValue($uri, 'E%3Dmc%5e2'); + self::assertSame('foo=bar', $uri->getQuery(), 'Handles key in encoded form'); + } + + public function testSchemeIsNormalizedToLowercase() + { + $uri = new Uri('HTTP://example.com'); + + self::assertSame('http', $uri->getScheme()); + self::assertSame('http://example.com', (string) $uri); + + $uri = (new Uri('//example.com'))->withScheme('HTTP'); + + self::assertSame('http', $uri->getScheme()); + self::assertSame('http://example.com', (string) $uri); + } + + public function testHostIsNormalizedToLowercase() + { + $uri = new Uri('//eXaMpLe.CoM'); + + self::assertSame('example.com', $uri->getHost()); + self::assertSame('//example.com', (string) $uri); + + $uri = (new Uri())->withHost('eXaMpLe.CoM'); + + self::assertSame('example.com', $uri->getHost()); + self::assertSame('//example.com', (string) $uri); + } + + public function testPortIsNullIfStandardPortForScheme() + { + // HTTPS standard port + $uri = new Uri('https://example.com:443'); + self::assertNull($uri->getPort()); + self::assertSame('example.com', $uri->getAuthority()); + + $uri = (new Uri('https://example.com'))->withPort(443); + self::assertNull($uri->getPort()); + self::assertSame('example.com', $uri->getAuthority()); + + // HTTP standard port + $uri = new Uri('http://example.com:80'); + self::assertNull($uri->getPort()); + self::assertSame('example.com', $uri->getAuthority()); + + $uri = (new Uri('http://example.com'))->withPort(80); + self::assertNull($uri->getPort()); + self::assertSame('example.com', $uri->getAuthority()); + } + + public function testPortIsReturnedIfSchemeUnknown() + { + $uri = (new Uri('//example.com'))->withPort(80); + + self::assertSame(80, $uri->getPort()); + self::assertSame('example.com:80', $uri->getAuthority()); + } + + public function testStandardPortIsNullIfSchemeChanges() + { + $uri = new Uri('http://example.com:443'); + self::assertSame('http', $uri->getScheme()); + self::assertSame(443, $uri->getPort()); + + $uri = $uri->withScheme('https'); + self::assertNull($uri->getPort()); + } + + public function testPortPassedAsStringIsCastedToInt() + { + $uri = (new Uri('//example.com'))->withPort('8080'); + + self::assertSame(8080, $uri->getPort(), 'Port is returned as integer'); + self::assertSame('example.com:8080', $uri->getAuthority()); + } + + public function testPortCanBeRemoved() + { + $uri = (new Uri('http://example.com:8080'))->withPort(null); + + self::assertNull($uri->getPort()); + self::assertSame('http://example.com', (string) $uri); + } + + /** + * In RFC 8986 the host is optional and the authority can only + * consist of the user info and port. + */ + public function testAuthorityWithUserInfoOrPortButWithoutHost() + { + $uri = (new Uri())->withUserInfo('user', 'pass'); + + self::assertSame('user:pass', $uri->getUserInfo()); + self::assertSame('user:pass@', $uri->getAuthority()); + + $uri = $uri->withPort(8080); + self::assertSame(8080, $uri->getPort()); + self::assertSame('user:pass@:8080', $uri->getAuthority()); + self::assertSame('//user:pass@:8080', (string) $uri); + + $uri = $uri->withUserInfo(''); + self::assertSame(':8080', $uri->getAuthority()); + } + + public function testHostInHttpUriDefaultsToLocalhost() + { + $uri = (new Uri())->withScheme('http'); + + self::assertSame('localhost', $uri->getHost()); + self::assertSame('localhost', $uri->getAuthority()); + self::assertSame('http://localhost', (string) $uri); + } + + public function testHostInHttpsUriDefaultsToLocalhost() + { + $uri = (new Uri())->withScheme('https'); + + self::assertSame('localhost', $uri->getHost()); + self::assertSame('localhost', $uri->getAuthority()); + self::assertSame('https://localhost', (string) $uri); + } + + public function testFileSchemeWithEmptyHostReconstruction() + { + $uri = new Uri('file:///tmp/filename.ext'); + + self::assertSame('', $uri->getHost()); + self::assertSame('', $uri->getAuthority()); + self::assertSame('file:///tmp/filename.ext', (string) $uri); + } + + public function uriComponentsEncodingProvider() + { + $unreserved = 'a-zA-Z0-9.-_~!$&\'()*+,;=:@'; + + return [ + // Percent encode spaces + ['/pa th?q=va lue#frag ment', '/pa%20th', 'q=va%20lue', 'frag%20ment', '/pa%20th?q=va%20lue#frag%20ment'], + // Percent encode multibyte + ['/€?€#€', '/%E2%82%AC', '%E2%82%AC', '%E2%82%AC', '/%E2%82%AC?%E2%82%AC#%E2%82%AC'], + // Don't encode something that's already encoded + ['/pa%20th?q=va%20lue#frag%20ment', '/pa%20th', 'q=va%20lue', 'frag%20ment', '/pa%20th?q=va%20lue#frag%20ment'], + // Percent encode invalid percent encodings + ['/pa%2-th?q=va%2-lue#frag%2-ment', '/pa%252-th', 'q=va%252-lue', 'frag%252-ment', '/pa%252-th?q=va%252-lue#frag%252-ment'], + // Don't encode path segments + ['/pa/th//two?q=va/lue#frag/ment', '/pa/th//two', 'q=va/lue', 'frag/ment', '/pa/th//two?q=va/lue#frag/ment'], + // Don't encode unreserved chars or sub-delimiters + ["/$unreserved?$unreserved#$unreserved", "/$unreserved", $unreserved, $unreserved, "/$unreserved?$unreserved#$unreserved"], + // Encoded unreserved chars are not decoded + ['/p%61th?q=v%61lue#fr%61gment', '/p%61th', 'q=v%61lue', 'fr%61gment', '/p%61th?q=v%61lue#fr%61gment'], + ]; + } + + /** + * @dataProvider uriComponentsEncodingProvider + */ + public function testUriComponentsGetEncodedProperly($input, $path, $query, $fragment, $output) + { + $uri = new Uri($input); + self::assertSame($path, $uri->getPath()); + self::assertSame($query, $uri->getQuery()); + self::assertSame($fragment, $uri->getFragment()); + self::assertSame($output, (string) $uri); + } + + public function testWithPathEncodesProperly() + { + $uri = (new Uri())->withPath('/baz?#€/b%61r'); + // Query and fragment delimiters and multibyte chars are encoded. + self::assertSame('/baz%3F%23%E2%82%AC/b%61r', $uri->getPath()); + self::assertSame('/baz%3F%23%E2%82%AC/b%61r', (string) $uri); + } + + public function testWithQueryEncodesProperly() + { + $uri = (new Uri())->withQuery('?=#&€=/&b%61r'); + // A query starting with a "?" is valid and must not be magically removed. Otherwise it would be impossible to + // construct such an URI. Also the "?" and "/" does not need to be encoded in the query. + self::assertSame('?=%23&%E2%82%AC=/&b%61r', $uri->getQuery()); + self::assertSame('??=%23&%E2%82%AC=/&b%61r', (string) $uri); + } + + public function testWithFragmentEncodesProperly() + { + $uri = (new Uri())->withFragment('#€?/b%61r'); + // A fragment starting with a "#" is valid and must not be magically removed. Otherwise it would be impossible to + // construct such an URI. Also the "?" and "/" does not need to be encoded in the fragment. + self::assertSame('%23%E2%82%AC?/b%61r', $uri->getFragment()); + self::assertSame('#%23%E2%82%AC?/b%61r', (string) $uri); + } + + public function testAllowsForRelativeUri() + { + $uri = (new Uri)->withPath('foo'); + self::assertSame('foo', $uri->getPath()); + self::assertSame('foo', (string) $uri); + } + + public function testRelativePathAndAuhorityIsAutomagicallyFixed() + { + // concatenating a relative path with a host doesn't work: "//example.comfoo" would be wrong + $uri = (new Uri)->withPath('foo')->withHost('example.com'); + self::assertSame('/foo', $uri->getPath()); + self::assertSame('//example.com/foo', (string) $uri); + } + + public function testPathStartingWithTwoSlashesAndNoAuthorityIsInvalid() + { + $this->expectExceptionGuzzle('InvalidArgumentException', 'The path of a URI without an authority must not start with two slashes "//"'); + + // URI "//foo" would be interpreted as network reference and thus change the original path to the host + (new Uri)->withPath('//foo'); + } + + public function testPathStartingWithTwoSlashes() + { + $uri = new Uri('http://example.org//path-not-host.com'); + self::assertSame('//path-not-host.com', $uri->getPath()); + + $uri = $uri->withScheme(''); + self::assertSame('//example.org//path-not-host.com', (string) $uri); // This is still valid + $this->expectExceptionGuzzle('\InvalidArgumentException'); + $uri->withHost(''); // Now it becomes invalid + } + + public function testRelativeUriWithPathBeginngWithColonSegmentIsInvalid() + { + $this->expectExceptionGuzzle('InvalidArgumentException', 'A relative URI must not have a path beginning with a segment containing a colon'); + + (new Uri)->withPath('mailto:foo'); + } + + public function testRelativeUriWithPathHavingColonSegment() + { + $uri = (new Uri('urn:/mailto:foo'))->withScheme(''); + self::assertSame('/mailto:foo', $uri->getPath()); + + $this->expectExceptionGuzzle('\InvalidArgumentException'); + (new Uri('urn:mailto:foo'))->withScheme(''); + } + + public function testDefaultReturnValuesOfGetters() + { + $uri = new Uri(); + + self::assertSame('', $uri->getScheme()); + self::assertSame('', $uri->getAuthority()); + self::assertSame('', $uri->getUserInfo()); + self::assertSame('', $uri->getHost()); + self::assertNull($uri->getPort()); + self::assertSame('', $uri->getPath()); + self::assertSame('', $uri->getQuery()); + self::assertSame('', $uri->getFragment()); + } + + public function testImmutability() + { + $uri = new Uri(); + + self::assertNotSame($uri, $uri->withScheme('https')); + self::assertNotSame($uri, $uri->withUserInfo('user', 'pass')); + self::assertNotSame($uri, $uri->withHost('example.com')); + self::assertNotSame($uri, $uri->withPort(8080)); + self::assertNotSame($uri, $uri->withPath('/path/123')); + self::assertNotSame($uri, $uri->withQuery('q=abc')); + self::assertNotSame($uri, $uri->withFragment('test')); + } + + public function testExtendingClassesInstantiates() + { + // The non-standard port triggers a cascade of private methods which + // should not use late static binding to access private static members. + // If they do, this will fatal. + self::assertInstanceOf( + 'GuzzleHttp\Tests\Psr7\ExtendedUriTest', + new ExtendedUriTest('http://h:9/') + ); + } + + public function testSpecialCharsOfUserInfo() + { + // The `userInfo` must always be URL-encoded. + $uri = (new Uri)->withUserInfo('foo@bar.com', 'pass#word'); + self::assertSame('foo%40bar.com:pass%23word', $uri->getUserInfo()); + + // The `userInfo` can already be URL-encoded: it should not be encoded twice. + $uri = (new Uri)->withUserInfo('foo%40bar.com', 'pass%23word'); + self::assertSame('foo%40bar.com:pass%23word', $uri->getUserInfo()); + } + + public function testInternationalizedDomainName() + { + $uri = new Uri('https://яндекс.рф'); + self::assertSame('яндекс.рф', $uri->getHost()); + + $uri = new Uri('https://яндекAс.рф'); + self::assertSame('яндекaс.рф', $uri->getHost()); + } + + public function testIPv6Host() + { + $uri = new Uri('https://[2a00:f48:1008::212:183:10]'); + self::assertSame('[2a00:f48:1008::212:183:10]', $uri->getHost()); + + $uri = new Uri('http://[2a00:f48:1008::212:183:10]:56?foo=bar'); + self::assertSame('[2a00:f48:1008::212:183:10]', $uri->getHost()); + self::assertSame(56, $uri->getPort()); + self::assertSame('foo=bar', $uri->getQuery()); + } +} + +class ExtendedUriTest extends Uri +{ +} diff --git a/vendor/guzzlehttp/psr7/tests/UtilsTest.php b/vendor/guzzlehttp/psr7/tests/UtilsTest.php new file mode 100644 index 0000000000..4f62e3a324 --- /dev/null +++ b/vendor/guzzlehttp/psr7/tests/UtilsTest.php @@ -0,0 +1,469 @@ +seek(0); + self::assertSame('foo', Psr7\Utils::copyToString($s, 3)); + self::assertSame('baz', Psr7\Utils::copyToString($s, 3)); + self::assertSame('', Psr7\Utils::copyToString($s)); + } + + public function testCopiesToStringStopsWhenReadFails() + { + $s1 = Psr7\Utils::streamFor('foobaz'); + $s1 = FnStream::decorate($s1, [ + 'read' => function () { + return ''; + }, + ]); + $result = Psr7\Utils::copyToString($s1); + self::assertSame('', $result); + } + + public function testCopiesToStream() + { + $s1 = Psr7\Utils::streamFor('foobaz'); + $s2 = Psr7\Utils::streamFor(''); + Psr7\Utils::copyToStream($s1, $s2); + self::assertSame('foobaz', (string)$s2); + $s2 = Psr7\Utils::streamFor(''); + $s1->seek(0); + Psr7\Utils::copyToStream($s1, $s2, 3); + self::assertSame('foo', (string)$s2); + Psr7\Utils::copyToStream($s1, $s2, 3); + self::assertSame('foobaz', (string)$s2); + } + + public function testStopsCopyToStreamWhenWriteFails() + { + $s1 = Psr7\Utils::streamFor('foobaz'); + $s2 = Psr7\Utils::streamFor(''); + $s2 = FnStream::decorate($s2, [ + 'write' => function () { + return 0; + }, + ]); + Psr7\Utils::copyToStream($s1, $s2); + self::assertSame('', (string)$s2); + } + + public function testStopsCopyToSteamWhenWriteFailsWithMaxLen() + { + $s1 = Psr7\Utils::streamFor('foobaz'); + $s2 = Psr7\Utils::streamFor(''); + $s2 = FnStream::decorate($s2, [ + 'write' => function () { + return 0; + }, + ]); + Psr7\Utils::copyToStream($s1, $s2, 10); + self::assertSame('', (string)$s2); + } + + public function testCopyToStreamReadsInChunksInsteadOfAllInMemory() + { + $sizes = []; + $s1 = new Psr7\FnStream([ + 'eof' => function () { + return false; + }, + 'read' => function ($size) use (&$sizes) { + $sizes[] = $size; + return str_repeat('.', $size); + }, + ]); + $s2 = Psr7\Utils::streamFor(''); + Psr7\Utils::copyToStream($s1, $s2, 16394); + $s2->seek(0); + self::assertSame(16394, strlen($s2->getContents())); + self::assertSame(8192, $sizes[0]); + self::assertSame(8192, $sizes[1]); + self::assertSame(10, $sizes[2]); + } + + public function testStopsCopyToSteamWhenReadFailsWithMaxLen() + { + $s1 = Psr7\Utils::streamFor('foobaz'); + $s1 = FnStream::decorate($s1, [ + 'read' => function () { + return ''; + }, + ]); + $s2 = Psr7\Utils::streamFor(''); + Psr7\Utils::copyToStream($s1, $s2, 10); + self::assertSame('', (string)$s2); + } + + public function testReadsLines() + { + $s = Psr7\Utils::streamFor("foo\nbaz\nbar"); + self::assertSame("foo\n", Psr7\Utils::readLine($s)); + self::assertSame("baz\n", Psr7\Utils::readLine($s)); + self::assertSame('bar', Psr7\Utils::readLine($s)); + } + + public function testReadsLinesUpToMaxLength() + { + $s = Psr7\Utils::streamFor("12345\n"); + self::assertSame('123', Psr7\Utils::readLine($s, 4)); + self::assertSame("45\n", Psr7\Utils::readLine($s)); + } + + public function testReadLinesEof() + { + // Should return empty string on EOF + $s = Psr7\Utils::streamFor("foo\nbar"); + while (!$s->eof()) { + Psr7\Utils::readLine($s); + } + self::assertSame('', Psr7\Utils::readLine($s)); + } + + public function testReadsLineUntilFalseReturnedFromRead() + { + $s = $this->getMockBuilder('GuzzleHttp\Psr7\Stream') + ->setMethods(['read', 'eof']) + ->disableOriginalConstructor() + ->getMock(); + $s->expects(self::exactly(2)) + ->method('read') + ->will(self::returnCallback(function () { + static $c = false; + if ($c) { + return false; + } + $c = true; + return 'h'; + })); + $s->expects(self::exactly(2)) + ->method('eof') + ->will(self::returnValue(false)); + self::assertSame('h', Psr7\Utils::readLine($s)); + } + + public function testCalculatesHash() + { + $s = Psr7\Utils::streamFor('foobazbar'); + self::assertSame(md5('foobazbar'), Psr7\Utils::hash($s, 'md5')); + } + + public function testCalculatesHashThrowsWhenSeekFails() + { + $s = new NoSeekStream(Psr7\Utils::streamFor('foobazbar')); + $s->read(2); + + $this->expectExceptionGuzzle('RuntimeException'); + + Psr7\Utils::hash($s, 'md5'); + } + + public function testCalculatesHashSeeksToOriginalPosition() + { + $s = Psr7\Utils::streamFor('foobazbar'); + $s->seek(4); + self::assertSame(md5('foobazbar'), Psr7\Utils::hash($s, 'md5')); + self::assertSame(4, $s->tell()); + } + + public function testOpensFilesSuccessfully() + { + $r = Psr7\Utils::tryFopen(__FILE__, 'r'); + $this->assertInternalTypeGuzzle('resource', $r); + fclose($r); + } + + public function testThrowsExceptionNotWarning() + { + $this->expectExceptionGuzzle('RuntimeException', 'Unable to open "/path/to/does/not/exist" using mode "r"'); + + Psr7\Utils::tryFopen('/path/to/does/not/exist', 'r'); + } + + public function testThrowsExceptionNotValueError() + { + $this->expectExceptionGuzzle('RuntimeException', 'Unable to open "" using mode "r"'); + + Psr7\Utils::tryFopen('', 'r'); + } + + public function testCreatesUriForValue() + { + self::assertInstanceOf('GuzzleHttp\Psr7\Uri', Psr7\Utils::uriFor('/foo')); + self::assertInstanceOf( + 'GuzzleHttp\Psr7\Uri', + Psr7\Utils::uriFor(new Psr7\Uri('/foo')) + ); + } + + public function testValidatesUri() + { + $this->expectExceptionGuzzle('InvalidArgumentException'); + + Psr7\Utils::uriFor([]); + } + + public function testKeepsPositionOfResource() + { + $h = fopen(__FILE__, 'r'); + fseek($h, 10); + $stream = Psr7\Utils::streamFor($h); + self::assertSame(10, $stream->tell()); + $stream->close(); + } + + public function testCreatesWithFactory() + { + $stream = Psr7\Utils::streamFor('foo'); + self::assertInstanceOf('GuzzleHttp\Psr7\Stream', $stream); + self::assertSame('foo', $stream->getContents()); + $stream->close(); + } + + public function testFactoryCreatesFromEmptyString() + { + $s = Psr7\Utils::streamFor(); + self::assertInstanceOf('GuzzleHttp\Psr7\Stream', $s); + } + + public function testFactoryCreatesFromNull() + { + $s = Psr7\Utils::streamFor(null); + self::assertInstanceOf('GuzzleHttp\Psr7\Stream', $s); + } + + public function testFactoryCreatesFromResource() + { + $r = fopen(__FILE__, 'r'); + $s = Psr7\Utils::streamFor($r); + self::assertInstanceOf('GuzzleHttp\Psr7\Stream', $s); + self::assertSame(file_get_contents(__FILE__), (string)$s); + } + + public function testFactoryCreatesFromObjectWithToString() + { + $r = new HasToString(); + $s = Psr7\Utils::streamFor($r); + self::assertInstanceOf('GuzzleHttp\Psr7\Stream', $s); + self::assertSame('foo', (string)$s); + } + + public function testCreatePassesThrough() + { + $s = Psr7\Utils::streamFor('foo'); + self::assertSame($s, Psr7\Utils::streamFor($s)); + } + + public function testThrowsExceptionForUnknown() + { + $this->expectExceptionGuzzle('InvalidArgumentException'); + + Psr7\Utils::streamFor(new \stdClass()); + } + + public function testReturnsCustomMetadata() + { + $s = Psr7\Utils::streamFor('foo', ['metadata' => ['hwm' => 3]]); + self::assertSame(3, $s->getMetadata('hwm')); + self::assertArrayHasKey('hwm', $s->getMetadata()); + } + + public function testCanSetSize() + { + $s = Psr7\Utils::streamFor('', ['size' => 10]); + self::assertSame(10, $s->getSize()); + } + + public function testCanCreateIteratorBasedStream() + { + $a = new \ArrayIterator(['foo', 'bar', '123']); + $p = Psr7\Utils::streamFor($a); + self::assertInstanceOf('GuzzleHttp\Psr7\PumpStream', $p); + self::assertSame('foo', $p->read(3)); + self::assertFalse($p->eof()); + self::assertSame('b', $p->read(1)); + self::assertSame('a', $p->read(1)); + self::assertSame('r12', $p->read(3)); + self::assertFalse($p->eof()); + self::assertSame('3', $p->getContents()); + self::assertTrue($p->eof()); + self::assertSame(9, $p->tell()); + } + + public function testConvertsRequestsToStrings() + { + $request = new Psr7\Request('PUT', 'http://foo.com/hi?123', [ + 'Baz' => 'bar', + 'Qux' => 'ipsum', + ], 'hello', '1.0'); + self::assertSame( + "PUT /hi?123 HTTP/1.0\r\nHost: foo.com\r\nBaz: bar\r\nQux: ipsum\r\n\r\nhello", + Psr7\Message::toString($request) + ); + } + + public function testConvertsResponsesToStrings() + { + $response = new Psr7\Response(200, [ + 'Baz' => 'bar', + 'Qux' => 'ipsum', + ], 'hello', '1.0', 'FOO'); + self::assertSame( + "HTTP/1.0 200 FOO\r\nBaz: bar\r\nQux: ipsum\r\n\r\nhello", + Psr7\Message::toString($response) + ); + } + + public function testCorrectlyRendersSetCookieHeadersToString() + { + $response = new Psr7\Response(200, [ + 'Set-Cookie' => ['bar','baz','qux'] + ], 'hello', '1.0', 'FOO'); + self::assertSame( + "HTTP/1.0 200 FOO\r\nSet-Cookie: bar\r\nSet-Cookie: baz\r\nSet-Cookie: qux\r\n\r\nhello", + Psr7\Message::toString($response) + ); + } + + public function testCanModifyRequestWithUri() + { + $r1 = new Psr7\Request('GET', 'http://foo.com'); + $r2 = Psr7\Utils::modifyRequest($r1, [ + 'uri' => new Psr7\Uri('http://www.foo.com'), + ]); + self::assertSame('http://www.foo.com', (string)$r2->getUri()); + self::assertSame('www.foo.com', (string)$r2->getHeaderLine('host')); + } + + public function testCanModifyRequestWithUriAndPort() + { + $r1 = new Psr7\Request('GET', 'http://foo.com:8000'); + $r2 = Psr7\Utils::modifyRequest($r1, [ + 'uri' => new Psr7\Uri('http://www.foo.com:8000'), + ]); + self::assertSame('http://www.foo.com:8000', (string)$r2->getUri()); + self::assertSame('www.foo.com:8000', (string)$r2->getHeaderLine('host')); + } + + public function testCanModifyRequestWithCaseInsensitiveHeader() + { + $r1 = new Psr7\Request('GET', 'http://foo.com', ['User-Agent' => 'foo']); + $r2 = Psr7\Utils::modifyRequest($r1, ['set_headers' => ['User-agent' => 'bar']]); + self::assertSame('bar', $r2->getHeaderLine('User-Agent')); + self::assertSame('bar', $r2->getHeaderLine('User-agent')); + } + + public function testReturnsAsIsWhenNoChanges() + { + $r1 = new Psr7\Request('GET', 'http://foo.com'); + $r2 = Psr7\Utils::modifyRequest($r1, []); + self::assertInstanceOf('GuzzleHttp\Psr7\Request', $r2); + + $r1 = new Psr7\ServerRequest('GET', 'http://foo.com'); + $r2 = Psr7\Utils::modifyRequest($r1, []); + self::assertInstanceOf('Psr\Http\Message\ServerRequestInterface', $r2); + } + + public function testReturnsUriAsIsWhenNoChanges() + { + $r1 = new Psr7\Request('GET', 'http://foo.com'); + $r2 = Psr7\Utils::modifyRequest($r1, ['set_headers' => ['foo' => 'bar']]); + self::assertNotSame($r1, $r2); + self::assertSame('bar', $r2->getHeaderLine('foo')); + } + + public function testRemovesHeadersFromMessage() + { + $r1 = new Psr7\Request('GET', 'http://foo.com', ['foo' => 'bar']); + $r2 = Psr7\Utils::modifyRequest($r1, ['remove_headers' => ['foo']]); + self::assertNotSame($r1, $r2); + self::assertFalse($r2->hasHeader('foo')); + } + + public function testAddsQueryToUri() + { + $r1 = new Psr7\Request('GET', 'http://foo.com'); + $r2 = Psr7\Utils::modifyRequest($r1, ['query' => 'foo=bar']); + self::assertNotSame($r1, $r2); + self::assertSame('foo=bar', $r2->getUri()->getQuery()); + } + + public function testModifyRequestKeepInstanceOfRequest() + { + $r1 = new Psr7\Request('GET', 'http://foo.com'); + $r2 = Psr7\Utils::modifyRequest($r1, ['remove_headers' => ['non-existent']]); + self::assertInstanceOf('GuzzleHttp\Psr7\Request', $r2); + + $r1 = new Psr7\ServerRequest('GET', 'http://foo.com'); + $r2 = Psr7\Utils::modifyRequest($r1, ['remove_headers' => ['non-existent']]); + self::assertInstanceOf('Psr\Http\Message\ServerRequestInterface', $r2); + } + + public function testModifyServerRequestWithUploadedFiles() + { + $request = new Psr7\ServerRequest('GET', 'http://example.com/bla'); + $file = new Psr7\UploadedFile('Test', 100, \UPLOAD_ERR_OK); + $request = $request->withUploadedFiles([$file]); + + /** @var Psr7\ServerRequest $modifiedRequest */ + $modifiedRequest = Psr7\Utils::modifyRequest($request, ['set_headers' => ['foo' => 'bar']]); + + self::assertCount(1, $modifiedRequest->getUploadedFiles()); + + $files = $modifiedRequest->getUploadedFiles(); + self::assertInstanceOf('GuzzleHttp\Psr7\UploadedFile', $files[0]); + } + + public function testModifyServerRequestWithCookies() + { + $request = (new Psr7\ServerRequest('GET', 'http://example.com/bla')) + ->withCookieParams(['name' => 'value']); + + /** @var Psr7\ServerRequest $modifiedRequest */ + $modifiedRequest = Psr7\Utils::modifyRequest($request, ['set_headers' => ['foo' => 'bar']]); + + self::assertSame(['name' => 'value'], $modifiedRequest->getCookieParams()); + } + + public function testModifyServerRequestParsedBody() + { + $request = (new Psr7\ServerRequest('GET', 'http://example.com/bla')) + ->withParsedBody(['name' => 'value']); + + /** @var Psr7\ServerRequest $modifiedRequest */ + $modifiedRequest = Psr7\Utils::modifyRequest($request, ['set_headers' => ['foo' => 'bar']]); + + self::assertSame(['name' => 'value'], $modifiedRequest->getParsedBody()); + } + + public function testModifyServerRequestQueryParams() + { + $request = (new Psr7\ServerRequest('GET', 'http://example.com/bla')) + ->withQueryParams(['name' => 'value']); + + /** @var Psr7\ServerRequest $modifiedRequest */ + $modifiedRequest = Psr7\Utils::modifyRequest($request, ['set_headers' => ['foo' => 'bar']]); + + self::assertSame(['name' => 'value'], $modifiedRequest->getQueryParams()); + } + + public function testModifyServerRequestRetainsAttributes() + { + $request = (new Psr7\ServerRequest('GET', 'http://example.com/bla')) + ->withAttribute('foo', 'bar'); + + /** @var Psr7\ServerRequest $modifiedRequest */ + $modifiedRequest = Psr7\Utils::modifyRequest($request, []); + + self::assertSame(['foo' => 'bar'], $modifiedRequest->getAttributes()); + } +} diff --git a/vendor/pear/pear-core-minimal/.gitattributes b/vendor/pear/pear-core-minimal/.gitattributes new file mode 100644 index 0000000000..24f26c22d2 --- /dev/null +++ b/vendor/pear/pear-core-minimal/.gitattributes @@ -0,0 +1,2 @@ +/.* export-ignore +/copy-from-pear-core.sh export-ignore diff --git a/vendor/pear/pear-core-minimal/.gitignore b/vendor/pear/pear-core-minimal/.gitignore new file mode 100644 index 0000000000..daa30a3f75 --- /dev/null +++ b/vendor/pear/pear-core-minimal/.gitignore @@ -0,0 +1 @@ +README.html diff --git a/vendor/pear/pear-core-minimal/copy-from-pear-core.sh b/vendor/pear/pear-core-minimal/copy-from-pear-core.sh new file mode 100755 index 0000000000..d68655ba21 --- /dev/null +++ b/vendor/pear/pear-core-minimal/copy-from-pear-core.sh @@ -0,0 +1,5 @@ +#!/bin/sh +cp ../pear-core/OS/Guess.php src/OS/Guess.php +cp ../pear-core/PEAR.php src/PEAR.php +cp ../pear-core/PEAR/ErrorStack.php src/PEAR/ErrorStack.php +cp ../pear-core/System.php src/System.php diff --git a/vendor/pear/pear-core-minimal/src/OS/Guess.php b/vendor/pear/pear-core-minimal/src/OS/Guess.php index 88cd659102..0e37a0956a 100644 --- a/vendor/pear/pear-core-minimal/src/OS/Guess.php +++ b/vendor/pear/pear-core-minimal/src/OS/Guess.php @@ -245,7 +245,7 @@ function _readGlibCVersionFromFeaturesHeaderFile() return array(); } if (!@file_exists('/usr/bin/cpp') || !@is_executable('/usr/bin/cpp')) { - return $this-_parseFeaturesHeaderFile($features_header_file); + return $this->_parseFeaturesHeaderFile($features_header_file); } // no cpp return $this->_fromGlibCTest(); diff --git a/vendor/pear/pear-core-minimal/src/PEAR.php b/vendor/pear/pear-core-minimal/src/PEAR.php index fee6638f44..27b1b4b299 100644 --- a/vendor/pear/pear-core-minimal/src/PEAR.php +++ b/vendor/pear/pear-core-minimal/src/PEAR.php @@ -219,7 +219,7 @@ public function __call($method, $arguments) ); } return call_user_func_array( - array(get_class(), '_' . $method), + array(self::class, '_' . $method), array_merge(array($this), $arguments) ); } @@ -232,7 +232,7 @@ public static function __callStatic($method, $arguments) ); } return call_user_func_array( - array(get_class(), '_' . $method), + array(self::class, '_' . $method), array_merge(array(null), $arguments) ); } @@ -859,6 +859,7 @@ class PEAR_Error var $message = ''; var $userinfo = ''; var $backtrace = null; + var $callback = null; /** * PEAR_Error constructor diff --git a/vendor/psr/http-factory/.gitattributes b/vendor/psr/http-factory/.gitattributes new file mode 100644 index 0000000000..a229004800 --- /dev/null +++ b/vendor/psr/http-factory/.gitattributes @@ -0,0 +1,3 @@ +/.gitattributes export-ignore +/.gitignore export-ignore +/.pullapprove.yml export-ignore diff --git a/vendor/psr/http-factory/README.md b/vendor/psr/http-factory/README.md index 41d362a625..bf8913b576 100644 --- a/vendor/psr/http-factory/README.md +++ b/vendor/psr/http-factory/README.md @@ -1,10 +1,12 @@ HTTP Factories ============== -This repository holds all interfaces related to [PSR-17 (HTTP Message Factories)][psr-17]. -Please refer to the specification for a description. +This repository holds all interfaces related to [PSR-17 (HTTP Factories)][psr-url]. -You can find implementations of the specification by looking for packages providing the -[psr/http-factory-implementation](https://packagist.org/providers/psr/http-factory-implementation) virtual package. +Note that this is not a HTTP Factory implementation of its own. It is merely interfaces that describe the components of a HTTP Factory. -[psr-17]: https://www.php-fig.org/psr/psr-17/ +The installable [package][package-url] and [implementations][implementation-url] are listed on Packagist. + +[psr-url]: https://www.php-fig.org/psr/psr-17/ +[package-url]: https://packagist.org/packages/psr/http-factory +[implementation-url]: https://packagist.org/providers/psr/http-factory-implementation diff --git a/vendor/psr/http-factory/composer.json b/vendor/psr/http-factory/composer.json index af62b290f9..d1bbddeea3 100644 --- a/vendor/psr/http-factory/composer.json +++ b/vendor/psr/http-factory/composer.json @@ -15,12 +15,12 @@ "authors": [ { "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" + "homepage": "https://www.php-fig.org/" } ], "require": { "php": ">=7.0.0", - "psr/http-message": "^1.0" + "psr/http-message": "^1.0 || ^2.0" }, "autoload": { "psr-4": { diff --git a/vendor/symfony/var-dumper/.gitattributes b/vendor/symfony/var-dumper/.gitattributes new file mode 100644 index 0000000000..84c7add058 --- /dev/null +++ b/vendor/symfony/var-dumper/.gitattributes @@ -0,0 +1,4 @@ +/Tests export-ignore +/phpunit.xml.dist export-ignore +/.gitattributes export-ignore +/.gitignore export-ignore diff --git a/vendor/symfony/var-dumper/.gitignore b/vendor/symfony/var-dumper/.gitignore new file mode 100644 index 0000000000..5414c2c655 --- /dev/null +++ b/vendor/symfony/var-dumper/.gitignore @@ -0,0 +1,3 @@ +composer.lock +phpunit.xml +vendor/ diff --git a/vendor/symfony/var-dumper/Dumper/CliDumper.php b/vendor/symfony/var-dumper/Dumper/CliDumper.php index 94dc8ee973..b5d9ac3366 100644 --- a/vendor/symfony/var-dumper/Dumper/CliDumper.php +++ b/vendor/symfony/var-dumper/Dumper/CliDumper.php @@ -198,6 +198,9 @@ public function dumpString(Cursor $cursor, string $str, bool $bin, int $cut) } if ('' === $str) { $this->line .= '""'; + if ($cut) { + $this->line .= '…'.$cut; + } $this->endValue($cursor); } else { $attr += [ @@ -445,7 +448,8 @@ protected function style(string $style, string $value, array $attr = []) if (null === $this->handlesHrefGracefully) { $this->handlesHrefGracefully = 'JetBrains-JediTerm' !== getenv('TERMINAL_EMULATOR') - && (!getenv('KONSOLE_VERSION') || (int) getenv('KONSOLE_VERSION') > 201100); + && (!getenv('KONSOLE_VERSION') || (int) getenv('KONSOLE_VERSION') > 201100) + && !isset($_SERVER['IDEA_INITIAL_DIRECTORY']); } if (isset($attr['ellipsis'], $attr['ellipsis-type'])) { diff --git a/vendor/symfony/var-dumper/Tests/Caster/CasterTest.php b/vendor/symfony/var-dumper/Tests/Caster/CasterTest.php new file mode 100644 index 0000000000..55eb4040f6 --- /dev/null +++ b/vendor/symfony/var-dumper/Tests/Caster/CasterTest.php @@ -0,0 +1,188 @@ + + * + * 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\Test\VarDumperTestTrait; + +/** + * @author Nicolas Grekas + */ +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 DateCasterTest extends TestCase +{ + use VarDumperTestTrait; + + /** + * @dataProvider provideDateTimes + */ + public function testDumpDateTime($time, $timezone, $xDate, $xTimestamp) + { + $date = new \DateTime($time, new \DateTimeZone($timezone)); + + $xDump = <<assertDumpEquals($xDump, $date); + } + + /** + * @dataProvider provideDateTimes + */ + public function testCastDateTime($time, $timezone, $xDate, $xTimestamp, $xInfos) + { + $stub = new Stub(); + $date = new \DateTime($time, new \DateTimeZone($timezone)); + $cast = DateCaster::castDateTime($date, Caster::castObject($date, \DateTime::class), $stub, false, 0); + + $xDump = << $xDate +] +EODUMP; + + $this->assertDumpEquals($xDump, $cast); + + $xDump = <<assertDumpMatchesFormat($xDump, $cast["\0~\0date"]); + } + + public static function provideDateTimes() + { + return [ + ['2017-04-30 00:00:00.000000', 'Europe/Zurich', '2017-04-30 00:00:00.0 Europe/Zurich (+02:00)', 1493503200, 'Sunday, April 30, 2017%Afrom now%ADST On'], + ['2017-12-31 00:00:00.000000', 'Europe/Zurich', '2017-12-31 00:00:00.0 Europe/Zurich (+01:00)', 1514674800, 'Sunday, December 31, 2017%Afrom now%ADST Off'], + ['2017-04-30 00:00:00.000000', '+02:00', '2017-04-30 00:00:00.0 +02:00', 1493503200, 'Sunday, April 30, 2017%Afrom now'], + + ['2017-04-30 00:00:00.100000', '+00:00', '2017-04-30 00:00:00.100 +00:00', 1493510400, 'Sunday, April 30, 2017%Afrom now'], + ['2017-04-30 00:00:00.120000', '+00:00', '2017-04-30 00:00:00.120 +00:00', 1493510400, 'Sunday, April 30, 2017%Afrom now'], + ['2017-04-30 00:00:00.123000', '+00:00', '2017-04-30 00:00:00.123 +00:00', 1493510400, 'Sunday, April 30, 2017%Afrom now'], + ['2017-04-30 00:00:00.123400', '+00:00', '2017-04-30 00:00:00.123400 +00:00', 1493510400, 'Sunday, April 30, 2017%Afrom now'], + ['2017-04-30 00:00:00.123450', '+00:00', '2017-04-30 00:00:00.123450 +00:00', 1493510400, 'Sunday, April 30, 2017%Afrom now'], + ['2017-04-30 00:00:00.123456', '+00:00', '2017-04-30 00:00:00.123456 +00:00', 1493510400, 'Sunday, April 30, 2017%Afrom now'], + ]; + } + + public function testCastDateTimeWithAdditionalChildProperty() + { + $stub = new Stub(); + $date = new DateTimeChild('2020-02-13 00:00:00.123456', new \DateTimeZone('Europe/Paris')); + $objectCast = Caster::castObject($date, DateTimeChild::class); + $dateCast = DateCaster::castDateTime($date, $objectCast, $stub, false, 0); + + $xDate = '2020-02-13 00:00:00.123456 Europe/Paris (+01:00)'; + $xInfo = 'Thursday, February 13, 2020%Afrom now'; + $xDump = << "foo" + "\\x00~\\x00date" => $xDate +] +EODUMP; + + $this->assertDumpEquals($xDump, $dateCast); + + $xDump = <<assertDumpMatchesFormat($xDump, $dateCast["\0~\0date"]); + } + + /** + * @dataProvider provideIntervals + */ + public function testDumpInterval($intervalSpec, $ms, $invert, $expected) + { + $interval = $this->createInterval($intervalSpec, $ms, $invert); + + $xDump = <<assertDumpMatchesFormat($xDump, $interval); + } + + /** + * @dataProvider provideIntervals + */ + public function testDumpIntervalExcludingVerbosity($intervalSpec, $ms, $invert, $expected) + { + $interval = $this->createInterval($intervalSpec, $ms, $invert); + + $xDump = <<assertDumpEquals($xDump, $interval, Caster::EXCLUDE_VERBOSE); + } + + /** + * @dataProvider provideIntervals + */ + public function testCastInterval($intervalSpec, $ms, $invert, $xInterval, $xSeconds) + { + $interval = $this->createInterval($intervalSpec, $ms, $invert); + $stub = new Stub(); + + $cast = DateCaster::castInterval($interval, ['foo' => 'bar'], $stub, false, Caster::EXCLUDE_VERBOSE); + + $xDump = << $xInterval +] +EODUMP; + + $this->assertDumpEquals($xDump, $cast); + + if (null === $xSeconds) { + return; + } + + $xDump = <<assertDumpMatchesFormat($xDump, $cast["\0~\0interval"]); + } + + public static function provideIntervals() + { + return [ + ['PT0S', 0, 0, '0s', '0s'], + ['PT0S', 0.1, 0, '+ 00:00:00.100', '%is'], + ['PT1S', 0, 0, '+ 00:00:01.0', '%is'], + ['PT2M', 0, 0, '+ 00:02:00.0', '%is'], + ['PT3H', 0, 0, '+ 03:00:00.0', '%ss'], + ['P4D', 0, 0, '+ 4d', '%ss'], + ['P5M', 0, 0, '+ 5m', null], + ['P6Y', 0, 0, '+ 6y', null], + ['P1Y2M3DT4H5M6S', 0, 0, '+ 1y 2m 3d 04:05:06.0', null], + ['PT1M60S', 0, 0, '+ 00:02:00.0', null], + ['PT1H60M', 0, 0, '+ 02:00:00.0', null], + ['P1DT24H', 0, 0, '+ 2d', null], + ['P1M32D', 0, 0, '+ 1m 32d', null], + + ['PT0S', 0, 1, '0s', '0s'], + ['PT0S', 0.1, 1, '- 00:00:00.100', '%is'], + ['PT1S', 0, 1, '- 00:00:01.0', '%is'], + ['PT2M', 0, 1, '- 00:02:00.0', '%is'], + ['PT3H', 0, 1, '- 03:00:00.0', '%ss'], + ['P4D', 0, 1, '- 4d', '%ss'], + ['P5M', 0, 1, '- 5m', null], + ['P6Y', 0, 1, '- 6y', null], + ['P1Y2M3DT4H5M6S', 0, 1, '- 1y 2m 3d 04:05:06.0', null], + ['PT1M60S', 0, 1, '- 00:02:00.0', null], + ['PT1H60M', 0, 1, '- 02:00:00.0', null], + ['P1DT24H', 0, 1, '- 2d', null], + ['P1M32D', 0, 1, '- 1m 32d', null], + ]; + } + + /** + * @dataProvider provideTimeZones + */ + public function testDumpTimeZone($timezone, $expected) + { + $timezone = new \DateTimeZone($timezone); + + $xDump = <<assertDumpMatchesFormat($xDump, $timezone); + } + + /** + * @dataProvider provideTimeZones + */ + public function testDumpTimeZoneExcludingVerbosity($timezone, $expected) + { + $timezone = new \DateTimeZone($timezone); + + $xDump = <<assertDumpMatchesFormat($xDump, $timezone, Caster::EXCLUDE_VERBOSE); + } + + /** + * @dataProvider provideTimeZones + */ + public function testCastTimeZone($timezone, $xTimezone, $xRegion) + { + $timezone = new \DateTimeZone($timezone); + $stub = new Stub(); + + $cast = DateCaster::castTimeZone($timezone, ['foo' => 'bar'], $stub, false, Caster::EXCLUDE_VERBOSE); + + $xDump = << $xTimezone +] +EODUMP; + + $this->assertDumpMatchesFormat($xDump, $cast); + + $xDump = <<assertDumpMatchesFormat($xDump, $cast["\0~\0timezone"]); + } + + public static function provideTimeZones() + { + $xRegion = \extension_loaded('intl') ? '%s' : ''; + + return [ + // type 1 (UTC offset) + ['-12:00', '-12:00', ''], + ['+00:00', '+00:00', ''], + ['+14:00', '+14:00', ''], + + // type 2 (timezone abbreviation) + ['GMT', '+00:00', ''], + ['a', '+01:00', ''], + ['b', '+02:00', ''], + ['z', '+00:00', ''], + + // type 3 (timezone identifier) + ['Africa/Tunis', 'Africa/Tunis (%s:00)', $xRegion], + ['America/Panama', 'America/Panama (%s:00)', $xRegion], + ['Asia/Jerusalem', 'Asia/Jerusalem (%s:00)', $xRegion], + ['Atlantic/Canary', 'Atlantic/Canary (%s:00)', $xRegion], + ['Australia/Perth', 'Australia/Perth (%s:00)', $xRegion], + ['Europe/Zurich', 'Europe/Zurich (%s:00)', $xRegion], + ['Pacific/Tahiti', 'Pacific/Tahiti (%s:00)', $xRegion], + ]; + } + + /** + * @dataProvider providePeriods + */ + public function testDumpPeriod($start, $interval, $end, $options, $expected) + { + $p = new \DatePeriod(new \DateTime($start), new \DateInterval($interval), \is_int($end) ? $end : new \DateTime($end), $options); + + $xDump = <<assertDumpMatchesFormat($xDump, $p); + } + + /** + * @dataProvider providePeriods + */ + public function testCastPeriod($start, $interval, $end, $options, $xPeriod, $xDates) + { + $p = new \DatePeriod(new \DateTime($start, new \DateTimeZone('UTC')), new \DateInterval($interval), \is_int($end) ? $end : new \DateTime($end, new \DateTimeZone('UTC')), $options); + $stub = new Stub(); + + $cast = DateCaster::castPeriod($p, [], $stub, false, 0); + + $xDump = << $xPeriod +] +EODUMP; + + $this->assertDumpEquals($xDump, $cast); + + $xDump = <<assertDumpMatchesFormat($xDump, $cast["\0~\0period"]); + } + + public static function providePeriods() + { + $periods = [ + ['2017-01-01', 'P1D', '2017-01-03', 0, 'every + 1d, from [2017-01-01 00:00:00.0 to 2017-01-03 00:00:00.0[', '1) 2017-01-01%a2) 2017-01-02'], + ['2017-01-01', 'P1D', 1, 0, 'every + 1d, from [2017-01-01 00:00:00.0 recurring 2 time/s', '1) 2017-01-01%a2) 2017-01-02'], + + ['2017-01-01', 'P1D', '2017-01-04', 0, 'every + 1d, from [2017-01-01 00:00:00.0 to 2017-01-04 00:00:00.0[', '1) 2017-01-01%a2) 2017-01-02%a3) 2017-01-03'], + ['2017-01-01', 'P1D', 2, 0, 'every + 1d, from [2017-01-01 00:00:00.0 recurring 3 time/s', '1) 2017-01-01%a2) 2017-01-02%a3) 2017-01-03'], + + ['2017-01-01', 'P1D', '2017-01-05', 0, 'every + 1d, from [2017-01-01 00:00:00.0 to 2017-01-05 00:00:00.0[', '1) 2017-01-01%a2) 2017-01-02%a1 more'], + ['2017-01-01', 'P1D', 3, 0, 'every + 1d, from [2017-01-01 00:00:00.0 recurring 4 time/s', '1) 2017-01-01%a2) 2017-01-02%a3) 2017-01-03%a1 more'], + + ['2017-01-01', 'P1D', '2017-01-21', 0, 'every + 1d, from [2017-01-01 00:00:00.0 to 2017-01-21 00:00:00.0[', '1) 2017-01-01%a17 more'], + ['2017-01-01', 'P1D', 19, 0, 'every + 1d, from [2017-01-01 00:00:00.0 recurring 20 time/s', '1) 2017-01-01%a17 more'], + + ['2017-01-01 01:00:00', 'P1D', '2017-01-03 01:00:00', 0, 'every + 1d, from [2017-01-01 01:00:00.0 to 2017-01-03 01:00:00.0[', '1) 2017-01-01 01:00:00.0%a2) 2017-01-02 01:00:00.0'], + ['2017-01-01 01:00:00', 'P1D', 1, 0, 'every + 1d, from [2017-01-01 01:00:00.0 recurring 2 time/s', '1) 2017-01-01 01:00:00.0%a2) 2017-01-02 01:00:00.0'], + + ['2017-01-01', 'P1DT1H', '2017-01-03', 0, 'every + 1d 01:00:00.0, from [2017-01-01 00:00:00.0 to 2017-01-03 00:00:00.0[', '1) 2017-01-01 00:00:00.0%a2) 2017-01-02 01:00:00.0'], + ['2017-01-01', 'P1DT1H', 1, 0, 'every + 1d 01:00:00.0, from [2017-01-01 00:00:00.0 recurring 2 time/s', '1) 2017-01-01 00:00:00.0%a2) 2017-01-02 01:00:00.0'], + + ['2017-01-01', 'P1D', '2017-01-04', \DatePeriod::EXCLUDE_START_DATE, 'every + 1d, from ]2017-01-01 00:00:00.0 to 2017-01-04 00:00:00.0[', '1) 2017-01-02%a2) 2017-01-03'], + ['2017-01-01', 'P1D', 2, \DatePeriod::EXCLUDE_START_DATE, 'every + 1d, from ]2017-01-01 00:00:00.0 recurring 2 time/s', '1) 2017-01-02%a2) 2017-01-03'], + ]; + + return $periods; + } + + private function createInterval($intervalSpec, $ms, $invert) + { + $interval = new \DateInterval($intervalSpec); + $interval->f = $ms; + $interval->invert = $invert; + + return $interval; + } +} diff --git a/vendor/symfony/var-dumper/Tests/Caster/DoctrineCasterTest.php b/vendor/symfony/var-dumper/Tests/Caster/DoctrineCasterTest.php new file mode 100644 index 0000000000..85f6293b13 --- /dev/null +++ b/vendor/symfony/var-dumper/Tests/Caster/DoctrineCasterTest.php @@ -0,0 +1,45 @@ + + * + * 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 Doctrine\Common\Collections\ArrayCollection; +use Doctrine\ORM\EntityManagerInterface; +use Doctrine\ORM\Mapping\ClassMetadata; +use Doctrine\ORM\PersistentCollection; +use PHPUnit\Framework\TestCase; +use Symfony\Component\VarDumper\Test\VarDumperTestTrait; + +/** + * @requires function \Doctrine\Common\Collections\ArrayCollection::__construct + */ +class DoctrineCasterTest extends TestCase +{ + use VarDumperTestTrait; + + public function testCastPersistentCollection() + { + $classMetadata = new ClassMetadata(__CLASS__); + + $collection = new PersistentCollection($this->createMock(EntityManagerInterface::class), $classMetadata, new ArrayCollection(['test'])); + + $expected = <<assertDumpMatchesFormat($expected, $collection); + } +} diff --git a/vendor/symfony/var-dumper/Tests/Caster/ExceptionCasterTest.php b/vendor/symfony/var-dumper/Tests/Caster/ExceptionCasterTest.php new file mode 100644 index 0000000000..157dc99c90 --- /dev/null +++ b/vendor/symfony/var-dumper/Tests/Caster/ExceptionCasterTest.php @@ -0,0 +1,360 @@ + + * + * 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\ErrorHandler\Exception\SilencedErrorContext; +use Symfony\Component\VarDumper\Caster\Caster; +use Symfony\Component\VarDumper\Caster\ExceptionCaster; +use Symfony\Component\VarDumper\Caster\FrameStub; +use Symfony\Component\VarDumper\Caster\TraceStub; +use Symfony\Component\VarDumper\Cloner\VarCloner; +use Symfony\Component\VarDumper\Dumper\HtmlDumper; +use Symfony\Component\VarDumper\Test\VarDumperTestTrait; + +class ExceptionCasterTest extends TestCase +{ + use VarDumperTestTrait; + + private function getTestException($msg, &$ref = null) + { + return new \Exception(''.$msg); + } + + private function getTestError($msg): \Error + { + return new \Error(''.$msg); + } + + private function getTestErrorException($msg): \ErrorException + { + return new \ErrorException(''.$msg); + } + + private function getTestSilencedErrorContext(): SilencedErrorContext + { + return new SilencedErrorContext(\E_ERROR, __FILE__, __LINE__); + } + + protected function tearDown(): void + { + ExceptionCaster::$srcContext = 1; + ExceptionCaster::$traceArgs = true; + } + + public function testDefaultSettings() + { + $ref = ['foo']; + $e = $this->getTestException('foo', $ref); + + $expectedDump = <<<'EODUMP' +Exception { + #message: "foo" + #code: 0 + #file: "%sExceptionCasterTest.php" + #line: %d + trace: { + %s%eTests%eCaster%eExceptionCasterTest.php:%d { + Symfony\Component\VarDumper\Tests\Caster\ExceptionCasterTest->getTestException($msg, &$ref = null) + › { + › return new \Exception(''.$msg); + › } + } + %s%eTests%eCaster%eExceptionCasterTest.php:%d { …} +%A +EODUMP; + + $this->assertDumpMatchesFormat($expectedDump, $e); + $this->assertSame(['foo'], $ref); + } + + public function testDefaultSettingsOnError() + { + $e = $this->getTestError('foo'); + + $expectedDump = <<<'EODUMP' +Error { + #message: "foo" + #code: 0 + #file: "%sExceptionCasterTest.php" + #line: %d + trace: { + %s%eTests%eCaster%eExceptionCasterTest.php:%d { + Symfony\Component\VarDumper\Tests\Caster\ExceptionCasterTest->getTestError($msg): Error + › { + › return new \Error(''.$msg); + › } + } + %s%eTests%eCaster%eExceptionCasterTest.php:%d { …} +%A +EODUMP; + + $this->assertDumpMatchesFormat($expectedDump, $e); + } + + public function testDefaultSettingsOnErrorException() + { + $e = $this->getTestErrorException('foo'); + + $expectedDump = <<<'EODUMP' +ErrorException { + #message: "foo" + #code: 0 + #file: "%sExceptionCasterTest.php" + #line: %d + #severity: E_ERROR + trace: { + %s%eTests%eCaster%eExceptionCasterTest.php:%d { + Symfony\Component\VarDumper\Tests\Caster\ExceptionCasterTest->getTestErrorException($msg): ErrorException + › { + › return new \ErrorException(''.$msg); + › } + } + %s%eTests%eCaster%eExceptionCasterTest.php:%d { …} +%A +EODUMP; + + $this->assertDumpMatchesFormat($expectedDump, $e); + } + + /** + * @requires function \Symfony\Component\ErrorHandler\Exception\SilencedErrorContext::__construct + */ + public function testCastSilencedErrorContext() + { + $e = $this->getTestSilencedErrorContext(); + + $expectedDump = <<<'EODUMP' +Symfony\Component\ErrorHandler\Exception\SilencedErrorContext { + +count: 1 + -severity: E_ERROR + trace: { + %s%eTests%eCaster%eExceptionCasterTest.php:%d { + › { + › return new SilencedErrorContext(\E_ERROR, __FILE__, __LINE__); + › } + } + } +} +EODUMP; + + $this->assertDumpMatchesFormat($expectedDump, $e); + } + + public function testSeek() + { + $e = $this->getTestException(2); + + $expectedDump = <<<'EODUMP' +{ + %s%eTests%eCaster%eExceptionCasterTest.php:%d { + Symfony\Component\VarDumper\Tests\Caster\ExceptionCasterTest->getTestException($msg, &$ref = null) + › { + › return new \Exception(''.$msg); + › } + } + %s%eTests%eCaster%eExceptionCasterTest.php:%d { …} +%A +EODUMP; + + $this->assertStringMatchesFormat($expectedDump, $this->getDump($e, 'trace')); + } + + public function testNoArgs() + { + $e = $this->getTestException(1); + ExceptionCaster::$traceArgs = false; + + $expectedDump = <<<'EODUMP' +Exception { + #message: "1" + #code: 0 + #file: "%sExceptionCasterTest.php" + #line: %d + trace: { + %sExceptionCasterTest.php:%d { + Symfony\Component\VarDumper\Tests\Caster\ExceptionCasterTest->getTestException($msg, &$ref = null) + › { + › return new \Exception(''.$msg); + › } + } + %s%eTests%eCaster%eExceptionCasterTest.php:%d { …} +%A +EODUMP; + + $this->assertDumpMatchesFormat($expectedDump, $e); + } + + public function testNoSrcContext() + { + $e = $this->getTestException(1); + ExceptionCaster::$srcContext = -1; + + $expectedDump = <<<'EODUMP' +Exception { + #message: "1" + #code: 0 + #file: "%sExceptionCasterTest.php" + #line: %d + trace: { + %s%eTests%eCaster%eExceptionCasterTest.php:%d + %s%eTests%eCaster%eExceptionCasterTest.php:%d +%A +EODUMP; + + $this->assertDumpMatchesFormat($expectedDump, $e); + } + + public function testShouldReturnTraceForConcreteTwigWithError() + { + require_once \dirname(__DIR__).'/Fixtures/Twig.php'; + + $innerExc = (new \__TwigTemplate_VarDumperFixture_u75a09(null, __FILE__))->provideError(); + $nestingWrapper = new \stdClass(); + $nestingWrapper->trace = new TraceStub($innerExc->getTrace()); + + $expectedDump = <<<'EODUMP' +{ + +"trace": { + %sTwig.php:%d { + AbstractTwigTemplate->provideError() + › { + › return $this->createError(); + › } + } + %sExceptionCasterTest.php:%d { …} +%A +EODUMP; + + $this->assertDumpMatchesFormat($expectedDump, $nestingWrapper); + } + + public function testHtmlDump() + { + if (\ini_get('xdebug.file_link_format') || get_cfg_var('xdebug.file_link_format')) { + $this->markTestSkipped('A custom file_link_format is defined.'); + } + + $e = $this->getTestException(1); + ExceptionCaster::$srcContext = -1; + + $cloner = new VarCloner(); + $cloner->setMaxItems(1); + $dumper = new HtmlDumper(); + $dumper->setDumpHeader(''); + $dumper->setDumpBoundaries('', ''); + $dump = $dumper->dump($cloner->cloneVar($e)->withRefHandles(false), true); + + $expectedDump = <<<'EODUMP' +Exception { + #message: "1" + #code: 0 + #file: "%s%eVarDumper%eTests%eCaster%eExceptionCasterTest.php" + #line: %d + trace: { + %s%eVarDumper%eTests%eCaster%eExceptionCasterTest.php:%d + …%d + } +} + +EODUMP; + + $this->assertStringMatchesFormat($expectedDump, $dump); + } + + /** + * @requires function Twig\Template::getSourceContext + */ + public function testFrameWithTwig() + { + require_once \dirname(__DIR__).'/Fixtures/Twig.php'; + + $f = [ + new FrameStub([ + 'file' => \dirname(__DIR__).'/Fixtures/Twig.php', + 'line' => 33, + 'class' => '__TwigTemplate_VarDumperFixture_u75a09', + ]), + new FrameStub([ + 'file' => \dirname(__DIR__).'/Fixtures/Twig.php', + 'line' => 34, + 'class' => '__TwigTemplate_VarDumperFixture_u75a09', + 'object' => new \__TwigTemplate_VarDumperFixture_u75a09(null, __FILE__), + ]), + ]; + + $expectedDump = <<<'EODUMP' +array:2 [ + 0 => { + class: "__TwigTemplate_VarDumperFixture_u75a09" + src: { + %sTwig.php:1 { + ›%s + › foo bar + › twig source + } + } + } + 1 => { + class: "__TwigTemplate_VarDumperFixture_u75a09" + object: __TwigTemplate_VarDumperFixture_u75a09 { + %A + } + src: { + %sExceptionCasterTest.php:2 { + › foo bar + › twig source + ›%s + } + } + } +] +EODUMP; + + $this->assertDumpMatchesFormat($expectedDump, $f); + } + + public function testExcludeVerbosity() + { + $e = $this->getTestException('foo'); + + $expectedDump = <<<'EODUMP' +Exception { + #message: "foo" + #code: 0 + #file: "%sExceptionCasterTest.php" + #line: %d +} +EODUMP; + + $this->assertDumpMatchesFormat($expectedDump, $e, Caster::EXCLUDE_VERBOSE); + } + + public function testAnonymous() + { + $e = new \Exception(sprintf('Boo "%s" ba.', \get_class(new class('Foo') extends \Exception { + }))); + + $expectedDump = <<<'EODUMP' +Exception { + #message: "Boo "Exception@anonymous" ba." + #code: 0 + #file: "%sExceptionCasterTest.php" + #line: %d +} +EODUMP; + + $this->assertDumpMatchesFormat($expectedDump, $e, Caster::EXCLUDE_VERBOSE); + } +} diff --git a/vendor/symfony/var-dumper/Tests/Caster/FiberCasterTest.php b/vendor/symfony/var-dumper/Tests/Caster/FiberCasterTest.php new file mode 100644 index 0000000000..ff501db0b6 --- /dev/null +++ b/vendor/symfony/var-dumper/Tests/Caster/FiberCasterTest.php @@ -0,0 +1,85 @@ + + * + * 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\Test\VarDumperTestTrait; + +/** + * @requires PHP 8.1 + */ +class FiberCasterTest extends TestCase +{ + use VarDumperTestTrait; + + public function testCastFiberNotStarted() + { + $fiber = new \Fiber(static function () { + return true; + }); + + $expected = <<assertDumpEquals($expected, $fiber); + } + + public function testCastFiberTerminated() + { + $fiber = new \Fiber(static function () { + return true; + }); + $fiber->start(); + + $expected = <<assertDumpEquals($expected, $fiber); + } + + public function testCastFiberSuspended() + { + $fiber = new \Fiber(static function () { + \Fiber::suspend(); + }); + $fiber->start(); + + $expected = <<assertDumpEquals($expected, $fiber); + } + + public function testCastFiberRunning() + { + $fiber = new \Fiber(function () { + $expected = <<assertDumpEquals($expected, \Fiber::getCurrent()); + }); + + $fiber->start(); + } +} diff --git a/vendor/symfony/var-dumper/Tests/Caster/GmpCasterTest.php b/vendor/symfony/var-dumper/Tests/Caster/GmpCasterTest.php new file mode 100644 index 0000000000..eb758e8e2c --- /dev/null +++ b/vendor/symfony/var-dumper/Tests/Caster/GmpCasterTest.php @@ -0,0 +1,48 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Tests\Caster; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\VarDumper\Caster\GmpCaster; +use Symfony\Component\VarDumper\Cloner\Stub; +use Symfony\Component\VarDumper\Test\VarDumperTestTrait; + +class GmpCasterTest extends TestCase +{ + use VarDumperTestTrait; + + /** + * @requires extension gmp + */ + public function testCastGmp() + { + $gmpString = gmp_init('1234'); + $gmpOctal = gmp_init(010); + $gmp = gmp_init('01101'); + $gmpDump = << %s +] +EODUMP; + $this->assertDumpEquals(sprintf($gmpDump, $gmpString), GmpCaster::castGmp($gmpString, [], new Stub(), false, 0)); + $this->assertDumpEquals(sprintf($gmpDump, $gmpOctal), GmpCaster::castGmp($gmpOctal, [], new Stub(), false, 0)); + $this->assertDumpEquals(sprintf($gmpDump, $gmp), GmpCaster::castGmp($gmp, [], new Stub(), false, 0)); + + $dump = <<assertDumpEquals($dump, $gmp); + } +} diff --git a/vendor/symfony/var-dumper/Tests/Caster/IntlCasterTest.php b/vendor/symfony/var-dumper/Tests/Caster/IntlCasterTest.php new file mode 100644 index 0000000000..0bff5bf496 --- /dev/null +++ b/vendor/symfony/var-dumper/Tests/Caster/IntlCasterTest.php @@ -0,0 +1,297 @@ + + * + * 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\Test\VarDumperTestTrait; + +/** + * @requires extension intl + */ +class IntlCasterTest extends TestCase +{ + use VarDumperTestTrait; + + public function testMessageFormatter() + { + $var = new \MessageFormatter('en', 'Hello {name}'); + + $expected = <<assertDumpEquals($expected, $var); + } + + public function testCastNumberFormatter() + { + $var = new \NumberFormatter('en', \NumberFormatter::DECIMAL); + + $expectedLocale = $var->getLocale(); + $expectedPattern = $var->getPattern(); + + $expectedAttribute1 = $var->getAttribute(\NumberFormatter::PARSE_INT_ONLY); + $expectedAttribute2 = $var->getAttribute(\NumberFormatter::GROUPING_USED); + $expectedAttribute3 = $var->getAttribute(\NumberFormatter::DECIMAL_ALWAYS_SHOWN); + $expectedAttribute4 = $var->getAttribute(\NumberFormatter::MAX_INTEGER_DIGITS); + $expectedAttribute5 = $var->getAttribute(\NumberFormatter::MIN_INTEGER_DIGITS); + $expectedAttribute6 = $var->getAttribute(\NumberFormatter::INTEGER_DIGITS); + $expectedAttribute7 = $var->getAttribute(\NumberFormatter::MAX_FRACTION_DIGITS); + $expectedAttribute8 = $var->getAttribute(\NumberFormatter::MIN_FRACTION_DIGITS); + $expectedAttribute9 = $var->getAttribute(\NumberFormatter::FRACTION_DIGITS); + $expectedAttribute10 = $var->getAttribute(\NumberFormatter::MULTIPLIER); + $expectedAttribute11 = $var->getAttribute(\NumberFormatter::GROUPING_SIZE); + $expectedAttribute12 = $var->getAttribute(\NumberFormatter::ROUNDING_MODE); + $expectedAttribute13 = number_format($var->getAttribute(\NumberFormatter::ROUNDING_INCREMENT), 1); + $expectedAttribute14 = $this->getDump($var->getAttribute(\NumberFormatter::FORMAT_WIDTH)); + $expectedAttribute15 = $var->getAttribute(\NumberFormatter::PADDING_POSITION); + $expectedAttribute16 = $var->getAttribute(\NumberFormatter::SECONDARY_GROUPING_SIZE); + $expectedAttribute17 = $var->getAttribute(\NumberFormatter::SIGNIFICANT_DIGITS_USED); + $expectedAttribute18 = $this->getDump($var->getAttribute(\NumberFormatter::MIN_SIGNIFICANT_DIGITS)); + $expectedAttribute19 = $this->getDump($var->getAttribute(\NumberFormatter::MAX_SIGNIFICANT_DIGITS)); + $expectedAttribute20 = $var->getAttribute(\NumberFormatter::LENIENT_PARSE); + + $expectedTextAttribute1 = $var->getTextAttribute(\NumberFormatter::POSITIVE_PREFIX); + $expectedTextAttribute2 = $var->getTextAttribute(\NumberFormatter::POSITIVE_SUFFIX); + $expectedTextAttribute3 = $var->getTextAttribute(\NumberFormatter::NEGATIVE_PREFIX); + $expectedTextAttribute4 = $var->getTextAttribute(\NumberFormatter::NEGATIVE_SUFFIX); + $expectedTextAttribute5 = $var->getTextAttribute(\NumberFormatter::PADDING_CHARACTER); + $expectedTextAttribute6 = $var->getTextAttribute(\NumberFormatter::CURRENCY_CODE); + $expectedTextAttribute7 = $var->getTextAttribute(\NumberFormatter::DEFAULT_RULESET) ? 'true' : 'false'; + $expectedTextAttribute8 = $var->getTextAttribute(\NumberFormatter::PUBLIC_RULESETS) ? 'true' : 'false'; + + $expectedSymbol1 = $var->getSymbol(\NumberFormatter::DECIMAL_SEPARATOR_SYMBOL); + $expectedSymbol2 = $var->getSymbol(\NumberFormatter::GROUPING_SEPARATOR_SYMBOL); + $expectedSymbol3 = $var->getSymbol(\NumberFormatter::PATTERN_SEPARATOR_SYMBOL); + $expectedSymbol4 = $var->getSymbol(\NumberFormatter::PERCENT_SYMBOL); + $expectedSymbol5 = $var->getSymbol(\NumberFormatter::ZERO_DIGIT_SYMBOL); + $expectedSymbol6 = $var->getSymbol(\NumberFormatter::DIGIT_SYMBOL); + $expectedSymbol7 = $var->getSymbol(\NumberFormatter::MINUS_SIGN_SYMBOL); + $expectedSymbol8 = $var->getSymbol(\NumberFormatter::PLUS_SIGN_SYMBOL); + $expectedSymbol9 = $var->getSymbol(\NumberFormatter::CURRENCY_SYMBOL); + $expectedSymbol10 = $var->getSymbol(\NumberFormatter::INTL_CURRENCY_SYMBOL); + $expectedSymbol11 = $var->getSymbol(\NumberFormatter::MONETARY_SEPARATOR_SYMBOL); + $expectedSymbol12 = $var->getSymbol(\NumberFormatter::EXPONENTIAL_SYMBOL); + $expectedSymbol13 = $var->getSymbol(\NumberFormatter::PERMILL_SYMBOL); + $expectedSymbol14 = $var->getSymbol(\NumberFormatter::PAD_ESCAPE_SYMBOL); + $expectedSymbol15 = $var->getSymbol(\NumberFormatter::INFINITY_SYMBOL); + $expectedSymbol16 = $var->getSymbol(\NumberFormatter::NAN_SYMBOL); + $expectedSymbol17 = $var->getSymbol(\NumberFormatter::SIGNIFICANT_DIGIT_SYMBOL); + $expectedSymbol18 = $var->getSymbol(\NumberFormatter::MONETARY_GROUPING_SEPARATOR_SYMBOL); + + $expected = <<assertDumpEquals($expected, $var); + } + + public function testCastIntlTimeZoneWithDST() + { + $var = \IntlTimeZone::createTimeZone('America/Los_Angeles'); + + $expectedDisplayName = $var->getDisplayName(); + $expectedDSTSavings = $var->getDSTSavings(); + $expectedID = $var->getID(); + $expectedRawOffset = $var->getRawOffset(); + + $expected = <<assertDumpEquals($expected, $var); + } + + public function testCastIntlTimeZoneWithoutDST() + { + $var = \IntlTimeZone::createTimeZone('Asia/Bangkok'); + + $expectedDisplayName = $var->getDisplayName(); + $expectedID = $var->getID(); + $expectedRawOffset = $var->getRawOffset(); + + $expected = <<assertDumpEquals($expected, $var); + } + + public function testCastIntlCalendar() + { + $var = \IntlCalendar::createInstance('America/Los_Angeles', 'en'); + + $expectedType = $var->getType(); + $expectedFirstDayOfWeek = $var->getFirstDayOfWeek(); + $expectedMinimalDaysInFirstWeek = $var->getMinimalDaysInFirstWeek(); + $expectedRepeatedWallTimeOption = $var->getRepeatedWallTimeOption(); + $expectedSkippedWallTimeOption = $var->getSkippedWallTimeOption(); + $expectedTime = $var->getTime().'.0'; + $expectedInDaylightTime = $var->inDaylightTime() ? 'true' : 'false'; + $expectedIsLenient = $var->isLenient() ? 'true' : 'false'; + + $expectedTimeZone = $var->getTimeZone(); + $expectedTimeZoneDisplayName = $expectedTimeZone->getDisplayName(); + $expectedTimeZoneID = $expectedTimeZone->getID(); + $expectedTimeZoneRawOffset = $expectedTimeZone->getRawOffset(); + $expectedTimeZoneDSTSavings = $expectedTimeZone->getDSTSavings(); + + $expected = <<assertDumpEquals($expected, $var); + } + + public function testCastDateFormatter() + { + $var = new \IntlDateFormatter('en', \IntlDateFormatter::TRADITIONAL, \IntlDateFormatter::TRADITIONAL); + + $expectedLocale = $var->getLocale(); + $expectedPattern = $var->getPattern(); + $expectedCalendar = $var->getCalendar(); + $expectedTimeZoneId = $var->getTimeZoneId(); + $expectedTimeType = $var->getTimeType(); + $expectedDateType = $var->getDateType(); + + $expectedTimeZone = $var->getTimeZone(); + $expectedTimeZoneDisplayName = $expectedTimeZone->getDisplayName(); + $expectedTimeZoneID = $expectedTimeZone->getID(); + $expectedTimeZoneRawOffset = $expectedTimeZone->getRawOffset(); + $expectedTimeZoneDSTSavings = $expectedTimeZone->useDaylightTime() ? "\n dst_savings: ".$expectedTimeZone->getDSTSavings() : ''; + + $expectedCalendarObject = $var->getCalendarObject(); + $expectedCalendarObjectType = $expectedCalendarObject->getType(); + $expectedCalendarObjectFirstDayOfWeek = $expectedCalendarObject->getFirstDayOfWeek(); + $expectedCalendarObjectMinimalDaysInFirstWeek = $expectedCalendarObject->getMinimalDaysInFirstWeek(); + $expectedCalendarObjectRepeatedWallTimeOption = $expectedCalendarObject->getRepeatedWallTimeOption(); + $expectedCalendarObjectSkippedWallTimeOption = $expectedCalendarObject->getSkippedWallTimeOption(); + $expectedCalendarObjectTime = $expectedCalendarObject->getTime().'.0'; + $expectedCalendarObjectInDaylightTime = $expectedCalendarObject->inDaylightTime() ? 'true' : 'false'; + $expectedCalendarObjectIsLenient = $expectedCalendarObject->isLenient() ? 'true' : 'false'; + + $expectedCalendarObjectTimeZone = $expectedCalendarObject->getTimeZone(); + $expectedCalendarObjectTimeZoneDisplayName = $expectedCalendarObjectTimeZone->getDisplayName(); + $expectedCalendarObjectTimeZoneID = $expectedCalendarObjectTimeZone->getID(); + $expectedCalendarObjectTimeZoneRawOffset = $expectedCalendarObjectTimeZone->getRawOffset(); + $expectedCalendarObjectTimeZoneDSTSavings = $expectedTimeZone->useDaylightTime() ? "\n dst_savings: ".$expectedCalendarObjectTimeZone->getDSTSavings() : ''; + + $expected = <<assertDumpEquals($expected, $var); + } +} diff --git a/vendor/symfony/var-dumper/Tests/Caster/MemcachedCasterTest.php b/vendor/symfony/var-dumper/Tests/Caster/MemcachedCasterTest.php new file mode 100644 index 0000000000..b252675c7a --- /dev/null +++ b/vendor/symfony/var-dumper/Tests/Caster/MemcachedCasterTest.php @@ -0,0 +1,93 @@ + + * + * 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\Test\VarDumperTestTrait; + +/** + * @author Jan Schädlich + */ +class MemcachedCasterTest extends TestCase +{ + use VarDumperTestTrait; + + public function testCastMemcachedWithDefaultOptions() + { + if (!class_exists(\Memcached::class)) { + $this->markTestSkipped('Memcached not available'); + } + + $var = new \Memcached(); + $var->addServer('127.0.0.1', 11211); + $var->addServer('127.0.0.2', 11212); + + $expected = << array:3 [ + "host" => "127.0.0.1" + "port" => 11211 + "type" => "TCP" + ] + 1 => array:3 [ + "host" => "127.0.0.2" + "port" => 11212 + "type" => "TCP" + ] + ] + options: {} +} +EOTXT; + $this->assertDumpEquals($expected, $var); + } + + public function testCastMemcachedWithCustomOptions() + { + if (!class_exists(\Memcached::class)) { + $this->markTestSkipped('Memcached not available'); + } + + $var = new \Memcached(); + $var->addServer('127.0.0.1', 11211); + $var->addServer('127.0.0.2', 11212); + + // set a subset of non default options to test boolean, string and integer output + $var->setOption(\Memcached::OPT_COMPRESSION, false); + $var->setOption(\Memcached::OPT_PREFIX_KEY, 'pre'); + $var->setOption(\Memcached::OPT_DISTRIBUTION, \Memcached::DISTRIBUTION_CONSISTENT); + + $expected = <<<'EOTXT' +Memcached { + servers: array:2 [ + 0 => array:3 [ + "host" => "127.0.0.1" + "port" => 11211 + "type" => "TCP" + ] + 1 => array:3 [ + "host" => "127.0.0.2" + "port" => 11212 + "type" => "TCP" + ] + ] + options: { + OPT_COMPRESSION: false + OPT_PREFIX_KEY: "pre" + OPT_DISTRIBUTION: 1 + } +} +EOTXT; + + $this->assertDumpEquals($expected, $var); + } +} diff --git a/vendor/symfony/var-dumper/Tests/Caster/MysqliCasterTest.php b/vendor/symfony/var-dumper/Tests/Caster/MysqliCasterTest.php new file mode 100644 index 0000000000..983f541a3f --- /dev/null +++ b/vendor/symfony/var-dumper/Tests/Caster/MysqliCasterTest.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Tests\Caster; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\VarDumper\Test\VarDumperTestTrait; + +/** + * @requires extension mysqli + * + * @group integration + */ +class MysqliCasterTest extends TestCase +{ + use VarDumperTestTrait; + + public function testNotConnected() + { + $driver = new \mysqli_driver(); + $driver->report_mode = 3; + + $xCast = <<assertDumpMatchesFormat($xCast, $driver); + } +} diff --git a/vendor/symfony/var-dumper/Tests/Caster/PdoCasterTest.php b/vendor/symfony/var-dumper/Tests/Caster/PdoCasterTest.php new file mode 100644 index 0000000000..564c8a0166 --- /dev/null +++ b/vendor/symfony/var-dumper/Tests/Caster/PdoCasterTest.php @@ -0,0 +1,67 @@ + + * + * 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\ConstStub; +use Symfony\Component\VarDumper\Caster\EnumStub; +use Symfony\Component\VarDumper\Caster\PdoCaster; +use Symfony\Component\VarDumper\Cloner\Stub; +use Symfony\Component\VarDumper\Test\VarDumperTestTrait; + +/** + * @author Nicolas Grekas + */ +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 = <<assertDumpMatchesFormat($expectedDump, $conf); + } + + public function testDumpProducer() + { + if (!$this->hasBroker) { + $this->markTestSkipped('Test requires an active broker'); + } + + $producer = new Producer(new Conf()); + $producer->addBrokers($this->broker); + + $expectedDump = <<broker/1001" + brokers: RdKafka\Metadata\Collection { + +0: RdKafka\Metadata\Broker { + id: 1001 + host: "%s" + port: %d + } + } + topics: RdKafka\Metadata\Collection { + +0: RdKafka\Metadata\Topic { + name: "%s" + partitions: RdKafka\Metadata\Collection { + +0: RdKafka\Metadata\Partition { + id: 0 + err: 0 + leader: 1001 + }%A + } + }%A + } +} +EODUMP; + + $this->assertDumpMatchesFormat($expectedDump, $producer); + } + + public function testDumpTopicConf() + { + $topicConf = new TopicConf(); + $topicConf->set('auto.offset.reset', 'smallest'); + + $expectedDump = <<assertDumpMatchesFormat($expectedDump, $topicConf); + } + + public function testDumpKafkaConsumer() + { + if (!$this->hasBroker) { + $this->markTestSkipped('Test requires an active broker'); + } + + $conf = new Conf(); + $conf->set('metadata.broker.list', $this->broker); + $conf->set('group.id', self::GROUP_ID); + + $consumer = new KafkaConsumer($conf); + $consumer->subscribe([self::TOPIC]); + + $expectedDump = << "test-topic" + ] + assignment: [] + orig_broker_id: %i + orig_broker_name: "$this->broker/%s" + brokers: RdKafka\Metadata\Collection { + +0: RdKafka\Metadata\Broker { + id: 1001 + host: "%s" + port: %d + } + } + topics: RdKafka\Metadata\Collection { + +0: RdKafka\Metadata\Topic { + name: "%s" + partitions: RdKafka\Metadata\Collection { + +0: RdKafka\Metadata\Partition { + id: 0 + err: 0 + leader: 1001 + }%A + } + }%A + } +} +EODUMP; + + $this->assertDumpMatchesFormat($expectedDump, $consumer); + } + + public function testDumpProducerTopic() + { + $producer = new Producer(new Conf()); + $producer->addBrokers($this->broker); + + $topic = $producer->newTopic('test'); + $topic->produce(\RD_KAFKA_PARTITION_UA, 0, '{}'); + + $expectedDump = <<assertDumpMatchesFormat($expectedDump, $topic); + } + + public function testDumpMessage() + { + $conf = new Conf(); + $conf->set('metadata.broker.list', $this->broker); + $conf->set('group.id', self::GROUP_ID); + + $consumer = new KafkaConsumer($conf); + $consumer->subscribe([self::TOPIC]); + + // Force timeout + $message = $consumer->consume(0); + + $expectedDump = <<assertDumpMatchesFormat($expectedDump, $message); + } +} diff --git a/vendor/symfony/var-dumper/Tests/Caster/RedisCasterTest.php b/vendor/symfony/var-dumper/Tests/Caster/RedisCasterTest.php new file mode 100644 index 0000000000..566de12a5e --- /dev/null +++ b/vendor/symfony/var-dumper/Tests/Caster/RedisCasterTest.php @@ -0,0 +1,75 @@ + + * + * 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\Test\VarDumperTestTrait; + +/** + * @author Nicolas Grekas + * + * @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 = <<assertDumpMatchesFormat($xCast, $redis); + } +} diff --git a/vendor/symfony/var-dumper/Tests/Caster/ReflectionCasterTest.php b/vendor/symfony/var-dumper/Tests/Caster/ReflectionCasterTest.php new file mode 100644 index 0000000000..c0aca218be --- /dev/null +++ b/vendor/symfony/var-dumper/Tests/Caster/ReflectionCasterTest.php @@ -0,0 +1,732 @@ + + * + * 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\Test\VarDumperTestTrait; +use Symfony\Component\VarDumper\Tests\Fixtures\ExtendsReflectionTypeFixture; +use Symfony\Component\VarDumper\Tests\Fixtures\GeneratorDemo; +use Symfony\Component\VarDumper\Tests\Fixtures\LotsOfAttributes; +use Symfony\Component\VarDumper\Tests\Fixtures\NotLoadableClass; +use Symfony\Component\VarDumper\Tests\Fixtures\ReflectionIntersectionTypeFixture; +use Symfony\Component\VarDumper\Tests\Fixtures\ReflectionNamedTypeFixture; +use Symfony\Component\VarDumper\Tests\Fixtures\ReflectionUnionTypeFixture; +use Symfony\Component\VarDumper\Tests\Fixtures\ReflectionUnionTypeWithIntersectionFixture; + +/** + * @author Nicolas Grekas + */ +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( + << Symfony\Component\VarDumper\Tests\Caster\ReflectionCasterTest::testFromCallableClosureCaster() { + this: Symfony\Component\VarDumper\Tests\Caster\ReflectionCasterTest { …} + file: "%sReflectionCasterTest.php" + line: "%d to %d" + } + 1 => Symfony\Component\VarDumper\Tests\Caster\ReflectionCasterTest::stub(): void { + returnType: "void" + file: "%sReflectionCasterTest.php" + line: "%d to %d" + } +] +EOTXT + , $var + ); + } + + public function testClosureCasterExcludingVerbosity() + { + $var = function &($a = 5) {}; + + $this->assertDumpEquals('Closure&($a = 5) { …5}', $var, Caster::EXCLUDE_VERBOSE); + } + + public function testReflectionParameter() + { + $var = new \ReflectionParameter(reflectionParameterFixture::class, 0); + + $this->assertDumpMatchesFormat( + <<<'EOTXT' +ReflectionParameter { + +name: "arg1" + position: 0 + allowsNull: true + typeHint: "Symfony\Component\VarDumper\Tests\Fixtures\NotLoadableClass" +} +EOTXT + , $var + ); + } + + public function testReflectionParameterScalar() + { + $f = eval('return function (int $a) {};'); + $var = new \ReflectionParameter($f, 0); + + $this->assertDumpMatchesFormat( + <<<'EOTXT' +ReflectionParameter { + +name: "a" + position: 0 + typeHint: "int" +} +EOTXT + , $var + ); + } + + /** + * @requires PHP 8 + */ + public function testReflectionParameterMixed() + { + $f = eval('return function (mixed $a) {};'); + $var = new \ReflectionParameter($f, 0); + + $this->assertDumpMatchesFormat( + <<<'EOTXT' +ReflectionParameter { + +name: "a" + position: 0 + allowsNull: true + typeHint: "mixed" +} +EOTXT + , $var + ); + } + + /** + * @requires PHP 8 + */ + public function testReflectionParameterUnion() + { + $f = eval('return function (int|float $a) {};'); + $var = new \ReflectionParameter($f, 0); + + $this->assertDumpMatchesFormat( + <<<'EOTXT' +ReflectionParameter { + +name: "a" + position: 0 + typeHint: "int|float" +} +EOTXT + , $var + ); + } + + /** + * @requires PHP 8 + */ + public function testReflectionParameterNullableUnion() + { + $f = eval('return function (int|float|null $a) {};'); + $var = new \ReflectionParameter($f, 0); + + $this->assertDumpMatchesFormat( + <<<'EOTXT' +ReflectionParameter { + +name: "a" + position: 0 + allowsNull: true + typeHint: "int|float|null" +} +EOTXT + , $var + ); + } + + /** + * @requires PHP 8.1 + */ + public function testReflectionParameterIntersection() + { + $f = eval('return function (Traversable&Countable $a) {};'); + $var = new \ReflectionParameter($f, 0); + + $this->assertDumpMatchesFormat( + <<<'EOTXT' +ReflectionParameter { + +name: "a" + position: 0 + typeHint: "Traversable&Countable" +} +EOTXT + , $var + ); + } + + /** + * @requires PHP 7.4 + */ + public function testReflectionPropertyScalar() + { + $var = new \ReflectionProperty(ReflectionNamedTypeFixture::class, 'a'); + $this->assertDumpMatchesFormat( + <<<'EOTXT' +ReflectionProperty { + +name: "a" + +class: "Symfony\Component\VarDumper\Tests\Fixtures\ReflectionNamedTypeFixture" + modifiers: "public" +} +EOTXT + , $var + ); + } + + /** + * @requires PHP 7.4 + */ + public function testReflectionNamedType() + { + $var = (new \ReflectionProperty(ReflectionNamedTypeFixture::class, 'a'))->getType(); + $this->assertDumpMatchesFormat( + <<<'EOTXT' +ReflectionNamedType { + name: "int" + allowsNull: false + isBuiltin: true +} +EOTXT + , $var + ); + } + + /** + * @requires PHP 8 + */ + public function testReflectionUnionType() + { + $var = (new \ReflectionProperty(ReflectionUnionTypeFixture::class, 'a'))->getType(); + $this->assertDumpMatchesFormat( + <<<'EOTXT' +ReflectionUnionType { + allowsNull: false + types: array:2 [ + 0 => ReflectionNamedType { + name: "string" + allowsNull: false + isBuiltin: true + } + 1 => ReflectionNamedType { + name: "int" + allowsNull: false + isBuiltin: true + } + ] +} +EOTXT + , $var + ); + } + + /** + * @requires PHP 8.1 + */ + public function testReflectionIntersectionType() + { + $var = (new \ReflectionProperty(ReflectionIntersectionTypeFixture::class, 'a'))->getType(); + $this->assertDumpMatchesFormat( + <<<'EOTXT' +ReflectionIntersectionType { + allowsNull: false + types: array:2 [ + 0 => ReflectionNamedType { + name: "Traversable" + allowsNull: false + isBuiltin: false + } + 1 => ReflectionNamedType { + name: "Countable" + allowsNull: false + isBuiltin: false + } + ] +} +EOTXT + , $var + ); + } + + /** + * @requires PHP 8.2 + */ + public function testReflectionUnionTypeWithIntersection() + { + $var = (new \ReflectionProperty(ReflectionUnionTypeWithIntersectionFixture::class, 'a'))->getType(); + $this->assertDumpMatchesFormat( + <<<'EOTXT' +ReflectionUnionType { + allowsNull: true + types: array:2 [ + 0 => ReflectionIntersectionType { + allowsNull: false + types: array:2 [ + 0 => ReflectionNamedType { + name: "Traversable" + allowsNull: false + isBuiltin: false + } + 1 => ReflectionNamedType { + name: "Countable" + allowsNull: false + isBuiltin: false + } + ] + } + 1 => ReflectionNamedType { + name: "null" + allowsNull: true + isBuiltin: true + } + ] +} +EOTXT + , $var + ); + } + + /** + * @requires PHP 8 + */ + public function testExtendsReflectionType() + { + $var = new ExtendsReflectionTypeFixture(); + $this->assertDumpMatchesFormat( + <<<'EOTXT' +Symfony\Component\VarDumper\Tests\Fixtures\ExtendsReflectionTypeFixture { + allowsNull: false +} +EOTXT + , $var + ); + } + + /** + * @requires PHP < 8 + */ + public function testLegacyExtendsReflectionType() + { + $var = new ExtendsReflectionTypeFixture(); + $this->assertDumpMatchesFormat( + <<<'EOTXT' +Symfony\Component\VarDumper\Tests\Fixtures\ExtendsReflectionTypeFixture { + name: "fake" + allowsNull: false + isBuiltin: false +} +EOTXT + , $var + ); + } + + public function testReturnType() + { + $f = eval('return function ():int {};'); + $line = __LINE__ - 1; + + $this->assertDumpMatchesFormat( + <<assertDumpMatchesFormat( + <<assertDumpMatchesFormat( + <<assertDumpMatchesFormat( + <<markTestSkipped('xdebug is active'); + } + + $generator = new GeneratorDemo(); + $generator = $generator->baz(); + + $expectedDump = <<<'EODUMP' +Generator { + this: Symfony\Component\VarDumper\Tests\Fixtures\GeneratorDemo { …} + %s: { + %sGeneratorDemo.php:14 { + Symfony\Component\VarDumper\Tests\Fixtures\GeneratorDemo->baz() + › { + › yield from bar(); + › } + } +%A} + closed: false +} +EODUMP; + + $this->assertDumpMatchesFormat($expectedDump, $generator); + + foreach ($generator as $v) { + break; + } + + $expectedDump = <<<'EODUMP' +array:2 [ + 0 => ReflectionGenerator { + this: Symfony\Component\VarDumper\Tests\Fixtures\GeneratorDemo { …} + %s: { + %s%eTests%eFixtures%eGeneratorDemo.php:%d { + Symfony\Component\VarDumper\Tests\Fixtures\GeneratorDemo::foo() +%A › yield 1; +%A } + %s%eTests%eFixtures%eGeneratorDemo.php:20 { …} + %s%eTests%eFixtures%eGeneratorDemo.php:14 { …} +%A } + closed: false + } + 1 => Generator { + %s: { + %s%eTests%eFixtures%eGeneratorDemo.php:%d { + Symfony\Component\VarDumper\Tests\Fixtures\GeneratorDemo::foo() + › yield 1; + › } + › + } +%A } + closed: false + } +] +EODUMP; + + $r = new \ReflectionGenerator($generator); + $this->assertDumpMatchesFormat($expectedDump, [$r, $r->getExecutingGenerator()]); + + foreach ($generator as $v) { + } + + $expectedDump = <<<'EODUMP' +Generator { + closed: true +} +EODUMP; + $this->assertDumpMatchesFormat($expectedDump, $generator); + } + + /** + * @requires PHP 8.1 + */ + public function testNewInInitializer() + { + $f = eval('return function ($a = new stdClass()) {};'); + $line = __LINE__ - 1; + + $this->assertDumpMatchesFormat( + <<assertDumpMatchesFormat(<<< 'EOTXT' +ReflectionClass { + +name: "Symfony\Component\VarDumper\Tests\Fixtures\LotsOfAttributes" +%A attributes: array:1 [ + 0 => ReflectionAttribute { + name: "Symfony\Component\VarDumper\Tests\Fixtures\MyAttribute" + arguments: [] + } + ] +%A +} +EOTXT + , $var); + } + + /** + * @requires PHP 8 + */ + public function testReflectionMethodWithAttribute() + { + $var = new \ReflectionMethod(LotsOfAttributes::class, 'someMethod'); + + $this->assertDumpMatchesFormat(<<< 'EOTXT' +ReflectionMethod { + +name: "someMethod" + +class: "Symfony\Component\VarDumper\Tests\Fixtures\LotsOfAttributes" +%A attributes: array:1 [ + 0 => ReflectionAttribute { + name: "Symfony\Component\VarDumper\Tests\Fixtures\MyAttribute" + arguments: array:1 [ + 0 => "two" + ] + } + ] +%A +} +EOTXT + , $var); + } + + /** + * @requires PHP 8 + */ + public function testReflectionPropertyWithAttribute() + { + $var = new \ReflectionProperty(LotsOfAttributes::class, 'someProperty'); + + $this->assertDumpMatchesFormat(<<< 'EOTXT' +ReflectionProperty { + +name: "someProperty" + +class: "Symfony\Component\VarDumper\Tests\Fixtures\LotsOfAttributes" +%A attributes: array:1 [ + 0 => ReflectionAttribute { + name: "Symfony\Component\VarDumper\Tests\Fixtures\MyAttribute" + arguments: array:2 [ + 0 => "one" + "extra" => "hello" + ] + } + ] +} +EOTXT + , $var); + } + + /** + * @requires PHP 8 + */ + public function testReflectionClassConstantWithAttribute() + { + $var = new \ReflectionClassConstant(LotsOfAttributes::class, 'SOME_CONSTANT'); + + $this->assertDumpMatchesFormat(<<< 'EOTXT' +ReflectionClassConstant { + +name: "SOME_CONSTANT" + +class: "Symfony\Component\VarDumper\Tests\Fixtures\LotsOfAttributes" + modifiers: "public" + value: "some value" + attributes: array:2 [ + 0 => ReflectionAttribute { + name: "Symfony\Component\VarDumper\Tests\Fixtures\RepeatableAttribute" + arguments: array:1 [ + 0 => "one" + ] + } + 1 => ReflectionAttribute { + name: "Symfony\Component\VarDumper\Tests\Fixtures\RepeatableAttribute" + arguments: array:1 [ + 0 => "two" + ] + } + ] +} +EOTXT + , $var); + } + + /** + * @requires PHP 8 + */ + public function testReflectionParameterWithAttribute() + { + $var = new \ReflectionParameter([LotsOfAttributes::class, 'someMethod'], 'someParameter'); + + $this->assertDumpMatchesFormat(<<< 'EOTXT' +ReflectionParameter { + +name: "someParameter" + position: 0 + attributes: array:1 [ + 0 => ReflectionAttribute { + name: "Symfony\Component\VarDumper\Tests\Fixtures\MyAttribute" + arguments: array:1 [ + 0 => "three" + ] + } + ] +%A +} +EOTXT + , $var); + } + + public static function stub(): void + { + } +} + +function reflectionParameterFixture(NotLoadableClass $arg1 = null, $arg2) +{ +} diff --git a/vendor/symfony/var-dumper/Tests/Caster/SplCasterTest.php b/vendor/symfony/var-dumper/Tests/Caster/SplCasterTest.php new file mode 100644 index 0000000000..4093471f89 --- /dev/null +++ b/vendor/symfony/var-dumper/Tests/Caster/SplCasterTest.php @@ -0,0 +1,234 @@ + + * + * 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\Test\VarDumperTestTrait; + +/** + * @author Grégoire Pineau + */ +class SplCasterTest extends TestCase +{ + use VarDumperTestTrait; + + public static function getCastFileInfoTests() + { + return [ + [__FILE__, <<<'EOTXT' +SplFileInfo { +%Apath: "%sCaster" + filename: "SplCasterTest.php" + basename: "SplCasterTest.php" + pathname: "%sSplCasterTest.php" + extension: "php" + realPath: "%sSplCasterTest.php" + aTime: %s-%s-%d %d:%d:%d + mTime: %s-%s-%d %d:%d:%d + cTime: %s-%s-%d %d:%d:%d + inode: %i + size: %d + perms: 0%d + owner: %d + group: %d + type: "file" + writable: true + readable: true + executable: false + file: true + dir: false + link: false +%A} +EOTXT + ], + ['http://example.com/about', <<<'EOTXT' +SplFileInfo { +%Apath: "http://example.com" + filename: "about" + basename: "about" + pathname: "http://example.com/about" + extension: "" + realPath: false +%A} +EOTXT + ], + ]; + } + + /** @dataProvider getCastFileInfoTests */ + public function testCastFileInfo($file, $dump) + { + $this->assertDumpMatchesFormat($dump, new \SplFileInfo($file)); + } + + public function testCastFileObject() + { + $var = new \SplFileObject(__FILE__); + $var->setFlags(\SplFileObject::DROP_NEW_LINE | \SplFileObject::SKIP_EMPTY); + $dump = <<<'EOTXT' +SplFileObject { +%Apath: "%sCaster" + filename: "SplCasterTest.php" + basename: "SplCasterTest.php" + pathname: "%sSplCasterTest.php" + extension: "php" + realPath: "%sSplCasterTest.php" + aTime: %s-%s-%d %d:%d:%d + mTime: %s-%s-%d %d:%d:%d + cTime: %s-%s-%d %d:%d:%d + inode: %i + size: %d + perms: 0%d + owner: %d + group: %d + type: "file" + writable: true + readable: true + executable: false + file: true + dir: false + link: false +%AcsvControl: array:%d [ + 0 => "," + 1 => """ +%A] + flags: DROP_NEW_LINE|SKIP_EMPTY + maxLineLen: 0 + fstat: array:26 [ + "dev" => %d + "ino" => %i + "nlink" => %d + "rdev" => 0 + "blksize" => %i + "blocks" => %i + …20 + ] + eof: false + key: 0 +} +EOTXT; + $this->assertDumpMatchesFormat($dump, $var); + } + + /** + * @dataProvider provideCastSplDoublyLinkedList + */ + public function testCastSplDoublyLinkedList($modeValue, $modeDump) + { + $var = new \SplDoublyLinkedList(); + $var->setIteratorMode($modeValue); + $dump = <<assertDumpMatchesFormat($dump, $var); + } + + public static function provideCastSplDoublyLinkedList() + { + return [ + [\SplDoublyLinkedList::IT_MODE_FIFO, 'IT_MODE_FIFO | IT_MODE_KEEP'], + [\SplDoublyLinkedList::IT_MODE_LIFO, 'IT_MODE_LIFO | IT_MODE_KEEP'], + [\SplDoublyLinkedList::IT_MODE_FIFO | \SplDoublyLinkedList::IT_MODE_DELETE, 'IT_MODE_FIFO | IT_MODE_DELETE'], + [\SplDoublyLinkedList::IT_MODE_LIFO | \SplDoublyLinkedList::IT_MODE_DELETE, 'IT_MODE_LIFO | IT_MODE_DELETE'], + ]; + } + + public function testCastObjectStorageIsntModified() + { + $var = new \SplObjectStorage(); + $var->attach(new \stdClass()); + $var->rewind(); + $current = $var->current(); + + $this->assertDumpMatchesFormat('%A', $var); + $this->assertSame($current, $var->current()); + } + + public function testCastObjectStorageDumpsInfo() + { + $var = new \SplObjectStorage(); + $var->attach(new \stdClass(), new \DateTime()); + + $this->assertDumpMatchesFormat('%ADateTime%A', $var); + } + + public function testCastArrayObject() + { + $var = new + #[\AllowDynamicProperties] + class([123]) extends \ArrayObject {}; + $var->foo = 234; + + $expected = << 123 + ] + flag::STD_PROP_LIST: false + flag::ARRAY_AS_PROPS: false + iteratorClass: "ArrayIterator" +} +EOTXT; + if (\PHP_VERSION_ID < 70400) { + $expected = str_replace('-storage:', 'storage:', $expected); + } + $this->assertDumpEquals($expected, $var); + } + + public function testArrayIterator() + { + $var = new MyArrayIterator([234]); + + $expected = << 234 + ] + flag::STD_PROP_LIST: false + flag::ARRAY_AS_PROPS: false +} +EOTXT; + if (\PHP_VERSION_ID < 70400) { + $expected = str_replace('-storage:', 'storage:', $expected); + } + $this->assertDumpEquals($expected, $var); + } + + public function testBadSplFileInfo() + { + $var = new BadSplFileInfo(); + + $expected = <<assertDumpEquals($expected, $var); + } +} + +class MyArrayIterator extends \ArrayIterator +{ + private $foo = 123; +} + +class BadSplFileInfo extends \SplFileInfo +{ + public function __construct() + { + } +} diff --git a/vendor/symfony/var-dumper/Tests/Caster/StubCasterTest.php b/vendor/symfony/var-dumper/Tests/Caster/StubCasterTest.php new file mode 100644 index 0000000000..cd6876cdff --- /dev/null +++ b/vendor/symfony/var-dumper/Tests/Caster/StubCasterTest.php @@ -0,0 +1,213 @@ + + * + * 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\ArgsStub; +use Symfony\Component\VarDumper\Caster\ClassStub; +use Symfony\Component\VarDumper\Caster\LinkStub; +use Symfony\Component\VarDumper\Cloner\VarCloner; +use Symfony\Component\VarDumper\Dumper\HtmlDumper; +use Symfony\Component\VarDumper\Test\VarDumperTestTrait; +use Symfony\Component\VarDumper\Tests\Fixtures\FooInterface; + +class StubCasterTest extends TestCase +{ + use VarDumperTestTrait; + + public function testArgsStubWithDefaults($foo = 234, $bar = 456) + { + $args = [new ArgsStub([123], __FUNCTION__, __CLASS__)]; + + $expectedDump = <<<'EODUMP' +array:1 [ + 0 => { + $foo: 123 + } +] +EODUMP; + + $this->assertDumpMatchesFormat($expectedDump, $args); + } + + public function testArgsStubWithExtraArgs($foo = 234) + { + $args = [new ArgsStub([123, 456], __FUNCTION__, __CLASS__)]; + + $expectedDump = <<<'EODUMP' +array:1 [ + 0 => { + $foo: 123 + ...: { + 456 + } + } +] +EODUMP; + + $this->assertDumpMatchesFormat($expectedDump, $args); + } + + public function testArgsStubNoParamWithExtraArgs() + { + $args = [new ArgsStub([123], __FUNCTION__, __CLASS__)]; + + $expectedDump = <<<'EODUMP' +array:1 [ + 0 => { + 123 + } +] +EODUMP; + + $this->assertDumpMatchesFormat($expectedDump, $args); + } + + public function testArgsStubWithClosure() + { + $args = [new ArgsStub([123], '{closure}', null)]; + + $expectedDump = <<<'EODUMP' +array:1 [ + 0 => { + 123 + } +] +EODUMP; + + $this->assertDumpMatchesFormat($expectedDump, $args); + } + + public function testLinkStub() + { + $var = [new LinkStub(__CLASS__, 0, __FILE__)]; + + $cloner = new VarCloner(); + $dumper = new HtmlDumper(); + $dumper->setDumpHeader(''); + $dumper->setDumpBoundaries('', ''); + $dumper->setDisplayOptions(['fileLinkFormat' => '%f:%l']); + $dump = $dumper->dump($cloner->cloneVar($var), true); + + $expectedDump = <<<'EODUMP' +array:1 [ + 0 => "Symfony\Component\VarDumper\Tests\Caster\StubCasterTest" +] + +EODUMP; + + $this->assertStringMatchesFormat($expectedDump, $dump); + } + + public function testLinkStubWithNoFileLink() + { + $var = [new LinkStub('example.com', 0, 'http://example.com')]; + + $cloner = new VarCloner(); + $dumper = new HtmlDumper(); + $dumper->setDumpHeader(''); + $dumper->setDumpBoundaries('', ''); + $dumper->setDisplayOptions(['fileLinkFormat' => '%f:%l']); + $dump = $dumper->dump($cloner->cloneVar($var), true); + + $expectedDump = <<<'EODUMP' +array:1 [ + 0 => "example.com" +] + +EODUMP; + + $this->assertStringMatchesFormat($expectedDump, $dump); + } + + public function testClassStub() + { + $var = [new ClassStub('hello', [FooInterface::class, 'foo'])]; + + $cloner = new VarCloner(); + $dumper = new HtmlDumper(); + $dumper->setDumpHeader(''); + $dumper->setDumpBoundaries('', ''); + $dump = $dumper->dump($cloner->cloneVar($var), true, ['fileLinkFormat' => '%f:%l']); + + $expectedDump = <<<'EODUMP' +array:1 [ + 0 => "hello(?stdClass $a, stdClass $b = null)" +] + +EODUMP; + + $this->assertStringMatchesFormat($expectedDump, $dump); + } + + public function testClassStubWithNotExistingClass() + { + $var = [new ClassStub(NotExisting::class)]; + + $cloner = new VarCloner(); + $dumper = new HtmlDumper(); + $dumper->setDumpHeader(''); + $dumper->setDumpBoundaries('', ''); + $dump = $dumper->dump($cloner->cloneVar($var), true); + + $expectedDump = <<<'EODUMP' +array:1 [ + 0 => "Symfony\Component\VarDumper\Tests\Caster\NotExisting" +] + +EODUMP; + + $this->assertStringMatchesFormat($expectedDump, $dump); + } + + public function testClassStubWithNotExistingMethod() + { + $var = [new ClassStub('hello', [FooInterface::class, 'missing'])]; + + $cloner = new VarCloner(); + $dumper = new HtmlDumper(); + $dumper->setDumpHeader(''); + $dumper->setDumpBoundaries('', ''); + $dump = $dumper->dump($cloner->cloneVar($var), true, ['fileLinkFormat' => '%f:%l']); + + $expectedDump = <<<'EODUMP' +array:1 [ + 0 => "hello" +] + +EODUMP; + + $this->assertStringMatchesFormat($expectedDump, $dump); + } + + public function testClassStubWithAnonymousClass() + { + $var = [new ClassStub(\get_class(new class() extends \Exception { + }))]; + + $cloner = new VarCloner(); + $dumper = new HtmlDumper(); + $dumper->setDumpHeader(''); + $dumper->setDumpBoundaries('', ''); + $dump = $dumper->dump($cloner->cloneVar($var), true, ['fileLinkFormat' => '%f:%l']); + + $expectedDump = <<<'EODUMP' +array:1 [ + 0 => "Exception@anonymous" +] + +EODUMP; + + $this->assertStringMatchesFormat($expectedDump, $dump); + } +} diff --git a/vendor/symfony/var-dumper/Tests/Caster/SymfonyCasterTest.php b/vendor/symfony/var-dumper/Tests/Caster/SymfonyCasterTest.php new file mode 100644 index 0000000000..fff40dfb58 --- /dev/null +++ b/vendor/symfony/var-dumper/Tests/Caster/SymfonyCasterTest.php @@ -0,0 +1,83 @@ + + * + * 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\Uid\Ulid; +use Symfony\Component\Uid\UuidV4; +use Symfony\Component\Uid\UuidV6; +use Symfony\Component\VarDumper\Test\VarDumperTestTrait; + +final class SymfonyCasterTest extends TestCase +{ + use VarDumperTestTrait; + + public function testCastUuid() + { + $uuid = new UuidV4('83a9db35-3c8c-4040-b3c1-02eccc00b419'); + $expectedDump = <<assertDumpEquals($expectedDump, $uuid); + + $uuid = new UuidV6('1ebc50e9-8a23-6704-ad6f-59afd5cda7e5'); + if (method_exists($uuid, 'getDateTime')) { + $expectedDump = <<assertDumpEquals($expectedDump, $uuid); + } + + public function testCastUlid() + { + $ulid = new Ulid('01F7B252SZQGTSQGYSGACASAW6'); + if (method_exists($ulid, 'getDateTime')) { + $expectedDump = <<assertDumpEquals($expectedDump, $ulid); + } +} diff --git a/vendor/symfony/var-dumper/Tests/Caster/XmlReaderCasterTest.php b/vendor/symfony/var-dumper/Tests/Caster/XmlReaderCasterTest.php new file mode 100644 index 0000000000..78416f3094 --- /dev/null +++ b/vendor/symfony/var-dumper/Tests/Caster/XmlReaderCasterTest.php @@ -0,0 +1,262 @@ + + * + * 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\Test\VarDumperTestTrait; + +/** + * @author Baptiste Clavié + */ +class XmlReaderCasterTest extends TestCase +{ + use VarDumperTestTrait; + + /** @var \XmlReader */ + private $reader; + + protected function setUp(): void + { + $this->reader = new \XMLReader(); + $this->reader->open(__DIR__.'/../Fixtures/xml_reader.xml'); + } + + protected function tearDown(): void + { + $this->reader->close(); + } + + public function testParserProperty() + { + $this->reader->setParserProperty(\XMLReader::SUBST_ENTITIES, true); + + $expectedDump = <<<'EODUMP' +XMLReader { + +nodeType: NONE + parserProperties: { + SUBST_ENTITIES: true + …3 + } + …12 +} +EODUMP; + + $this->assertDumpMatchesFormat($expectedDump, $this->reader); + } + + /** + * @dataProvider provideNodes + */ + public function testNodes($seek, $expectedDump) + { + while ($seek--) { + $this->reader->read(); + } + $this->assertDumpMatchesFormat($expectedDump, $this->reader); + } + + public static function provideNodes() + { + return [ + [0, <<<'EODUMP' +XMLReader { + +nodeType: NONE + …13 +} +EODUMP + ], + [1, <<<'EODUMP' +XMLReader { + +localName: "foo" + +nodeType: ELEMENT + +baseURI: "%sxml_reader.xml" + …11 +} +EODUMP + ], + [2, <<<'EODUMP' +XMLReader { + +localName: "#text" + +nodeType: SIGNIFICANT_WHITESPACE + +depth: 1 + +value: """ + \n + + """ + +baseURI: "%sxml_reader.xml" + …9 +} +EODUMP + ], + [3, <<<'EODUMP' +XMLReader { + +localName: "bar" + +nodeType: ELEMENT + +depth: 1 + +baseURI: "%sxml_reader.xml" + …10 +} +EODUMP + ], + [4, <<<'EODUMP' +XMLReader { + +localName: "bar" + +nodeType: END_ELEMENT + +depth: 1 + +baseURI: "%sxml_reader.xml" + …10 +} +EODUMP + ], + [6, <<<'EODUMP' +XMLReader { + +localName: "bar" + +nodeType: ELEMENT + +depth: 1 + +isEmptyElement: true + +baseURI: "%sxml_reader.xml" + …9 +} +EODUMP + ], + [9, <<<'EODUMP' +XMLReader { + +localName: "#text" + +nodeType: TEXT + +depth: 2 + +value: "With text" + +baseURI: "%sxml_reader.xml" + …9 +} +EODUMP + ], + [12, <<<'EODUMP' +XMLReader { + +localName: "bar" + +nodeType: ELEMENT + +depth: 1 + +attributeCount: 2 + +baseURI: "%sxml_reader.xml" + …9 +} +EODUMP + ], + [13, <<<'EODUMP' +XMLReader { + +localName: "bar" + +nodeType: END_ELEMENT + +depth: 1 + +baseURI: "%sxml_reader.xml" + …10 +} +EODUMP + ], + [15, <<<'EODUMP' +XMLReader { + +localName: "bar" + +nodeType: ELEMENT + +depth: 1 + +attributeCount: 1 + +baseURI: "%sxml_reader.xml" + …9 +} +EODUMP + ], + [16, <<<'EODUMP' +XMLReader { + +localName: "#text" + +nodeType: SIGNIFICANT_WHITESPACE + +depth: 2 + +value: """ + \n + + """ + +baseURI: "%sxml_reader.xml" + …9 +} +EODUMP + ], + [17, <<<'EODUMP' +XMLReader { + +localName: "baz" + +prefix: "baz" + +nodeType: ELEMENT + +depth: 2 + +namespaceURI: "http://symfony.com" + +baseURI: "%sxml_reader.xml" + …8 +} +EODUMP + ], + [18, <<<'EODUMP' +XMLReader { + +localName: "baz" + +prefix: "baz" + +nodeType: END_ELEMENT + +depth: 2 + +namespaceURI: "http://symfony.com" + +baseURI: "%sxml_reader.xml" + …8 +} +EODUMP + ], + [19, <<<'EODUMP' +XMLReader { + +localName: "#text" + +nodeType: SIGNIFICANT_WHITESPACE + +depth: 2 + +value: """ + \n + + """ + +baseURI: "%sxml_reader.xml" + …9 +} +EODUMP + ], + [21, <<<'EODUMP' +XMLReader { + +localName: "#text" + +nodeType: SIGNIFICANT_WHITESPACE + +depth: 1 + +value: "\n" + +baseURI: "%sxml_reader.xml" + …9 +} +EODUMP + ], + [22, <<<'EODUMP' +XMLReader { + +localName: "foo" + +nodeType: END_ELEMENT + +baseURI: "%sxml_reader.xml" + …11 +} +EODUMP + ], + ]; + } + + public function testWithUninitializedXMLReader() + { + $this->reader = new \XMLReader(); + + $expectedDump = <<<'EODUMP' +XMLReader { + +nodeType: NONE + …13 +} +EODUMP; + + $this->assertDumpMatchesFormat($expectedDump, $this->reader); + } +} diff --git a/vendor/symfony/var-dumper/Tests/Cloner/DataTest.php b/vendor/symfony/var-dumper/Tests/Cloner/DataTest.php new file mode 100644 index 0000000000..d4b6c24c11 --- /dev/null +++ b/vendor/symfony/var-dumper/Tests/Cloner/DataTest.php @@ -0,0 +1,115 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Tests\Cloner; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\VarDumper\Caster\Caster; +use Symfony\Component\VarDumper\Caster\ClassStub; +use Symfony\Component\VarDumper\Cloner\Data; +use Symfony\Component\VarDumper\Cloner\VarCloner; + +class DataTest extends TestCase +{ + public function testBasicData() + { + $values = [1 => 123, 4.5, 'abc', null, false]; + $data = $this->cloneVar($values); + $clonedValues = []; + + $this->assertInstanceOf(Data::class, $data); + $this->assertCount(\count($values), $data); + $this->assertFalse(isset($data->{0})); + $this->assertFalse(isset($data[0])); + + foreach ($data as $k => $v) { + $this->assertTrue(isset($data->{$k})); + $this->assertTrue(isset($data[$k])); + $this->assertSame(\gettype($values[$k]), $data->seek($k)->getType()); + $this->assertSame($values[$k], $data->seek($k)->getValue()); + $this->assertSame($values[$k], $data->{$k}); + $this->assertSame($values[$k], $data[$k]); + $this->assertSame((string) $values[$k], (string) $data->seek($k)); + + $clonedValues[$k] = $v->getValue(); + } + + $this->assertSame($values, $clonedValues); + } + + public function testObject() + { + $data = $this->cloneVar(new \Exception('foo')); + + $this->assertSame('Exception', $data->getType()); + + $this->assertSame('foo', $data->message); + $this->assertSame('foo', $data->{Caster::PREFIX_PROTECTED.'message'}); + + $this->assertSame('foo', $data['message']); + $this->assertSame('foo', $data[Caster::PREFIX_PROTECTED.'message']); + + $this->assertStringMatchesFormat('Exception (count=%d)', (string) $data); + } + + public function testArray() + { + $values = [[], [123]]; + $data = $this->cloneVar($values); + + $this->assertSame($values, $data->getValue(true)); + + $children = $data->getValue(); + + $this->assertIsArray($children); + + $this->assertInstanceOf(Data::class, $children[0]); + $this->assertInstanceOf(Data::class, $children[1]); + + $this->assertEquals($children[0], $data[0]); + $this->assertEquals($children[1], $data[1]); + + $this->assertSame($values[0], $children[0]->getValue(true)); + $this->assertSame($values[1], $children[1]->getValue(true)); + } + + public function testStub() + { + $data = $this->cloneVar([new ClassStub('stdClass')]); + $data = $data[0]; + + $this->assertSame('string', $data->getType()); + $this->assertSame('stdClass', $data->getValue()); + $this->assertSame('stdClass', (string) $data); + } + + public function testHardRefs() + { + $values = [[]]; + $values[1] = &$values[0]; + $values[2][0] = &$values[2]; + + $data = $this->cloneVar($values); + + $this->assertSame([], $data[0]->getValue()); + $this->assertSame([], $data[1]->getValue()); + $this->assertEquals([$data[2]->getValue()], $data[2]->getValue(true)); + + $this->assertSame('array (count=3)', (string) $data); + } + + private function cloneVar($value) + { + $cloner = new VarCloner(); + + return $cloner->cloneVar($value); + } +} diff --git a/vendor/symfony/var-dumper/Tests/Cloner/VarClonerTest.php b/vendor/symfony/var-dumper/Tests/Cloner/VarClonerTest.php new file mode 100644 index 0000000000..a69fac25bd --- /dev/null +++ b/vendor/symfony/var-dumper/Tests/Cloner/VarClonerTest.php @@ -0,0 +1,678 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Tests\Cloner; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\VarDumper\Cloner\VarCloner; +use Symfony\Component\VarDumper\Tests\Fixtures\Php74; +use Symfony\Component\VarDumper\Tests\Fixtures\Php81Enums; + +/** + * @author Nicolas Grekas + */ +class VarClonerTest extends TestCase +{ + public function testMaxIntBoundary() + { + $data = [\PHP_INT_MAX => 123]; + + $cloner = new VarCloner(); + $clone = $cloner->cloneVar($data); + + $expected = << Array + ( + [0] => Array + ( + [0] => Array + ( + [1] => 1 + ) + + ) + + [1] => Array + ( + [%s] => 123 + ) + + ) + + [position:Symfony\Component\VarDumper\Cloner\Data:private] => 0 + [key:Symfony\Component\VarDumper\Cloner\Data:private] => 0 + [maxDepth:Symfony\Component\VarDumper\Cloner\Data:private] => 20 + [maxItemsPerDepth:Symfony\Component\VarDumper\Cloner\Data:private] => -1 + [useRefHandles:Symfony\Component\VarDumper\Cloner\Data:private] => -1 + [context:Symfony\Component\VarDumper\Cloner\Data:private] => Array + ( + ) + +) + +EOTXT; + $this->assertSame(sprintf($expected, \PHP_INT_MAX), print_r($clone, true)); + } + + public function testClone() + { + $json = json_decode('{"1":{"var":"val"},"2":{"var":"val"}}'); + + $cloner = new VarCloner(); + $clone = $cloner->cloneVar($json); + + $expected = << Array + ( + [0] => Array + ( + [0] => Symfony\Component\VarDumper\Cloner\Stub Object + ( + [type] => 4 + [class] => stdClass + [value] => + [cut] => 0 + [handle] => %i + [refCount] => 0 + [position] => 1 + [attr] => Array + ( + ) + + ) + + ) + + [1] => Array + ( + [\000+\0001] => Symfony\Component\VarDumper\Cloner\Stub Object + ( + [type] => 4 + [class] => stdClass + [value] => + [cut] => 0 + [handle] => %i + [refCount] => 0 + [position] => 2 + [attr] => Array + ( + ) + + ) + + [\000+\0002] => Symfony\Component\VarDumper\Cloner\Stub Object + ( + [type] => 4 + [class] => stdClass + [value] => + [cut] => 0 + [handle] => %i + [refCount] => 0 + [position] => 3 + [attr] => Array + ( + ) + + ) + + ) + + [2] => Array + ( + [\000+\000var] => val + ) + + [3] => Array + ( + [\000+\000var] => val + ) + + ) + + [position:Symfony\Component\VarDumper\Cloner\Data:private] => 0 + [key:Symfony\Component\VarDumper\Cloner\Data:private] => 0 + [maxDepth:Symfony\Component\VarDumper\Cloner\Data:private] => 20 + [maxItemsPerDepth:Symfony\Component\VarDumper\Cloner\Data:private] => -1 + [useRefHandles:Symfony\Component\VarDumper\Cloner\Data:private] => -1 + [context:Symfony\Component\VarDumper\Cloner\Data:private] => Array + ( + ) + +) + +EOTXT; + $this->assertStringMatchesFormat($expected, print_r($clone, true)); + } + + public function testLimits() + { + // Level 0: + $data = [ + // Level 1: + [ + // Level 2: + [ + // Level 3: + 'Level 3 Item 0', + 'Level 3 Item 1', + 'Level 3 Item 2', + 'Level 3 Item 3', + ], + [ + 999 => 'Level 3 Item 4', + 'Level 3 Item 5', + 'Level 3 Item 6', + ], + [ + 'Level 3 Item 7', + ], + ], + [ + [ + 'Level 3 Item 8', + ], + 'Level 2 Item 0', + ], + [ + 'Level 2 Item 1', + ], + 'Level 1 Item 0', + [ + // Test setMaxString: + 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', + 'SHORT', + ], + ]; + + $cloner = new VarCloner(); + $cloner->setMinDepth(2); + $cloner->setMaxItems(5); + $cloner->setMaxString(20); + $clone = $cloner->cloneVar($data); + + $expected = << Array + ( + [0] => Array + ( + [0] => Array + ( + [2] => 1 + ) + + ) + + [1] => Array + ( + [0] => Array + ( + [2] => 2 + ) + + [1] => Array + ( + [2] => 3 + ) + + [2] => Array + ( + [2] => 4 + ) + + [3] => Level 1 Item 0 + [4] => Array + ( + [2] => 5 + ) + + ) + + [2] => Array + ( + [0] => Array + ( + [2] => 6 + ) + + [1] => Array + ( + [0] => 2 + [1] => 7 + ) + + [2] => Array + ( + [0] => 1 + [2] => 0 + ) + + ) + + [3] => Array + ( + [0] => Array + ( + [0] => 1 + [2] => 0 + ) + + [1] => Level 2 Item 0 + ) + + [4] => Array + ( + [0] => Level 2 Item 1 + ) + + [5] => Array + ( + [0] => Symfony\Component\VarDumper\Cloner\Stub Object + ( + [type] => 2 + [class] => 2 + [value] => ABCDEFGHIJKLMNOPQRST + [cut] => 6 + [handle] => 0 + [refCount] => 0 + [position] => 0 + [attr] => Array + ( + ) + + ) + + [1] => SHORT + ) + + [6] => Array + ( + [0] => Level 3 Item 0 + [1] => Level 3 Item 1 + [2] => Level 3 Item 2 + [3] => Level 3 Item 3 + ) + + [7] => Array + ( + [999] => Level 3 Item 4 + ) + + ) + + [position:Symfony\Component\VarDumper\Cloner\Data:private] => 0 + [key:Symfony\Component\VarDumper\Cloner\Data:private] => 0 + [maxDepth:Symfony\Component\VarDumper\Cloner\Data:private] => 20 + [maxItemsPerDepth:Symfony\Component\VarDumper\Cloner\Data:private] => -1 + [useRefHandles:Symfony\Component\VarDumper\Cloner\Data:private] => -1 + [context:Symfony\Component\VarDumper\Cloner\Data:private] => Array + ( + ) + +) + +EOTXT; + $this->assertStringMatchesFormat($expected, print_r($clone, true)); + } + + public function testJsonCast() + { + if (2 == \ini_get('xdebug.overload_var_dump')) { + $this->markTestSkipped('xdebug is active'); + } + + $data = (array) json_decode('{"1":{}}'); + + $cloner = new VarCloner(); + $clone = $cloner->cloneVar($data); + + $expected = <<<'EOTXT' +object(Symfony\Component\VarDumper\Cloner\Data)#%d (7) { + ["data":"Symfony\Component\VarDumper\Cloner\Data":private]=> + array(2) { + [0]=> + array(1) { + [0]=> + array(1) { + [1]=> + int(1) + } + } + [1]=> + array(1) { + ["1"]=> + object(Symfony\Component\VarDumper\Cloner\Stub)#%i (8) { + ["type"]=> + int(4) + ["class"]=> + string(8) "stdClass" + ["value"]=> + NULL + ["cut"]=> + int(0) + ["handle"]=> + int(%i) + ["refCount"]=> + int(0) + ["position"]=> + int(0) + ["attr"]=> + array(0) { + } + } + } + } + ["position":"Symfony\Component\VarDumper\Cloner\Data":private]=> + int(0) + ["key":"Symfony\Component\VarDumper\Cloner\Data":private]=> + int(0) + ["maxDepth":"Symfony\Component\VarDumper\Cloner\Data":private]=> + int(20) + ["maxItemsPerDepth":"Symfony\Component\VarDumper\Cloner\Data":private]=> + int(-1) + ["useRefHandles":"Symfony\Component\VarDumper\Cloner\Data":private]=> + int(-1) + ["context":"Symfony\Component\VarDumper\Cloner\Data":private]=> + array(0) { + } +} + +EOTXT; + ob_start(); + var_dump($clone); + $this->assertStringMatchesFormat(str_replace('"1"', '1', $expected), ob_get_clean()); + } + + public function testCaster() + { + $cloner = new VarCloner([ + '*' => function ($obj, $array) { + return ['foo' => 123]; + }, + __CLASS__ => function ($obj, $array) { + ++$array['foo']; + + return $array; + }, + ]); + $clone = $cloner->cloneVar($this); + + $expected = << Array + ( + [0] => Array + ( + [0] => Symfony\Component\VarDumper\Cloner\Stub Object + ( + [type] => 4 + [class] => %s + [value] => + [cut] => 0 + [handle] => %i + [refCount] => 0 + [position] => 1 + [attr] => Array + ( + [file] => %a%eVarClonerTest.php + [line] => 22 + ) + + ) + + ) + + [1] => Array + ( + [foo] => 124 + ) + + ) + + [position:Symfony\Component\VarDumper\Cloner\Data:private] => 0 + [key:Symfony\Component\VarDumper\Cloner\Data:private] => 0 + [maxDepth:Symfony\Component\VarDumper\Cloner\Data:private] => 20 + [maxItemsPerDepth:Symfony\Component\VarDumper\Cloner\Data:private] => -1 + [useRefHandles:Symfony\Component\VarDumper\Cloner\Data:private] => -1 + [context:Symfony\Component\VarDumper\Cloner\Data:private] => Array + ( + ) + +) + +EOTXT; + $this->assertStringMatchesFormat($expected, print_r($clone, true)); + } + + /** + * @requires PHP 7.4 + */ + public function testPhp74() + { + $data = new Php74(); + + $cloner = new VarCloner(); + $clone = $cloner->cloneVar($data); + + $expected = <<<'EOTXT' +Symfony\Component\VarDumper\Cloner\Data Object +( + [data:Symfony\Component\VarDumper\Cloner\Data:private] => Array + ( + [0] => Array + ( + [0] => Symfony\Component\VarDumper\Cloner\Stub Object + ( + [type] => 4 + [class] => Symfony\Component\VarDumper\Tests\Fixtures\Php74 + [value] => + [cut] => 0 + [handle] => %i + [refCount] => 0 + [position] => 1 + [attr] => Array + ( + [file] => %s + [line] => 5 + ) + + ) + + ) + + [1] => Array + ( + [p1] => 123 + [p2] => Symfony\Component\VarDumper\Cloner\Stub Object + ( + [type] => 1 + [class] => + [value] => Symfony\Component\VarDumper\Cloner\Stub Object + ( + [type] => 4 + [class] => stdClass + [value] => + [cut] => 0 + [handle] => %i + [refCount] => 1 + [position] => 0 + [attr] => Array + ( + ) + + ) + + [cut] => 0 + [handle] => 1 + [refCount] => 1 + [position] => 0 + [attr] => Array + ( + ) + + ) + + [p3] => Symfony\Component\VarDumper\Cloner\Stub Object + ( + [type] => 1 + [class] => + [value] => Symfony\Component\VarDumper\Cloner\Stub Object + ( + [type] => 4 + [class] => stdClass + [value] => + [cut] => 0 + [handle] => %i + [refCount] => 1 + [position] => 0 + [attr] => Array + ( + ) + + ) + + [cut] => 0 + [handle] => 1 + [refCount] => 1 + [position] => 0 + [attr] => Array + ( + ) + + ) + + ) + + ) + + [position:Symfony\Component\VarDumper\Cloner\Data:private] => 0 + [key:Symfony\Component\VarDumper\Cloner\Data:private] => 0 + [maxDepth:Symfony\Component\VarDumper\Cloner\Data:private] => 20 + [maxItemsPerDepth:Symfony\Component\VarDumper\Cloner\Data:private] => -1 + [useRefHandles:Symfony\Component\VarDumper\Cloner\Data:private] => -1 + [context:Symfony\Component\VarDumper\Cloner\Data:private] => Array + ( + ) + +) + +EOTXT; + $this->assertStringMatchesFormat($expected, print_r($clone, true)); + } + + /** + * @requires PHP 8.1 + */ + public function testPhp81Enums() + { + $data = new Php81Enums(); + + $cloner = new VarCloner(); + $clone = $cloner->cloneVar($data); + + $expected = <<<'EOTXT' +Symfony\Component\VarDumper\Cloner\Data Object +( + [data:Symfony\Component\VarDumper\Cloner\Data:private] => Array + ( + [0] => Array + ( + [0] => Symfony\Component\VarDumper\Cloner\Stub Object + ( + [type] => 4 + [class] => Symfony\Component\VarDumper\Tests\Fixtures\Php81Enums + [value] => + [cut] => 0 + [handle] => %i + [refCount] => 0 + [position] => 1 + [attr] => Array + ( + [file] => %s + [line] => 5 + ) + + ) + + ) + + [1] => Array + ( + [e1] => Symfony\Component\VarDumper\Cloner\Stub Object + ( + [type] => 4 + [class] => Symfony\Component\VarDumper\Tests\Fixtures\UnitEnumFixture + [value] => + [cut] => 0 + [handle] => %i + [refCount] => 0 + [position] => 2 + [attr] => Array + ( + [file] => %s + [line] => 5 + ) + + ) + + [e2] => Symfony\Component\VarDumper\Cloner\Stub Object + ( + [type] => 4 + [class] => Symfony\Component\VarDumper\Tests\Fixtures\BackedEnumFixture + [value] => + [cut] => 0 + [handle] => %i + [refCount] => 0 + [position] => 3 + [attr] => Array + ( + [file] => %s + [line] => 5 + ) + + ) + + ) + + [2] => Array + ( + [name] => Hearts + ) + + [3] => Array + ( + [name] => Diamonds + [value] => D + ) + + ) + + [position:Symfony\Component\VarDumper\Cloner\Data:private] => 0 + [key:Symfony\Component\VarDumper\Cloner\Data:private] => 0 + [maxDepth:Symfony\Component\VarDumper\Cloner\Data:private] => 20 + [maxItemsPerDepth:Symfony\Component\VarDumper\Cloner\Data:private] => -1 + [useRefHandles:Symfony\Component\VarDumper\Cloner\Data:private] => -1 + [context:Symfony\Component\VarDumper\Cloner\Data:private] => Array + ( + ) + +) + +EOTXT; + $this->assertStringMatchesFormat($expected, print_r($clone, true)); + } +} diff --git a/vendor/symfony/var-dumper/Tests/Command/Descriptor/CliDescriptorTest.php b/vendor/symfony/var-dumper/Tests/Command/Descriptor/CliDescriptorTest.php new file mode 100644 index 0000000000..5941508a3a --- /dev/null +++ b/vendor/symfony/var-dumper/Tests/Command/Descriptor/CliDescriptorTest.php @@ -0,0 +1,173 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Tests\Command\Descriptor; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Console\Formatter\OutputFormatterStyle; +use Symfony\Component\Console\Output\BufferedOutput; +use Symfony\Component\VarDumper\Cloner\Data; +use Symfony\Component\VarDumper\Command\Descriptor\CliDescriptor; +use Symfony\Component\VarDumper\Dumper\CliDumper; + +class CliDescriptorTest extends TestCase +{ + private static $timezone; + private static $prevTerminalEmulator; + + public static function setUpBeforeClass(): void + { + self::$timezone = date_default_timezone_get(); + date_default_timezone_set('UTC'); + + self::$prevTerminalEmulator = getenv('TERMINAL_EMULATOR'); + putenv('TERMINAL_EMULATOR'); + } + + public static function tearDownAfterClass(): void + { + date_default_timezone_set(self::$timezone); + putenv('TERMINAL_EMULATOR'.(self::$prevTerminalEmulator ? '='.self::$prevTerminalEmulator : '')); + } + + /** + * @dataProvider provideContext + */ + public function testDescribe(array $context, string $expectedOutput, bool $decorated = false) + { + $output = new BufferedOutput(); + $output->setDecorated($decorated); + $descriptor = new CliDescriptor(new CliDumper(function ($s) { + return $s; + })); + + $descriptor->describe($output, new Data([[123]]), $context + ['timestamp' => 1544804268.3668], 1); + + $this->assertStringMatchesFormat(trim($expectedOutput), str_replace(\PHP_EOL, "\n", trim($output->fetch()))); + } + + public static function provideContext() + { + yield 'source' => [ + [ + 'source' => [ + 'name' => 'CliDescriptorTest.php', + 'line' => 30, + 'file' => '/Users/ogi/symfony/src/Symfony/Component/VarDumper/Tests/Command/Descriptor/CliDescriptorTest.php', + ], + ], + << [ + [ + 'source' => [ + 'name' => 'CliDescriptorTest.php', + 'line' => 30, + 'file_relative' => 'src/Symfony/Component/VarDumper/Tests/Command/Descriptor/CliDescriptorTest.php', + 'file' => '/Users/ogi/symfony/src/Symfony/Component/VarDumper/Tests/Command/Descriptor/CliDescriptorTest.php', + 'file_link' => 'phpstorm://open?file=/Users/ogi/symfony/src/Symfony/Component/VarDumper/Tests/Command/Descriptor/CliDescriptorTest.php&line=30', + ], + ], + method_exists(OutputFormatterStyle::class, 'setHref') ? + << [ + [ + 'source' => [ + 'name' => 'CliDescriptorTest.php', + 'line' => 30, + 'file_relative' => 'src/Symfony/Component/VarDumper/Tests/Command/Descriptor/CliDescriptorTest.php', + 'file_link' => 'phpstorm://open?file=/Users/ogi/symfony/src/Symfony/Component/VarDumper/Tests/Command/Descriptor/CliDescriptorTest.php&line=30', + ], + ], + << [ + [ + 'cli' => [ + 'identifier' => 'd8bece1c', + 'command_line' => 'bin/phpunit', + ], + ], + << [ + [ + 'request' => [ + 'identifier' => 'd8bece1c', + 'controller' => new Data([['FooController.php']]), + 'method' => 'GET', + 'uri' => 'http://localhost/foo', + ], + ], + << + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Tests\Command\Descriptor; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Console\Output\BufferedOutput; +use Symfony\Component\VarDumper\Cloner\Data; +use Symfony\Component\VarDumper\Command\Descriptor\HtmlDescriptor; +use Symfony\Component\VarDumper\Dumper\HtmlDumper; + +class HtmlDescriptorTest extends TestCase +{ + private static $timezone; + + public static function setUpBeforeClass(): void + { + self::$timezone = date_default_timezone_get(); + date_default_timezone_set('UTC'); + } + + public static function tearDownAfterClass(): void + { + date_default_timezone_set(self::$timezone); + } + + public function testItOutputsStylesAndScriptsOnFirstDescribeCall() + { + $output = new BufferedOutput(); + $dumper = $this->createMock(HtmlDumper::class); + $dumper->method('dump')->willReturn('[DUMPED]'); + $descriptor = new HtmlDescriptor($dumper); + + $descriptor->describe($output, new Data([[123]]), ['timestamp' => 1544804268.3668], 1); + + $this->assertStringMatchesFormat('%A', $output->fetch(), 'styles & scripts are output'); + + $descriptor->describe($output, new Data([[123]]), ['timestamp' => 1544804268.3668], 1); + + $this->assertStringNotMatchesFormat('%A', $output->fetch(), 'styles & scripts are output only once'); + } + + /** + * @dataProvider provideContext + */ + public function testDescribe(array $context, string $expectedOutput) + { + $output = new BufferedOutput(); + $dumper = $this->createMock(HtmlDumper::class); + $dumper->method('dump')->willReturn('[DUMPED]'); + $descriptor = new HtmlDescriptor($dumper); + + $descriptor->describe($output, new Data([[123]]), $context + ['timestamp' => 1544804268.3668], 1); + + $this->assertStringMatchesFormat(trim($expectedOutput), trim(preg_replace('@@s', '', $output->fetch()))); + } + + public static function provideContext() + { + yield 'source' => [ + [ + 'source' => [ + 'name' => 'CliDescriptorTest.php', + 'line' => 30, + 'file' => '/Users/ogi/symfony/src/Symfony/Component/VarDumper/Tests/Command/Descriptor/CliDescriptorTest.php', + ], + ], + << +
+
+

-

+ +
+ +
+
+

+ CliDescriptorTest.php on line 30 +

+ [DUMPED] +
+ +TXT + ]; + + yield 'source full' => [ + [ + 'source' => [ + 'name' => 'CliDescriptorTest.php', + 'project_dir' => 'src/Symfony/', + 'line' => 30, + 'file_relative' => 'src/Symfony/Component/VarDumper/Tests/Command/Descriptor/CliDescriptorTest.php', + 'file' => '/Users/ogi/symfony/src/Symfony/Component/VarDumper/Tests/Command/Descriptor/CliDescriptorTest.php', + 'file_link' => 'phpstorm://open?file=/Users/ogi/symfony/src/Symfony/Component/VarDumper/Tests/Command/Descriptor/CliDescriptorTest.php&line=30', + ], + ], + << +
+
+

-

+ +
+
+
    +
  • project dirsrc/Symfony/
  • +
+
+
+
+

+ CliDescriptorTest.php on line 30 +

+ [DUMPED] +
+ +TXT + ]; + + yield 'cli' => [ + [ + 'cli' => [ + 'identifier' => 'd8bece1c', + 'command_line' => 'bin/phpunit', + ], + ], + << +
+
+

$ bin/phpunit

+ +
+ +
+
+

+ +

+ [DUMPED] +
+ +TXT + ]; + + yield 'request' => [ + [ + 'request' => [ + 'identifier' => 'd8bece1c', + 'controller' => new Data([['FooController.php']]), + 'method' => 'GET', + 'uri' => 'http://localhost/foo', + ], + ], + << +
+
+

GET http://localhost/foo

+ +
+
+
    +
  • controller[DUMPED]
  • +
+
+
+
+

+ +

+ [DUMPED] +
+ +TXT + ]; + } +} diff --git a/vendor/symfony/var-dumper/Tests/Command/ServerDumpCommandTest.php b/vendor/symfony/var-dumper/Tests/Command/ServerDumpCommandTest.php new file mode 100644 index 0000000000..d593608de8 --- /dev/null +++ b/vendor/symfony/var-dumper/Tests/Command/ServerDumpCommandTest.php @@ -0,0 +1,47 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Tests\Command; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Console\Tester\CommandCompletionTester; +use Symfony\Component\VarDumper\Command\ServerDumpCommand; +use Symfony\Component\VarDumper\Server\DumpServer; + +class ServerDumpCommandTest extends TestCase +{ + /** + * @dataProvider provideCompletionSuggestions + */ + public function testComplete(array $input, array $expectedSuggestions) + { + if (!class_exists(CommandCompletionTester::class)) { + $this->markTestSkipped('Test command completion requires symfony/console 5.4+.'); + } + + $tester = new CommandCompletionTester($this->createCommand()); + + $this->assertSame($expectedSuggestions, $tester->complete($input)); + } + + public static function provideCompletionSuggestions() + { + yield 'option --format' => [ + ['--format', ''], + ['cli', 'html'], + ]; + } + + private function createCommand(): ServerDumpCommand + { + return new ServerDumpCommand($this->createMock(DumpServer::class)); + } +} diff --git a/vendor/symfony/var-dumper/Tests/Dumper/CliDumperTest.php b/vendor/symfony/var-dumper/Tests/Dumper/CliDumperTest.php new file mode 100644 index 0000000000..968b48749f --- /dev/null +++ b/vendor/symfony/var-dumper/Tests/Dumper/CliDumperTest.php @@ -0,0 +1,569 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Tests\Dumper; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\VarDumper\Caster\CutStub; +use Symfony\Component\VarDumper\Cloner\VarCloner; +use Symfony\Component\VarDumper\Dumper\AbstractDumper; +use Symfony\Component\VarDumper\Dumper\CliDumper; +use Symfony\Component\VarDumper\Test\VarDumperTestTrait; +use Twig\Environment; +use Twig\Loader\FilesystemLoader; + +/** + * @author Nicolas Grekas + */ +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( + << 1 + 0 => &1 null + "const" => 1.1 + 1 => true + 2 => false + 3 => NAN + 4 => INF + 5 => -INF + 6 => {$intMax} + "str" => "déjà\\n" + 7 => b""" + é\\x01test\\t\\n + ing + """ + "[]" => [] + "res" => stream resource {@{$res} +%A wrapper_type: "plainfile" + stream_type: "STDIO" + mode: "r" + unread_bytes: 0 + seekable: true +%A options: [] + } + "obj" => Symfony\Component\VarDumper\Tests\Fixture\DumbFoo {#%d + +foo: ""…3 + +"bar": "bar" + } + "closure" => Closure(\$a, PDO &\$b = null) {#%d + class: "Symfony\Component\VarDumper\Tests\Dumper\CliDumperTest" + this: Symfony\Component\VarDumper\Tests\Dumper\CliDumperTest {#%d …} + file: "%s%eTests%eFixtures%edumb-var.php" + line: "{$var['line']} to {$var['line']}" + } + "line" => {$var['line']} + "nobj" => array:1 [ + 0 => &3 {#%d} + ] + "recurs" => &4 array:1 [ + 0 => &4 array:1 [&4] + ] + 8 => &1 null + "sobj" => Symfony\Component\VarDumper\Tests\Fixture\DumbFoo {#%d} + "snobj" => &3 {#%d} + "snobj2" => {#%d} + "file" => "{$var['file']}" + b"bin-key-é" => "" +] + +EOTXT + , + $out + ); + } + + /** + * @dataProvider provideDumpWithCommaFlagTests + */ + public function testDumpWithCommaFlag($expected, $flags) + { + $dumper = new CliDumper(null, null, $flags); + $dumper->setColors(false); + $cloner = new VarCloner(); + + $var = [ + 'array' => ['a', 'b'], + 'string' => 'hello', + 'multiline string' => "this\nis\na\multiline\nstring", + ]; + + $dump = $dumper->dump($cloner->cloneVar($var), true); + + $this->assertSame($expected, $dump); + } + + public function testDumpWithCommaFlagsAndExceptionCodeExcerpt() + { + $dumper = new CliDumper(null, null, CliDumper::DUMP_TRAILING_COMMA); + $dumper->setColors(false); + $cloner = new VarCloner(); + + $ex = new \RuntimeException('foo'); + + $dump = $dumper->dump($cloner->cloneVar($ex)->withRefHandles(false), true); + + $this->assertStringMatchesFormat(<<<'EOTXT' +RuntimeException { + #message: "foo" + #code: 0 + #file: "%ACliDumperTest.php" + #line: %d + trace: { + %ACliDumperTest.php:%d { + Symfony\Component\VarDumper\Tests\Dumper\CliDumperTest->testDumpWithCommaFlagsAndExceptionCodeExcerpt() + › + › $ex = new \RuntimeException('foo'); + › + } + %A + } +} + +EOTXT + , $dump); + } + + public static function provideDumpWithCommaFlagTests() + { + $expected = <<<'EOTXT' +array:3 [ + "array" => array:2 [ + 0 => "a", + 1 => "b" + ], + "string" => "hello", + "multiline string" => """ + this\n + is\n + a\multiline\n + string + """ +] + +EOTXT; + + yield [$expected, CliDumper::DUMP_COMMA_SEPARATOR]; + + $expected = <<<'EOTXT' +array:3 [ + "array" => array:2 [ + 0 => "a", + 1 => "b", + ], + "string" => "hello", + "multiline string" => """ + this\n + is\n + a\multiline\n + string + """, +] + +EOTXT; + + yield [$expected, CliDumper::DUMP_TRAILING_COMMA]; + } + + /** + * @requires extension xml + * @requires PHP < 8.0 + */ + public function testXmlResource() + { + $var = xml_parser_create(); + + $this->assertDumpMatchesFormat( + <<<'EOTXT' +xml resource { + current_byte_index: %i + current_column_number: %i + current_line_number: 1 + error_code: XML_ERROR_NONE +} +EOTXT + , + $var + ); + } + + public function testJsonCast() + { + $var = (array) json_decode('{"0":{},"1":null}'); + foreach ($var as &$v) { + } + $var[] = &$v; + $var[''] = 2; + + $this->assertDumpMatchesFormat( + <<<'EOTXT' +array:4 [ + 0 => {} + 1 => &1 null + 2 => &1 null + "" => 2 +] +EOTXT + , + $var + ); + } + + public function testObjectCast() + { + $var = (object) [1 => 1]; + $var->{1} = 2; + + $this->assertDumpMatchesFormat( + <<<'EOTXT' +{ + +"1": 2 +} +EOTXT + , + $var + ); + } + + public function testClosedResource() + { + $var = fopen(__FILE__, 'r'); + fclose($var); + + $dumper = new CliDumper('php://output'); + $dumper->setColors(false); + $cloner = new VarCloner(); + $data = $cloner->cloneVar($var); + + ob_start(); + $dumper->dump($data); + $out = ob_get_clean(); + $res = (int) $var; + + $this->assertStringMatchesFormat( + << 'bar'], + ]; + + $this->assertDumpEquals( + << (3) "foo" + 2 => (3) "bar" + ] +] +EOTXT + , + $var + ); + + putenv('DUMP_LIGHT_ARRAY='); + putenv('DUMP_STRING_LENGTH='); + } + + /** + * @requires function Twig\Template::getSourceContext + */ + public function testThrowingCaster() + { + $out = fopen('php://memory', 'r+'); + + require_once __DIR__.'/../Fixtures/Twig.php'; + $twig = new \__TwigTemplate_VarDumperFixture_u75a09(new Environment(new FilesystemLoader())); + + $dumper = new CliDumper(); + $dumper->setColors(false); + $cloner = new VarCloner(); + $cloner->addCasters([ + ':stream' => function ($res, $a) { + unset($a['wrapper_data']); + + return $a; + }, + ]); + $cloner->addCasters([ + ':stream' => eval('return function () use ($twig) { + try { + $twig->render([]); + } catch (\Twig\Error\RuntimeError $e) { + throw $e->getPrevious(); + } + };'), + ]); + $ref = (int) $out; + + $data = $cloner->cloneVar($out); + $dumper->dump($data, $out); + $out = stream_get_contents($out, -1, 0); + + $this->assertStringMatchesFormat( + <<doDisplay(array \$context, array \$blocks = []) + › foo bar + › twig source + › + } + %s%eTemplate.php:%d { …} + %s%eTemplate.php:%d { …} + %s%eTemplate.php:%d { …} + %s%eTests%eDumper%eCliDumperTest.php:%d { …} +%A } + } +%Awrapper_type: "PHP" + stream_type: "MEMORY" + mode: "%s+b" + unread_bytes: 0 + seekable: true + uri: "php://memory" +%Aoptions: [] +} + +EOTXT + , + $out + ); + } + + public function testRefsInProperties() + { + $var = (object) ['foo' => 'foo']; + $var->bar = &$var->foo; + + $dumper = new CliDumper(); + $dumper->setColors(false); + $cloner = new VarCloner(); + + $data = $cloner->cloneVar($var); + $out = $dumper->dump($data, true); + + $this->assertStringMatchesFormat( + <<getSpecialVars(); + + $this->assertDumpEquals( + <<<'EOTXT' +array:3 [ + 0 => array:1 [ + 0 => &1 array:1 [ + 0 => &1 array:1 [&1] + ] + ] + 1 => array:1 [ + "GLOBALS" => & array:1 [ …1] + ] + 2 => &3 array:1 [ + "GLOBALS" => &3 array:1 [&3] + ] +] +EOTXT + , + $var + ); + } + + /** + * @runInSeparateProcess + * @preserveGlobalState disabled + * @requires PHP < 8.1 + */ + public function testGlobals() + { + $var = $this->getSpecialVars(); + unset($var[0]); + $out = ''; + + $dumper = new CliDumper(function ($line, $depth) use (&$out) { + if ($depth >= 0) { + $out .= str_repeat(' ', $depth).$line."\n"; + } + }); + $dumper->setColors(false); + $cloner = new VarCloner(); + + $data = $cloner->cloneVar($var); + $dumper->dump($data); + + $this->assertSame( + <<<'EOTXT' +array:2 [ + 1 => array:1 [ + "GLOBALS" => & array:1 [ …1] + ] + 2 => &2 array:1 [ + "GLOBALS" => &2 array:1 [&2] + ] +] + +EOTXT + , + $out + ); + } + + public function testIncompleteClass() + { + $unserializeCallbackHandler = ini_set('unserialize_callback_func', null); + $var = unserialize('O:8:"Foo\Buzz":0:{}'); + ini_set('unserialize_callback_func', $unserializeCallbackHandler); + + $this->assertDumpMatchesFormat( + << 'bar'], + 0, + << "\e[1;38;5;113mbar\e[0;38;5;208m"\e[m +\e[0;38;5;208m]\e[m + +EOTXT + ]; + + yield [[], AbstractDumper::DUMP_LIGHT_ARRAY, "\e[0;38;5;208m[]\e[m\n"]; + + yield [ + ['foo' => 'bar'], + AbstractDumper::DUMP_LIGHT_ARRAY, + << "\e[1;38;5;113mbar\e[0;38;5;208m"\e[m +\e[0;38;5;208m]\e[m + +EOTXT + ]; + + yield [[], 0, "\e[0;38;5;208m[]\e[m\n"]; + } + + /** + * @dataProvider provideDumpArrayWithColor + */ + public function testDumpArrayWithColor($value, $flags, $expectedOut) + { + if ('\\' === \DIRECTORY_SEPARATOR) { + $this->markTestSkipped('Windows console does not support coloration'); + } + + $out = ''; + $dumper = new CliDumper(function ($line, $depth) use (&$out) { + if ($depth >= 0) { + $out .= str_repeat(' ', $depth).$line."\n"; + } + }, null, $flags); + $dumper->setColors(true); + $cloner = new VarCloner(); + $dumper->dump($cloner->cloneVar($value)); + + $this->assertSame($expectedOut, $out); + } + + private function getSpecialVars() + { + foreach (array_keys($GLOBALS) as $var) { + if ('GLOBALS' !== $var) { + unset($GLOBALS[$var]); + } + } + + $var = function &() { + $var = []; + $var[] = &$var; + + return $var; + }; + + return eval('return [$var(), $GLOBALS, &$GLOBALS];'); + } +} diff --git a/vendor/symfony/var-dumper/Tests/Dumper/ContextProvider/RequestContextProviderTest.php b/vendor/symfony/var-dumper/Tests/Dumper/ContextProvider/RequestContextProviderTest.php new file mode 100644 index 0000000000..5c1415951f --- /dev/null +++ b/vendor/symfony/var-dumper/Tests/Dumper/ContextProvider/RequestContextProviderTest.php @@ -0,0 +1,49 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Dumper\ContextProvider; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\VarDumper\Cloner\Data; +use Symfony\Component\VarDumper\Dumper\ContextProvider\RequestContextProvider; + +/** + * @requires function \Symfony\Component\HttpFoundation\RequestStack::__construct + */ +class RequestContextProviderTest extends TestCase +{ + public function testGetContextOnNullRequest() + { + $requestStack = new RequestStack(); + $provider = new RequestContextProvider($requestStack); + + $this->assertNull($provider->getContext()); + } + + public function testGetContextOnRequest() + { + $request = Request::create('https://example.org/', 'POST'); + $request->attributes->set('_controller', 'MyControllerClass'); + + $requestStack = new RequestStack(); + $requestStack->push($request); + + $context = (new RequestContextProvider($requestStack))->getContext(); + $this->assertSame('https://example.org/', $context['uri']); + $this->assertSame('POST', $context['method']); + $this->assertInstanceOf(Data::class, $context['controller']); + $this->assertSame('MyControllerClass', $context['controller']->getValue()); + $this->assertSame('https://example.org/', $context['uri']); + $this->assertArrayHasKey('identifier', $context); + } +} diff --git a/vendor/symfony/var-dumper/Tests/Dumper/ContextualizedDumperTest.php b/vendor/symfony/var-dumper/Tests/Dumper/ContextualizedDumperTest.php new file mode 100644 index 0000000000..ba717940bc --- /dev/null +++ b/vendor/symfony/var-dumper/Tests/Dumper/ContextualizedDumperTest.php @@ -0,0 +1,43 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Tests\Dumper; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\VarDumper\Cloner\VarCloner; +use Symfony\Component\VarDumper\Dumper\CliDumper; +use Symfony\Component\VarDumper\Dumper\ContextProvider\SourceContextProvider; +use Symfony\Component\VarDumper\Dumper\ContextualizedDumper; + +/** + * @author Kévin Thérage + */ +class ContextualizedDumperTest extends TestCase +{ + public function testContextualizedCliDumper() + { + $wrappedDumper = new CliDumper('php://output'); + $wrappedDumper->setColors(true); + + $var = 'example'; + $href = sprintf('file://%s#L%s', __FILE__, 37); + $dumper = new ContextualizedDumper($wrappedDumper, [new SourceContextProvider()]); + $cloner = new VarCloner(); + $data = $cloner->cloneVar($var); + + ob_start(); + $dumper->dump($data); + $out = ob_get_clean(); + + $this->assertStringContainsString("\e]8;;{$href}\e\\\e[", $out); + $this->assertStringContainsString("m{$var}\e[", $out); + } +} diff --git a/vendor/symfony/var-dumper/Tests/Dumper/FunctionsTest.php b/vendor/symfony/var-dumper/Tests/Dumper/FunctionsTest.php new file mode 100644 index 0000000000..7444d4bf58 --- /dev/null +++ b/vendor/symfony/var-dumper/Tests/Dumper/FunctionsTest.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Tests\Dumper; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\VarDumper\Cloner\VarCloner; +use Symfony\Component\VarDumper\Dumper\CliDumper; +use Symfony\Component\VarDumper\VarDumper; + +class FunctionsTest extends TestCase +{ + public function testDumpReturnsFirstArg() + { + $this->setupVarDumper(); + + $var1 = 'a'; + + ob_start(); + $return = dump($var1); + ob_end_clean(); + + $this->assertEquals($var1, $return); + } + + public function testDumpReturnsAllArgsInArray() + { + $this->setupVarDumper(); + + $var1 = 'a'; + $var2 = 'b'; + $var3 = 'c'; + + ob_start(); + $return = dump($var1, $var2, $var3); + ob_end_clean(); + + $this->assertEquals([$var1, $var2, $var3], $return); + } + + protected function setupVarDumper() + { + $cloner = new VarCloner(); + $dumper = new CliDumper('php://output'); + VarDumper::setHandler(function ($var) use ($cloner, $dumper) { + $dumper->dump($cloner->cloneVar($var)); + }); + } +} diff --git a/vendor/symfony/var-dumper/Tests/Dumper/HtmlDumperTest.php b/vendor/symfony/var-dumper/Tests/Dumper/HtmlDumperTest.php new file mode 100644 index 0000000000..8c9592e47b --- /dev/null +++ b/vendor/symfony/var-dumper/Tests/Dumper/HtmlDumperTest.php @@ -0,0 +1,190 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Tests\Dumper; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\VarDumper\Caster\ImgStub; +use Symfony\Component\VarDumper\Cloner\VarCloner; +use Symfony\Component\VarDumper\Dumper\HtmlDumper; + +/** + * @author Nicolas Grekas + */ +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(''); + $dumper->setDumpBoundaries('', ''); + $cloner = new VarCloner(); + $cloner->addCasters([ + ':stream' => function ($res, $a) { + unset($a['uri'], $a['wrapper_data']); + + return $a; + }, + ]); + $data = $cloner->cloneVar($var); + + ob_start(); + $dumper->dump($data); + $out = ob_get_clean(); + $out = preg_replace('/[ \t]+$/m', '', $out); + $var['file'] = htmlspecialchars($var['file'], \ENT_QUOTES, 'UTF-8'); + $intMax = \PHP_INT_MAX; + preg_match('/sf-dump-\d+/', $out, $dumpId); + $dumpId = $dumpId[0]; + $res = (int) $var['res']; + + $this->assertStringMatchesFormat( + <<
array:24 [ + "number" => 1 + 0 => &1 null + "const" => 1.1 + 1 => true + 2 => false + 3 => NAN + 4 => INF + 5 => -INF + 6 => {$intMax} + "str" => "d&%s;j&%s;\\n" + 7 => b""" + é\\x01test\\t\\n + ing + """ + "[]" => [] + "res" => stream resource @{$res} +%A wrapper_type: "plainfile" + stream_type: "STDIO" + mode: "r" + unread_bytes: 0 + seekable: true +%A options: [] + } + "obj" => Symfony\Component\VarDumper\Tests\Fixture\DumbFoo {#%d + +foo: "foo" + +"bar": "bar" + } + "closure" => Closure(\$a, PDO &\$b = null) {#%d + class: "Symfony\Component\VarDumper\Tests\Dumper\HtmlDumperTest" + this: Symfony\Component\VarDumper\Tests\Dumper\HtmlDumperTest {#%d &%s;} + file: "%s%eVarDumper%eTests%eFixtures%edumb-var.php" + line: "{$var['line']} to {$var['line']}" + } + "line" => {$var['line']} + "nobj" => array:1 [ + 0 => &3 {#%d} + ] + "recurs" => &4 array:1 [ + 0 => &4 array:1 [&4] + ] + 8 => &1 null + "sobj" => Symfony\Component\VarDumper\Tests\Fixture\DumbFoo {#%d} + "snobj" => &3 {#%d} + "snobj2" => {#%d} + "file" => "{$var['file']}" + b"bin-key-&%s;" => "" +] + + +EOTXT + , + + $out + ); + } + + public function testCharset() + { + $var = mb_convert_encoding('Словарь', 'CP1251', 'UTF-8'); + + $dumper = new HtmlDumper('php://output', 'CP1251'); + $dumper->setDumpHeader(''); + $dumper->setDumpBoundaries('', ''); + $cloner = new VarCloner(); + + $data = $cloner->cloneVar($var); + $out = $dumper->dump($data, true); + + $this->assertStringMatchesFormat( + <<<'EOTXT' +b"Словарь" + + +EOTXT + , + $out + ); + } + + public function testAppend() + { + $out = fopen('php://memory', 'r+'); + + $dumper = new HtmlDumper(); + $dumper->setDumpHeader(''); + $dumper->setDumpBoundaries('', ''); + $cloner = new VarCloner(); + + $dumper->dump($cloner->cloneVar(123), $out); + $dumper->dump($cloner->cloneVar(456), $out); + + $out = stream_get_contents($out, -1, 0); + + $this->assertSame(<<<'EOTXT' +123 + +456 + + +EOTXT + , + $out + ); + } + + /** + * @dataProvider varToDumpProvider + */ + public function testDumpString($var, $needle) + { + $dumper = new HtmlDumper(); + $cloner = new VarCloner(); + + ob_start(); + $dumper->dump($cloner->cloneVar($var)); + $out = ob_get_clean(); + + $this->assertStringContainsString($needle, $out); + } + + public static function varToDumpProvider() + { + return [ + [['dummy' => new ImgStub('dummy', 'img/png', '100em')], ''], + ['foo', 'foo'], + ]; + } +} diff --git a/vendor/symfony/var-dumper/Tests/Dumper/ServerDumperTest.php b/vendor/symfony/var-dumper/Tests/Dumper/ServerDumperTest.php new file mode 100644 index 0000000000..44036295ef --- /dev/null +++ b/vendor/symfony/var-dumper/Tests/Dumper/ServerDumperTest.php @@ -0,0 +1,98 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Tests\Dumper; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Process\PhpProcess; +use Symfony\Component\Process\Process; +use Symfony\Component\VarDumper\Cloner\VarCloner; +use Symfony\Component\VarDumper\Dumper\ContextProvider\ContextProviderInterface; +use Symfony\Component\VarDumper\Dumper\DataDumperInterface; +use Symfony\Component\VarDumper\Dumper\ServerDumper; + +class ServerDumperTest extends TestCase +{ + private const VAR_DUMPER_SERVER = 'tcp://127.0.0.1:9913'; + + public function testDumpForwardsToWrappedDumperWhenServerIsUnavailable() + { + $wrappedDumper = $this->createMock(DataDumperInterface::class); + + $dumper = new ServerDumper(self::VAR_DUMPER_SERVER, $wrappedDumper); + + $cloner = new VarCloner(); + $data = $cloner->cloneVar('foo'); + + $wrappedDumper->expects($this->once())->method('dump')->with($data); + + $dumper->dump($data); + } + + public function testDump() + { + if ('True' === getenv('APPVEYOR')) { + $this->markTestSkipped('Skip transient test on AppVeyor'); + } + + $wrappedDumper = $this->createMock(DataDumperInterface::class); + $wrappedDumper->expects($this->never())->method('dump'); // test wrapped dumper is not used + + $cloner = new VarCloner(); + $data = $cloner->cloneVar('foo'); + $dumper = new ServerDumper(self::VAR_DUMPER_SERVER, $wrappedDumper, [ + 'foo_provider' => new class() implements ContextProviderInterface { + public function getContext(): ?array + { + return ['foo']; + } + }, + ]); + + $dumped = null; + $process = $this->getServerProcess(); + $process->start(function ($type, $buffer) use ($process, &$dumped, $dumper, $data) { + if (Process::ERR === $type) { + $process->stop(); + $this->fail(); + } elseif ("READY\n" === $buffer) { + $dumper->dump($data); + } else { + $dumped .= $buffer; + } + }); + + $process->wait(); + + $this->assertTrue($process->isSuccessful()); + $this->assertStringMatchesFormat(<<<'DUMP' +(3) "foo" +[ + "timestamp" => %d.%d + "foo_provider" => [ + (3) "foo" + ] +] +%d +DUMP + , $dumped); + } + + private function getServerProcess(): Process + { + $process = new PhpProcess(file_get_contents(__DIR__.'/../Fixtures/dump_server.php'), null, [ + 'COMPONENT_ROOT' => __DIR__.'/../../', + 'VAR_DUMPER_SERVER' => self::VAR_DUMPER_SERVER, + ]); + + return $process->setTimeout('\\' === \DIRECTORY_SEPARATOR ? 19 : 9); + } +} diff --git a/vendor/symfony/var-dumper/Tests/Fixtures/BackedEnumFixture.php b/vendor/symfony/var-dumper/Tests/Fixtures/BackedEnumFixture.php new file mode 100644 index 0000000000..79c31431d0 --- /dev/null +++ b/vendor/symfony/var-dumper/Tests/Fixtures/BackedEnumFixture.php @@ -0,0 +1,10 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Tests\Fixtures; + +#[MyAttribute] +final class LotsOfAttributes +{ + #[RepeatableAttribute('one'), RepeatableAttribute('two')] + public const SOME_CONSTANT = 'some value'; + + #[MyAttribute('one', extra: 'hello')] + private string $someProperty; + + #[MyAttribute('two')] + public function someMethod( + #[MyAttribute('three')] string $someParameter + ): void { + } +} diff --git a/vendor/symfony/var-dumper/Tests/Fixtures/MyAttribute.php b/vendor/symfony/var-dumper/Tests/Fixtures/MyAttribute.php new file mode 100644 index 0000000000..e84ca0fd45 --- /dev/null +++ b/vendor/symfony/var-dumper/Tests/Fixtures/MyAttribute.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Tests\Fixtures; + +#[\Attribute] +final class MyAttribute +{ + public function __construct( + private string $foo = 'default', + private ?string $extra = null, + ) { + } + + public function getFoo(): string + { + return $this->foo; + } + + public function getExtra(): ?string + { + return $this->extra; + } +} diff --git a/vendor/symfony/var-dumper/Tests/Fixtures/NotLoadableClass.php b/vendor/symfony/var-dumper/Tests/Fixtures/NotLoadableClass.php new file mode 100644 index 0000000000..d8e0cb359f --- /dev/null +++ b/vendor/symfony/var-dumper/Tests/Fixtures/NotLoadableClass.php @@ -0,0 +1,7 @@ +p2 = new \stdClass(); + $this->p3 = &$this->p2; + } +} diff --git a/vendor/symfony/var-dumper/Tests/Fixtures/Php81Enums.php b/vendor/symfony/var-dumper/Tests/Fixtures/Php81Enums.php new file mode 100644 index 0000000000..ab5d03a981 --- /dev/null +++ b/vendor/symfony/var-dumper/Tests/Fixtures/Php81Enums.php @@ -0,0 +1,15 @@ +e1 = UnitEnumFixture::Hearts; + $this->e2 = BackedEnumFixture::Diamonds; + } +} diff --git a/vendor/symfony/var-dumper/Tests/Fixtures/ReflectionIntersectionTypeFixture.php b/vendor/symfony/var-dumper/Tests/Fixtures/ReflectionIntersectionTypeFixture.php new file mode 100644 index 0000000000..d9d8d21769 --- /dev/null +++ b/vendor/symfony/var-dumper/Tests/Fixtures/ReflectionIntersectionTypeFixture.php @@ -0,0 +1,8 @@ +createError(); + } +} + +/* foo.twig */ +class __TwigTemplate_VarDumperFixture_u75a09 extends AbstractTwigTemplate +{ + private $path; + + public function __construct(Twig\Environment $env = null, $path = null) + { + if (null !== $env) { + parent::__construct($env); + } + $this->parent = false; + $this->blocks = []; + $this->path = $path; + } + + protected function doDisplay(array $context, array $blocks = []) + { + // line 2 + throw new \Exception('Foobar'); + } + + public function getTemplateName() + { + return 'foo.twig'; + } + + public function getDebugInfo() + { + return [33 => 1, 34 => 2]; + } + + public function getSourceContext() + { + return new Twig\Source(" foo bar\n twig source\n\n", 'foo.twig', $this->path ?: __FILE__); + } +} diff --git a/vendor/symfony/var-dumper/Tests/Fixtures/UnitEnumFixture.php b/vendor/symfony/var-dumper/Tests/Fixtures/UnitEnumFixture.php new file mode 100644 index 0000000000..4a054b640f --- /dev/null +++ b/vendor/symfony/var-dumper/Tests/Fixtures/UnitEnumFixture.php @@ -0,0 +1,10 @@ +bar = 'bar'; + +$g = fopen(__FILE__, 'r'); + +$var = [ + 'number' => 1, null, + 'const' => 1.1, true, false, NAN, INF, -INF, PHP_INT_MAX, + 'str' => "déjà\n", "\xE9\x01test\t\ning", + '[]' => [], + 'res' => $g, + 'obj' => $foo, + 'closure' => function ($a, \PDO &$b = null) {}, + 'line' => __LINE__ - 1, + 'nobj' => [(object) []], +]; + +$r = []; +$r[] = &$r; + +$var['recurs'] = &$r; +$var[] = &$var[0]; +$var['sobj'] = $var['obj']; +$var['snobj'] = &$var['nobj'][0]; +$var['snobj2'] = $var['nobj'][0]; +$var['file'] = __FILE__; +$var["bin-key-\xE9"] = ''; + +unset($g, $r); diff --git a/vendor/symfony/var-dumper/Tests/Fixtures/dump_server.php b/vendor/symfony/var-dumper/Tests/Fixtures/dump_server.php new file mode 100644 index 0000000000..ed8bbfba58 --- /dev/null +++ b/vendor/symfony/var-dumper/Tests/Fixtures/dump_server.php @@ -0,0 +1,38 @@ +setMaxItems(-1); + +$dumper = new CliDumper(null, null, CliDumper::DUMP_LIGHT_ARRAY | CliDumper::DUMP_STRING_LENGTH); +$dumper->setColors(false); + +VarDumper::setHandler(function ($var) use ($cloner, $dumper) { + $data = $cloner->cloneVar($var)->withRefHandles(false); + $dumper->dump($data); +}); + +$server = new DumpServer(getenv('VAR_DUMPER_SERVER')); + +$server->start(); + +echo "READY\n"; + +$server->listen(function (Data $data, array $context, $clientId) { + dump((string) $data, $context, $clientId); + + exit(0); +}); diff --git a/vendor/symfony/var-dumper/Tests/Fixtures/xml_reader.xml b/vendor/symfony/var-dumper/Tests/Fixtures/xml_reader.xml new file mode 100644 index 0000000000..740c399fc4 --- /dev/null +++ b/vendor/symfony/var-dumper/Tests/Fixtures/xml_reader.xml @@ -0,0 +1,10 @@ + + + + + With text + + + + + diff --git a/vendor/symfony/var-dumper/Tests/Server/ConnectionTest.php b/vendor/symfony/var-dumper/Tests/Server/ConnectionTest.php new file mode 100644 index 0000000000..e15b8d6acf --- /dev/null +++ b/vendor/symfony/var-dumper/Tests/Server/ConnectionTest.php @@ -0,0 +1,91 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Tests\Server; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Process\PhpProcess; +use Symfony\Component\Process\Process; +use Symfony\Component\VarDumper\Cloner\VarCloner; +use Symfony\Component\VarDumper\Dumper\ContextProvider\ContextProviderInterface; +use Symfony\Component\VarDumper\Server\Connection; + +class ConnectionTest extends TestCase +{ + private const VAR_DUMPER_SERVER = 'tcp://127.0.0.1:9913'; + + public function testDump() + { + if ('True' === getenv('APPVEYOR')) { + $this->markTestSkipped('Skip transient test on AppVeyor'); + } + + $cloner = new VarCloner(); + $data = $cloner->cloneVar('foo'); + $connection = new Connection(self::VAR_DUMPER_SERVER, [ + 'foo_provider' => new class() implements ContextProviderInterface { + public function getContext(): ?array + { + return ['foo']; + } + }, + ]); + + $dumped = null; + $process = $this->getServerProcess(); + $process->start(function ($type, $buffer) use ($process, &$dumped, $connection, $data) { + if (Process::ERR === $type) { + $process->stop(); + $this->fail(); + } elseif ("READY\n" === $buffer) { + $connection->write($data); + } else { + $dumped .= $buffer; + } + }); + + $process->wait(); + + $this->assertTrue($process->isSuccessful()); + $this->assertStringMatchesFormat(<<<'DUMP' +(3) "foo" +[ + "timestamp" => %d.%d + "foo_provider" => [ + (3) "foo" + ] +] +%d + +DUMP + , $dumped); + } + + public function testNoServer() + { + $cloner = new VarCloner(); + $data = $cloner->cloneVar('foo'); + $connection = new Connection(self::VAR_DUMPER_SERVER); + $start = microtime(true); + $this->assertFalse($connection->write($data)); + $this->assertLessThan(4, microtime(true) - $start); + } + + private function getServerProcess(): Process + { + $process = new PhpProcess(file_get_contents(__DIR__.'/../Fixtures/dump_server.php'), null, [ + 'COMPONENT_ROOT' => __DIR__.'/../../', + 'VAR_DUMPER_SERVER' => self::VAR_DUMPER_SERVER, + ]); + + return $process->setTimeout(9); + } +} diff --git a/vendor/symfony/var-dumper/Tests/Test/VarDumperTestTraitTest.php b/vendor/symfony/var-dumper/Tests/Test/VarDumperTestTraitTest.php new file mode 100644 index 0000000000..0c982bdc74 --- /dev/null +++ b/vendor/symfony/var-dumper/Tests/Test/VarDumperTestTraitTest.php @@ -0,0 +1,78 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Tests\Test; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\VarDumper\Cloner\Stub; +use Symfony\Component\VarDumper\Dumper\CliDumper; +use Symfony\Component\VarDumper\Test\VarDumperTestTrait; + +class VarDumperTestTraitTest extends TestCase +{ + use VarDumperTestTrait; + + public function testItComparesLargeData() + { + $howMany = 700; + $data = array_fill_keys(range(0, $howMany), ['a', 'b', 'c', 'd']); + + $expected = sprintf("array:%d [\n", $howMany + 1); + for ($i = 0; $i <= $howMany; ++$i) { + $expected .= << array:4 [ + 0 => "a" + 1 => "b" + 2 => "c" + 3 => "d" + ]\n +EODUMP; + } + $expected .= "]\n"; + + $this->assertDumpEquals($expected, $data); + } + + public function testAllowsNonScalarExpectation() + { + $this->assertDumpEquals(new \ArrayObject(['bim' => 'bam']), new \ArrayObject(['bim' => 'bam'])); + } + + public function testItCanBeConfigured() + { + $this->setUpVarDumper($casters = [ + \DateTimeInterface::class => static function (\DateTimeInterface $date, array $a, Stub $stub): array { + $stub->class = 'DateTime'; + + return ['date' => $date->format('d/m/Y')]; + }, + ], CliDumper::DUMP_LIGHT_ARRAY | CliDumper::DUMP_COMMA_SEPARATOR); + + $this->assertSame(CliDumper::DUMP_LIGHT_ARRAY | CliDumper::DUMP_COMMA_SEPARATOR, $this->varDumperConfig['flags']); + $this->assertSame($casters, $this->varDumperConfig['casters']); + + $this->assertDumpEquals(<<tearDownVarDumper(); + + $this->assertNull($this->varDumperConfig['flags']); + $this->assertSame([], $this->varDumperConfig['casters']); + } +} diff --git a/vendor/symfony/var-dumper/phpunit.xml.dist b/vendor/symfony/var-dumper/phpunit.xml.dist new file mode 100644 index 0000000000..3243fcd027 --- /dev/null +++ b/vendor/symfony/var-dumper/phpunit.xml.dist @@ -0,0 +1,33 @@ + + + + + + + + + + + + ./Tests/ + + + + + + ./ + + ./Resources + ./Tests + ./vendor + + + +