Completed
Push — master ( 3f6680...f2b731 )
by Tim
02:55
created

IndexerService::getCurrentItems()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 11
rs 9.9
c 0
b 0
f 0
cc 1
nc 1
nop 2
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\Extbase\SignalSlot\Dispatcher;
17
18
/**
19
 * Index the given events.
20
 */
21
class IndexerService extends AbstractService
22
{
23
    /**
24
     * Index table name.
25
     */
26
    const TABLE_NAME = 'tx_calendarize_domain_model_index';
27
28
    /**
29
     * Reindex all elements.
30
     */
31
    public function reindexAll()
32
    {
33
        $dispatcher = GeneralUtility::makeInstance(Dispatcher::class);
34
        $dispatcher->dispatch(__CLASS__, __FUNCTION__ . 'Pre', [$this]);
35
36
        $this->removeInvalidConfigurationIndex();
37
        $q = HelperUtility::getDatabaseConnection(self::TABLE_NAME)->createQueryBuilder();
38
39
        $q->getRestrictions()
40
            ->removeAll()
41
            ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
42
43
        foreach (Register::getRegister() as $key => $configuration) {
44
            $tableName = $configuration['tableName'];
45
            $this->removeInvalidRecordIndex($tableName);
46
47
            $q->resetQueryParts();
48
49
            $transPointer = $GLOBALS['TCA'][$tableName]['ctrl']['transOrigPointerField'] ?? false; // e.g. l10n_parent
50
51
            if ($transPointer) {
52
                // Note: In loclized tables, it is important, that the "default language records" are indexed first, so the
53
                // overlays can connect with l10n_paretn to the right default record.
54
                $q->select('uid')
55
                    ->from($tableName)
56
                    ->orderBy((string) $transPointer);
57
            } else {
58
                $q->select('uid')
59
                    ->from($tableName);
60
            }
61
62
            $rows = $q->execute()->fetchAll();
63
            foreach ($rows as $row) {
64
                $this->updateIndex($key, $configuration['tableName'], $row['uid']);
65
            }
66
        }
67
68
        $dispatcher->dispatch(__CLASS__, __FUNCTION__ . 'Post', [$this]);
69
    }
70
71
    /**
72
     * Reindex the given element.
73
     *
74
     * @param string $configurationKey
75
     * @param string $tableName
76
     * @param int    $uid
77
     */
78
    public function reindex($configurationKey, $tableName, $uid)
79
    {
80
        $dispatcher = GeneralUtility::makeInstance(Dispatcher::class);
81
        $dispatcher->dispatch(__CLASS__, __FUNCTION__ . 'Pre', [$configurationKey, $tableName, $uid, $this]);
82
83
        $this->removeInvalidConfigurationIndex();
84
        $this->removeInvalidRecordIndex($tableName);
85
        $this->updateIndex($configurationKey, $tableName, $uid);
86
87
        $dispatcher->dispatch(__CLASS__, __FUNCTION__ . 'Post', [$configurationKey, $tableName, $uid, $this]);
88
    }
89
90
    /**
91
     * Get index count.
92
     *
93
     * @param string $tableName
94
     * @param int $uid
95
     *
96
     * @return mixed
97
     */
98
    public function getIndexCount(string $tableName, int $uid)
99
    {
100
        return $this->getCurrentItems($tableName, $uid)->rowCount();
101
    }
102
103
    /**
104
     * Get the next events.
105
     *
106
     * @param string $table
107
     * @param int    $uid
108
     * @param int    $limit
109
     *
110
     * @return array|null
111
     */
112
    public function getNextEvents($table, $uid, $limit = 5)
113
    {
114
        $q = HelperUtility::getDatabaseConnection($table)->createQueryBuilder();
115
        $now = DateTimeUtility::getNow();
116
        $now->setTime(0, 0, 0);
117
118
        $q->getRestrictions()
119
            ->removeAll()
120
            ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
121
122
        $q->select('*')
123
            ->from(self::TABLE_NAME)
124
            ->where(
125
                $q->expr()->andX(
126
                    $q->expr()->gte('start_date', $now->getTimestamp()),
127
                    $q->expr()->eq('foreign_table', $q->createNamedParameter($table)),
128
                    $q->expr()->eq('foreign_uid', $q->createNamedParameter((int) $uid, \PDO::PARAM_INT))
129
                )
130
            )
131
            ->addOrderBy('start_date', 'ASC')
132
            ->addOrderBy('start_time', 'ASC')
133
            ->setMaxResults($limit);
134
135
        return $q->execute()->fetchAll();
136
    }
137
138
    /**
139
     * Build the index for one element.
140
     *
141
     * @param string $configurationKey
142
     * @param string $tableName
143
     * @param int    $uid
144
     */
145
    protected function updateIndex($configurationKey, $tableName, $uid)
146
    {
147
        /** @var $preparationService IndexPreparationService */
148
        static $preparationService = null;
149
        if (null === $preparationService) {
150
            $preparationService = GeneralUtility::makeInstance(IndexPreparationService::class);
151
        }
152
        $neededItems = $preparationService->prepareIndex($configurationKey, $tableName, $uid);
153
        $this->insertAndUpdateNeededItems($neededItems, $tableName, $uid);
154
    }
155
156
    /**
157
     * Get the current items (ignore enable fields)
158
     *
159
     * @param string $tableName
160
     * @param int $uid
161
     * @return \Doctrine\DBAL\Driver\Statement|int
162
     */
163
    protected function getCurrentItems(string $tableName, int $uid) {
164
        $q = HelperUtility::getDatabaseConnection(self::TABLE_NAME)->createQueryBuilder();
165
        $q->getRestrictions()
166
            ->removeAll()
167
            ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
168
        $q->resetQueryParts();
169
        $q->select('*')
170
            ->from(self::TABLE_NAME)
171
            ->where($q->expr()->eq('foreign_table', $q->quote($tableName)), $q->expr()->eq('foreign_uid', $uid));
172
        return $q->execute();
173
    }
174
175
    /**
176
     * Insert and/or update the needed index records.
177
     *
178
     * @param array  $neededItems
179
     * @param string $tableName
180
     * @param int    $uid
181
     */
182
    protected function insertAndUpdateNeededItems(array $neededItems, $tableName, $uid)
183
    {
184
        $databaseConnection = HelperUtility::getDatabaseConnection($tableName);
185
        $currentItems = $this->getCurrentItems($tableName, $uid)->fetchAll();
186
187
        foreach ($neededItems as $neededKey => $neededItem) {
188
            $remove = false;
189
            foreach ($currentItems as $currentKey => $currentItem) {
190
                if (ArrayUtility::isEqualArray($neededItem, $currentItem)) {
191
                    $remove = true;
192
                    unset($neededItems[$neededKey], $currentItems[$currentKey]);
193
194
                    break;
195
                }
196
            }
197
            if ($remove) {
198
                continue;
199
            }
200
        }
201
        foreach ($currentItems as $item) {
202
            $databaseConnection->delete(self::TABLE_NAME, ['uid' => $item['uid']]);
203
        }
204
205
        $neededItems = \array_values($neededItems);
206
        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...
207
            $databaseConnection->bulkInsert(self::TABLE_NAME, $neededItems, \array_keys($neededItems[0]));
208
        }
209
    }
