GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Passed
Push — bs4 ( 61e2ca...108bf2 )
by Andrey
04:30
created

NamedFormElement::composeId()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 1 Features 0
Metric Value
cc 1
eloc 2
c 1
b 1
f 0
nc 1
nop 1
dl 0
loc 5
rs 10
1
<?php
2
3
namespace SleepingOwl\Admin\Form\Element;
4
5
use Closure;
6
use LogicException;
7
use Illuminate\Support\Arr;
8
use Illuminate\Support\Str;
9
use Illuminate\Database\Eloquent\Model;
10
use SleepingOwl\Admin\Form\FormElement;
11
use Illuminate\Contracts\Support\Htmlable;
12
use KodiComponents\Support\HtmlAttributes;
13
use Illuminate\Database\Eloquent\Relations\HasOne;
14
use Illuminate\Database\Eloquent\Relations\MorphOne;
15
use Illuminate\Database\Eloquent\Relations\Relation;
16
use Illuminate\Database\Eloquent\Relations\BelongsTo;
17
use SleepingOwl\Admin\Exceptions\Form\FormElementException;
18
19
abstract class NamedFormElement extends FormElement
20
{
21
    use HtmlAttributes;
22
23
    /**
24
     * @var string
25
     */
26
    protected $path;
27
28
    /**
29
     * @var string
30
     */
31
    protected $name;
32
33
    /**
34
     * @var string
35
     */
36
    protected $id;
37
38
    /**
39
     * @var string
40
     */
41
    protected $modelAttributeKey;
42
43
    /**
44
     * @var string
45
     */
46
    protected $label;
47
48
    /**
49
     * @var string
50
     */
51
    protected $helpText;
52
53
    /**
54
     * @var mixed
55
     */
56
    protected $exactValue;
57
58
    /**
59
     * @var bool
60
     */
61
    protected $exactValueSet = false;
62
63
    /**
64
     * @var mixed
65
     */
66
    protected $defaultValue;
67
68
    /**
69
     * @var \Closure
70
     */
71
    protected $mutator;
72
73
    /**
74
     * @param string $path
75
     * @param string|null $label
76
     *
77
     * @throws FormElementException
78
     */
79
    public function __construct($path, $label = null)
80
    {
81
        if (empty($path)) {
82
            throw new FormElementException('You must specify field path');
83
        }
84
85
        $this->setPath($path);
86
        $this->setLabel($label);
87
88
        $parts = explode('.', $path);
89
        $this->setName($this->composeName($parts));
90
        $this->setId($this->composeId($path));
91
        $this->setModelAttributeKey(end($parts));
92
93
        parent::__construct();
94
    }
95
96
    /**
97
     * Compose html name from array like this: 'first[second][third]'.
98
     *
99
     * @param array $parts
100
     *
101
     * @return string
102
     */
103
    private function composeName(array $parts)
104
    {
105
        $name = array_shift($parts);
106
107
        while (! empty($parts)) {
108
            $part = array_shift($parts);
109
            $name .= "[$part]";
110
        }
111
112
        return $name;
113
    }
114
115
    /**
116
     * Compose html id from array like this: 'first__second__third'.
117
     *
118
     * @param string $path
119
     *
120
     * @return string
121
     */
122
    private function composeId(string $path)
123
    {
124
        $name = strtr($path, ['.' => '__', '[' => '__', ']' => '']);
125
126
        return $name;
127
    }
128
129
    /**
130
     * @return string
131
     */
132
    public function getPath()
133
    {
134
        return $this->path;
135
    }
136
137
    /**
138
     * @param string $path
139
     *
140
     * @return $this
141
     */
142
    public function setPath($path)
143
    {
144
        $this->path = $path;
145
146
        return $this;
147
    }
148
149
    /**
150
     * @return string
151
     */
152
    public function getNameKey()
153
    {
154
        return strtr($this->getName(), ['[' => '.', ']' => '']);
155
    }
156
157
    /**
158
     * @return string
159
     */
160
    public function getName()
161
    {
162
        return $this->name;
163
    }
164
165
    /**
166
     * @param string $name
167
     *
168
     * @return $this
169
     */
170
    public function setName($name)
171
    {
172
        $this->name = $name;
173
174
        return $this;
175
    }
176
177
    /**
178
     * @return string
179
     */
180
    public function getId()
181
    {
182
        return $this->id;
183
    }
184
185
    /**
186
     * @param string $id
187
     *
188
     * @return $this
189
     */
190
    public function setId($id)
191
    {
192
        $this->id = $id;
193
194
        return $this;
195
    }
196
197
    /**
198
     * @param string $id
199
     *
200
     * @return $this
201
     */
202
    public function setComposeId($id)
203
    {
204
        $this->setId($this->composeId($id));
205
206
        return $this;
207
    }
208
209
    /**
210
     * @return string
211
     */
212
    public function getLabel()
213
    {
214
        return $this->label;
215
    }
216
217
    /**
218
     * @param string $label
219
     *
220
     * @return $this
221
     */
222
    public function setLabel($label)
223
    {
224
        $this->label = $label;
225
226
        return $this;
227
    }
228
229
    /**
230
     * @return string
231
     */
232
    public function getModelAttributeKey()
233
    {
234
        return $this->modelAttributeKey;
235
    }
236
237
    /**
238
     * @param string $key
239
     *
240
     * @return $this
241
     */
242
    public function setModelAttributeKey($key)
243
    {
244
        $this->modelAttributeKey = $key;
245
246
        return $this;
247
    }
248
249
    /**
250
     * @return mixed
251
     */
252
    public function getDefaultValue()
253
    {
254
        return $this->defaultValue;
255
    }
256
257
    /**
258
     * @param mixed $defaultValue
259
     *
260
     * @return $this
261
     */
262
    public function setDefaultValue($defaultValue)
263
    {
264
        $this->defaultValue = $defaultValue;
265
266
        return $this;
267
    }
268
269
    /**
270
     * @return string|Htmlable $helpText
271
     */
272
    public function getHelpText()
273
    {
274
        if ($this->helpText instanceof Htmlable) {
0 ignored issues
show
introduced by
$this->helpText is never a sub-type of Illuminate\Contracts\Support\Htmlable.
Loading history...
275
            return $this->helpText->toHtml();
276
        }
277
278
        return $this->helpText;
279
    }
280
281
    /**
282
     * @param string|Htmlable $helpText
283
     *
284
     * @return $this
285
     */
286
    public function setHelpText($helpText)
287
    {
288
        $this->helpText = $helpText;
0 ignored issues
show
Documentation Bug introduced by
It seems like $helpText can also be of type Illuminate\Contracts\Support\Htmlable. However, the property $helpText is declared as type string. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

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

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
289
290
        return $this;
291
    }
292
293
    /**
294
     * @param string|null $message
295
     *
296
     * @return $this
297
     */
298
    public function required($message = null)
299
    {
300
        $this->addValidationRule('required', $message);
301
302
        return $this;
303
    }
304
305
    /**
306
     * @param string|null $message
307
     *
308
     * @return $this
309
     */
310
    public function unique($message = null)
311
    {
312
        $this->addValidationRule('_unique');
313
314
        if (! is_null($message)) {
315
            $this->addValidationMessage('unique', $message);
316
        }
317
318
        return $this;
319
    }
320
321
    /**
322
     * @return array
323
     */
324
    public function getValidationMessages()
325
    {
326
        $messages = parent::getValidationMessages();
327
328
        foreach ($messages as $rule => $message) {
329
            $messages[$this->getName().'.'.$rule] = $message;
330
            unset($messages[$rule]);
331
        }
332
333
        return $messages;
334
    }
335
336
    /**
337
     * @return array
338
     */
339
    public function getValidationLabels()
340
    {
341
        return [$this->getPath() => $this->getLabel()];
342
    }
343
344
    /**
345
     * If FormElement has `_unique` rule, it will get all appropriate
346
     * validation rules based on underlying model.
347
     *
348
     * @return array
349
     */
350
    public function getValidationRules()
351
    {
352
        $rules = parent::getValidationRules();
353
354
        foreach ($rules as &$rule) {
355
            if ($rule !== '_unique') {
356
                continue;
357
            }
358
359
            $model = $this->resolvePath();
360
            $table = $model->getTable();
361
362
            $rule = 'unique:'.$table.','.$this->getModelAttributeKey();
363
            if ($model->exists) {
364
                $rule .= ','.$model->getKey();
365
            }
366
        }
367
        unset($rule);
368
369
        return [$this->getPath() => $rules];
370
    }
371
372
    /**
373
     * Get model related to form element.
374
     *
375
     * @return mixed
376
     */
377
    public function resolvePath()
378
    {
379
        $model = $this->getModel();
380
        $relations = explode('.', $this->getPath());
381
        $count = count($relations);
382
383
        if ($count === 1) {
384
            return $model;
385
        }
386
387
        foreach ($relations as $relation) {
388
            if ($count === 1) {
389
                return $model;
390
            }
391
392
            if ($model->exists && ($value = $model->getAttribute($relation)) instanceof Model) {
393
                $model = $value;
394
395
                $count--;
396
                continue;
397
            }
398
399
            if (method_exists($model, $relation)) {
400
                $relation = $model->{$relation}();
401
402
                if ($relation instanceof Relation) {
403
                    $model = $relation->getModel();
404
                    $count--;
405
                    continue;
406
                }
407
            }
408
409
            break;
410
        }
411
412
        throw new LogicException("Can not resolve path for field '{$this->getPath()}'. Probably relation definition is incorrect");
413
    }
414
415
    /**
416
     * @param \Illuminate\Http\Request $request
417
     *
418
     * @return array|string
419
     */
420
    public function getValueFromRequest(\Illuminate\Http\Request $request)
421
    {
422
        if ($request->hasSession() && ! is_null($value = $request->old($this->getPath()))) {
423
            return $value;
424
        }
425
426
        return $request->input($this->getPath());
427
    }
428
429
    /**
430
     * @return mixed
431
     */
432
    public function getValueFromModel()
433
    {
434
        if (($value = $this->getValueFromRequest(request())) !== null) {
0 ignored issues
show
introduced by
The condition $value = $this->getValue...est(request()) !== null is always true.
Loading history...
435
            return $value;
436
        }
437
438
        $model = $this->getModel();
439
        $path = $this->getPath();
440
        $value = $this->getDefaultValue();
441
442
        if ($model === null || ! $model->exists) {
443
            /*
444
              * First check for model existence must go here, before all checks are made
445
              */
446
            return $value;
447
        }
448
449
        /*
450
          * Implement json parsing
451
          */
452
        if (strpos($path, '->') !== false) {
453
            $casts = collect($model->getCasts());
454
            $jsonParts = collect(explode('->', $path));
455
456
            $jsonAttr = $model->{$jsonParts->first()};
457
458
            $cast = $casts->get($jsonParts->first(), false);
459
460
            if ($cast === 'object') {
461
                $jsonAttr = json_decode(json_encode($jsonAttr), true);
462
            } elseif ($cast !== 'array') {
463
                $jsonAttr = json_decode($jsonAttr);
464
            }
465
466
            return Arr::get($jsonAttr, $jsonParts->slice(1)->implode('.'));
467
        }
468
469
        $relations = explode('.', $path);
470
        $count = count($relations);
471
472
        if ($count === 1) {
473
            $attribute = $model->getAttribute($this->getModelAttributeKey());
474
            if (! empty($attribute) || $attribute === 0 || is_null($value)) {
475
                return $attribute;
476
            }
477
        }
478
479
        foreach ($relations as $relation) {
480
            if ($model->{$relation} instanceof Model) {
481
                $model = $model->{$relation};
482
                continue;
483
            }
484
            if ($count === 2) {
485
                if (Str::contains($relation, '->')) {
486
                    $parts = explode('->', $relation);
487
                    $relationField = array_shift($array);
488
                    $jsonPath = implode('.', $parts);
489
                    $attribute = data_get($model->{$relationField}, $jsonPath);
490
                } else {
491
                    $attribute = $model->getAttribute($relation);
492
                }
493
                if (! empty($attribute) || is_null($value)) {
494
                    return $attribute;
495
                }
496
            }
497
498
            if ($this->getDefaultValue() === null) {
499
                throw new LogicException("Can not fetch value for field '{$path}'. Probably relation definition is incorrect");
500
            }
501
        }
502
503
        return $value;
504
    }
505
506
    /**
507
     * @param \Illuminate\Http\Request $request
508
     *
509
     * @return void
510
     */
511
    public function save(\Illuminate\Http\Request $request)
512
    {
513
        $this->setModelAttribute($this->getValueFromRequest($request));
514
    }
515
516
    /**
517
     * @param mixed $value
518
     *
519
     * @return void
520
     */
521
    public function setModelAttribute($value)
522
    {
523
        $model = $this->getModelByPath($this->getPath());
524
525
        if ($this->isValueSkipped()) {
526
            return;
527
        }
528
529
        $model->setAttribute($this->getModelAttributeKey(), $this->prepareValue($value));
530
    }
531
532
    /**
533
     * @param string $path
534
     *
535
     * @return Model|null
536
     */
537
    protected function getModelByPath($path)
538
    {
539
        $model = $this->getModel();
540
541
        $relations = explode('.', $path);
542
        $count = count($relations);
543
        $i = 1;
544
545
        if ($count > 1) {
546
            $i++;
547
            $previousModel = $model;
548
549
            /*
0 ignored issues
show
Unused Code Comprehensibility introduced by
38% 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...
550
              * @var Model $model
551
              */
552
            foreach ($relations as $relation) {
553
                $relatedModel = null;
554
                if ($previousModel->getAttribute($relation) instanceof Model) {
555
                    $relatedModel = $previousModel->getAttribute($relation);
556
                } elseif (method_exists($previousModel, $relation)) {
557
558
                    /**
559
                     * @var Relation
560
                     */
561
                    $relationObject = $previousModel->{$relation}();
562
563
                    switch (get_class($relationObject)) {
564
                        case BelongsTo::class:
565
                            $relationObject->associate($relatedModel = $relationObject->getRelated());
566
                            break;
567
                        case HasOne::class:
568
                        case MorphOne::class:
569
                            $relatedModel = $relationObject->getRelated()->newInstance();
570
                            $relatedModel->setAttribute($this->getForeignKeyNameFromRelation($relationObject), $relationObject->getParentKey());
571
                            $model->setRelation($relation, $relatedModel);
572
                            break;
573
                    }
574
                }
575
576
                $previousModel = $relatedModel;
577
                if ($i === $count) {
578
                    break;
579
                } elseif (is_null($relatedModel)) {
580
                    throw new LogicException("Field [{$path}] can't be mapped to relations of model ".get_class($model).'. Probably some dot delimeted segment is not a supported relation type');
581
                }
582
            }
583
584
            $model = $previousModel;
585
        }
586
587
        return $model;
588
    }
589
590
    protected function getForeignKeyNameFromRelation($relation)
591
    {
592
        return method_exists($relation, 'getForeignKeyName') ? $relation->getForeignKeyName()
593
            : $relation->getPlainForeignKey();
594
    }
595
596
    /**
597
     * Field->mutateValue(function($value) {
598
     *     return bcrypt($value);
599
     * }).
600
     *
601
     * @param \Closure $mutator
602
     *
603
     * @return $this
604
     */
605
    public function mutateValue(Closure $mutator)
606
    {
607
        $this->mutator = $mutator;
608
609
        return $this;
610
    }
611
612
    /**
613
     * @return bool
614
     */
615
    public function hasMutator()
616
    {
617
        return is_callable($this->mutator);
618
    }
619
620
    /**
621
     * @param mixed $value
622
     *
623
     * @return mixed
624
     */
625
    public function prepareValue($value)
626
    {
627
        if ($this->hasMutator()) {
628
            $value = call_user_func($this->mutator, $value);
629
        }
630
631
        return $value;
632
    }
633
634
    /**
635
     * @return mixed
636
     */
637
    public function getExactValue()
638
    {
639
        return $this->exactValue;
640
    }
641
642
    /**
643
     * @param mixed $exactValue
644
     *
645
     * @return $this
646
     */
647
    public function setExactValue($exactValue)
648
    {
649
        $this->exactValue = $exactValue;
650
        $this->exactValueSet = true;
651
652
        return $this;
653
    }
654
655
    /**
656
     * @return array
657
     */
658
    public function toArray()
659
    {
660
        $this->setHtmlAttributes([
661
            'id' => $this->getId(),
662
            'name' => $this->getName(),
663
        ]);
664
665
        return array_merge(parent::toArray(), [
666
            'id' => $this->getId(),
667
            'value' => $this->exactValueSet ? $this->getExactValue() : $this->getValueFromModel(),
668
            'name' => $this->getName(),
669
            'path' => $this->getPath(),
670
            'label' => $this->getLabel(),
671
            'attributes' => $this->htmlAttributesToString(),
672
            'helpText' => $this->getHelpText(),
673
            'required' => in_array('required', $this->validationRules),
674
        ]);
675
    }
676
}
677