From e4a83ccfe904fa0f7ac2087ce9a923cbcc88fc22 Mon Sep 17 00:00:00 2001
From: Aaron Bauman <260-aaronbauman@users.noreply.drupalcode.org>
Date: Mon, 27 Mar 2023 17:42:11 +0000
Subject: [PATCH] Issue #3320557 by AaronBauman, Dmitriy.trt: D10 Readiness

---
 composer.json                                 |   8 +-
 .../salesforce_address.info.yml               |   2 +-
 .../salesforce_example.info.yml               |   2 +-
 modules/salesforce_jwt/composer.json          |   2 +-
 .../salesforce_jwt/salesforce_jwt.info.yml    |   4 +-
 .../salesforce_logger.info.yml                |   2 +-
 modules/salesforce_mapping/composer.json      |   7 +-
 .../salesforce_mapping.drush.inc              | 323 ------------
 .../salesforce_mapping.info.yml               |   4 +-
 .../PropertiesExtended.php                    |   6 +
 .../RelatedProperties.php                     |   6 +
 .../salesforce_mapping_ui.info.yml            |   2 +-
 .../salesforce_oauth.info.yml                 |   2 +-
 modules/salesforce_pull/composer.json         |   5 +-
 .../salesforce_pull/salesforce_pull.drush.inc | 315 -----------
 .../salesforce_pull/salesforce_pull.info.yml  |   2 +-
 .../tests/src/Functional/PullQueueTest.php    |  14 +-
 .../tests/src/Unit/DeleteHandlerTest.php      |   2 +-
 .../tests/src/Unit/QueueHandlerTest.php       |   2 +-
 modules/salesforce_push/composer.json         |   5 +-
 .../salesforce_push/salesforce_push.drush.inc | 108 ----
 .../salesforce_push/salesforce_push.info.yml  |   2 +-
 .../tests/src/Unit/PushQueueTest.php          |   2 +-
 .../SalesforcePushQueueProcessorRestTest.php  |   8 +-
 modules/salesforce_soap/composer.json         |   2 +-
 .../salesforce_soap/salesforce_soap.info.yml  |   2 +-
 modules/salesforce_webform/composer.json      |   5 +-
 .../salesforce_webform.info.yml               |   4 +-
 salesforce.drush.inc                          | 492 ------------------
 salesforce.info.yml                           |   2 +-
 src/Tests/TestHttpClient.php                  |   3 +-
 src/Tests/TestHttpClientWrapper.php           |  14 +-
 .../salesforce_test_rest_client.info.yml      |   2 +-
 ...alesforceTestRestClientServiceProvider.php |   4 +-
 34 files changed, 79 insertions(+), 1286 deletions(-)
 delete mode 100644 modules/salesforce_mapping/salesforce_mapping.drush.inc
 delete mode 100644 modules/salesforce_pull/salesforce_pull.drush.inc
 delete mode 100644 modules/salesforce_push/salesforce_push.drush.inc
 delete mode 100644 salesforce.drush.inc

diff --git a/composer.json b/composer.json
index 3c349755..7acaed47 100644
--- a/composer.json
+++ b/composer.json
@@ -27,7 +27,7 @@
   "extra": {
     "drush": {
       "services": {
-        "drush.services.yml": "^9"
+        "drush.services.yml": "^9 || ^10 || ^11"
       }
     }
   },
@@ -41,9 +41,9 @@
     "drupal/key": "^1.14",
     "firebase/php-jwt": "^5.0 || ^6.0",
     "lusitanian/oauth": "^0.8.11",
-    "drupal/dynamic_entity_reference": "^1.9 || ^2.0",
-    "drupal/typed_data": "^1.0-alpha5",
-    "messageagency/force.com-toolkit-for-php": "^1.0.1",
+    "drupal/dynamic_entity_reference": "^1.9 || ^2.0 || ^3 || ^4",
+    "drupal/typed_data": "^1.0-beta2",
+    "messageagency/force.com-toolkit-for-php": "^1.0.2",
     "ext-soap": "*"
   },
   "suggest": {
diff --git a/modules/salesforce_address/salesforce_address.info.yml b/modules/salesforce_address/salesforce_address.info.yml
index 89314300..2567ac9a 100644
--- a/modules/salesforce_address/salesforce_address.info.yml
+++ b/modules/salesforce_address/salesforce_address.info.yml
@@ -1,7 +1,7 @@
 name: Salesforce Address
 type: module
 description: A custom Address Field Widget for Salesforce compatibility.
-core_version_requirement: ^9.1
+core_version_requirement: ^9 || ^10
 package: Salesforce
 dependencies:
   - address:address
diff --git a/modules/salesforce_example/salesforce_example.info.yml b/modules/salesforce_example/salesforce_example.info.yml
index d021e892..3a0e260e 100644
--- a/modules/salesforce_example/salesforce_example.info.yml
+++ b/modules/salesforce_example/salesforce_example.info.yml
@@ -1,7 +1,7 @@
 name: Salesforce Example
 type: module
 description: Salesforce Examples
-core_version_requirement: ^9.1
+core_version_requirement: ^9 || ^10
 package: Salesforce
 dependencies:
   - salesforce:salesforce_push
diff --git a/modules/salesforce_jwt/composer.json b/modules/salesforce_jwt/composer.json
index 98144e31..456f7617 100644
--- a/modules/salesforce_jwt/composer.json
+++ b/modules/salesforce_jwt/composer.json
@@ -25,7 +25,7 @@
     "source": "http://cgit.drupalcode.org/salesforce"
   },
   "require": {
-    "drupal/key": "^1.14",
+    "drupal/key": "^1.14 || 1.x-dev",
     "firebase/php-jwt": "^5.0 || ^6.0",
     "lusitanian/oauth": "^0.8.11"
   }
diff --git a/modules/salesforce_jwt/salesforce_jwt.info.yml b/modules/salesforce_jwt/salesforce_jwt.info.yml
index c9b7dc15..56bf9287 100644
--- a/modules/salesforce_jwt/salesforce_jwt.info.yml
+++ b/modules/salesforce_jwt/salesforce_jwt.info.yml
@@ -1,9 +1,9 @@
 name: Salesforce JWT Auth Provider
 type: module
 description: Provides key-based Salesforce authentication.
-core_version_requirement: ^9.1
+core_version_requirement: ^9 || ^10
 package: Salesforce
 configure: salesforce.auth_config
 dependencies:
   - salesforce:salesforce
-  - key:key (>= 1.14)
+  - key:key
diff --git a/modules/salesforce_logger/salesforce_logger.info.yml b/modules/salesforce_logger/salesforce_logger.info.yml
index b86658df..ef85f72d 100644
--- a/modules/salesforce_logger/salesforce_logger.info.yml
+++ b/modules/salesforce_logger/salesforce_logger.info.yml
@@ -1,7 +1,7 @@
 name: Salesforce Logger
 type: module
 description: Consolidated logging for Salesforce Log events.
-core_version_requirement: ^9.1
+core_version_requirement: ^9 || ^10
 package: Salesforce
 configure: salesforce_logger.settings
 dependencies:
diff --git a/modules/salesforce_mapping/composer.json b/modules/salesforce_mapping/composer.json
index 8cdaac2c..8a47be29 100644
--- a/modules/salesforce_mapping/composer.json
+++ b/modules/salesforce_mapping/composer.json
@@ -9,14 +9,13 @@
     }
   ],
   "require": {
-    "php": ">=5.6.0",
-    "drupal/dynamic_entity_reference": "^1.9 || ^2.0",
-    "drupal/typed_data": "^1.0-alpha5"
+    "drupal/dynamic_entity_reference": "^1.9 || ^2.0 || ^3 || ^4",
+    "drupal/typed_data": "^1.0-beta2"
   },
   "extra": {
     "drush": {
       "services": {
-        "drush.services.yml": "^9"
+        "drush.services.yml": "^9 || ^10 || ^11"
       }
     }
   }
