Skip to content

Commit

Permalink
Issue #2980700 by bojanz: Introduce offer conditions
Browse files Browse the repository at this point in the history
  • Loading branch information
bojanz committed Jun 28, 2018
1 parent f4c04e4 commit 06497cd
Show file tree
Hide file tree
Showing 10 changed files with 360 additions and 121 deletions.
14 changes: 14 additions & 0 deletions modules/promotion/commerce_promotion.module
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,20 @@ use Drupal\Core\Field\BaseFieldDefinition;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Render\Element;

/**
* Implements hook_commerce_condition_info_alter().
*/
function commerce_promotion_commerce_condition_info_alter(&$definitions) {
foreach ($definitions as &$definition) {
// Force all order item conditions to have the same category.
// This prevents them from accidentally showing in vertical tabs
// in the promotion offer UI.
if ($definition['entity_type'] == 'commerce_order_item') {
$definition['category'] = t('Products');
}
}
}

/**
* Implements hook_theme().
*/
Expand Down
151 changes: 151 additions & 0 deletions modules/promotion/commerce_promotion.post_update.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
*/

use Drupal\commerce_promotion\Entity\PromotionInterface;
use Drupal\commerce_promotion\Plugin\Commerce\PromotionOffer\OrderItemPromotionOfferInterface;
use Drupal\Core\Field\BaseFieldDefinition;

