Git Product home page Git Product logo

limit-orders's Introduction

Limit Orders for WooCommerce

Build Status WordPress Plugin Version

While many stores would be thrilled to have a never-ending order queue, some store owners are faced with the opposite problem: how can I make sure I don't get overwhelmed by too many orders?

Limit Orders for WooCommerce lets you limit the number of orders your store will accept per day, week, or month, while giving you full control over the messaging shown to your customers when orders are no longer being accepted. Once limiting is in effect, "Add to Cart" buttons and the checkout screens will automatically be disabled.

A notice at the top of a WooCommerce catalog with the message "Due to increased demand, new orders will be temporarily suspended until March 27, 2020."

Requirements

  • WordPress 5.7 or newer
  • WooCommerce 6.9 or newer
  • PHP 7.4 or newer

Installation

  1. Download and extract a .zip archive of the plugin (or clone this repository) into your site's plugins directory (wp-content/plugins/ by default).
  2. Activate the plugin through the Plugins screen in WP Admin.
  3. Configure the plugin.

Configuration

Configuration for Limit Orders for WooCommerce is available through WooCommerce › Settings › Order Limiting:

The settings screen for Limit Orders for WooCommerce

⚠️ Please be aware that any changes made to the settings will take effect immediately.

For example, if you're using an hourly interval and switch it to daily, the plugin will re-calculate whether or not to disable ordering based on the number of orders received since the start of the current day (midnight, by default).

If you need to clear the cached order count, you may do so via WooCommerce › Status › Tools › Reset Order Limiting within WP Admin.

General settings

These settings determine how and when order limiting should take effect.

Enable Order Limiting
Check this box to enable order limiting.
Should you ever want to disable the limiting temporarily, simply uncheck this box.
Maximum # of orders
Customers will be unable to checkout after this number of orders are received.
Shop owners will still be able to create orders via WP Admin, even after the limit has been reached.
Interval
How often the limit is reset. By default, this can be "hourly", daily", "weekly", or "monthly".
When choosing "weekly", the plugin will respect the value of the store's "week starts on" setting.

Messaging

Limit Orders for WooCommerce lets you customize the messages shown on the front-end of your store:

Customer notice
This notice will be added to all WooCommerce pages on the front-end of your store once the limit has been reached.
"Place Order" button
If a customer happens to visit the checkout screen after the order limit has been reached, this message will replace the "Place Order" button.
Checkout error message
If a customer submits an order after the order limits have been reached, this text will be used in the resulting error message.

Variables

In any of these messages, you may also use the following variables:

{limit}
The maximum number of orders accepted.
{current_interval}
The date the current interval started.
{current_interval:date}
An alias of {current_interval}
{current_interval:time}
The time the current interval started.
{next_interval}
The date the next interval will begin (e.g. when orders will be accepted again).
{next_interval:date}
An alias of {next_interval}
{next_interval:time}
The time the next interval will begin.
{timezone}
The store's timezone, e.g. "PST", "EDT", etc. This will automatically update based on Daylight Saving Time.

Dates and times will be formatted according to the "date format" and "time format" settings for your store, respectively.

If you would like to add custom placeholders, see Adding Custom Placeholders below.

Customizing plugin behavior

Limit Orders for WooCommerce includes a number of actions and filters that enable store owners to modify the plugin's behavior.

Examples of common customizations are included below.

Adding custom intervals

