Passed
Push — 1.3 ( 4019e2...1b8afc )
by Morven
03:55
created

AddLineItem::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 2
dl 0
loc 4
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace SilverCommerce\OrdersAdmin\Forms\GridField;
4
5
use LogicException;
6
use SilverStripe\Core\Convert;
7
use SilverStripe\ORM\DataList;
8
use SilverStripe\View\SSViewer;
9
use SilverStripe\View\ArrayData;
10
use SilverStripe\Forms\FieldList;
11
use SilverStripe\Forms\TextField;
12
use SilverStripe\Control\Controller;
13
use SilverStripe\Core\Config\Config;
14
use SilverStripe\Control\HTTPResponse;
15
use SilverCommerce\TaxAdmin\Model\TaxRate;
16
use SilverStripe\Forms\GridField\GridField;
17
use SilverStripe\Forms\GridField\GridField_FormAction;
18
use Doctrine\Instantiator\Exception\UnexpectedValueException;
19
use SilverStripe\Dev\Debug;
20
use SilverStripe\Forms\GridField\GridFieldAddExistingAutocompleter;
21
22
/**
23
 * A specific gridfield field designed to allow the creation of a new
24
 * order item and that auto completes all fields from a pre-defined
25
 * object (default Product).
26
 *
27
 * @package orders-admin
28
 *
29
 * @author ilateral <[email protected]>
30
 * @author Michael Strong <[email protected]>
31
**/
32
class AddLineItem extends GridFieldAddExistingAutocompleter
33
{
34
35
    /**
36
     * Default field to create the dataobject by should be Title.
37
     *
38
     * @var string
39
     **/
40
    protected $dataObjectField = "Title";
41
    
42
    /**
43
     * Default field to create the dataobject from.
44
     *
45
     * @var string
46
     **/
47
    protected $source_class = "Product";
48
49
    /**
50
     * When we check for existing items, should we check based on all
51
     * filters or any of the chosen (setting this to true uses
52
     * $list->filter() where as false uses $list->filterAny())
53
     *
54
     * @var boolean
55
     */
56
    protected $strict_filter = true;
57
58
    /**
59
     * What filter should the initial search use?
60
     *
61
     * @var string
62
     */
63
    protected $search_filter = 'PartialMatch';
64
65
    /**
66
     * Fields that we try and find our source object based on
67
     *
68
     * @var array
69
     **/
70
    protected $filter_fields = [
71
        "Title",
72
        "StockID"
73
    ];
74
75
    /**
76
     * Fields that we use to filter items for our autocomplete
77
     *
78
     * @var array
79
     **/
80
    protected $autocomplete_fields = [
81
        "Title",
82
        "StockID"
83
    ];
84
85
    /**
86
     * If filter fails, set this field when creating
87
     *
88
     * @var String
89
     **/
90
    protected $create_field = "Title";
91
92
    /**
93
     * Fields that we are mapping from the source object to our item
94
     *
95
     * @var array
96
     **/
97
    protected $source_fields = [
98
        "Title" => "Title",
99
        "StockID" => "StockID",
100
        "BasePrice" => "BasePrice"
101
    ];
102
103
    /**
104
     * This is the field that we attempt to match a TAX rate to
105
     * when setting up an order item
106
     *
107
     * @var string
108
     **/
109
    protected $source_tax_field = "TaxPercentage";
110
111
    public function getSourceClass()
112
    {
113
        return $this->source_class;
114
    }
115
116
    public function setSourceClass($class)
117
    {
118
        $this->source_class = $class;
119
        return $this;
120
    }
121
122
    public function getStrictFilter()
123
    {
124
        return $this->strict_filter;
125
    }
126
127
    public function setStrictFilter($bool)
128
    {
129
        $this->strict_filter = $bool;
130
        return $this;
131
    }
132
133
    public function getFilterFields()
134
    {
135
        return $this->filter_fields;
136
    }
137
138
    public function setFilterFields($fields)
139
    {
140
        $this->filter_fields = $fields;
141
        return $this;
142
    }
143
144
    public function getAutocompleteFields()
145
    {
146
        return $this->autocomplete_fields;
147
    }
148
149
    public function setAutocompleteFields($fields)
150
    {
151
        $this->autocomplete_fields = $fields;
152
        return $this;
153
    }
154
155
    public function getCreateField()
156
    {
157
        return $this->create_field;
158
    }
159
160
    public function setCreateField($field)
161
    {
162
        $this->create_field = $field;
163
        return $this;
164
    }
165
166
    public function getSourceFields()
167
    {
168
        return $this->source_fields;
169
    }
170
171
    public function setSourceFields($fields)
172
    {
173
        $this->source_fields = $fields;
174
        return $this;
175
    }
176
177
    public function getSourceTaxField()
178
    {
179
        return $this->source_tax_field;
180
    }
181
182
    public function setSourceTaxField($field)
183
    {
184
        $this->source_tax_field = $field;
185
        return $this;
186
    }
187
188
    public function getSearchFilter()
189
    {
190
        return $this->search_filter;
191
    }
192
193
    public function setSearchFilter(string $search_filter)
194
    {
195
        $this->search_filter = $search_filter;
196
        return $this;
197
    }
198
199
200
    /**
201
     * Handles the add action for the given DataObject
202
     *
203
     * @param $grid GridFIeld
204
     * @param $actionName string
205
     * @param $arguments mixed
206
     * @param $data array
207
     **/
208
    public function handleAction(GridField $grid, $actionName, $arguments, $data)
209
    {
210
        if ($actionName == "addto") {
211
            // Get our submitted fields and object class
212
            $dbField = $this->getDataObjectField();
213
            $objClass = $grid->getModelClass();
214
            $source_class = $this->getSourceClass();
215
            $source_item = null;
216
            $list = $grid->getList();
217
            $filter = [];
218
219
            // Has the user used autocomplete
220
            if (isset($data['relationID']) && $data['relationID']) {
221
                $string = $data['relationID'];
222
            } else {
223
                $string = null;
224
            }
225
226
            foreach ($this->getFilterFields() as $filter_field) {
227
                $filter[$filter_field] = $string;
228
            }
229
            
230
            // First check if we already have an object or if we need to
231
            // create one
232
            if ($this->getStrictFilter()) {
233
                $existing_obj = $list
234
                    ->filter($filter)
0 ignored issues
show
Bug introduced by
The method filter() does not exist on SilverStripe\ORM\SS_List. It seems like you code against a sub-type of said class. However, the method does not exist in SilverStripe\ORM\Sortable or SilverStripe\ORM\Limitable. Are you sure you never get one of those? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

234
                    ->/** @scrutinizer ignore-call */ filter($filter)
Loading history...
235
                    ->first();
236
            } else {
237
                $existing_obj = $list
238
                    ->filterAny($filter)
0 ignored issues
show
Bug introduced by
The method filterAny() does not exist on SilverStripe\ORM\SS_List. It seems like you code against a sub-type of said class. However, the method does not exist in SilverStripe\ORM\Sortable or SilverStripe\ORM\Limitable. Are you sure you never get one of those? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

238
                    ->/** @scrutinizer ignore-call */ filterAny($filter)
Loading history...
239
                    ->first();
240
            }
241
            
242
            if ($existing_obj) {
243
                $obj = $existing_obj;
244
            } else {
245
                $obj = $objClass::create();
246
            }
247
248
            // Is this a valid field
249
            if (!$obj->hasField($dbField)) {
250
                throw new UnexpectedValueException("Invalid field (" . $dbField . ") on  " . $obj->ClassName . ".");
251
            }
252
        
253
            if ($obj->ID && $obj->canEdit()) {
254
                // An existing record and can edit, update quantity
255
                $curr_qty = ($obj->Quantity) ? $obj->Quantity : 0;
256
                
257
                $obj->setCastedField(
258
                    "Quantity",
259
                    $curr_qty + 1
260
                );
261
                
262
                $list->add($obj);
263
            }
264
            
265
            if (!$obj->ID && $obj->canCreate()) {
266
                // If source item not set, try and get one or get a
267
                // an existing record
268
                if (!$source_item && class_exists($source_class)) {
0 ignored issues
show
introduced by
$source_item is of type null, thus it always evaluated to false.
Loading history...
269
                    $source_item = $source_class::get()
270
                        ->filterAny($filter)
271
                        ->first();
272
                }
273
                    
274
                if ($source_item) {
275
                    foreach ($this->getSourceFields() as $obj_field => $source_field) {
276
                        $obj->setCastedField(
277
                            $obj_field,
278
                            $source_item->$source_field
279
                        );
280
                    }
281
282
                    // Setup the tax
283
                    $tax_field = $this->getSourceTaxField();
284
                    $tax = TaxRate::get()->find("Rate", $source_item->$tax_field);
285
                    if ($tax) {
0 ignored issues
show
introduced by
$tax is of type SilverStripe\ORM\DataObject, thus it always evaluated to true.
Loading history...
286
                        $obj->TaxRateID = $tax->ID;
287
                    }
288
                } else {
289
                    $obj->setCastedField($this->getCreateField(), $string);
290
                }
291
292
                $obj->setCastedField("Quantity", 1);
293
                $list->add($obj, []);
0 ignored issues
show
Unused Code introduced by
The call to SilverStripe\ORM\SS_List::add() has too many arguments starting with array(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

293
                $list->/** @scrutinizer ignore-call */ 
294
                       add($obj, []);

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
294
            }
295
296
            // Finally, issue a redirect to update totals
297
            $controller = Controller::curr();
298
299
            $response = $controller->response;
300
            $response->addHeader('X-Pjax', 'Content');
301
            $response->addHeader('X-Reload', true);
0 ignored issues
show
Bug introduced by
true of type true is incompatible with the type string expected by parameter $value of SilverStripe\Control\HTTPResponse::addHeader(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

301
            $response->addHeader('X-Reload', /** @scrutinizer ignore-type */ true);
Loading history...
302
303
            return $controller->redirect($grid->getForm()->controller->Link(), 302);
304
        }
305
    }
306
307
    /**
308
     * Renders the TextField and add button to the GridField.
309
     *
310
     * @param $girdField GridField
311
     *
312
     * @return string HTML
313
     **/
314
    public function getHTMLFragments($gridField)
315
    {
316
        $forTemplate = ArrayData::create([
317
            'Fields' => FieldList::create()
318
        ]);
319
320
        $searchField = TextField::create(
321
            'gridfield_relationsearch',
322
            _t('SilverStripe\\Forms\\GridField\\GridField.RelationSearch', "Relation search")
323
        )->setAttribute('data-search-url', Controller::join_links($gridField->Link('search')))
324
        ->setAttribute(
325
            'placeholder',
326
            _t(
327
                __CLASS__ . ".TypeToAdd",
328
                "Type to add by {Filters} or {Title}",
329
                "Inform the user what to add based on",
330
                [
331
                    "Filters" => implode(", ", $this->getFilterFields()),
332
                    "Title" => $this->getCreateField()
333
                ]
334
            )
335
        )->addExtraClass('relation-search no-change-track action_gridfield_relationsearch');
336
337
        $findAction = GridField_FormAction::create(
338
            $gridField,
339
            'gridfield_relationfind',
340
            _t('SilverStripe\\Forms\\GridField\\GridField.Find', "Find"),
341
            'find',
342
            'find'
343
        )->setAttribute('data-icon', 'relationfind')
344
        ->addExtraClass('action_gridfield_relationfind');
345
346
        $addAction = GridField_FormAction::create(
347
            $gridField,
348
            'gridfield_relationadd',
349
            _t(__CLASS__ . 'Add', "Add"),
350
            'addto',
351
            'addto'
352
        )->setAttribute('data-icon', 'plus-circled')
353
        ->addExtraClass('btn btn-primary font-icon-plus-circled action_gridfield_relationadd');
354
355
        // If an object is not found, disable the action
356
        if (!is_int($gridField->State->GridFieldAddRelation(null))) {
357
            $addAction->setReadonly(true);
358
        }
359
360
        $forTemplate->Fields->push($searchField);
361
        $forTemplate->Fields->push($findAction);
362
        $forTemplate->Fields->push($addAction);
363
364
        if ($form = $gridField->getForm()) {
365
            $forTemplate->Fields->setForm($form);
366
        }
367
368
        $template = SSViewer::get_templates_by_class($this, '', __CLASS__);
369
        return [
370
            $this->targetFragment => $forTemplate->renderWith($template)
371
        ];
372
    }
373
    
374
    /**
375
     * Returns a json array of a search results that can be used by for
376
     * example Jquery.ui.autosuggestion
377
     *
378
     * @param GridField $gridField
379
     * @param SS_HTTPRequest $request
0 ignored issues
show
Bug introduced by
The type SilverCommerce\OrdersAdm...ridField\SS_HTTPRequest was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
380
     */
381
    public function doSearch($gridField, $request)
382
    {
383
        $source_class = $this->getSourceClass();
384
        $search_filter = $this->getSearchFilter();
385
        $params = [];
386
        
387
        // Do we have filter fields setup?
388
        if ($this->getAutocompleteFields()) {
389
            $search_fields = $this->getAutocompleteFields();
390
        } else {
391
            $search_fields = $this->scaffoldSearchFields($source_class);
392
        }
393
        
394
        if (!$search_fields) {
395
            throw new LogicException(
396
                sprintf(
397
                    'GridFieldAddExistingAutocompleter: No searchable fields could be found for class "%s"',
398
                    $source_class
399
                )
400
            );
401
        }
402
        
403
        foreach ($search_fields as $search_field) {
404
            $name = (strpos($search_field, ':') !== false) ? $search_field : $search_field . ":" . $search_filter;
405
            $params[$name] = $request->getVar('gridfield_relationsearch');
406
        }
407
408
        $json = [];
409
        Config::nest();
410
411
        if (class_exists($source_class)) {
412
            $results = DataList::create($source_class)
413
                ->filterAny($params)
414
                ->sort(strtok($search_fields[0], ':'), 'ASC')
415
                ->limit($this->getResultsLimit());
416
417
            $originalSourceFileComments = SSViewer::config()->get('source_file_comments');
418
            
419
            SSViewer::config()->update('source_file_comments', false);
420
            $viewer = SSViewer::fromString($this->resultsFormat);
421
422
            foreach ($results as $result) {
423
                $title = Convert::html2raw($viewer->process($result));
424
                $json[] = [
425
                    'label' => $title,
426
                    'value' => $title,
427
                    'id' => $title,
428
                ];
429
            }
430
431
            SSViewer::config()->update('source_file_comments', $originalSourceFileComments);
432
        }
433
434
        Config::unnest();
435
        $response = new HTTPResponse(json_encode($json));
436
        $response->addHeader('Content-Type', 'application/json');
437
        return $response;
438
    }
439
440
    /**
441
     * Returns the database field for which we'll add the new data object.
442
     *
443
     * @return string
444
     **/
445
    public function getDataObjectField()
446
    {
447
        return $this->dataObjectField;
448
    }
449
450
    /**
451
     * Set the database field.
452
     *
453
     * @param $field string
454
     **/
455
    public function setDataObjectField($field)
456
    {
457
        $this->dataObjectField = (string) $field;
458
    }
459
}
460