Completed
Push — master ( 25637c...558213 )
by Tim
02:09
created

IndexerService::insertAndUpdateNeededItems()   B

Complexity

Conditions 7
Paths 28

Size

Total Lines 30

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 30
rs 8.5066
c 0
b 0
f 0
cc 7
nc 28
nop 3
1
<?php
2
3
/**
4
 * Index the given events.
5
 */
6
declare(strict_types=1);
7
8
namespace HDNET\Calendarize\Service;
9
10
use HDNET\Calendarize\Register;
11
use HDNET\Calendarize\Utility\ArrayUtility;
12
use HDNET\Calendarize\Utility\DateTimeUtility;
13
use HDNET\Calendarize\Utility\HelperUtility;
14
use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
15
use TYPO3\CMS\Core\Utility\GeneralUtility;
16
use TYPO3\CMS\Core\Utility\MathUtility;
17
use TYPO3\CMS\Extbase\SignalSlot\Dispatcher;
18
19
/**
20
 * Index the given events.
21
 */
22
class IndexerService extends AbstractService
23
{
24
    /**
25
     * Index table name.
26
     */
27
    const TABLE_NAME = 'tx_calendarize_domain_model_index';
28
29
    /**
30
     * @var Dispatcher
31
     */
32
    protected $signalSlot;
33
34
    public function __construct()
35
    {
36
        $this->signalSlot = GeneralUtility::makeInstance(Dispatcher::class);
37
    }
38
39
40
    /**
41
     * Reindex all elements.
42
     */
43
    public function reindexAll()
44
    {
45
        $this->signalSlot->dispatch(__CLASS__, __FUNCTION__ . 'Pre', [$this]);
46
47
        $this->removeInvalidConfigurationIndex();
48
        $q = HelperUtility::getDatabaseConnection(self::TABLE_NAME)->createQueryBuilder();
49
50
        $q->getRestrictions()
51
            ->removeAll()
52
            ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
53
54
        foreach (Register::getRegister() as $key => $configuration) {
55
            $tableName = $configuration['tableName'];
56
            $this->removeInvalidRecordIndex($tableName);
57
58
            $q->resetQueryParts();
59
60
            $transPointer = $GLOBALS['TCA'][$tableName]['ctrl']['transOrigPointerField'] ?? false; // e.g. l10n_parent
61
62
            if ($transPointer) {
63
                // Note: In loclized tables, it is important, that the "default language records" are indexed first, so the
64
                // overlays can connect with l10n_paretn to the right default record.
65
                $q->select('uid')
66
                    ->from($tableName)
67
                    ->orderBy((string) $transPointer);
68
            } else {
69
                $q->select('uid')
70
                    ->from($tableName);
71
            }
72
73
            $rows = $q->execute()->fetchAll();
74
            foreach ($rows as $row) {
75
                $this->updateIndex($key, $configuration['tableName'], $row['uid']);
76
            }
77
        }
78
79
        $this->signalSlot->dispatch(__CLASS__, __FUNCTION__ . 'Post', [$this]);
80
    }
81
82
    /**
83
     * Reindex the given element.
84
     *
85
     * @param string $configurationKey
86
     * @param string $tableName
87
     * @param int    $uid
88
     */
89
    public function reindex($configurationKey, $tableName, $uid)
90
    {
91
        $dispatcher = GeneralUtility::makeInstance(Dispatcher::class);
92
        $dispatcher->dispatch(__CLASS__, __FUNCTION__ . 'Pre', [$configurationKey, $tableName, $uid, $this]);
93
94
        $this->removeInvalidConfigurationIndex();
95
        $this->removeInvalidRecordIndex($tableName);
96
        $this->updateIndex($configurationKey, $tableName, $uid);
97
98
        $dispatcher->dispatch(__CLASS__, __FUNCTION__ . 'Post', [$configurationKey, $tableName, $uid, $this]);
99
    }
100
101
    /**
102
     * Get index count.
103
     *
104
     * @param string $tableName
105
     * @param int    $uid
106
     *
107
     * @return int
108
     */
109
    public function getIndexCount(string $tableName, $uid): int
110
    {
111
        // Note: "uid" could be e.g. NEW6273482 in DataHandler process
112
        if (MathUtility::canBeInterpretedAsInteger($uid)) {
113
            return (int) $this->getCurrentItems($tableName, $uid)->rowCount();
114
        }
115
116
        return 0;
117
    }
118
119
    /**
120
     * Get the next events.
121
     *
122
     * @param string $table
123
     * @param int    $uid
124
     * @param int    $limit
125
     *
126
     * @return array|null
127
     */
128
    public function getNextEvents($table, $uid, $limit = 5)
129
    {
130
        $q = HelperUtility::getDatabaseConnection($table)->createQueryBuilder();
131
        $now = DateTimeUtility::getNow();
132
        $now->setTime(0, 0, 0);
133
134
        $q->getRestrictions()
135
            ->removeAll()
136
            ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
137
138
        $q->select('*')
139
            ->from(self::TABLE_NAME)
140
            ->where(
141
                $q->expr()->andX(
142
                    $q->expr()->gte('start_date', $now->getTimestamp()),
143
                    $q->expr()->eq('foreign_table', $q->createNamedParameter($table)),
144
                    $q->expr()->eq('foreign_uid', $q->createNamedParameter((int) $uid, \PDO::PARAM_INT))
145
                )
146
            )
147
            ->addOrderBy('start_date', 'ASC')
148
            ->addOrderBy('start_time', 'ASC')
149
            ->setMaxResults($limit);
150
151
        return $q->execute()->fetchAll();
152
    }
153
154
    /**
155
     * Build the index for one element.
156
     *
157
     * @param string $configurationKey
158
     * @param string $tableName
159
     * @param int    $uid
160
     */
161
    protected function updateIndex($configurationKey, $tableName, $uid)
162
    {
163
        /** @var $preparationService IndexPreparationService */
164
        static $preparationService = null;
165
        if (null === $preparationService) {
166
            $preparationService = GeneralUtility::makeInstance(IndexPreparationService::class);
167
        }
168
        $neededItems = $preparationService->prepareIndex($configurationKey, $tableName, $uid);
169
        $this->insertAndUpdateNeededItems($neededItems, $tableName, $uid);
170
    }
171
172
    /**
173
     * Get the current items (ignore enable fields).
174
     *
175
     * @param string $tableName
176
     * @param int    $uid
177
     *
178
     * @return \Doctrine\DBAL\Driver\Statement|int
179
     */
180
    protected function getCurrentItems(string $tableName, int $uid)
181
    {
182
        $q = HelperUtility::getDatabaseConnection(self::TABLE_NAME)->createQueryBuilder();
183
        $q->getRestrictions()
184
            ->removeAll()
185
            ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
186
        $q->resetQueryParts();
187
        $q->select('*')
188
            ->from(self::TABLE_NAME)
189
            ->where($q->expr()->eq('foreign_table', $q->quote($tableName)), $q->expr()->eq('foreign_uid', $uid));
190
191
        return $q->execute();
192
    }
193
194
    /**
195
     * Insert and/or update the needed index records.
196
     *
197
     * @param array  $neededItems
198
     * @param string $tableName
199
     * @param int    $uid
200
     */
201
    protected function insertAndUpdateNeededItems(array $neededItems, $tableName, $uid)
202
    {
203
        $databaseConnection = HelperUtility::getDatabaseConnection($tableName);
204
        $currentItems = $this->getCurrentItems($tableName, $uid)->fetchAll();
205
        
206
        $this->signalSlot->dispatch(__CLASS__, __FUNCTION__ . 'Pre', [$neededItems, $tableName, $uid]);
207
208
        foreach ($neededItems as $neededKey => $neededItem) {
209
            $remove = false;
210
            foreach ($currentItems as $currentKey => $currentItem) {
211
                if (ArrayUtility::isEqualArray($neededItem, $currentItem)) {
212
                    $remove = true;
213
                    unset($neededItems[$neededKey], $currentItems[$currentKey]);
214
215
                    break;
216
                }
217
            }
218
            if ($remove) {
219
                continue;
220
            }
221
        }
222
        foreach ($currentItems as $item) {
223
            $databaseConnection->delete(self::TABLE_NAME, ['uid' => $item['uid']]);
224
        }
225
226
        $neededItems = \array_values($neededItems);
227
        if ($neededItems) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $neededItems of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
228
            $databaseConnection->bulkInsert(self::TABLE_NAME, $neededItems, \array_keys($neededItems[0]));
229
        }
230
    }
