Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit c191d32
Showing
13 changed files
with
619 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
Copyright © Spicy Web | ||
|
||
Permission is hereby granted to any person obtaining a copy of this software | ||
(the “Software”) to use, copy, modify, merge, publish and/or distribute copies | ||
of the Software, and to permit persons to whom the Software is furnished to do | ||
so, subject to the following conditions: | ||
|
||
1. **Don’t plagiarize.** The above copyright notice and this license shall be | ||
included in all copies or substantial portions of the Software. | ||
|
||
2. **Don’t use the same license on more than one project.** Each licensed copy | ||
of the Software shall be actively installed in no more than one production | ||
environment at a time. | ||
|
||
3. **Don’t mess with the licensing features.** Software features related to | ||
licensing shall not be altered or circumvented in any way, including (but | ||
not limited to) license validation, payment prompts, feature restrictions, | ||
and update eligibility. | ||
|
||
4. **Pay up.** Payment shall be made immediately upon receipt of any notice, | ||
prompt, reminder, or other message indicating that a payment is owed. | ||
|
||
5. **Follow the law.** All use of the Software shall not violate any applicable | ||
law or regulation, nor infringe the rights of any other person or entity. | ||
|
||
Failure to comply with the foregoing conditions will automatically and | ||
immediately result in termination of the permission granted hereby. This | ||
license does not include any right to receive updates to the Software or | ||
technical support. Licensees bear all risk related to the quality and | ||
performance of the Software and any modifications made or obtained to it, | ||
including liability for actual and consequential harm, such as loss or | ||
corruption of data, and any necessary service, repair, or correction. | ||
|
||
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER | ||
LIABILITY, INCLUDING SPECIAL, INCIDENTAL AND CONSEQUENTIAL DAMAGES, WHETHER IN | ||
AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION | ||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
<img src="docs/icon.png" width="60"> | ||
|
||
# ReOrder | ||
|
||
#### Easy reordering of previous Craft Commerce user orders. | ||
|
||
ReOrder makes it easy to allow users to quickly replicate an old order's line items in their existing cart, including the items' quantities, product options and notes. | ||
|
||
## Requirements | ||
|
||
- Craft CMS 3.0.0 or newer | ||
- Craft Commerce 2.0.0-beta.5 or newer | ||
|
||
## Installation | ||
|
||
ReOrder can be installed through the Craft [Plugin Store](https://plugins.craftcms.com/). It can also be set up using Composer: | ||
|
||
``` | ||
composer require spicyweb/craft-reorder | ||
``` | ||
|
||
Then browse to **Settings → Plugins** in the Craft control panel and choose to install ReOrder. | ||
|
||
## Usage | ||
|
||
ReOrder can be configured to either keep or discard the existing cart items when reordering an old order, and to allow or disallow reordering an order if not all of the associated purchasables are still available, whether they have been deleted, disabled or are just out of stock -- in which case, if reordering is allowed, ReOrder will just replicate the available items. | ||
|
||
These options can be configured globally in the Craft control panel and can be overridden on a case-by-case basis in your template files. | ||
|
||
#### Example: retain cart but disallow partial reorders | ||
|
||
```twig | ||
<form method="POST"> | ||
<input type="hidden" name="action" value="reorder/reorder"> | ||
{{ csrfInput() }} | ||
{{ redirectInput('shop/checkout') }} | ||
<input type="hidden" name="order" value="{{ order.number }}"> | ||
<input type="hidden" name="retainCart" value="1"> | ||
<input type="hidden" name="allowPartial" value="0"> | ||
<button type="submit">ReOrder!</button> | ||
</form> | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
{ | ||
"name": "spicyweb/craft-reorder", | ||
"description": "Easy reordering of previous Craft Commerce user orders", | ||
"version": "1.0.0", | ||
"type": "craft-plugin", | ||
"keywords": [ | ||
"craftcms", | ||
"craft-plugin", | ||
"commerce" | ||
], | ||
"license": "proprietary", | ||
"homepage": "https://spicyweb.com.au/", | ||
"support": { | ||
"email": "craft@spicyweb.com.au", | ||
"issues": "https://github.com/spicywebau/craft-reorder/issues" | ||
}, | ||
"require": { | ||
"craftcms/cms": "^3.0.0", | ||
"craftcms/commerce": "^2.0.0-beta.5" | ||
}, | ||
"autoload": { | ||
"psr-4": { | ||
"spicyweb\\reorder\\": "src/" | ||
} | ||
}, | ||
"extra": { | ||
"handle": "reorder", | ||
"name": "ReOrder", | ||
"class": "spicyweb\\reorder\\Plugin", | ||
"developer": "Spicy Web", | ||
"developerUrl": "https://spicyweb.com.au/", | ||
"changelogUrl": "https://github.com/spicywebau/craft-reorder/blob/master/CHANGELOG.md", | ||
"downloadUrl": "https://github.com/spicywebau/craft-reorder/archive/master.zip" | ||
} | ||
} |
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
<?php | ||
namespace spicyweb\reorder; | ||
|
||
use yii\base\Event; | ||
|
||
use Craft; | ||
use craft\base\Plugin as BasePlugin; | ||
use craft\web\twig\variables\CraftVariable; | ||
|
||
use spicyweb\reorder\models\Settings; | ||
|
||
/** | ||
* Class Plugin | ||
* | ||
* @package spicyweb\reorder | ||
* @author Spicy Web <craft@spicyweb.com.au> | ||
* @since 1.0.0 | ||
*/ | ||
class Plugin extends BasePlugin | ||
{ | ||
/** | ||
* @var Plugin | ||
*/ | ||
public static $plugin; | ||
|
||
/** | ||
* @inheritdoc | ||
*/ | ||
public $hasCpSettings = true; | ||
|
||
/** | ||
* @inheritdoc | ||
*/ | ||
public function init() | ||
{ | ||
parent::init(); | ||
|
||
self::$plugin = $this; | ||
|
||
$this->setComponents([ | ||
'methods' => Service::class, | ||
]); | ||
|
||
Event::on(CraftVariable::class, CraftVariable::EVENT_INIT, function(Event $event) | ||
{ | ||
$event->sender->set('reorder', Variable::class); | ||
}); | ||
} | ||
|
||
/** | ||
* @inheritdoc | ||
*/ | ||
protected function createSettingsModel(): Settings | ||
{ | ||
return new Settings(); | ||
} | ||
|
||
/** | ||
* @inheritdoc | ||
*/ | ||
protected function settingsHtml(): string | ||
{ | ||
return Craft::$app->getView()->renderTemplate('reorder/settings', [ | ||
'settings' => $this->getSettings(), | ||
]); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,183 @@ | ||
<?php | ||
namespace spicyweb\reorder; | ||
|
||
use yii\base\Component; | ||
|
||
use Craft; | ||
use craft\base\Element; | ||
use craft\commerce\Plugin as Commerce; | ||
use craft\commerce\elements\Order; | ||
use craft\commerce\elements\Variant; | ||
use craft\commerce\models\LineItem; | ||
|
||
use spicyweb\reorder\enums\LineItemStatus; | ||
|
||
/** | ||
* Class Service | ||
* | ||
* @package spicyweb\reorder | ||
* @author Spicy Web <craft@spicyweb.com.au> | ||
* @since 1.0.0 | ||
*/ | ||
class Service extends Component | ||
{ | ||
/** | ||
* Copies line items from an order to the user's cart. | ||
* | ||
* @param Order $order The order from which the line items will be copied. | ||
* @param bool $allowPartial Whether to allow partially available items, e.g. if there's insufficient stock. | ||
* @return bool Whether the line items were successfully copied. | ||
*/ | ||
public function copyLineItems(Order $order, bool $allowPartial = false): bool | ||
{ | ||
$commerce = Commerce::getInstance(); | ||
$cart = $commerce->getCarts()->getCart(); | ||
|
||
foreach ($order->lineItems as $item) | ||
{ | ||
$itemStatus = $this->_getLineItemStatus($item, $cart->id); | ||
$itemAvailable = $itemStatus === LineItemStatus::Available; | ||
$itemInsufficientStock = $itemStatus === LineItemStatus::InsufficientStock; | ||
$itemAboveMaxQty = $itemStatus === LineItemStatus::AboveMaxQty; | ||
$itemPartiallyAvailable = $allowPartial && ($itemInsufficientStock || $itemAboveMaxQty); | ||
|
||
if ($itemAvailable || $itemPartiallyAvailable) | ||
{ | ||
$purchasable = $item->getPurchasable(); | ||
$qty = $itemAboveMaxQty ? $purchasable->maxQty : | ||
($itemInsufficientStock ? $purchasable->stock : $item->qty); | ||
|
||
$lineItem = $commerce->getLineItems()->resolveLineItem( | ||
$cart->id, | ||
$purchasable->id, | ||
$item->options, | ||
$qty, | ||
$item->note | ||
); | ||
|
||
// If the item had insufficient stock but was already in the cart, its quantity will now exceed the | ||
// available stock and will need to be reset to the available stock. | ||
if ($itemInsufficientStock && $lineItem->qty > $purchasable->stock) | ||
{ | ||
$lineItem->qty = $purchasable->stock; | ||
} | ||
|
||
// If the item quantity exceeded the maximum for that purchasable but was already in the cart, its | ||
// quantity will now exceed the maximum and will need to be reset to the maximum. | ||
if ($itemAboveMaxQty && $lineItem->qty > $purchasable->maxQty) | ||
{ | ||
$lineItem->qty = $purchasable->maxQty; | ||
} | ||
|
||
$cart->addLineItem($lineItem); | ||
} | ||
} | ||
|
||
return $cart->validate() && Craft::$app->getElements()->saveElement($cart, false); | ||
} | ||
|
||
/** | ||
* Checks an order's line items and returns the unavailable items along with why they're unavailable. | ||
* | ||
* @param Order $order The order to check. | ||
* @param int $cartId A cart ID, to check for the quantity of items already in the user's cart. | ||
* return array The line items that are unavailable and why. | ||
*/ | ||
public function getUnavailableLineItems(Order $order, int $cartId = null): array | ||
{ | ||
$unavailableLineItems = []; | ||
|
||
foreach ($order->lineItems as $item) | ||
{ | ||
$itemStatus = $this->_getLineItemStatus($item, $cartId); | ||
|
||
if ($itemStatus !== LineItemStatus::Available) | ||
{ | ||
$itemData = [ | ||
'lineItem' => $item, | ||
'status' => $itemStatus, | ||
]; | ||
|
||
array_push($unavailableLineItems, $itemData); | ||
} | ||
} | ||
|
||
return $unavailableLineItems; | ||
} | ||
|
||
/** | ||
* Checks whether a line item's purchasable is available. | ||
* | ||
* @param LineItem $lineItem The line item to check. | ||
* @param int $cartId A cart ID, to check for the quantity of items already in the user's cart. | ||
* @return string The line item status. | ||
*/ | ||
private function _getLineItemStatus(LineItem $lineItem, int $cartId = null): string | ||
{ | ||
$commerce = Commerce::getInstance(); | ||
$purchasable = $lineItem->getPurchasable(); | ||
|
||
if ($purchasable === null) | ||
{ | ||
return LineItemStatus::Deleted; | ||
} | ||
|
||
if ($purchasable instanceof Element && $purchasable->getStatus() !== Element::STATUS_ENABLED) | ||
{ | ||
return LineItemStatus::Disabled; | ||
} | ||
|
||
if ($purchasable instanceof Variant) | ||
{ | ||
$qty = $lineItem->qty; | ||
|
||
// Make sure any item quantity checks take into account what's already in the user's cart. | ||
if ($cartId !== null) | ||
{ | ||
$cartItem = $commerce->getLineItems()->resolveLineItem( | ||
$cartId, | ||
$purchasable->id, | ||
$lineItem->options, | ||
$qty, | ||
$lineItem->note | ||
); | ||
|
||
// Only count the cart item quantity if the item has an ID (and is therefore actually in the cart). | ||
if ($cartItem->id !== null) | ||
{ | ||
$qty += $cartItem->qty; | ||
} | ||
} | ||
|
||
$minQty = $purchasable->minQty; | ||
$maxQty = $purchasable->maxQty; | ||
|
||
if ($minQty !== null && $qty < $minQty) | ||
{ | ||
return LineItemStatus::BelowMinQty; | ||
} | ||
|
||
if ($maxQty !== null && $qty > $maxQty) | ||
{ | ||
return LineItemStatus::AboveMaxQty; | ||
} | ||
|
||
if (!$purchasable->hasUnlimitedStock) | ||
{ | ||
$stock = (int)$purchasable->stock; | ||
|
||
if ($stock === 0) | ||
{ | ||
return LineItemStatus::OutOfStock; | ||
} | ||
|
||
if ($qty > $stock) | ||
{ | ||
return LineItemStatus::InsufficientStock; | ||
} | ||
} | ||
} | ||
|
||
return LineItemStatus::Available; | ||
} | ||
} |
Oops, something went wrong.