Completed
Push — master ( 7b9104...2433c1 )
by Tim
02:44
created

CalMigrationUpdate::isCalWithSysCategories()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 15
rs 9.7666
c 0
b 0
f 0
cc 1
nc 1
nop 0
1
<?php
2
3
/**
4
 * CalMigrationUpdate.
5
 */
6
declare(strict_types=1);
7
8
namespace HDNET\Calendarize\Updates;
9
10
use HDNET\Autoloader\Annotation\SignalClass;
11
use HDNET\Autoloader\Annotation\SignalName;
12
use HDNET\Calendarize\Service\IndexerService;
13
use HDNET\Calendarize\Utility\HelperUtility;
14
use TYPO3\CMS\Core\Database\Connection;
15
use TYPO3\CMS\Core\Database\ConnectionPool;
16
use TYPO3\CMS\Core\Database\Query\Expression\CompositeExpression;
17
use TYPO3\CMS\Core\Database\Query\Expression\ExpressionBuilder;
18
use TYPO3\CMS\Core\Database\Query\QueryBuilder;
19
use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
20
use TYPO3\CMS\Core\Database\Query\Restriction\QueryRestrictionInterface;
21
use TYPO3\CMS\Core\Utility\GeneralUtility;
22
use TYPO3\CMS\Extbase\SignalSlot\Dispatcher;
23
use TYPO3\CMS\Install\Updates\DatabaseUpdatedPrerequisite;
24
25
/**
26
 * CalMigrationUpdate.
27
 *
28
 * If using the slots please use the m with func_get_args!
29
 * Example:
30
 * /**
31
 *  * @SignalClass("HDNET\Calendarize\Updates\CalMigrationUpdate")
32
 *  * @SignalName("getCalendarizeEventUid")
33
 *
34
 *  *@return array
35
 *  *
36
 * public function getCalendarizeEventUid()
37
 * {
38
 *    $args = func_get_args();
39
 *    list($table, $dbQueries, $call) = $args;
40
 *
41
 *    $variables = [
42
 *        'table'     => self::EVENT_TABLE,
43
 *        'dbQueries' => $dbQueries
44
 *    ];
45
 *
46
 *    return $variables;
47
 * }
48
 */