210
211
    /**
212
     * Remove Index items of the given table of records
213
     * that are deleted or do not exists anymore.
214
     *
215
     * @param string $tableName
216
     */
217
    protected function removeInvalidRecordIndex($tableName)
218
    {
219
        $q = HelperUtility::getDatabaseConnection($tableName)->createQueryBuilder();
220
        $q->getRestrictions()
221
            ->removeAll()
222
            ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
223
224
        $q->select('uid')
225
            ->from($tableName);
226
227
        $rows = $q->execute()->fetchAll();
228
229
        $q->resetQueryParts()->resetRestrictions();
230
        $q->delete(self::TABLE_NAME)
231
            ->where(
232
                $q->expr()->eq('foreign_table', $q->createNamedParameter($tableName))
233
            );
234
235
        $ids = [];
236
        foreach ($rows as $row) {
237
            $ids[] = $row['uid'];
238
        }
239
        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...
240
            $q->andWhere(
241
                $q->expr()->notIn('foreign_uid', $ids)
242
            );
243
        }
244
245
        $q->execute();
246
    }
247
248
    /**
249
     * Remove index Items of configurations that are not valid anymore.
250
     *
251
     * @return bool
252
     */
253
    protected function removeInvalidConfigurationIndex()
254
    {
255
        $db = HelperUtility::getDatabaseConnection(self::TABLE_NAME);
256
        $q = $db->createQueryBuilder();
257
258
        $validKeys = \array_keys(Register::getRegister());
259
        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...
260
            foreach ($validKeys as $key => $value) {
261
                $validKeys[$key] = $q->createNamedParameter($value);
262
            }
263
264
            $q->delete(self::TABLE_NAME)
265
                ->where(
266
                    $q->expr()->notIn('unique_register_key', $validKeys)
267
                )->execute();
268
269
            return (bool) $q->execute();
270
        }
271
272
        return (bool) $db->truncate(self::TABLE_NAME);
273
    }
274
}
275