Passed
Push — main ( 7049cd...74d068 )
by Nobufumi
02:57
created

TableRenderer::getRowValues()   B

Complexity

Conditions 8
Paths 7

Size

Total Lines 49
Code Lines 32

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 8
eloc 32
c 2
b 0
f 0
nc 7
nop 3
dl 0
loc 49
rs 8.1635
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
        $type = $this->fields[$name]['type'] ?? 'text';
128
        if ($name !== 'status' && in_array($type, ['select', 'checkbox', 'radio'])) {
129
            $options = $this->fields[$name]['options'] ?? [];
130
            return [$options[$row[$name]] ?? ''];
131
        }
132
133
        $saveAsUtc = $this->fields[$name]['save_as_utc'] ?? false;
134
        if ($saveAsUtc) {
135
            $rawValue = $row[$name] ?? null;
136
137
            if ($rawValue) {
138
                try {
139
                    $carbon = Carbon::parse($rawValue, 'UTC')->setTimezone(env('TIMEZONE', 'UTC'));
140
                    return [$carbon->format('Y-m-d H:i')];
141
                } catch (\Exception $e) {
142
                    // fallback in case of invalid datetime
143
                    return [$rawValue];
144
                }
145
            }
146
147
            return [''];
148
        }
149
150
        if ($name !== 'status') {
151
            return [$row[$name] ?? ''];
152
        }
153
154
        $values = [__($row[$name]) ?: ''];
155
156
        $this->addStatusIfConditionMet(
157
            $values,
158
            $row,
159
            'published_at',
160
            $currentTime,
161
            fn($time) => $time->greaterThan($currentTime),
162
            'reserved'
163
        );
164
        $this->addStatusIfConditionMet(
165
            $values,
166
            $row,
167
            'expired_at',
168
            $currentTime,
169
            fn($time) => $currentTime->greaterThan($time),
170
            'expired'
171
        );
172
173
        return $values;
174
    }
175
176
    /**
177
     * Add a status to values if the condition is met.
178
     *
179
     * @param array  $values      Reference to the values array.
180
     * @param array  $row         The data row.
181
     * @param string $key         The key to check in the row.
182
     * @param Carbon $currentTime The current timestamp.
183
     * @param callable $condition Callback that takes a Carbon instance and returns a boolean.
184
     * @param string $status      The status text to add if the condition is met.
185
     */
186
    private function addStatusIfConditionMet(
187
        array &$values,
188
        array $row,
189
        string $key,
190
        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

190
        /** @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...
191
        callable $condition,
192
        string $status
193
    ): void {
194
        if (!empty($row[$key])) {
195
            $time = Carbon::parse($row[$key]);
196
            if ($condition($time)) {
197
                $values[0] = __($status);
198
            }
199
        }
200
    }
201
202
    protected function renderActions(array $row): string
203
    {
204
        $id = e($row['id']);
205
206
        $uri = env('BASEPATH', '') . "/{$this->adminDirName}/%s/%s";
207
208
        $tpl = '<a href="' . $uri . '" class="btn btn-%s btn-sm">%s</a> ';
209
        $tplTrash = '<a href="' . $uri . '" class="btn btn-%s btn-sm">%s <span class="fa-solid fa-trash"></span></a> ';
210
        $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> ';
211
212
        $actions = [
213
            'edit' => sprintf($tpl, 'edit', $id, 'primary', __('edit')),
214
            'delete' => sprintf($tplTrash, 'delete', $id, 'danger', __('delete')),
215
            'trash' => sprintf($tplTrash, 'trash', $id, 'warning', __('to_trash')),
216
            'restore' => sprintf($tpl, 'restore', $id, 'success', __('restore')),
217
            'preview' => sprintf($tplPreview, 'preview', $id, 'info', __('preview')),
218
        ];
219
220
        $previewBtn = $this->view->getAttributes()['is_previewable'] ? $actions['preview'] : '';
221
222
        $html = '';
223
        if ($this->deleteType == 'hardDelete') {
224
            $html .= $actions['edit'] . $actions['delete'];
225
        } elseif ($this->context == 'trash') {
226
            $html .= $actions['restore'] . $previewBtn . $actions['delete'];
227
        } elseif ($this->deleteType == 'softDelete') {
228
            $html .= $actions['edit'] . $previewBtn . $actions['trash'];
229
        } else {
230
            $html .= $actions['edit'];
231
        }
232
233
        return sprintf('<td class="text-nowrap">%s</td>', $html);
234
    }
235
}
236