Completed
Push — master ( c833c1...b511be )
by Song
03:16
created

Builder::getResourceId()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 0
dl 0
loc 4
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace Encore\Admin\Form;
4
5
use Encore\Admin\Admin;
6
use Encore\Admin\Form;
7
use Encore\Admin\Form\Field\Hidden;
8
use Illuminate\Support\Arr;
9
use Illuminate\Support\Collection;
10
use Illuminate\Support\Facades\URL;
11
use Illuminate\Support\Str;
12
13
/**
14
 * Class Builder.
15
 */
16
class Builder
17
{
18
    /**
19
     *  Previous url key.
20
     */
21
    const PREVIOUS_URL_KEY = '_previous_';
22
23
    /**
24
     * @var mixed
25
     */
26
    protected $id;
27
28
    /**
29
     * @var Form
30
     */
31
    protected $form;
32
33
    /**
34
     * @var
35
     */
36
    protected $action;
37
38
    /**
39
     * @var Collection
40
     */
41
    protected $fields;
42
43
    /**
44
     * @var array
45
     */
46
    protected $options = [];
47
48
    /**
49
     * Modes constants.
50
     */
51
    const MODE_EDIT = 'edit';
52
    const MODE_CREATE = 'create';
53
54
    /**
55
     * Form action mode, could be create|view|edit.
56
     *
57
     * @var string
58
     */
59
    protected $mode = 'create';
60
61
    /**
62
     * @var array
63
     */
64
    protected $hiddenFields = [];
65
66
    /**
67
     * @var Tools
68
     */
69
    protected $tools;
70
71
    /**
72
     * @var Footer
73
     */
74
    protected $footer;
75
76
    /**
77
     * Width for label and field.
78
     *
79
     * @var array
80
     */
81
    protected $width = [
82
        'label' => 2,
83
        'field' => 8,
84
    ];
85
86
    /**
87
     * View for this form.
88
     *
89
     * @var string
90
     */
91
    protected $view = 'admin::form';
92
93
    /**
94
     * Form title.
95
     *
96
     * @var string
97
     */
98
    protected $title;
99
100
    /**
101
     * @var string
102
     */
103
    protected $formClass;
104
105
    /**
106
     * Builder constructor.
107
     *
108
     * @param Form $form
109
     */
110
    public function __construct(Form $form)
111
    {
112
        $this->form = $form;
113
114
        $this->fields = new Collection();
115
116
        $this->init();
117
    }
118
119
    /**
120
     * Do initialize.
121
     */
122
    public function init()
123
    {
124
        $this->tools = new Tools($this);
125
        $this->footer = new Footer($this);
126
127
        $this->formClass = 'model-form-'.uniqid();
128
    }
129
130
    /**
131
     * Get form tools instance.
132
     *
133
     * @return Tools
134
     */
135
    public function getTools()
136
    {
137
        return $this->tools;
138
    }
139
140
    /**
141
     * Get form footer instance.
142
     *
143
     * @return Footer
144
     */
145
    public function getFooter()
146
    {
147
        return $this->footer;
148
    }
149
150
    /**
151
     * Set the builder mode.
152
     *
153
     * @param string $mode
154
     *
155
     * @return void
156
     */
157
    public function setMode($mode = 'create')
158
    {
159
        $this->mode = $mode;
160
    }
161
162
    /**
163
     * @return string
164
     */
165
    public function getMode(): string
166
    {
167
        return $this->mode;
168
    }
169
170
    /**
171
     * Returns builder is $mode.
172
     *
173
     * @param $mode
174
     *
175
     * @return bool
176
     */
177
    public function isMode($mode): bool
178
    {
179
        return $this->mode === $mode;
180
    }
181
182
    /**
183
     * Check if is creating resource.
184
     *
185
     * @return bool
186
     */
187
    public function isCreating(): bool
188
    {
189
        return $this->isMode(static::MODE_CREATE);
190
    }
191
192
    /**
193
     * Check if is editing resource.
194
     *
195
     * @return bool
196
     */
197
    public function isEditing(): bool
198
    {
199
        return $this->isMode(static::MODE_EDIT);
200
    }
201
202
    /**
203
     * Set resource Id.
204
     *
205
     * @param $id
206
     *
207
     * @return void
208
     */
209
    public function setResourceId($id)
210
    {
211
        $this->id = $id;
212
    }
213
214
    /**
215
     * Get Resource id.
216
     *
217
     * @return mixed
218
     */
219
    public function getResourceId()
220
    {
221
        return $this->id;
222
    }
223
224
    /**
225
     * @param int|null $slice
226
     *
227
     * @return string
228
     */
229
    public function getResource(int $slice = null): string
230
    {
231
        if ($this->mode === self::MODE_CREATE) {
232
            return $this->form->resource(-1);
233
        }
234
        if ($slice !== null) {
235
            return $this->form->resource($slice);
236
        }
237
238
        return $this->form->resource();
239
    }
240
241
    /**
242
     * @param int $field
243
     * @param int $label
244
     *
245
     * @return $this
246
     */
247
    public function setWidth($field = 8, $label = 2): self
248
    {
249
        $this->width = [
250
            'label' => $label,
251
            'field' => $field,
252
        ];
253
254
        return $this;
255
    }
256
257
    /**
258
     * Get label and field width.
259
     *
260
     * @return array
261
     */
262
    public function getWidth(): array
263
    {
264
        return $this->width;
265
    }
266
267
    /**
268
     * Set form action.
269
     *
270
     * @param string $action
271
     */
272
    public function setAction($action)
273
    {
274
        $this->action = $action;
275
    }
276
277
    /**
278
     * Get Form action.
279
     *
280
     * @return string
281
     */
282
    public function getAction(): string
283
    {
284
        if ($this->action) {
285
            return $this->action;
286
        }
287
288
        if ($this->isMode(static::MODE_EDIT)) {
289
            return $this->form->resource().'/'.$this->id;
290
        }
291
292
        if ($this->isMode(static::MODE_CREATE)) {
293
            return $this->form->resource(-1);
294
        }
295
296
        return '';
297
    }
298
299
    /**
300
     * Set view for this form.
301
     *
302
     * @param string $view
303
     *
304
     * @return $this
305
     */
306
    public function setView($view): self
307
    {
308
        $this->view = $view;
309
310
        return $this;
311
    }
312
313
    /**
314
     * Set title for form.
315
     *
316
     * @param string $title
317
     *
318
     * @return $this
319
     */
320
    public function setTitle($title): self
321
    {
322
        $this->title = $title;
323
324
        return $this;
325
    }
326
327
    /**
328
     * Get fields of this builder.
329
     *
330
     * @return Collection
331
     */
332
    public function fields(): Collection
333
    {
334
        return $this->fields;
335
    }
336
337
    /**
338
     * Get specify field.
339
     *
340
     * @param string $name
341
     *
342
     * @return mixed
343
     */
344
    public function field($name)
345
    {
346
        return $this->fields()->first(function (Field $field) use ($name) {
347
            return $field->column() === $name;
348
        });
349
    }
350
351
    /**
352
     * If the parant form has rows.
353
     *
354
     * @return bool
355
     */
356
    public function hasRows(): bool
357
    {
358
        return !empty($this->form->rows);
359
    }
360
361
    /**
362
     * Get field rows of form.
363
     *
364
     * @return array
365
     */
366
    public function getRows(): array
367
    {
368
        return $this->form->rows;
369
    }
370
371
    /**
372
     * @return array
373
     */
374
    public function getHiddenFields(): array
375
    {
376
        return $this->hiddenFields;
377
    }
378
379
    /**
380
     * @param Field $field
381
     *
382
     * @return void
383
     */
384
    public function addHiddenField(Field $field)
385
    {
386
        $this->hiddenFields[] = $field;
387
    }
388
389
    /**
390
     * Add or get options.
391
     *
392
     * @param array $options
393
     *
394
     * @return array|null
395
     */
396
    public function options($options = [])
397
    {
398
        if (empty($options)) {
399
            return $this->options;
400
        }
401
402
        $this->options = array_merge($this->options, $options);
403
    }
404
405
    /**
406
     * Get or set option.
407
     *
408
     * @param string $option
409
     * @param mixed  $value
410
     *
411
     * @return $this
412
     */
413
    public function option($option, $value = null)
414
    {
415
        if (func_num_args() === 1) {
416
            return Arr::get($this->options, $option);
417
        }
418
419
        $this->options[$option] = $value;
420
421
        return $this;
422
    }
423
424
    /**
425
     * @return string
426
     */
427
    public function title(): string
428
    {
429
        if ($this->title) {
430
            return $this->title;
431
        }
432
433
        if ($this->mode === static::MODE_CREATE) {
434
            return trans('admin.create');
435
        }
436
437
        if ($this->mode === static::MODE_EDIT) {
438
            return trans('admin.edit');
439
        }
440
441
        return '';
442
    }
443
444
    /**
445
     * Determine if form fields has files.
446
     *
447
     * @return bool
448
     */
449
    public function hasFile(): bool
450
    {
451
        foreach ($this->fields() as $field) {
452
            if ($field instanceof Field\File || $field instanceof Field\MultipleFile) {
453
                return true;
454
            }
455
        }
456
457
        return false;
458
    }
459
460
    /**
461
     * Add field for store redirect url after update or store.
462
     *
463
     * @return void
464
     */
465
    protected function addRedirectUrlField()
466
    {
467
        $previous = URL::previous();
468
469
        if (!$previous || $previous === URL::current()) {
470
            return;
471
        }
472
473
        if (Str::contains($previous, url($this->getResource()))) {
0 ignored issues
show
Bug introduced by
It seems like url($this->getResource()) targeting url() can also be of type object<Illuminate\Contracts\Routing\UrlGenerator>; however, Illuminate\Support\Str::contains() does only seem to accept string|array<integer,string>, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
474
            $this->addHiddenField((new Hidden(static::PREVIOUS_URL_KEY))->value($previous));
475
        }
476
    }
477
478
    /**
479
     * Open up a new HTML form.
480
     *
481
     * @param array $options
482
     *
483
     * @return string
484
     */
485
    public function open($options = []): string
486
    {
487
        $attributes = [];
488
489
        if ($this->isMode(self::MODE_EDIT)) {
490
            $this->addHiddenField((new Hidden('_method'))->value('PUT'));
491
        }
492
493
        $this->addRedirectUrlField();
494
495
        $attributes['action'] = $this->getAction();
496
        $attributes['method'] = Arr::get($options, 'method', 'post');
497
        $attributes['class'] = implode(' ', ['form-horizontal', $this->formClass]);
498
        $attributes['accept-charset'] = 'UTF-8';
499
500
        if ($this->hasFile()) {
501
            $attributes['enctype'] = 'multipart/form-data';
502
        }
503
504
        $html = [];
505
        foreach ($attributes as $name => $value) {
506
            $html[] = "$name=\"$value\"";
507
        }
508
509
        return '<form '.implode(' ', $html).' pjax-container>';
510
    }
511
512
    /**
513
     * Close the current form.
514
     *
515
     * @return string
516
     */
517
    public function close(): string
518
    {
519
        $this->form = null;
520
        $this->fields = null;
521
522
        return '</form>';
523
    }
524
525
    /**
526
     * @param string $message
527
     */
528
    public function confirm(string $message)
529
    {
530
        $trans = [
531
            'confirm' => trans('admin.confirm'),
532
            'cancel'  => trans('admin.cancel'),
533
        ];
534
535
        $script = <<<SCRIPT
536
$('form.{$this->formClass} button[type=submit]').click(function (e) {
537
    e.preventDefault();
538
    var form = $(this).parents('form');
539
    swal({
540
        title: "$message",
541
        type: "warning",
542
        showCancelButton: true,
543
        confirmButtonColor: "#DD6B55",
544
        confirmButtonText: "{$trans['confirm']}",
545
        cancelButtonText: "{$trans['cancel']}",
546
    }).then(function (result) {
547
        if (result.value) {
548
          form.submit();
549
        }
550
    });
551
});
552
SCRIPT;
553
554
        Admin::script($script);
555
    }
556
557
    /**
558
     * Remove reserved fields like `id` `created_at` `updated_at` in form fields.
559
     *
560
     * @return void
561
     */
562
    protected function removeReservedFields()
563
    {
564
        if (!$this->isCreating()) {
565
            return;
566
        }
567
568
        $reservedColumns = [
569
            $this->form->model()->getKeyName(),
570
            $this->form->model()->getCreatedAtColumn(),
571
            $this->form->model()->getUpdatedAtColumn(),
572
        ];
573
574
        $this->form->getLayout()->removeReservedFields($reservedColumns);
575
576
        $this->fields = $this->fields()->reject(function (Field $field) use ($reservedColumns) {
577
            return in_array($field->column(), $reservedColumns, true);
578
        });
579
    }
580
581
    /**
582
     * Render form header tools.
583
     *
584
     * @return string
585
     */
586
    public function renderTools(): string
587
    {
588
        return $this->tools->render();
589
    }
590
591
    /**
592
     * Render form footer.
593
     *
594
     * @return string
595
     */
596
    public function renderFooter(): string
597
    {
598
        return $this->footer->render();
599
    }
600
601
    /**
602
     * Add script for tab form.
603
     */
604
    protected function addTabformScript()
605
    {
606
        $script = <<<'SCRIPT'
607
608
var hash = document.location.hash;
609
if (hash) {
610
    $('.nav-tabs a[href="' + hash + '"]').tab('show');
611
}
612
613
// Change hash for page-reload
614
$('.nav-tabs a').on('shown.bs.tab', function (e) {
615
    history.pushState(null,null, e.target.hash);
616
});
617
618
if ($('.has-error').length) {
619
    $('.has-error').each(function () {
620
        var tabId = '#'+$(this).closest('.tab-pane').attr('id');
621
        $('li a[href="'+tabId+'"] i').removeClass('hide');
622
    });
623
624
    var first = $('.has-error:first').closest('.tab-pane').attr('id');
625
    $('li a[href="#'+first+'"]').tab('show');
626
}
627
628
SCRIPT;
629
        Admin::script($script);
630
    }
631
632
    protected function addCascadeScript()
633
    {
634
        $script = <<<SCRIPT
635
(function () {
636
    $('form.{$this->formClass}').submit(function (e) {
637
        e.preventDefault();
638
        $(this).find('div.form-group.cascade.hide :input').attr('disabled', true);
639
    });
640
})();
641
SCRIPT;
642
643
        Admin::script($script);
644
    }
645
646
    /**
647
     * Render form.
648
     *
649
     * @return string
650
     */
651
    public function render(): string
652
    {
653
        $this->removeReservedFields();
654
655
        $tabObj = $this->form->setTab();
656
657
        if (!$tabObj->isEmpty()) {
658
            $this->addTabformScript();;
659
        }
660
661
        $this->addCascadeScript();
662
663
        $data = [
664
            'form'   => $this,
665
            'tabObj' => $tabObj,
666
            'width'  => $this->width,
667
            'layout' => $this->form->getLayout(),
668
        ];
669
670
        return view($this->view, $data)->render();
0 ignored issues
show
Bug introduced by
The method render does only exist in Illuminate\View\View, but not in Illuminate\Contracts\View\Factory.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
671
    }
672
}
673