Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
88.64% |
273 / 308 |
|
38.89% |
7 / 18 |
CRAP | |
0.00% |
0 / 1 |
EventStore | |
88.64% |
273 / 308 |
|
38.89% |
7 / 18 |
53.67 | |
0.00% |
0 / 1 |
__construct | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
1 | |||
getEventByID | |
90.00% |
18 / 20 |
|
0.00% |
0 / 1 |
3.01 | |||
getEventByPage | |
82.14% |
23 / 28 |
|
0.00% |
0 / 1 |
3.05 | |||
getEventAddressRow | |
100.00% |
13 / 13 |
|
100.00% |
1 / 1 |
3 | |||
getEventTrackingToolRow | |
92.31% |
12 / 13 |
|
0.00% |
0 / 1 |
3.00 | |||
getEventsByOrganizer | |
100.00% |
15 / 15 |
|
100.00% |
1 / 1 |
2 | |||
getEventsByParticipant | |
100.00% |
18 / 18 |
|
100.00% |
1 / 1 |
2 | |||
newEventsFromDBRows | |
100.00% |
20 / 20 |
|
100.00% |
1 / 1 |
3 | |||
getAddressRowsForEvents | |
92.31% |
12 / 13 |
|
0.00% |
0 / 1 |
3.00 | |||
getTrackingToolsRowsForEvents | |
92.31% |
12 / 13 |
|
0.00% |
0 / 1 |
3.00 | |||
newEventFromDBRow | |
97.83% |
45 / 46 |
|
0.00% |
0 / 1 |
5 | |||
saveRegistration | |
87.76% |
43 / 49 |
|
0.00% |
0 / 1 |
4.03 | |||
updateStoredAddresses | |
100.00% |
23 / 23 |
|
100.00% |
1 / 1 |
4 | |||
deleteRegistration | |
100.00% |
12 / 12 |
|
100.00% |
1 / 1 |
1 | |||
getMeetingTypeFromDBVal | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
12 | |||
meetingTypeToDBVal | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
12 | |||
getEventStatusDBVal | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
6 | |||
getEventStatusFromDBVal | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
6 |
1 | <?php |
2 | |
3 | declare( strict_types=1 ); |
4 | |
5 | namespace MediaWiki\Extension\CampaignEvents\Event\Store; |
6 | |
7 | use DateTimeZone; |
8 | use InvalidArgumentException; |
9 | use LogicException; |
10 | use MediaWiki\Extension\CampaignEvents\Address\AddressStore; |
11 | use MediaWiki\Extension\CampaignEvents\Database\CampaignsDatabaseHelper; |
12 | use MediaWiki\Extension\CampaignEvents\Event\EventRegistration; |
13 | use MediaWiki\Extension\CampaignEvents\Event\ExistingEventRegistration; |
14 | use MediaWiki\Extension\CampaignEvents\MWEntity\CampaignsPageFactory; |
15 | use MediaWiki\Extension\CampaignEvents\MWEntity\ICampaignsPage; |
16 | use MediaWiki\Extension\CampaignEvents\Questions\EventQuestionsStore; |
17 | use MediaWiki\Extension\CampaignEvents\TrackingTool\TrackingToolAssociation; |
18 | use MediaWiki\Extension\CampaignEvents\TrackingTool\TrackingToolUpdater; |
19 | use MediaWiki\Extension\CampaignEvents\Utils; |
20 | use RuntimeException; |
21 | use stdClass; |
22 | use Wikimedia\Rdbms\IDatabase; |
23 | use Wikimedia\Rdbms\IDBAccessObject; |
24 | |
25 | /** |
26 | * @note Some pieces of code involving addresses may seem unnecessarily complex, but this is necessary because |
27 | * we will add support for multiple addresses (T321811). |
28 | */ |
29 | class EventStore implements IEventStore, IEventLookup { |
30 | private const EVENT_STATUS_MAP = [ |
31 | EventRegistration::STATUS_OPEN => 1, |
32 | EventRegistration::STATUS_CLOSED => 2, |
33 | ]; |
34 | |
35 | private const EVENT_TYPE_MAP = [ |
36 | EventRegistration::TYPE_GENERIC => 'generic', |
37 | ]; |
38 | |
39 | private const EVENT_MEETING_TYPE_MAP = [ |
40 | EventRegistration::MEETING_TYPE_IN_PERSON => 1, |
41 | EventRegistration::MEETING_TYPE_ONLINE => 2, |
42 | ]; |
43 | |
44 | private CampaignsDatabaseHelper $dbHelper; |
45 | private CampaignsPageFactory $campaignsPageFactory; |
46 | private AddressStore $addressStore; |
47 | private TrackingToolUpdater $trackingToolUpdater; |
48 | private EventQuestionsStore $eventQuestionsStore; |
49 | private EventWikisStore $eventWikisStore; |
50 | private EventTopicsStore $eventTopicsStore; |
51 | |
52 | /** |
53 | * @var array<int,ExistingEventRegistration> Cache of stored registrations, keyed by ID. |
54 | */ |
55 | private array $cache = []; |
56 | |
57 | public function __construct( |
58 | CampaignsDatabaseHelper $dbHelper, |
59 | CampaignsPageFactory $campaignsPageFactory, |
60 | AddressStore $addressStore, |
61 | TrackingToolUpdater $trackingToolUpdater, |
62 | EventQuestionsStore $eventQuestionsStore, |
63 | EventWikisStore $eventWikisStore, |
64 | EventTopicsStore $eventTopicsStore |
65 | ) { |
66 | $this->dbHelper = $dbHelper; |
67 | $this->campaignsPageFactory = $campaignsPageFactory; |
68 | $this->addressStore = $addressStore; |
69 | $this->trackingToolUpdater = $trackingToolUpdater; |
70 | $this->eventQuestionsStore = $eventQuestionsStore; |
71 | $this->eventWikisStore = $eventWikisStore; |
72 | $this->eventTopicsStore = $eventTopicsStore; |
73 | } |
74 | |
75 | /** |
76 | * @inheritDoc |
77 | */ |
78 | public function getEventByID( int $eventID ): ExistingEventRegistration { |
79 | if ( isset( $this->cache[$eventID] ) ) { |
80 | return $this->cache[$eventID]; |
81 | } |
82 | |
83 | $dbr = $this->dbHelper->getDBConnection( DB_REPLICA ); |
84 | $eventRow = $dbr->newSelectQueryBuilder() |
85 | ->select( '*' ) |
86 | ->from( 'campaign_events' ) |
87 | ->where( [ 'event_id' => $eventID ] ) |
88 | ->caller( __METHOD__ ) |
89 | ->fetchRow(); |
90 | if ( !$eventRow ) { |
91 | throw new EventNotFoundException( "Event $eventID not found" ); |
92 | } |
93 | |
94 | $this->cache[$eventID] = $this->newEventFromDBRow( |
95 | $eventRow, |
96 | $this->getEventAddressRow( $dbr, $eventID ), |
97 | $this->getEventTrackingToolRow( $dbr, $eventID ), |
98 | $this->eventWikisStore->getEventWikis( $eventID ), |
99 | $this->eventTopicsStore->getEventTopics( $eventID ), |
100 | $this->eventQuestionsStore->getEventQuestions( $eventID ) |
101 | ); |
102 | return $this->cache[$eventID]; |
103 | } |
104 | |
105 | /** |
106 | * @inheritDoc |
107 | */ |
108 | public function getEventByPage( |
109 | ICampaignsPage $page, |
110 | int $readFlags = IDBAccessObject::READ_NORMAL |
111 | ): ExistingEventRegistration { |
112 | if ( ( $readFlags & IDBAccessObject::READ_LATEST ) === IDBAccessObject::READ_LATEST ) { |
113 | $db = $this->dbHelper->getDBConnection( DB_PRIMARY ); |
114 | } else { |
115 | $db = $this->dbHelper->getDBConnection( DB_REPLICA ); |
116 | } |
117 | |
118 | $eventRow = $db->newSelectQueryBuilder() |
119 | ->select( '*' ) |
120 | ->from( 'campaign_events' ) |
121 | ->where( [ |
122 | 'event_page_namespace' => $page->getNamespace(), |
123 | 'event_page_title' => $page->getDBkey(), |
124 | 'event_page_wiki' => Utils::getWikiIDString( $page->getWikiId() ), |
125 | ] ) |
126 | ->caller( __METHOD__ ) |
127 | ->recency( $readFlags ) |
128 | ->fetchRow(); |
129 | if ( !$eventRow ) { |
130 | throw new EventNotFoundException( |
131 | "No event found for the given page (ns={$page->getNamespace()}, " . |
132 | "dbkey={$page->getDBkey()}, wiki={$page->getWikiId()})" |
133 | ); |
134 | } |
135 | |
136 | $eventID = (int)$eventRow->event_id; |
137 | return $this->newEventFromDBRow( |
138 | $eventRow, |
139 | $this->getEventAddressRow( $db, $eventID ), |
140 | $this->getEventTrackingToolRow( $db, $eventID ), |
141 | $this->eventWikisStore->getEventWikis( $eventID ), |
142 | $this->eventTopicsStore->getEventTopics( $eventID ), |
143 | $this->eventQuestionsStore->getEventQuestions( $eventID ) |
144 | ); |
145 | } |
146 | |
147 | /** |
148 | * @param IDatabase $db |
149 | * @param int $eventID |
150 | * @return stdClass|null |
151 | */ |
152 | private function getEventAddressRow( IDatabase $db, int $eventID ): ?stdClass { |
153 | $addressRows = $db->newSelectQueryBuilder() |
154 | ->select( '*' ) |
155 | ->from( 'ce_address' ) |
156 | ->join( 'ce_event_address', null, [ 'ceea_address=cea_id', 'ceea_event' => $eventID ] ) |
157 | ->caller( __METHOD__ ) |
158 | ->fetchResultSet(); |
159 | |
160 | // TODO Add support for multiple addresses per event |
161 | if ( count( $addressRows ) > 1 ) { |
162 | throw new RuntimeException( 'Events should have only one address.' ); |
163 | } |
164 | |
165 | $addressRow = null; |
166 | foreach ( $addressRows as $row ) { |
167 | $addressRow = $row; |
168 | break; |
169 | } |
170 | return $addressRow; |
171 | } |
172 | |
173 | /** |
174 | * @param IDatabase $db |
175 | * @param int $eventID |
176 | * @return stdClass|null |
177 | */ |
178 | private function getEventTrackingToolRow( IDatabase $db, int $eventID ): ?stdClass { |
179 | $trackingToolsRows = $db->newSelectQueryBuilder() |
180 | ->select( '*' ) |
181 | ->from( 'ce_tracking_tools' ) |
182 | ->where( [ 'cett_event' => $eventID ] ) |
183 | ->caller( __METHOD__ ) |
184 | ->fetchResultSet(); |
185 | |
186 | // TODO Add support for multiple tracking tools per event |
187 | if ( count( $trackingToolsRows ) > 1 ) { |
188 | throw new RuntimeException( 'Events should have only one tracking tool.' ); |
189 | } |
190 | |
191 | $trackingToolRow = null; |
192 | foreach ( $trackingToolsRows as $row ) { |
193 | $trackingToolRow = $row; |
194 | break; |
195 | } |
196 | return $trackingToolRow; |
197 | } |
198 | |
199 | /** |
200 | * @inheritDoc |
201 | */ |
202 | public function getEventsByOrganizer( int $organizerID, ?int $limit = null ): array { |
203 | $dbr = $this->dbHelper->getDBConnection( DB_REPLICA ); |
204 | |
205 | $queryBuilder = $dbr->newSelectQueryBuilder() |
206 | ->select( '*' ) |
207 | ->from( 'campaign_events' ) |
208 | ->join( 'ce_organizers', null, [ |
209 | 'event_id=ceo_event_id', |
210 | 'ceo_deleted_at' => null |
211 | ] ) |
212 | ->where( [ 'ceo_user_id' => $organizerID ] ) |
213 | ->orderBy( 'event_id' ) |
214 | ->caller( __METHOD__ ); |
215 | if ( $limit !== null ) { |
216 | $queryBuilder->limit( $limit ); |
217 | } |
218 | $eventRows = $queryBuilder->fetchResultSet(); |
219 | |
220 | return $this->newEventsFromDBRows( $dbr, $eventRows ); |
221 | } |
222 | |
223 | /** |
224 | * @inheritDoc |
225 | */ |
226 | public function getEventsByParticipant( int $participantID, ?int $limit = null ): array { |
227 | $dbr = $this->dbHelper->getDBConnection( DB_REPLICA ); |
228 | |
229 | $queryBuilder = $dbr->newSelectQueryBuilder() |
230 | ->select( '*' ) |
231 | ->from( 'campaign_events' ) |
232 | ->join( 'ce_participants', null, [ |
233 | 'event_id=cep_event_id', |
234 | // TODO Perhaps consider more granular permission check here. |
235 | 'cep_private' => false, |
236 | ] ) |
237 | ->where( [ |
238 | 'cep_user_id' => $participantID, |
239 | 'cep_unregistered_at' => null, |
240 | ] ) |
241 | ->orderBy( 'event_id' ) |
242 | ->caller( __METHOD__ ); |
243 | if ( $limit !== null ) { |
244 | $queryBuilder->limit( $limit ); |
245 | } |
246 | $eventRows = $queryBuilder->fetchResultSet(); |
247 | |
248 | return $this->newEventsFromDBRows( $dbr, $eventRows ); |
249 | } |
250 | |
251 | /** |
252 | * @param IDatabase $db |
253 | * @param iterable<stdClass> $eventRows |
254 | * @return ExistingEventRegistration[] |
255 | */ |
256 | private function newEventsFromDBRows( IDatabase $db, iterable $eventRows ): array { |
257 | $eventIDs = []; |
258 | foreach ( $eventRows as $eventRow ) { |
259 | $eventIDs[] = (int)$eventRow->event_id; |
260 | } |
261 | |
262 | $addressRowsByEvent = $this->getAddressRowsForEvents( $db, $eventIDs ); |
263 | $trackingToolRowsByEvent = $this->getTrackingToolsRowsForEvents( $db, $eventIDs ); |
264 | $wikisByEvent = $this->eventWikisStore->getEventWikisMulti( $eventIDs ); |
265 | $topicsByEvent = $this->eventTopicsStore->getEventTopicsMulti( $eventIDs ); |
266 | $questionsByEvent = $this->eventQuestionsStore->getEventQuestionsMulti( $eventIDs ); |
267 | |
268 | $events = []; |
269 | foreach ( $eventRows as $row ) { |
270 | $curEventID = (int)$row->event_id; |
271 | $events[] = $this->newEventFromDBRow( |
272 | $row, |
273 | $addressRowsByEvent[$curEventID] ?? null, |
274 | $trackingToolRowsByEvent[$curEventID] ?? null, |
275 | $wikisByEvent[$curEventID], |
276 | $topicsByEvent[$curEventID], |
277 | $questionsByEvent[$curEventID] |
278 | ); |
279 | } |
280 | return $events; |
281 | } |
282 | |
283 | /** |
284 | * @param IDatabase $db |
285 | * @param int[] $eventIDs |
286 | * @return array<int,stdClass> Maps event IDs to the corresponding address row |
287 | */ |
288 | private function getAddressRowsForEvents( IDatabase $db, array $eventIDs ): array { |
289 | $addressRows = $db->newSelectQueryBuilder() |
290 | ->select( '*' ) |
291 | ->from( 'ce_address' ) |
292 | ->join( 'ce_event_address', null, [ 'ceea_address=cea_id', 'ceea_event' => $eventIDs ] ) |
293 | ->caller( __METHOD__ ) |
294 | ->fetchResultSet(); |
295 | |
296 | $addressRowsByEvent = []; |
297 | foreach ( $addressRows as $addressRow ) { |
298 | $curEventID = (int)$addressRow->ceea_event; |
299 | if ( isset( $addressRowsByEvent[$curEventID] ) ) { |
300 | // TODO Add support for multiple addresses per event |
301 | throw new RuntimeException( "Event $curEventID should have only one address." ); |
302 | } |
303 | $addressRowsByEvent[$curEventID] = $addressRow; |
304 | } |
305 | return $addressRowsByEvent; |
306 | } |
307 | |
308 | /** |
309 | * @param IDatabase $db |
310 | * @param int[] $eventIDs |
311 | * @return array<int,stdClass> Maps event IDs to the corresponding tracking tool row |
312 | */ |
313 | private function getTrackingToolsRowsForEvents( |
314 | IDatabase $db, |
315 | array $eventIDs |
316 | ): array { |
317 | $trackingToolsRows = $db->newSelectQueryBuilder() |
318 | ->select( '*' ) |
319 | ->from( 'ce_tracking_tools' ) |
320 | ->where( [ 'cett_event' => $eventIDs ] ) |
321 | ->caller( __METHOD__ ) |
322 | ->fetchResultSet(); |
323 | |
324 | $trackingToolsRowsByEvent = []; |
325 | foreach ( $trackingToolsRows as $trackingToolRow ) { |
326 | $curEventID = (int)$trackingToolRow->cett_event; |
327 | if ( isset( $trackingToolsRowsByEvent[$curEventID] ) ) { |
328 | // TODO Add support for multiple tracking tools per event |
329 | throw new RuntimeException( "Event $curEventID should have only one tracking tool." ); |
330 | } |
331 | $trackingToolsRowsByEvent[$curEventID] = $trackingToolRow; |
332 | } |
333 | return $trackingToolsRowsByEvent; |
334 | } |
335 | |
336 | /** |
337 | * @param stdClass $row |
338 | * @param stdClass|null $addressRow |
339 | * @param stdClass|null $trackingToolRow |
340 | * @param string[]|true $wikis List of wiki IDs or {@see EventRegistration::ALL_WIKIS} |
341 | * @param string[] $topics |
342 | * @param int[] $questionIDs |
343 | * @return ExistingEventRegistration |
344 | */ |
345 | private function newEventFromDBRow( |
346 | stdClass $row, |
347 | ?stdClass $addressRow, |
348 | ?stdClass $trackingToolRow, |
349 | $wikis, |
350 | array $topics, |
351 | array $questionIDs |
352 | ): ExistingEventRegistration { |
353 | $eventPage = $this->campaignsPageFactory->newPageFromDB( |
354 | (int)$row->event_page_namespace, |
355 | $row->event_page_title, |
356 | $row->event_page_prefixedtext, |
357 | $row->event_page_wiki |
358 | ); |
359 | $meetingType = self::getMeetingTypeFromDBVal( $row->event_meeting_type ); |
360 | |
361 | $address = null; |
362 | $country = null; |
363 | if ( $addressRow ) { |
364 | // TODO this is ugly and should be removed as soon as we remove the country on the front end |
365 | $address = explode( " \n ", $addressRow->cea_full_address ); |
366 | array_pop( $address ); |
367 | $address = implode( " \n ", $address ); |
368 | $country = $addressRow->cea_country; |
369 | } |
370 | |
371 | if ( $trackingToolRow ) { |
372 | $trackingTools = [ |
373 | new TrackingToolAssociation( |
374 | (int)$trackingToolRow->cett_tool_id, |
375 | $trackingToolRow->cett_tool_event_id, |
376 | TrackingToolUpdater::dbSyncStatusToConst( (int)$trackingToolRow->cett_sync_status ), |
377 | wfTimestampOrNull( TS_UNIX, $trackingToolRow->cett_last_sync ) |
378 | ) |
379 | ]; |
380 | } else { |
381 | $trackingTools = []; |
382 | } |
383 | |
384 | return new ExistingEventRegistration( |
385 | (int)$row->event_id, |
386 | $row->event_name, |
387 | $eventPage, |
388 | $row->event_chat_url !== '' ? $row->event_chat_url : null, |
389 | $wikis, |
390 | $topics, |
391 | $trackingTools, |
392 | array_search( (int)$row->event_status, self::EVENT_STATUS_MAP, true ), |
393 | new DateTimeZone( $row->event_timezone ), |
394 | wfTimestamp( TS_MW, $row->event_start_local ), |
395 | wfTimestamp( TS_MW, $row->event_end_local ), |
396 | array_search( $row->event_type, self::EVENT_TYPE_MAP, true ), |
397 | $meetingType, |
398 | $row->event_meeting_url !== '' ? $row->event_meeting_url : null, |
399 | $country, |
400 | $address, |
401 | $questionIDs, |
402 | wfTimestamp( TS_UNIX, $row->event_created_at ), |
403 | wfTimestamp( TS_UNIX, $row->event_last_edit ), |
404 | wfTimestampOrNull( TS_UNIX, $row->event_deleted_at ) |
405 | ); |
406 | } |
407 | |
408 | /** |
409 | * @inheritDoc |
410 | */ |
411 | public function saveRegistration( EventRegistration $event ): int { |
412 | $dbw = $this->dbHelper->getDBConnection( DB_PRIMARY ); |
413 | $curDBTimestamp = $dbw->timestamp(); |
414 | |
415 | $curCreationTS = $event->getCreationTimestamp(); |
416 | $curDeletionTS = $event->getDeletionTimestamp(); |
417 | // The local timestamps are already guaranteed to be in TS_MW format and the EventRegistration constructor |
418 | // enforces that, but convert them again as an extra safeguard to avoid any chance of storing garbage. |
419 | $localStartDB = wfTimestamp( TS_MW, $event->getStartLocalTimestamp() ); |
420 | $localEndDB = wfTimestamp( TS_MW, $event->getEndLocalTimestamp() ); |
421 | $newRow = [ |
422 | 'event_name' => $event->getName(), |
423 | 'event_page_namespace' => $event->getPage()->getNamespace(), |
424 | 'event_page_title' => $event->getPage()->getDBkey(), |
425 | 'event_page_prefixedtext' => $event->getPage()->getPrefixedText(), |
426 | 'event_page_wiki' => Utils::getWikiIDString( $event->getPage()->getWikiId() ), |
427 | 'event_chat_url' => $event->getChatURL() ?? '', |
428 | 'event_status' => self::EVENT_STATUS_MAP[$event->getStatus()], |
429 | 'event_timezone' => $event->getTimezone()->getName(), |
430 | 'event_start_local' => $localStartDB, |
431 | 'event_start_utc' => $dbw->timestamp( $event->getStartUTCTimestamp() ), |
432 | 'event_end_local' => $localEndDB, |
433 | 'event_end_utc' => $dbw->timestamp( $event->getEndUTCTimestamp() ), |
434 | 'event_type' => self::EVENT_TYPE_MAP[$event->getType()], |
435 | 'event_meeting_type' => self::meetingTypeToDBVal( $event->getMeetingType() ), |
436 | 'event_meeting_url' => $event->getMeetingURL() ?? '', |
437 | 'event_created_at' => $curCreationTS ? $dbw->timestamp( $curCreationTS ) : $curDBTimestamp, |
438 | 'event_last_edit' => $curDBTimestamp, |
439 | 'event_deleted_at' => $curDeletionTS ? $dbw->timestamp( $curDeletionTS ) : null, |
440 | ]; |
441 | |
442 | $eventID = $event->getID(); |
443 | $dbw->startAtomic( __METHOD__ ); |
444 | if ( $eventID === null ) { |
445 | $dbw->newInsertQueryBuilder() |
446 | ->insertInto( 'campaign_events' ) |
447 | ->row( $newRow ) |
448 | ->caller( __METHOD__ ) |
449 | ->execute(); |
450 | $eventID = $dbw->insertId(); |
451 | } else { |
452 | $dbw->newUpdateQueryBuilder() |
453 | ->update( 'campaign_events' ) |
454 | ->set( $newRow ) |
455 | ->where( [ 'event_id' => $eventID ] ) |
456 | ->caller( __METHOD__ ) |
457 | ->execute(); |
458 | } |
459 | |
460 | $this->updateStoredAddresses( $dbw, $event->getMeetingAddress(), $event->getMeetingCountry(), $eventID ); |
461 | $this->trackingToolUpdater->replaceEventTools( $eventID, $event->getTrackingTools(), $dbw ); |
462 | $this->eventQuestionsStore->replaceEventQuestions( $eventID, $event->getParticipantQuestions() ); |
463 | $this->eventWikisStore->addOrUpdateEventWikis( $eventID, $event->getWikis() ); |
464 | $this->eventTopicsStore->addOrUpdateEventTopics( $eventID, $event->getTopics() ); |
465 | |
466 | $dbw->endAtomic( __METHOD__ ); |
467 | |
468 | unset( $this->cache[$eventID] ); |
469 | |
470 | return $eventID; |
471 | } |
472 | |
473 | /** |
474 | * @param IDatabase $dbw |
475 | * @param string|null $meetingAddress |
476 | * @param string|null $meetingCountry |
477 | * @param int $eventID |
478 | * @return void |
479 | */ |
480 | private function updateStoredAddresses( |
481 | IDatabase $dbw, |
482 | ?string $meetingAddress, |
483 | ?string $meetingCountry, |
484 | int $eventID |
485 | ): void { |
486 | $where = [ 'ceea_event' => $eventID ]; |
487 | if ( $meetingAddress || $meetingCountry ) { |
488 | $meetingAddress .= " \n " . $meetingCountry; |
489 | $where[] = $dbw->expr( 'cea_full_address', '!=', $meetingAddress ); |
490 | } |
491 | |
492 | $dbw->deleteJoin( |
493 | 'ce_event_address', |
494 | 'ce_address', |
495 | 'ceea_address', |
496 | 'cea_id', |
497 | $where, |
498 | __METHOD__ |
499 | ); |
500 | |
501 | if ( $meetingAddress ) { |
502 | $addressID = $this->addressStore->acquireAddressID( $meetingAddress, $meetingCountry ); |
503 | $dbw->newInsertQueryBuilder() |
504 | ->insertInto( 'ce_event_address' ) |
505 | ->ignore() |
506 | ->row( [ |
507 | 'ceea_event' => $eventID, |
508 | 'ceea_address' => $addressID |
509 | ] ) |
510 | ->caller( __METHOD__ ) |
511 | ->execute(); |
512 | } |
513 | } |
514 | |
515 | /** |
516 | * @inheritDoc |
517 | */ |
518 | public function deleteRegistration( ExistingEventRegistration $registration ): bool { |
519 | $dbw = $this->dbHelper->getDBConnection( DB_PRIMARY ); |
520 | $dbw->newUpdateQueryBuilder() |
521 | ->update( 'campaign_events' ) |
522 | ->set( [ 'event_deleted_at' => $dbw->timestamp() ] ) |
523 | ->where( [ |
524 | 'event_id' => $registration->getID(), |
525 | 'event_deleted_at' => null |
526 | ] ) |
527 | ->caller( __METHOD__ ) |
528 | ->execute(); |
529 | unset( $this->cache[$registration->getID()] ); |
530 | return $dbw->affectedRows() > 0; |
531 | } |
532 | |
533 | /** |
534 | * Converts a meeting type as stored in the DB into a combination of the EventRegistration::MEETING_TYPE_* constants |
535 | * @param string $dbMeetingType |
536 | * @return int |
537 | */ |
538 | public static function getMeetingTypeFromDBVal( string $dbMeetingType ): int { |
539 | $ret = 0; |
540 | $dbMeetingTypeNum = (int)$dbMeetingType; |
541 | foreach ( self::EVENT_MEETING_TYPE_MAP as $eventVal => $dbVal ) { |
542 | if ( $dbMeetingTypeNum & $dbVal ) { |
543 | $ret |= $eventVal; |
544 | } |
545 | } |
546 | return $ret; |
547 | } |
548 | |
549 | /** |
550 | * Converts an EventRegistration::MEETING_TYPE_* constant to the corresponding value used in the database. |
551 | * @param int $meetingType |
552 | * @return int |
553 | */ |
554 | public static function meetingTypeToDBVal( int $meetingType ): int { |
555 | $dbMeetingType = 0; |
556 | foreach ( self::EVENT_MEETING_TYPE_MAP as $eventVal => $dbVal ) { |
557 | if ( $meetingType & $eventVal ) { |
558 | $dbMeetingType |= $dbVal; |
559 | } |
560 | } |
561 | return $dbMeetingType; |
562 | } |
563 | |
564 | /** |
565 | * Converts an EventRegistration::STATUS_* constant into the respective DB value. |
566 | * @param string $eventStatus |
567 | * @return int |
568 | */ |
569 | public static function getEventStatusDBVal( string $eventStatus ): int { |
570 | if ( isset( self::EVENT_STATUS_MAP[$eventStatus] ) ) { |
571 | return self::EVENT_STATUS_MAP[$eventStatus]; |
572 | } |
573 | throw new LogicException( "Unknown status $eventStatus" ); |
574 | } |
575 | |
576 | /** |
577 | * Converts an event status as stored in the database to an EventRegistration::STATUS_* constant |
578 | * @param string $eventStatus |
579 | * @return string |
580 | */ |
581 | public static function getEventStatusFromDBVal( string $eventStatus ): string { |
582 | $val = array_search( (int)$eventStatus, self::EVENT_STATUS_MAP, true ); |
583 | if ( $val === false ) { |
584 | throw new InvalidArgumentException( "Unknown event status: $eventStatus" ); |
585 | } |
586 | return $val; |
587 | } |
588 | } |