diff --git a/modules/salesforce_mapping/salesforce_mapping.drush.inc b/modules/salesforce_mapping/salesforce_mapping.drush.inc
deleted file mode 100644
index 46069360..00000000
--- a/modules/salesforce_mapping/salesforce_mapping.drush.inc
+++ /dev/null
@@ -1,323 +0,0 @@
-<?php
-
-/**
- * @file
- * Drush integration for Salesforce.
- */
-
-use Drupal\salesforce\SelectQuery;
-use Drupal\salesforce\Rest\RestClientInterface;
-use Drupal\Core\Entity\ContentEntityStorageInterface;
-
-/**
- * Implements hook_drush_command().
- */
-function salesforce_mapping_drush_command() {
-  $items['sf-prune-revisions'] = [
-    'description' => 'Delete old revisions of Mapped Objects, based on revision limit settings. Useful if you have recently changed settings, or if you have just updated to a version with prune support.',
-    'aliases' => ['sfprune'],
-  ];
-  $items['sf-purge-drupal'] = [
-    'description' => 'Clean up Mapped Objects table by deleting any records which reference missing Drupal entities.',
-    'aliases' => ['sfpd'],
-    'options' => [
-      'mapping' => 'Given a mapping id, limit purge to only those referenced.',
-    ],
-  ];
-  $items['sf-purge-salesforce'] = [
-    'description' => 'Clean up Mapped Objects table by deleting any records which reference missing Salesforce records.',
-    'aliases' => ['sfpsf'],
-    'options' => [
-      'mapping' => 'Given a mapping id, limit purge to only those referenced.',
-    ],
-  ];
-  $items['sf-purge-mapping'] = [
-    'description' => 'Clean up Mapped Objects table by deleting any records which reference missing Mappings.',
-    'aliases' => ['sfpmap'],
-    'options' => [
-      'mapping' => 'Given a mapping id, limit purge to only those referenced.',
-    ],
-  ];
-  $items['sf-purge-all'] = [
-    'description' => 'Clean up Mapped Objects table by deleting any records which reference missing Mappings, Entities, or Salesforce records.',
-    'aliases' => ['sfpall'],
-    'options' => [
-      'mapping' => 'Given a mapping id, limit purge to only those referenced.',
-    ],
-  ];
-  return $items;
-}
-
-/**
- * Support for drush 8 is deprecated and will be removed in a future release.
- */
-function drush_salesforce_mapping_sf_prune_revisions() {
-  _drush_salesforce_deprecated();
-
-  $limit =
-    \Drupal::service('config.factory')
-      ->get('salesforce.settings')
-      ->get('limit_mapped_object_revisions');
-  if ($limit <= 0) {
-    drush_log('Mapped Object revisions limit is 0. No action taken.', 'warning');
-    return;
-  }
-  $etm = \Drupal::service('entity_type.manager');
-  /** @var \Drupal\salesforce_mapping\MappedObjectStorage $storage */
-  $storage = $etm
-    ->getStorage('salesforce_mapped_object');
-  $revision_table = $etm
-    ->getDefinition('salesforce_mapped_object')
-    ->getRevisionTable();
-  $ids = \Drupal::service('database')
-    ->select($revision_table, 'r')
-    ->fields('r', ['id'])
-    ->having('COUNT(r.id) > ' . $limit)
-    ->groupBy('r.id')
-    ->execute()
-    ->fetchCol();
-  if (empty($ids)) {
-    drush_log("No Mapped Objects with more than $limit revision(s). No action taken.", 'warning');
-    return;
-  }
-  drush_log('Found ' . count($ids) . ' mapped objects with excessive revisions. Will prune to ' . $limit . ' revision(s) each. This may take a while.', 'ok');
-  $total = count($ids);
-  $i = 0;
-  $buckets = ceil($total / 20);
-  if ($buckets == 0) {
-    $buckets = 1;
-  }
-  foreach ($ids as $id) {
-    if ($i++ % $buckets == 0) {
-      drush_log("Pruned $i of $total records.", 'ok');
-    }
-    /** @var \Drupal\salesforce_mapping\Entity\MappedObject $mapped_object */
-    if ($mapped_object = $storage->load($id)) {
-      $mapped_object->pruneRevisions($storage);
-    }
-  }
-
-}
-
-/**
- * Support for drush 8 is deprecated and will be removed in a future release.
- */
-function drush_salesforce_mapping_sf_purge_drupal() {
-  _drush_salesforce_deprecated();
-  $etm = \Drupal::service('entity_type.manager');
-  $mapped_obj_storage = $etm->getStorage('salesforce_mapped_object');
-  $mapped_obj_table = $etm
-    ->getDefinition('salesforce_mapped_object')
-    ->getBaseTable();
-
-  $query = \Drupal::service('database')
-    ->select($mapped_obj_table, 'm')
-    ->fields('m', ['drupal_entity__target_type'])
-    ->distinct();
-  if ($mapping_id = drush_get_option('mapping')) {
-    $query->condition('salesforce_mapping', $mapping_id);
-  }
-  $entity_type_ids = $query
-    ->execute()
-    ->fetchCol();
-  if (empty($entity_type_ids)) {
-    drush_log('No orphaned mapped objects found by Drupal entities.', 'ok');
-    return;
-  }
-
-  foreach ($entity_type_ids as $et_id) {
-    $query = \Drupal::service('database')
-      ->select($mapped_obj_table, 'm')
-      ->fields('m', ['id'])
-      ->condition('drupal_entity__target_type', $et_id);
-
-    $entity_type = $etm->getDefinition($et_id);
-    if ($entity_type) {
-      $id_key = $entity_type->getKey('id');
-      $query->addJoin("LEFT", $entity_type->getBaseTable(), 'et', "et.$id_key = m.drupal_entity__target_id_int");
-      $query->isNull("et.$id_key");
-    }
-    $mapped_obj_ids = $query->execute()->fetchCol();
-    if (empty($mapped_obj_ids)) {
-      drush_log('No orphaned mapped objects found for ' . $et_id . '.', 'ok');
-      continue;
-    }
-    _drush_salesforce_mapping_confirm_and_delete($mapped_obj_ids, $mapped_obj_storage, 'entity type: ' . $et_id);
-  }
-}
-
-/**
- * Support for drush 8 is deprecated and will be removed in a future release.
- */
-function drush_salesforce_mapping_sf_purge_salesforce() {
-  _drush_salesforce_deprecated();
-  $etm = \Drupal::service('entity_type.manager');
-  $client = \Drupal::service('salesforce.client');
-  $object_types = _drush_salesforce_mapping_object_types_by_prefix($client);
-
-  $mapped_obj_storage = $etm->getStorage('salesforce_mapped_object');
-  $mapped_obj_table = $etm
-    ->getDefinition('salesforce_mapped_object')
-    ->getBaseTable();
-
-  $query = \Drupal::service('database')
-    ->select($mapped_obj_table, 'm');
-  $query->addExpression('distinct substr(salesforce_id, 1, 3)');
-  $mapping_id = FALSE;
-  if ($mapping_id = drush_get_option('mapping')) {
-    $query->condition('salesforce_mapping', $mapping_id);
-  }
-  $sfid_prefixes = $query
-    ->execute()
-    ->fetchCol();
-
-  foreach ($sfid_prefixes as $prefix) {
-    if (empty($object_types[$prefix]['name'])) {
-      $mapped_obj_ids = \Drupal::service('database')
-        ->select($mapped_obj_table, 'm')
-        ->fields('m', ['salesforce_id', 'id'])
-        ->condition('salesforce_id', $prefix . '%', 'LIKE')
-        ->execute()
-        ->fetchAllKeyed();
-      if (empty($mapped_obj_ids)) {
-        continue;
-      }
-      drush_log('Unknown object type for Salesforce ID prefix ' . $prefix);
-      _drush_salesforce_mapping_confirm_and_delete($mapped_obj_ids, $mapped_obj_storage, 'prefix ' . $prefix);
-      continue;
-    }
-    $query = \Drupal::service('database')
-      ->select($mapped_obj_table, 'm')
-      ->fields('m', ['salesforce_id', 'id']);
-    if ($mapping_id) {
-      $query->condition('salesforce_mapping', $mapping_id);
-    }
-    else {
-      $query->condition('salesforce_id', $prefix . '%', 'LIKE');
-    }
-    $sfids = $query
-      ->execute()
-      ->fetchAllKeyed();
-    $to_delete = $sfids;
-    // SOQL queries are limited to 4000-characters in where statements.
-    // Chunkify in case we have more than ~200 sfids.
-    foreach (array_chunk($sfids, 200, TRUE) as $chunk) {
-      $soql_query = new SelectQuery($object_types[$prefix]['name']);
-      $soql_query->fields[] = 'Id';
-      $soql_query->addCondition('Id', array_keys($chunk));
-      $results = $client->query($soql_query);
-      foreach ($results->records() as $record) {
-        unset($to_delete[(string) $record->id()]);
-      }
-    }
-    if (empty($to_delete)) {
-      drush_log('No orphaned mapped objects found for SObject type ' . $object_types[$prefix]['name'], 'ok');
-      continue;
-    }
-    _drush_salesforce_mapping_confirm_and_delete(array_values($to_delete), $mapped_obj_storage, 'SObject type *' . $object_types[$prefix]['name'] . '*');
-  }
-}
-
-/**
- * Support for drush 8 is deprecated and will be removed in a future release.
- */
-function drush_salesforce_mapping_sf_purge_mapping() {
-  _drush_salesforce_deprecated();
-  $etm = \Drupal::service('entity_type.manager');
-  $mapped_obj_storage = $etm->getStorage('salesforce_mapped_object');
-  $mapped_obj_table = $etm
-    ->getDefinition('salesforce_mapped_object')
-    ->getBaseTable();
-
-  $query = \Drupal::service('database')
-    ->select($mapped_obj_table, 'm')
-    ->fields('m', ['salesforce_mapping'])
-    ->distinct();
-  if ($mapping_id = drush_get_option('mapping')) {
-    $query->condition('salesforce_mapping', $mapping_id);
-  }
-  $mapping_ids = $query
-    ->execute()
-    ->fetchCol();
-  if (empty($entity_type_ids)) {
-    drush_log('No orphaned mapped objects found by mapping.', 'ok');
-    return;
-  }
-
-  foreach ($mapping_ids as $mapping_id) {
-    $mapping = $etm
-      ->getStorage('salesforce_mapping')
-      ->load($mapping_id);
-    // If mapping loads successsfully, we assume the mapped object is OK.
-    if ($mapping) {
-      continue;
-    }
-    $mapped_obj_ids = \Drupal::service('database')
-      ->select($mapped_obj_table, 'm')
-      ->fields('m', ['id'])
-      ->condition('salesforce_mapping', $mapping_id)
-      ->distinct()
-      ->execute()
-      ->fetchCol();
-
-    _drush_salesforce_mapping_confirm_and_delete($mapped_obj_ids, $mapped_obj_storage, 'missing mapping: ' . $mapping_id);
-  }
-}
-
-/**
- * Support for drush 8 is deprecated and will be removed in a future release.
- */
-function drush_salesforce_mapping_sf_purge_all() {
-  _drush_salesforce_deprecated();
-  drush_salesforce_mapping_sf_purge_mapping();
-  drush_salesforce_mapping_sf_purge_drupal();
-  drush_salesforce_mapping_sf_purge_salesforce();
-}
-
-/**
- * Helper to fetch object types by prefix.
- *
- * @param \Drupal\salesforce\Rest\RestClientInterface $client
- *   Client interface.
- *
- * @return array
- *   Array of objects, indexed by key prefix.
- */
-function _drush_salesforce_mapping_object_types_by_prefix(RestClientInterface $client) {
-  $ret = [];
-  $describe = $client->objects();
-  foreach ($describe as $object) {
-    $ret[$object['keyPrefix']] = $object;
-  }
-  return $ret;
-}
-
-/**
- * Helper to interactively confirm delete.
- *
- * @param array $object_ids
- *   Records to be deleted.
- * @param \Drupal\Core\Entity\ContentEntityStorageInterface $storage
- *   Storage.
- * @param string $extra
- *   Extra message parts.
- *
- * @throws \Drupal\Core\Entity\EntityStorageException
- */
-function _drush_salesforce_mapping_confirm_and_delete(array $object_ids, ContentEntityStorageInterface $storage, $extra = '') {
-  if (empty($object_ids)) {
-    return;
-  }
-  $message = 'Delete ' . count($object_ids) . ' orphaned mapped objects';
-  if ($extra) {
-    $message .= ' for ' . $extra;
-  }
-  $message .= '?';
-  if (!drush_confirm($message)) {
-    return;
-  }
-
-  // Still have to *load* entities in order to delete them. **UGH**.
-  $mapped_objs = $storage->loadMultiple($object_ids);
-  $storage->delete($mapped_objs);
-}
diff --git a/modules/salesforce_mapping/salesforce_mapping.info.yml b/modules/salesforce_mapping/salesforce_mapping.info.yml
index ffe53f7a..13fc08b5 100644
--- a/modules/salesforce_mapping/salesforce_mapping.info.yml
+++ b/modules/salesforce_mapping/salesforce_mapping.info.yml
@@ -2,8 +2,8 @@ name: Salesforce Mapping
 type: module
 description: Map Drupal entities to Salesforce objects.
 package: Salesforce
