Skip to content

Commit

Permalink
Merge pull request #41 from nexcess/feature/limiter-filters
Browse files Browse the repository at this point in the history
Add new filters to the OrderLimiter
  • Loading branch information
bswatson authored Jul 15, 2020
2 parents bbe0cb9 + 2c1a952 commit 2a1cae9
Show file tree
Hide file tree
Showing 3 changed files with 184 additions and 0 deletions.
70 changes: 70 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -207,3 +207,73 @@ add_filter( 'limit_orders_message_placeholders', function ( $placeholders ) {
Now, we can create customer-facing notices like:

> {store_name} is a little overwhelmed right now, but we'll be able to take more orders on {next_interval:date}. Please check back then!
### Dynamically changing limiter behavior

In certain cases, you may want to further customize the logic around _which_ orders count toward the limit or, for example, change the behavior based on time of day. Limit Orders for WooCommerce has you covered:

#### Customize the counting of qualified orders

Sometimes, you only want to limit certain types of orders. Maybe some orders are fulfilled via third parties (e.g. [dropshipping](https://www.liquidweb.com/woocommerce-resource/dropshipping-glossary/)), or perhaps you're willing to bend the limits a bit for orders that contain certain products.

You can customize the logic used to calculate the count via the `limit_orders_pre_count_qualifying_orders` filter:

```php
/**
* Determine how many orders to count against the current interval.
*
* @param bool $preempt Whether the counting logic should be preempted. Returning
* anything but FALSE will bypass the default logic.
* @param OrderLimiter $limiter The current OrderLimiter instance.
*
* @return int The number of orders that should be counted against the limit.
*/
add_filter( 'limit_orders_pre_count_qualifying_orders', function ( $preempt, $limiter ) {
/*
* Do whatever you need to do here to count how many orders count.
*
* Pay close attention to date ranges here, and check out the public methods
* on the Nexcess\LimitOrders\OrderLimiter class.
*/
}, 10, 2 );
```

Please note that the `LimitOrders::count_qualifying_orders()` method (where this filter is defined) is only called in two situations:

1. When a new order is created.
2. If the `limit_orders_order_count` transient disappears.

#### Dynamically change the order limit

If, for example, you want to automatically turn off the store overnight, you might do so by setting the limit to `0` only during certain hours.

You can accomplish this using the `limit_orders_pre_get_remaining_orders` filter:

```php
/**
* Disable the store between 10pm and 8am.
*
* This works by setting the limit on Limit Orders for WooCommerce to zero if
* the current time is between those hours.
*
* @param bool $preempt Whether or not the default logic should be preempted.
* Returning anything besides FALSE will be treated as the
* number of remaining orders that can be accepted.
*
* @return int|bool Either 0 if the store is closed (meaning zero orders remaining)
* or the value of $preempt if Limit Orders should proceed normally.
*/
add_filter( 'limit_orders_pre_get_remaining_orders', function ( $preempt ) {
$open = new \DateTime('08:00', wp_timezone());
$close = new \DateTime('22:00', wp_timezone());
$now = current_datetime();

// We're currently inside normal business hours.
if ( $now >= $open && $now < $close ) {
return $preempt;
}

// If we've gotten this far, turn off ordering.
return 0;
} );
```
28 changes: 28 additions & 0 deletions src/OrderLimiter.php
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,21 @@ public function get_placeholders( $setting = '', $message = '' ) {
public function get_remaining_orders() {
$limit = $this->get_limit();

/**
* Filter the number of orders remaining for the current interval.
*
* @param bool $preempt Whether or not the default logic should be preempted.
* Returning anything besides FALSE will be treated as the
* number of remaining orders that can be accepted.
* @param OrderLimiter $limiter The current OrderLimiter object.
*/
$remaining = apply_filters( 'limit_orders_pre_get_remaining_orders', false, $this );

// Return early if a non-false value was returned from the filter.
if ( false !== $remaining ) {
return (int) $remaining;
}

// If there are no limits set, return -1.
if ( ! $this->is_enabled() || -1 === $limit ) {
return -1;
Expand Down Expand Up @@ -384,6 +399,19 @@ public function reset_limiter_on_update( $previous, $new ) {
* @return int The number of orders that have taken place within the defined interval.
*/
protected function count_qualifying_orders() {
/**
* Replace the logic used to count qualified orders.
*
* @param bool $preempt Whether the counting logic should be preempted. Returning
* anything but FALSE will bypass the default logic.
* @param OrderLimiter $limiter The current OrderLimiter instance.
*/
$count = apply_filters( 'limit_orders_pre_count_qualifying_orders', false, $this );

if ( false !== $count ) {
return (int) $count;
}

$orders = wc_get_orders( [
'type' => wc_get_order_types( 'order-count' ),
'date_created' => '>=' . $this->get_interval_start()->getTimestamp(),
Expand Down
86 changes: 86 additions & 0 deletions tests/OrderLimiterTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,46 @@ public function get_remaining_orders_should_return_zero_if_limits_are_met_or_exc
$this->assertSame( 0, ( new OrderLimiter() )->get_remaining_orders() );
}

/**
* @test
* @testdox get_remaining_orders() should be filterable
*/
public function get_remaining_orders_should_be_filterable() {
$instance = new OrderLimiter();
$called = false;

update_option( OrderLimiter::OPTION_KEY, [
'enabled' => true,
'limit' => 5,
] );

add_filter( 'limit_orders_pre_get_remaining_orders', function ( $preempt, $limiter ) use ( $instance, &$called ) {
$this->assertFalse( $preempt, 'The $preempt argument should start as false.' );
$this->assertSame( $instance, $limiter );
$called = true;

return -1;
}, 10, 2 );

$this->assertSame( -1, $instance->get_remaining_orders() );
$this->assertTrue( $called );
}

/**
* @test
* @testdox get_remaining_orders() should be filterable
*/
public function get_remaining_orders_should_cast_the_return_values_as_integers() {
update_option( OrderLimiter::OPTION_KEY, [
'enabled' => true,
'limit' => 5,
] );

add_filter( 'limit_orders_pre_get_remaining_orders', '__return_true' );

$this->assertSame( 1, ( new OrderLimiter() )->get_remaining_orders(), 'TRUE should be cast as 1.' );
}

/**
* @test
* @group Intervals
Expand Down Expand Up @@ -946,4 +986,50 @@ public function count_qualifying_orders_should_not_limit_results() {

$this->assertSame( 5, $method->invoke( $instance ) );
}

/**
* @test
* @testdox count_qualifying_orders() should be filterable
*/
public function count_qualifying_orders_should_be_filterable() {
$instance = new OrderLimiter();
$called = false;
$method = new \ReflectionMethod( $instance, 'count_qualifying_orders' );
$method->setAccessible( true );

update_option( OrderLimiter::OPTION_KEY, [
'enabled' => true,
'limit' => 1,
] );

add_filter( 'limit_orders_pre_count_qualifying_orders', function ( $preempt, $limiter ) use ( $instance, &$called ) {
$this->assertFalse( $preempt );
$this->assertSame( $instance, $limiter );
$called = true;

return 5;
}, 10, 2 );

$this->assertSame( 5, $method->invoke( $instance ) );
$this->assertTrue( $called );
}

/**
* @test
* @testdox Return values from the limit_orders_pre_count_qualifying_orders filter should be cast as integers
*/
public function return_values_from_pre_count_qualifying_orders_should_be_cast_as_int() {
$instance = new OrderLimiter();
$method = new \ReflectionMethod( $instance, 'count_qualifying_orders' );
$method->setAccessible( true );

update_option( OrderLimiter::OPTION_KEY, [
'enabled' => true,
'limit' => 1,
] );

add_filter( 'limit_orders_pre_count_qualifying_orders', '__return_true' );

$this->assertSame( 1, $method->invoke( $instance ) );
}
}

0 comments on commit 2a1cae9

Please sign in to comment.