1
|
|
|
<?php |
2
|
|
|
/** |
3
|
|
|
* Adds pricing to Buyables |
4
|
|
|
* |
5
|
|
|
* |
6
|
|
|
*/ |
7
|
|
|
|
8
|
|
|
class CountryPrice_BuyableExtension extends DataExtension |
9
|
|
|
{ |
10
|
|
|
private static $db = array( |
11
|
|
|
"AllCountries" => "Boolean" |
12
|
|
|
); |
13
|
|
|
|
14
|
|
|
private static $many_many = array( |
15
|
|
|
"IncludedCountries" => "EcommerceCountry", |
16
|
|
|
"ExcludedCountries" => "EcommerceCountry" |
17
|
|
|
); |
18
|
|
|
|
19
|
|
|
private static $allow_usage_of_distributor_backup_country_pricing = false; |
20
|
|
|
|
21
|
|
|
public function updateCMSFields(FieldList $fields) |
22
|
|
|
{ |
23
|
|
|
$excludedCountries = EcommerceCountry::get() |
24
|
|
|
->filter(array("DoNotAllowSales" => 1, "AlwaysTheSameAsID" => 0)); |
25
|
|
|
if ($excludedCountries->count()) { |
26
|
|
|
$excludedCountries = $excludedCountries->map('ID', 'Name')->toArray(); |
27
|
|
|
} |
28
|
|
|
$includedCountries = EcommerceCountry::get() |
29
|
|
|
->filter(array("DoNotAllowSales" => 0, "AlwaysTheSameAsID" => 0)); |
30
|
|
|
if ($includedCountries->count()) { |
31
|
|
|
$includedCountries = $includedCountries->map('ID', 'Name')->toArray(); |
32
|
|
|
} |
33
|
|
|
if ($this->owner->AllCountries) { |
|
|
|
|
34
|
|
|
$tabs = new TabSet( |
35
|
|
|
'Countries', |
36
|
|
|
new Tab( |
37
|
|
|
'Include', |
38
|
|
|
new CheckboxField("AllCountries", "All Countries") |
39
|
|
|
) |
40
|
|
|
); |
41
|
|
|
} else { |
42
|
|
|
$tabs = new TabSet( |
43
|
|
|
'Countries', |
44
|
|
|
$includeTab = new Tab( |
45
|
|
|
'Include', |
46
|
|
|
new CheckboxField("AllCountries", "All Countries") |
47
|
|
|
), |
48
|
|
|
$excludeTab = new Tab( |
49
|
|
|
'Exclude' |
50
|
|
|
) |
51
|
|
|
); |
52
|
|
View Code Duplication |
if (count($excludedCountries)) { |
|
|
|
|
53
|
|
|
$includeTab->push( |
54
|
|
|
new LiteralField( |
55
|
|
|
"ExplanationInclude", |
56
|
|
|
"<p>Products are not available in the countries listed below. You can include sales of <i>".$this->owner->Title."</i> to new countries by ticking the box(es) next to any country.</p>" |
|
|
|
|
57
|
|
|
) |
58
|
|
|
); |
59
|
|
|
$includeTab->push( |
60
|
|
|
new CheckboxSetField('IncludedCountries', '', $excludedCountries) |
61
|
|
|
); |
62
|
|
|
} |
63
|
|
View Code Duplication |
if (count($includedCountries)) { |
|
|
|
|
64
|
|
|
$excludeTab->push( |
65
|
|
|
new LiteralField("ExplanationExclude", "<p>Products are available in all countries listed below. You can exclude sales of <i>".$this->owner->Title."</i> from these countries by ticking the box next to any of them.</p>") |
66
|
|
|
); |
67
|
|
|
$excludeTab->push( |
68
|
|
|
new CheckboxSetField('ExcludedCountries', '', $includedCountries) |
69
|
|
|
); |
70
|
|
|
} |
71
|
|
|
} |
72
|
|
|
|
73
|
|
|
|
74
|
|
|
if ($this->owner->ID) { |
|
|
|
|
75
|
|
|
//start cms_object hack |
76
|
|
|
CountryPrice::set_cms_object($this->owner); |
77
|
|
|
//end cms_object hack |
78
|
|
|
$source = $this->owner->AllCountryPricesForBuyable(); |
79
|
|
|
$table = new GridField( |
80
|
|
|
'CountryPrices', |
81
|
|
|
'Country Prices', |
82
|
|
|
$source, |
83
|
|
|
GridFieldConfig_RecordEditor::create() |
84
|
|
|
); |
85
|
|
|
$tab = 'Root.Countries.Pricing'; |
86
|
|
|
$fields->addFieldsToTab( |
87
|
|
|
$tab, |
88
|
|
|
array( |
89
|
|
|
NumericField::create('Price', 'Main Price', '', 12), |
90
|
|
|
HeaderField::create('OtherCountryPricing', "Prices for other countries"), |
91
|
|
|
$table |
92
|
|
|
) |
93
|
|
|
); |
94
|
|
|
} |
95
|
|
|
|
96
|
|
|
$fields->addFieldToTab('Root.Countries', $tabs); |
97
|
|
|
} |
98
|
|
|
|
99
|
|
|
private $debug = false; |
100
|
|
|
|
101
|
|
|
/** |
102
|
|
|
* This is called from /ecommerce/code/Product |
103
|
|
|
* returning NULL is like returning TRUE OR FALSE, i.e. ignore this. |
104
|
|
|
* @param Member (optional) $member |
|
|
|
|
105
|
|
|
* @param bool (optional) $checkPrice |
106
|
|
|
* @return false | null |
|
|
|
|
107
|
|
|
*/ |
108
|
|
|
public function canPurchaseByCountry(Member $member = null, $checkPrice = true, $countryCode = '') |
|
|
|
|
109
|
|
|
{ |
110
|
|
|
$countryObject = CountryPrice_EcommerceCountry::get_real_country($countryCode); |
111
|
|
|
if ($countryObject) { |
112
|
|
|
if ($this->debug) { |
113
|
|
|
debug::log('found country object: '.$countryObject->Code); |
114
|
|
|
} |
115
|
|
|
$countryCode = $countryObject->Code; |
116
|
|
|
} |
117
|
|
|
if ($countryCode == '') { |
118
|
|
|
if ($this->debug) { |
119
|
|
|
debug::log('There is no country Code! '); |
120
|
|
|
} |
121
|
|
|
|
122
|
|
|
//we can not decide |
123
|
|
|
return null; |
124
|
|
|
} else { |
125
|
|
|
$canSell = false; |
126
|
|
|
|
127
|
|
|
//easy ... overrules all ... |
128
|
|
|
if ($this->owner->AllCountries) { |
|
|
|
|
129
|
|
|
//is there a valid price ??? |
130
|
|
|
if ($this->debug) { |
131
|
|
|
debug::log('All countries applies - updated ... new price = '.floatval($this->owner->updateCalculatedPrice())); |
132
|
|
|
} |
133
|
|
|
$canSell = true; |
134
|
|
|
} else { |
135
|
|
|
|
136
|
|
|
|
137
|
|
|
//excluded first... |
138
|
|
|
$excluded = $this->owner->getManyManyComponents('ExcludedCountries', "\"Code\" = '$countryCode'")->Count(); |
139
|
|
|
if ($excluded) { |
140
|
|
|
if ($this->debug) { |
141
|
|
|
debug::log('excluded country'); |
142
|
|
|
} |
143
|
|
|
|
144
|
|
|
//no! |
145
|
|
|
return false; |
146
|
|
|
} |
147
|
|
|
|
148
|
|
|
//default country is included by default ... |
149
|
|
|
if ($countryCode == EcommerceConfig::get('EcommerceCountry', 'default_country_code')) { |
150
|
|
|
if ($this->debug) { |
151
|
|
|
debug::log('we are in the default country! exiting now ... '); |
152
|
|
|
} |
153
|
|
|
$canSell = true; |
154
|
|
|
} elseif ($this->owner->IncludedCountries()->count()) { |
155
|
|
|
$included = $this->owner->getManyManyComponents('IncludedCountries', "\"Code\" = '$countryCode'")->Count(); |
156
|
|
|
if ($included) { |
157
|
|
|
if ($this->debug) { |
158
|
|
|
debug::log('In included countries'); |
159
|
|
|
} |
160
|
|
|
//null basically means - ignore ... |
161
|
|
|
$canSell = true; |
162
|
|
|
} else { |
163
|
|
|
//if countries are included and the current country is not included ... |
164
|
|
|
return false; |
165
|
|
|
} |
166
|
|
|
} |
167
|
|
|
} |
168
|
|
|
if ($this->debug) { |
169
|
|
|
debug::log('the product is '.($canSell ? '' : 'NOT ').' for sale - lets check price ... '); |
170
|
|
|
} |
171
|
|
|
|
172
|
|
|
//is there a valid price ??? |
173
|
|
|
$countryPrice = $this->owner->getCalculatedPrice(true); |
174
|
|
|
if ($this->debug) { |
175
|
|
|
debug::log('nothing applies, but we have a country price... '.$countryPrice); |
176
|
|
|
} |
177
|
|
|
|
178
|
|
|
return floatval($countryPrice) > 0 ? null : false; |
179
|
|
|
} |
180
|
|
|
} |
181
|
|
|
|
182
|
|
|
/** |
183
|
|
|
* |
184
|
|
|
* @return DataList |
185
|
|
|
*/ |
186
|
|
|
public function AllCountryPricesForBuyable() |
187
|
|
|
{ |
188
|
|
|
$filterArray = array("ObjectClass" => ClassInfo::subclassesFor($this->ownerBaseClass), "ObjectID" => $this->owner->ID); |
|
|
|
|
189
|
|
|
return CountryPrice::get() |
190
|
|
|
->filter($filterArray); |
191
|
|
|
} |
192
|
|
|
|
193
|
|
|
/** |
194
|
|
|
* returns all the prices for a particular country and/or currency |
195
|
|
|
* for the object |
196
|
|
|
* @param string (optional) $country |
197
|
|
|
* @param string (optional) $currency |
198
|
|
|
* @return DataList |
199
|
|
|
*/ |
200
|
|
|
public function CountryPricesForCountryAndCurrency($countryCode = null, $currency = null) |
201
|
|
|
{ |
202
|
|
|
$countryObject = CountryPrice_EcommerceCountry::get_real_country($countryCode); |
203
|
|
|
$allCountryPricesForBuyable = $this->AllCountryPricesForBuyable(); |
204
|
|
|
if ($countryObject) { |
205
|
|
|
$filterArray["Country"] = $countryObject->Code; |
|
|
|
|
206
|
|
|
} |
207
|
|
|
if ($currency) { |
208
|
|
|
$filterArray["Currency"] = $currency; |
|
|
|
|
209
|
|
|
} |
210
|
|
|
$allCountryPricesForBuyable = $allCountryPricesForBuyable->filter($filterArray); |
211
|
|
|
return $allCountryPricesForBuyable; |
212
|
|
|
} |
213
|
|
|
|
214
|
|
|
private static $_buyable_price = array(); |
215
|
|
|
|
216
|
|
|
/*** |
217
|
|
|
* |
218
|
|
|
* updates the calculated price to the local price... |
219
|
|
|
* if there is no price then we return 0 |
220
|
|
|
* if the default price can be used then we use NULL (i.e. ignore it!) |
221
|
|
|
* @param float $price (optional) |
|
|
|
|
222
|
|
|
* @return Float | null (ignore this value and use original value) |
223
|
|
|
*/ |
224
|
|
|
public function updateBeforeCalculatedPrice($price = null) |
|
|
|
|
225
|
|
|
{ |
226
|
|
|
$countryCode = ''; |
227
|
|
|
$countryObject = CountryPrice_EcommerceCountry::get_real_country(); |
228
|
|
|
if ($countryObject) { |
229
|
|
|
$countryCode = $countryObject->Code; |
230
|
|
|
} |
231
|
|
|
if ($countryCode === '' || $countryCode === EcommerceConfig::get('EcommerceCountry', 'default_country_code')) { |
232
|
|
|
if ($this->debug) { |
233
|
|
|
debug::log('No country code or default country code: '.$countryCode); |
234
|
|
|
} |
235
|
|
|
|
236
|
|
|
return null; |
237
|
|
|
} |
238
|
|
|
$key = $this->owner->ClassName."___".$this->owner->ID.'____'.$countryCode; |
|
|
|
|
239
|
|
|
if (! isset(self::$_buyable_price[$key])) { |
240
|
|
|
//basics |
241
|
|
|
$currency = null; |
242
|
|
|
$currencyCode = null; |
243
|
|
|
|
244
|
|
|
if ($countryCode) { |
245
|
|
|
$order = ShoppingCart::current_order(); |
246
|
|
|
//if the order has never been localised, then we do this now!!!! |
247
|
|
|
if (count(self::$_buyable_price) === 0) { |
248
|
|
|
CountryPrice_OrderDOD::localise_order($countryCode, $force = false, $runAgain = true); |
249
|
|
|
|
250
|
|
|
// CRUCIAL!!!! |
251
|
|
|
// reload order with new values! |
252
|
|
|
$order = ShoppingCart::current_order(); |
253
|
|
|
} |
254
|
|
|
if ($order && $order->exists()) { |
255
|
|
|
$currency = $order->CurrencyUsed(); |
256
|
|
|
} |
257
|
|
|
if ($currency && $currency->exists()) { |
258
|
|
|
//do nothing |
259
|
|
|
} else { |
260
|
|
|
$currency = CountryPrice_EcommerceCurrency::get_currency_for_country($countryCode); |
261
|
|
|
} |
262
|
|
|
if ($currency) { |
263
|
|
|
$currencyCode = strtoupper($currency->Code); |
264
|
|
|
//1. exact price for country |
265
|
|
View Code Duplication |
if ($currencyCode) { |
|
|
|
|
266
|
|
|
$prices = $this->owner->CountryPricesForCountryAndCurrency( |
267
|
|
|
$countryCode, |
268
|
|
|
$currencyCode |
269
|
|
|
); |
270
|
|
|
if ($prices && $prices->count() == 1) { |
271
|
|
|
self::$_buyable_price[$key] = $prices->First()->Price; |
272
|
|
|
return self::$_buyable_price[$key]; |
273
|
|
|
} elseif ($prices) { |
274
|
|
|
if ($this->debug) { |
275
|
|
|
debug::log('MAIN COUNTRY: There is an error number of prices: '.$prices->count().' based on a search for '.$countryCode.' - '.$currencyCode); |
276
|
|
|
} |
277
|
|
|
} else { |
278
|
|
|
if ($this->debug) { |
279
|
|
|
debug::log('MAIN COUNTRY: There is no country price: '); |
280
|
|
|
} |
281
|
|
|
} |
282
|
|
|
} else { |
283
|
|
|
if ($this->debug) { |
284
|
|
|
debug::log('MAIN COUNTRY: There is no currency code '.$currencyCode.''); |
285
|
|
|
} |
286
|
|
|
} |
287
|
|
|
} else { |
288
|
|
|
if ($this->debug) { |
289
|
|
|
debug::log('MAIN COUNTRY: there is no currency'); |
290
|
|
|
} |
291
|
|
|
} |
292
|
|
|
if (Config::inst()->get('CountryPrice_BuyableExtension', 'allow_usage_of_distributor_backup_country_pricing')) { |
293
|
|
|
//there is a specific country price ... |
294
|
|
|
//check for distributor primary country price |
295
|
|
|
// if it is the same currency, then use that one ... |
296
|
|
|
$distributorCountry = CountryPrice_EcommerceCountry::get_distributor_primary_country($countryCode); |
297
|
|
|
if ($distributorCurrency = $distributorCountry->EcommerceCurrency()) { |
298
|
|
|
if ($distributorCurrency->ID == $currency->ID) { |
299
|
|
|
$distributorCurrencyCode = strtoupper($distributorCurrency->Code); |
300
|
|
|
$distributorCountryCode = $distributorCountry->Code; |
301
|
|
View Code Duplication |
if ($distributorCurrencyCode && $distributorCountryCode) { |
|
|
|
|
302
|
|
|
$prices = $this->owner->CountryPricesForCountryAndCurrency( |
303
|
|
|
$distributorCountryCode, |
304
|
|
|
$distributorCurrencyCode |
305
|
|
|
); |
306
|
|
|
if ($prices && $prices->count() == 1) { |
307
|
|
|
self::$_buyable_price[$key] = $prices->First()->Price; |
308
|
|
|
|
309
|
|
|
return self::$_buyable_price[$key]; |
310
|
|
|
} elseif ($prices) { |
311
|
|
|
if ($this->debug) { |
312
|
|
|
debug::log('BACKUP COUNTRY: There is an error number of prices: '.$prices->count()); |
313
|
|
|
} |
314
|
|
|
} else { |
315
|
|
|
if ($this->debug) { |
316
|
|
|
debug::log('BACKUP COUNTRY: There is no country price: '); |
317
|
|
|
} |
318
|
|
|
} |
319
|
|
|
} else { |
320
|
|
|
if ($this->debug) { |
321
|
|
|
debug::log('BACKUP COUNTRY: We are missing the distributor currency code ('.$distributorCurrencyCode.') or the distributor country code ('.$distributorCountryCode.')'); |
322
|
|
|
} |
323
|
|
|
} |
324
|
|
|
} else { |
325
|
|
|
if ($this->debug) { |
326
|
|
|
debug::log('BACKUP COUNTRY: The distributor currency ID ('.$distributorCurrency->ID.') is not the same as the order currency ID ('.$currency->ID.').'); |
327
|
|
|
} |
328
|
|
|
} |
329
|
|
|
} |
330
|
|
|
} else { |
331
|
|
|
if ($this->debug) { |
332
|
|
|
debug::log('We do not allow backup country pricing'); |
333
|
|
|
} |
334
|
|
|
} |
335
|
|
|
} else { |
336
|
|
|
if ($this->debug) { |
337
|
|
|
debug::log('There is not Country Code '); |
338
|
|
|
} |
339
|
|
|
} |
340
|
|
|
//order must have a country and a currency |
341
|
|
|
if (! $currencyCode || ! $countryCode) { |
|
|
|
|
342
|
|
|
if ($this->debug) { |
343
|
|
|
debug::log('No currency ('.$currencyCode.') or no country code ('.$countryCode.') for order: '); |
344
|
|
|
} |
345
|
|
|
} |
346
|
|
|
//catch error 2: no country price BUT currency is not default currency ... |
347
|
|
|
if (EcommercePayment::site_currency() != $currencyCode) { |
348
|
|
|
if ($this->debug) { |
349
|
|
|
debug::log('site currency ('.EcommercePayment::site_currency().') is not the same order currency ('.$currencyCode.')'); |
350
|
|
|
} |
351
|
|
|
} else { |
352
|
|
|
if ($this->debug) { |
353
|
|
|
debug::log('SETTING '.$key.' to ZERO - NOT FOR SALE'); |
354
|
|
|
} |
355
|
|
|
} |
356
|
|
|
self::$_buyable_price[$key] = 0; |
357
|
|
|
} |
358
|
|
|
|
359
|
|
|
return self::$_buyable_price[$key]; |
360
|
|
|
} |
361
|
|
|
|
362
|
|
|
/** |
363
|
|
|
* delete the related prices |
364
|
|
|
*/ |
365
|
|
|
public function onBeforeDelete() |
366
|
|
|
{ |
367
|
|
|
$prices = $this->AllCountryPricesForBuyable(); |
368
|
|
|
if ($prices && $prices->count()) { |
369
|
|
|
foreach ($prices as $price) { |
370
|
|
|
$price->delete(); |
371
|
|
|
} |
372
|
|
|
} |
373
|
|
|
} |
374
|
|
|
|
375
|
|
|
// VARIATION CODE ONLY |
376
|
|
|
|
377
|
|
|
// We us isNew to presave if we should add some country price for the newy created variation based on the "possibly" pre-existing ones of the product |
378
|
|
|
protected $isNew = false; |
379
|
|
|
|
380
|
|
|
public function onBeforeWrite() |
381
|
|
|
{ |
382
|
|
|
$this->isNew = $this->owner->ID == 0; |
|
|
|
|
383
|
|
|
} |
384
|
|
|
|
385
|
|
|
|
386
|
|
|
public function onAfterWrite() |
387
|
|
|
{ |
388
|
|
|
//only run if these are variations |
389
|
|
|
if ($this->isNew && $this->owner instanceof ProductVariation) { |
|
|
|
|
390
|
|
|
$product = $this->owner->Product(); |
391
|
|
|
if ($product) { |
392
|
|
|
$productPrices = $product->AllCountryPricesForBuyable(); |
393
|
|
|
foreach ($productPrices as $productPrice) { |
394
|
|
|
if ($productPrice->Country) { |
395
|
|
|
if ( |
396
|
|
|
$countryVariationPrice = CountryPrice::get() |
|
|
|
|
397
|
|
|
->filter( |
398
|
|
|
array( |
399
|
|
|
"Country" => $productPrice->Country, |
400
|
|
|
"ObjectClass" => $this->owner->ClassName, |
401
|
|
|
"ObjectID" => $this->owner->ID |
402
|
|
|
) |
403
|
|
|
) |
404
|
|
|
->First() |
405
|
|
|
) { |
406
|
|
|
//do nothing |
407
|
|
|
} else { |
408
|
|
|
$countryVariationPrice = new CountryPrice( |
409
|
|
|
array( |
410
|
|
|
'Price' => $productPrice->Price, |
411
|
|
|
'Country' => $productPrice->Country, |
412
|
|
|
'Currency' => $productPrice->Currency, |
413
|
|
|
'ObjectClass' => $this->owner->ClassName, |
414
|
|
|
'ObjectID' => $this->owner->ID |
415
|
|
|
) |
416
|
|
|
); |
417
|
|
|
$countryVariationPrice->write(); |
418
|
|
|
} |
419
|
|
|
} |
420
|
|
|
} |
421
|
|
|
} |
422
|
|
|
} |
423
|
|
|
} |
424
|
|
|
|
425
|
|
|
/** |
426
|
|
|
* as long as we do not give distributors access to the Products |
427
|
|
|
* this is fairly safe. |
428
|
|
|
* @param member (optiona) $member |
|
|
|
|
429
|
|
|
* @return null / bool |
|
|
|
|
430
|
|
|
*/ |
431
|
|
|
public function canEdit($member = null) |
432
|
|
|
{ |
433
|
|
|
if (! $member) { |
434
|
|
|
$member = Member::currentUser(); |
435
|
|
|
} |
436
|
|
|
if ($member) { |
437
|
|
|
$distributor = $member->Distributor(); |
438
|
|
|
if ($distributor->exists()) { |
439
|
|
|
return true; |
440
|
|
|
} |
441
|
|
|
} |
442
|
|
|
return false; |
443
|
|
|
} |
444
|
|
|
} |
445
|
|
|
|
An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.
If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.