Issues (281)

Branch: master

src/Backend/Modules/FormBuilder/Engine/Model.php (1 issue)

1
<?php
2
3
namespace Backend\Modules\FormBuilder\Engine;
4
5
use Backend\Core\Language\Language as BL;
6
use Backend\Core\Engine\Model as BackendModel;
7
use Common\ModuleExtraType;
8
use Frontend\Core\Language\Language as FL;
9
use Symfony\Component\Finder\Finder;
10
11
/**
12
 * In this file we store all generic functions that we will be using in the form_builder module
13
 */
14
class Model
15
{
16
    const QUERY_BROWSE =
17
        'SELECT i.id, i.name, i.email, i.method,
18
         (SELECT COUNT(fd.form_id) FROM forms_data AS fd WHERE fd.form_id = i.id) AS sent_forms
19
         FROM forms AS i
20
         WHERE i.language = ?';
21
22
    /**
23
     * Calculate time ago.
24
     *
25
     * @param int $timestamp Unix timestamp from the past.
26
     *
27
     * @return string
28
     */
29
    public static function calculateTimeAgo(int $timestamp): string
30
    {
31
        $secondsBetween = time() - $timestamp;
32
33
        // calculate
34
        $hours = floor($secondsBetween / (60 * 60));
35
        $minutes = floor($secondsBetween / 60);
36
        $seconds = floor($secondsBetween);
37
38
        // today start
39
        $todayStart = (int) strtotime(date('d F Y'));
40
41
        // today
42
        if ($timestamp >= $todayStart) {
43
            // today
44
            if ($hours >= 1) {
45
                return BL::getLabel('Today') . ' ' . date('H:i', $timestamp);
46
            }
47
48
            if ($minutes > 1) {
49
                // more than one minute
50
                return sprintf(BL::getLabel('MinutesAgo'), $minutes);
51
            }
52
53
            if ($minutes == 1) {
54
                // one minute
55
                return BL::getLabel('OneMinuteAgo');
56
            }
57
58
            if ($seconds > 1) {
59
                // more than one second
60
                return sprintf(BL::getLabel('SecondsAgo'), $seconds);
61
            }
62
63
            return BL::getLabel('OneSecondAgo');
64
        }
65
66
        if ($timestamp < $todayStart && $timestamp >= ($todayStart - 86400)) {
67
            // yesterday
68
            return BL::getLabel('Yesterday') . ' ' . date('H:i', $timestamp);
69
        }
70
71
        // older
72
        return date('d/m/Y H:i', $timestamp);
73
    }
74
75
    /**
76
     * Create an unique identifier.
77
     *
78
     * @return string
79
     */
80
    public static function createIdentifier(): string
81
    {
82
        // get last id
83
        $id = (int) BackendModel::getContainer()->get('database')->getVar(
84
            'SELECT i.id FROM forms AS i ORDER BY i.id DESC LIMIT 1'
85
        );
86
87
        // create identifier
88
        do {
89
            ++$id;
90
            $identifier = 'form' . $id;
91
        } while (self::identifierExist($identifier));
92
93
        return $identifier;
94
    }
95
96
    /**
97
     * @param string $identifier
98
     *
99
     * @return bool
100
     */
101
    private static function identifierExist(string $identifier): bool
102
    {
103
        return (int) BackendModel::getContainer()->get('database')
104
                ->getVar(
105
                    'SELECT 1
106
                 FROM forms AS i
107
                 WHERE i.identifier = ?
108
                 LIMIT 1',
109
                    $identifier
110
                ) > 0;
111
    }
112
113
    /**
114
     * Delete an item.
115
     *
116
     * @param int $id The id of the record to delete.
117
     */
118
    public static function delete(int $id): void
119
    {
120
        $database = BackendModel::getContainer()->get('database');
121
122
        // get field ids
123
        $fieldIds = (array) $database->getColumn('SELECT i.id FROM forms_fields AS i WHERE i.form_id = ?', $id);
124
125
        // we have items to be deleted
126
        if (!empty($fieldIds)) {
127
            // delete all fields
128
            $database->delete('forms_fields', 'form_id = ?', $id);
129
            $database->delete('forms_fields_validation', 'field_id IN(' . implode(',', $fieldIds) . ')');
130
        }
131
132
        // get data ids
133
        $dataIds = (array) $database->getColumn('SELECT i.id FROM forms_data AS i WHERE i.form_id = ?', $id);
134
135
        // we have items to be deleted
136
        if (!empty($dataIds)) {
137
            self::deleteData($dataIds);
138
        }
139
140
        // delete extra
141
        BackendModel::deleteExtra('FormBuilder', 'widget', ['id' => $id]);
142
143
        // delete form
144
        $database->delete('forms', 'id = ?', $id);
145
    }
146
147
    /**
148
     * Deletes one or more data items.
149
     *
150
     * @param array $ids Ids of data items.
151
     */
152
    public static function deleteData(array $ids): void
153
    {
154
        $database = BackendModel::getContainer()->get('database');
155
        $ids = array_map('intval', $ids);
156
157
        $database->delete('forms_data', 'id IN(' . implode(',', $ids) . ')');
158
        $database->delete('forms_data_fields', 'data_id IN(' . implode(',', $ids) . ')');
159
    }
160
161
    /**
162
     * Delete a field.
163
     *
164
     * @param int $id Id of a field.
165
     */
166
    public static function deleteField(int $id): void
167
    {
168
        // delete linked validation
169
        self::deleteFieldValidation($id);
170
171
        // delete field
172
        BackendModel::getContainer()->get('database')->delete('forms_fields', 'id = ?', $id);
173
    }
174
175
    /**
176
     * Delete all validation of a field.
177
     *
178
     * @param int $id Id of a field.
179
     */
180
    public static function deleteFieldValidation(int $id): void
181
    {
182
        BackendModel::getContainer()->get('database')->delete('forms_fields_validation', 'field_id = ?', $id);
183
    }
184
185
    /**
186
     * Does the item exist.
187
     *
188
     * @param int $id Id of a form.
189
     *
190
     * @return bool
191
     */
192
    public static function exists(int $id): bool
193
    {
194
        return (bool) BackendModel::getContainer()->get('database')->getVar(
195
            'SELECT 1
196
             FROM forms AS f
197
             WHERE f.id = ?
198
             LIMIT 1',
199
            $id
200
        );
201
    }
202
203
    /**
204
     * Does the data item exist.
205
     *
206
     * @param int $id Id of the data item.
207
     *
208
     * @return bool
209
     */
210
    public static function existsData(int $id): bool
211
    {
212
        return (bool) BackendModel::getContainer()->get('database')->getVar(
213
            'SELECT 1
214
             FROM forms_data AS fd
215
             WHERE fd.id = ?
216
             LIMIT 1',
217
            $id
218
        );
219
    }
220
221
    /**
222
     * Does a field exist (within a form).
223
     *
224
     * @param int $id Id of a field.
225
     * @param int $formId Id of a form.
226
     *
227
     * @return bool
228
     */
229
    public static function existsField(int $id, int $formId = null): bool
230
    {
231
        // exists
232
        if ($formId === null) {
233
            return (bool) BackendModel::getContainer()->get('database')->getVar(
234
                'SELECT 1
235
                 FROM forms_fields AS ff
236
                 WHERE ff.id = ?
237
                 LIMIT 1',
238
                $id
239
            );
240
        }
241
242
        // exists and ignore an id
243
        return (bool) BackendModel::getContainer()->get('database')->getVar(
244
            'SELECT 1
245
             FROM forms_fields AS ff
246
             WHERE ff.id = ? AND ff.form_id = ?
247
             LIMIT 1',
248
            [$id, $formId]
249
        );
250
    }
251
252
    /**
253
     * Does an identifier exist.
254
     *
255
     * @param string $identifier Identifier.
256
     * @param int $ignoreId Field id to ignore.
257
     *
258
     * @return bool
259
     */
260
    public static function existsIdentifier(string $identifier, int $ignoreId = null): bool
261
    {
262
        // exists
263
        if ($ignoreId === null) {
264
            return (bool) BackendModel::getContainer()->get('database')->getVar(
265
                'SELECT 1
266
                 FROM forms AS f
267
                 WHERE f.identifier = ?
268
                 LIMIT 1',
269
                $identifier
270
            );
271
        }
272
273
        // exists and ignore an id
274
        return (bool) BackendModel::getContainer()->get('database')->getVar(
275
            'SELECT 1
276
             FROM forms AS f
277
             WHERE f.identifier = ? AND f.id != ?
278
             LIMIT 1',
279
            [$identifier, $ignoreId]
280
        );
281
    }
282
283
    /**
284
     * Formats the recipients based on the serialized string
285
     *
286
     * @param string $string The serialized string that should be formatted
287
     *
288
     * @return string
289
     */
290
    public static function formatRecipients(string $string): string
291
    {
292
        return implode(
293
            ', ',
294
            (array) array_map(
295
                'htmlspecialchars',
296
                @unserialize($string, ['allowed_classes' => false])
297
            )
298
        );
299
    }
300
301
    /**
302
     * Get all data for a given id.
303
     *
304
     * @param int $id The id for the record to get.
305
     *
306
     * @return array
307
     */
308
    public static function get(int $id): array
309
    {
310
        $return = (array) BackendModel::getContainer()->get('database')->getRecord(
311
            'SELECT f.* FROM forms AS f WHERE f.id = ?',
312
            $id
313
        );
314
315
        // unserialize the emailaddresses
316
        if (isset($return['email'])) {
317
            $return['email'] = (array) unserialize($return['email'], ['allowed_classes' => false]);
318
        }
319
320
        return $return;
321
    }
322
323
    /**
324
     * Get data for a given id.
325
     *
326
     * @param int $id The id for the record to get.
327
     *
328
     * @return array
329
     */
330
    public static function getData(int $id): array
331
    {
332
        // get data
333
        $data = (array) BackendModel::getContainer()->get('database')->getRecord(
334
            'SELECT fd.id, fd.form_id, UNIX_TIMESTAMP(fd.sent_on) AS sent_on
335
             FROM forms_data AS fd
336
             WHERE fd.id = ?',
337
            $id
338
        );
339
340
        // get fields
341
        $data['fields'] = (array) BackendModel::getContainer()->get('database')->getRecords(
342
            'SELECT fdf.label, fdf.value
343
             FROM forms_data_fields AS fdf
344
             WHERE fdf.data_id = ?
345
             ORDER BY fdf.id',
346
            (int) $data['id']
347
        );
348
349
        // unserialize values
350
        foreach ($data['fields'] as &$field) {
351
            if ($field['value'] !== null) {
352
                $field['value'] = unserialize($field['value'], ['allowed_classes' => false]);
353
            }
354
        }
355
356
        return $data;
357
    }
358
359
    /**
360
     * Get errors (optional by type).
361
     *
362
     * @param string $type Type of error.
363
     *
364
     * @return mixed
365
     */
366
    public static function getErrors(string $type = null)
367
    {
368
        $errors = [];
369
        $errors['required'] = FL::getError('FieldIsRequired');
370
        $errors['email'] = FL::getError('EmailIsInvalid');
371
        $errors['number'] = FL::getError('NumericCharactersOnly');
372
        $errors['time'] = FL::getError('TimeIsInvalid');
373
374
        // specific type
375
        if ($type !== null) {
376
            return $errors[$type];
377
        }
378
379
        // all errors
380
        $return = [];
381
382
        // loop errors
383
        foreach ($errors as $key => $error) {
384
            $return[] = ['type' => $key, 'message' => $error];
385
        }
386
387
        return $return;
388
    }
389
390
    /**
391
     * Get a field.
392
     *
393
     * @param int $id Id of a field.
394
     *
395
     * @return array
396
     */
397
    public static function getField(int $id): array
398
    {
399
        $field = (array) BackendModel::getContainer()->get('database')->getRecord(
400
            'SELECT ff.id, ff.form_id, ff.type, ff.settings
401
             FROM forms_fields AS ff
402
             WHERE ff.id = ?',
403
            $id
404
        );
405
406
        // unserialize settings
407
        if ($field['settings'] !== null) {
408
            $field['settings'] = unserialize($field['settings'], ['allowed_classes' => false]);
409
        }
410
411
        // get validation
412
        $field['validations'] = (array) BackendModel::getContainer()->get('database')->getRecords(
413
            'SELECT ffv.type, ffv.parameter, ffv.error_message
414
             FROM forms_fields_validation AS ffv
415
             WHERE ffv.field_id = ?',
416
            $field['id'],
417
            'type'
418
        );
419
420
        return $field;
421
    }
422
423
    /**
424
     * Get all fields of a form.
425
     *
426
     * @param int $id Id of a form.
427
     *
428
     * @return array
429
     */
430
    public static function getFields(int $id): array
431
    {
432
        $fields = (array) BackendModel::getContainer()->get('database')->getRecords(
433
            'SELECT ff.id, ff.type, ff.settings
434
             FROM forms_fields AS ff
435
             WHERE ff.form_id = ?
436
             ORDER BY ff.sequence ASC',
437
            $id
438
        );
439
440
        foreach ($fields as &$field) {
441
            // unserialize
442
            if ($field['settings'] !== null) {
443
                $field['settings'] = unserialize($field['settings'], ['allowed_classes' => false]);
444
            }
445
446
            // get validation
447
            $field['validations'] = (array) BackendModel::getContainer()->get('database')->getRecords(
448
                'SELECT ffv.type, ffv.parameter, ffv.error_message
449
                 FROM forms_fields_validation AS ffv
450
                 WHERE ffv.field_id = ?',
451
                $field['id'],
452
                'type'
453
            );
454
        }
455
456
        return $fields;
457
    }
458
459
    /**
460
     * Get a label/action/message from locale.
461
     * Used as datagridfunction.
462
     *
463
     * @param string $name Name of the locale item.
464
     * @param string $type Type of locale item.
465
     * @param string $application Name of the application.
466
     *
467
     * @return string
468
     */
469
    public static function getLocale(string $name, string $type = 'label', string $application = 'Backend'): string
470
    {
471
        $name = \SpoonFilter::toCamelCase($name);
472
        $class = \SpoonFilter::ucfirst($application) . '\Core\Language\Language';
473
        $function = 'get' . \SpoonFilter::ucfirst($type);
474
475
        // execute and return value
476
        return \SpoonFilter::ucfirst(call_user_func_array([$class, $function], [$name]));
477
    }
478
479
    /**
480
     * Get the maximum sequence for fields in a form.
481
     *
482
     * @param int $formId Id of the form.
483
     *
484
     * @return int
485
     */
486
    public static function getMaximumSequence(int $formId): int
487
    {
488
        return (int) BackendModel::getContainer()->get('database')->getVar(
489
            'SELECT MAX(ff.sequence)
490
             FROM forms_fields AS ff
491
             WHERE ff.form_id = ?',
492
            $formId
493
        );
494
    }
495
496
    /**
497
     * Add a new item.
498
     *
499
     * @param array $values The data to insert.
500
     *
501
     * @return int
502
     */
503
    public static function insert(array $values): int
504
    {
505
        // define form id
506
        $formId = BackendModel::getContainer()->get('database')->insert('forms', $values);
507
508
        // insert extra
509
        BackendModel::insertExtra(
510
            ModuleExtraType::widget(),
511
            'FormBuilder',
512
            'Form',
513
            'FormBuilder',
514
            [
515
                'id' => $formId,
516
                'extra_label' => $values['name'],
517
                'language' => $values['language'],
518
                'edit_url' => BackendModel::createUrlForAction('Edit') . '&id=' . $formId,
519
            ],
520
            false,
521
            '400' . $formId
0 ignored issues
show
'400' . $formId of type string is incompatible with the type integer|null expected by parameter $sequence of Backend\Core\Engine\Model::insertExtra(). ( Ignorable by Annotation )

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

521
            /** @scrutinizer ignore-type */ '400' . $formId
Loading history...
522
        );
523
524
        return $formId;
525
    }
526
527
    /**
528
     * Add a new field.
529
     *
530
     * @param array $values The data to insert.
531
     *
532
     * @return int
533
     */
534
    public static function insertField(array $values): int
535
    {
536
        return BackendModel::getContainer()->get('database')->insert('forms_fields', $values);
537
    }
538
539
    /**
540
     * Add validation for a field.
541
     *
542
     * @param array $values The data to insert.
543
     *
544
     * @return int
545
     */
546
    public static function insertFieldValidation(array $values): int
547
    {
548
        return BackendModel::getContainer()->get('database')->insert('forms_fields_validation', $values);
549
    }
550
551
    /**
552
     * Update an existing item.
553
     *
554
     * @param int $id The id for the item to update.
555
     * @param array $values The new data.
556
     *
557
     * @return int
558
     */
559
    public static function update(int $id, array $values): int
560
    {
561
        $database = BackendModel::getContainer()->get('database');
562
563
        // update item
564
        $database->update('forms', $values, 'id = ?', $id);
565
566
        // build array
567
        $extra = [
568
            'data' => serialize(
569
                [
570
                    'language' => BL::getWorkingLanguage(),
571
                    'extra_label' => $values['name'],
572
                    'id' => $id,
573
                    'edit_url' => BackendModel::createUrlForAction('Edit') . '&id=' . $id,
574
                ]
575
            ),
576
        ];
577
578
        // update extra
579
        $database->update(
580
            'modules_extras',
581
            $extra,
582
            'module = ? AND type = ? AND sequence = ?',
583
            ['FormBuilder', 'widget', '400' . $id]
584
        );
585
586
        return $id;
587
    }
588
589
    /**
590
     * Update a field.
591
     *
592
     * @param int $id The id for the item to update.
593
     * @param array $values The new data.
594
     *
595
     * @return int
596
     */
597
    public static function updateField(int $id, array $values): int
598
    {
599
        BackendModel::getContainer()->get('database')->update('forms_fields', $values, 'id = ?', $id);
600
601
        return $id;
602
    }
603
604
    /**
605
     * Get templates.
606
     *
607
     * @return array
608
     */
609
    public static function getTemplates(): array
610
    {
611
        $templates = [];
612
        $finder = new Finder();
613
        $finder->name('*.html.twig');
614
        $finder->in(FRONTEND_MODULES_PATH . '/FormBuilder/Layout/Templates/Mails');
615
616
        // if there is a custom theme we should include the templates there also
617
        $theme = BackendModel::get('fork.settings')->get('Core', 'theme', 'Fork');
618
        if ($theme !== 'core') {
619
            $path = FRONTEND_PATH . '/Themes/' . $theme . '/Modules/FormBuilder/Layout/Templates/Mails';
620
            if (is_dir($path)) {
621
                $finder->in($path);
622
            }
623
        }
624
625
        foreach ($finder->files() as $file) {
626
            $templates[] = $file->getBasename();
627
        }
628
629
        return array_unique($templates);
630
    }
631
}
632