49
class CalMigrationUpdate extends AbstractUpdate
50
{
51
    /**
52
     * Import prefix.
53
     */
54
    const IMPORT_PREFIX = 'calMigration:';
55
56
    /**
57
     * Event table.
58
     */
59
    const EVENT_TABLE = 'tx_calendarize_domain_model_event';
60
61
    /**
62
     * Configuration table.
63
     */
64
    const CONFIGURATION_TABLE = 'tx_calendarize_domain_model_configuration';
65
66
    /**
67
     * ConfigurationGroup table.
68
     */
69
    const CONFIGURATION_GROUP_TABLE = 'tx_calendarize_domain_model_configurationgroup';
70
71
    /**
72
     * The human-readable title of the upgrade wizard.
73
     *
74
     * @var string
75
     */
76
    protected $title = 'Migrate cal event structures to the new calendarize event structures.
77
    Try to migrate all cal information and place the new calendarize event models in the same folder
78
    as the cal-records. Please note: the migration will be create calendarize default models.';
79
80
    public function getIdentifier(): string
81
    {
82
        return 'calendarize_calMigration';
83
    }
84
85
    /**
86
     * Checks whether updates are required.
87
     *
88
     * @param string &$description The description for the update
89
     *
90
     * @return bool Whether an update is required (TRUE) or not (FALSE)
91
     */
92
    public function checkForUpdate(&$description)
93
    {
94
        $nonMigratedCalIds = $this->getNonMigratedCalIds();
95
        $count = \count($nonMigratedCalIds);
96
        if (0 === $count) {
97
            return false;
98
        }
99
        $description = 'There ' . ($count > 1 ? 'are ' . $count : 'is ' . $count) . ' non migrated EXT:cal event
100
        ' . ($count > 1 ? 's' : '') . '. Run the update process to migrate the events to EXT:calendarize events.';
101
102
        return true;
103
    }
104
105
    /**
106
     * Performs the accordant updates.
107
     *
108
     * @param array &$dbQueries      Queries done in this update
109
     * @param mixed &$customMessages Custom messages
110
     *
111
     * @return bool Whether everything went smoothly or not
112
     */
113
    public function executeUpdate(): bool
114
    {
115
        $calIds = $this->getNonMigratedCalIds();
116
        if (empty($calIds)) {
117
            return true;
118
        }
119
        $dbQueries = [];
120
121
        /**
122
         * @var bool
123
         */
124
        $calUsesSysCategories = $this->isCalWithSysCategories();
125
126
        if (!$calUsesSysCategories) {
127
            $this->performSysCategoryUpdate($calIds, $dbQueries, $customMessages);
128
        }
129
        $this->performSysFileReferenceUpdate($calIds, $dbQueries, $customMessages);
130
        $this->performExceptionEventUpdate($calIds, $dbQueries, $customMessages);
131
        $this->performCalEventUpdate($calIds, $dbQueries, $customMessages);
132
        if ($calUsesSysCategories) {
133
            $this->performLinkEventToSysCategory($calIds, $dbQueries, $customMessages);
134
        } else {
135
            $this->performLinkEventToCategory($calIds, $dbQueries, $customMessages);
136
        }
137
        $this->performLinkEventToConfigurationGroup($calIds, $dbQueries, $customMessages);
138
139
        return true;
140
    }
141
142
    /**
143
     * Check if cal is already using sys_category instead
144
     * of tx_cal_category. This affects the conversion of
145
     * categories and category / event relations.
146
     *
147
     * @return bool
148
     */
149
    protected function isCalWithSysCategories(): bool
150
    {
151
        $table = 'sys_category_record_mm';
152
153
        $q = $this->getQueryBuilder($table);
154
        $count = (int)$q->count('*')
155
            ->from($table)
156
            ->where(
157
                $q->expr()->eq('tablenames', $q->createNamedParameter('tx_cal_event')),
158
                $q->expr()->eq('fieldname', $q->createNamedParameter('category_id'))
159
            )
160
            ->execute();
161
162
        return $count > 0;
163
    }
164
165
    /**
166
     * Perform CAL event update.
167
     *
168
     * @param       $calIds
169
     * @param array $dbQueries
170
     * @param       $customMessages
171
     *
172
     * @return bool
173
     */
174
    public function performCalEventUpdate($calIds, array &$dbQueries, &$customMessages)
175
    {
176
        $table = 'tx_cal_event';
177
        $q = $this->getQueryBuilder($table);
178
179
        $locationTable = 'tx_cal_location';
180
        $locations = $this->getQueryBuilder($locationTable)
181
            ->select('*')
182
            ->from($locationTable)
183
            ->execute()
184
            ->fetchAll();
185
        $locationByUid = [];
186
        foreach ($locations as $location) {
187
            $locationByUid[$location['uid']] = $location['name'];
188
        }
189
190
        $events = $q->select('*')
191
            ->from($table)
192
            ->where(
193
                $q->expr()->in('uid', $q->createNamedParameter($calIds, Connection::PARAM_INT_ARRAY))
194
            )
195
            ->orderBy('l18n_parent')
196
            ->execute()->fetchAll();
197
198
        foreach ($events as $event) {
199
            // Get the parent id of the event record
200
            // Note: The parent record should exist at this time, since we orderBy('l18n_parent')
201
            $parentEventUid = 0;
202
            if ($event['l18n_parent']) {
203
                $parentEventUid = (int)$this->getCalendarizeEventUid(
204
                    self::IMPORT_PREFIX . $event['l18n_parent'],
205
                    $dbQueries,
206
                    $customMessages
207
                );
208
            }
209
210
            $calendarizeEventRecord = [
211
                'pid' => $event['pid'],
212
                'import_id' => self::IMPORT_PREFIX . (int)$event['uid'],
213
                'sys_language_uid' => $event['sys_language_uid'] ?? 0,
214
                'l10n_parent' => $parentEventUid,
215
                'tstamp' => $event['tstamp'],
216
                'crdate' => $event['crdate'],
217
                'hidden' => $event['hidden'],
218
                'starttime' => $event['starttime'],
219
                'endtime' => $event['endtime'],
220
                'title' => $event['title'],
221
                'organizer' => $event['organizer'],
222
                'location' => $event['location_id'] > 0 ? $locationByUid[$event['location_id']] : $event['location'],
223
                'abstract' => $event['teaser'],
224
                'description' => $event['description'],
225
                'images' => (int)$event['image'],
226
                'downloads' => (int)$event['attachment'],
227
                'calendarize' => $this->buildConfigurations($event, $dbQueries),
228
            ];
229
230
            $variables = [
231
                'calendarizeEventRecord' => $calendarizeEventRecord,
232
                'event' => $event,
233
                'table' => self::EVENT_TABLE,
234
                'dbQueries' => $dbQueries,
235
            ];
236
237
            $dispatcher = self::getSignalSlotDispatcher();
238
            $variables = $dispatcher->dispatch(__CLASS__, __FUNCTION__ . 'PreInsert', $variables);
239
240
            $db = HelperUtility::getDatabaseConnection($variables['table']);
241
            $q = $db->createQueryBuilder();
242
            $q->insert($variables['table'])->values($variables['calendarizeEventRecord']);
243
            $dbQueries[] = $q->getSQL();
244
245
            $q->execute();
246
247
            $variablesPostInsert = [
248
                'calendarizeEventRecord' => $calendarizeEventRecord,
249
                'event' => $event,
250
                'table' => $variables['table'],
251
                'recordId' => $db->lastInsertId($variables['table']),
252
                'dbQueries' => $dbQueries,
253
            ];
254
255
            $dispatcher = self::getSignalSlotDispatcher();
256
            $dispatcher->dispatch(__CLASS__, __FUNCTION__ . 'PostInsert', $variablesPostInsert);
257
        }
258
259
        $indexer = GeneralUtility::makeInstance(IndexerService::class);
260
        $indexer->reindexAll();
261
262
        return true;
263
    }
264
265
    /**
266
     * Perform exception event update.
267
     *
268
     * @param       $calIds
269
     * @param array $dbQueries
270
     * @param array $customMessages
271
     *
272
     * @return bool
273
     */
274
    public function performExceptionEventUpdate($calIds, &$dbQueries, &$customMessages)
275
    {
276
        $table = 'tx_cal_exception_event_group';
277
        // ConfigurationGroup für jede ExceptionGroup
278
279
        $variables = [
280
            'table' => $table,
281
            'dbQueries' => $dbQueries,
282
            'calIds' => $calIds,
283
        ];
284
285
        $q = $this->getQueryBuilder($table);
286
287
        $q->select('*')->from($variables['table']);
288
289
        $selectResults = $q->execute()->fetchAll();
290
        $dbQueries[] = $q->getSQL();
291
292
        foreach ($selectResults as $selectResult) {
293
            $group = [
294
                'pid' => $selectResult['pid'],
295
                'tstamp' => $selectResult['tstamp'],
296
                'crdate' => $selectResult['crdate'],
297
                'cruser_id' => $selectResult['cruser_id'],
298
                'title' => $selectResult['title'],
299
                'configurations' => $this->getExceptionConfigurationForExceptionGroup($selectResult['uid'], $dbQueries),
300
                'hidden' => $selectResult['hidden'],
301
                'import_id' => self::IMPORT_PREFIX . $selectResult['uid'],
302
            ];
303
304
            $q = $this->getQueryBuilder($table);
305
            $q->insert(self::CONFIGURATION_GROUP_TABLE)->values($group);
306
            $dbQueries[] = $q->getSQL();
307
308
            $q->execute();
309
        }
310
311
        return true;
312
    }
313
314
    /**
315
     * Perform link event to configuration group.
316
     *
317
     * @param $calIds
318
     * @param $dbQueries
319
     * @param $customMessages
320
     *
321
     * @return bool
322
     */
323
    public function performLinkEventToConfigurationGroup($calIds, &$dbQueries, &$customMessages)
324
    {
325
        $q = $this->getQueryBuilder(self::CONFIGURATION_GROUP_TABLE);
326
        $now = new \DateTime();
327
328
        $variables = [
329
            'table' => self::CONFIGURATION_GROUP_TABLE,
330
            'dbQueries' => $dbQueries,
331
            'calIds' => $calIds,
332
        ];
333
334
        $selectResults = $q->select('*')->from($variables['table'])->execute()->fetchAll();
335
        $dbQueries[] = $q->getSQL();
336
337
        foreach ($selectResults as $group) {
338
            $importId = explode(':', $group['import_id']);
339
            $groupId = (int)$importId[1];
340
341
            $variables = [
342
                'table' => 'tx_cal_exception_event_mm',
343
                'dbQueries' => $dbQueries,
344
                'calIds' => $calIds,
345
            ];
346
347
            $q = $this->getQueryBuilder($variables['table']);
348
349
            $q->select('uid_local')
350
                ->from($variables['table'])
351
                ->where(
352
                    $q->expr()->andX(
353
                        $q->expr()->eq('tablenames', $q->createNamedParameter('tx_cal_exception_event_group')),
354
                        $q->expr()->eq('uid_foreign', $q->createNamedParameter((int)$groupId, \PDO::PARAM_INT))
355
                    )
356
                );
357
358
            $dbQueries[] = $q->getSQL();
359
            $selectResults = $q->execute()->fetchAll();
360
361
            foreach ($selectResults as $eventUid) {
362
                $eventImportId = self::IMPORT_PREFIX . (int)$eventUid['uid_local'];
363
                $configurationRow = [
364
                    'pid' => (int)$group['pid'],
365
                    'tstamp' => $now->getTimestamp(),
366
                    'crdate' => $now->getTimestamp(),
367
                    'type' => 'group',
368
                    'handling' => 'exclude',
369
                    'groups' => $group['uid'],
370
                ];
371
372
                $this->updateEventWithConfiguration($eventImportId, $configurationRow, $dbQueries, $customMessages);
373
            }
374
        }
375
376
        return true;
377
    }
378
379
    /**
380
     * Migrate the 'sys_file_reference' entries from 'tx_cal_event' to 'tx_calendarize_domain_model_event'.
381
     * Mark the imported entries with the import-id.
382
     *
383
     * @param       $calIds
384
     * @param array $dbQueries
385
     * @param       $customMessages
386
     */
387
    public function performSysFileReferenceUpdate($calIds, array &$dbQueries, &$customMessages)
388
    {
389
        $q = $this->getQueryBuilder('tx_cal_event');
390
391
        $variables = [
392
            'table' => 'tx_cal_event',
393
            'fieldnames' => ['image', 'attachment'],
394
            'dbQueries' => $dbQueries,
395
            'calIds' => $calIds,
396
        ];
397
398
        // select all not migrated entries
399
        $fieldnames = 'fieldname = \'' . implode('\' OR fieldname = \'', $variables['fieldnames']) . '\'';
400
        $selectWhere = 'tablenames = \'' . $variables['table'] . '\' AND (' . $fieldnames . ')';
401
        $selectWhere .= ' AND NOT EXISTS (SELECT NULL FROM sys_file_reference sfr2 WHERE sfr2.import_id = CONCAT(\'' . self::IMPORT_PREFIX . '\', sfr1.uid))';
402
403
        $q->select('*')
404
            ->from('sys_file_reference', 'sfr1')
405
            ->where($selectWhere);
406
407
        $dbQueries[] = $q->getSQL();
408
        $selectResults = $q->execute()->fetchAll();
409
410
        $variables = [
411
            'table' => self::EVENT_TABLE,
412
            'fieldnames' => $variables['fieldnames'],
413
            'dbQueries' => $dbQueries,
414
            'calIds' => $calIds,
415
            'selectResults' => $selectResults,
416
        ];
417
418
        // create new entry with import_id
419
        foreach ($variables['selectResults'] as $selectResult) {
420
            $selectResult['tablenames'] = $variables['table'];
421
            $selectResult['import_id'] = self::IMPORT_PREFIX . $selectResult['uid'];
422
            $selectResult['fieldname'] = ('image' === $selectResult['fieldname']) ? 'images' : 'downloads';
423
            unset($selectResult['uid_foreign'], $selectResult['uid']);
424
425
            $q = $this->getQueryBuilder('sys_file_reference');
426
            $q->insert('sys_file_reference')->values($selectResult);
427
428
            $dbQueries[] = $q->getSQL();
429
430
            $q->execute();
431
        }
432
    }
433
434
    /**
435
     * Link the Events to the migrated Categories.
436
     * This build up the 'sys_category_record_mm' table on base of the 'tx_cal_event_category_mm' table.
437
     *
438
     * @param       $calIds
439
     * @param array $dbQueries
440
     * @param array $customMessages
441
     */
442
    public function performLinkEventToCategory($calIds, &$dbQueries, &$customMessages)
443
    {
444
        $table = 'tx_cal_event_category_mm';
445
446
        $q = $this->getQueryBuilder($table);
447
448
        $q->select('*')->from($table);
449
        $dbQueries[] = $q->getSQL();
450
451
        $selectResults = $q->execute()->fetchAll();
452
453
        $variables = [
454
            'tablenames' => self::EVENT_TABLE,
455
            'fieldname' => 'categories',
456
            'dbQueries' => $dbQueries,
457
        ];
458
459
        $dispatcher = self::getSignalSlotDispatcher();
460
        $variables = $dispatcher->dispatch(__CLASS__, __FUNCTION__, $variables);
461
462
        foreach ($selectResults as $mm) {
463
            $eventUid = (int)$this->getCalendarizeEventUid(self::IMPORT_PREFIX . $mm['uid_local'], $dbQueries, $customMessages);
464
            $categoryUid = (int)$this->getCalendarizeCategoryUid(
465
                self::IMPORT_PREFIX . $mm['uid_foreign'],
466
                $dbQueries,
467
                $customMessages
468
            );
469
470
            if (0 !== $eventUid && 0 !== $categoryUid) {
471
                $insertValues = [
472
                    'uid_local' => $categoryUid,
473
                    'uid_foreign' => $eventUid,
474
                    'tablenames' => $variables['tablenames'],
475
                    'fieldname' => $variables['fieldname'],
476
                ];
477
478
                $q = $this->getQueryBuilder($table);
479
                $q->insert($table)->values($insertValues);
480
                $dbQueries[] = $q->getSQL();
481
482
                $q->execute();
483
            }
484
        }
485
    }
486
487
    /**
488
     * Link the Events to the migrated categories.
489
     *
490
     * This uses the existing 'sys_category_record_mm' table which links tx_cal_event to sys_category.
491
     * The fields must be updated to use tx_calendarize_domain_model_event instead.
492
     * Additionally, the uid_foreign must be updated to point to the new event uid.
493
     *
494
     * Before: tablenames='tx_cal_event', fieldname='category_id'
495
     * After: tablenames='tx_calendarize_domain_model_event', fieldname='categories'
496
     *
497
     * @param       $calIds
498
     * @param array $dbQueries
499
     * @param array $customMessages
500
     */
501
    public function performLinkEventToSysCategory($calIds, &$dbQueries, &$customMessages)
502
    {
503
        $table = 'sys_category_record_mm';
504
505
        $q = $this->getQueryBuilder($table);
506
507
        $q->select('uid_foreign')->from($table)
508
            ->where(
509
                $q->expr()->eq('tablenames', $q->createNamedParameter('tx_cal_event')),
510
                $q->expr()->eq('fieldname', $q->createNamedParameter('category_id')),
511
                $q->expr()->neq('uid_local', $q->createNamedParameter(0, \PDO::PARAM_INT)),
512
                $q->expr()->neq('uid_foreign', $q->createNamedParameter(0, \PDO::PARAM_INT))
513
            )->groupBy('uid_foreign');
514
515
        $selectResults = $q->execute()->fetchAll();
516
517
        $variables = [
518
            'tablenames' => self::EVENT_TABLE,
519
            'fieldname' => 'categories',
520
        ];
521
522
        $dispatcher = self::getSignalSlotDispatcher();
523
        $variables = $dispatcher->dispatch(__CLASS__, __FUNCTION__, $variables);
524
525
        foreach ($selectResults as $mm) {
526
            $eventUidOld = (int)$mm['uid_foreign'];
527
            // event id is in uid_foreign
528
            $eventUid = (int)$this->getCalendarizeEventUid(self::IMPORT_PREFIX . $eventUidOld, $dbQueries, $customMessages);
529
530
            $q = $this->getQueryBuilder($table);
531
            if (0 !== $eventUid) {
532
                $q->update($table)
533
                    ->set('tablenames', $variables['tablenames'])
534
                    ->set('fieldname', $variables['fieldname'])
535
                    ->set('uid_foreign', $eventUid)
536
                    ->where(
537
                        $q->expr()->eq('uid_foreign', $q->createNamedParameter($eventUidOld, \PDO::PARAM_INT)),
538
                        $q->expr()->eq('tablenames', $q->createNamedParameter('tx_cal_event')),
539
                        $q->expr()->eq('fieldname', $q->createNamedParameter('category_id'))
540
                    )->execute();
541
            } else {
542
                $q->delete($table)
543
                    ->where(
544
                        $q->expr()->eq('uid_foreign', $q->createNamedParameter($eventUid, \PDO::PARAM_INT)),
545
                        $q->expr()->eq('tablenames', $q->createNamedParameter('tx_cal_event')),
546
                        $q->expr()->eq('fieldname', $q->createNamedParameter('category_id'))
547
                    )
548
                    ->execute();
549
            }
550
        }
551
552
        // delete remaining entries with insufficient values (e.g. uid_foreign=0)
553
        $q = $this->getQueryBuilder($table);
554
        $q->delete($table)
555
            ->where(
556
                $q->expr()->eq('tablenames', $q->createNamedParameter('tx_cal_event')),
557
                $q->expr()->orX(
558
                    $q->expr()->eq('fieldname', $q->createNamedParameter('')),
559
                    $q->expr()->eq('uid_local', $q->createNamedParameter(0, \PDO::PARAM_INT)),
560
                    $q->expr()->eq('uid_foreign', $q->createNamedParameter(0, \PDO::PARAM_INT))
561
                )
562
            )->execute();
563
    }
564
565
    /**
566
     * Update event with configuration.
567
     *
568
     * @param $eventImportId
569
     * @param $configuration
570
     * @param $dbQueries
571
     * @param $customMessages
572
     *
573
     * @return array
574
     */
575
    protected function updateEventWithConfiguration($eventImportId, $configuration, &$dbQueries, &$customMessages)
576
    {
577
        $configurationRow = $this->findEventExcludeConfiguration($eventImportId, $dbQueries, $customMessages);
578
        if ($configurationRow) {
579
            $configurationRow['groups'] = $this->addValueToCsv($configurationRow['groups'], $configuration['groups']);
580
581
            unset($configurationRow['uid']);
582
583
            $q = $this->getQueryBuilder(self::CONFIGURATION_GROUP_TABLE);
584
            $q->update(self::CONFIGURATION_GROUP_TABLE)
585
                ->where('uid', $q->createNamedParameter((int)$configuration['uid'], \PDO::PARAM_INT));
586
            foreach ($configurationRow as $key => $value) {
0 ignored issues
show
Bug introduced by
The expression $configurationRow of type array|boolean is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
587
                $q->set($key, $value);
588
            }
589
590
            $dbQueries[] = $q->getSQL();
591
            $results = $q->execute();
592
        } else {
593
            $db = HelperUtility::getDatabaseConnection(self::CONFIGURATION_TABLE);
594
            $q = $db->createQueryBuilder();
595
            $q->insert(self::CONFIGURATION_TABLE)->values($configuration);
596
            $dbQueries[] = $q->getSQL();
597
598
            $configurationId = $db->lastInsertId(self::CONFIGURATION_TABLE);
599
600
            $results = $this->addConfigurationIdToEvent($eventImportId, $configurationId, $dbQueries, $customMessages);
601
        }
602
603
        return $results;
604
    }
605
606
    /**
607
     * Add Value to CSV.
608
     *
609
     * @param string $csv
610
     * @param string $value
611
     *
612
     * @return string
613
     */
614
    protected function addValueToCsv($csv, $value)
615
    {
616
        $csvArray = GeneralUtility::trimExplode(',', $csv);
617
618
        // check for doubles
619
        $values = array_flip($csvArray);
620
        if (isset($values[$value])) {
621
            return $csv;
622
        }
623
        $csvArray[] = $value;
624
        $csv = implode(',', $csvArray);
625
626
        return $csv;
627
    }
628
629
    /**
630
     * Add configuration ID to event.
631
     *
632
     * @param string $eventImportId
633
     * @param int    $configurationId
634
     * @param array  $dbQueries
635
     * @param array  $customMessages
636
     *
637
     * @return array|bool
638
     */
639
    protected function addConfigurationIdToEvent($eventImportId, $configurationId, &$dbQueries, &$customMessages)
640
    {
641
        $event = $this->findEventByImportId($eventImportId, $dbQueries, $customMessages);
642
        if (!$event) {
643
            return false;
644
        }
645
646
        $event['calendarize'] = $this->addValueToCsv($event['calendarize'], $configurationId);
647
648
        return $this->updateEvent($event['uid'], $event, $dbQueries, $customMessages);
649
    }
650
651
    /**
652
     * Update event.
653
     *
654
     * @param int   $eventId
655
     * @param array $values
656
     * @param array $dbQueries
657
     * @param array $customMessages
658
     *
659
     * @return array
660
     */
661
    protected function updateEvent($eventId, $values, &$dbQueries, &$customMessages)
662
    {
663
        $q = $this->getQueryBuilder(self::EVENT_TABLE);
664
665
        $variables = [
666
            'table' => self::EVENT_TABLE,
667
            'eventId' => (int)$eventId,
668
            'values' => $values,
669
            'dbQueries' => $dbQueries,
670
        ];
671
672
        $dispatcher = self::getSignalSlotDispatcher();
673
        $variables = $dispatcher->dispatch(__CLASS__, __FUNCTION__, $variables);
674
675
        $q->update($variables['table'])
676
            ->where(
677
                $q->expr()->eq('uid', $q->createNamedParameter((int)$eventId, \PDO::PARAM_INT))
678
            );
679
        foreach ($variables['values'] as $key => $value) {
680
            $q->set($key, $value);
681
        }
682
683
        unset($values['uid']);
684
685
        $dbQueries[] = $q->getSQL();
686
687
        return $q->execute()->fetchAll();
688
    }
689
690
    /**
691
     * Find event by import ID.
692
     *
693
     * @param $eventImportId
694
     * @param $dbQueries
695
     * @param $customMessages
696
     *
697
     * @return array|bool
698
     */
699
    protected function findEventByImportId($eventImportId, &$dbQueries, &$customMessages)
700
    {
701
        $q = $this->getQueryBuilder(self::EVENT_TABLE);
702
703
        $variables = [
704
            'table' => self::EVENT_TABLE,
705
            'dbQueries' => $dbQueries,
706
            'eventImportId' => $eventImportId,
707
        ];
708
709
        $dispatcher = self::getSignalSlotDispatcher();
710
        $variables = $dispatcher->dispatch(__CLASS__, __FUNCTION__, $variables);
711
712
        $q->select('*')->from($variables['table'])
713
            ->where(
714
                $q->expr()->eq('import_id', $q->createNamedParameter($eventImportId))
715
            );
716
717
        $dbQueries[] = $q->getSQL();
718
719
        return $q->execute()->fetchAll();
720
    }
721
722
    /**
723
     * Find event exclude configuration.
724
     *
725
     * @param string $eventImportId
726
     * @param array  $dbQueries
727
     * @param array  $customMessages
728
     *
729
     * @return array|bool
730
     */
731
    protected function findEventExcludeConfiguration($eventImportId, &$dbQueries, &$customMessages)
732
    {
733
        $event = $this->findEventByImportId($eventImportId, $dbQueries, $customMessages);
734
735
        if (!$event) {
736
            return false;
737
        }
738
739
        $variables = [
740
            'table' => self::CONFIGURATION_TABLE,
741
            'dbQueries' => $dbQueries,
742
            'event' => $event,
743
        ];
744
745
        $q = $this->getQueryBuilder($variables['table']);
746
747
        $q->select('*')
748
            ->from($variables['table'])
749
            ->where(
750
                $q->expr()->andX(
751
                    $q->expr()->eq('type', 'group'),
752
                    $q->expr()->eq('handling', 'exclude'),
753
                    $q->expr()->in('uid', $variables['event']['calendarize'])
754
                )
755
            );
756
757
        $dbQueries[] = $q->getSQL();
758
759
        return $q->execute()->fetchAll();
760
    }
761
762
    /**
763
     * Get exception configuration for exception group.
764
     *
765
     * @param       $groupId
766
     * @param array $dbQueries
767
     *
768
     * @return string
769
     */
770
    protected function getExceptionConfigurationForExceptionGroup($groupId, &$dbQueries)
771
    {
772
        $recordIds = [];
773
        $variables = [
774
            'table' => 'tx_cal_exception_event_group_mm',
775
            'dbQueries' => $dbQueries,
776
        ];
777
778
        $q = $this->getQueryBuilder($variables['table']);
779
780
        $q->select('*')
781
            ->from($variables['table'])
782
            ->where('uid_local', $q->createNamedParameter((int)$groupId, \PDO::PARAM_INT));
783
784
        $dbQueries[] = $q->getSQL();
785
786
        $mmResults = $q->execute()->fetchAll();
787
        foreach ($mmResults as $mmResult) {
788
            $variables = [
789
                'table' => 'tx_cal_exception_event',
790
                'dbQueries' => $dbQueries,
791
            ];
792
793
            $q = $this->getQueryBuilder($variables['table']);
794
            $q->select('*')
795
                ->from($variables['table'])
796
                ->where(
797
                    $q->expr()->eq('uid', $q->createNamedParameter((int)$mmResult['uid_foreign'], \PDO::PARAM_INT))
798
                );
799
800
            $dbQueries[] = $q->getSQL();
801
802
            $selectResults = $q->execute()->fetchAll();
803
804
            foreach ($selectResults as $selectResult) {
805
                $configurationRow = [
806
                    'pid' => $selectResult['pid'],
807
                    'tstamp' => $selectResult['tstamp'],
808
                    'crdate' => $selectResult['crdate'],
809
                    'type' => 'time',
810
                    'handling' => 'include',
811
                    'start_date' => (string)$selectResult['start_date'] ?: null,
812
                    'end_date' => (string)$selectResult['end_date'] ?: null,
813
                    'start_time' => (int)$selectResult['start_time'],
814
                    'end_time' => (int)$selectResult['end_time'],
815
                    'all_day' => (null === $selectResult['start_time'] && null === $selectResult['end_time']) ? 1 : 0,
816
                    'frequency' => $this->mapFrequency($selectResult['freq']),
817
                    'till_date' => (string)$selectResult['until'] ?: null,
818
                    'counter_amount' => (int)$selectResult['cnt'],
819
                    'counter_interval' => (int)$selectResult['interval'],
820
                    'import_id' => self::IMPORT_PREFIX . $selectResult['uid'],
821
                ];
822
823
                $variables = [
824
                    'table' => self::CONFIGURATION_TABLE,
825
                    'configurationRow' => $configurationRow,
826
                ];
827
828
                $dispatcher = self::getSignalSlotDispatcher();
829
                $variables = $dispatcher->dispatch(__CLASS__, __FUNCTION__ . 'PreInsert', $variables);
830
831
                $db = HelperUtility::getDatabaseConnection($variables['table']);
832
                $q = $db->createQueryBuilder();
833
                $q->insert($variables['table'])->values($variables['configurationRow']);
834
835
                $dbQueries[] = $q->getSQL();
836
837
                $q->execute();
838
839
                $recordIds[] = $db->lastInsertId($variables['table']);
840
            }
841
        }
842
843
        return implode(',', $recordIds);
844
    }
845
846
    /**
847
     * Map frequency.
848
     *
849
     * @param string $calFrequency
850
     *
851
     * @return string
852
     */
853
    protected function mapFrequency($calFrequency)
854
    {
855
        $freq = [
856
            'none' => null,
857
            'day' => 'daily',
858
            'week' => 'weekly',
859
            'month' => 'monthly',
860
            'year' => 'yearly',
861
        ];
862
863
        if (!isset($freq[$calFrequency])) {
864
            return '';
865
        }
866
867
        return $freq[$calFrequency];
868
    }
869
870
    /**
871
     * Migrate the 'tx_cal_category' table to the 'sys_category' table.
872
     *
873
     * @param       $calIds
874
     * @param array $dbQueries
875
     * @param       $customMessages
876
     */
877
    protected function performSysCategoryUpdate($calIds, array &$dbQueries, &$customMessages)
878
    {
879
        // first migrate from tx_cal_category to sys_category
880
        $variables = [
881
            'table' => 'tx_cal_category',
882
            'dbQueries' => $dbQueries,
883
            'calIds' => $calIds,
884
        ];
885
886
        $q = $this->getQueryBuilder($variables['table']);
887
        $q->getRestrictions()
888
            ->removeAll()
889
            ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
890
891
        $q->select('*')
892
            ->from($variables['table']);
893
894
        $dbQueries[] = $q->getSQL();
895
896
        $selectResults = $q->execute()->fetchAll();
897
898
        foreach ($selectResults as $category) {
899
            $sysCategoryRecord = [
900
                'pid' => $category['pid'],
901
                'tstamp' => $category['tstamp'],
902
                'crdate' => $category['crdate'],
903
                'cruser_id' => $category['cruser_id'],
904
                'deleted' => $category['deleted'],
905
                'hidden' => $category['hidden'],
906
                'starttime' => $category['starttime'],
907
                'endtime' => $category['endtime'],
908
                'sys_language_uid' => $category['sys_language_uid'],
909
                'l10n_parent' => $category['l18n_parent'],
910
                'title' => $category['title'],
911
                'parent' => (int)$category['parent_category'],
912
                'import_id' => self::IMPORT_PREFIX . (int)$category['uid'],
913
                'sorting' => $category['sorting'],
914
            ];
915
916
            $q = $this->getQueryBuilder('sys_category');
917
            $q->insert('sys_category')->values($sysCategoryRecord);
918
            $dbQueries[] = $q->getSQL();
919
920
            $q->execute();
921
        }
922
923
        // second rewrite the tree
924
        $variables = [
925
            'table' => 'sys_category',
926
            'dbQueries' => $dbQueries,
927
            'calIds' => $calIds,
928
        ];
929
930
        $q = $this->getQueryBuilder($variables['table']);
931
932
        $q->select('*')
933
            ->from($variables['table'])
934
            ->where(
935
                $q->expr()->neq('import_id', $q->createNamedParameter(''))
936
            );
937
938
        $dbQueries[] = $q->getSQL();
939
        $selectResults = $q->execute()->fetchAll();
940
941
        foreach ($selectResults as $sysCategory) {
942
            // update parent, because there are just the old uids
943
            $q = $this->getQueryBuilder('sys_category');
944
            $q->update('sys_category')
945
                ->where(
946
                    $q->expr()->eq('uid', $q->createNamedParameter((int)$sysCategory['uid'], \PDO::PARAM_INT))
947
                )->set(
948
                    'parent',
949
                    $this->getSysCategoryParentUid(self::IMPORT_PREFIX . (int)$sysCategory['parent'])
950
                );
951
952
            $dbQueries[] = $q->getSQL();
953
954
            $q->execute();
955
        }
956
    }
957
958
    /**
959
     * Return the parentUid for the 'sys_category' entry on base of the import_id.
960
     *
961
     * @param string $importId
962
     *
963
     * @return int
964
     */
965
    protected function getSysCategoryParentUid($importId)
966
    {
967
        $table = 'sys_category';
968
        $q = $this->getQueryBuilder($table);
969
970
        $q->select('uid')
971
            ->from($table)
972
            ->where(
973
                $q->expr()->eq('import_id', $q->createNamedParameter($importId))
974
            );
975
976
        $dbQueries[] = $q->getSQL();
0 ignored issues
show
Coding Style Comprehensibility introduced by
$dbQueries was never initialized. Although not strictly required by PHP, it is generally a good practice to add $dbQueries = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
977
978
        $result = $q->execute()->fetchAll();
979
980
        return (int)$result[0]['uid'];
981
    }
982
983
    /**
984
     * Get the event uid on base of the given import_id.
985
     * The import_id is the original tx_cal_event id prefixed with the IMPORT_PREFIX.
986
     *
987
     * @param string $importId
988
     * @param array  $dbQueries
989
     * @param array  $customMessages
990
     *
991
     * @return int
992
     */
993
    protected function getCalendarizeEventUid($importId, &$dbQueries, &$customMessages)
994
    {
995
        $variables = [
996
            'table' => self::EVENT_TABLE,
997
            'dbQueries' => $dbQueries,
998
        ];
999
1000
        $dispatcher = self::getSignalSlotDispatcher();
1001
        $variables = $dispatcher->dispatch(__CLASS__, __FUNCTION__, $variables);
1002
1003
        $q = $this->getQueryBuilder($variables['table']);
1004
1005
        // also get restricted (e.g. hidden) records, otherwise retrieving event uid for
1006
        // restricted records will always return 0
1007
        $q->getRestrictions()
1008
            ->removeAll()
1009
            ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
1010
        $q->select('uid')
1011
            ->from($variables['table'])
1012
            ->where(
1013
                $q->expr()->eq('import_id', $q->createNamedParameter($importId))
1014
            );
1015
1016
        $dbQueries[] = $q->getSQL();
1017
1018
        $result = $q->execute()->fetchAll();
1019
1020
        return (int)$result[0]['uid'];
1021
    }
1022
1023
    /**
1024
     * Get the sys_category uid on base of the given import_id.
1025
     * The import_id is the original tx_cal_category id prefixed with the IMPORT_PREFIX.
1026
     *
1027
     * @see CalMigrationUpdate::IMPORT_PREFIX
1028
     *
1029
     * @param string $importId
1030
     * @param array  $dbQueries
1031
     * @param array  $customMessages
1032
     *
1033
     * @return int
1034
     */
1035
    protected function getCalendarizeCategoryUid($importId, &$dbQueries, &$customMessages)
1036
    {
1037
        $variables = [
1038
            'table' => 'sys_category',
1039
            'dbQueries' => $dbQueries,
1040
        ];
1041
1042
        $dispatcher = self::getSignalSlotDispatcher();
1043
        $variables = $dispatcher->dispatch(__CLASS__, __FUNCTION__, $variables);
1044
1045
        $q = $this->getQueryBuilder($variables['table']);
1046
1047
        $q->select('uid')
1048
            ->from($variables['table'])
1049
            ->where(
1050
                $q->expr()->eq('import_id', $q->createNamedParameter($importId))
1051
            );
1052
1053
        $dbQueries[] = $q->getSQL();
1054
1055
        $result = $q->execute()->fetchAll();
1056
1057
        return (int)$result[0]['uid'];
1058
    }
1059
1060
    /**
1061
     * Build configurations.
1062
     *
1063
     * @param $calEventRow
1064
     * @param $dbQueries
1065
     *
1066
     * @return int
1067
     */
1068
    protected function buildConfigurations($calEventRow, &$dbQueries)
1069
    {
1070
        $configurationRow = [
1071
            'pid' => $calEventRow['pid'],
1072
            'tstamp' => $calEventRow['tstamp'],
1073
            'crdate' => $calEventRow['crdate'],
1074
            'type' => 'time',
1075
            'handling' => 'include',
1076
            'start_date' => (string)$calEventRow['start_date'] ?: null,
1077
            'end_date' => (string)$calEventRow['end_date'] ?: null,
1078
            'start_time' => (int)$calEventRow['start_time'],
1079
            'end_time' => (int)$calEventRow['end_time'],
1080
            'all_day' => $calEventRow['allday'],
1081
            'frequency' => $this->mapFrequency($calEventRow['freq']),
1082
            'till_date' => (string)$calEventRow['until'] ?: null,
1083
            'counter_amount' => (int)$calEventRow['cnt'],
1084
            'counter_interval' => (int)$calEventRow['interval'],
1085
        ];
1086
1087
        $variables = [
1088
            'table' => self::CONFIGURATION_TABLE,
1089
            'configurationRow' => $configurationRow,
1090
            'calEventRow' => $calEventRow,
1091
            'dbQueries' => $dbQueries,
1092
        ];
1093
1094
        $db = HelperUtility::getDatabaseConnection($variables['table']);
1095
        $q = $db->createQueryBuilder();
1096
1097
        $dispatcher = self::getSignalSlotDispatcher();
1098
        $variables = $dispatcher->dispatch(__CLASS__, __FUNCTION__ . 'PreInsert', $variables);
1099
1100
        $q->insert($variables['table'])
1101
            ->values($variables['configurationRow']);
1102
1103
        $dbQueries[] = $q->getSQL();
1104
        $q->execute();
1105
        $recordId = $db->lastInsertId($variables['table']);
1106
1107
        $variables = [
1108
            'table' => $variables['table'],
1109
            'configurationRow' => $configurationRow,
1110
            'calEventRow' => $calEventRow,
1111
            'recordId' => $recordId,
1112
            'dbQueries' => $dbQueries,
1113
        ];
1114
1115
        $dispatcher = self::getSignalSlotDispatcher();
1116
        $variables = $dispatcher->dispatch(__CLASS__, __FUNCTION__ . 'PostInsert', $variables);
1117
1118
        return $variables['recordId'];
1119
    }
1120
1121
    /**
1122
     * Get the non migrated cal IDs.
1123
     *
1124
     * @return array
1125
     */
1126
    protected function getNonMigratedCalIds()
1127
    {
1128
        $checkImportIds = [];
1129
        $nonMigrated = [];
1130
1131
        $table = 'tx_cal_event';
1132
        $q = $this->getQueryBuilder($table);
1133
1134
        $events = $q->select('uid')
1135
            ->from($table)
1136
            ->execute()
1137
            ->fetchAll();
1138
1139
        foreach ($events as $event) {
1140
            $checkImportIds[] = '"' . self::IMPORT_PREFIX . $event['uid'] . '"';
1141
            $nonMigrated[(int)$event['uid']] = (int)$event['uid'];
1142
        }
1143
1144
        $countOriginal = \count($checkImportIds);
1145
        if (0 === $countOriginal) {
1146
            return [];
1147
        }
1148
1149
        $variables = [
1150
            'table' => self::EVENT_TABLE,
1151
        ];
1152
1153
        $dispatcher = self::getSignalSlotDispatcher();
1154
        $variables = $dispatcher->dispatch(__CLASS__, __FUNCTION__ . 'PreSelect', $variables);
1155
1156
        $q = $this->getQueryBuilder($variables['table']);
1157
1158
        $migratedRows = $q->select('uid', 'import_id')
1159
            ->from($variables['table'])
1160
            ->where(
1161
                $q->expr()->in('import_id', $checkImportIds)
1162
            )
1163
            ->execute()
1164
            ->fetchAll();
1165
1166
        foreach ($migratedRows as $migratedRow) {
1167
            $importId = (int)str_replace(self::IMPORT_PREFIX, '', $migratedRow['import_id']);
1168
            if (isset($nonMigrated[$importId])) {
1169
                unset($nonMigrated[$importId]);
1170
            }
1171
        }
1172
1173
        $variables = [
1174
            'table' => $variables['table'],
1175
            'migratedRows' => $migratedRows,
1176
            'nonMigrated' => $nonMigrated,
1177
        ];
1178
1179
        $dispatcher = self::getSignalSlotDispatcher();
1180
        $variables = $dispatcher->dispatch(__CLASS__, __FUNCTION__ . 'ReadyParsed', $variables);
1181
1182
        return $variables['nonMigrated'];
1183
    }
1184
1185
    public function updateNecessary(): bool
1186
    {
1187
        $connectionPool = GeneralUtility::makeInstance(ConnectionPool::class);
1188
        $connection = $connectionPool->getConnectionByName(ConnectionPool::DEFAULT_CONNECTION_NAME);
1189
        $dbSchema = $connection->getSchemaManager()->createSchema();
1190
1191
        $tableNames = array_map(function ($table) {
1192
            return $table->getName();
1193
        }, $dbSchema->getTables());
1194
1195
        return \in_array('tx_cal_event', $tableNames);
1196
    }
1197
1198
    public function getPrerequisites(): array
1199
    {
1200
        return [
1201
            DatabaseUpdatedPrerequisite::class,
1202
        ];
1203
    }
1204
1205
    private function getQueryBuilder(string $table): QueryBuilder
1206
    {
1207
        $queryBuilder = HelperUtility::getDatabaseConnection($table)->createQueryBuilder();
1208
1209
        // This adds a custom restriction similar to DeletedRestriction
1210
        // When 'cal' is not installed there is no TCA definition.
1211
        // This causes the default restrictions to not work.
1212
        // This adds a custom restriction to filter out deleted records.
1213
        if (str_starts_with($table, 'tx_cal_')) {
1214
            $queryBuilder
1215
                ->getRestrictions()
1216
                ->removeAll()
1217
                ->add(new class() implements QueryRestrictionInterface {
1218
                    public function buildExpression(
1219
                        array $queriedTables,
1220
                        ExpressionBuilder $expressionBuilder
1221
                    ): CompositeExpression {
1222
                        $constraints = [];
1223
                        foreach ($queriedTables as $tableAlias => $tableName) {
1224
                            if (str_starts_with($tableName, 'tx_cal_') && !str_ends_with($tableName, '_mm')) {
1225
                                $constraints[] = $expressionBuilder->eq(
1226
                                    $tableAlias . '.deleted',
1227
                                    0
1228
                                );
1229
                            }
1230
                        }
1231
1232
                        return $expressionBuilder->andX(...$constraints);
1233
                    }
1234
                });
1235
        }
1236
1237
        return $queryBuilder;
1238
    }
1239
1240
    /**
1241
     * Get the signal slot dispatcher.
1242
     *
1243
     * @return Dispatcher
1244
     */
1245
    public static function getSignalSlotDispatcher(): Dispatcher
1246
    {
1247
        return GeneralUtility::makeInstance(Dispatcher::class);
1248
    }
1249
}
1250