Passed
Push — main ( 25353d...351785 )
by Sammy
01:44
created

ORM::viewport_listing()   B

Complexity

Conditions 7
Paths 7

Size

Total Lines 28
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 7
eloc 18
nc 7
nop 3
dl 0
loc 28
rs 8.8333
c 0
b 0
f 0
1
<?php
2
3
namespace HexMakina\kadro\Controllers;
4
5
use HexMakina\TightORM\Interfaces\ModelInterface;
6
use HexMakina\LeMarchand\LeMarchand;
7
8
abstract class ORM extends Kadro implements Interfaces\ORMInterface
9
{
10
    protected $model_class_name = null;
11
    protected $model_type = null;
12
13
    protected $load_model = null;
14
    protected $form_model = null;
15
16
17
    public function add_errors($errors)
18
    {
19
        foreach ($errors as $err) {
20
            if (is_array($err)) {
21
                $this->add_error(array_unshift($err), array_unshift($err));
22
            } else {
23
                $this->add_error($err);
24
            }
25
        }
26
    }
27
28
    public function load_model(): ?ModelInterface
29
    {
30
        return $this->load_model;
31
    }
32
33
    public function formModel(ModelInterface $setter = null): ModelInterface
34
    {
35
        if (!is_null($setter)) {
36
            $this->form_model = $setter;
37
        } elseif (is_null($this->form_model)) {
38
            $reflection = new \ReflectionClass($this->modelClassName());
39
            $this->form_model = $reflection->newInstanceWithoutConstructor(); //That's it!
40
        }
41
        return $this->form_model;
42
    }
43
44
    // shortcut to model_type
45
    public function modelType(): string
46
    {
47
      // have to go through the model to garantee model_type existence via interface
48
        if (is_null($this->model_type)) {
49
            $this->model_type = get_class($this->formModel())::model_type();
50
        }
51
52
        return $this->model_type;
53
    }
54
55
    public function modelPrefix($suffix = null): string
56
    {
57
        $ret = $this->modelType();
58
59
        if (!is_null($suffix)) {
60
            $ret .= '_' . $suffix;
61
        }
62
63
        return $ret;
64
    }
65
66
    public function prepare()
67
    {
68
        parent::prepare();
69
70
        $this->model_type = $this->modelClassName()::model_type();
71
72
        $pk_values = [];
73
74
        if ($this->router()->submits()) {
75
            $this->formModel()->import($this->sanitize_post_data($this->router()->submitted()));
76
            $pk_values = $this->modelClassName()::table()->primary_keys_match($this->router()->submitted());
77
78
            $this->load_model = $this->modelClassName()::exists($pk_values);
79
        } elseif ($this->router()->requests()) {
80
            $pk_values = $this->modelClassName()::table()->primary_keys_match($this->router()->params());
81
82
            if (!is_null($this->load_model = $this->modelClassName()::exists($pk_values))) {
83
                $this->formModel(clone $this->load_model);
84
            }
85
        }
86
        // TODO restore model history
87
        // if (!is_null($this->load_model) && is_subclass_of($this->load_model, '\HexMakina\Tracer\TraceableInterface') && $this->load_model->traceable()) {
88
        //   // $traces = $this->tracer()->traces_by_model($this->load_model);
89
        //     $traces = $this->load_model->traces();
90
        //     //$this->tracer()->history_by_model($this->load_model);
91
        //     $this->viewport('load_model_history', $traces ?? []);
92
        // }
93
    }
94
95
    public function has_load_model()
96
    {
97
        return !empty($this->load_model);
98
    }
99
100
    // ----------- META -----------
101
102
    // CoC class name by
103
    // 1. replacing namespace Controllers by Models
104
    // 2. removing the  from classname
105
    // overwrite this behavior by setting the model_class_name at controller construction
106
    public function modelClassName(): string
107
    {
108
        if (is_null($this->model_class_name)) {
109
            preg_match(LeMarchand::RX_MVC, get_called_class(), $m);
0 ignored issues
show
Bug introduced by
The constant HexMakina\LeMarchand\LeMarchand::RX_MVC was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
110
            $this->model_class_name = $this->get('Models\\'.$m[2].'::class');
111
        }
112
113
        return $this->model_class_name;
114
    }
115
116
    public function table_name(): string
117
    {
118
        return $this->modelClassName()::table_name();
119
    }
120
121
    public function model_type_to_label($model = null)
122
    {
123
        $model = $model ?? $this->load_model ?? $this->formModel();
124
        return $this->l(sprintf('MODEL_%s_INSTANCE', get_class($model)::model_type()));
125
    }
126
    public function field_name_to_label($model, $field_name)
127
    {
128
        $model = $model ?? $this->load_model ?? $this->formModel();
129
        return $this->l(sprintf('MODEL_%s_FIELD_%s', (get_class($model))::model_type(), $field_name));
130
    }
131
132
    public function dashboard()
133
    {
134
        $this->listing(); //default dashboard is a listing
135
    }
136
137
    public function listing($model = null, $filters = [], $options = [])
138
    {
139
        $class_name = is_null($model) ? $this->modelClassName() : get_class($model);
140
141
        if (!isset($filters['date_start'])) {
142
            $filters['date_start'] = $this->get('StateAgent')->filters('date_start');
143
        }
144
        if (!isset($filters['date_stop'])) {
145
            $filters['date_stop'] = $this->get('StateAgent')->filters('date_stop');
146
        }
147
148
        $listing = $this->modelClassName()::filter($filters);
149
150
        $this->viewport_listing($class_name, $listing, $this->find_template($this->get('template_engine'), __FUNCTION__));
151
    }
152
153
    public function viewport_listing($class_name, $listing, $listing_template)
154
    {
155
        $listing_fields = [];
156
        if (empty($listing)) {
157
            $hidden_columns = ['created_by', 'created_on', 'password'];
158
            foreach ($class_name::table()->columns() as $column) {
159
                if (!$column->isAutoIncremented() && !in_array($column->name(), $hidden_columns)) {
160
                    $listing_fields[$column->name()] = $this->l(sprintf('MODEL_%s_FIELD_%s', $class_name::model_type(), $column->name()));
161
                }
162
            }
163
        } else {
164
            $current = current($listing);
165
            if (is_object($current)) {
166
                $current = get_object_vars($current);
167
            }
168
169
            foreach (array_keys($current) as $field) {
170
                $listing_fields[$field] = $this->l(sprintf('MODEL_%s_FIELD_%s', $class_name::model_type(), $field));
171
            }
172
        }
173
174
        $this->viewport('listing', $listing);
175
        $this->viewport('listing_title', $this->l(sprintf('MODEL_%s_INSTANCES', $class_name::model_type())));
176
        $this->viewport('listing_fields', $listing_fields);
177
        $this->viewport('listing_template', $listing_template);
178
179
        $this->viewport('route_new', $this->router()->hyp($class_name::model_type() . '_new'));
180
        $this->viewport('route_export', $this->router()->hyp($class_name::model_type() . '_export'));
181
    }
182
183
    public function copy()
184
    {
185
        $this->formModel($this->load_model->copy());
186
187
        $this->route_back($this->load_model);
188
        $this->edit();
189
    }
190
191
    public function edit()
192
    {
193
    }
194
195
    public function save()
196
    {
197
        $model = $this->persist_model($this->formModel());
198
199
        if (empty($this->errors())) {
200
            $this->route_back($model);
201
        } else {
202
            $this->edit();
203
            return 'edit';
204
        }
205
    }
206
207
    public function persist_model($model): ?ModelInterface
208
    {
209
        $this->errors = $model->save($this->operator()->operator_id()); // returns [errors]
210
        if (empty($this->errors())) {
211
            $this->logger()->nice($this->l('CRUDITES_INSTANCE_ALTERED', [$this->l('MODEL_' . get_class($model)::model_type() . '_INSTANCE')]));
212
            return $model;
213
        }
214
        foreach ($this->errors() as $field => $error_msg) {
215
            $this->logger()->warning($this->l($error_msg, [$field]));
216
        }
217
218
        return null;
219
    }
220
221
    public function before_edit()
222
    {
223
        if (!is_null($this->router()->params('id')) && is_null($this->load_model)) {
224
            $this->logger()->warning($this->l('CRUDITES_ERR_INSTANCE_NOT_FOUND', [$this->l('MODEL_' . $this->modelClassName()::model_type() . '_INSTANCE')]));
225
            $this->router()->hop($this->modelClassName()::model_type());
226
        }
227
    }
228
229
    public function before_save()
230
    {
231
        return [];
232
    }
233
234
  // default: hop to altered object
235
    public function after_save()
236
    {
237
        $this->router()->hop($this->route_back());
238
    }
239
240
    public function destroy_confirm()
241
    {
242
        if (is_null($this->load_model)) {
243
            $this->logger()->warning($this->l('CRUDITES_ERR_INSTANCE_NOT_FOUND', [$this->l('MODEL_' . $this->model_type . '_INSTANCE')]));
244
            $this->router()->hop($this->model_type);
245
        }
246
247
        $this->before_destroy();
248
249
        return 'destroy';
250
    }
251
252
    public function before_destroy() // default: checks for load_model and immortality, hops back to object on failure
253
    {
254
        if (is_null($this->load_model)) {
255
            $this->logger()->warning($this->l('CRUDITES_ERR_INSTANCE_NOT_FOUND', [$this->l('MODEL_' . $this->model_type . '_INSTANCE')]));
256
            $this->router()->hop($this->model_type);
257
        } elseif ($this->load_model->immortal()) {
258
            $this->logger()->warning($this->l('CRUDITES_ERR_INSTANCE_IS_IMMORTAL', [$this->l('MODEL_' . $this->model_type . '_INSTANCE')]));
259
            $this->router()->hop($this->route_model($this->load_model));
260
        }
261
    }
262
263
    public function destroy()
264
    {
265
        if (!$this->router()->submits()) {
266
            throw new \Exception('KADRO_ROUTER_MUST_SUBMIT');
267
        }
268
269
        if ($this->load_model->destroy($this->operator()->operator_id()) === false) {
270
            $this->logger()->info($this->l('CRUDITES_ERR_INSTANCE_IS_UNDELETABLE', ['' . $this->load_model]));
271
            $this->route_back($this->load_model);
272
        } else {
273
            $this->logger()->nice($this->l('CRUDITES_INSTANCE_DESTROYED', [$this->l('MODEL_' . $this->model_type . '_INSTANCE')]));
274
            $this->route_back($this->model_type);
275
        }
276
    }
277
278
    public function after_destroy()
279
    {
280
        $this->router()->hop($this->route_back());
281
    }
282
283
    public function conclude()
284
    {
285
        $this->viewport('errors', $this->errors());
286
        $this->viewport('form_model_type', $this->model_type);
287
        $this->viewport('form_model', $this->formModel());
288
289
        if (isset($this->load_model)) {
290
            $this->viewport('load_model', $this->load_model);
291
        }
292
    }
293
294
    public function collection_to_csv($collection, $filename)
295
    {
296
      // TODO use Format/File/CSV class to generate file
297
        $file_path = $this->get('settings.export.directory') . $filename . '.csv';
298
        $fp = fopen($file_path, 'w');
299
300
        $header = false;
301
302
        foreach ($collection as $line) {
303
            $line = get_object_vars($line);
304
            if ($header === false) {
305
                fputcsv($fp, array_keys($line));
306
                $header = true;
307
            }
308
            fputcsv($fp, $line);
309
        }
310
        fclose($fp);
311
312
        return $file_path;
313
    }
314
315
    public function export()
316
    {
317
        $format = $this->router()->params('format');
318
        switch ($format) {
319
            case null:
320
                $filename = $this->model_type;
321
                $collection = $this->modelClassName()::listing();
322
                $file_path = $this->collection_to_csv($collection, $filename);
323
                $this->router()->sendFile($file_path);
324
                break;
325
326
            case 'xlsx':
327
                $report_controller = $this->get('HexMakina\koral\Controllers\ReportController');
328
                return $report_controller->collection($this->modelClassName());
329
        }
330
    }
331
332
    public function route_new(ModelInterface $model): string
333
    {
334
        return $this->router()->hyp(get_class($model)::model_type() . '_new');
335
    }
336
337
    public function route_list(ModelInterface $model): string
338
    {
339
        return $this->router()->hyp(get_class($model)::model_type());
340
    }
341
342
    public function route_model(ModelInterface $model): string
343
    {
344
        $route_params = [];
345
346
        $route_name = get_class($model)::model_type() . '_';
347
        if ($model->is_new()) {
348
            $route_name .= 'new';
349
        } else {
350
            $route_name .= 'default';
351
            $route_params = ['id' => $model->get_id()];
352
        }
353
        $res = $this->router()->hyp($route_name, $route_params);
354
        return $res;
355
    }
356
357
    public function route_factory($route = null, $route_params = []): string
358
    {
359
        if (is_null($route) && $this->router()->submits()) {
360
            $route = $this->formModel();
361
        }
362
363
        if (!is_null($route) && is_subclass_of($route, '\HexMakina\TightORM\Interfaces\ModelInterface')) {
364
            $route = $this->route_model($route);
365
        }
366
367
        return parent::route_factory($route, $route_params);
368
    }
369
370
    private function sanitize_post_data($post_data = [])
371
    {
372
        foreach ($this->modelClassName()::table()->columns() as $col) {
373
            if ($col->type()->isBoolean()) {
374
                $post_data[$col->name()] = !empty($post_data[$col->name()]);
375
            }
376
        }
377
378
        return $post_data;
379
    }
380
381
    // overriding displaycontroller
382
    protected function template_base()
383
    {
384
        return $this->modelClassName()::model_type();
385
    }
386
}
387