231
232
    /**
233
     * Remove Index items of the given table of records
234
     * that are deleted or do not exists anymore.
235
     *
236
     * @param string $tableName
237
     */
238
    protected function removeInvalidRecordIndex($tableName)
239
    {
240
        $q = HelperUtility::getDatabaseConnection($tableName)->createQueryBuilder();
241
        $q->getRestrictions()
242
            ->removeAll()
243
            ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
244
245
        $q->select('uid')
246
            ->from($tableName);
247
248
        $rows = $q->execute()->fetchAll();
249
250
        $q->resetQueryParts()->resetRestrictions();
251
        $q->delete(self::TABLE_NAME)
252
            ->where(
253
                $q->expr()->eq('foreign_table', $q->createNamedParameter($tableName))
254
            );
255
256
        $ids = [];
257
        foreach ($rows as $row) {
258
            $ids[] = $row['uid'];
259
        }
260
        if ($ids) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $ids of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
261
            $q->andWhere(
262
                $q->expr()->notIn('foreign_uid', $ids)
263
            );
264
        }
265
266
        $q->execute();
267
    }
268
269
    /**
270
     * Remove index Items of configurations that are not valid anymore.
271
     *
272
     * @return bool
273
     */
274
    protected function removeInvalidConfigurationIndex()
275
    {
276
        $db = HelperUtility::getDatabaseConnection(self::TABLE_NAME);
277
        $q = $db->createQueryBuilder();
278
279
        $validKeys = \array_keys(Register::getRegister());
280
        if ($validKeys) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $validKeys of type array<integer|string> is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
281
            foreach ($validKeys as $key => $value) {
282
                $validKeys[$key] = $q->createNamedParameter($value);
283
            }
284
285
            $q->delete(self::TABLE_NAME)
286
                ->where(
287
                    $q->expr()->notIn('unique_register_key', $validKeys)
288
                )->execute();
289
290
            return (bool) $q->execute();
291
        }
292
293
        return (bool) $db->truncate(self::TABLE_NAME);
294
    }
295
}
296