The plugin includes a few intervals by default:

  1. Hourly (resets at the top of every hour)
  2. Daily (resets every day)
  3. Weekly (resets every week, respecting the store's "Week Starts On" setting)
  4. Monthly (resets on the first of the month)

If your store needs a custom interval, you may add them using filters built into the plugin.

You may also use these gists, which define custom plugins that can be run alongside Limit Orders:

Example: Reset Limits Annually

Let's say your store can only accept a certain number of orders in a year.

You may accomplish this by adding the following code into your theme's functions.php file or (preferably) by saving it as a custom plugin:

<?php
/**
 * Plugin Name: Limit Orders for WooCommerce - Annual Intervals
 * Description: Add a "Annually" option to Limit Orders for WooCommerce.
 * Author:      Nexcess
 * Author URI:  https://nexcess.net
 */

/**
 * Add "Annually" to the list of intervals.
 *
 * @param array $intervals Available time intervals.
 *
 * @return array The filtered array of intervals.
 */
add_filter( 'limit_orders_interval_select', function ( $intervals ) {
	// Return early if it already exists.
	if ( isset( $intervals['annually'] ) ) {
		return $intervals;
	}

	$intervals['annually'] = __( 'Annually (Resets on the first of the year)', 'limit-orders' );

	return $intervals;
} );

/**
 * Get a DateTime object representing the beginning of the current year.
 *
 * @param \DateTime $start    The DateTime representing the start of the current interval.
 * @param string    $interval The type of interval being calculated.
 *
 * @return \DateTime A DateTime object representing the top of the current hour or $start, if the
 *                   current $interval is not "annually".
 */
add_filter( 'limit_orders_interval_start', function ( $start, $interval ) {
	if ( 'annually' !== $interval ) {
		return $start;
	}

	// Happy New Year!
	return ( new \DateTime( 'now' ) )
		->setDate( (int) $start->format( 'Y' ), 1, 1 )
		->setTime( 0, 0, 0 );
}, 10, 2 );

/**
 * Filter the DateTime at which the next interval should begin.
 *
 * @param \DateTime $start    A DateTime representing the start time for the next interval.
 * @param \DateTime $current  A DateTime representing the beginning of the current interval.
 * @param string    $interval The specified interval.
 *
 * @return \DateTime The DateTime at which the next interval should begin, or $start if the
 *                   current $interval is not "annually".
 */
add_filter( 'limit_orders_next_interval', function ( $start, $current, $interval ) {
	if ( 'annually' !== $interval ) {
		return $start;
	}

	return $current->add( new \DateInterval( 'P1Y' ) );
}, 10, 3 );

Adding Custom Placeholders

The placeholders used for customer-facing messaging are editable via the limit_orders_message_placeholders filter.

For example, imagine we wanted to add a placeholder for the WooCommerce store name. The code to accomplish this may look like:

/**
 * Append a {store_name} placeholder.
 *
 * @param array $placeholders The currently-defined placeholders.
 *
 * @return array The filtered array of placeholders.
 */
add_filter( 'limit_orders_message_placeholders', function ( $placeholders ) {
	$placeholders['{store_name}'] = get_option( 'blogname' );

	return $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), 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:

/**
 * 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:

/**
 * 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;
} );

limit-orders's People

Contributors

bswatson avatar stevegrunwell avatar szepeviktor avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

limit-orders's Issues

Limit based on Delivery Date rather than Order Date

Is your feature request related to a problem? Please describe.
I’ve got a Pizzeria that can only make 50 pizzas per day roughly, we’re limiting the orders to 25 as there’s an average of 2-3 pizzas per order.

We’d like to set the limit by the delivery day rather than order day, is this possible?

I’m using a woo extension, WooCommerce Order Delivery

https://woocommerce.com/products/woocommerce-order-delivery/

I’ve created custom shipping methods to allow me to deliver to certain areas on certain days but then I can’t limit the amount of orders that then go out on each delivery day so hitting a bit of a brick wall.

Describe the solution you'd like
Integrate with WooCommerce Order Delivery to provide the ability to limit orders by delivery date.

Additional context
Original request

{timezone} Placeholder

Since #18 is introducing intervals that last less than a day, timezones might come more into play. As such, it would be helpful to have a {timezone} placeholder for customer-facing messaging.

Error thrown in with usage of string in closure

spl_autoload_register( function ( string $class ) {

[20-Apr-2020 15:51:12 UTC] PHP Catchable fatal error: Argument 1 passed to WP_CLI\Runner::Nexcess\LimitOrders{closure}() must be an instance of Nexcess\LimitOrders\string, string given in /chroot/home/aa5b74b2/ssldelay.watsonbot.com/html/wp-content/plugins/limit-orders/limit-orders.php on line 24

Improve release workflow

This plugin is already using the 10up/action-wordpress-plugin-deploy GitHub Action for deploying new tags to the WordPress.org plugin repo, but there are still a number of manual steps.

Ideally, the process would look like this:

  1. Create a new release/vX.X.X branch off of develop, bumping versions, updating changelogs, et al
  2. Open a new PR against master with the following format:
    • Title: Version X.X.X
    • Body: (The contents that should eventually end up in the release)
    • Label: release
  3. Upon merge, create a new release using the title and body of the PR, along with the v.X.X.X portion of the PR's branch name
  4. As the tag is created, let the 10up workflow deploy the new version to the WordPress.org plugin repo
  5. Automatically merge master into develop, ensuring the branches are even (or, at the very least, that develop isn't behind)

Some actions that may be helpful (opting to use official GitHub actions when possible):

Fire an action when order limits are active

When OrderLimiter::disable_ordering() is called, an action should be fired to let other plugins get in on the action.

/**
 * Fires when ordering has been disabled.
 *
 * @param \Nexcess\LimitOrders\OrderLimiter $limiter The order limiter instance.
 */
do_action( 'limit_orders_disable_ordering', $this );

Incompatibility with WooCommerce recommended post storage option

I've installed the plugin and I am getting an incompatibility error.

"This plugin is incompatible with the enabled WooCommerce feature 'High-Performance order storage', it shouldn't be activated."

In WooCommerce settings under Advanced tab > Features there are two choices for "Order Date Storage":

  • WordPress posts storage (legacy)
  • High-performance order storage (recommended)

Plus an additional option:

  • Enable compatibility mode (synchronizes orders to the posts table).

The default and recommended option "High-performance order storage" is incompatible with your plugin. What do you recommend as the best option?

  • Plugin version 2.0.0
  • WordPress version 6.4.3
  • WooCommerce version 8.6.1
  • PHP version 8.2

Should we let WooCommerce empty the cart once limits are reached?

WordPress.org user intriguemediaweb raised an interesting question yesterday: how does a shop owner keep WooCommerce from emptying customer carts once the order threshold has been met?

Digging in, WooCommerce runs WC_Cart_Session::get_cart_from_session() on the "wp_loaded" hook, where it loads up the current cart and filters out anything that is no longer available for purchase.

Meanwhile, Limit Orders uses add_filter('woocommerce_is_purchasable', '__return_false'); as one of the components of OrderLimiter::limit_ordering(), as it prevents WooCommerce from showing the "Add to Cart" button.

The side-effect here is that once the threshold has been hit, the limit_ordering() callback goes into effect, which in-turn causes get_cart_from_session() to remove the now "unpurchasable" items.

I've come up with a filter-based work-around to help in the short term, but it's worth considering if there's a better filter we can use to disable the "Add to Cart" buttons without clearing the carts. Perhaps the answer is to not remove the buttons at all, letting users populate carts but not check out?

Missing Plugin Headers

Describe the bug
A few plugin headers are not provided, and therefore some useful information is not available.

To Reproduce
Steps to reproduce the behavior:

  1. Go to WP Admin
  2. Click on WooCommerce -> Status
  3. Scroll down to Active Plugins
  4. See Limit Orders for WooCommerce does not have a plugin link to GitHub.

Expected behavior
Plugin URI, Requires at least, Requires PHP, License, and License URI should have information provided.

Versions:

  • Plugin version 1.2.1
  • WordPress version 5.4.1
  • WooCommerce version 4.1.0
  • PHP version 7.4.5

Manual resetting of limits

If a store needs to reset the limiter for any reason (examples: switching from a daily interval down to hourly, a sudden influx of capacity, etc.), they currently need to manually remove the "limit_orders_order_count" transient.

It would be helpful if there was a button right on the settings screen to enable store owners to reset the limit for the current interval.

00:00:00 to midnight conversions

In places where intervals either start or end at midnight (e.g. a daily interval or an hourly interval that started at 11:00pm and will end at 12:00am), it might be nice to automatically convert these values to user-friendly strings like "midnight".

For example: "Please check back after midnight" v. "Please check back after 12:00am".

The same reasoning might also apply to "noon" v. "12:00pm".

If we move forward with this, we should also make sure that the Admin::admin_notice() method is updated to match.

Automatically reset limits when settings are changed

Let's assume we have a store that limits the number of orders it'll accept in a day (10, for the sake of argument): so far, the store has 3 orders today, and the store owner decides they want to switch to an hourly limit of 2.

At this point, OrderLimiter::get_remaining_orders() would return 0 (the lowest value it can return), as the value of the "limit_orders_order_count" transient would still be 3 (as it's set to expire at midnight), but OrderLimiter::getLimit() would return 2.

Now, consider if the store had only received 1 order for the day before switching to the 2/hr limit: "limit_orders_order_count" would be 1, meaning the store would accept one more order, then OrderLimiter::regenerate_transient() would set the new expiration of the transient to the top of the next hour.

The issue lies with some data (the number of orders made within the current interval) being cached, whereas the settings are read from the [autoloaded] option on every request.

To clear up this ambiguity, the "limit_orders_order_count" transient should be cleared any time the "limit_orders" option is modified (as in, the value actually changes). Documentation and the UI should be updated as well to explain this.

Limit orders based on amount of orders from interval

Add an ability to limit based on orders amount from selected interval.
Example: Owner of the shop cannot sell products with total amount higher than 600$.

Plugin should have additional setting "Maximum amount of orders".
Plugin should calculate conditions (for count and amount) and block checkout screen if limit is reached.

There is still a problem what do if user want to but few products and limit will be reached after finalization.
If plugin cannot react on this scenario then admin has to set lower limit to handle big orders.

Show tab only for admin

Describe the solution you'd like
Hi, is there anyway to hide the Order Limit tab to all roles but admin?

Describe alternatives you've considered
I'm renting a woocommerce store, and based on order limits, he can use it for free or pay for it. But the limit order tab should not be enable for shop manager;

0d2afe63-deba-47f6-92fe-b85f2392f5b4

Placeholder displays should localize the timezones

When displaying admin and customer-facing notices, the times are currently based on UTC, not the store's local timezone.

Example output on a store in the America/Detroit timezone using the "hourly" interval.

Admin notice, indicating a reset at 4:00am
Customer-facing notice, indicating a reset at 4:00am

Transient is being regenerated before WooCommerce post types are registered

When regenerating the "limit_orders_order_count" transient, we're using the following criteria:

$orders = wc_get_orders( [
    'type'         => wc_get_order_types( 'order-count' ),
    'date_created' => '>=' . $this->get_interval_start()->getTimestamp(),
    'return'       => 'ids',
    'limit'        => max( $this->get_limit(), 1000 ),
] );

However, if wc_get_order_types() is called before the custom post types for orders are defined ("init" at priority 5), the "type" query arg will be empty, resulting in 0 qualifying orders.

I believe this to be the cause of https://wordpress.org/support/topic/stops-working-randomly-3/ — if something is clearing transients, the next request will cause Limit Orders to attempt to regenerate it which may happen before the post types are registered, resulting in a qualifying order count of 0 (thereby preventing order limiting from taking effect).

The solution appears to be two fold:

  1. Defer the loading of Limit Orders until "init" (while checking to see if "woocommerce_loaded" has happened)
  2. If wc_get_order_types( 'order-count' ) returns an empty array, return early and do not create the transient

Less-than-helpful messaging when using <24hr intervals

If a store is using intervals < 24hr, the {current_interval} and {next_interval} placeholders use the date format instead of time.

For example, imagine that a store using hourly intervals has reached its limit for the 1:00pm hour on September 17, 2020:

Expected:

Due to increased demand, new orders will be temporarily suspended until 2:00pm.

Actual:

Due to increased demand, new orders will be temporarily suspended until September 17, 2020.

If the next interval is < 24hr away, the time format should be used instead.

WooCommerce System Status Report

It would be helpful if we could include details from this plugin in the WooCommerce System Status Report, as that's one of the more common ways to get support for WooCommerce sites.

Output might look something like this:

### Limit Orders ###

Enabled: Yes
Limit: 1000
Submitted orders: 640
Remaining orders: 360
Interval: Daily
Interval start: 2020-04-15T00:00:00-04:00
Interval resets: 2020-04-16T00:00:00-04:00

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.