Passed
Push — main ( c6d2b5...2757d6 )
by Nobufumi
03:20
created

TableRenderer::resolveOptionLabel()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 2
c 1
b 0
f 0
nc 1
nop 2
dl 0
loc 4
rs 10
1
<?php
2
3
namespace Jidaikobo\Kontiki\Renderers;
4
5
use Carbon\Carbon;
6
use Slim\Views\PhpRenderer;
7
use Jidaikobo\Kontiki\Models\ModelInterface;
8
9
class TableRenderer
10
{
11
    protected $fields;
12
    protected $data;
13
    protected $view;
14
    protected $table;
15
    protected $adminDirName;
16
    protected $context;
17
    protected $routes;
18
    protected $postType;
19
    protected $deleteType; // Context: "hardDelete" or "softDelete"
20
    private ?ModelInterface $model = null;
21
22
    public function __construct(PhpRenderer $view)
23
    {
24
        $this->view = $view;
25
    }
26
27
    public function setModel(ModelInterface $model): void
28
    {
29
        $this->model = $model;
30
    }
31
32
    public function render(
33
        array $data,
34
        string $adminDirName,
35
        array $routes = [],
36
        string $context = 'all'
37
    ): string {
38
        $this->deleteType = $this->model->getDeleteType();
0 ignored issues
show
Bug introduced by
The method getDeleteType() does not exist on null. ( Ignorable by Annotation )

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

38
        /** @scrutinizer ignore-call */ 
39
        $this->deleteType = $this->model->getDeleteType();

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...
39
        $this->data = $data;
40
        $this->adminDirName = $adminDirName;
41
        $this->routes = $routes;
42
        $this->context = $context;
43
44
        $this->fields = array_filter(
45
            $this->model->getFields(),
46
            fn($field) => isset($field['display_in_list']) &&
47
                ($field['display_in_list'] === true || $field['display_in_list'] == $context)
48
        );
49
50
        $createButton = $this->renderCreateButton();
51
        $displayModes = $this->renderDisplayModes();
52
        $headers = $this->renderHeaders();
53
        $rows = array_map(function ($row) {
54
            return $this->renderRow($row);
55
        }, $this->data);
56
57
        return $this->view->fetch('tables/table.php', [
58
            'createButton' => $createButton,
59
            'displayModes' => $displayModes,
60
            'headers' => $headers,
61
            'rows' => implode("\n", $rows),
62
        ]);
63
    }
64
65
    protected function renderCreateButton(): array
66
    {
67
        $filtered = array_filter($this->routes, function ($routes) {
68
            return in_array('createButton', $routes['type'], true);
69
        });
70
        $createButton = !empty($filtered) ? reset($filtered) : [];
71
        return $createButton;
72
    }
73
74
    protected function renderDisplayModes(): array
75
    {
76
        $displayModes = [];
77
78
        foreach ($this->routes as $route) {
79
            if (strpos($route['path'], $this->adminDirName . '/index') === false) {
80
                continue;
81
            }
82
83
            $displayModes[] = [
84
                'name' => __(basename($route['path'])),
85
                'path' => $route['path'],
86
            ];
87
        }
88
89
        return $displayModes;
90
    }
91
92
    protected function renderHeaders(): string
93
    {
94
        $headerHtml = '';
95
        foreach ($this->fields as $name => $config) {
96
            $label = e($config['label']);
97
            $headerHtml .= sprintf('<th class="text-nowrap">%s</th>', $label);
98
        }
99
        $headerHtml .= '<th>' . __('actions') . '</th>';
100
        return $headerHtml;
101
    }
102
103
    protected function renderRow(array $row): string
104
    {
105
        $cellsHtml = $this->renderValues($row);
106
        $cellsHtml .= $this->renderActions($row);
107
108
        return sprintf('<tr>%s</tr>', $cellsHtml);
109
    }
110
111
    protected function renderValues(array $row): string
112
    {
113
        $currentTime = Carbon::now('UTC')->setTimezone(env('TIMEZONE', 'UTC'));
114
        $cellsHtml = '';
115
116
        foreach (array_keys($this->fields) as $name) {
117
            $values = $this->getRowValues($name, $row, $currentTime);
118
            $value = implode(', ', array_filter($values));
119
            $cellsHtml .= sprintf('<td>%s</td>', e($value));
120
        }
121
122
        return $cellsHtml;
123
    }
124
125
    protected function getRowValues(string $name, array $row, Carbon $currentTime): array
126
    {
127
        if ($name === 'status') {
128
            return $this->getStatusValues($row, $currentTime);
129
        }
130
131
        if ($this->isSelectableField($name)) {
132
            return [$this->resolveOptionLabel($name, $row[$name] ?? '')];
133
        }
134
135
        if ($this->isUtcField($name)) {
136
            return [$this->formatUtcField($row[$name] ?? '')];
137
        }
138
139
        return [$row[$name] ?? ''];
140
    }
141
142
    private function isSelectableField(string $name): bool
143
    {
144
        $type = $this->fields[$name]['type'] ?? 'text';
145
        return in_array($type, ['select', 'checkbox', 'radio'], true) && $name !== 'status';
146
    }
147
148
    private function resolveOptionLabel(string $name, string|int|null $value): string
149
    {
150
        $options = $this->fields[$name]['options'] ?? [];
151
        return $options[$value] ?? '';
152
    }
153
154
    private function isUtcField(string $name): bool
155
    {
156
        return !empty($this->fields[$name]['save_as_utc']);
157
    }
158
159
    private function formatUtcField(?string $value): string
160
    {
161
        if (empty($value)) {
162
            return '';
163
        }
164
165
        try {
166
            return Carbon::parse($value, 'UTC')
167
                ->setTimezone(env('TIMEZONE', 'UTC'))
168
                ->format('Y-m-d H:i');
169
        } catch (\Exception $e) {
170
            return $value; // fallback to raw
171
        }
172
    }
173
174
    private function getStatusValues(array $row, Carbon $currentTime): array
175
    {
176
        $values = [__($row['status'] ?? '') ?: ''];
177
178
        $this->addStatusIfConditionMet(
179
            $values,
180
            $row,
181
            'published_at',
182
            $currentTime,
183
            fn($time) => $time->greaterThan($currentTime),
184
            'reserved'
185
        );
186
187
        $this->addStatusIfConditionMet(
188
            $values,
189
            $row,
190
            'expired_at',
191
            $currentTime,
192
            fn($time) => $currentTime->greaterThan($time),
193
            'expired'
194
        );
195
196
        return $values;
197
    }
198
199
    /**
200
     * Add a status to values if the condition is met.
201
     *
202
     * @param array  $values      Reference to the values array.
203
     * @param array  $row         The data row.
204
     * @param string $key         The key to check in the row.
205
     * @param Carbon $currentTime The current timestamp.
206
     * @param callable $condition Callback that takes a Carbon instance and returns a boolean.
207
     * @param string $status      The status text to add if the condition is met.
208
     */
209
    private function addStatusIfConditionMet(
210
        array &$values,
211
        array $row,
212
        string $key,
213
        Carbon $currentTime,
0 ignored issues
show
Unused Code introduced by
The parameter $currentTime is not used and could be removed. ( Ignorable by Annotation )

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

213
        /** @scrutinizer ignore-unused */ Carbon $currentTime,

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
214
        callable $condition,
215
        string $status
216
    ): void {
217
        if (!empty($row[$key])) {
218
            $time = Carbon::parse($row[$key]);
219
            if ($condition($time)) {
220
                $values[0] = __($status);
221
            }
222
        }
223
    }
224
225
    protected function renderActions(array $row): string
226
    {
227
        $id = e($row['id']);
228
229
        $uri = env('BASEPATH', '') . "/{$this->adminDirName}/%s/%s";
230
231
        $tpl = '<a href="' . $uri . '" class="btn btn-%s btn-sm">%s</a> ';
232
        $tplTrash = '<a href="' . $uri . '" class="btn btn-%s btn-sm">%s <span class="fa-solid fa-trash"></span></a> ';
233
        $tplPreview = '<a href="' . $uri . '" class="btn btn-%s btn-sm" target="preview">%s <span class="fa-solid fa-arrow-up-right-from-square" aria-label="' . __('open_in_new_window') . '"></span></a> ';
234
235
        $actions = [
236
            'edit' => sprintf($tpl, 'edit', $id, 'primary', __('edit')),
237
            'delete' => sprintf($tplTrash, 'delete', $id, 'danger', __('delete')),
238
            'trash' => sprintf($tplTrash, 'trash', $id, 'warning', __('to_trash')),
239
            'restore' => sprintf($tpl, 'restore', $id, 'success', __('restore')),
240
            'preview' => sprintf($tplPreview, 'preview', $id, 'info', __('preview')),
241
        ];
242
243
        $previewBtn = $this->view->getAttributes()['is_previewable'] ? $actions['preview'] : '';
244
245
        $html = '';
246
        if ($this->deleteType == 'hardDelete') {
247
            $html .= $actions['edit'] . $actions['delete'];
248
        } elseif ($this->context == 'trash') {
249
            $html .= $actions['restore'] . $previewBtn . $actions['delete'];
250
        } elseif ($this->deleteType == 'softDelete') {
251
            $html .= $actions['edit'] . $previewBtn . $actions['trash'];
252
        } else {
253
            $html .= $actions['edit'];
254
        }
255
256
        return sprintf('<td class="text-nowrap">%s</td>', $html);
257
    }
258
}
259