From 788dfeec8e9ee653820e9b49790c56ba7c521ada Mon Sep 17 00:00:00 2001
From: aaronbauman <aaronbauman@384578.no-reply.drupal.org>
Date: Thu, 3 Jun 2021 08:45:36 -0400
Subject: [PATCH] Issue #3211681 by AaronBauman: Multipick List error getting
 value

---
 .../SalesforceMappingField/PropertiesBase.php | 38 +++++++--
 .../RelatedProperties.php                     |  8 +-
 ...st_content.field_salesforce_test_multi.yml | 20 +++++
 ...orage.node.field_salesforce_test_multi.yml | 29 +++++++
 ...e.type.salesforce_mapping_test_content.yml |  1 +
 ...apping.salesforce_mapping.test_mapping.yml | 36 ++++++---
 .../salesforce_mapping_test.info.yml          |  4 +
 .../tests/src/Functional/PushParamsTest.php   | 35 ++++----
 src/Tests/objectDescribe.json                 | 81 ++++++++++++++++++-
 9 files changed, 219 insertions(+), 33 deletions(-)
 create mode 100644 modules/salesforce_mapping/tests/modules/salesforce_mapping_test/config/install/field.field.node.salesforce_mapping_test_content.field_salesforce_test_multi.yml
 create mode 100644 modules/salesforce_mapping/tests/modules/salesforce_mapping_test/config/install/field.storage.node.field_salesforce_test_multi.yml

diff --git a/modules/salesforce_mapping/src/Plugin/SalesforceMappingField/PropertiesBase.php b/modules/salesforce_mapping/src/Plugin/SalesforceMappingField/PropertiesBase.php
index 5340b428..72bdbdf7 100644
--- a/modules/salesforce_mapping/src/Plugin/SalesforceMappingField/PropertiesBase.php
+++ b/modules/salesforce_mapping/src/Plugin/SalesforceMappingField/PropertiesBase.php
@@ -2,6 +2,7 @@
 
 namespace Drupal\salesforce_mapping\Plugin\SalesforceMappingField;
 
