Completed
Push — master ( 58cbef...df59e7 )
by Nicolaas
01:53
created

createVariationFromData()   B

Complexity

Conditions 2
Paths 2

Size

Total Lines 25
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 25
rs 8.8571
c 0
b 0
f 0
cc 2
eloc 15
nc 2
nop 3
1
<?php
2
/**
3
 * @author nicolaas [at] sunnysideup.co.nz
4
 * @requires ecommerce
5
 * @requires ecommerce_product_variation
6
 */
7
class AnyPriceProductPage extends Product
0 ignored issues
show
Coding Style Compatibility introduced by
PSR1 recommends that each class must be in a namespace of at least one level to avoid collisions.

You can fix this by adding a namespace to your class:

namespace YourVendor;

class YourClass { }

When choosing a vendor namespace, try to pick something that is not too generic to avoid conflicts with other libraries.

Loading history...
8
{
9
    private static $db = array(
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
Unused Code introduced by
The property $db is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
10
        'DescriptionFieldLabel' => 'Varchar(255)',
11
        'AmountFieldLabel' => 'Varchar(255)',
12
        'ActionFieldLabel' => 'Varchar(255)',
13
        'MinimumAmount' => 'Decimal(9,2)',
14
        'MaximumAmount' => 'Decimal(9,2)',
15
        'RecommendedAmounts' => 'Varchar(255)',
16
        'CanSetDescription' => 'Boolean',
17
        'DefaultDescription' => 'Varchar(255)',
18
    );
19
20
    private static $defaults = array(
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
Unused Code introduced by
The property $defaults is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
21
        'DescriptionFieldLabel' => 'Enter Description',
22
        'AmountFieldLabel' => 'Enter Amount',
23
        'ActionFieldLabel' => 'Add to cart',
24
        'MinimumAmount' => 1,
25
        'MaximumAmount' => 100,
26
        'AllowPurchase' => false,
27
        'Price' => 0,
28
    );
29
30
    private static $field_labels = array(
0 ignored issues
show
Unused Code introduced by
The property $field_labels is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
31
        'DescriptionFieldLabel' => 'Description Label',
32
        'AmountFieldLabel' => 'Amount Label',
33
        'ActionFieldLabel' => 'Button Label',
34
        'MinimumAmount' => 'Minimum Amount',
35
        'MaximumAmount' => 'Maximum Amount',
36
        'RecommendedAmounts' => 'Hinted amounts',
37
        'CanSetDescription' => 'Customer Adds Description',
38
        'DefaultDescription' => 'Default Description',
39
    );
40
41
    private static $field_labels_right = array(
0 ignored issues
show
Unused Code introduced by
The property $field_labels_right is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
42
        'DescriptionFieldLabel' => 'e.g. please enter title for payment',
43
        'AmountFieldLabel' => 'e.g. please enter amount for payment',
44
        'ActionFieldLabel' => 'e.g. pay now',
45
        'MinimumAmount' => 'e.g. 10.00',
46
        'MaximumAmount' => 'e.g. 100.00',
47
        'RecommendedAmounts' => 'create a list of recommended payment amounts, separated by a space, e.g. 10.00 12.00 19.00 23.00',
48
        'CanSetDescription' => 'can the customer add their own description to the payment?',
49
        'DefaultDescription' => 'e.g. generic product, this field is optional',
50
    );
51
52
    private static $singular_name = 'Any Price Product';
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
Unused Code introduced by
The property $singular_name is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
53
    public function i18n_singular_name()
54
    {
55
        return _t('AnyPriceProductPage.ANY_PRICE_PRODUCT', 'Any Price Product');
56
    }
57
58
    private static $plural_name = 'Any Price Products';
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
Unused Code introduced by
The property $plural_name is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
59
    public function i18n_plural_name()
60
    {
61
        return _t('AnyPriceProductPage.ANY_PRICE_PRODUCTS', 'Any Price Products');
62
    }
63
64
    private static $icon = 'ecommerce_anypriceproduct/images/treeicons/AnyPriceProductPage';
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
Unused Code introduced by
The property $icon is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
65
66
    /**
67
     * @config
68
     *
69
     * @var string Description of the class functionality, typically shown to a user
70
     *             when selecting which page type to create. Translated through {@link provideI18nEntities()}.
71
     */
72
    private static $description = 'Generic product that can be used to allow customers to choose a specific amount to pay.';
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
Unused Code introduced by
The property $description is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
73
74
    public function canCreate($member = null)
75
    {
76
        return SiteTree::get()->filter(array('ClassName' => 'AnyPriceProductPage'))->count() ? false : true;
77
    }
78
79
    public function canPurchase(Member $member = null, $checkPrice = true)
80
    {
81
        return false;
82
    }
83
84
    public function getCMSFields()
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
85
    {
86
        $fields = parent::getCMSFields();
87
        $fieldLabels = $this->fieldLabels();
88
        $fieldLabelsRight = Config::inst()->get('AnyPriceProductPage', 'field_labels_right');
89
        $exampleLink = Director::absoluteURL($this->Link('setamount')).'/123.45/?description='.urlencode('test payment only');
90
        $exampleLinkExplanation = sprintf(_t('AnyPriceProductPage.EXPLANATION', '<br /><br /><h5>How to preset the amount?</h5><p>The link <a href="%1$s">%1$s</a> will pre-set the amount to 123.45. You can use this link (and vary the amount as needed) to cutomers to receive payments.</p>.'), $exampleLink);
91
        $fields->addFieldsToTab(
92
            'Root.Form',
93
            array(
94
                TextField::create('DescriptionFieldLabel', $fieldLabels['DescriptionFieldLabel'])->setDescription($fieldLabelsRight['DescriptionFieldLabel']),
95
                TextField::create('AmountFieldLabel', $fieldLabels['AmountFieldLabel'])->setDescription($fieldLabelsRight['AmountFieldLabel']),
96
                TextField::create('ActionFieldLabel', $fieldLabels['ActionFieldLabel'])->setDescription($fieldLabelsRight['ActionFieldLabel']),
97
                NumericField::create('MinimumAmount', $fieldLabels['MinimumAmount'])->setDescription($fieldLabelsRight['MinimumAmount']),
98
                NumericField::create('MaximumAmount', $fieldLabels['MaximumAmount'])->setDescription($fieldLabelsRight['MaximumAmount']),
99
                TextField::create('RecommendedAmounts', $fieldLabels['RecommendedAmounts'])->setDescription($fieldLabelsRight['RecommendedAmounts']),
100
                CheckboxField::create('CanSetDescription', $fieldLabels['CanSetDescription'])->setDescription($fieldLabelsRight['CanSetDescription']),
101
                TextField::create('DefaultDescription', $fieldLabels['DefaultDescription'])->setDescription($fieldLabelsRight['DefaultDescription']),
102
                LiteralField::create('ExampleLinkExplanation', $exampleLinkExplanation),
103
            )
104
        );
105
        if (!$this->CanSetDescription) {
106
            $fields->removeByName('DescriptionFieldLabel');
107
        }
108
        // Standard product detail fields
109
        $fields->removeFieldsFromTab(
110
            'Root.Details',
111
            array(
112
                'Weight',
113
                'Price',
114
                'Model',
115
            )
116
        );
117
118
        // Flags for this product which affect it's behaviour on the site
119
        $fields->removeFieldsFromTab(
120
            'Root.Details',
121
            array(
122
                'FeaturedProduct',
123
            )
124
        );
125
126
        return $fields;
127
    }
128
}
129
130
class AnyPriceProductPage_Controller extends Product_Controller
0 ignored issues
show
Coding Style Compatibility introduced by
PSR1 recommends that each class should be in its own file to aid autoloaders.

