marketo_ma-8.x-2.x-dev/modules/marketo_ma_webform/src/Plugin/WebformHandler/MarketoMaWebformHandler.php
modules/marketo_ma_webform/src/Plugin/WebformHandler/MarketoMaWebformHandler.php
<?php
namespace Drupal\marketo_ma_webform\Plugin\WebformHandler;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Field\BaseFieldDefinition;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Logger\LoggerChannelFactoryInterface;
use Drupal\marketo_ma\Exception\ProcessingException;
use Drupal\marketo_ma\Lead;
use Drupal\marketo_ma\MarketoFieldDefinition;
use Drupal\marketo_ma\Service\MarketoMaServiceInterface;
use Drupal\taxonomy\Entity\Term;
use Drupal\webform\Plugin\WebformHandlerBase;
use Drupal\webform\WebformSubmissionConditionsValidatorInterface;
use Drupal\webform\WebformSubmissionInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\RequestStack;
/**
* Marketo MA Webform Handler.
*
* @WebformHandler(
* id = "marketo_ma",
* label = @Translation("Marketo MA"),
* category = @Translation("External"),
* description = @Translation("Sends a webform submission via Marketo MA."),
* cardinality = \Drupal\webform\Plugin\WebformHandlerInterface::CARDINALITY_UNLIMITED,
* results = \Drupal\webform\Plugin\WebformHandlerInterface::RESULTS_PROCESSED,
* )
*/
class MarketoMaWebformHandler extends WebformHandlerBase {
/**
* The Marketo MA service.
*
* @var \Drupal\marketo_ma\Service\MarketoMaServiceInterface
*/
protected $marketoMaService;
/**
* Request stack for retrieving information about the request.
*
* @var \Symfony\Component\HttpFoundation\RequestStack
*/
protected $requestStack;
/**
* MarketoMaWebformHandler constructor.
*
* @param array $configuration
* A configuration array containing information about the plugin instance.
* @param string $plugin_id
* The plugin_id for the plugin instance.
* @param mixed $plugin_definition
* The plugin implementation definition.
* @param \Drupal\Core\Logger\LoggerChannelFactoryInterface $logger_factory
* Logger factory.
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
* Configuration factory.
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
* Entity type manager.
* @param \Drupal\webform\WebformSubmissionConditionsValidatorInterface $conditions_validator
* Condition validator.
* @param \Drupal\marketo_ma\Service\MarketoMaServiceInterface $marketo_ma_service
* Marketo integration service.
* @param \Symfony\Component\HttpFoundation\RequestStack $request_stack
* Request stack for retrieving information about the request.
*/
public function __construct(
array $configuration,
$plugin_id,
$plugin_definition,
LoggerChannelFactoryInterface $logger_factory,
ConfigFactoryInterface $config_factory,
EntityTypeManagerInterface $entity_type_manager,
WebformSubmissionConditionsValidatorInterface $conditions_validator,
MarketoMaServiceInterface $marketo_ma_service,
RequestStack $request_stack
) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
// Work around webform api break and factory abuse.
$this->loggerFactory = $logger_factory;
$this->configFactory = $config_factory;
$this->entityTypeManager = $entity_type_manager;
$this->conditionsValidator = $conditions_validator;
$this->marketoMaService = $marketo_ma_service;
$this->requestStack = $request_stack;
$this->setConfiguration($configuration);
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$container->get('logger.factory'),
$container->get('config.factory'),
$container->get('entity_type.manager'),
$container->get('webform_submission.conditions_validator'),
$container->get('marketo_ma'),
$container->get('request_stack')
);
}
/**
* {@inheritdoc}
*/
public function getSummary() {
$configuration = $this->getConfiguration();
return [
'#settings' => $configuration['settings'],
] + parent::getSummary();
}
/**
* {@inheritdoc}
*/
public function defaultConfiguration() {
return [
'formid' => '',
'marketo_ma_mapping' => [],
'marketo_ma_list' => '',
'program_name' => '',
];
}
/**
* {@inheritdoc}
*/
public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
$webform = $this->getWebform();
// Assemble webform mapping source fields.
$mapSources = [];
$elements = $this->webform->getElementsDecodedAndFlattened();
foreach ($elements as $key => $val) {
if (empty($elements[$key]['#title'])) {
continue;
}
$mapSources[$key] = $elements[$key]['#title'];
}
/** @var \Drupal\webform\WebformSubmissionStorageInterface $webform_storage */
$webform_storage = $this->entityTypeManager->getStorage('webform_submission');
$fieldDefinitions = $webform_storage->checkFieldDefinitionAccess($webform, $webform_storage->getFieldDefinitions());
foreach ($fieldDefinitions as $fieldName => $fieldDefinition) {
if (!$fieldDefinition instanceof BaseFieldDefinition) {
$mapSources[$fieldName] = sprintf('%s (type: %s)', $fieldDefinition['title'], $fieldDefinition['type']);
}
}
$marketoFieldOptions = array_map([$this, 'getDisplayNameAndId'], $this->marketoMaService->getAvailableFields());
$form['formid'] = [
'#type' => 'textfield',
'#title' => $this->t('Form ID'),
'#description' => $this->t('Specify this to use Forms2'),
'#default_value' => $this->configuration['formid'] ?? '',
];
$form['program_name'] = [
'#type' => 'textfield',
'#title' => $this->t('Program Name'),
'#description' => $this->t('Specify this to use Program Name'),
'#default_value' => $this->configuration['program_name'] ?? '',
'#states' => [
'visible' => [
':input[name="settings[formid]"]' => ['filled' => FALSE],
],
],
];
$form['cookie'] = [
'#type' => 'checkbox',
'#title' => $this->t('Submit cookie'),
'#description' => $this->t(
'Submit tracking cookie with form submission. This is generally important for connecting in browser activities with the lead.'
),
'#default_value' => $this->configuration['cookie'] ?? TRUE,
'#states' => [
'visible' => [
':input[name="settings[formid]"]' => ['filled' => TRUE],
],
],
];
$form['marketo_ma_list'] = [
'#type' => 'textfield',
'#title' => $this->t('Marketo List ID'),
'#description' => $this->t(
'Optionally you can add the generated lead into a Marketo List. The List Id can be obtained from the URL of the list in the UI, where the URL will resemble https://app-***.marketo.com/#ST1001A1. In this URL, the id is 1001, it will always be between the first set of letters in the URL and the second set of letters.'
),
'#default_value' => $this->configuration['marketo_ma_list'] ?? '',
];
$form['marketo_ma_mapping'] = [
'#type' => 'webform_mapping',
'#title' => $this->t('Webform to Marketo MA Lead mapping'),
'#description' => $this->t('Only Maps with specified "Marketo MA Lead Field" will be submitted to Marketo.'),
'#source__title' => $this->t('Webform Submitted Data'),
'#destination__title' => $this->t('Marketo MA Lead Field'),
'#source' => $mapSources,
'#destination__type' => 'webform_select_other',
'#destination' => $marketoFieldOptions,
'#default_value' => $this->configuration['marketo_ma_mapping'] ?? '',
];
return $form;
}
/**
* {@inheritdoc}
*/
public function submitConfigurationForm(array &$form, FormStateInterface $form_state) {
parent::submitConfigurationForm($form, $form_state);
$this->applyFormStateToConfiguration($form_state);
}
/**
* {@inheritdoc}
*/
public function postSave(WebformSubmissionInterface $webform_submission, $update = TRUE) {
// @todo support triggering on more states like the email handler?
$state = $webform_submission->getWebform()->getSetting(
'results_disabled'
) ? WebformSubmissionInterface::STATE_COMPLETED : $webform_submission->getState();
if (($state == WebformSubmissionInterface::STATE_COMPLETED)) {
$form_data = $this->getMappedData($webform_submission);
$context = [
'@form' => $this->getWebform()->label(),
'link' => $this->getWebform()->toLink($this->t('Edit'), 'handlers')->toString(),
];
if ($this->configuration['cookie'] ?? TRUE) {
$cookie = $this->requestStack->getCurrentRequest()->cookies->get('_mkto_trk');
}
if (!empty($this->configuration['formid'])) {
$id = $this->formSubmit(
$this->configuration['formid'],
$form_data,
$cookie ?? NULL
);
}
else {
$id = $this->updateLead(
$this->getLead($webform_submission),
$cookie ?? NULL,
$this->configuration['program_name'] ?? NULL,
$this->configuration['marketo_ma_list'] ?? NULL
);
}
// Log message in Drupal's log.
if (!empty($id)) {
$this->getLogger()->notice('@form webform synced Marketo MA lead @lead_id', $context + ['@lead_id' => $id]);
}
}
}
/**
* Optimistic form submission.
*
* @todo this doesn't support submitting visitor data.
*
* @param string $form_id
* Marketo Form ID.
* @param array<string, string> $data
* Field data to submit to the marketo form.
* @param string|null $cookie
* A cookie to connect with the submission.
*
* @return int|null
* Lead associated with the form submission. Null on failure.
*/
protected function formSubmit(string $form_id, array $data, ?string $cookie): ?int {
try {
return $this->marketoMaService->postForm($form_id, $data, $cookie);
}
catch (ProcessingException $e) {
$this->getLogger()->error('@form webform failed to sync a Marketo MA lead. Marketo error: %error', [
'@form' => $this->getWebform()->label(),
'link' => $this->getWebform()->toLink($this->t('Edit'), 'handlers')->toString(),
'%error' => $e->getMessage(),
]);
}
return NULL;
}
/**
* Update the lead directly.
*
* @param \Drupal\marketo_ma\Lead $lead
* Lead object.
* @param string|null $cookie
* A cookie to connect with the submission.
* @param string|null $program_name
* A program name to associate the lead with.
* @param string|null $list
* Add lead to a specific list.
*
* @return int|null
* Lead associated with the form submission. Null on failure.
*/
protected function updateLead(Lead $lead, ?string $cookie, ?string $program_name, ?string $list): ?int {
if (isset($cookie)) {
$lead->setCookie($cookie);
}
// @todo needs to check for correct program name
if (!empty($program_name)) {
$lead->setProgramName($program_name);
}
$id = $this->marketoMaService->updateLead($lead);
// @todo is it a failure... or deferred?
if (empty($id)) {
$this->getLogger()->error('@form webform failed to sync a Marketo MA lead', [
'@form' => $this->getWebform()->label(),
'link' => $this->getWebform()->toLink($this->t('Edit'), 'handlers')->toString(),
]);
}
else {
$this->marketoMaService->addLeadToListByEmail($lead->getEmail(), (int) $list);
$result = $this->marketoMaService->getUpdateLeadResult();
$context = [
'@form' => $this->getWebform()->label(),
'link' => $this->getWebform()->toLink($this->t('Edit'), 'handlers')->toString(),
'@list_id' => $list,
];
if (!$result) {
$this->getLogger()->error(
'@form webform failed to add Marketo MA lead @lead_id to list @list_id.',
$context
);
}
else {
$this->getLogger()->notice('@form webform added Marketo MA lead @lead_id to list @list_id.', $context);
}
}
return $id;
}
/**
* Creates a Marketo MA Lead populated with mapped form data.
*
* @param \Drupal\webform\WebformSubmissionInterface $webform_submission
* Submission data from webform.
*
* @return \Drupal\marketo_ma\Lead
* The Marketo lead to sync.
*/
protected function getLead(WebformSubmissionInterface $webform_submission): Lead {
// Build and return lead.
$data = $this->getMappedData($webform_submission);
$lead = new Lead();
foreach ($data as $name => $value) {
$lead->set($name, $value);
}
return $lead;
}
/**
* Get submission data mapped to submittable fields.
*
* @param \Drupal\webform\WebformSubmissionInterface $webform_submission
* Submission data from webform.
*
* @return array
* Mapped data for submission.
*/
protected function getMappedData(WebformSubmissionInterface $webform_submission): array {
// Flatten submission data.
$webform_submission_data = $webform_submission->toArray(TRUE);
$webform_submission_data = $webform_submission_data['data'] + $webform_submission_data;
unset($webform_submission_data['data']);
$fields = $this->marketoMaService->getMarketoFields();
$data = [];
$elements = $webform_submission->getWebform()->getElementsDecodedAndFlattened();
foreach ($webform_submission_data as $webform_field_name => $value) {
if (isset($this->configuration['marketo_ma_mapping'][$webform_field_name])) {
$id = $this->configuration['marketo_ma_mapping'][$webform_field_name];
$name = $fields[$id]->getRestName();
if (isset($elements[$webform_field_name])) {
// If this is a term select field the numeric value won't mean anything
// to marketo. Load the string and submit that instead.
if ($elements[$webform_field_name]['#type'] == 'webform_term_select') {
// This should succeed since validation will have limited the allowed
// values to a valid list of terms.
$value = Term::load($value)->getName();
}
// If this is a checkbox, we need to convert the array into something marketo can parse.
elseif ($elements[$webform_field_name]['#type'] == 'checkboxes') {
$value = implode(',', $value);
}
}
$data[$name] = $value;
}
}
return $data;
}
/**
* Helper to map field definitions into a displayable string.
*
* @param \Drupal\marketo_ma\MarketoFieldDefinition $marketoField
* A marketo field definition.
*
* @return string
* The display string.
*/
public static function getDisplayNameAndId(MarketoFieldDefinition $marketoField): string {
return sprintf('%s (%s)', $marketoField->getDisplayName(), $marketoField->getRestName());
}
}
