Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 630 |
|
0.00% |
0 / 22 |
CRAP | |
0.00% |
0 / 1 |
EventPageDecorator | |
0.00% |
0 / 630 |
|
0.00% |
0 / 22 |
8556 | |
0.00% |
0 / 1 |
__construct | |
0.00% |
0 / 16 |
|
0.00% |
0 / 1 |
2 | |||
decoratePage | |
0.00% |
0 / 8 |
|
0.00% |
0 / 1 |
20 | |||
maybeAddEnableRegistrationHeader | |
0.00% |
0 / 13 |
|
0.00% |
0 / 1 |
6 | |||
getEnableRegistrationHeader | |
0.00% |
0 / 31 |
|
0.00% |
0 / 1 |
2 | |||
addRegistrationHeader | |
0.00% |
0 / 47 |
|
0.00% |
0 / 1 |
12 | |||
getEventQuestionsData | |
0.00% |
0 / 32 |
|
0.00% |
0 / 1 |
56 | |||
getHeaderElement | |
0.00% |
0 / 17 |
|
0.00% |
0 / 1 |
6 | |||
getParticipantNoticeRow | |
0.00% |
0 / 11 |
|
0.00% |
0 / 1 |
12 | |||
getEventInfoHeaderRow | |
0.00% |
0 / 86 |
|
0.00% |
0 / 1 |
30 | |||
getDetailsDialogContent | |
0.00% |
0 / 26 |
|
0.00% |
0 / 1 |
2 | |||
getDetailsDialogOrganizers | |
0.00% |
0 / 26 |
|
0.00% |
0 / 1 |
12 | |||
getDetailsDialogEventInfo | |
0.00% |
0 / 12 |
|
0.00% |
0 / 1 |
2 | |||
getDetailsDialogDates | |
0.00% |
0 / 26 |
|
0.00% |
0 / 1 |
2 | |||
getDetailsDialogLocation | |
0.00% |
0 / 61 |
|
0.00% |
0 / 1 |
182 | |||
getDetailsDialogChat | |
0.00% |
0 / 24 |
|
0.00% |
0 / 1 |
72 | |||
getDetailsDialogParticipants | |
0.00% |
0 / 41 |
|
0.00% |
0 / 1 |
20 | |||
getParticipantRows | |
0.00% |
0 / 24 |
|
0.00% |
0 / 1 |
56 | |||
getActionElement | |
0.00% |
0 / 66 |
|
0.00% |
0 / 1 |
72 | |||
getUserStatus | |
0.00% |
0 / 17 |
|
0.00% |
0 / 1 |
182 | |||
getParticipantFooter | |
0.00% |
0 / 10 |
|
0.00% |
0 / 1 |
2 | |||
getParticipantRow | |
0.00% |
0 / 24 |
|
0.00% |
0 / 1 |
12 | |||
makeDetailsDialogSection | |
0.00% |
0 / 12 |
|
0.00% |
0 / 1 |
2 |
1 | <?php |
2 | |
3 | declare( strict_types=1 ); |
4 | |
5 | namespace MediaWiki\Extension\CampaignEvents\EventPage; |
6 | |
7 | use Language; |
8 | use LogicException; |
9 | use MediaWiki\Extension\CampaignEvents\Event\EventRegistration; |
10 | use MediaWiki\Extension\CampaignEvents\Event\ExistingEventRegistration; |
11 | use MediaWiki\Extension\CampaignEvents\Event\PageEventLookup; |
12 | use MediaWiki\Extension\CampaignEvents\MWEntity\CampaignsCentralUserLookup; |
13 | use MediaWiki\Extension\CampaignEvents\MWEntity\CampaignsPageFactory; |
14 | use MediaWiki\Extension\CampaignEvents\MWEntity\CentralUser; |
15 | use MediaWiki\Extension\CampaignEvents\MWEntity\CentralUserNotFoundException; |
16 | use MediaWiki\Extension\CampaignEvents\MWEntity\HiddenCentralUserException; |
17 | use MediaWiki\Extension\CampaignEvents\MWEntity\ICampaignsAuthority; |
18 | use MediaWiki\Extension\CampaignEvents\MWEntity\ICampaignsPage; |
19 | use MediaWiki\Extension\CampaignEvents\MWEntity\MWAuthorityProxy; |
20 | use MediaWiki\Extension\CampaignEvents\MWEntity\UserLinker; |
21 | use MediaWiki\Extension\CampaignEvents\MWEntity\UserNotGlobalException; |
22 | use MediaWiki\Extension\CampaignEvents\Organizers\OrganizersStore; |
23 | use MediaWiki\Extension\CampaignEvents\Participants\Participant; |
24 | use MediaWiki\Extension\CampaignEvents\Participants\ParticipantsStore; |
25 | use MediaWiki\Extension\CampaignEvents\Participants\RegisterParticipantCommand; |
26 | use MediaWiki\Extension\CampaignEvents\Participants\UnregisterParticipantCommand; |
27 | use MediaWiki\Extension\CampaignEvents\Permissions\PermissionChecker; |
28 | use MediaWiki\Extension\CampaignEvents\Questions\EventQuestionsRegistry; |
29 | use MediaWiki\Extension\CampaignEvents\Special\SpecialCancelEventRegistration; |
30 | use MediaWiki\Extension\CampaignEvents\Special\SpecialEnableEventRegistration; |
31 | use MediaWiki\Extension\CampaignEvents\Special\SpecialEventDetails; |
32 | use MediaWiki\Extension\CampaignEvents\Special\SpecialRegisterForEvent; |
33 | use MediaWiki\Extension\CampaignEvents\Time\EventTimeFormatter; |
34 | use MediaWiki\Extension\CampaignEvents\Utils; |
35 | use MediaWiki\Extension\CampaignEvents\Widget\TextWithIconWidget; |
36 | use MediaWiki\Html\Html; |
37 | use MediaWiki\Linker\Linker; |
38 | use MediaWiki\Linker\LinkRenderer; |
39 | use MediaWiki\Output\OutputPage; |
40 | use MediaWiki\Page\ProperPageIdentity; |
41 | use MediaWiki\Permissions\Authority; |
42 | use MediaWiki\SpecialPage\SpecialPage; |
43 | use MediaWiki\User\UserIdentity; |
44 | use OOUI\ButtonWidget; |
45 | use OOUI\Element; |
46 | use OOUI\HorizontalLayout; |
47 | use OOUI\HtmlSnippet; |
48 | use OOUI\IconWidget; |
49 | use OOUI\MessageWidget; |
50 | use OOUI\PanelLayout; |
51 | use OOUI\Tag; |
52 | use UnexpectedValueException; |
53 | use Wikimedia\Message\IMessageFormatterFactory; |
54 | use Wikimedia\Message\ITextFormatter; |
55 | use Wikimedia\Message\MessageValue; |
56 | |
57 | /** |
58 | * This service is used to add some widgets to the event page, like the registration header. |
59 | */ |
60 | class EventPageDecorator { |
61 | public const SERVICE_NAME = 'CampaignEventsEventPageDecorator'; |
62 | |
63 | private const ADDRESS_MAX_LENGTH = 30; |
64 | // See T304719#7909758 for how these numbers were chosen |
65 | private const ORGANIZERS_LIMIT = 4; |
66 | private const PARTICIPANTS_LIMIT = 10; |
67 | |
68 | // Constants for the different statuses of a user wrt a given event registration |
69 | private const USER_STATUS_BLOCKED = 1; |
70 | private const USER_STATUS_ORGANIZER = 2; |
71 | private const USER_STATUS_PARTICIPANT_CAN_UNREGISTER = 3; |
72 | private const USER_STATUS_CAN_REGISTER = 4; |
73 | private const USER_STATUS_CANNOT_REGISTER_ENDED = 5; |
74 | private const USER_STATUS_CANNOT_REGISTER_CLOSED = 6; |
75 | |
76 | private PageEventLookup $pageEventLookup; |
77 | private ParticipantsStore $participantsStore; |
78 | private OrganizersStore $organizersStore; |
79 | private PermissionChecker $permissionChecker; |
80 | private LinkRenderer $linkRenderer; |
81 | private CampaignsPageFactory $campaignsPageFactory; |
82 | private CampaignsCentralUserLookup $centralUserLookup; |
83 | private UserLinker $userLinker; |
84 | private EventTimeFormatter $eventTimeFormatter; |
85 | private EventPageCacheUpdater $eventPageCacheUpdater; |
86 | private EventQuestionsRegistry $eventQuestionsRegistry; |
87 | |
88 | private Language $language; |
89 | private ICampaignsAuthority $authority; |
90 | private UserIdentity $viewingUser; |
91 | private OutputPage $out; |
92 | private ITextFormatter $msgFormatter; |
93 | |
94 | /** |
95 | * @var bool|null Whether the user is registered publicly or privately. This value is lazy-loaded iff the user |
96 | * status is USER_STATUS_PARTICIPANT_CAN_UNREGISTER. |
97 | */ |
98 | private ?bool $participantIsPublic = null; |
99 | |
100 | /** |
101 | * @param PageEventLookup $pageEventLookup |
102 | * @param ParticipantsStore $participantsStore |
103 | * @param OrganizersStore $organizersStore |
104 | * @param PermissionChecker $permissionChecker |
105 | * @param IMessageFormatterFactory $messageFormatterFactory |
106 | * @param LinkRenderer $linkRenderer |
107 | * @param CampaignsPageFactory $campaignsPageFactory |
108 | * @param CampaignsCentralUserLookup $centralUserLookup |
109 | * @param UserLinker $userLinker |
110 | * @param EventTimeFormatter $eventTimeFormatter |
111 | * @param EventPageCacheUpdater $eventPageCacheUpdater |
112 | * @param EventQuestionsRegistry $eventQuestionsRegistry |
113 | * @param Language $language |
114 | * @param Authority $viewingAuthority |
115 | * @param OutputPage $out |
116 | */ |
117 | public function __construct( |
118 | PageEventLookup $pageEventLookup, |
119 | ParticipantsStore $participantsStore, |
120 | OrganizersStore $organizersStore, |
121 | PermissionChecker $permissionChecker, |
122 | IMessageFormatterFactory $messageFormatterFactory, |
123 | LinkRenderer $linkRenderer, |
124 | CampaignsPageFactory $campaignsPageFactory, |
125 | CampaignsCentralUserLookup $centralUserLookup, |
126 | UserLinker $userLinker, |
127 | EventTimeFormatter $eventTimeFormatter, |
128 | EventPageCacheUpdater $eventPageCacheUpdater, |
129 | EventQuestionsRegistry $eventQuestionsRegistry, |
130 | Language $language, |
131 | Authority $viewingAuthority, |
132 | OutputPage $out |
133 | ) { |
134 | $this->pageEventLookup = $pageEventLookup; |
135 | $this->participantsStore = $participantsStore; |
136 | $this->organizersStore = $organizersStore; |
137 | $this->permissionChecker = $permissionChecker; |
138 | $this->linkRenderer = $linkRenderer; |
139 | $this->campaignsPageFactory = $campaignsPageFactory; |
140 | $this->centralUserLookup = $centralUserLookup; |
141 | $this->userLinker = $userLinker; |
142 | $this->eventTimeFormatter = $eventTimeFormatter; |
143 | $this->eventPageCacheUpdater = $eventPageCacheUpdater; |
144 | $this->eventQuestionsRegistry = $eventQuestionsRegistry; |
145 | |
146 | $this->language = $language; |
147 | $this->authority = new MWAuthorityProxy( $viewingAuthority ); |
148 | $this->viewingUser = $viewingAuthority->getUser(); |
149 | $this->out = $out; |
150 | $this->msgFormatter = $messageFormatterFactory->getTextFormatter( $language->getCode() ); |
151 | } |
152 | |
153 | /** |
154 | * This is the main entry point for this class. It adds all the necessary HTML (registration header, popup etc.) |
155 | * to the given OutputPage, as well as loading some JS/CSS resources. |
156 | * |
157 | * @param ProperPageIdentity $page |
158 | */ |
159 | public function decoratePage( ProperPageIdentity $page ): void { |
160 | $registration = $this->pageEventLookup->getRegistrationForLocalPage( $page ); |
161 | |
162 | if ( $registration && $registration->getDeletionTimestamp() !== null ) { |
163 | return; |
164 | } |
165 | |
166 | if ( $registration ) { |
167 | $this->addRegistrationHeader( $registration ); |
168 | $this->eventPageCacheUpdater->adjustCacheForPageWithRegistration( $this->out, $registration ); |
169 | } else { |
170 | $campaignsPage = $this->campaignsPageFactory->newFromLocalMediaWikiPage( $page ); |
171 | $this->maybeAddEnableRegistrationHeader( $campaignsPage ); |
172 | } |
173 | } |
174 | |
175 | /** |
176 | * @param ICampaignsPage $eventPage |
177 | */ |
178 | private function maybeAddEnableRegistrationHeader( ICampaignsPage $eventPage ): void { |
179 | if ( !$this->permissionChecker->userCanEnableRegistration( $this->authority, $eventPage ) ) { |
180 | return; |
181 | } |
182 | |
183 | $this->out->enableOOUI(); |
184 | $this->out->addModuleStyles( [ |
185 | 'ext.campaignEvents.eventpage.styles', |
186 | 'oojs-ui.styles.icons-editing-advanced', |
187 | ] ); |
188 | $this->out->addModules( [ 'ext.campaignEvents.eventpage' ] ); |
189 | // We pass this to the client to avoid hardcoding the name of the page field in JS. Apparently we can't use |
190 | // a RL callback for this because it doesn't provide the current page. |
191 | $enableRegistrationURL = SpecialPage::getTitleFor( SpecialEnableEventRegistration::PAGE_NAME )->getLocalURL( [ |
192 | SpecialEnableEventRegistration::PAGE_FIELD_NAME => $eventPage->getPrefixedText() |
193 | ] ); |
194 | $this->out->addJsConfigVars( [ 'wgCampaignEventsEnableRegistrationURL' => $enableRegistrationURL ] ); |
195 | $this->out->addHTML( $this->getEnableRegistrationHeader( $enableRegistrationURL ) ); |
196 | } |
197 | |
198 | /** |
199 | * @param string $enableRegistrationURL |
200 | * @return Tag |
201 | */ |
202 | private function getEnableRegistrationHeader( string $enableRegistrationURL ): Tag { |
203 | $organizerText = ( new Tag( 'div' ) )->appendContent( |
204 | $this->msgFormatter->format( MessageValue::new( 'campaignevents-eventpage-enableheader-organizer' ) ) |
205 | )->setAttributes( [ 'class' => 'ext-campaignevents-eventpage-organizer-label' ] ); |
206 | |
207 | // Wrap it in a span for use inside a flex container, since the message contains HTML. |
208 | // XXX Can't use ITextFormatter here because the message contains HTML, see T260689 |
209 | $infoMsg = ( new Tag( 'span' ) )->appendContent( |
210 | new HtmlSnippet( $this->out->msg( 'campaignevents-eventpage-enableheader-eventpage-desc' )->parse() ) |
211 | ); |
212 | $infoText = ( new Tag( 'div' ) )->appendContent( |
213 | new IconWidget( [ 'icon' => 'calendar', 'classes' => [ 'ext-campaignevents-eventpage-icon' ] ] ), |
214 | $infoMsg |
215 | )->setAttributes( [ 'class' => 'ext-campaignevents-eventpage-enableheader-message' ] ); |
216 | $infoElement = ( new Tag( 'div' ) )->appendContent( $organizerText, $infoText ); |
217 | |
218 | $enableRegistrationBtn = new ButtonWidget( [ |
219 | 'flags' => [ 'primary', 'progressive' ], |
220 | 'label' => $this->msgFormatter->format( |
221 | MessageValue::new( 'campaignevents-eventpage-enableheader-button-label' ) |
222 | ), |
223 | 'classes' => [ 'ext-campaignevents-eventpage-enable-registration-btn' ], |
224 | 'href' => $enableRegistrationURL, |
225 | ] ); |
226 | |
227 | $layout = new PanelLayout( [ |
228 | 'content' => [ $infoElement, $enableRegistrationBtn ], |
229 | 'padded' => true, |
230 | 'framed' => true, |
231 | 'expanded' => false, |
232 | 'classes' => [ 'ext-campaignevents-eventpage-enableheader' ], |
233 | ] ); |
234 | |
235 | $layout->setAttributes( [ |
236 | // Set the lang/dir explicitly, otherwise it will use that of the site/page language, |
237 | // not that of the interface. |
238 | 'dir' => $this->language->getDir(), |
239 | 'lang' => $this->language->getHtmlCode() |
240 | ] ); |
241 | return $layout; |
242 | } |
243 | |
244 | /** |
245 | * @param ExistingEventRegistration $registration |
246 | */ |
247 | private function addRegistrationHeader( ExistingEventRegistration $registration ): void { |
248 | $this->out->setPreventClickjacking( true ); |
249 | $this->out->enableOOUI(); |
250 | $this->out->addModuleStyles( array_merge( |
251 | [ |
252 | 'ext.campaignEvents.eventpage.styles', |
253 | 'oojs-ui.styles.icons-location', |
254 | 'oojs-ui.styles.icons-interactions', |
255 | 'oojs-ui.styles.icons-moderation', |
256 | 'oojs-ui.styles.icons-user', |
257 | 'oojs-ui.styles.icons-alerts', |
258 | ], |
259 | UserLinker::MODULE_STYLES |
260 | ) ); |
261 | |
262 | $this->out->addModules( [ 'ext.campaignEvents.eventpage' ] ); |
263 | |
264 | try { |
265 | $centralUser = $this->centralUserLookup->newFromAuthority( $this->authority ); |
266 | $curParticipant = $this->participantsStore->getEventParticipant( |
267 | $registration->getID(), |
268 | $centralUser, |
269 | true |
270 | ); |
271 | $hasAggregatedAnswers = $this->participantsStore->userHasAggregatedAnswers( |
272 | $registration->getID(), |
273 | $centralUser |
274 | ); |
275 | } catch ( UserNotGlobalException $_ ) { |
276 | $centralUser = null; |
277 | $curParticipant = null; |
278 | $hasAggregatedAnswers = false; |
279 | } |
280 | |
281 | $userStatus = $this->getUserStatus( $registration, $centralUser, $curParticipant ); |
282 | |
283 | $this->out->addHTML( $this->getHeaderElement( $registration, $userStatus ) ); |
284 | $this->out->addHTML( |
285 | $this->getDetailsDialogContent( |
286 | $registration, |
287 | $userStatus, |
288 | $curParticipant |
289 | ) |
290 | ); |
291 | |
292 | $aggregationTimestamp = $curParticipant |
293 | ? Utils::getAnswerAggregationTimestamp( $curParticipant, $registration ) |
294 | : null; |
295 | $this->out->addJsConfigVars( [ |
296 | 'wgCampaignEventsEventID' => $registration->getID(), |
297 | 'wgCampaignEventsParticipantIsPublic' => $this->participantIsPublic, |
298 | 'wgCampaignEventsEventQuestions' => $this->getEventQuestionsData( $registration, $curParticipant ), |
299 | 'wgCampaignEventsAnswersAlreadyAggregated' => $hasAggregatedAnswers, |
300 | 'wgCampaignEventsAggregationTimestamp' => $aggregationTimestamp |
301 | ] ); |
302 | } |
303 | |
304 | /** |
305 | * @param ExistingEventRegistration $registration |
306 | * @param Participant|null $participant |
307 | * @return array[] |
308 | */ |
309 | private function getEventQuestionsData( |
310 | ExistingEventRegistration $registration, |
311 | ?Participant $participant |
312 | ): array { |
313 | $enabledQuestions = $registration->getParticipantQuestions(); |
314 | $curAnswers = $participant ? $participant->getAnswers() : []; |
315 | $questionsToShow = EventQuestionsRegistry::getParticipantQuestionsToShow( $enabledQuestions, $curAnswers ); |
316 | |
317 | $questionsData = []; |
318 | $questionsAPI = $this->eventQuestionsRegistry->getQuestionsForAPI( $questionsToShow ); |
319 | // Localise all messages to avoid having to do that in the client side. |
320 | foreach ( $questionsAPI as $questionAPIData ) { |
321 | $curQuestionData = [ |
322 | 'type' => $questionAPIData['type'], |
323 | 'label' => $this->msgFormatter->format( MessageValue::new( $questionAPIData['label-message'] ) ), |
324 | ]; |
325 | if ( isset( $questionAPIData['options-messages'] ) ) { |
326 | $curQuestionData['options'] = []; |
327 | foreach ( $questionAPIData['options-messages'] as $messageKey => $value ) { |
328 | $message = $this->msgFormatter->format( MessageValue::new( $messageKey ) ); |
329 | $curQuestionData['options'][$messageKey] = [ |
330 | 'value' => $value, |
331 | 'message' => $message |
332 | ]; |
333 | } |
334 | } |
335 | if ( isset( $questionAPIData['other-options'] ) ) { |
336 | $curQuestionData['other-options'] = []; |
337 | foreach ( $questionAPIData['other-options'] as $showIfVal => $otherOptData ) { |
338 | $curQuestionData['other-options'][$showIfVal] = [ |
339 | 'type' => $otherOptData['type'], |
340 | 'placeholder' => $this->msgFormatter->format( |
341 | MessageValue::new( $otherOptData['label-message'] ) |
342 | ), |
343 | ]; |
344 | } |
345 | } |
346 | $questionsData[$questionAPIData['name']] = $curQuestionData; |
347 | } |
348 | |
349 | return [ |
350 | 'questions' => $questionsData, |
351 | 'answers' => $this->eventQuestionsRegistry->formatAnswersForAPI( $curAnswers, $enabledQuestions ) |
352 | ]; |
353 | } |
354 | |
355 | /** |
356 | * Returns the header element. |
357 | * |
358 | * @param ExistingEventRegistration $registration |
359 | * @param int $userStatus One of the self::USER_STATUS_* constants |
360 | * @return Tag |
361 | */ |
362 | private function getHeaderElement( |
363 | ExistingEventRegistration $registration, |
364 | int $userStatus |
365 | ): Tag { |
366 | $content = []; |
367 | |
368 | $participantNoticeRow = $this->getParticipantNoticeRow( $userStatus ); |
369 | if ( $participantNoticeRow ) { |
370 | $content[] = $participantNoticeRow; |
371 | } |
372 | |
373 | $content[] = $this->getEventInfoHeaderRow( $registration, $userStatus ); |
374 | |
375 | $layout = new PanelLayout( [ |
376 | 'content' => $content, |
377 | 'padded' => true, |
378 | 'framed' => true, |
379 | 'expanded' => false, |
380 | 'classes' => [ 'ext-campaignevents-eventpage-header' ], |
381 | ] ); |
382 | |
383 | $layout->setAttributes( [ |
384 | // Set the lang/dir explicitly, otherwise it will use that of the site/page language, |
385 | // not that of the interface. |
386 | 'dir' => $this->language->getDir(), |
387 | 'lang' => $this->language->getHtmlCode() |
388 | ] ); |
389 | |
390 | return $layout; |
391 | } |
392 | |
393 | /** |
394 | * @param int $userStatus |
395 | * @return Tag|null |
396 | */ |
397 | private function getParticipantNoticeRow( int $userStatus ): ?Tag { |
398 | if ( $userStatus !== self::USER_STATUS_PARTICIPANT_CAN_UNREGISTER ) { |
399 | return null; |
400 | } |
401 | $msg = $this->participantIsPublic |
402 | ? 'campaignevents-eventpage-header-registered-publicly' |
403 | : 'campaignevents-eventpage-header-registered-privately'; |
404 | return new MessageWidget( [ |
405 | 'type' => 'success', |
406 | 'label' => $this->msgFormatter->format( MessageValue::new( $msg ) ), |
407 | 'inline' => true, |
408 | 'classes' => [ 'ext-campaignevents-eventpage-participant-notice' ] |
409 | ] ); |
410 | } |
411 | |
412 | /** |
413 | * @param ExistingEventRegistration $registration |
414 | * @param int $userStatus |
415 | * @return Tag |
416 | */ |
417 | private function getEventInfoHeaderRow( |
418 | ExistingEventRegistration $registration, |
419 | int $userStatus |
420 | ): Tag { |
421 | $eventID = $registration->getID(); |
422 | $items = []; |
423 | |
424 | $meetingType = $registration->getMeetingType(); |
425 | if ( $meetingType === EventRegistration::MEETING_TYPE_ONLINE_AND_IN_PERSON ) { |
426 | $locationContent = $this->msgFormatter->format( |
427 | MessageValue::new( 'campaignevents-eventpage-header-type-online-and-in-person' ) |
428 | ); |
429 | } elseif ( $meetingType & EventRegistration::MEETING_TYPE_ONLINE ) { |
430 | $locationContent = $this->msgFormatter->format( |
431 | MessageValue::new( 'campaignevents-eventpage-header-type-online' ) |
432 | ); |
433 | } else { |
434 | // In-person event |
435 | $address = $registration->getMeetingAddress(); |
436 | if ( $address !== null ) { |
437 | $locationContent = new Tag( 'div' ); |
438 | $locationContent->setAttributes( [ |
439 | 'dir' => Utils::guessStringDirection( $address ) |
440 | ] ); |
441 | $locationContent->addClasses( [ 'ext-campaignevents-eventpage-header-address' ] ); |
442 | $locationContent->appendContent( |
443 | $this->language->truncateForVisual( $address, self::ADDRESS_MAX_LENGTH ) |
444 | ); |
445 | } else { |
446 | $locationContent = $this->msgFormatter->format( |
447 | MessageValue::new( 'campaignevents-eventpage-header-type-in-person' ) |
448 | ); |
449 | } |
450 | } |
451 | $items[] = new TextWithIconWidget( [ |
452 | 'icon' => 'mapPin', |
453 | 'content' => $locationContent, |
454 | 'label' => $this->msgFormatter->format( |
455 | MessageValue::new( 'campaignevents-eventpage-header-location-label' ) |
456 | ), |
457 | 'icon_classes' => [ 'ext-campaignevents-eventpage-icon' ], |
458 | ] ); |
459 | |
460 | $formattedStart = $this->eventTimeFormatter->formatStart( $registration, $this->language, $this->viewingUser ); |
461 | $formattedEnd = $this->eventTimeFormatter->formatEnd( $registration, $this->language, $this->viewingUser ); |
462 | $datesMsg = $this->msgFormatter->format( |
463 | MessageValue::new( 'campaignevents-eventpage-header-dates' )->params( |
464 | $formattedStart->getTimeAndDate(), |
465 | $formattedStart->getDate(), |
466 | $formattedStart->getTime(), |
467 | $formattedEnd->getTimeAndDate(), |
468 | $formattedEnd->getDate(), |
469 | $formattedEnd->getTime() |
470 | ) |
471 | ); |
472 | $formattedTimezone = $this->eventTimeFormatter->formatTimezone( $registration, $this->viewingUser ); |
473 | // XXX Can't use ITextFormatter due to parse() |
474 | $timezoneMsg = $this->out->msg( 'campaignevents-eventpage-header-timezone' ) |
475 | ->params( $formattedTimezone ) |
476 | ->parse(); |
477 | $items[] = new TextWithIconWidget( [ |
478 | 'icon' => 'clock', |
479 | 'content' => [ |
480 | $datesMsg, |
481 | ( new Tag( 'div' ) )->appendContent( new HtmlSnippet( $timezoneMsg ) ) |
482 | ], |
483 | 'label' => $this->msgFormatter->format( |
484 | MessageValue::new( 'campaignevents-eventpage-header-dates-label' ) |
485 | ), |
486 | 'icon_classes' => [ 'ext-campaignevents-eventpage-icon' ], |
487 | ] ); |
488 | |
489 | $items[] = new TextWithIconWidget( [ |
490 | 'icon' => 'userGroup', |
491 | 'content' => $this->msgFormatter->format( |
492 | MessageValue::new( 'campaignevents-eventpage-header-participants' ) |
493 | ->numParams( $this->participantsStore->getFullParticipantCountForEvent( $eventID ) ) |
494 | ), |
495 | 'label' => $this->msgFormatter->format( |
496 | MessageValue::new( 'campaignevents-eventpage-header-participants-label' ) |
497 | ), |
498 | 'icon_classes' => [ 'ext-campaignevents-eventpage-icon' ], |
499 | ] ); |
500 | |
501 | $btnContainer = ( new Tag( 'div' ) ) |
502 | ->addClasses( [ 'ext-campaignevents-eventpage-header-buttons' ] ); |
503 | $btnContainer->appendContent( new ButtonWidget( [ |
504 | 'framed' => false, |
505 | 'flags' => [ 'progressive' ], |
506 | 'label' => $this->msgFormatter->format( MessageValue::new( 'campaignevents-eventpage-header-details' ) ), |
507 | 'classes' => [ 'ext-campaignevents-event-details-btn' ], |
508 | 'href' => SpecialPage::getTitleFor( SpecialEventDetails::PAGE_NAME, (string)$eventID )->getLocalURL(), |
509 | ] ) ); |
510 | |
511 | $actionElement = $this->getActionElement( $eventID, $userStatus ); |
512 | if ( $actionElement ) { |
513 | $btnContainer->appendContent( $actionElement ); |
514 | } |
515 | |
516 | $items[] = $btnContainer; |
517 | |
518 | return ( new Tag( 'div' ) ) |
519 | ->addClasses( [ 'ext-campaignevents-eventpage-header-eventinfo' ] ) |
520 | ->appendContent( ...$items ); |
521 | } |
522 | |
523 | /** |
524 | * Returns the content of the "more details" dialog. Unfortunately, we have to build it here rather then on the |
525 | * client side, for the following reasons: |
526 | * - There's no way to format dates according to the user preferences (T21992) |
527 | * - There's no easy way to get the directionality of a language (T181684) |
528 | * - Other utilities are missing (e.g., generating user links) |
529 | * - Secondarily, no need to make 3 API requests and worry about them failing. |
530 | * |
531 | * @param ExistingEventRegistration $registration |
532 | * @param int $userStatus One of the self::USER_STATUS_* constants |
533 | * @param Participant|null $participant |
534 | * @return string |
535 | */ |
536 | private function getDetailsDialogContent( |
537 | ExistingEventRegistration $registration, |
538 | int $userStatus, |
539 | ?Participant $participant |
540 | ): string { |
541 | $eventID = $registration->getID(); |
542 | $organizersCount = $this->organizersStore->getOrganizerCountForEvent( $eventID ); |
543 | |
544 | $eventInfoContainer = $this->getDetailsDialogEventInfo( |
545 | $registration, |
546 | $organizersCount, |
547 | $userStatus |
548 | ); |
549 | $participantsContainer = $this->getDetailsDialogParticipants( |
550 | $eventID, |
551 | $participant |
552 | ); |
553 | |
554 | $dialogContent = Html::element( |
555 | 'h2', |
556 | [ 'class' => 'ext-campaignevents-detailsdialog-header' ], |
557 | $registration->getName() |
558 | ); |
559 | $dialogContent .= $this->getDetailsDialogOrganizers( |
560 | $eventID, |
561 | $organizersCount |
562 | ); |
563 | $dialogContent .= Html::rawElement( |
564 | 'div', |
565 | [ 'class' => 'ext-campaignevents-detailsdialog-body-container' ], |
566 | $eventInfoContainer . $participantsContainer |
567 | ); |
568 | |
569 | return Html::rawElement( 'div', [ 'id' => 'ext-campaignEvents-detailsDialog-content' ], $dialogContent ); |
570 | } |
571 | |
572 | /** |
573 | * @param int $eventID |
574 | * @param int $organizersCount |
575 | * @return string |
576 | */ |
577 | private function getDetailsDialogOrganizers( |
578 | int $eventID, |
579 | int $organizersCount |
580 | ): string { |
581 | $partialOrganizers = $this->organizersStore->getEventOrganizers( $eventID, self::ORGANIZERS_LIMIT ); |
582 | |
583 | $organizerElements = []; |
584 | foreach ( $partialOrganizers as $organizer ) { |
585 | $organizerElements[] = $this->userLinker->generateUserLinkWithFallback( |
586 | $organizer->getUser(), |
587 | $this->language->getCode() |
588 | ); |
589 | } |
590 | // XXX We need to use OutputPage here because there's no supported way to change the format of |
591 | // MessageFormatterFactory... |
592 | $organizersStr = $this->out->msg( 'campaignevents-eventpage-dialog-organizers' ) |
593 | ->rawParams( $this->language->commaList( $organizerElements ) ) |
594 | ->escaped(); |
595 | if ( count( $partialOrganizers ) < $organizersCount ) { |
596 | $organizersStr .= Html::rawElement( |
597 | 'p', |
598 | [], |
599 | $this->linkRenderer->makeKnownLink( |
600 | SpecialPage::getTitleFor( SpecialEventDetails::PAGE_NAME, (string)$eventID ), |
601 | $this->msgFormatter->format( |
602 | MessageValue::new( 'campaignevents-eventpage-dialog-organizers-view-all' ) |
603 | ) |
604 | ) |
605 | ); |
606 | } |
607 | |
608 | return Html::rawElement( |
609 | 'div', |
610 | [ 'class' => 'ext-campaignevents-detailsdialog-organizers' ], |
611 | $organizersStr |
612 | ); |
613 | } |
614 | |
615 | /** |
616 | * @param ExistingEventRegistration $registration |
617 | * @param int $organizersCount |
618 | * @param int $userStatus |
619 | * @return string |
620 | */ |
621 | private function getDetailsDialogEventInfo( |
622 | ExistingEventRegistration $registration, |
623 | int $organizersCount, |
624 | int $userStatus |
625 | ): string { |
626 | $eventInfo = $this->getDetailsDialogDates( $registration ); |
627 | $eventInfo .= $this->getDetailsDialogLocation( |
628 | $registration, |
629 | $organizersCount, |
630 | $userStatus |
631 | ); |
632 | $eventInfo .= $this->getDetailsDialogChat( $registration, $userStatus ); |
633 | |
634 | return Html::rawElement( |
635 | 'div', |
636 | [ 'class' => 'ext-campaignevents-detailsdialog-eventinfo-container' ], |
637 | $eventInfo |
638 | ); |
639 | } |
640 | |
641 | /** |
642 | * @param ExistingEventRegistration $registration |
643 | * @return string |
644 | */ |
645 | private function getDetailsDialogDates( ExistingEventRegistration $registration ): string { |
646 | $formattedStart = $this->eventTimeFormatter->formatStart( $registration, $this->language, $this->viewingUser ); |
647 | $formattedEnd = $this->eventTimeFormatter->formatEnd( $registration, $this->language, $this->viewingUser ); |
648 | $datesMsg = $this->msgFormatter->format( |
649 | MessageValue::new( 'campaignevents-eventpage-dialog-dates' )->params( |
650 | $formattedStart->getTimeAndDate(), |
651 | $formattedStart->getDate(), |
652 | $formattedStart->getTime(), |
653 | $formattedEnd->getTimeAndDate(), |
654 | $formattedEnd->getDate(), |
655 | $formattedEnd->getTime() |
656 | ) |
657 | ); |
658 | $formattedTimezone = $this->eventTimeFormatter->formatTimezone( $registration, $this->viewingUser ); |
659 | // XXX Can't use $msgFormatter due to parse() |
660 | $timezoneMsg = $this->out->msg( 'campaignevents-eventpage-dialog-timezone' ) |
661 | ->params( $formattedTimezone ) |
662 | ->parse(); |
663 | return $this->makeDetailsDialogSection( |
664 | 'clock', |
665 | [ |
666 | $datesMsg, |
667 | ( new Tag( 'div' ) )->appendContent( new HtmlSnippet( $timezoneMsg ) ) |
668 | ], |
669 | $this->msgFormatter->format( |
670 | MessageValue::new( 'campaignevents-eventpage-dialog-dates-label' ) |
671 | ) |
672 | ); |
673 | } |
674 | |
675 | /** |
676 | * @param ExistingEventRegistration $registration |
677 | * @param int $organizersCount |
678 | * @param int $userStatus |
679 | * @return string |
680 | */ |
681 | private function getDetailsDialogLocation( |
682 | ExistingEventRegistration $registration, |
683 | int $organizersCount, |
684 | int $userStatus |
685 | ): string { |
686 | $locationElements = []; |
687 | $onlineLocationElements = []; |
688 | if ( $registration->getMeetingType() & EventRegistration::MEETING_TYPE_ONLINE ) { |
689 | $onlineLocationElements[] = ( new Tag( 'h4' ) ) |
690 | ->addClasses( [ 'ext-campaignevents-eventpage-detailsdialog-location-header' ] ) |
691 | ->appendContent( |
692 | $this->msgFormatter->format( |
693 | MessageValue::new( 'campaignevents-eventpage-dialog-online-label' ) |
694 | ) ); |
695 | $meetingURL = $registration->getMeetingURL(); |
696 | if ( $meetingURL === null ) { |
697 | $linkContent = $this->msgFormatter->format( |
698 | MessageValue::new( 'campaignevents-eventpage-dialog-link-not-available' ) |
699 | ->numParams( $organizersCount ) |
700 | ); |
701 | } elseif ( |
702 | $userStatus === self::USER_STATUS_ORGANIZER || |
703 | $userStatus === self::USER_STATUS_PARTICIPANT_CAN_UNREGISTER |
704 | ) { |
705 | $linkContent = new HtmlSnippet( Linker::makeExternalLink( $meetingURL, $meetingURL ) ); |
706 | } elseif ( $userStatus === self::USER_STATUS_CAN_REGISTER ) { |
707 | $linkContent = $this->msgFormatter->format( |
708 | MessageValue::new( 'campaignevents-eventpage-dialog-link-register' ) |
709 | ); |
710 | } elseif ( |
711 | $userStatus === self::USER_STATUS_BLOCKED || |
712 | $userStatus === self::USER_STATUS_CANNOT_REGISTER_CLOSED || |
713 | $userStatus === self::USER_STATUS_CANNOT_REGISTER_ENDED |
714 | ) { |
715 | $linkContent = ''; |
716 | } else { |
717 | throw new LogicException( "Unexpected user status $userStatus" ); |
718 | } |
719 | $onlineLocationElements[] = ( new Tag( 'p' ) )->appendContent( $linkContent ); |
720 | } |
721 | if ( $registration->getMeetingType() & EventRegistration::MEETING_TYPE_IN_PERSON ) { |
722 | $rawAddress = $registration->getMeetingAddress(); |
723 | $rawCountry = $registration->getMeetingCountry(); |
724 | $addressElement = new Tag( 'p' ); |
725 | $addressElement->addClasses( [ 'ext-campaignevents-eventpage-details-address' ] ); |
726 | if ( $rawAddress || $rawCountry ) { |
727 | $address = $rawAddress . "\n" . $rawCountry; |
728 | $addressElement->setAttributes( [ |
729 | 'dir' => Utils::guessStringDirection( $address ) |
730 | ] ); |
731 | $addressElement->appendContent( $address ); |
732 | } else { |
733 | $addressElement->appendContent( $this->msgFormatter->format( |
734 | MessageValue::new( 'campaignevents-eventpage-dialog-venue-not-available' ) |
735 | ->numParams( $organizersCount ) |
736 | ) ); |
737 | } |
738 | if ( $onlineLocationElements ) { |
739 | $inPersonLabel = ( new Tag( 'h4' ) ) |
740 | ->addClasses( [ 'ext-campaignevents-eventpage-detailsdialog-location-header' ] ) |
741 | ->appendContent( $this->msgFormatter->format( |
742 | MessageValue::new( 'campaignevents-eventpage-dialog-in-person-label' ) |
743 | ) ); |
744 | $locationElements[] = $inPersonLabel; |
745 | $locationElements[] = $addressElement; |
746 | $locationElements = array_merge( $locationElements, $onlineLocationElements ); |
747 | } else { |
748 | $locationElements[] = $addressElement; |
749 | } |
750 | } else { |
751 | $locationElements = array_merge( $locationElements, $onlineLocationElements ); |
752 | } |
753 | return $this->makeDetailsDialogSection( |
754 | 'mapPin', |
755 | $locationElements, |
756 | $this->msgFormatter->format( |
757 | MessageValue::new( 'campaignevents-eventpage-dialog-location-label' ) |
758 | ) |
759 | ); |
760 | } |
761 | |
762 | /** |
763 | * @param ExistingEventRegistration $registration |
764 | * @param int $userStatus |
765 | * @return string |
766 | */ |
767 | private function getDetailsDialogChat( |
768 | ExistingEventRegistration $registration, |
769 | int $userStatus |
770 | ): string { |
771 | $chatURL = $registration->getChatURL(); |
772 | if ( $chatURL === null ) { |
773 | $chatURLContent = $this->msgFormatter->format( |
774 | MessageValue::new( 'campaignevents-eventpage-dialog-chat-not-available' ) |
775 | ); |
776 | } elseif ( |
777 | $userStatus === self::USER_STATUS_ORGANIZER || |
778 | $userStatus === self::USER_STATUS_PARTICIPANT_CAN_UNREGISTER |
779 | ) { |
780 | $chatURLContent = new HtmlSnippet( Linker::makeExternalLink( $chatURL, $chatURL ) ); |
781 | } elseif ( $userStatus === self::USER_STATUS_CAN_REGISTER ) { |
782 | $chatURLContent = $this->msgFormatter->format( |
783 | MessageValue::new( 'campaignevents-eventpage-dialog-chat-register' ) |
784 | ); |
785 | } elseif ( |
786 | $userStatus === self::USER_STATUS_BLOCKED || |
787 | $userStatus === self::USER_STATUS_CANNOT_REGISTER_CLOSED || |
788 | $userStatus === self::USER_STATUS_CANNOT_REGISTER_ENDED |
789 | ) { |
790 | $chatURLContent = ''; |
791 | } else { |
792 | throw new LogicException( "Unexpected user status $userStatus" ); |
793 | } |
794 | return $this->makeDetailsDialogSection( |
795 | 'speechBubbles', |
796 | $chatURLContent, |
797 | $this->msgFormatter->format( |
798 | MessageValue::new( 'campaignevents-eventpage-dialog-chat-label' ) |
799 | ) |
800 | ); |
801 | } |
802 | |
803 | /** |
804 | * @param int $eventID |
805 | * @param Participant|null $participant |
806 | * @return string |
807 | */ |
808 | private function getDetailsDialogParticipants( |
809 | int $eventID, |
810 | ?Participant $participant |
811 | ): string { |
812 | $showPrivateParticipants = $this->permissionChecker->userCanViewPrivateParticipants( |
813 | $this->authority, |
814 | $eventID |
815 | ); |
816 | $participantsCount = $this->participantsStore->getFullParticipantCountForEvent( $eventID ); |
817 | $privateCount = $this->participantsStore->getPrivateParticipantCountForEvent( $eventID ); |
818 | $participantsList = $this->getParticipantRows( |
819 | $eventID, |
820 | $participant, |
821 | $showPrivateParticipants |
822 | ); |
823 | $participantsFooter = ''; |
824 | if ( self::PARTICIPANTS_LIMIT < $participantsCount ) { |
825 | $participantsFooter = $this->getParticipantFooter( $eventID ); |
826 | } |
827 | |
828 | $privateCountFooter = ''; |
829 | if ( $privateCount > 0 ) { |
830 | $privateCountFooter = new Tag(); |
831 | $privateCountFooter->addClasses( [ |
832 | 'ext-campaignevents-detailsdialog-private-participants-footer' |
833 | ] ); |
834 | $privateCountIcon = new IconWidget( [ |
835 | 'icon' => 'lock' |
836 | ] ); |
837 | $privateCountText = ( new Tag( 'span' ) ) |
838 | ->addClasses( [ 'ext-campaignevents-detailsdialog-private-participants-footer-text' ] ); |
839 | $privateCountText->appendContent( |
840 | $this->msgFormatter->format( |
841 | MessageValue::new( 'campaignevents-eventpage-dialog-participants-private' ) |
842 | ->numParams( $privateCount ) |
843 | ) |
844 | ); |
845 | |
846 | $privateCountFooter->appendContent( [ $privateCountIcon, $privateCountText ] ); |
847 | } |
848 | |
849 | return $this->makeDetailsDialogSection( |
850 | 'userGroup', |
851 | [ $participantsList ?: '', $participantsFooter ], |
852 | $this->msgFormatter->format( |
853 | MessageValue::new( 'campaignevents-eventpage-dialog-participants' ) |
854 | ->numParams( $participantsCount ) |
855 | ), |
856 | $privateCountFooter |
857 | ); |
858 | } |
859 | |
860 | /** |
861 | * @param int $eventID |
862 | * @param Participant|null $curUserParticipant |
863 | * @param bool $showPrivateParticipants |
864 | * |
865 | * @return Tag|null |
866 | */ |
867 | private function getParticipantRows( |
868 | int $eventID, |
869 | ?Participant $curUserParticipant, |
870 | bool $showPrivateParticipants |
871 | ): ?Tag { |
872 | $participantsList = ( new Tag( 'ul' ) ) |
873 | ->addClasses( [ 'ext-campaignevents-detailsdialog-participants-list' ] ); |
874 | $partialParticipants = $this->participantsStore->getEventParticipants( |
875 | $eventID, |
876 | $curUserParticipant ? |
877 | self::PARTICIPANTS_LIMIT - 1 : |
878 | self::PARTICIPANTS_LIMIT, |
879 | null, |
880 | null, |
881 | null, |
882 | $showPrivateParticipants, |
883 | $curUserParticipant ? [ $curUserParticipant->getUser()->getCentralID() ] : null |
884 | ); |
885 | |
886 | if ( !$curUserParticipant && !$partialParticipants ) { |
887 | return null; |
888 | } |
889 | |
890 | if ( $curUserParticipant ) { |
891 | $participantsList->appendContent( |
892 | $this->getParticipantRow( $curUserParticipant ) |
893 | ); |
894 | } |
895 | foreach ( $partialParticipants as $participant ) { |
896 | $participantsList->appendContent( |
897 | $this->getParticipantRow( $participant ) |
898 | ); |
899 | } |
900 | return $participantsList; |
901 | } |
902 | |
903 | /** |
904 | * Returns the "action" element for the header (that are also cloned into the popup). This can be a button for |
905 | * managing the event, or one to register for it. Or it can be a widget informing the user that they are already |
906 | * registered, with a button to unregister. There can also be no element if the user is not allowed to register. |
907 | * |
908 | * @param int $eventID |
909 | * @param int $userStatus |
910 | * @return Element|null |
911 | */ |
912 | private function getActionElement( int $eventID, int $userStatus ): ?Element { |
913 | if ( $userStatus === self::USER_STATUS_BLOCKED ) { |
914 | return null; |
915 | } |
916 | |
917 | if ( |
918 | $userStatus === self::USER_STATUS_CANNOT_REGISTER_CLOSED || |
919 | $userStatus === self::USER_STATUS_CANNOT_REGISTER_ENDED |
920 | ) { |
921 | $msgKey = $userStatus === self::USER_STATUS_CANNOT_REGISTER_CLOSED |
922 | ? 'campaignevents-eventpage-btn-registration-closed' |
923 | : 'campaignevents-eventpage-btn-event-ended'; |
924 | return new ButtonWidget( [ |
925 | 'disabled' => true, |
926 | 'label' => $this->msgFormatter->format( MessageValue::new( $msgKey ) ), |
927 | 'classes' => [ |
928 | 'ext-campaignevents-eventpage-action-element' |
929 | ], |
930 | ] ); |
931 | } |
932 | |
933 | if ( $userStatus === self::USER_STATUS_ORGANIZER ) { |
934 | return new ButtonWidget( [ |
935 | 'flags' => [ 'progressive' ], |
936 | 'label' => $this->msgFormatter->format( MessageValue::new( 'campaignevents-eventpage-btn-manage' ) ), |
937 | 'classes' => [ |
938 | 'ext-campaignevents-eventpage-action-element', |
939 | ], |
940 | 'href' => SpecialPage::getTitleFor( |
941 | SpecialEventDetails::PAGE_NAME, |
942 | (string)$eventID |
943 | )->getLocalURL(), |
944 | ] ); |
945 | } |
946 | |
947 | if ( $userStatus === self::USER_STATUS_PARTICIPANT_CAN_UNREGISTER ) { |
948 | $unregisterURL = SpecialPage::getTitleFor( |
949 | SpecialCancelEventRegistration::PAGE_NAME, |
950 | (string)$eventID |
951 | )->getLocalURL(); |
952 | |
953 | // Note that this will be replaced with a ButtonMenuSelectWidget in JS. |
954 | return new HorizontalLayout( [ |
955 | 'items' => [ |
956 | new ButtonWidget( [ |
957 | 'flags' => [ 'progressive' ], |
958 | 'label' => $this->msgFormatter->format( |
959 | MessageValue::new( 'campaignevents-eventpage-btn-edit' ) |
960 | ), |
961 | 'href' => SpecialPage::getTitleFor( SpecialRegisterForEvent::PAGE_NAME, (string)$eventID ) |
962 | ->getLocalURL(), |
963 | ] ), |
964 | new ButtonWidget( [ |
965 | 'flags' => [ 'destructive' ], |
966 | 'label' => $this->msgFormatter->format( |
967 | MessageValue::new( 'campaignevents-eventpage-btn-cancel' ) |
968 | ), |
969 | 'href' => $unregisterURL, |
970 | ] ) |
971 | ], |
972 | 'classes' => [ |
973 | 'ext-campaignevents-eventpage-manage-registration-layout', |
974 | 'ext-campaignevents-eventpage-action-element' |
975 | ] |
976 | ] ); |
977 | } |
978 | |
979 | if ( $userStatus === self::USER_STATUS_CAN_REGISTER ) { |
980 | return new ButtonWidget( [ |
981 | 'flags' => [ 'primary', 'progressive' ], |
982 | 'label' => $this->msgFormatter->format( MessageValue::new( 'campaignevents-eventpage-btn-register' ) ), |
983 | 'classes' => [ |
984 | 'ext-campaignevents-eventpage-register-btn', |
985 | 'ext-campaignevents-eventpage-action-element' |
986 | ], |
987 | 'href' => SpecialPage::getTitleFor( SpecialRegisterForEvent::PAGE_NAME, (string)$eventID ) |
988 | ->getLocalURL(), |
989 | ] ); |
990 | } |
991 | throw new LogicException( "Unexpected user status $userStatus" ); |
992 | } |
993 | |
994 | /** |
995 | * @param ExistingEventRegistration $event |
996 | * @param CentralUser|null $centralUser Corresponding to $this->authority, if it exists |
997 | * @param Participant|null $participant For $centralUser, if they're a participant |
998 | * @return int One of the SELF::USER_STATUS_* constants |
999 | */ |
1000 | private function getUserStatus( |
1001 | ExistingEventRegistration $event, |
1002 | ?CentralUser $centralUser, |
1003 | ?Participant $participant |
1004 | ): int { |
1005 | // Do not check user blocks or other user-dependent conditions for logged-out users, so that we can serve the |
1006 | // same (cached) version of the page to everyone. Also, even if the IP is blocked, the user might have an |
1007 | // account that they can log into, so showing the button is fine. |
1008 | if ( $centralUser ) { |
1009 | if ( $this->authority->isSitewideBlocked() ) { |
1010 | return self::USER_STATUS_BLOCKED; |
1011 | } |
1012 | |
1013 | if ( $this->organizersStore->isEventOrganizer( $event->getID(), $centralUser ) ) { |
1014 | return self::USER_STATUS_ORGANIZER; |
1015 | } |
1016 | |
1017 | if ( $participant ) { |
1018 | $checkUnregistrationAllowedVal = UnregisterParticipantCommand::checkIsUnregistrationAllowed( $event ); |
1019 | switch ( $checkUnregistrationAllowedVal ) { |
1020 | case UnregisterParticipantCommand::CANNOT_UNREGISTER_DELETED: |
1021 | throw new UnexpectedValueException( "Registration should not be deleted at this point." ); |
1022 | case UnregisterParticipantCommand::CAN_UNREGISTER: |
1023 | $this->participantIsPublic = !$participant->isPrivateRegistration(); |
1024 | return self::USER_STATUS_PARTICIPANT_CAN_UNREGISTER; |
1025 | default: |
1026 | throw new UnexpectedValueException( "Unexpected value $checkUnregistrationAllowedVal" ); |
1027 | } |
1028 | } |
1029 | } |
1030 | |
1031 | // User is logged-in and not already participating, or logged-out, in which case we'll know better |
1032 | // once they log in. |
1033 | $checkRegistrationAllowedVal = RegisterParticipantCommand::checkIsRegistrationAllowed( $event ); |
1034 | switch ( $checkRegistrationAllowedVal ) { |
1035 | case RegisterParticipantCommand::CANNOT_REGISTER_DELETED: |
1036 | throw new UnexpectedValueException( "Registration should not be deleted at this point." ); |
1037 | case RegisterParticipantCommand::CANNOT_REGISTER_ENDED: |
1038 | return self::USER_STATUS_CANNOT_REGISTER_ENDED; |
1039 | case RegisterParticipantCommand::CANNOT_REGISTER_CLOSED: |
1040 | return self::USER_STATUS_CANNOT_REGISTER_CLOSED; |
1041 | case RegisterParticipantCommand::CAN_REGISTER: |
1042 | return self::USER_STATUS_CAN_REGISTER; |
1043 | default: |
1044 | throw new UnexpectedValueException( "Unexpected value $checkRegistrationAllowedVal" ); |
1045 | } |
1046 | } |
1047 | |
1048 | /** |
1049 | * @param int $eventID |
1050 | * @return Tag |
1051 | */ |
1052 | private function getParticipantFooter( int $eventID ): Tag { |
1053 | $viewParticipantsURL = SpecialPage::getTitleFor( SpecialEventDetails::PAGE_NAME, (string)$eventID ) |
1054 | ->getLocalURL( [ 'tab' => SpecialEventDetails::PARTICIPANTS_PANEL ] ); |
1055 | return new ButtonWidget( [ |
1056 | 'framed' => false, |
1057 | 'flags' => [ 'progressive' ], |
1058 | 'label' => $this->msgFormatter->format( |
1059 | MessageValue::new( 'campaignevents-eventpage-dialog-participants-view-list' ) |
1060 | ), |
1061 | 'href' => $viewParticipantsURL, |
1062 | ] ); |
1063 | } |
1064 | |
1065 | /** |
1066 | * @param Participant $participant |
1067 | * @return Tag |
1068 | */ |
1069 | private function getParticipantRow( Participant $participant ): Tag { |
1070 | $usernameElement = new HtmlSnippet( |
1071 | $this->userLinker->generateUserLinkWithFallback( |
1072 | $participant->getUser(), |
1073 | $this->language->getCode() |
1074 | ) |
1075 | ); |
1076 | |
1077 | $tag = ( new Tag( 'li' ) ) |
1078 | ->appendContent( $usernameElement ); |
1079 | |
1080 | if ( $participant->isPrivateRegistration() ) { |
1081 | try { |
1082 | $userName = $this->centralUserLookup->getUserName( $participant->getUser() ); |
1083 | } catch ( CentralUserNotFoundException | HiddenCentralUserException $_ ) { |
1084 | // Hack: use an invalid username to force unspecified gender |
1085 | $userName = '@'; |
1086 | } |
1087 | $labelText = $this->msgFormatter->format( |
1088 | MessageValue::new( 'campaignevents-eventpage-dialog-private-registration-label' ) |
1089 | ->params( $userName ) |
1090 | ); |
1091 | $tag->appendContent( new IconWidget( [ |
1092 | 'icon' => 'lock', |
1093 | 'title' => $labelText, |
1094 | 'label' => $labelText, |
1095 | 'classes' => [ 'ext-campaignevents-event-details-participants-private-icon' ] |
1096 | ] ) |
1097 | ); |
1098 | } |
1099 | |
1100 | return $tag; |
1101 | } |
1102 | |
1103 | /** |
1104 | * @param string $icon |
1105 | * @param string|Tag|array $content |
1106 | * @param string $label |
1107 | * @param string|Tag|array $footer |
1108 | * @return string |
1109 | */ |
1110 | private function makeDetailsDialogSection( string $icon, $content, string $label, $footer = '' ): string { |
1111 | $iconWidget = new IconWidget( [ |
1112 | 'icon' => $icon, |
1113 | 'classes' => [ 'ext-campaignevents-eventpage-detailsdialog-section-icon' ] |
1114 | ] ); |
1115 | $header = ( new Tag( 'h3' ) ) |
1116 | ->appendContent( $iconWidget, ( new Tag( 'span' ) )->appendContent( $label ) ) |
1117 | ->addClasses( [ 'ext-campaignevents-eventpage-detailsdialog-section-header' ] ); |
1118 | |
1119 | $contentTag = ( new Tag( 'div' ) ) |
1120 | ->appendContent( $content ) |
1121 | ->addClasses( [ 'ext-campaignevents-eventpage-detailsdialog-section-content' ] ); |
1122 | |
1123 | return (string)( new Tag( 'div' ) ) |
1124 | ->appendContent( $header, $contentTag, $footer ); |
1125 | } |
1126 | } |