/**
Expand Down Expand Up @@ -233,3 +234,153 @@ function commerce_promotion_post_update_8(&$sandbox = NULL) {
$sandbox['#finished'] = ($sandbox['total_count'] - $sandbox['current_count']) / $sandbox['total_count'];
}
}

/**
* Update offers and conditions.
*/
function commerce_promotion_post_update_9(&$sandbox = NULL) {
$promotion_storage = \Drupal::entityTypeManager()->getStorage('commerce_promotion');
if (!isset($sandbox['current_count'])) {
$query = $promotion_storage->getQuery();
$sandbox['total_count'] = $query->count()->execute();
$sandbox['current_count'] = 0;
$sandbox['disabled_offers'] = [];
$sandbox['disabled_conditions'] = [];

if (empty($sandbox['total_count'])) {
$sandbox['#finished'] = 1;
return;
}
}

$query = $promotion_storage->getQuery();
$query->range($sandbox['current_count'], 25);
$result = $query->execute();
if (empty($result)) {
$sandbox['#finished'] = 1;
return;
}

/** @var \Drupal\commerce_promotion\Entity\PromotionInterface[] $promotions */
$promotions = $promotion_storage->loadMultiple($result);
foreach ($promotions as $promotion) {
$needs_save = FALSE;
$needs_disable = FALSE;

$conditions = $promotion->getConditions();
$order_item_conditions = array_filter($conditions, function ($condition) {
/** @var \Drupal\commerce\Plugin\Commerce\Condition\ConditionInterface $condition */
return $condition->getEntityTypeId() == 'commerce_order_item' && $condition->getPluginId() != 'order_item_quantity';
});
$condition_map = [
'order_item_product' => 'order_product',
'order_item_product_type' => 'order_product_type',
'order_item_variation_type' => 'order_variation_type',
];
$condition_items = $promotion->get('conditions')->getValue();

$known_order_item_offers = [
'order_item_fixed_amount_off',
'order_item_percentage_off',
];
$offer = $promotion->getOffer();
$offer_item = $promotion->get('offer')->first()->getValue();

if ($offer->getEntityTypeId() == 'commerce_order_item') {
$needs_save = TRUE;
// Transfer order item conditions to the offer.
// Modify the offer item directly to be able to upgrade offers that
// haven't yet been converted to extend OfferItemPromotionOfferBase.
$offer_item['target_plugin_configuration']['conditions'] = [];
foreach ($order_item_conditions as $condition) {
$offer_item['target_plugin_configuration']['conditions'][] = [
'plugin' => $condition->getPluginId(),
'configuration' => $condition->getConfiguration(),
];
}

// The promotion is using a custom offer which hasn't been updated yet,
// disable it so that it can get updated without crashing everything.
if (!in_array($offer->getPluginId(), $known_order_item_offers)) {
if (!($offer instanceof OrderItemPromotionOfferInterface)) {
$needs_disable = TRUE;
$sandbox['disabled_offers'][] = $promotion->label();
}
}
}

// Convert known order item conditions to order conditions.
if ($order_item_conditions) {
foreach ($condition_items as $index => $condition_item) {
if (array_key_exists($condition_item['target_plugin_id'], $condition_map)) {
$condition_items[$index]['target_plugin_id'] = $condition_map[$condition_item['target_plugin_id']];
$needs_save = TRUE;
}
}
$promotion->set('conditions', $condition_items);
}

// Drop unknown order item conditions.
$conditions = $promotion->getConditions();
$order_item_conditions = array_filter($conditions, function ($condition) {
/** @var \Drupal\commerce\Plugin\Commerce\Condition\ConditionInterface $condition */
return $condition->getEntityTypeId() == 'commerce_order_item' && $condition->getPluginId() != 'order_item_quantity';
});
foreach ($order_item_conditions as $condition) {
foreach ($condition_items as $index => $condition_item) {
if ($condition_item['target_plugin_id'] == $condition->getPluginId()) {
unset($condition_items[$index]);
$needs_save = TRUE;
// An unrecognized offer was dropped, but because the offer applies
// to the order, wasn't transferred there. Disable the promotion
// to allow the merchant to double check the new configuration.
if ($offer->getEntityTypeId() == 'commerce_order') {
$needs_disable = TRUE;
$sandbox['disabled_conditions'][$promotion->id()] = [$promotion->label(), $condition->getPluginId()];
}
}
}
}

if ($needs_disable) {
$promotion->setEnabled(FALSE);
}
if ($needs_save) {
$promotion->set('offer', $offer_item);
$promotion->set('conditions', array_values($condition_items));
$promotion->save();
}
}

$sandbox['current_count'] += 25;
if ($sandbox['current_count'] >= $sandbox['total_count']) {
$sandbox['#finished'] = 1;
}
else {
$sandbox['#finished'] = ($sandbox['total_count'] - $sandbox['current_count']) / $sandbox['total_count'];
}

if ($sandbox['#finished']) {
$message = '';
if ($sandbox['disabled_offers']) {
$message .= 'These promotions have been disabled because their offers need to be updated for Commerce 2.8: <br>';
foreach ($sandbox['disabled_offers'] as $promotion_title) {
$message .= '- ' . $promotion_title . '<br>';
}
}
if ($sandbox['disabled_conditions']) {
$message .= 'These promotions have been disabled because their conditions need to be updated for Commerce 2.8: <br>';
foreach ($sandbox['disabled_conditions'] as $item) {
$message .= '- ' . $item[0] . ' (Condition: ' . $item[1] . ') <br>';
}
}
if ($message) {
$message .= 'Please see https://www.drupal.org/node/2982334 for more information.';
}
else {
$message .= 'Successfully updated all promotions';
}

return $message;
}
}
57 changes: 13 additions & 44 deletions modules/promotion/src/Entity/Promotion.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
use Drupal\commerce\Entity\CommerceContentEntityBase;
use Drupal\commerce\Plugin\Commerce\Condition\ConditionInterface;
use Drupal\commerce_order\Entity\OrderInterface;
use Drupal\commerce_promotion\Plugin\Commerce\PromotionOffer\OrderItemPromotionOfferInterface;
use Drupal\commerce_promotion\Plugin\Commerce\PromotionOffer\PromotionOfferInterface;
use Drupal\Core\Datetime\DrupalDateTime;
use Drupal\Core\Entity\EntityStorageInterface;
Expand Down Expand Up @@ -456,66 +457,34 @@ public function applies(OrderInterface $order) {
// Promotions without conditions always apply.
return TRUE;
}
$order_conditions = array_filter($conditions, function ($condition) {
// Filter the conditions just in case there are leftover order item
// conditions (which have been moved to offer conditions).
$conditions = array_filter($conditions, function ($condition) {
/** @var \Drupal\commerce\Plugin\Commerce\Condition\ConditionInterface $condition */
return $condition->getEntityTypeId() == 'commerce_order';
});
$order_item_conditions = array_filter($conditions, function ($condition) {
/** @var \Drupal\commerce\Plugin\Commerce\Condition\ConditionInterface $condition */
return $condition->getEntityTypeId() == 'commerce_order_item';
});
$condition_operator = $this->getConditionOperator();
$order_conditions = new ConditionGroup($order_conditions, $condition_operator);
$order_item_conditions = new ConditionGroup($order_item_conditions, $condition_operator);

$order_conditions_apply = $order_conditions->evaluate($order);
if ($condition_operator == 'AND' && !$order_conditions_apply) {
return FALSE;
}

$order_item_conditions_apply = FALSE;
foreach ($order->getItems() as $order_item) {
// Order item conditions must match at least one order item.
if ($order_item_conditions->evaluate($order_item)) {
$order_item_conditions_apply = TRUE;
break;
}
}

if ($condition_operator == 'AND') {
return $order_conditions_apply && $order_item_conditions_apply;
}
elseif ($condition_operator == 'OR') {
// Empty condition groups are TRUE by default, which leads to incorrect
// logic with ORed groups due to false positives.
$order_conditions_apply = $order_conditions->getConditions() && $order_conditions_apply;
$order_item_conditions_apply = $order_item_conditions->getConditions() && $order_item_conditions_apply;
$condition_group = new ConditionGroup($conditions, $this->getConditionOperator());

return $order_conditions_apply || $order_item_conditions_apply;
}
return $condition_group->evaluate($order);
}

/**
* {@inheritdoc}
*/
public function apply(OrderInterface $order) {
$offer = $this->getOffer();
if ($offer->getEntityTypeId() == 'commerce_order') {
$offer->apply($order, $this);
}
elseif ($offer->getEntityTypeId() == 'commerce_order_item') {
$order_item_conditions = array_filter($this->getConditions(), function ($condition) {
/** @var \Drupal\commerce\Plugin\Commerce\Condition\ConditionInterface $condition */
return $condition->getEntityTypeId() == 'commerce_order_item';
});
$order_item_conditions = new ConditionGroup($order_item_conditions, $this->getConditionOperator());
if ($offer instanceof OrderItemPromotionOfferInterface) {
$offer_conditions = new ConditionGroup($offer->getConditions(), 'OR');
// Apply the offer to order items that pass the conditions.
foreach ($order->getItems() as $order_item) {
if ($order_item_conditions->evaluate($order_item)) {
if ($offer_conditions->evaluate($order_item)) {
$offer->apply($order_item, $this);
}
}
}
else {
$offer->apply($order, $this);
}
}

/**
Expand Down Expand Up @@ -634,7 +603,7 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
'type' => 'commerce_conditions',
'weight' => 3,
'settings' => [
'entity_types' => ['commerce_order', 'commerce_order_item'],
'entity_types' => ['commerce_order'],
],
]);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,6 @@ public function onFilterConditions(FilterConditionsEvent $event) {
$definitions = $event->getDefinitions();
unset($definitions['order_store']);
unset($definitions['order_type']);
// Remove until #2980700 lands.
unset($definitions['order_product']);
unset($definitions['order_product_type']);
unset($definitions['order_variation_type']);
$event->setDefinitions($definitions);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ public function defaultConfiguration() {
* {@inheritdoc}
*/
public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
$form += parent::buildConfigurationForm($form, $form_state);
$form = parent::buildConfigurationForm($form, $form_state);

$amount = $this->configuration['amount'];
// A bug in the plugin_select form element causes $amount to be incomplete.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,78 @@

namespace Drupal\commerce_promotion\Plugin\Commerce\PromotionOffer;

use Drupal\commerce\Plugin\Commerce\Condition\ConditionInterface;
use Drupal\Core\Form\FormStateInterface;

/**
* Provides the base class for order item offers.
*/
abstract class OrderItemPromotionOfferBase extends PromotionOfferBase implements OrderItemPromotionOfferInterface {

/**
* {@inheritdoc}
*/
public function defaultConfiguration() {
return [
'conditions' => [],
] + parent::defaultConfiguration();
}

/**
* {@inheritdoc}
*/
public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
$form = parent::buildConfigurationForm($form, $form_state);

$form['conditions'] = [
'#type' => 'commerce_conditions',
'#title' => $this->t('Applies to'),
'#parent_entity_type' => 'commerce_promotion',
'#entity_types' => ['commerce_order_item'],
'#default_value' => $this->configuration['conditions'],
];

return $form;
}

/**
* {@inheritdoc}
*/
public function submitConfigurationForm(array &$form, FormStateInterface $form_state) {
parent::submitConfigurationForm($form, $form_state);

if (!$form_state->getErrors()) {
$values = $form_state->getValue($form['#parents']);
$this->configuration['conditions'] = $values['conditions'];
}
}

/**
* {@inheritdoc}
*/
public function getConditions() {
$plugin_manager = \Drupal::service('plugin.manager.commerce_condition');
$conditions = [];
foreach ($this->configuration['conditions'] as $condition) {
$conditions[] = $plugin_manager->createInstance($condition['plugin'], $condition['configuration']);
}
return $conditions;
}

/**
* {@inheritdoc}
*/
public function setConditions(array $conditions) {
$this->configuration['conditions'] = [];
foreach ($conditions as $condition) {
if ($condition instanceof ConditionInterface) {
$this->configuration['conditions'][] = [
'plugin' => $condition->getPluginId(),
'configuration' => $condition->getConfiguration(),
];
}
}
return $this;
}

}
Loading

0 comments on commit 06497cd

Please sign in to comment.