Passed
Push — 1.0 ( 07b100...83f8df )
by Morven
05:31
created

AddLineItem::getSourceFields()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace SilverCommerce\OrdersAdmin\Forms\GridField;
4
5
use SilverStripe\Control\Controller;
6
use SilverStripe\View\ArrayData;
7
use SilverStripe\ORM\ArrayList;
8
use SilverStripe\ORM\DataList;
9
use SilverStripe\View\SSViewer;
10
use SilverStripe\Core\Convert;
11
use SilverStripe\Forms\GridField\GridField;
12
use SilverStripe\Forms\GridField\GridField_ActionProvider;
13
use SilverStripe\Forms\GridField\GridField_HTMLProvider;
14
use SilverStripe\Forms\GridField\GridField_URLHandler;
15
use SilverStripe\Forms\TextField;
16
use SilverStripe\Forms\GridField\GridField_FormAction;
17
use Doctrine\Instantiator\Exception\UnexpectedValueException;
0 ignored issues
show
Bug introduced by
The type Doctrine\Instantiator\Ex...nexpectedValueException 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...
18
use SilverCommerce\TaxAdmin\Model\TaxRate;
19
use LogicException;
20
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 implements GridField_ActionProvider, GridField_HTMLProvider, GridField_URLHandler
33
{
34
35
    /**
36
     * HTML Fragment to render the field.
37
     *
38
     * @var string
39
     **/
40
    protected $targetFragment;
41
42
    /**
43
     * Default field to create the dataobject by should be Title.
44
     *
45
     * @var string
46
     **/
47
    protected $dataObjectField = "Title";
48
    
49
    /**
50
     * @var string SSViewer template to render the results presentation
51
     */
52
    protected $results_format = '$Title';
53
    
54
    /**
55
     * Default field to create the dataobject from.
56
     *
57
     * @var string
58
     **/
59
    protected $source_class = "Product";
60
61
    public function getSourceClass()
62
    {
63
        return $this->source_class;
64
    }
65
66
    public function setSourceClass($class)
67
    {
68
        $this->source_class = $class;
69
        return $this;
70
    }
71
72
    /**
73
     * When we check for existing items, should we check based on all
74
     * filters or any of the chosen (setting this to true uses 
75
     * $list->filter() where as false uses $list->filterAny())
76
     * 
77
     * @var boolean
78
     */
79
    protected $strict_filter = true;
80
81
    /**
82
     * Getter for strict_filter param
83
     *
84
     * @return boolean
85
     */
86
    public function getStrictFilter()
87
    {
88
        return $this->strict_filter;
89
    }
90
91
    /**
92
     * Setter for strict_filter param
93
     *
94
     * @param boolean $bool
95
     * @return void
96
     */
97
    public function setStrictFilter($bool)
98
    {
99
        $this->strict_filter = $bool;
100
        return $this;
101
    }
102
103
    /**
104
     * Fields that we try and find our source object based on
105
     *
106
     * @var array
107
     **/
108
    protected $filter_fields = [
109
        "Title",
110
        "StockID"
111
    ];
112
113
    public function getFilterFields()
114
    {
115
        return $this->filter_fields;
116
    }
117
118
    public function setFilterFields($fields)
119
    {
120
        $this->filter_fields = $fields;
121
        return $this;
122
    }
123
124
    /**
125
     * Fields that we use to filter items for our autocomplete
126
     *
127
     * @var array
128
     **/
129
    protected $autocomplete_fields = [
130
        "Title",
131
        "StockID"
132
    ];
133
134
    public function getAutocompleteFields()
135
    {
136
        return $this->autocomplete_fields;
137
    }
138
139
    public function setAutocompleteFields($fields)
140
    {
141
        $this->autocomplete_fields = $fields;
142
        return $this;
143
    }
144
145
    /**
146
     * If filter fails, set this field when creating
147
     *
148
     * @var String
149
     **/
150
    protected $create_field = "Title";
151
152
    public function getCreateField()
153
    {
154
        return $this->create_field;
155
    }
156
157
    public function setCreateField($field)
158
    {
159
        $this->create_field = $field;
160
        return $this;
161
    }
162
163
    /**
164
     * Fields that we are mapping from the source object to our item
165
     *
166
     * @var array
167
     **/
168
    protected $source_fields = [
169
        "Title" => "Title",
170
        "StockID" => "StockID",
171
        "Price" => "BasePrice"
172
    ];
173
174
    /**
175
     * Get list of source fields
176
     *
177
     * @return array
178
     */
179
    public function getSourceFields()
180
    {
181
        return $this->source_fields;
182
    }
183
184
    /**
185
     * Set the list of source fields
186
     *
187
     * @param  $fields
188
     * @return AddLineItem
189
     */
190
    public function setSourceFields($fields)
191
    {
192
        $this->source_fields = $fields;
193
        return $this;
194
    }
195
196
    /**
197
     * This is the field that we attempt to match a TAX rate to
198
     * when setting up an order item 
199
     *
200
     * @var string
201
     **/
202
    protected $source_tax_field = "TaxRate";
203
204
    /**
205
     * @return array
206
     */
207
    public function getSourceTaxField()
208
    {
209
        return $this->source_tax_field;
210
    }
211
212
    /**
213
     * @param  $field
214
     * @return AddLineItem
215
     */
216
    public function setSourceTaxField($field)
217
    {
218
        $this->source_tax_field = $field;
219
        return $this;
220
    }
221
222
    /**
223
     * Number of results to appear in autocomplete
224
     * 
225
     * @var int
226
     */
227
    protected $results_limit = 20;
228
    
229
    public function getResultsLimit()
230
    {
231
        return $this->results_limit;
232
    }
233
234
    public function setResultsLimit($fields)
235
    {
236
        $this->results_limit = $fields;
237
        return $this;
238
    }
239
240
    public function __construct($targetFragment = 'before', $dataObjectField = "Title")
241
    {
242
        $this->targetFragment = $targetFragment;
243
        $this->dataObjectField = (string) $dataObjectField;
244
    }
245
    
246
    /**
247
     *
248
     * @param GridField $gridField
249
     * @return array
250
     */
251
    public function getURLHandlers($gridField)
252
    {
253
        return [
254
            'search' => 'doSearch',
255
        ];
256
    }
257
258
    /**
259
     * Provide actions to this component.
260
     *
261
     * @param $gridField GridField
262
     *
263
     * @return array
264
     **/
265
    public function getActions($gridField)
266
    {
267
        return ["add"];
268
    }
269
270
271
    /**
272
     * Handles the add action for the given DataObject
273
     *
274
     * @param $grid GridFIeld
275
     * @param $actionName string
276
     * @param $arguments mixed
277
     * @param $data array
278
     **/
279
    public function handleAction(GridField $grid, $actionName, $arguments, $data)
280
    {
281
        if ($actionName == "add") {
282
            // Get our submitted fields and object class
283
            $dbField = $this->getDataObjectField();
284
            $objClass = $grid->getModelClass();
285
            $source_class = $this->getSourceClass();
286
            $source_item = null;
287
            $filter = array();
288
289
            // Has the user used autocomplete
290
            if (isset($data['relationID']) && $data['relationID']) {
291
                $id = $data['relationID'];
292
            } else {
293
                $id = null;
294
            }
295
            
296
            // If we have an ID try and get an existing object then
297
            // check if we have a copy in items
298
            if ($id) {
299
                $source_item = $source_class::get()->byID($id);
300
                
301
                foreach ($this->getFilterFields() as $filter_field) {
302
                    $filter[$filter_field] = $source_item->$filter_field;
303
                }
304
            } else {
305
                // Generate the filter we need to use
306
                $string = $data['gridfieldaddbydbfield'];
307
                
308
                foreach ($this->getFilterFields() as $filter_field) {
309
                    $filter[$filter_field] = $string;
310
                }
311
            }
312
            
313
            // First check if we already have an object or if we need to
314
            // create one
315
            if ($this->getStrictFilter()) {
316
                $existing_obj = $grid
317
                    ->getList()
318
                    ->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

318
                    ->/** @scrutinizer ignore-call */ filter($filter)
Loading history...
319
                    ->first();
320
            } else {
321
                $existing_obj = $grid
322
                    ->getList()
323
                    ->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

323
                    ->/** @scrutinizer ignore-call */ filterAny($filter)
Loading history...
324
                    ->first();
325
            }
326
            
327
            if ($existing_obj) {
328
                $obj = $existing_obj;
329
            } else {
330
                $obj = $objClass::create();
331
            }
332
333
            // Is this a valid field
334
            if (!$obj->hasField($dbField)) {
335
                throw new UnexpectedValueException("Invalid field (" . $dbField . ") on  " . $obj->ClassName . ".");
336
            }
337
        
338
            if ($obj->ID && $obj->canEdit()) {
339
                // An existing record and can edit, update quantity
340
                $curr_qty = ($obj->Quantity) ? $obj->Quantity : 0;
341
                
342
                $obj->setCastedField(
343
                    "Quantity",
344
                     $curr_qty + 1
345
                );
346
                
347
                $id = $grid->getList()->add($obj);
0 ignored issues
show
Unused Code introduced by
The assignment to $id is dead and can be removed.
Loading history...
348
            }
349
            
350
            if (!$obj->ID && $obj->canCreate()) {
351
                // If source item not set, try and get one or get a 
352
                // an existing record
353
                if (!$source_item && class_exists($source_class)) {
354
                    $source_item = $source_class::get()
355
                        ->filterAny($filter)
356
                        ->first();
357
                }
358
                    
359
                if ($source_item) {
360
                    foreach ($this->getSourceFields() as $obj_field => $source_field) {
361
                        $obj->setCastedField(
362
                            $obj_field,
363
                            $source_item->$source_field
364
                        );
365
                    }
366
367
                    // Setup the tax
368
                    $tax_field = $this->getSourceTaxField();
369
                    $tax = TaxRate::get()->find("Rate", $source_item->$tax_field);
370
                    if ($tax) {
0 ignored issues
show
introduced by
The condition $tax can never be true.
Loading history...
371
                        $obj->TaxID = $tax->ID;
372
                    }
373
                } else {
374
                    $obj->setCastedField($this->getCreateField(), $string);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $string does not seem to be defined for all execution paths leading up to this point.
Loading history...
375
                }
376
377
                $obj->setCastedField("Quantity", 1);
378
                $grid->getList()->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

378
                $grid->getList()->/** @scrutinizer ignore-call */ 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...
379
            }
380
381
            // Finally, issue a redirect to update totals
382
            $controller = Controller::curr();
383
384
            $response = $controller->response;
385
            $response->addHeader('X-Pjax', 'Content');
386
            $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

386
            $response->addHeader('X-Reload', /** @scrutinizer ignore-type */ true);
Loading history...
387
388
            return $controller->redirect($grid->getForm()->controller->Link(), 302);
389
        }
390
    }
391
392
393
394
    /**
395
     * Renders the TextField and add button to the GridField.
396
     *
397
     * @param $girdField GridField
398
     *
399
     * @return string HTML
400
     **/
401
    public function getHTMLFragments($grid)
402
    {
403
        $dataClass = $grid->getList()->dataClass();
404
        $obj = singleton($dataClass);
405
406
        if (!$obj->canCreate()) {
407
            return "";
408
        }
409
410
        $text_field = TextField::create("gridfieldaddbydbfield")
0 ignored issues
show
Bug introduced by
'gridfieldaddbydbfield' of type string is incompatible with the type array expected by parameter $args of SilverStripe\View\ViewableData::create(). ( Ignorable by Annotation )

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

410
        $text_field = TextField::create(/** @scrutinizer ignore-type */ "gridfieldaddbydbfield")
Loading history...
411
            ->setAttribute(
412
                "placeholder",
413
                _t(
414
                    "GridFieldAddLineItem.TypeToAdd",
415
                    "Type to add by {Filters} or {Title}",
416
                    "Inform the user what to add based on",
417
                    array(
418
                        "Filters" => implode(", ", $this->getFilterFields()),
419
                        "Title" => $this->getCreateField()
420
                    )
421
                )
422
            )->addExtraClass("relation-search no-change-track")
423
            ->setAttribute(
424
                'data-search-url',
425
                Controller::join_links($grid->Link('search'))
426
            );
427
428
        $find_action = GridField_FormAction::create(
429
            $grid,
430
            'gridfield_relationfind',
431
			_t('GridField.Find', "Find"), 'find', 'find'
432
        );
433
		$find_action->setAttribute('data-icon', 'relationfind');
434
435
        $add_action = GridField_FormAction::create(
436
            $grid,
437
            'gridfield_lineitemadd',
438
            _t("GridFieldAddLineItem.Add", "Add"),
439
            'add',
440
            'add'
441
        );
442
        $add_action->setAttribute('data-icon', 'add');
443
444
        // Start thinking about rending this back to the GF
445
        $fields = ArrayList::create();
446
447
        $fields->push($text_field);
448
        $fields->push($find_action);
449
        $fields->push($add_action);
450
        
451
        $forTemplate = ArrayData::create([]);
452
        $forTemplate->Fields = $fields;
453
454
        return [
455
            $this->targetFragment => $forTemplate->renderWith(AddLineItem::class)
456
        ];
457
    }
458
    
459
    /**
460
     * Returns a json array of a search results that can be used by for
461
     * example Jquery.ui.autosuggestion
462
     *
463
     * @param GridField $gridField
464
     * @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...
465
     */
466
    public function doSearch($gridField, $request)
467
    {
468
        $product_class = $this->getSourceClass();
469
        $params = array();
470
        
471
        // Do we have filter fields setup?
472
        if ($this->getAutocompleteFields()) {
473
            $search_fields = $this->getAutocompleteFields();
474
        } else {
475
            $search_fields = $this->scaffoldSearchFields($product_class);
0 ignored issues
show
Bug introduced by
The method scaffoldSearchFields() does not exist on SilverCommerce\OrdersAdm...s\GridField\AddLineItem. ( Ignorable by Annotation )

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

475
            /** @scrutinizer ignore-call */ 
476
            $search_fields = $this->scaffoldSearchFields($product_class);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
476
        }
477
        
478
        if (!$search_fields) {
479
            throw new LogicException(
480
                sprintf('GridFieldAddExistingAutocompleter: No searchable fields could be found for class "%s"',
481
                $product_class)
482
            );
483
        }
484
        
485
        foreach ($search_fields as $search_field) {
486
            $name = (strpos($search_field, ':') !== false) ? $search_field : "$search_field:StartsWith";
487
            $params[$name] = $request->getVar('gridfieldaddbydbfield');
488
        }
489
490
        $json = array();
491
492
        if (class_exists($product_class)) {
493
            $results = DataList::create($product_class)
0 ignored issues
show
Bug introduced by
$product_class of type string is incompatible with the type array expected by parameter $args of SilverStripe\View\ViewableData::create(). ( Ignorable by Annotation )

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

493
            $results = DataList::create(/** @scrutinizer ignore-type */ $product_class)
Loading history...
494
                ->filterAny($params)
495
                ->sort(strtok($search_fields[0], ':'), 'ASC')
496
                ->limit($this->getResultsLimit());
497
            
498
            $originalSourceFileComments = SSViewer::config()->get('source_file_comments');
499
            
500
            SSViewer::config()->update('source_file_comments', false);
501
            
502
            foreach ($results as $result) {
503
                $json[$result->ID] = html_entity_decode(SSViewer::fromString($this->results_format)->process($result));
504
            }
505
506
            SSViewer::config()->update('source_file_comments', $originalSourceFileComments);
507
        }
508
        
509
        return Convert::array2json($json);
510
    }
511
512
513
514
    /**
515
     * Returns the database field for which we'll add the new data object.
516
     *
517
     * @return string
518
     **/
519
    public function getDataObjectField()
520
    {
521
        return $this->dataObjectField;
522
    }
523
524
525
526
    /**
527
     * Set the database field.
528
     *
529
     * @param $field string
530
     **/
531
    public function setDataObjectField($field)
532
    {
533
        $this->dataObjectField = (string) $field;
534
    }
535
}
536