+use Drupal\Component\Utility\NestedArray;
 use Drupal\Core\Entity\EntityInterface;
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\TypedData\ComplexDataDefinitionInterface;
@@ -90,11 +91,14 @@ abstract class PropertiesBase extends SalesforceMappingFieldPluginBase {
       ->objectDescribe($mapping->getSalesforceObjectType());
     $field_definition = $describe->getField($this->config('salesforce_field'));
     if ($field_definition['type'] == 'multipicklist') {
-      $values = [];
-      foreach ($entity->get($this->config('drupal_field_value')) as $value) {
-        $values[] = $this->getStringValue($entity, $value);
+      $data = $this->getDataValue($entity, $this->config('drupal_field_value'));
+      if (!empty($data)) {
+        $strings = [];
+        foreach ($data as $item) {
+          $strings[] = $item->getString();
+        }
+        return implode(';', $strings);
       }
-      return implode(';', $values);
     }
     else {
       return $this->getStringValue($entity, $this->config('drupal_field_value'));
@@ -211,8 +215,30 @@ abstract class PropertiesBase extends SalesforceMappingFieldPluginBase {
    */
   protected function getStringValue(EntityInterface $entity, $drupal_field_value) {
     try {
-      return $this->dataFetcher()->fetchDataByPropertyPath($entity->getTypedData(), $drupal_field_value)
-        ->getString();
+      $data = $this->getDataValue($entity, $drupal_field_value);
+      return empty($data) ? NULL : $data->getString();
+    }
+    catch (\Exception $e) {
+      return NULL;
+    }
+  }
+
+  /**
+   * Another helper Method to check for and retrieve field data.
+   *
+   * Same as getStringValue(), but returns the typed data prior to stringifying.
+   *
+   * @param \Drupal\Core\Entity\EntityInterface $entity
+   *   The entity to search the Typed Data for.
+   * @param string $drupal_field_value
+   *   The Typed Data property to get.
+   *
+   * @return \Drupal\Core\TypedData\TypedDataInterface|NULL
+   *   The array representation of the Typed Data property value.
+   */
+  protected function getDataValue(EntityInterface $entity, $drupal_field_value) {
+    try {
+      return $this->dataFetcher()->fetchDataByPropertyPath($entity->getTypedData(), $drupal_field_value);
     }
     catch (\Exception $e) {
       return NULL;
diff --git a/modules/salesforce_mapping/src/Plugin/SalesforceMappingField/RelatedProperties.php b/modules/salesforce_mapping/src/Plugin/SalesforceMappingField/RelatedProperties.php
index 0742a65f..7538745d 100644
--- a/modules/salesforce_mapping/src/Plugin/SalesforceMappingField/RelatedProperties.php
+++ b/modules/salesforce_mapping/src/Plugin/SalesforceMappingField/RelatedProperties.php
@@ -51,7 +51,7 @@ class RelatedProperties extends SalesforceMappingFieldPluginBase {
    * {@inheritdoc}
    */
   public function value(EntityInterface $entity, SalesforceMappingInterface $mapping) {
-    list($field_name, $referenced_field_name) = explode(':', $this->config('drupal_field_value'), 2);
+    [$field_name, $referenced_field_name] = explode(':', $this->config('drupal_field_value'), 2);
     // Since we're not setting hard restrictions around bundles/fields, we may
     // have a field that doesn't exist for the given bundle/entity. In that
     // case, calling get() on an entity with a non-existent field argument
@@ -80,7 +80,9 @@ class RelatedProperties extends SalesforceMappingFieldPluginBase {
       if ($field_definition['type'] == 'multipicklist') {
         $values = [];
         foreach ($field as $ref_entity) {
-          $values[] = $ref_entity->entity->get($referenced_field_name)->value;
+          if (!$ref_entity->entity->get($referenced_field_name)->isEmpty()) {
+            $values[] = $ref_entity->entity->get($referenced_field_name)->value;
+          }
         }
         return implode(';', $values);
       }
@@ -101,7 +103,7 @@ class RelatedProperties extends SalesforceMappingFieldPluginBase {
     $definition['config_dependencies']['config'] = [];
     $field_name = $this->config('drupal_field_value');
     if (strpos($field_name, ':')) {
-      list($field_name, $dummy) = explode(':', $field_name, 2);
+      [$field_name, $dummy] = explode(':', $field_name, 2);
     }
     // Add reference field.
     if ($field = FieldConfig::loadByName($this->mapping->getDrupalEntityType(), $this->mapping->getDrupalBundle(), $field_name)) {
diff --git a/modules/salesforce_mapping/tests/modules/salesforce_mapping_test/config/install/field.field.node.salesforce_mapping_test_content.field_salesforce_test_multi.yml b/modules/salesforce_mapping/tests/modules/salesforce_mapping_test/config/install/field.field.node.salesforce_mapping_test_content.field_salesforce_test_multi.yml
new file mode 100644
index 00000000..6ef71933
--- /dev/null
+++ b/modules/salesforce_mapping/tests/modules/salesforce_mapping_test/config/install/field.field.node.salesforce_mapping_test_content.field_salesforce_test_multi.yml
@@ -0,0 +1,20 @@
+langcode: en
+status: true
+dependencies:
+  config:
+    - field.storage.node.field_salesforce_test_multi
+    - node.type.salesforce_mapping_test_content
+  module:
+    - options
+id: node.salesforce_mapping_test_content.field_salesforce_test_multi
+field_name: field_salesforce_test_multi
+entity_type: node
+bundle: salesforce_mapping_test_content
+label: 'Salesforce Test Multi'
+description: ''
+required: false
+translatable: false
+default_value: {  }
+default_value_callback: ''
+settings: {  }
+field_type: list_string
diff --git a/modules/salesforce_mapping/tests/modules/salesforce_mapping_test/config/install/field.storage.node.field_salesforce_test_multi.yml b/modules/salesforce_mapping/tests/modules/salesforce_mapping_test/config/install/field.storage.node.field_salesforce_test_multi.yml
new file mode 100644
index 00000000..4a9a221f
--- /dev/null
+++ b/modules/salesforce_mapping/tests/modules/salesforce_mapping_test/config/install/field.storage.node.field_salesforce_test_multi.yml
@@ -0,0 +1,29 @@
+langcode: en
+status: true
+dependencies:
+  module:
+    - node
+    - options
+id: node.field_salesforce_test_multi
+field_name: field_salesforce_test_multi
+entity_type: node
+type: list_string
+settings:
+  allowed_values:
+    -
+      value: 'Value 1'
+      label: 'Value 1'
+    -
+      value: 'Value 2'
+      label: 'Value 2'
+    -
+      value: 'Value 3'
+      label: 'Value 3'
+  allowed_values_function: ''
+module: options
+locked: false
+cardinality: -1
+translatable: true
+indexes: {  }
+persist_with_no_fields: false
+custom_storage: false
diff --git a/modules/salesforce_mapping/tests/modules/salesforce_mapping_test/config/install/node.type.salesforce_mapping_test_content.yml b/modules/salesforce_mapping/tests/modules/salesforce_mapping_test/config/install/node.type.salesforce_mapping_test_content.yml
index f9812274..64371fbc 100644
--- a/modules/salesforce_mapping/tests/modules/salesforce_mapping_test/config/install/node.type.salesforce_mapping_test_content.yml
+++ b/modules/salesforce_mapping/tests/modules/salesforce_mapping_test/config/install/node.type.salesforce_mapping_test_content.yml
@@ -1,5 +1,6 @@
 langcode: en
 status: true
+dependencies: {  }
 name: 'Salesforce Mapping Test Content Type'
 type: salesforce_mapping_test_content
 description: ''
diff --git a/modules/salesforce_mapping/tests/modules/salesforce_mapping_test/config/install/salesforce_mapping.salesforce_mapping.test_mapping.yml b/modules/salesforce_mapping/tests/modules/salesforce_mapping_test/config/install/salesforce_mapping.salesforce_mapping.test_mapping.yml
index 9bd338e0..d6ea9611 100644
--- a/modules/salesforce_mapping/tests/modules/salesforce_mapping_test/config/install/salesforce_mapping.salesforce_mapping.test_mapping.yml
+++ b/modules/salesforce_mapping/tests/modules/salesforce_mapping_test/config/install/salesforce_mapping.salesforce_mapping.test_mapping.yml
@@ -6,12 +6,15 @@ dependencies:
     - field.field.node.salesforce_mapping_test_content.field_salesforce_test_date
     - field.field.node.salesforce_mapping_test_content.field_salesforce_test_email
     - field.field.node.salesforce_mapping_test_content.field_salesforce_test_link
+    - field.field.node.salesforce_mapping_test_content.field_salesforce_test_multi
     - field.field.node.salesforce_mapping_test_content.field_salesforce_test_reference
     - field.storage.node.field_salesforce_test_link
     - field.storage.node.field_salesforce_test_reference
     - node.type.salesforce_mapping_test_content
   module:
     - link
+    - salesforce_pull
+    - salesforce_push
 id: test_mapping
 label: 'Test Mapping'
 weight: 0
@@ -38,49 +41,64 @@ field_mappings:
     drupal_field_value: 'SALESFORCE TEST'
     salesforce_field: FirstName
     direction: drupal_sf
-    id: 8
+    description: ''
+    id: 0
   -
     drupal_field_type: DrupalConstant
     drupal_field_value: title
     direction: sf_drupal
+    description: ''
     drupal_constant: 'SALESFORCE TEST'
-    id: 9
+    id: 1
   -
     drupal_field_type: properties
     drupal_field_value: field_salesforce_test_email
     salesforce_field: Email
     direction: sync
-    id: 10
+    description: ''
+    id: 2
   -
     drupal_field_type: properties
-    drupal_field_value: field_salesforce_test_date
+    drupal_field_value: field_salesforce_test_date.value
     salesforce_field: Birthdate
     direction: sync
-    id: 11
+    description: ''
+    id: 3
   -
     drupal_field_type: properties
     drupal_field_value: field_salesforce_test_bool
     salesforce_field: d5__Do_Not_Mail__c
     direction: sync
-    id: 12
+    description: ''
+    id: 4
   -
     drupal_field_type: properties_extended
     drupal_field_value: field_salesforce_test_link.uri
     salesforce_field: Description
     direction: sync
-    id: 13
+    description: ''
+    id: 5
   -
     drupal_field_type: RelatedIDs
     drupal_field_value: field_salesforce_test_reference
     salesforce_field: ReportsToId
     direction: sync
-    id: 14
+    description: ''
+    id: 6
   -
     drupal_field_type: record_type
     drupal_field_value: Contact_Type_1
     salesforce_field: RecordTypeId
     direction: drupal_sf
-    id: 15
+    description: ''
+    id: 7
+  -
+    drupal_field_type: properties
+    drupal_field_value: field_salesforce_test_multi
+    salesforce_field: d5__Multipicklist_Test__c
+    direction: sync
+    description: ''
+    id: 8
 push_limit: 0
 push_retries: 3
 push_frequency: 0
diff --git a/modules/salesforce_mapping/tests/modules/salesforce_mapping_test/salesforce_mapping_test.info.yml b/modules/salesforce_mapping/tests/modules/salesforce_mapping_test/salesforce_mapping_test.info.yml
index d36ea1da..98c0948c 100644
--- a/modules/salesforce_mapping/tests/modules/salesforce_mapping_test/salesforce_mapping_test.info.yml
+++ b/modules/salesforce_mapping/tests/modules/salesforce_mapping_test/salesforce_mapping_test.info.yml
@@ -7,6 +7,10 @@ dependencies:
   - datetime
   - link
   - node
+  - options
+  - user
   - salesforce_mapping
+  - salesforce_push
+  - salesforce_pull
   - salesforce_test_rest_client
   - taxonomy
diff --git a/modules/salesforce_mapping/tests/src/Functional/PushParamsTest.php b/modules/salesforce_mapping/tests/src/Functional/PushParamsTest.php
index 55eac14e..d5830c16 100644
--- a/modules/salesforce_mapping/tests/src/Functional/PushParamsTest.php
+++ b/modules/salesforce_mapping/tests/src/Functional/PushParamsTest.php
@@ -4,6 +4,7 @@ namespace Drupal\Tests\salesforce_mapping\Functional;
 
 use Drupal\Core\Datetime\DrupalDateTime;
 use Drupal\datetime\Plugin\Field\FieldType\DateTimeItemInterface;
+use Drupal\KernelTests\KernelTestBase;
 use Drupal\node\Entity\Node;
 use Drupal\salesforce_mapping\Entity\MappedObject;
 use Drupal\salesforce_mapping\Entity\SalesforceMapping;
@@ -32,8 +33,12 @@ class PushParamsTest extends BrowserTestBase {
    */
   protected static $modules = [
     'typed_data',
+    'options',
     'dynamic_entity_reference',
+    'salesforce',
     'salesforce_mapping',
+    'salesforce_push',
+    'salesforce_pull',
     'salesforce_mapping_test',
   ];
 
@@ -54,12 +59,11 @@ class PushParamsTest extends BrowserTestBase {
     $entity1->save();
 
     // Mapped Object to be used for RelatedIDs push params property.
-    $mappedObject = MappedObject::create([
-      'drupal_entity' => $entity1,
-      'salesforce_mapping' => $mapping,
-      'salesforce_id' => '0123456789ABCDEFGH',
-      'salesforce_link' => NULL,
-    ]);
+    $mappedObject = \Drupal::entityTypeManager()
+      ->getStorage('salesforce_mapped_object')
+      ->loadByEntityAndMapping($entity1, $mapping);
+
+    $mappedObject->set('salesforce_id', '0123456789ABCDEFGH');
     $mappedObject->save();
 
     // Entity 2 to be mapped to Salesforce.
@@ -71,6 +75,7 @@ class PushParamsTest extends BrowserTestBase {
       'field_salesforce_test_email' => 'test2@example.com',
       'field_salesforce_test_link' => 'https://example.com',
       'field_salesforce_test_reference' => $entity1,
+      'field_salesforce_test_multi' => [['value' => 'Value 1'], ['value' => 'Value 2'], ['value' => 'Value 3']],
     ]);
     $entity2->save();
 
@@ -81,11 +86,12 @@ class PushParamsTest extends BrowserTestBase {
     $expected = [
       'FirstName' => 'SALESFORCE TEST',
       'Email' => 'test2@example.com',
-      'Birthdate' => $expectedDate->format(DateTime::ISO8601),
+      'Birthdate' => $expectedDate->format('Y-m-d\TH:i:sO'),
       'd5__Do_Not_Mail__c' => TRUE,
       'ReportsToId' => '0123456789ABCDEFGH',
       'RecordTypeId' => '012i0000001B15mAAC',
       'Description' => 'https://example.com',
+      'd5__Multipicklist_Test__c' => 'Value 1;Value 2;Value 3'
     ];
     $actual = $pushParams->getParams();
     ksort($actual);
@@ -108,12 +114,11 @@ class PushParamsTest extends BrowserTestBase {
     $entity1->save();
 
     // Mapped Object to be used for RelatedIDs push params property.
-    $mappedObject = MappedObject::create([
-      'drupal_entity' => $entity1,
-      'salesforce_mapping' => $mapping,
-      'salesforce_id' => '0123456789ABCDEFGH',
-      'salesforce_link' => NULL,
-    ]);
+    $mappedObject = \Drupal::entityTypeManager()
+      ->getStorage('salesforce_mapped_object')
+      ->loadByEntityAndMapping($entity1, $mapping);
+
+    $mappedObject->set('salesforce_id', '0123456789ABCDEFGH');
     $mappedObject->save();
 
     // Entity 2 to be mapped to Salesforce.
@@ -125,6 +130,7 @@ class PushParamsTest extends BrowserTestBase {
       'field_salesforce_test_email' => 'test2@example.com',
       'field_salesforce_test_link' => 'https://example.com',
       'field_salesforce_test_reference' => $entity1,
+      'field_salesforce_test_multi' => ['Value 1', 'Value 2', 'Value 3'],
     ]);
     $entity2->save();
 
@@ -133,11 +139,12 @@ class PushParamsTest extends BrowserTestBase {
     $expected = [
       'FirstName' => 'SALESFORCE TEST',
       'Email' => 'test2@example.com',
-      'Birthdate' => '',
+      'Birthdate' => null,
       'd5__Do_Not_Mail__c' => TRUE,
       'ReportsToId' => '0123456789ABCDEFGH',
       'RecordTypeId' => '012i0000001B15mAAC',
       'Description' => 'https://example.com',
+      'd5__Multipicklist_Test__c' => 'Value 1;Value 2;Value 3'
     ];
     $actual = $pushParams->getParams();
     ksort($actual);
diff --git a/src/Tests/objectDescribe.json b/src/Tests/objectDescribe.json
index b6b0d4e2..bb22337b 100644
--- a/src/Tests/objectDescribe.json
+++ b/src/Tests/objectDescribe.json
@@ -4140,6 +4140,85 @@
       "unique": false,
       "updateable": false,
       "writeRequiresMasterRead": false
-    }
+    },
+      "d5__Multipicklist_Test__c": {
+          "aggregatable": true,
+          "autoNumber": false,
+          "byteLength": 4099,
+          "calculated": true,
+          "calculatedFormula": "",
+          "cascadeDelete": false,
+          "caseSensitive": false,
+          "compoundFieldName": null,
+          "controllerName": null,
+          "createable": true,
+          "custom": true,
+          "defaultValue": null,
+          "defaultValueFormula": null,
+          "defaultedOnCreate": false,
+          "dependentPicklist": false,
+          "deprecatedAndHidden": false,
+          "digits": 0,
+          "displayLocationInDecimal": false,
+          "encrypted": false,
+          "externalId": false,
+          "extraTypeInfo": null,
+          "filterable": true,
+          "filteredLookupInfo": null,
+          "groupable": false,
+          "highScaleNumber": false,
+          "htmlFormatted": false,
+          "idLookup": false,
+          "inlineHelpText": null,
+          "label": "Multipicklist Test",
+          "length": 4099,
+          "mask": null,
+          "maskType": null,
+          "name": "d5__Multipicklist_Test__c",
+          "nameField": false,
+          "namePointing": false,
+          "nillable": true,
+          "permissionable": true,
+          "picklistValues": [
+              {
+                  "active": true,
+                  "defaultValue": false,
+                  "label": "Value 1",
+                  "validFor": null,
+                  "value": "Value 1"
+              },
+              {
+                  "active": true,
+                  "defaultValue": false,
+                  "label": "Value 2",
+                  "validFor": null,
+                  "value": "Value 2"
+              },
+              {
+                  "active": true,
+                  "defaultValue": false,
+                  "label": "Value 3",
+                  "validFor": null,
+                  "value": "Value 3"
+              }
+          ],
+          "polymorphicForeignKey": false,
+          "precision": 0,
+          "queryByDistance": false,
+          "referenceTargetField": null,
+          "referenceTo": [],
+          "relationshipName": null,
+          "relationshipOrder": null,
+          "restrictedDelete": false,
+          "restrictedPicklist": false,
+          "scale": 0,
+          "searchPrefilterable": false,
+          "soapType": "xsd:string",
+          "sortable": true,
+          "type": "multipicklist",
+          "unique": false,
+          "updateable": true,
+          "writeRequiresMasterRead": false
+      }
   }
 }
\ No newline at end of file
-- 
GitLab