Having each class in a dedicated file usually plays nice with PSR autoloaders and is therefore a well established practice. If you use other autoloaders, you might not want to follow this rule.

Loading history...
Coding Style Compatibility introduced by
PSR1 recommends that each class must be in a namespace of at least one level to avoid collisions.

You can fix this by adding a namespace to your class:

namespace YourVendor;

class YourClass { }

When choosing a vendor namespace, try to pick something that is not too generic to avoid conflicts with other libraries.

Loading history...
131
{
132
    private static $allowed_actions = array(
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
Unused Code introduced by
The property $allowed_actions is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
133
        'AddNewPriceForm',
134
        'doaddnewpriceform',
135
        'setamount'
136
    );
137
138
139
    /**
140
     * A list of variations.
141
     *
142
     * @return DataList
143
     */
144
    public function Variations()
145
    {
146
        $options = explode(array(',', ' '), $this->RecommendedAmounts.' 0 0');
147
        if (is_array($options)  && count($options)) {
148
            foreach ($options as $key => $option) {
149
                if (!$option) {
150
                    unset($options[$key]);
151
                }
152
            }
153
        }
154
        $className = $this->getClassNameOfVariations();
155
        if (count($options)) {
156
            return $className::get()->filter(array(
157
                'ProductID' => $this->ID,
158
                'Price' => $options,
159
            ));
160
        } else {
161
            return $className::get()->filter(array(
162
                'ProductID' => $this->ID,
163
            ));
164
        }
165
    }
166
167
    public function AddNewPriceForm()
168
    {
169
        $requiredFields = array();
170
        $amount = $this->MinimumAmount;
171
        if ($newAmount = Session::get('AnyPriceProductPageAmount')) {
172
            $amount = $newAmount;
173
        }
174
        $description = $this->DefaultDescription;
175
        if ($newDescription = Session::get('AnyPriceProductPageDescription')) {
176
            $description = $newDescription;
177
        }
178
        $fields = FieldList::create();
179
        if ($this->CanSetDescription) {
180
            $fields->push(TextField::create('Description', $this->DescriptionFieldLabel, $description));
181
            $requiredFields[] = 'Description';
182
        }
183
        $fields->push(CurrencyField::create('Amount', $this->AmountFieldLabel, $amount));
184
        $requiredFields[] = 'Amount';
185
186
        $actions = FieldList::create(
187
            FormAction::create('doaddnewpriceform', $this->ActionFieldLabel)
188
        );
189
        $requiredFields = RequiredFields::create($requiredFields);
190
191
        return Form::create(
192
            $controller = $this,
193
            $name = 'AddNewPriceForm',
194
            $fields,
195
            $actions,
196
            $requiredFields
197
        );
198
    }
199
200
    public function doaddnewpriceform($data, $form)
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
201
    {
202
        //check amount
203
        $amount = $this->parseFloat($data['Amount']);
204
        if ($this->MinimumAmount > 0 && ($amount < $this->MinimumAmount)) {
205
            $form->sessionMessage(_t('AnyPriceProductPage.ERRORINFORMTOOLOW', 'Please enter a higher amount.'), 'bad');
206
            $this->redirectBack();
207
208
            return;
209
        } elseif ($this->MaximumAmount > 0 && ($amount > $this->MaximumAmount)) {
210
            $form->sessionMessage(_t('AnyPriceProductPage.ERRORINFORMTOOHIGH', 'Please enter a lower amount.'), 'bad');
211
            $this->redirectBack();
212
213
            return;
214
        }
215
216
        //clear settings from URL
217
        Session::clear('AnyPriceProductPageAmount');
218
        Session::clear('AnyPriceProductPageDescription');
219
220
        //create a description
221
        if (isset($data['Description']) && $data['Description']) {
222
            $description = Convert::raw2sql($data['Description']);
223
        } elseif ($this->DefaultDescription) {
224
            $description = $this->DefaultDescription;
225
        } else {
226
            Currency::setCurrencySymbol(EcommercePayment::site_currency());
0 ignored issues
show
Deprecated Code introduced by
The method Currency::setCurrencySymbol() has been deprecated with message: 4.0 Use the "Currency.currency_symbol" config setting instead

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
227
            $titleDescriptor = new Currency('titleDescriptor');
228
            $titleDescriptor->setValue($amount);
229
            $description = _t('AnyPriceProductPage.PAYMENTFOR', 'Payment for: ').$titleDescriptor->Nice();
230
        }
231
232
        //create variation and update it ... if needed
233
        $variation = $this->createVariationFromData($amount, $description, $data);
234
        $variation = $this->updateProductVariation($variation, $data, $form);
235
        //create order item and update it ... if needed
236
        $orderItem = $this->createOrderItemFromVariation($variation);
237
        if (!$orderItem) {
238
            $form->sessionMessage(_t('AnyPriceProductPage.ERROROTHER', 'Sorry, we could not add your entry.'), 'bad');
239
            $this->redirectBack();
240
241
            return;
242
        }
243
        $orderItem = $this->updateOrderItem($orderItem, $data, $form);
244
245
        if (! $orderItem) {
246
            $form->sessionMessage(_t('AnyPriceProductPage.ERROROTHER', 'Sorry, we could not add your entry.'), 'bad');
247
            $this->redirectBack();
248
249
            return;
250
        }
251
        $checkoutPage = DataObject::get_one('CheckoutPage');
252
        if ($checkoutPage) {
253
            return $this->redirect($checkoutPage->Link());
254
        }
255
        return array();
256
    }
257
258
    public function setamount($request)
0 ignored issues
show
Coding Style introduced by
setamount uses the super-global variable $_GET which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
259
    {
260
        if ($amount = floatval($request->param('ID'))) {
261
            Session::set('AnyPriceProductPageAmount', $amount);
262
        }
263
        if ($description = Convert::raw2sql($request->param('OtherID'))) {
0 ignored issues
show
Unused Code introduced by
$description is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
264
            Session::set('AnyPriceProductPageDescription', $_GET['description']);
265
        }
266
        $this->redirect($this->Link());
267
268
        return array();
269
    }
270
271
    /**
272
     * clean up the amount, we may improve this in the future.
273
     *
274
     * @return float
275
     */
276
    protected function parseFloat($floatString)
277
    {
278
        return preg_replace('/([^0-9\\.])/i', '', $floatString);
279
    }
280
281
    /**
282
     *
283
     * @param currency $amount
284
     * @param string $description
285
     * @param array $data (form data)
286
     *
287
     * @return ProductVariation
288
     */
289
    protected function createVariationFromData($amount, $description, $data)
290
    {
291
        //check if we have one now
292
        $filter = array(
293
            'ProductID' => $this->ID,
294
            'Price' => $amount,
295
            'Description' => $description,
296
        );
297
        $className = $this->getClassNameOfVariations();
298
        $variation = DataObject::get_one(
299
            $className,
300
            $filter,
301
            $cacheDataObjectGetOne = false
302
        );
303
        if (! $variation) {
304
            $variation = $className::create($filter);
305
        }
306
307
        $variation->AllowPurchase = true;
308
        $variation->write();
309
310
        // line below does not work - suspected bug in framework Versioning System
311
        //$componentSet->add($obj);
0 ignored issues
show
Unused Code Comprehensibility introduced by
86% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
312
        return $variation;
313
    }
314
315
    /**
316
     * @param Variation (optional) $variation
0 ignored issues
show
Documentation introduced by
Should the type for parameter $variation not be optional|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
317
     * @return OrderItem | null
318
     */
319
    protected function createOrderItemFromVariation($variation = null)
320
    {
321
        if ($variation) {
322
            $shoppingCart = ShoppingCart::singleton();
323
            $orderItem = $shoppingCart->addBuyable($variation);
324
            return $orderItem;
325
        }
326
    }
327
328
329
    /**
330
     * you can add this method to a class extending
331
     * AnyPriceProductPage_Controller so that you can do something with the Product Variation
332
     *
333
     * @param ProductVariation $variation
334
     * @param array $data
335
     * @param Form $form
336
     *
337
     * @return ProductVariation
338
     */
339
    protected function updateProductVariation($variation, $data, $form)
340
    {
341
        return $variation;
342
    }
343
344
    /**
345
     * you can add this method to a class extending
346
     * AnyPriceProductPage_Controller so that you can do something with the OrderItem
347
     *
348
     * @param OrderItem $orderItem
349
     * @param array $data
350
     * @param Form $form
351
     *
352
     * @return OrderItem
353
     */
354
    protected function updateOrderItem($orderItem, $data, $form)
355
    {
356
        return $orderItem;
357
    }
358
}
359