-core_version_requirement: ^9.1
+core_version_requirement: ^9 || ^10
 dependencies:
   - salesforce:salesforce
   - dynamic_entity_reference:dynamic_entity_reference
-  - typed_data:typed_data (>= 1.0-alpha5)
+  - typed_data:typed_data
diff --git a/modules/salesforce_mapping/src/Plugin/SalesforceMappingField/PropertiesExtended.php b/modules/salesforce_mapping/src/Plugin/SalesforceMappingField/PropertiesExtended.php
index e5ddb815..ef8fe535 100644
--- a/modules/salesforce_mapping/src/Plugin/SalesforceMappingField/PropertiesExtended.php
+++ b/modules/salesforce_mapping/src/Plugin/SalesforceMappingField/PropertiesExtended.php
@@ -25,7 +25,13 @@ class PropertiesExtended extends PropertiesBase {
    */
   public function getPluginDefinition() {
     $definition = parent::getPluginDefinition();
+
     $field_name = $this->config('drupal_field_value');
+    if ($field_name === NULL) {
+      // No need to load the field if the plugin isn't configured.
+      return $definition;
+    }
+
     if (strpos($field_name, '.')) {
       list($field_name, $dummy) = explode('.', $field_name, 2);
     }
diff --git a/modules/salesforce_mapping/src/Plugin/SalesforceMappingField/RelatedProperties.php b/modules/salesforce_mapping/src/Plugin/SalesforceMappingField/RelatedProperties.php
index 7538745d..82fed025 100644
--- a/modules/salesforce_mapping/src/Plugin/SalesforceMappingField/RelatedProperties.php
+++ b/modules/salesforce_mapping/src/Plugin/SalesforceMappingField/RelatedProperties.php
@@ -101,7 +101,13 @@ class RelatedProperties extends SalesforceMappingFieldPluginBase {
   public function getPluginDefinition() {
     $definition = parent::getPluginDefinition();
     $definition['config_dependencies']['config'] = [];
+
     $field_name = $this->config('drupal_field_value');
+    if ($field_name === NULL) {
+      // No need to load the field if the plugin isn't configured.
+      return $definition;
+    }
+
     if (strpos($field_name, ':')) {
       [$field_name, $dummy] = explode(':', $field_name, 2);
     }
diff --git a/modules/salesforce_mapping_ui/salesforce_mapping_ui.info.yml b/modules/salesforce_mapping_ui/salesforce_mapping_ui.info.yml
index 2f9dfe4a..57e038f7 100644
--- a/modules/salesforce_mapping_ui/salesforce_mapping_ui.info.yml
+++ b/modules/salesforce_mapping_ui/salesforce_mapping_ui.info.yml
@@ -2,7 +2,7 @@ name: Salesforce Mapping UI
 type: module
 description: User interface for managing Salesforce mappings.
 package: Salesforce
-core_version_requirement: ^9.1
+core_version_requirement: ^9 || ^10
 configure: entity.salesforce_mapping.list
 dependencies:
   - salesforce:salesforce_mapping
diff --git a/modules/salesforce_oauth/salesforce_oauth.info.yml b/modules/salesforce_oauth/salesforce_oauth.info.yml
index a8c15b9a..aa589a0f 100644
--- a/modules/salesforce_oauth/salesforce_oauth.info.yml
+++ b/modules/salesforce_oauth/salesforce_oauth.info.yml
@@ -1,7 +1,7 @@
 name: Salesforce OAuth user-agent Provider
 type: module
 description: Provides user-agent-based Salesforce OAuth authentication.
-core_version_requirement: ^9.1
+core_version_requirement: ^9 || ^10
 package: Salesforce
 configure: salesforce.auth_config
 dependencies:
diff --git a/modules/salesforce_pull/composer.json b/modules/salesforce_pull/composer.json
index 573c6a5b..e5f00555 100644
--- a/modules/salesforce_pull/composer.json
+++ b/modules/salesforce_pull/composer.json
@@ -8,13 +8,10 @@
             "email": "aaron@messageagency.com"
         }
     ],
-    "require": {
-        "php": ">=5.6.0"
-    },
     "extra": {
         "drush": {
             "services": {
-                "drush.services.yml": "^9"
+                "drush.services.yml": "^9 || ^10 || ^11"
             }
         }
     }
diff --git a/modules/salesforce_pull/salesforce_pull.drush.inc b/modules/salesforce_pull/salesforce_pull.drush.inc
deleted file mode 100644
index 067e49b0..00000000
--- a/modules/salesforce_pull/salesforce_pull.drush.inc
+++ /dev/null
@@ -1,315 +0,0 @@
-<?php
-
-/**
- * @file
- * Salesforce Pull drush 8 commands.
- */
-
-use Drupal\salesforce\SFID;
-use Drupal\salesforce\Event\SalesforceEvents;
-use Drupal\salesforce_mapping\Event\SalesforceQueryEvent;
-
-/**
- * Implements hook_drush_command().
- */
-function salesforce_pull_drush_command() {
-
-  $items['sf-pull-query'] = [
-    'category' => 'salesforce',
-    'description' => 'Given a mapping, enqueue records for pull from Salesforce, ignoring modification timestamp. This command is useful, for example, when seeding content for a Drupal site prior to deployment.',
-    'aliases' => ['sfpq', 'sfiq'],
-    'arguments' => [
-      'name' => 'Machine name of the Salesforce Mapping for which to queue pull records.',
-    ],
-    'options' => [
-      'where' => [
-        'description' => 'A WHERE clause to add to the SOQL pull query. Default behavior is to query and pull all records.',
-      ],
-      'start' => 'strtotime()able string for the start timeframe over which to pull, e.g. "-5 hours". If omitted, use the value given by the mapping\'s pull timestamp. Must be in the past.',
-      'stop' => 'strtotime()able string for the end timeframe over which to pull, e.g. "-5 hours". If omitted, defaults to "now". Must be "now" or earlier',
-      'force-pull' => 'if given, force all queried records to be pulled regardless of updated timestamps. If omitted, only Salesforce records which are newer than linked Drupal records will be pulled.',
-    ],
-    'examples' => [
-      'drush sfpq user' => 'Query and queue all records for "user" Salesforce mapping.',
-      'drush sfpq user --where="Email like \'%foo%\' AND (LastName = \'bar\' OR FirstName = \'bar\')"' => 'Query and queue all records for "user" Salesforce mapping with Email field containing the string "foo" and First or Last name equal to "bar"',
-      'drush sfpq' => 'Fetch and process all pull queue items',
-      'drush sfpq --start="-25 minutes" --stop="-5 minutes"' => 'Fetch updated records for all mappings between 25 minutes and 5 minutes old, and process them.',
-      'drush sfpq foo --start="-25 minutes" --stop="-5 minutes"' => 'Fetch updated records for mapping "foo" between 25 minutes and 5 minutes old, and process them.',
-    ],
-  ];
-
-  $items['sf-pull-file'] = [
-    'category' => 'salesforce',
-    'description' => 'Given a mapping, enqueue a list of object IDs to be pulled from a CSV file, e.g. a Salesforce report. The first column of the CSV file must be SFIDs. Additional columns will be ignored.',
-    'aliases' => ['sfpf', 'sfif'],
-    'arguments' => [
-      'file' => 'CSV file name of 15- or 18-character Salesforce ids to be pulled. ',
-      'name' => 'Machine name of the Salesforce Mapping for which to queue pull records.',
-    ],
-  ];
-
-  $items['sf-pull-reset'] = [
-    'category' => 'salesforce',
-    'description' => 'Reset pull timestamps for one or all Salesforce Mappings, and set all mapped objects to be force-pulled.',
-    'arguments' => [
-      'name' => [
-        'description' => 'Machine name of the Salesforce Mapping for which to reset pull timestamps.',
-      ],
-    ],
-    'options' => [
-      'delete' => 'Reset delete date timestamp (instead of pull date timestamp)',
-    ],
-    'examples' => [
-      'drush sf-pull-reset' => 'Reset pull timestamps for all mappings.',
-      'drush sf-pull-reset foo' => 'Reset pull timestamps for mapping "foo"',
-      'drush sf-pull-reset --delete' => 'Reset "delete" timestamps for all mappings',
-      'drush sf-pull-reset foo --delete' => 'Reset "delete" timestamp for mapping "foo"',
-    ],
-  ];
-
-  $items['sf-pull-set'] = [
-    'category' => 'salesforce',
-    'description' => 'Set pull timestamp on a single Salesforce Mappings to a specific point in history (or now).',
-    'arguments' => [
-      'name' => [
-        'description' => 'Machine name of the Salesforce Mapping for which to reset pull timestamps.',
-      ],
-      'time' => [
-        'description' => 'Timestamp to set the value to. Defaults to the runtime.',
-      ],
-    ],
-    'examples' => [
-      'drush sf-pull-set foo' => 'Set pull timestamps for mapping "foo" to "now"',
-      'drush sf-pull-set foo 1517416761' => 'Set pull timestamps for mapping "foo" to Jan 31, 2018, around 8:40am time in Portland, OR',
-    ],
-  ];
-
-  return $items;
-}
-
-/**
- * Queues records for pull from salesforce for the given mapping.
- *
- * @param string $name
- *   Mapping name.
- */
-function drush_salesforce_pull_sf_pull_query($name) {
-  _drush_salesforce_deprecated();
-  if (!($mapping = _salesforce_drush_get_mapping($name))) {
-    return;
-  }
-
-  if ($start = drush_get_option('start')) {
-    $start = strtotime($start);
-  }
-  else {
-    $start = 0;
-  }
-
-  if ($stop = drush_get_option('stop')) {
-    $stop = strtotime($stop);
-  }
-  else {
-    $stop = 0;
-  }
-
-  $where = drush_get_option('where');
-
-  if (!($soql = $mapping->getPullQuery([], $start, $stop))) {
-    drush_log(dt('!mapping: Unable to generate pull query. Does this mapping have any Salesforce Action Triggers enabled?', ['!mapping' => $mapping->id()]), 'error');
-    return;
-  }
-
-  if ($where) {
-    $soql->conditions[] = [$where];
-  }
-
-  \Drupal::service('event_dispatcher')->dispatch(
-    new SalesforceQueryEvent($mapping, $soql),
-    SalesforceEvents::PULL_QUERY
-  );
-
-  drush_log(dt('!mapping: Issuing pull query: !query', ['!query' => (string) $soql, '!mapping' => $mapping->id()]), 'notice');
-  $results = \Drupal::service('salesforce.client')->query($soql);
-
-  if (empty($results)) {
-    drush_log(dt('!mapping: No records found to pull.', ['!mapping' => $mapping->id()]), 'warning');
-    return;
-  }
-
-  $force_pull = drush_get_option('force-pull') ? TRUE : FALSE;
-
-  \Drupal::service('salesforce_pull.queue_handler')
-    ->enqueueAllResults($mapping, $results, $force_pull);
-  drush_log(dt('!mapping: Queued !count items for pull.', ['!count' => $results->size(), '!mapping' => $mapping->id()]), 'success');
-
-}
-
-/**
- * Queues records for pull from Salesforce from the given file and mapping.
- *
- * @param string $file
- *   File name with IDs.
- * @param string $name
- *   Mapping name.
- */
-function drush_salesforce_pull_sf_pull_file($file, $name = NULL) {
-  _drush_salesforce_deprecated();
-  if (empty($file)) {
-    drush_log("File argument is required.", 'error');
-    drush_log("usage:\n  drush sf-pull-file file_name [mapping_id]", 'error');
-    return;
-  }
-  if (!file_exists($file)) {
-    drush_log('File not found.', 'error');
-    return;
-  }
-
-  if (!($mapping = _salesforce_drush_get_mapping($name))) {
-    return;
-  }
-
-  // Fetch the base query to make sure we can pull using this mapping.
-  $soql = $mapping->getPullQuery([], 1, 0);
-  if (empty($soql)) {
-    drush_log(dt('Unable to generate pull query for !name. Does this mapping have any Salesforce Action Triggers enabled?'), 'error');
-    return;
-  }
-
-  $sf = \Drupal::service('salesforce.client');
-
-  $rows = array_map('str_getcsv', file($file));
-
-  // Track IDs to avoid duplicates.
-  $seen = [];
-
-  // Max length for SOQL query is 20,000 characters. Chunk the IDs into smaller
-  // units to avoid this limit. 1000 IDs per query * 18 chars per ID = up to
-  // 18000 characters per query, plus up to 2000 for fields, where condition,
-  // etc.
-  $queries = [];
-  foreach (array_chunk($rows, 1000) as $i => $chunk) {
-    // Reset our base query:
-    $soql = $mapping->getPullQuery([], 1, 0);
-
-    // Now add all the IDs to it.
-    $sfids = [];
-    foreach ($chunk as $j => $row) {
-      if (empty($row) || empty($row[0])) {
-        drush_log(dt('Skipping row !n, no SFID found.', ['!n' => $j]), 'warning');
-        continue;
-      }
-      try {
-        $sfid = new SFID($row[0]);
-        // Sanity check to make sure the key-prefix is correct.
-        // If so, this is probably a good SFID.
-        // If not, it is definitely not a good SFID.
-        if ($mapping->getSalesforceObjectType() != $sf->getObjectTypeName($sfid)) {
-          throw new \Exception();
-        }
-      }
-      catch (\Exception $e) {
-        drush_log(dt('Skipping row !n, no SFID found.', ['!n' => $j]), 'warning');
-        continue;
-      }
-      $sfid = (string) $sfid;
-      if (empty($sfids[$sfid])) {
-        $sfids[] = $sfid;
-        $seen[$sfid] = $sfid;
-      }
-    }
-    $soql->addCondition('Id', $sfids, 'IN');
-    $queries[] = $soql;
-  }
-  if (empty($seen)) {
-    drush_log(dt('No SFIDs found in the given file.'), 'error');
-    return;
-  }
-  if (!drush_confirm(dt('Ready to enqueue !count records for pull?', ['!count' => count($seen)]))) {
-    return;
-  }
-
-  foreach ($queries as $soql) {
-    \Drupal::service('event_dispatcher')->dispatch(
-      new SalesforceQueryEvent($mapping, $soql),
-      SalesforceEvents::PULL_QUERY
-    );
-
-    drush_log(dt('Issuing pull query: !query', ['!query' => (string) $soql]));
-
-    $results = \Drupal::service('salesforce.client')->query($soql);
-
-    if (empty($results)) {
-      drush_lo('No records found to pull.');
-      continue;
-    }
-
-    \Drupal::service('salesforce_pull.queue_handler')
-      ->enqueueAllResults($mapping, $results);
-    drush_print(dt('Queued !count items for pull.', ['!count' => $results->size()]));
-  }
-
-}
-
-/**
- * Get an array of all pull mappings, or the given mapping by name.
- *
- * @param string $name
- *   Mapping name.
- *
- * @return \Drupal\salesforce_mapping\Entity\SalesforceMappingInterface[]
- *   Mappings.
- *
- * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
- * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
- */
-function _salesforce_pull_load_single_mapping_array_or_all_pull_mappings($name = NULL) {
-  if ($name != NULL) {
-    $mapping = _salesforce_drush_get_mapping($name);
-    if ($mapping && !$mapping->doesPull()) {
-      return [];
-    }
-    if ($mapping) {
-      return [$mapping];
-    }
-    return [];
-  }
-  else {
-    return \Drupal::entityTypeManager()
-      ->getStorage('salesforce_mapping')
-      ->loadPullMappings();
-  }
-}
-
-/**
- * Reset pull time on all mappings, or the given mapping by name.
- */
-function drush_salesforce_pull_sf_pull_reset($name = NULL) {
-  $mappings = _salesforce_pull_load_single_mapping_array_or_all_pull_mappings($name);
-  if (empty($mappings)) {
-    return;
-  }
-  foreach ($mappings as $mapping) {
-    $mapping->setLastPullTime(NULL);
-    \Drupal::entityTypeManager()
-      ->getStorage('salesforce_mapped_object')
-      ->setForcePull($mapping);
-  }
-}
-
-/**
- * Set pull time on all mappings, or the given mapping by name.
- */
-function drush_salesforce_pull_sf_pull_set($name, $time = NULL) {
-  _drush_salesforce_deprecated();
-  if (is_null($time)) {
-    $time = time();
-  }
-  $mapping = _salesforce_drush_get_mapping($name);
-  if ($mapping) {
-    $mapping->setLastPullTime($time);
-    \Drupal::entityTypeManager()
-      ->getStorage('salesforce_mapped_object')
-      ->setForcePull($mapping);
-  }
-}
diff --git a/modules/salesforce_pull/salesforce_pull.info.yml b/modules/salesforce_pull/salesforce_pull.info.yml
index 5b497123..c93ae4ea 100644
--- a/modules/salesforce_pull/salesforce_pull.info.yml
+++ b/modules/salesforce_pull/salesforce_pull.info.yml
@@ -2,6 +2,6 @@ name: Salesforce Pull
 type: module
 description: Imports objects from Salesforce based on mappings defined in Salesforce Mapping.
 package: Salesforce
-core_version_requirement: ^9.1
+core_version_requirement: ^9 || ^10
 dependencies:
   - salesforce:salesforce_mapping
diff --git a/modules/salesforce_pull/tests/src/Functional/PullQueueTest.php b/modules/salesforce_pull/tests/src/Functional/PullQueueTest.php
index a388627a..64901206 100644
--- a/modules/salesforce_pull/tests/src/Functional/PullQueueTest.php
+++ b/modules/salesforce_pull/tests/src/Functional/PullQueueTest.php
@@ -76,8 +76,18 @@ class PullQueueTest extends BrowserTestBase {
       $this->assertEquals('SALESFORCE TEST', $createdEntity->getTitle());
       $this->assertEquals($data->getSobject()
         ->field('Email'), $createdEntity->field_salesforce_test_email->value);
-      $this->assertEquals(date('Y-m-d', strtotime($data->getSobject()
-        ->field('Birthdate'))), date('Y-m-d', strtotime($createdEntity->field_salesforce_test_date->value)));
+
+      $birthdate = $data->getSobject()->field('Birthdate');
+      if ($birthdate === NULL) {
+        $this->assertNull($createdEntity->field_salesforce_test_date->value);
+      }
+      else {
+        $this->assertEquals(
+          date('Y-m-d', strtotime($birthdate)),
+          date('Y-m-d', strtotime($createdEntity->field_salesforce_test_date->value))
+        );
+      }
+
       $this->assertEquals((boolean) $data->getSobject()
         ->field('d5__Do_Not_Mail__c'), (boolean) $createdEntity->field_salesforce_test_bool->value);
       $this->assertEquals($data->getSobject()
diff --git a/modules/salesforce_pull/tests/src/Unit/DeleteHandlerTest.php b/modules/salesforce_pull/tests/src/Unit/DeleteHandlerTest.php
index cf531814..c30e19e6 100644
--- a/modules/salesforce_pull/tests/src/Unit/DeleteHandlerTest.php
+++ b/modules/salesforce_pull/tests/src/Unit/DeleteHandlerTest.php
@@ -144,7 +144,7 @@ class DeleteHandlerTest extends UnitTestCase {
 
     // Mock event dispatcher.
     $prophecy = $this->prophesize(ContainerAwareEventDispatcher::CLASS);
-    $prophecy->dispatch(Argument::any(), Argument::any())->willReturn();
+    $prophecy->dispatch(Argument::any(), Argument::any())->willReturnArgument(0);
     $this->ed = $prophecy->reveal();
 
     $this->dh = new DeleteHandler(
diff --git a/modules/salesforce_pull/tests/src/Unit/QueueHandlerTest.php b/modules/salesforce_pull/tests/src/Unit/QueueHandlerTest.php
index 08667a88..8456a16c 100644
--- a/modules/salesforce_pull/tests/src/Unit/QueueHandlerTest.php
+++ b/modules/salesforce_pull/tests/src/Unit/QueueHandlerTest.php
@@ -118,7 +118,7 @@ class QueueHandlerTest extends UnitTestCase {
 
     // Mock event dispatcher.
     $prophecy = $this->prophesize(EventDispatcherInterface::CLASS);
-    $prophecy->dispatch(Argument::any(), Argument::any())->willReturn();
+    $prophecy->dispatch(Argument::any(), Argument::any())->willReturnArgument(0);
     $this->ed = $prophecy->reveal();
 
     $this->time = $this->getMockBuilder(TimeInterface::CLASS)->getMock();
diff --git a/modules/salesforce_push/composer.json b/modules/salesforce_push/composer.json
index a2a6c7ee..ee4c4b95 100644
--- a/modules/salesforce_push/composer.json
+++ b/modules/salesforce_push/composer.json
@@ -8,13 +8,10 @@
             "email": "aaron@messageagency.com"
         }
     ],
-    "require": {
-        "php": ">=5.6.0"
-    },
     "extra": {
         "drush": {
             "services": {
-                "drush.services.yml": "^9"
+                "drush.services.yml": "^9 || ^10 || ^11"
             }
         }
     }
diff --git a/modules/salesforce_push/salesforce_push.drush.inc b/modules/salesforce_push/salesforce_push.drush.inc
deleted file mode 100644
index 42e483a6..00000000
--- a/modules/salesforce_push/salesforce_push.drush.inc
+++ /dev/null
@@ -1,108 +0,0 @@
-<?php
-
-/**
- * @file
- * Drush tools for the Salesforce Push module.
- */
-
-/**
- * Implements hook_drush_command().
- */
-function salesforce_push_drush_command() {
-  $items = [];
-
-  $items['sf-push-queue'] = [
-    'category' => 'salesforce',
-    'description' => 'Process push queues (as though during cron) for one or all Salesforce Mappings.',
-    'aliases' => ['sfpushq', 'sfpm'],
-    'arguments' => [
-      'name' => [
-        'description' => 'Machine name of the Salesforce Mapping for which to process push queue. If omitted, process all queues.',
-      ],
-    ],
-    'examples' => [
-      'drush sfpushq' => 'Process all push queue items',
-      'drush sfpushq foo' => 'Process push queue items for mapping "foo"',
-    ],
-  ];
-
-  // Push entities in a mapping that have not yet been pushed.
-  $items['salesforce-push-unmapped'] = [
-    'description' => "Attempt to push entities of a mapped type that are not linked to Salesforce Objects.",
-    'aliases' => ['sfpu'],
-    'bootstrap' => DRUSH_BOOTSTRAP_DRUPAL_FULL,
-    'core' => ['8+'],
-    'arguments' => [
-      'mapping_id' => 'The Drupal machine name of the mapping for the entities.',
-    ],
-    'options' => [
-      'count' => [
-        'description' => 'The number of entities to try to sync. (Default is 50).',
-        'example-value' => 42,
-      ],
-    ],
-    'examples' => [
-      'drush sfpu foo' => 'Push 50 drupal entities without mapped objects for mapping "foo"',
-      'drush sfpu foo --count=42' => 'Push 42 unmapped drupal entities without mapped objects for mapping "foo"',
-    ],
-  ];
-
-  return $items;
-}
-
-/**
- * Implements drush_hook_COMMAND().
- */
-function drush_salesforce_push_sf_push_queue($name = NULL) {
-  _drush_salesforce_deprecated();
-  $queue = \Drupal::service('queue.salesforce_push');
-  if ($name !== NULL) {
-    if (!($mapping = _salesforce_drush_get_mapping($name))) {
-      return;
-    }
-    // Process one mapping queue.
-    $queue->processQueue($mapping);
-  }
-  else {
-    // Process all queues.
-    $queue->processQueues();
-  }
-}
-
-/**
- * Implements drush_hook_COMMAND().
- *
- * Search for entities without Salesforce Object mappings and attempt to push
- * them to Salesforce.
- */
-function drush_salesforce_push_unmapped($name = NULL) {
-  _drush_salesforce_deprecated();
-  if (!($mapping = _salesforce_drush_get_mapping($name))) {
-    return;
-  }
-
-  $entity_type = $mapping->get('drupal_entity_type');
-  $etm = \Drupal::entityTypeManager();
-  $entity_storage = $etm->getStorage($entity_type);
-  $entity_keys = $etm->getDefinition($entity_type)->getKeys();
-  $id_key = $entity_keys['id'];
-  $bundle_key = empty($entity_keys['bundle']) ? FALSE : $entity_keys['bundle'];
-  $query = \Drupal::database()->select($entity_storage->getBaseTable(), 'b');
-  $query->leftJoin('salesforce_mapped_object', 'm', "b.$id_key = m.drupal_entity__target_id AND m.drupal_entity__target_type = '$entity_type'");
-  if ($bundle_key) {
-    $query->condition("b.$bundle_key", $mapping->get('drupal_bundle'));
-  }
-  $results = $query
-    ->fields('b', [$id_key])
-    ->isNull('m.drupal_entity__target_id')
-    ->range(0, drush_get_option('count', 50))
-    ->execute()
-    ->fetchAllAssoc($id_key);
-  $entities = $entity_storage->loadMultiple(array_keys($results));
-  $log = [];
-  foreach ($entities as $entity) {
-    salesforce_push_entity_crud($entity, 'push_create');
-    $log[] = $entity->id();
-  }
-  drush_print(count($log) . " unmapped entities found and push to Salesforce attempted. See logs for more details.");
-}
diff --git a/modules/salesforce_push/salesforce_push.info.yml b/modules/salesforce_push/salesforce_push.info.yml
index 1f476102..c453d48d 100644
--- a/modules/salesforce_push/salesforce_push.info.yml
+++ b/modules/salesforce_push/salesforce_push.info.yml
@@ -2,6 +2,6 @@ name: Salesforce Push
 type: module
 description: Push data to Salesforce when updates are made to Drupal entities.
 package: Salesforce
-core_version_requirement: ^9.1
+core_version_requirement: ^9 || ^10
 dependencies:
   - salesforce:salesforce_mapping
diff --git a/modules/salesforce_push/tests/src/Unit/PushQueueTest.php b/modules/salesforce_push/tests/src/Unit/PushQueueTest.php
index 04a56638..d9aeb638 100644
--- a/modules/salesforce_push/tests/src/Unit/PushQueueTest.php
+++ b/modules/salesforce_push/tests/src/Unit/PushQueueTest.php
@@ -59,7 +59,7 @@ class PushQueueTest extends UnitTestCase {
     $this->eventDispatcher = $this->getMockBuilder(EventDispatcherInterface::CLASS)->getMock();
     $this->eventDispatcher->expects($this->any())
       ->method('dispatch')
-      ->willReturn(NULL);
+      ->willReturnArgument(0);
     $this->string_translation = $this->getMockBuilder(TranslationInterface::class)->getMock();
     $this->time = $this->getMockBuilder(TimeInterface::class)->getMock();
 
diff --git a/modules/salesforce_push/tests/src/Unit/SalesforcePushQueueProcessorRestTest.php b/modules/salesforce_push/tests/src/Unit/SalesforcePushQueueProcessorRestTest.php
index ba3ae473..e73623b8 100644
--- a/modules/salesforce_push/tests/src/Unit/SalesforcePushQueueProcessorRestTest.php
+++ b/modules/salesforce_push/tests/src/Unit/SalesforcePushQueueProcessorRestTest.php
@@ -7,6 +7,7 @@ use Drupal\Core\DependencyInjection\ContainerBuilder;
 use Drupal\Core\Entity\EntityTypeManagerInterface;
 use Drupal\Core\Entity\Sql\SqlEntityStorageInterface;
 use Drupal\Core\Queue\SuspendQueueException;
+use Drupal\Core\StringTranslation\TranslatableMarkup;
 use Drupal\salesforce\EntityNotFoundException;
 use Drupal\salesforce\SalesforceAuthProviderPluginManager;
 use Drupal\Tests\UnitTestCase;
@@ -48,10 +49,15 @@ class SalesforcePushQueueProcessorRestTest extends UnitTestCase {
     $this->eventDispatcher = $this->getMockBuilder(EventDispatcherInterface::CLASS)->getMock();
     $this->eventDispatcher->expects($this->any())
       ->method('dispatch')
-      ->willReturn(NULL);
+      ->willReturnArgument(0);
     $this->entity_manager = $this->getMockBuilder(EntityTypeManagerInterface::class)->getMock();
 
     $this->string_translation = $this->getMockBuilder(TranslationInterface::class)->getMock();
+    $this->string_translation->expects($this->any())
+      ->method('translateString')
+      ->willReturnCallback(function (TranslatableMarkup $markup) {
+        return $markup->getUntranslatedString();
+      });
 
     $this->mapping = $this->getMockBuilder(SalesforceMappingInterface::CLASS)->getMock();
 
diff --git a/modules/salesforce_soap/composer.json b/modules/salesforce_soap/composer.json
index 859c77f4..b31a88ee 100644
--- a/modules/salesforce_soap/composer.json
+++ b/modules/salesforce_soap/composer.json
@@ -25,7 +25,7 @@
     "source": "http://cgit.drupalcode.org/salesforce"
   },
   "require": {
-    "messageagency/force.com-toolkit-for-php": "^1.0.1",
+    "messageagency/force.com-toolkit-for-php": "^1.0.2",
     "ext-soap": "*"
   }
 }
diff --git a/modules/salesforce_soap/salesforce_soap.info.yml b/modules/salesforce_soap/salesforce_soap.info.yml
index bdd49fac..3c6e5263 100644
--- a/modules/salesforce_soap/salesforce_soap.info.yml
+++ b/modules/salesforce_soap/salesforce_soap.info.yml
@@ -2,6 +2,6 @@ name: Salesforce Soap
 type: module
 description: Exposes a SoapClient service for communicating with the Salesforce SOAP API.
 package: Salesforce
-core_version_requirement: ^9.1
+core_version_requirement: ^9 || ^10
 dependencies:
   - salesforce:salesforce
diff --git a/modules/salesforce_webform/composer.json b/modules/salesforce_webform/composer.json
index 77fd08ad..ca37d48d 100644
--- a/modules/salesforce_webform/composer.json
+++ b/modules/salesforce_webform/composer.json
@@ -8,13 +8,10 @@
             "email": "author@example.com"
         }
     ],
-    "require": {
-        "php": ">=5.6.0"
-    },
     "extra": {
         "drush": {
             "services": {
-                "drush.services.yml": "^9"
+                "drush.services.yml": "^9 || ^10 || ^11"
             }
         }
     }
diff --git a/modules/salesforce_webform/salesforce_webform.info.yml b/modules/salesforce_webform/salesforce_webform.info.yml
index 0c4c230e..c7e28be8 100644
--- a/modules/salesforce_webform/salesforce_webform.info.yml
+++ b/modules/salesforce_webform/salesforce_webform.info.yml
@@ -2,8 +2,8 @@ name: Salesforce Webform
 type: module
 description: Adds support for webforms fields in Salesforce Mapping.
 package: Salesforce
-core_version_requirement: ^9.1
+core_version_requirement: ^9 || ^10
 
 dependencies:
   - salesforce:salesforce_mapping
-  - webform:webform (>= 5.9-beta1)
+  - webform:webform
diff --git a/salesforce.drush.inc b/salesforce.drush.inc
deleted file mode 100644
index 43b84333..00000000
--- a/salesforce.drush.inc
+++ /dev/null
@@ -1,492 +0,0 @@
-<?php
-
-/**
- * @file
- * Drush integration for Salesforce.
- */
-
-use Drupal\salesforce\SFID;
-use Drupal\salesforce\SelectQuery;
-use Drupal\salesforce\Exception as SalesforceException;
-
-/**
- * Implements hook_drush_command().
- */
-function salesforce_drush_command() {
-  $items['sf-rest-version'] = [
-    'description' => 'Displays information about the current REST API version',
-    'aliases' => ['sfrv'],
-  ];
-
-  $items['sf-list-objects'] = [
-    'description' => 'List the objects that are available in your organization and available to the logged-in user.',
-    'aliases' => ['sflo'],
-  ];
-
-  $items['sf-describe-object'] = [
-    'description' => 'Retrieve all the metadata for an object, including information about each field, URLs, and child relationships.',
-    'aliases' => ['sfdo'],
-    'arguments' => [
-      'object' => 'The object name in Salesforce.',
-    ],
-    'options' => [
-      'output' => "Specify an output type.
-Options are:
-info: (default) Display metadata about an object
-fields: Display information about fields that are part of the object
-field-data FIELDNAME: Display information about a specific field that is part of an object
-raw: Display the complete, raw describe response.",
-    ],
-    'examples' => [
-      'drush sfdo Contact' => 'Show metadata about Contact SObject type.',
-      'drush sfdo Contact --output=fields' => 'Show addtional metadata about Contact fields.',
-      'drush sfdo Contact --output=field --field=Email' => 'Show full metadata about Contact.Email field.',
-      'drush sfdo Contact --output=raw' => 'Display the full metadata for Contact SObject type.',
-    ],
-  ];
-
-  $items['sf-list-resources'] = [
-    'description' => 'Lists the resources available for the specified API version. It provides the name and URI of each resource.',
-    'aliases' => ['sflr'],
-  ];
-
-  $items['sf-read-object'] = [
-    'description' => 'Retrieve all the data for an object with a specific ID.',
-    'aliases' => ['sfro'],
-    'arguments' => [
-      'id' => 'The object ID in Salesforce.',
-    ],
-    'options' => [
-      'format' => [
-        'description' => 'Format to output the object. Use "print_r" for print_r (default), "export" for var_export, and "json" for JSON.',
-        'example-value' => 'export',
-      ],
-    ],
-  ];
-
-  $items['sf-create-object'] = [
-    'description' => 'Create an object with specified data.',
-    'aliases' => ['sfco'],
-    'arguments' => [
-      'object' => 'The object type name in Salesforce (e.g. Account).',
-      'data' => 'The data to use when creating the object (default is JSON format). Use \'-\' to read the data from STDIN.',
-    ],
-    'options' => [
-      'format' => [
-        'description' => 'Format to parse the object. Use  "json" for JSON (default) or "query" for data formatted like a query string, e.g. \'Company=Foo&LastName=Bar\'.',
-        'example-value' => 'json',
-      ],
-    ],
-  ];
-
-  $items['sf-query-object'] = [
-    'description' => 'Query an object using SOQL with specified conditions.',
-    'aliases' => ['sfqo'],
-    'arguments' => [
-      'object' => 'The object type name in Salesforce (e.g. Account).',
-    ],
-    'options' => [
-      'format' => [
-        'description' => 'Format to output the objects. Use "print_r" for print_r (default), "export" for var_export, and "json" for JSON.',
-        'example-value' => 'export',
-      ],
-      'where' => [
-        'description' => 'A WHERE clause to add to the SOQL query',
-      ],
-      'fields' => [
-        'description' => 'A comma-separated list fields to select in the SOQL query. If absent, an API call is used to find all fields',
-      ],
-      'limit' => [
-        'description' => 'Integer limit on the number of results to return for the query.',
-      ],
-      'order' => [
-        'description' => 'Comma-separated fields by which to sort results. Make sure to enclose in quotes for any whitespace.',
-      ],
-    ],
-  ];
-
-  $items['sf-execute-query'] = [
-    'description' => 'Execute a SOQL query.',
-    'aliases' => ['sfeq', 'soql'],
-    'arguments' => [
-      'query' => 'The query to execute.',
-    ],
-  ];
-
-  $items['sf-pull-query'] = [
-    'description' => 'Given a mapping, enqueue records for pull from Salesforce, ignoring modification timestamp. This command is useful, for example, when seeding content for a Drupal site prior to deployment.',
-    'aliases' => ['sfpq', 'sfiq'],
-    'arguments' => [
-      'name' => 'Machine name of the Salesforce Mapping for which to queue pull records.',
-    ],
-    'options' => [
-      'where' => [
-        'description' => 'A WHERE clause to add to the SOQL pull query. Default behavior is to query and pull all records.',
-      ],
-    ],
-    'examples' => [
-      'drush sfpq' => 'Interactively select a mapping for which to queue records.',
-      'drush sfpq user' => 'Query and queue all records for "user" Salesforce mapping.',
-      'drush sfpq user --where="Email like \'%foo%\' AND (LastName = \'bar\' OR FirstName = \'bar\')"' => 'Query and queue all records for "user" Salesforce mapping with Email field containing the string "foo" and First or Last name equal to "bar"',
-    ],
-  ];
-
-  $items['sf-pull-file'] = [
-    'description' => 'Given a mapping, enqueue a list of object IDs to be pulled from a CSV file, e.g. a Salesforce report. The first column of the CSV file must be SFIDs. Additional columns will be ignored.',
-    'aliases' => ['sfpf', 'sfif'],
-    'arguments' => [
-      'file' => 'CSV file name of 15- or 18-character Salesforce ids to be pulled. ',
-      'name' => 'Machine name of the Salesforce Mapping for which to queue pull records.',
-    ],
-  ];
-
-  return $items;
-}
-
-/**
- * List the resources available for the specified API version.
- *
- * This command provides the name and URI of each resource.
- */
-function drush_salesforce_sf_list_resources() {
-  _drush_salesforce_deprecated();
-  $salesforce = \Drupal::service('salesforce.client');
-  $resources = $salesforce->listResources();
-  if ($resources) {
-    $items[] = ['Resource', 'URL'];
-    foreach ($resources->resources as $resource => $url) {
-      $items[] = [$resource, $url];
-    }
-    drush_print("The following resources are available:\n");
-    drush_print_table($items);
-  }
-  else {
-    drush_log('Could not obtain a list of resources!', 'error');
-  }
-}
-
-/**
- * Describes a Salesforce object.
- *
- * Use the --fields option to display information about the fields of an object,
- * or the --field-data option to display information about a single field in an
- * object.
- *
- * @param string $object_name
- *   The name of a Salesforce object to query.
- */
-function drush_salesforce_sf_describe_object($object_name = NULL) {
-  _drush_salesforce_deprecated();
-
-  if (!$object_name) {
-    return drush_log('Please specify an object as an argument.', 'error');
-  }
-  $salesforce = \Drupal::service('salesforce.client');
-
-  $object = $salesforce->objectDescribe($object_name);
-
-  // Return if we cannot load any data.
-  if (!is_object($object)) {
-    return drush_log(dt('Could not load data for object !object', ['!object' => $object_name]), 'error');
-  }
-
-  $output = drush_get_option('output');
-  switch ($output) {
-    case 'raw':
-      drush_print_r($object->data);
-      return;
-
-    case 'fields':
-      $rows = [['Name', 'Type', 'Label']];
-      foreach ($object->fields as $field) {
-        $rows[] = [$field['name'], $field['type'], $field['label']];
-      }
-      drush_print_table($rows, TRUE);
-      return;
-
-    case 'field':
-      $fieldname = drush_get_option('field');
-      if (empty($fieldname)) {
-        drush_log(dt('Please specify a field name'), 'error');
-        return;
-      }
-      try {
-        $field_data = $object->getField($fieldname);
-      }
-      catch (\Exception $e) {
-        watchdog_exception('salesforce.drush', $e);
-        drush_log(dt('Could not load data for field !field on !object object', [
-          '!field' => $fieldname,
-          '!object' => $object_name,
-        ]), 'error');
-        return;
-      }
-      drush_print_r($field_data);
-      return;
-
-    default:
-      if ($output != 'info') {
-        drush_log(dt('Unkonwn output option !output', ['!output' => $output]), 'error');
-        return;
-      }
-
-      // Display information about the object.
-      $rows = [];
-      $rows[] = ['Name', $object->name];
-      $rows[] = ['Label', $object->label];
-      $rows[] = ['Field count', count($object->getFields())];
-      $rows[] = ['SFID prefix', $object->keyPrefix];
-      $rows[] = [
-        'Child Relationships',
-        isset($object->childRelationships) ? count($object->childRelationships) : 0,
-      ];
-
-      $rows[] = ['Searchable', ($object->searchable == 1) ? 'TRUE' : 'FALSE'];
-      $rows[] = ['Creatable', ($object->createable == 1) ? 'TRUE' : 'FALSE'];
-      $rows[] = ['Deletable', ($object->deletable == 1) ? 'TRUE' : 'FALSE'];
-      $rows[] = ['Mergeable', ($object->mergeable == 1) ? 'TRUE' : 'FALSE'];
-      $rows[] = ['Queryable', ($object->queryable == 1) ? 'TRUE' : 'FALSE'];
-      drush_print_table($rows);
-      return;
-  }
-}
-
-/**
- * Displays information about the REST API version the site is using.
- */
-function drush_salesforce_sf_rest_version() {
-  _drush_salesforce_deprecated();
-
-  $salesforce = \Drupal::service('salesforce.client');
-  $version_id = $salesforce->getApiVersion();
-  $versions = $salesforce->getVersions();
-  $version = $versions[$version_id];
-  $latest = array_pop($versions);
-
-  foreach ($version as $key => $value) {
-    $rows[] = [$key, $value];
-  }
-  $rows[] = ['login url', $salesforce->getLoginUrl()];
-  $rows[] = [
-    'latest version',
-    strcmp($version_id, $latest['version']) ? $latest['version'] : 'Yes',
-  ];
-  drush_print_table($rows, TRUE);
-}
-
-/**
- * List Salesforce objects.
- *
- * This command lists Salesforce objects that are available in your organization
- * and available to the logged-in user.
- */
-function drush_salesforce_sf_list_objects() {
-  _drush_salesforce_deprecated();
-  $salesforce = \Drupal::service('salesforce.client');
-  if ($objects = $salesforce->objects()) {
-    print_r($objects);
-    drush_print('The following objects are available in your organization and available to the logged-in user.');
-    $rows[] = ['Name', 'Label', 'Label Plural'];
-    foreach ($objects as $object) {
-      $rows[] = [
-        $object['name'],
-        $object['label'],
-        $object['labelPlural'],
-      ];
-    }
-    drush_print_table($rows, TRUE);
-  }
-  else {
-    drush_log('Could not load any information about available objects.', 'error');
-  }
-
-}
-
-/**
- * Read a Salesforce object available to the logged-in user.
- *
- * @param string $id
- *   The Salesforce ID.
- *
- * @throws \Exception
- */
-function drush_salesforce_sf_read_object($id) {
-  _drush_salesforce_deprecated();
-  $salesforce = \Drupal::service('salesforce.client');
-  try {
-    $name = $salesforce->getObjectTypeName(new SFID($id));
-    if ($object = $salesforce->objectRead($name, $id)) {
-      drush_print(dt('!type with id !id:', [
-        '!type' => $object->type(),
-        '!id' => $object->id(),
-      ]));
-      drush_print(drush_format($object->fields()));
-    }
-  }
-  catch (SalesforceException $e) {
-    drush_log($e->getMessage(), 'error');
-  }
-}
-
-/**
- * Create a Salesforce object available to the logged-in user.
- *
- * @param string $name
- *   The object type name, e.g. Account.
- * @param string $data
- *   The object data, or '-' to read from stdin.
- */
-function drush_salesforce_sf_create_object($name, $data) {
-  _drush_salesforce_deprecated();
-
-  if ($data == '-') {
-    $data = stream_get_contents(STDIN);
-  }
-  $format = drush_get_option('format', 'json');
-  $params = [];
-  switch ($format) {
-    case 'query':
-      parse_str($data, $params);
-      break;
-
-    case 'json':
-      $params = json_decode($data, TRUE);
-      break;
-
-    default:
-      drush_log(dt('Invalid format'), 'error');
-      return;
-  }
-  $salesforce = \Drupal::service('salesforce.client');
-  try {
-    if ($result = $salesforce->objectCreate($name, $params)) {
-      drush_print_r($result);
-    }
-  }
-  catch (SalesforceException $e) {
-    drush_log($e->getMessage(), 'error');
-  }
-}
-
-/**
- * Query Salesforce objects available to the logged-in user.
- *
- * @param string $name
- *   The object type name, e.g. Account.
- */
-function drush_salesforce_sf_query_object($name) {
-  _drush_salesforce_deprecated();
-  $salesforce = \Drupal::service('salesforce.client');
-
-  $query = new SelectQuery($name);
-
-  $fields = drush_get_option('fields', '');
-  if (!$fields) {
-    $object = $salesforce->objectDescribe($name);
-    $query->fields = array_keys($object->getFields());
-  }
-  else {
-    $query->fields = explode(',', $fields);
-  }
-
-  $query->limit = drush_get_option('limit', '');
-
-  if ($where = drush_get_option('where', '')) {
-    $query->conditions = [[$where]];
-  }
-
-  if ($order = drush_get_option('order', '')) {
-    $query->order = [];
-    $orders = explode(',', $order);
-    foreach ($orders as $order) {
-      list($field, $dir) = preg_split('/\s+/', $order, 2);
-      $query->order[$field] = $dir;
-    }
-  }
-
-  try {
-    $result = $salesforce->query($query);
-  }
-  catch (SalesforceException $e) {
-    drush_log($e->getMessage(), 'error');
-    return;
-  }
-
-  foreach ($result->records() as $sfid => $record) {
-    drush_print(drush_format([$sfid => $record->fields()]));
-  }
-  $pretty_query = str_replace('+', ' ', (string) $query);
-  if (!$fields) {
-    $fields = implode(',', $query->fields);
-    $pretty_query = str_replace($fields, ' * ', $pretty_query);
-  }
-  drush_print(dt("Showing !size of !total records for query:\n!query", [
-    '!size' => count($result->records()),
-    '!total' => $result->size(),
-    '!query' => $pretty_query,
-  ]));
-
-}
-
-/**
- * Execute a SOQL query.
- *
- * @param string $query
- *   The query to execute.
- */
-function drush_salesforce_sf_execute_query($query = NULL) {
-  _drush_salesforce_deprecated();
-  if (!$query) {
-    drush_log('Please specify a query as an argument.', 'error');
-    return;
-  }
-  $salesforce = \Drupal::service('salesforce.client');
-  try {
-    $result = $salesforce->apiCall('query?q=' . urlencode($query));
-    drush_print(drush_format($result));
-  }
-  catch (SalesforceException $e) {
-    drush_log($e->getMessage(), 'error');
-  }
-}
-
-/**
- * Get a mapping from the given name, or from user input if name is empty.
- *
- * @param string $name
- *   The mapping name.
- *
- * @return \Drupal\salesforce_mapping\Entity\SalesforceMappingInterface|null
- *   The mapping.
- */
-function _salesforce_drush_get_mapping($name = NULL) {
-  _drush_salesforce_deprecated();
-  $mapping_storage = \Drupal::service('entity_type.manager')
-    ->getStorage('salesforce_mapping');
-
-  if (empty($name)) {
-    $choices = array_keys($mapping_storage->loadMultiple());
-    if (empty($choices)) {
-      drush_log(dt('No mappings found.'), 'error');
-      return;
-    }
-    ksort($choices);
-    $choice = drush_choice($choices, dt('Enter a number to choose which mapping to use.'));
-    if ($choice === FALSE) {
-      return;
-    }
-    $name = $choices[$choice];
-  }
-  $mapping = $mapping_storage->load($name);
-  if (empty($mapping)) {
-    drush_log(dt('Mapping !name not found.', ['!name' => $name]), 'error');
-  }
-  return $mapping;
-}
-
-/**
- * Trigger a deprecation error.
- */
-function _drush_salesforce_deprecated() {
-  trigger_error('Salesforce module support for Drush 8 is deprecated and will be removed in a future release', E_USER_DEPRECATED);
-}
diff --git a/salesforce.info.yml b/salesforce.info.yml
index 9fb7ef22..d48a7cd1 100644
--- a/salesforce.info.yml
+++ b/salesforce.info.yml
@@ -2,5 +2,5 @@ name: Salesforce Integration
 type: module
 description: Modules to integrate Drupal and Salesforce
 package: Salesforce
-core_version_requirement: ^9.1
+core_version_requirement: ^9 || ^10
 configure: salesforce.admin_config_salesforce
diff --git a/src/Tests/TestHttpClient.php b/src/Tests/TestHttpClient.php
index fe2de237..79bb4482 100644
--- a/src/Tests/TestHttpClient.php
+++ b/src/Tests/TestHttpClient.php
@@ -4,6 +4,7 @@ namespace Drupal\salesforce\Tests;
 
 use GuzzleHttp\Client;
 use GuzzleHttp\Psr7\Response;
+use Psr\Http\Message\ResponseInterface;
 
 /**
  * Empty http client.
@@ -15,7 +16,7 @@ class TestHttpClient extends Client {
   /**
    * We need to override the post() method in order to fake our OAuth process.
    */
-  public function post($url, $headers) {
+  public function post($uri, array $options = []): ResponseInterface {
     return new Response();
   }
 
diff --git a/src/Tests/TestHttpClientWrapper.php b/src/Tests/TestHttpClientWrapper.php
index 0d07746f..619bfb4a 100644
--- a/src/Tests/TestHttpClientWrapper.php
+++ b/src/Tests/TestHttpClientWrapper.php
@@ -2,6 +2,7 @@
 
 namespace Drupal\salesforce\Tests;
 
+use Drupal\Core\Extension\ExtensionPathResolver;
 use GuzzleHttp\ClientInterface as GuzzleClientInterface;
 use OAuth\Common\Http\Client\ClientInterface;
 use OAuth\Common\Http\Uri\UriInterface;
@@ -18,14 +19,23 @@ class TestHttpClientWrapper implements ClientInterface {
    */
   protected $httpClient;
 
+  /**
+   * The extension path resolver.
+   *
+   * @var \Drupal\Core\Extension\ExtensionPathResolver
+   */
+  protected $extensionPathResolver;
+
   /**
    * HttpClientWrapper constructor.
    *
    * @param \GuzzleHttp\ClientInterface $httpClient
    *   Guzzle HTTP client service, from core http_client.
+   * @param
    */
-  public function __construct(GuzzleClientInterface $httpClient) {
+  public function __construct(GuzzleClientInterface $httpClient, ExtensionPathResolver $extensionPathResolver) {
     $this->httpClient = $httpClient;
+    $this->extensionPathResolver = $extensionPathResolver;
   }
 
   /**
@@ -39,7 +49,7 @@ class TestHttpClientWrapper implements ClientInterface {
   ) {
     // This method is only used to Salesforce OAuth. Based on the given args,
     // return a hard-coded version of the expected response.
-    $dir = drupal_get_path('module', 'salesforce') . '/src/Tests/';
+    $dir = $this->extensionPathResolver->getPath('module', 'salesforce') . '/src/Tests/';
     if ($endpoint->getPath() == '/services/oauth2/token') {
       switch ($requestBody['grant_type']) {
         case 'authorization_code':
diff --git a/tests/modules/salesforce_test_rest_client/salesforce_test_rest_client.info.yml b/tests/modules/salesforce_test_rest_client/salesforce_test_rest_client.info.yml
index aa748a14..5427d74c 100644
--- a/tests/modules/salesforce_test_rest_client/salesforce_test_rest_client.info.yml
+++ b/tests/modules/salesforce_test_rest_client/salesforce_test_rest_client.info.yml
@@ -2,6 +2,6 @@ name: 'Salesforce Test Rest Client'
 type: module
 description: 'Provides a dummy Rest Client for functional tests.'
 package: Testing
-core_version_requirement: ^9.1
+core_version_requirement: ^9 || ^10
 dependencies:
   - salesforce:salesforce
diff --git a/tests/modules/salesforce_test_rest_client/src/SalesforceTestRestClientServiceProvider.php b/tests/modules/salesforce_test_rest_client/src/SalesforceTestRestClientServiceProvider.php
index 27c26fb8..c989c002 100644
--- a/tests/modules/salesforce_test_rest_client/src/SalesforceTestRestClientServiceProvider.php
+++ b/tests/modules/salesforce_test_rest_client/src/SalesforceTestRestClientServiceProvider.php
@@ -8,6 +8,7 @@ use Drupal\salesforce\Tests\TestHttpClientWrapper;
 use Drupal\salesforce\Tests\TestRestClient;
 use Drupal\salesforce\Tests\TestHttpClientFactory;
 use Drupal\salesforce\Tests\TestSalesforceAuthProviderPluginManager;
+use Symfony\Component\DependencyInjection\Reference;
 
 /**
  * Modifies the salesforce client service.
@@ -28,7 +29,8 @@ class SalesforceTestRestClientServiceProvider extends ServiceProviderBase {
     $container->getDefinition('plugin.manager.salesforce.auth_providers')
       ->setClass(TestSalesforceAuthProviderPluginManager::class);
     $container->getDefinition('salesforce.http_client_wrapper')
-      ->setClass(TestHttpClientWrapper::class);
+      ->setClass(TestHttpClientWrapper::class)
+      ->addArgument(new Reference('extension.path.resolver'));
   }
 
 }
-- 
GitLab