├── .github
└── ISSUE_TEMPLATE
├── .gitignore
├── README.md
├── docs
├── .sidebar.json
├── README.md
├── developers
│ ├── events-reference.md
│ └── templating.md
├── feature-tour
│ └── multi-add.md
├── get-started
│ ├── installation-setup.md
│ └── requirements.md
└── support
│ ├── credits.md
│ └── get-support.md
├── multiadd
├── MultiAddPlugin.php
├── controllers
│ └── MultiAddController.php
├── resources
│ └── icon.svg
├── services
│ └── MultiAdd_CartService.php
├── templates
│ └── _settings.html
└── variables
│ └── MultiAddVariable.php
└── releases.json
/.github/ISSUE_TEMPLATE:
--------------------------------------------------------------------------------
1 | ### Description
2 |
3 |
4 |
5 | ### Steps to reproduce
6 |
7 | 1.
8 | 2.
9 |
10 | ### Additional info
11 |
12 | - Plugin version:
13 | - Craft version:
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # CRAFT ENVIRONMENT
2 | .env.php
3 | .env.sh
4 | .env
5 |
6 | # COMPOSER
7 | /vendor
8 |
9 | # BUILD FILES
10 | /bower_components/*
11 | /node_modules/*
12 | /build/*
13 | /yarn-error.log
14 |
15 | # MISC FILES
16 | .cache
17 | .DS_Store
18 | .idea
19 | .project
20 | .settings
21 | *.esproj
22 | *.sublime-workspace
23 | *.sublime-project
24 | *.tmproj
25 | *.tmproject
26 | .vscode/*
27 | !.vscode/settings.json
28 | !.vscode/tasks.json
29 | !.vscode/launch.json
30 | !.vscode/extensions.json
31 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | >
2 | > ### Multi Add is for Craft 2 only, and will not make the migration to Craft 3. Mostly because you don't need it anymore! Read about it in our [blog post](https://verbb.io/blog/goodbye-multi-add).
3 | >
4 |
5 | # Multi Add Plugin for Craft CMS
6 |
7 |
8 |
9 | Provides an alternative controller to assist in adding multiple items to your Craft Commerce cart at once.
10 |
11 | Also provides some handy twig tags for adding items, removing a line item, and clearing the cart directly in your templates.
12 |
13 | ## Documentation
14 |
15 | Visit the [Multi Add Plugin page](https://verbb.io/craft-plugins/multi-add) for all documentation, guides, pricing and developer resources.
16 |
17 | ## Support
18 |
19 | Get in touch with us via the [Multi Add Support page](https://verbb.io/craft-plugins/multi-add/support) or by [creating a Github issue](/verbb/multi-add/issues)
20 |
21 |
'; 40 | print_r($items); 41 | echo ''; 42 | } 43 | 44 | if (!isset($items)) { 45 | $errors[] = "No items to add."; 46 | } else { 47 | $itemsToProcess = false; 48 | 49 | // Prevent submission of all 0 qtys 50 | foreach ($items as $key => $item) { 51 | $qty = isset($item['qty']) ? (int)$item['qty'] : 0; 52 | 53 | if ($qty >0){ 54 | $itemsToProcess = true; 55 | break; 56 | } 57 | } 58 | 59 | if (!$itemsToProcess) { 60 | $errors[] = "All items have 0 quantity."; 61 | } 62 | } 63 | 64 | // Do some cart-adding using our new, faster, rollback-able service 65 | if (!$errors) { 66 | $error = ""; 67 | 68 | if (!craft()->multiAdd_cart->multiAddToCart($cart, $items, $error)) { 69 | $errors[] = $error; 70 | } 71 | } 72 | 73 | if ($errors) { 74 | // Try to log referrer in case of misadventure - might help track down odd errors 75 | MultiAddPlugin::logError(craft()->request->getUrlReferrer()); 76 | 77 | foreach ($errors as $error) { 78 | MultiAddPlugin::logError($error); 79 | } 80 | 81 | craft()->urlManager->setRouteVariables(['error' => $errors]); 82 | } else { 83 | craft()->userSession->setFlash('notice', 'Products have been added to cart'); 84 | 85 | // Only redirect if we're not debugging and we haven't submitted by ajax 86 | if (!$debug and !$ajax) { 87 | $this->redirectToPostedUrl(); 88 | } 89 | } 90 | 91 | // Appropriate Ajax responses... 92 | if ($ajax) { 93 | if ($errors) { 94 | $this->returnErrorJson($errors); 95 | } else { 96 | $this->returnJson(['success' => true, 'cart' => $this->cartArray($cart)]); 97 | } 98 | } 99 | 100 | // Not AJAX? We're done! 101 | } 102 | 103 | public function actionUpdateCart() 104 | { 105 | $this->requirePostRequest(); 106 | $cart = craft()->commerce_cart->getCart(); 107 | 108 | $errors = array(); 109 | $items = craft()->request->getPost('items'); 110 | 111 | foreach ($items as $lineItemId => $item) { 112 | $lineItem = craft()->commerce_lineItems->getLineItemById($lineItemId); 113 | 114 | // Fail silently if its not their line item or it doesn't exist. 115 | if (!$lineItem || !$lineItem->id || ($cart->id != $lineItem->orderId)) { 116 | return true; 117 | } 118 | 119 | $lineItem->qty = $item['qty']; 120 | 121 | if (isset($item['options'])) { 122 | ksort($item['options']); 123 | $lineItem->options = $item['options']; 124 | $lineItem->optionsSignature = md5(json_encode($item['options'])); 125 | } 126 | 127 | if (!craft()->commerce_lineItems->updateLineItem($cart, $lineItem, $error)) { 128 | $errors[] = $error; 129 | } 130 | } 131 | 132 | // Set Coupon on Cart 133 | $couponCode = craft()->request->getPost('couponCode'); 134 | 135 | if ($couponCode) { 136 | if (!craft()->commerce_cart->applyCoupon($cart, $couponCode, $error)) { 137 | $errors[] = $error; 138 | } 139 | } 140 | 141 | if ($errors) { 142 | craft()->userSession->setError(Craft::t('Couldn’t update line item: {message}', [ 143 | 'message' => $error 144 | ])); 145 | 146 | MultiAddPlugin::logError('Couldn’t update line item: [$error]'); 147 | } else { 148 | craft()->userSession->setNotice(Craft::t('Items updated.')); 149 | $this->redirectToPostedUrl(); 150 | } 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /multiadd/resources/icon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /multiadd/services/MultiAdd_CartService.php: -------------------------------------------------------------------------------- 1 | db->getCurrentTransaction() === null ? craft()->db->beginTransaction() : null; 24 | 25 | // Saving current cart if it's new and empty 26 | if (!$order->id) { 27 | if (!craft()->commerce_orders->saveOrder($order)) { 28 | if ($transaction !== null) 29 | { 30 | $transaction->rollback(); 31 | } 32 | $error = Craft::t('Error on creating empty cart: ') . print_r($order->getAllErrors(), true); 33 | MultiAddPlugin::logError($error); 34 | throw new Exception($error); 35 | } 36 | } 37 | 38 | // Now loop through all our items and attempt to add them if any one fails, the whole thing fails 39 | $lineItems = []; 40 | 41 | foreach ($items as $key => $item) { 42 | $qty = isset($item['qty']) ? (int)$item['qty'] : 0; 43 | 44 | // Save time by only dealing with items we're actually trying to add 45 | if ($qty > 0) { 46 | 47 | $purchasableId = $item['purchasableId']; 48 | $note = isset($item['note']) ? $item['note'] : ""; 49 | $error = ""; 50 | 51 | // The following line means you can pass arbitrary options like this: items[0][options][note] 52 | $options = isset($item['options']) ? $item['options'] : []; 53 | 54 | // Filling item model 55 | $lineItem = craft()->commerce_lineItems->getLineItemByOrderPurchasableOptions($order->id, $purchasableId, $options); 56 | 57 | if ($lineItem) { 58 | foreach ($order->getLineItems() as $item) { 59 | if ($item->id == $lineItem->id) { 60 | $lineItem = $item; 61 | } 62 | } 63 | 64 | $lineItem->qty += $qty; 65 | } 66 | else { 67 | $lineItem = craft()->commerce_lineItems->createLineItem($purchasableId, $order, $options, $qty); 68 | } 69 | 70 | if ($note) { 71 | $lineItem->note = $note; 72 | } 73 | 74 | $lineItems[] = $lineItem; 75 | } 76 | } 77 | 78 | // Be bold, be brave, assume success...! 79 | $success = true; 80 | 81 | // Raising event 82 | $event = new Event($this, [ 83 | 'lineItems' => $lineItems, 84 | 'order' => $order, 85 | ]); 86 | $this->onBeforeMultiAddToCart($event); 87 | 88 | if (!$event->performAction) { 89 | $success = false; 90 | } 91 | else { 92 | foreach ($lineItems as $lineItem) { 93 | $lineItem->validate(); 94 | $lineItem->purchasable->validateLineItem($lineItem); 95 | 96 | if (!$lineItem->hasErrors()) { 97 | if (!craft()->commerce_lineItems->saveLineItem($lineItem)) { 98 | MultiAddPlugin::logError('Error when saving lineItem: ' . print_r($lineItem->getAllErrors(), true)); 99 | $success = false; 100 | $errors = $lineItem->getAllErrors(); 101 | break; 102 | } 103 | } 104 | else { 105 | MultiAddPlugin::logError('lineItem failed vaildation: ' . print_r($lineItem->getAllErrors(), true)); 106 | $success = false; 107 | $errors = $lineItem->getAllErrors(); 108 | break; 109 | } 110 | 111 | } 112 | } 113 | 114 | if ($success) { 115 | $orderSaveSuccess = craft()->commerce_orders->saveOrder($order); 116 | 117 | if ($orderSaveSuccess) { 118 | if ($transaction !== null) 119 | { 120 | $transaction->commit(); 121 | } 122 | } 123 | else { 124 | MultiAddPlugin::logError('Error when saving order: ' . print_r($order->getAllErrors(), true)); 125 | 126 | $errors = $order->getErrors(); 127 | $error = array_pop($errors); 128 | 129 | if ($transaction !== null) 130 | { 131 | $transaction->rollback(); 132 | } 133 | 134 | return false; 135 | } 136 | 137 | // Raising event 138 | $event = new Event($this, [ 139 | 'lineItems' => $lineItems, 140 | 'order' => $order, 141 | ]); 142 | $this->onMultiAddToCart($event); 143 | 144 | return true; 145 | } 146 | else { 147 | 148 | if ($transaction !== null) 149 | { 150 | $transaction->rollback(); 151 | } 152 | 153 | $errors = $lineItem->getAllErrors(); 154 | $error = array_pop($errors); 155 | 156 | return false; 157 | } 158 | } 159 | 160 | 161 | // Event Handlers 162 | // ========================================================================= 163 | 164 | /** 165 | * Before Event 166 | * Event params: order(Commerce_OrderModel), lineItems (array of Commerce_LineItemModel) 167 | * 168 | * @param \CEvent $event 169 | * 170 | * @throws \CException 171 | */ 172 | public function onBeforeMultiAddToCart(\CEvent $event) 173 | { 174 | $params = $event->params; 175 | 176 | if (empty($params['order']) || !($params['order'] instanceof Commerce_OrderModel)) { 177 | throw new Exception('onBeforeMultiAddToCart event requires "order" param with OrderModel instance'); 178 | } 179 | 180 | if (empty($params['lineItems'])) { 181 | throw new Exception('onBeforeMultiAddToCart event requires "lineItems" param with array of LineItemModel instances'); 182 | } 183 | 184 | $this->raiseEvent('onBeforeMultiAddToCart', $event); 185 | } 186 | 187 | /** 188 | * Event method. 189 | * Event params: order(Commerce_OrderModel), lineItems (array of Commerce_LineItemModel) 190 | * 191 | * @param \CEvent $event 192 | * 193 | * @throws \CException 194 | */ 195 | public function onMultiAddToCart(\CEvent $event) 196 | { 197 | $params = $event->params; 198 | 199 | if (empty($params['order']) || !($params['order'] instanceof Commerce_OrderModel)) { 200 | throw new Exception('onMultiAddToCart event requires "order" param with OrderModel instance'); 201 | } 202 | 203 | if (empty($params['lineItems'])) { 204 | throw new Exception('onMultiAddToCart event requires "lineItems" param with array of LineItemModel instances'); 205 | } 206 | 207 | $this->raiseEvent('onMultiAddToCart', $event); 208 | } 209 | 210 | } 211 | -------------------------------------------------------------------------------- /multiadd/templates/_settings.html: -------------------------------------------------------------------------------- 1 | {% import "_includes/forms" as forms %} 2 | 3 | {% block content %} 4 | 5 |
{{ 'Version: {version}' | t({ version: version }) }}
7 |{{ description }}
8 |Makes all log messages get logged.
21 |(LogLevel::Warning
and LogLevel::Error
are always logged).
{{ "Dumps the POST data back to the page & prevents redirects, so you can see if your form is submitting the correct data. (This setting is ignored if you're submitting by ajax - use your developer console to check that!)" | t }}
35 | 36 |