Skip to content

Commit 508d55f

Browse files
committed
added matrix rate shipping method, update methods button in checkout
1 parent 30b622c commit 508d55f

File tree

6 files changed

+535
-37
lines changed

6 files changed

+535
-37
lines changed

app/base/commerce/ShippingMethods/FlatRate.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ public function getShippingFormFieldset(Cart $cart, FAPI\Form $form, array &$for
7777
'type' => 'seamless_container',
7878
]);
7979

80-
$totalCosts = $this->calculateShipping(null, $cart)['shipping_cost'];
80+
$totalCosts = $this->evaluateShippingCosts($cart->getShippingAddress() ?? App::getInstance()->containerMake(Address::class), $cart);
8181
$totalCostsFormatted = App::getInstance()->getUtils()->formatPrice($totalCosts, $cart->getCurrencyCode());
8282

8383
$out->addField('shipping_costs', [
Lines changed: 249 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,249 @@
1+
<?php
2+
3+
/**
4+
* SiteBase
5+
* PHP Version 8.3
6+
*
7+
* @category CMS / Framework
8+
* @package Degami\Sitebase
9+
* @author Mirko De Grandis
10+
* @license MIT https://opensource.org/licenses/mit-license.php
11+
* @link https://github.com/degami/sitebase
12+
*/
13+
14+
namespace App\Base\Commerce\ShippingMethods;
15+
16+
use App\App;
17+
use Degami\PHPFormsApi as FAPI;
18+
use Degami\PHPFormsApi\Accessories\FormValues;
19+
use Degami\PHPFormsApi\Containers\SeamlessContainer;
20+
use App\Base\Models\Cart;
21+
use App\Base\Models\Address;
22+
use App\Base\Abstracts\Commerce\BaseShippingMethod;
23+
24+
class MatrixRate extends BaseShippingMethod
25+
{
26+
/**
27+
* {@inheritdoc}
28+
*/
29+
public function getCode(): string
30+
{
31+
return 'matrixrate';
32+
}
33+
34+
/**
35+
* {@inheritdoc}
36+
*/
37+
public function getName(): string
38+
{
39+
return 'Matrix Rate';
40+
}
41+
42+
protected function getSavedRules() : array
43+
{
44+
return @json_decode(App::getInstance()->getSiteData()->getConfigValue('shipping/matrixrate/matrixrate_rules'), true) ?: [];
45+
}
46+
47+
/**
48+
* {@inheritdoc}
49+
*/
50+
public function getConfigurationForm(FAPI\Form $form, array &$form_state): FAPI\Form
51+
{
52+
$countries = App::getInstance()->getUtils()->getCountriesSelectOptions();
53+
54+
$rules_container = $form->addField('rules', [
55+
'type' => 'tag_container',
56+
'id' => 'matrixrate-rules-target',
57+
]);
58+
59+
$saved_rules = $this->getSavedRules();
60+
$num_rules = 0;
61+
62+
// Determina quante righe mostrare
63+
foreach ($form_state['input_values'] as $key => $value) {
64+
if (preg_match("/^country_[0-9]+$/i", $key)) {
65+
$num_rules++;
66+
}
67+
}
68+
69+
if ($num_rules == 0) {
70+
$num_rules = count($saved_rules);
71+
}
72+
73+
if ($num_rules == 0 || $form->isPartial()) {
74+
$num_rules++;
75+
}
76+
77+
for ($i = 0; $i < $num_rules; $i++) {
78+
$defaults = $saved_rules[$i] ?? [];
79+
80+
$row = $rules_container->addField("rule_$i", [
81+
'type' => 'fieldset',
82+
'title' => "Regola #".($i + 1),
83+
'inner_attributes' => [
84+
'class' => 'row',
85+
]
86+
]);
87+
88+
$row
89+
->addField("country_$i", [
90+
'type' => 'select',
91+
'title' => 'Country',
92+
'container_class' => 'col-sm-6 pb-2',
93+
'options' => ['*' => '-- All --'] + $countries,
94+
'validate' => ['required'],
95+
'default_value' => $defaults['country'] ?? '*',
96+
'weight' => 0,
97+
])
98+
->addField("zip_pattern_$i", [
99+
'type' => 'textfield',
100+
'title' => 'ZIP code pattern (eg: 00*,10*,*)',
101+
'container_class' => 'col-sm-6 pb-2',
102+
'size' => 15,
103+
'default_value' => $defaults['zip_pattern'] ?? '*',
104+
'weight' => 1,
105+
])
106+
->addField("weight_from_$i", [
107+
'type' => 'number',
108+
'title' => 'Weight from (kg)',
109+
'container_class' => 'col-sm-6 pb-2',
110+
'step' => 0.01,
111+
'min' => 0,
112+
'default_value' => $defaults['weight_from'] ?? 0,
113+
'weight' => 2,
114+
])
115+
->addField("weight_to_$i", [
116+
'type' => 'number',
117+
'title' => 'Weight to (kg)',
118+
'container_class' => 'col-sm-6 pb-2',
119+
'step' => 0.01,
120+
'min' => 0,
121+
'default_value' => $defaults['weight_to'] ?? 9999,
122+
'weight' => 3,
123+
])
124+
->addField("cost_$i", [
125+
'type' => 'number',
126+
'title' => 'Cost (€)',
127+
'container_class' => 'col-sm-12 pb-2',
128+
'step' => 0.01,
129+
'min' => 0,
130+
'default_value' => $defaults['cost'] ?? 0,
131+
'weight' => 4,
132+
]);
133+
}
134+
135+
$form->addField('add_rule', [
136+
'type' => 'submit',
137+
'value' => 'Add Rule',
138+
'ajax_url' => App::getInstance()->getAdminRouter()->getUrl('crud.app.base.controllers.admin.json.shippingmethodscallback') . '?action=' . App::getInstance()->getEnvironment()->getRequest()->query->get('action') . '&code=' . App::getInstance()->getEnvironment()->getRequest()->query->get('code'),
139+
'event' => [[
140+
'event' => 'click',
141+
'callback' => [static::class, 'matrixrateFormCallback'],
142+
'target' => 'matrixrate-rules-target',
143+
'effect' => 'fade',
144+
'method' => 'replace',
145+
]],
146+
]);
147+
148+
return $form;
149+
}
150+
151+
public static function matrixrateFormCallback(FAPI\Form $form)
152+
{
153+
return $form->getField('rules');
154+
}
155+
156+
/**
157+
* {@inheritdoc}
158+
*/
159+
public function formSubmitted(FAPI\Form $form, array &$form_state): void
160+
{
161+
$values = $form_state['input_values'];
162+
$rules = [];
163+
164+
foreach ($values as $key => $val) {
165+
if (preg_match('/^country_([0-9]+)$/', $key, $matches)) {
166+
$i = $matches[1];
167+
$rules[] = [
168+
'country' => $values["country_$i"] ?? '*',
169+
'zip_pattern' => $values["zip_pattern_$i"] ?? '*',
170+
'weight_from' => (float) ($values["weight_from_$i"] ?? 0),
171+
'weight_to' => (float) ($values["weight_to_$i"] ?? 9999),
172+
'cost' => (float) ($values["cost_$i"] ?? 0),
173+
];
174+
}
175+
}
176+
177+
App::getInstance()->getSiteData()->setConfigValue('shipping/matrixrate/active', $values['active']);
178+
App::getInstance()->getSiteData()->setConfigValue('shipping/matrixrate/matrixrate_rules', json_encode($rules));
179+
}
180+
181+
/**
182+
* {@inheritdoc}
183+
*/
184+
public function evaluateShippingCosts(Address $shippingAddress, Cart $cart): float
185+
{
186+
$rules = $this->getSavedRules();
187+
if (empty($rules)) {
188+
return 0.0;
189+
}
190+
191+
$country = strtoupper($shippingAddress?->getCountryCode() ?? '*');
192+
$zip = $shippingAddress?->getPostcode() ?? '';
193+
$weight = $cart->getBillableWeight();
194+
195+
foreach ($rules as $rule) {
196+
$countryMatch = ($rule['country'] === '*' || strtoupper($rule['country']) === $country);
197+
$zipMatch = ($rule['zip_pattern'] === '*' || fnmatch($rule['zip_pattern'], $zip));
198+
$weightMatch = ($weight >= $rule['weight_from'] && $weight <= $rule['weight_to']);
199+
200+
if ($countryMatch && $zipMatch && $weightMatch) {
201+
return (float) $rule['cost'];
202+
}
203+
}
204+
205+
return 0.0;
206+
}
207+
208+
/**
209+
* {@inheritdoc}
210+
*/
211+
public function getShippingFormFieldset(Cart $cart, FAPI\Form $form, array &$form_state): FAPI\Interfaces\FieldsContainerInterface
212+
{
213+
/** @var SeamlessContainer $out */
214+
$out = $form->getFieldObj('matrixrate', ['type' => 'seamless_container']);
215+
216+
$totalCosts = $this->evaluateShippingCosts($cart->getShippingAddress() ?? App::getInstance()->containerMake(Address::class), $cart);
217+
$formatted = App::getInstance()->getUtils()->formatPrice($totalCosts, $cart->getCurrencyCode());
218+
219+
$out->addField('shipping_costs', [
220+
'type' => 'markup',
221+
'value' => App::getInstance()->getUtils()->translate("Shipping costs: %s", [$formatted]),
222+
]);
223+
224+
return $out;
225+
}
226+
227+
/**
228+
* {@inheritdoc}
229+
*/
230+
public function calculateShipping(?FormValues $values, Cart $cart): array
231+
{
232+
$cost = 0.0;
233+
$shippingAddress = $cart->getShippingAddress();
234+
235+
if ($shippingAddress) {
236+
$cost = $this->evaluateShippingCosts($shippingAddress, $cart);
237+
}
238+
239+
return [
240+
'shipping_cost' => $cost,
241+
'additional_data' => ['calculate_when' => time()],
242+
];
243+
}
244+
245+
public function showEvenIfNotCheapest() : bool
246+
{
247+
return true;
248+
}
249+
}

app/base/controllers/Admin/Commerce/ShippingMethods.php

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -307,13 +307,21 @@ public function formValidate(FAPI\Form $form, &$form_state): bool|string
307307
public function formSubmitted(FAPI\Form $form, &$form_state): mixed
308308
{
309309
$code = $this->getRequest()->query->get('code');
310-
$values = $form->values()->toArray();
311310

312-
unset($values['action']);
313-
unset($values['button']);
311+
/** @var ShippingMethodInterface $shippingMethod */
312+
$shippingMethod = $this->getShippingMethods()[$code];
314313

315-
foreach ($values as $key => $value) {
316-
$this->getSiteData()->setConfigValue('shipping/'.$code.'/'.$key, $value);
314+
if (method_exists($shippingMethod, 'formSubmitted')) {
315+
call_user_func_array([$shippingMethod, 'formSubmitted'], [$form, $form_state]);
316+
} else {
317+
$values = $form->values()->toArray();
318+
319+
unset($values['action']);
320+
unset($values['button']);
321+
322+
foreach ($values as $key => $value) {
323+
$this->getSiteData()->setConfigValue('shipping/'.$code.'/'.$key, $value);
324+
}
317325
}
318326

319327
return $this->refreshPage();
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
<?php
2+
3+
/**
4+
* SiteBase
5+
* PHP Version 8.3
6+
*
7+
* @category CMS / Framework
8+
* @package Degami\Sitebase
9+
* @author Mirko De Grandis <degami@github.com>
10+
* @license MIT https://opensource.org/licenses/mit-license.php
11+
* @link https://github.com/degami/sitebase
12+
*/
13+
14+
namespace App\Base\Controllers\Admin\Json;
15+
16+
use Degami\Basics\Exceptions\BasicException;
17+
use DI\DependencyException;
18+
use DI\NotFoundException;
19+
use Exception;
20+
use Phpfastcache\Exceptions\PhpfastcacheSimpleCacheException;
21+
use App\Base\Abstracts\Controllers\AdminJsonPage;
22+
use App\Base\Routing\RouteInfo;
23+
use Degami\PHPFormsApi as FAPI;
24+
use App\Base\Controllers\Admin\Commerce\ShippingMethods as ShippingMethodsController;
25+
use Symfony\Component\HttpFoundation\Response;
26+
27+
/**
28+
* Matrix Rule Form AJAX callback
29+
*/
30+
class ShippingmethodsCallback extends AdminJsonPage
31+
{
32+
/**
33+
* @var FAPI\Form form object
34+
*/
35+
protected FAPI\Form $form;
36+
37+
/**
38+
* {@inheritdoc}
39+
*
40+
* @return string
41+
*/
42+
public static function getAccessPermission(): string
43+
{
44+
return 'administer_orders';
45+
}
46+
47+
/**
48+
* returns an empty form
49+
*
50+
* @param FAPI\Form $form
51+
* @param array &$form_state
52+
* @return FAPI\Form
53+
*/
54+
public function emptyForm(FAPI\Form $form, &$form_state): FAPI\Form
55+
{
56+
return $form;
57+
}
58+
59+
/**
60+
* {@inheritdoc}
61+
*
62+
* @param RouteInfo|null $route_info
63+
* @param array $route_data
64+
* @return Response
65+
* @throws BasicException
66+
* @throws PhpfastcacheSimpleCacheException
67+
* @throws DependencyException
68+
* @throws NotFoundException
69+
*/
70+
public function process(?RouteInfo $route_info = null, $route_data = []): Response
71+
{
72+
try {
73+
$shipping_methods_controller = $this->containerMake(ShippingMethodsController::class);
74+
$this->form = $shipping_methods_controller->getForm();
75+
$out = json_decode($this->form->render());
76+
77+
if ($out == null) {
78+
$out = ['html' => '', 'js' => '', 'is_submitted' => false];
79+
}
80+
81+
return $this->getUtils()->createJsonResponse($out);
82+
} catch (Exception $e) {
83+
return $this->getUtils()->exceptionJson($e);
84+
}
85+
}
86+
87+
//not used on this class
88+
89+
/**
90+
* {@inheritdoc}
91+
*
92+
* @return array
93+
*/
94+
protected function getJsonData(): array
95+
{
96+
return [];
97+
}
98+
}

0 commit comments

Comments
 (0)