commerce_donation_flow-1.0.x-dev/src/Form/DonationSettingsForm.php
src/Form/DonationSettingsForm.php
<?php
declare(strict_types=1);
namespace Drupal\commerce_donation_flow\Form;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Form\ConfigFormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Routing\RouteBuilderInterface;
use Drupal\commerce\EntityHelper;
use Drupal\commerce_checkout\Entity\CheckoutFlowInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Configure Commerce Donation Flow settings for this site.
*/
final class DonationSettingsForm extends ConfigFormBase {
/**
* Document and be consistent with option value.
*/
const DONATE_ROUTE_OPTION = 'donate';
/**
* Document and be consistent with option value.
*/
const CART_ROUTE_OPTION = 'cart';
/**
* Document and be consistent with option value.
*/
const COMBINED_ROUTE_OPTION = 'both';
const CONTEXTUAL_DOCUMENTATION = <<<DOCS
<details>
<summary>Configuration scenarios</summary>
<ul>
<li>A site in which donations are the only e-commerce.</li>
<ol>
<li>Before completing this configuration, create or confirm that you have created a <a href="/admin/commerce/config/checkout-flows">checkout flow</a> that uses this modules's <code>donation_checkout_flow</code> plugin.</li>
<li>Choose the /donate route option below with</li>
<ul>
<li>The Donation OrderItem type.</li>
<li>The Default Order type.</li>
<li>The donation checkout flow you created previously.</li>
</ul>
<li>Access to the /cart and /checkout based paths will be denied.</li>
</ol>
<li>A site in which donations are an additional item in a typical e-commerce setup.</li>
<ol>
<li>Before completing this configuration, create or confirm that you have created a <a href="/admin/commerce/config/checkout-flows">checkout flow</a> that uses this modules's <code>donation_multistep_flow</code> plugin. Place the "Add Donation" pane in the first step of the flow, just before the Donation step.</li>
<li>Choose the /cart route option below with</li>
<ul>
<li>The Donation OrderItem type.</li>
<li>The Default Order type.</li>
<li>The donation checkout flow you created previously.</li>
</ul>
<li>Access to the /donate based paths will be denied.</li>
</ol>
<li>A site with a dedicated process for donations and a separate process for typical e-commerce.</li>
<ul>
<li>This will be more complicated.</li>
<li>You will need both checkout flows discussed above created and configured.</li>
<li>You will need two order types created. One for the specialized /donate route and one for the standard /cart route.</li>
<li>You will need to configure a second OrderItemType</li>
<li>You will need to create extensions of the donation and memorial panes that select for your additional OrderItem type, and use those panes in the checkout flow which you configure to use this second OrderItem.</li>
<li>You will want to add a filter to the cart views to filter out the order type you intend to have for the /donate route.</li>
</ul>
</ul>
</details>
DOCS;
/**
* Injected service.
*
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
*/
protected EntityTypeManagerInterface $entityTypeManager;
/**
* The module handler.
*
* @var \Drupal\Core\Extension\ModuleHandlerInterface
*/
protected $moduleHandler;
/**
* Injected service.
*
* @var \Drupal\Core\Routing\RouteBuilder
*/
protected RouteBuilderInterface $routeBuilder;
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
$instance = parent::create($container);
$instance->entityTypeManager = $container->get('entity_type.manager');
$instance->moduleHandler = $container->get('module_handler');
$instance->routeBuilder = $container->get('router.builder');
return $instance;
}
/**
* {@inheritdoc}
*/
public function getFormId(): string {
return 'commerce_donation_flow_donation_settings';
}
/**
* {@inheritdoc}
*/
protected function getEditableConfigNames(): array {
return ['commerce_donation_flow.settings'];
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state): array {
// Initialize.
$config = $this->config('commerce_donation_flow.settings')->get();
$orderTypes = $this->entityTypeManager->getStorage('commerce_order_type')
->loadMultiple();
$itemTypes = $this->entityTypeManager->getStorage('commerce_order_item_type')
->loadMultiple();
$flows = $this->entityTypeManager->getStorage('commerce_checkout_flow')
->loadMultiple();
$orderTypeOptions = EntityHelper::extractLabels($orderTypes);
$itemTypeOptions = EntityHelper::extractLabels($itemTypes);
$form['guidance'] = [
'#type' => 'inline_template',
'#template' => self::CONTEXTUAL_DOCUMENTATION,
];
// Build the form.
$form['donation_route'] = [
'#type' => 'radios',
'#title' => $this->t('Donation checkout'),
'#default_value' => $config['donation_route'] ?? self::DONATE_ROUTE_OPTION,
'#options' => [
self::DONATE_ROUTE_OPTION => $this->t('Use /donate route'),
self::CART_ROUTE_OPTION => $this->t('Use /cart route'),
self::COMBINED_ROUTE_OPTION => $this->t('Allow both'),
],
self::COMBINED_ROUTE_OPTION => [
'#description' => $this->t('Two different order item types and two different order types must be defined to use both routes.'),
'#disabled' => count($orderTypeOptions) < 2 || count($itemTypeOptions) < 2,
],
];
$form['donate_route_settings'] = [
'#type' => 'fieldset',
'#title' => $this->t('Configuration for /donate route'),
'#states' => [
'visible' => [
[':input[name="donation_route"]' => ['value' => self::DONATE_ROUTE_OPTION]],
[':input[name="donation_route"]' => ['value' => self::COMBINED_ROUTE_OPTION]],
],
],
];
$form['donate_route_settings']['donate_item_type'] = [
'#type' => 'radios',
'#title' => $this->t('OrderItem to use on /donate'),
'#default_value' => $config['donate_item_type'] ?? 'donation',
'#options' => $itemTypeOptions,
'#states' => [
'disabled' => [':input[name="donation_route"]' => ['value' => self::CART_ROUTE_OPTION]],
],
];
$form['donate_route_settings']['donate_order_type'] = [
'#type' => 'radios',
'#title' => $this->t('Order to use with the OrderItem chosen above.'),
'#default_value' => $config['donation_order_type'] ?? 'default',
'#options' => $orderTypeOptions,
'#states' => [
'disabled' => [':input[name="donation_route"]' => ['value' => self::CART_ROUTE_OPTION]],
],
];
$donateFlows = array_filter(
$flows,
fn(CheckoutFlowInterface $flow):bool => str_starts_with($flow->getPluginId(), 'donation_checkout')
);
$form['donate_route_settings']['donate_checkout_flow'] = [
'#type' => 'select',
'#title' => t('Checkout flow'),
'#options' => EntityHelper::extractLabels($donateFlows),
'#description' => $this->t('The /donate route needs a checkout flow based on a plugin similar to <em>donate_checkout_flow</em>'),
'#default_value' => $config['donate_checkout_flow'] ?? 'donation_checkout_flow',
'#required' => TRUE,
];
$form['cart_route_settings'] = [
'#type' => 'fieldset',
'#title' => $this->t('Configuration for Commerce standard /cart | /checkout routes'),
'#states' => [
'visible' => [
[':input[name="donation_route"]' => ['value' => self::CART_ROUTE_OPTION]],
[':input[name="donation_route"]' => ['value' => self::COMBINED_ROUTE_OPTION]],
],
],
];
$form['cart_route_settings']['cart_item_type'] = [
'#type' => 'radios',
'#title' => $this->t('OrderItem to use with /cart | /checkout'),
'#default_value' => $config['cart_item_type'] ?? 'donation',
'#options' => $itemTypeOptions,
'#states' => [
'disabled' => [':input[name="donation_route"]' => ['value' => self::DONATE_ROUTE_OPTION]],
],
];
$form['cart_route_settings']['cart_order_type'] = [
'#type' => 'radios',
'#title' => $this->t('Order to use with the OrderItem chosen above.'),
'#default_value' => $config['cart_order_type'] ?? 'default',
'#options' => $orderTypeOptions,
'#states' => [
'disabled' => [':input[name="donation_route"]' => ['value' => self::DONATE_ROUTE_OPTION]],
],
];
$cartFlows = array_filter(
$flows,
fn(CheckoutFlowInterface $flow):bool => !str_starts_with($flow->getPluginId(), 'donation_checkout')
);
$cartFlowDefault = array_reduce(
$flows,
function (string $carry, CheckoutFlowInterface $flow): string {
if (empty($carry) && $flow->getPluginId() === 'donation_multistep_flow') {
$carry = $flow->id();
}
return $carry;
},
''
);
$form['cart_route_settings']['cart_checkout_flow'] = [
'#type' => 'select',
'#title' => t('Checkout flow'),
'#description' => $this->t('These routes need a checkout flow with dedicated steps, or a separate process, for configuring the donation.'),
'#options' => EntityHelper::extractLabels($cartFlows),
'#default_value' => $cartFlowDefault,
'#required' => TRUE,
];
return parent::buildForm($form, $form_state);
}
/**
* {@inheritdoc}
*/
public function validateForm(array &$form, FormStateInterface $form_state): void {
$form_state->cleanValues();
$route = $form_state->getValue('donation_route');
$collidingOrderTypes = $form_state->getValue('donate_order_type') === $form_state->getValue('cart_order_type');
switch ($route) {
case self::DONATE_ROUTE_OPTION:
$form_state->setValue('cart_item_type', NULL);
$form_state->setValue('cart_order_type', NULL);
$form_state->setValue('cart_checkout_flow', NULL);
break;
case self::CART_ROUTE_OPTION:
$form_state->setValue('donate_item_type', NULL);
$form_state->setValue('donate_order_type', NULL);
$form_state->setValue('donate_checkout_flow', NULL);
break;
case self::COMBINED_ROUTE_OPTION:
if ($collidingOrderTypes) {
$error = $this->t('The /donate and /cart routes must use different order types');
$form_state->setErrorByName('donate_order_type', $error);
$form_state->setErrorByName('cart_order_type', $error);
}
break;
case 'default':
$form_state->setErrorByName('donation_route', $this->t('No value for route was found in the form. That should not happen. Please contact your site administrator or open an issue on drupal.org.'));
}
parent::validateForm($form, $form_state);
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state): void {
$values = $form_state->getValues();
// Store our configuration.
$config = $this->config('commerce_donation_flow.settings');
$config->set('donation_route', $values['donation_route']);
$config->set('donate_item_type', $values['donate_item_type']);
$config->set('donate_order_type', $values['donate_order_type']);
$config->set('donate_checkout_flow', $values['donate_checkout_flow']);
$config->set('cart_item_type', $values['cart_item_type']);
$config->set('cart_order_type', $values['cart_order_type']);
$config->set('cart_checkout_flow', $values['cart_checkout_flow']);
$config->save();
// Set the checkout flow.
if (!empty($values['donate_order_type'] && !empty($values['donate_checkout_flow']))) {
/** @var \Drupal\commerce_order\Entity\OrderTypeInterface $orderType */
$orderType = $this->entityTypeManager
->getStorage('commerce_order_type')
->load($values['donate_order_type']);
$orderType->setThirdPartySetting('commerce_checkout', 'checkout_flow', $values['donate_checkout_flow']);
$orderType->save();
}
if (!empty($values['cart_order_type'] && !empty($values['cart_checkout_flow']))) {
/** @var \Drupal\commerce_order\Entity\OrderTypeInterface $orderType */
$orderType = $this->entityTypeManager
->getStorage('commerce_order_type')
->load($values['cart_order_type']);
$orderType->setThirdPartySetting('commerce_checkout', 'checkout_flow', $values['cart_checkout_flow']);
$orderType->save();
}
// Associate the order type with the selected order item type.
if (!empty($values['donate_order_type'] && !empty($values['donate_item_type']))) {
/** @var \Drupal\commerce_order\Entity\OrderItemTypeInterface $orderItemType */
$orderItemType = $this->entityTypeManager
->getStorage('commerce_order_item_type')
->load($values['donate_item_type']);
$orderItemType->setOrderTypeId($values['donate_order_type']);
$orderItemType->save();
}
if (!empty($values['cart_order_type'] && !empty($values['cart_item_type']))) {
/** @var \Drupal\commerce_order\Entity\OrderItemTypeInterface $orderItemType */
$orderItemType = $this->entityTypeManager
->getStorage('commerce_order_item_type')
->load($values['cart_item_type']);
$orderItemType->setOrderTypeId($values['cart_order_type']);
$orderItemType->save();
}
// Flag routes for rebuilding.
$this->routeBuilder->setRebuildNeeded();
// Set the order in the order item.
parent::submitForm($form, $form_state);
}
}
