Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 287 |
|
0.00% |
0 / 9 |
CRAP | |
0.00% |
0 / 1 |
EventDetailsModule | |
0.00% |
0 / 287 |
|
0.00% |
0 / 9 |
1332 | |
0.00% |
0 / 1 |
__construct | |
0.00% |
0 / 9 |
|
0.00% |
0 / 1 |
2 | |||
createContent | |
0.00% |
0 / 33 |
|
0.00% |
0 / 1 |
2 | |||
getHeader | |
0.00% |
0 / 20 |
|
0.00% |
0 / 1 |
12 | |||
getInfoColumn | |
0.00% |
0 / 64 |
|
0.00% |
0 / 1 |
72 | |||
getOrganizersColumn | |
0.00% |
0 / 45 |
|
0.00% |
0 / 1 |
12 | |||
getLocationSection | |
0.00% |
0 / 42 |
|
0.00% |
0 / 1 |
132 | |||
getTrackingToolsSection | |
0.00% |
0 / 54 |
|
0.00% |
0 / 1 |
56 | |||
getFooter | |
0.00% |
0 / 8 |
|
0.00% |
0 / 1 |
2 | |||
makeSection | |
0.00% |
0 / 12 |
|
0.00% |
0 / 1 |
2 |
1 | <?php |
2 | |
3 | declare( strict_types=1 ); |
4 | |
5 | namespace MediaWiki\Extension\CampaignEvents\FrontendModules; |
6 | |
7 | use Language; |
8 | use LogicException; |
9 | use MediaWiki\DAO\WikiAwareEntity; |
10 | use MediaWiki\Extension\CampaignEvents\Event\ExistingEventRegistration; |
11 | use MediaWiki\Extension\CampaignEvents\Hooks\CampaignEventsHookRunner; |
12 | use MediaWiki\Extension\CampaignEvents\MWEntity\PageURLResolver; |
13 | use MediaWiki\Extension\CampaignEvents\MWEntity\UserLinker; |
14 | use MediaWiki\Extension\CampaignEvents\Organizers\OrganizersStore; |
15 | use MediaWiki\Extension\CampaignEvents\Special\SpecialEditEventRegistration; |
16 | use MediaWiki\Extension\CampaignEvents\Special\SpecialEventDetails; |
17 | use MediaWiki\Extension\CampaignEvents\Time\EventTimeFormatter; |
18 | use MediaWiki\Extension\CampaignEvents\TrackingTool\TrackingToolAssociation; |
19 | use MediaWiki\Extension\CampaignEvents\TrackingTool\TrackingToolRegistry; |
20 | use MediaWiki\Extension\CampaignEvents\Utils; |
21 | use MediaWiki\Html\Html; |
22 | use MediaWiki\Linker\Linker; |
23 | use MediaWiki\Output\OutputPage; |
24 | use MediaWiki\SpecialPage\SpecialPage; |
25 | use MediaWiki\User\UserIdentity; |
26 | use MediaWiki\WikiMap\WikiMap; |
27 | use OOUI\ButtonWidget; |
28 | use OOUI\HtmlSnippet; |
29 | use OOUI\IconWidget; |
30 | use OOUI\MessageWidget; |
31 | use OOUI\PanelLayout; |
32 | use OOUI\Tag; |
33 | use Wikimedia\Message\IMessageFormatterFactory; |
34 | use Wikimedia\Message\ITextFormatter; |
35 | use Wikimedia\Message\MessageValue; |
36 | |
37 | class EventDetailsModule { |
38 | private const ORGANIZERS_LIMIT = 10; |
39 | |
40 | public const MODULE_STYLES = [ |
41 | 'oojs-ui.styles.icons-location', |
42 | 'oojs-ui.styles.icons-interactions', |
43 | 'oojs-ui.styles.icons-editing-core', |
44 | 'oojs-ui.styles.icons-alerts', |
45 | 'oojs-ui.styles.icons-user', |
46 | 'oojs-ui.styles.icons-media', |
47 | ]; |
48 | |
49 | private OrganizersStore $organizersStore; |
50 | private PageURLResolver $pageURLResolver; |
51 | private UserLinker $userLinker; |
52 | private EventTimeFormatter $eventTimeFormatter; |
53 | private TrackingToolRegistry $trackingToolRegistry; |
54 | |
55 | private ExistingEventRegistration $registration; |
56 | private Language $language; |
57 | private ITextFormatter $msgFormatter; |
58 | private CampaignEventsHookRunner $hookRunner; |
59 | |
60 | /** |
61 | * @param IMessageFormatterFactory $messageFormatterFactory |
62 | * @param OrganizersStore $organizersStore |
63 | * @param PageURLResolver $pageURLResolver |
64 | * @param UserLinker $userLinker |
65 | * @param EventTimeFormatter $eventTimeFormatter |
66 | * @param TrackingToolRegistry $trackingToolRegistry |
67 | * @param CampaignEventsHookRunner $hookRunner |
68 | * @param ExistingEventRegistration $registration |
69 | * @param Language $language |
70 | */ |
71 | public function __construct( |
72 | IMessageFormatterFactory $messageFormatterFactory, |
73 | OrganizersStore $organizersStore, |
74 | PageURLResolver $pageURLResolver, |
75 | UserLinker $userLinker, |
76 | EventTimeFormatter $eventTimeFormatter, |
77 | TrackingToolRegistry $trackingToolRegistry, |
78 | CampaignEventsHookRunner $hookRunner, |
79 | ExistingEventRegistration $registration, |
80 | Language $language |
81 | ) { |
82 | $this->organizersStore = $organizersStore; |
83 | $this->pageURLResolver = $pageURLResolver; |
84 | $this->userLinker = $userLinker; |
85 | $this->eventTimeFormatter = $eventTimeFormatter; |
86 | $this->trackingToolRegistry = $trackingToolRegistry; |
87 | |
88 | $this->hookRunner = $hookRunner; |
89 | $this->registration = $registration; |
90 | $this->language = $language; |
91 | $this->msgFormatter = $messageFormatterFactory->getTextFormatter( $language->getCode() ); |
92 | } |
93 | |
94 | /** |
95 | * @param UserIdentity $viewingUser |
96 | * @param bool $isOrganizer |
97 | * @param bool $isParticipant |
98 | * @param string|false $wikiID |
99 | * @param OutputPage $out |
100 | * @return PanelLayout |
101 | * |
102 | * @note Ideally, this wouldn't use MW-specific classes for l10n, but it's hard-ish to avoid and |
103 | * probably not worth doing. |
104 | */ |
105 | public function createContent( |
106 | UserIdentity $viewingUser, |
107 | bool $isOrganizer, |
108 | bool $isParticipant, |
109 | $wikiID, |
110 | OutputPage $out |
111 | ): PanelLayout { |
112 | $eventID = $this->registration->getID(); |
113 | $isLocalWiki = $wikiID === WikiAwareEntity::LOCAL; |
114 | $organizersCount = $this->organizersStore->getOrganizerCountForEvent( $eventID ); |
115 | |
116 | $header = $this->getHeader( $isOrganizer, $isLocalWiki ); |
117 | |
118 | $contentWrapper = ( new Tag( 'div' ) ) |
119 | ->addClasses( [ 'ext-campaignevents-eventdetails-content-wrapper' ] ); |
120 | |
121 | $infoColumn = $this->getInfoColumn( |
122 | $viewingUser, |
123 | $out, |
124 | $isOrganizer, |
125 | $isParticipant, |
126 | $isLocalWiki, |
127 | $wikiID, |
128 | $organizersCount |
129 | ); |
130 | $organizersColumn = $this->getOrganizersColumn( $out, $organizersCount ); |
131 | |
132 | $this->hookRunner->onCampaignEventsGetEventDetails( |
133 | $infoColumn, |
134 | $organizersColumn, |
135 | $eventID, |
136 | $isOrganizer, |
137 | $out, |
138 | $isLocalWiki |
139 | ); |
140 | |
141 | $contentWrapper->appendContent( $infoColumn, $organizersColumn ); |
142 | |
143 | $footer = $this->getFooter(); |
144 | return new PanelLayout( [ |
145 | 'content' => [ $header, $contentWrapper, $footer ], |
146 | 'padded' => true, |
147 | 'framed' => true, |
148 | 'expanded' => false, |
149 | 'classes' => [ 'ext-campaignevents-eventdetails-panel' ], |
150 | ] ); |
151 | } |
152 | |
153 | /** |
154 | * @param bool $isOrganizer |
155 | * @param bool $isLocalWiki |
156 | * @return Tag |
157 | */ |
158 | private function getHeader( bool $isOrganizer, bool $isLocalWiki ): Tag { |
159 | $headerItems = []; |
160 | $headerItems[] = ( new Tag( 'h2' ) )->appendContent( |
161 | $this->msgFormatter->format( |
162 | MessageValue::new( 'campaignevents-event-details-label' ) |
163 | ) |
164 | ); |
165 | |
166 | if ( $isOrganizer && $isLocalWiki ) { |
167 | $headerItems[] = new ButtonWidget( [ |
168 | 'label' => $this->msgFormatter->format( |
169 | MessageValue::new( 'campaignevents-event-details-edit-button' ) |
170 | ), |
171 | 'href' => SpecialPage::getTitleFor( |
172 | SpecialEditEventRegistration::PAGE_NAME, |
173 | (string)$this->registration->getID() |
174 | )->getLocalURL(), |
175 | 'icon' => 'edit' |
176 | ] ); |
177 | } |
178 | |
179 | return ( new Tag( 'div' ) ) |
180 | ->appendContent( $headerItems ) |
181 | ->addClasses( [ 'ext-campaignevents-event-details-info-topbar' ] ); |
182 | } |
183 | |
184 | /** |
185 | * @param UserIdentity $viewingUser |
186 | * @param OutputPage $out |
187 | * @param bool $isOrganizer |
188 | * @param bool $isParticipant |
189 | * @param bool $isLocalWiki |
190 | * @param string|false $wikiID |
191 | * @param int $organizersCount |
192 | * @return Tag |
193 | */ |
194 | private function getInfoColumn( |
195 | UserIdentity $viewingUser, |
196 | OutputPage $out, |
197 | bool $isOrganizer, |
198 | bool $isParticipant, |
199 | bool $isLocalWiki, |
200 | $wikiID, |
201 | int $organizersCount |
202 | ): Tag { |
203 | $items = []; |
204 | |
205 | $formattedStart = $this->eventTimeFormatter->formatStart( $this->registration, $this->language, $viewingUser ); |
206 | $formattedEnd = $this->eventTimeFormatter->formatEnd( $this->registration, $this->language, $viewingUser ); |
207 | $datesMsg = $this->msgFormatter->format( |
208 | MessageValue::new( 'campaignevents-event-details-dates' )->params( |
209 | $formattedStart->getTimeAndDate(), |
210 | $formattedStart->getDate(), |
211 | $formattedStart->getTime(), |
212 | $formattedEnd->getTimeAndDate(), |
213 | $formattedEnd->getDate(), |
214 | $formattedEnd->getTime() |
215 | ) |
216 | ); |
217 | $formattedTimezone = $this->eventTimeFormatter->formatTimezone( $this->registration, $viewingUser ); |
218 | // XXX Can't use $this->msgFormatter due to parse() |
219 | $timezoneMsg = $out->msg( 'campaignevents-event-details-timezone' )->params( $formattedTimezone )->parse(); |
220 | $items[] = self::makeSection( |
221 | 'clock', |
222 | [ |
223 | $datesMsg, |
224 | ( new Tag( 'div' ) )->appendContent( new HtmlSnippet( $timezoneMsg ) ) |
225 | ], |
226 | $this->msgFormatter->format( MessageValue::new( 'campaignevents-event-details-dates-label' ) ) |
227 | ); |
228 | |
229 | $needToRegisterMsg = $this->msgFormatter->format( |
230 | MessageValue::new( 'campaignevents-event-details-register-prompt' ) |
231 | ); |
232 | |
233 | $wikiName = WikiMap::getWikiName( Utils::getWikiIDString( $wikiID ) ); |
234 | $foreignDetailsURL = WikiMap::getForeignURL( |
235 | $wikiID, 'Special:' . SpecialEventDetails::PAGE_NAME . "/{$this->registration->getID()}" |
236 | ); |
237 | $needToBeOnLocalWikiMessage = new HtmlSnippet( |
238 | $out->msg( 'campaignevents-event-details-not-local-wiki-prompt' ) |
239 | ->params( [ |
240 | $foreignDetailsURL, $wikiName |
241 | ] )->parse() |
242 | ); |
243 | $items[] = $this->getLocationSection( |
244 | $isOrganizer, |
245 | $isParticipant, |
246 | $isLocalWiki, |
247 | $organizersCount, |
248 | $needToRegisterMsg, |
249 | $needToBeOnLocalWikiMessage |
250 | ); |
251 | |
252 | $trackingToolsSection = $this->getTrackingToolsSection(); |
253 | if ( $trackingToolsSection ) { |
254 | $items[] = $trackingToolsSection; |
255 | } |
256 | |
257 | $chatURL = $this->registration->getChatURL(); |
258 | if ( $chatURL ) { |
259 | if ( ( $isOrganizer || $isParticipant ) && !$isLocalWiki ) { |
260 | $chatSectionContent = $needToBeOnLocalWikiMessage; |
261 | } elseif ( $isOrganizer || $isParticipant ) { |
262 | $chatSectionContent = new HtmlSnippet( Linker::makeExternalLink( $chatURL, $chatURL ) ); |
263 | } else { |
264 | $chatSectionContent = $needToRegisterMsg; |
265 | } |
266 | } else { |
267 | $chatSectionContent = $this->msgFormatter->format( |
268 | MessageValue::new( 'campaignevents-event-details-chat-link-not-available' ) |
269 | ); |
270 | } |
271 | |
272 | $items[] = self::makeSection( |
273 | 'speechBubbles', |
274 | $chatSectionContent, |
275 | $this->msgFormatter->format( MessageValue::new( 'campaignevents-event-details-chat-link' ) ) |
276 | ); |
277 | |
278 | return ( new Tag( 'div' ) ) |
279 | ->appendContent( $items ); |
280 | } |
281 | |
282 | /** |
283 | * @param OutputPage $out |
284 | * @param int $organizersCount |
285 | * @return Tag |
286 | */ |
287 | private function getOrganizersColumn( OutputPage $out, int $organizersCount ): Tag { |
288 | $ret = []; |
289 | |
290 | $partialOrganizers = $this->organizersStore->getEventOrganizers( |
291 | $this->registration->getID(), |
292 | self::ORGANIZERS_LIMIT |
293 | ); |
294 | $organizerListItems = ''; |
295 | $lastOrganizerID = null; |
296 | foreach ( $partialOrganizers as $organizer ) { |
297 | $organizerListItems .= Html::rawElement( |
298 | 'li', |
299 | [], |
300 | $this->userLinker->generateUserLinkWithFallback( $organizer->getUser(), $this->language->getCode() ) |
301 | ); |
302 | $lastOrganizerID = $organizer->getOrganizerID(); |
303 | } |
304 | $out->addJsConfigVars( [ |
305 | 'wgCampaignEventsLastOrganizerID' => $lastOrganizerID, |
306 | ] ); |
307 | $organizersList = Html::rawElement( |
308 | 'ul', |
309 | [ 'class' => 'ext-campaignevents-event-details-organizers-list' ], |
310 | $organizerListItems |
311 | ); |
312 | $ret[] = new HtmlSnippet( $organizersList ); |
313 | |
314 | if ( count( $partialOrganizers ) < $organizersCount ) { |
315 | $viewMoreBtn = new ButtonWidget( [ |
316 | 'label' => $this->msgFormatter->format( |
317 | MessageValue::new( 'campaignevents-event-details-organizers-view-more' ) |
318 | ), |
319 | 'classes' => [ 'ext-campaignevents-event-details-load-organizers-link' ], |
320 | 'framed' => false, |
321 | 'flags' => [ 'progressive' ] |
322 | ] ); |
323 | $viewMoreNoscript = ( new Tag( 'noscript' ) ) |
324 | ->appendContent( |
325 | $this->msgFormatter->format( |
326 | MessageValue::new( 'campaignevents-event-details-organizers-noscript' ) |
327 | ) |
328 | ); |
329 | $ret[] = ( new Tag( 'p' ) )->appendContent( $viewMoreBtn, $viewMoreNoscript ); |
330 | } |
331 | |
332 | $organizersSection = self::makeSection( |
333 | 'userRights', |
334 | $ret, |
335 | $this->msgFormatter->format( MessageValue::new( 'campaignevents-event-details-organizers-header' ) ) |
336 | ); |
337 | |
338 | return ( new Tag( 'div' ) )->appendContent( $organizersSection ); |
339 | } |
340 | |
341 | /** |
342 | * @param bool $isOrganizer |
343 | * @param bool $isParticipant |
344 | * @param bool $isLocalWiki |
345 | * @param int $organizersCount |
346 | * @param string $needToRegisterMsg |
347 | * @param HtmlSnippet $needToBeOnLocalWikiMessage |
348 | * @return Tag |
349 | */ |
350 | private function getLocationSection( |
351 | bool $isOrganizer, |
352 | bool $isParticipant, |
353 | bool $isLocalWiki, |
354 | int $organizersCount, |
355 | string $needToRegisterMsg, |
356 | HtmlSnippet $needToBeOnLocalWikiMessage |
357 | ): Tag { |
358 | $meetingType = $this->registration->getMeetingType(); |
359 | $items = []; |
360 | if ( $meetingType & ExistingEventRegistration::MEETING_TYPE_IN_PERSON ) { |
361 | $items[] = ( new Tag( 'h4' ) ) |
362 | ->appendContent( $this->msgFormatter->format( |
363 | MessageValue::new( 'campaignevents-event-details-in-person-event-label' ) |
364 | ) ); |
365 | |
366 | $rawAddress = $this->registration->getMeetingAddress(); |
367 | $rawCountry = $this->registration->getMeetingCountry(); |
368 | if ( $rawAddress || $rawCountry ) { |
369 | // NOTE: This is not pretty if exactly one of address and country is specified, but |
370 | // that's going to be fixed when we switch to using an actual geocoding service (T309325) |
371 | $address = $rawAddress . "\n" . $rawCountry; |
372 | $items[] = ( new Tag( 'div' ) ) |
373 | ->appendContent( $address ) |
374 | ->setAttributes( [ 'dir' => Utils::guessStringDirection( $address ) ] ); |
375 | } else { |
376 | $items[] = ( new Tag( 'div' ) ) |
377 | ->appendContent( |
378 | $this->msgFormatter->format( |
379 | MessageValue::new( 'campaignevents-event-details-venue-not-available' ) |
380 | ->numParams( $organizersCount ) |
381 | ) |
382 | ); |
383 | } |
384 | } |
385 | |
386 | if ( $meetingType & ExistingEventRegistration::MEETING_TYPE_ONLINE ) { |
387 | $items[] = ( new Tag( 'h4' ) ) |
388 | ->appendContent( $this->msgFormatter->format( |
389 | MessageValue::new( 'campaignevents-event-details-online-label' ) |
390 | ) ); |
391 | |
392 | $meetingURL = $this->registration->getMeetingURL(); |
393 | if ( $meetingURL ) { |
394 | if ( ( $isOrganizer || $isParticipant ) && !$isLocalWiki ) { |
395 | $items[] = $needToBeOnLocalWikiMessage; |
396 | } elseif ( $isOrganizer || $isParticipant ) { |
397 | $items[] = new HtmlSnippet( Linker::makeExternalLink( $meetingURL, $meetingURL ) ); |
398 | } else { |
399 | $items[] = $needToRegisterMsg; |
400 | } |
401 | } else { |
402 | $items[] = $this->msgFormatter->format( |
403 | MessageValue::new( 'campaignevents-event-details-online-link-not-available' ) |
404 | ->numParams( $organizersCount ) |
405 | ); |
406 | } |
407 | } |
408 | |
409 | return self::makeSection( |
410 | 'mapPin', |
411 | $items, |
412 | $this->msgFormatter->format( MessageValue::new( 'campaignevents-event-details-location-header' ) ) |
413 | ); |
414 | } |
415 | |
416 | /** |
417 | * @return Tag|null |
418 | */ |
419 | private function getTrackingToolsSection(): ?Tag { |
420 | $trackingTools = $this->registration->getTrackingTools(); |
421 | |
422 | if ( !$trackingTools ) { |
423 | return null; |
424 | } |
425 | |
426 | if ( count( $trackingTools ) > 1 ) { |
427 | throw new LogicException( "Not expecting more than one tool" ); |
428 | } |
429 | $toolAssoc = $trackingTools[0]; |
430 | $toolUserInfo = $this->trackingToolRegistry->getUserInfo( |
431 | $toolAssoc->getToolID(), |
432 | $toolAssoc->getToolEventID() |
433 | ); |
434 | if ( $toolUserInfo['user-id'] !== 'wikimedia-pe-dashboard' ) { |
435 | throw new LogicException( "Only the P&E Dashboard should be available as a tool for now" ); |
436 | } |
437 | |
438 | $syncStatus = $toolAssoc->getSyncStatus(); |
439 | $lastSyncTS = $toolAssoc->getLastSyncTimestamp(); |
440 | if ( $syncStatus === TrackingToolAssociation::SYNC_STATUS_UNKNOWN || $lastSyncTS === null ) { |
441 | // Maybe the tool is being added right now. But this shouldn't even happen, as |
442 | // UNKNOWN should currently only be used as a temporary placeholder within a single |
443 | // request. At any rate, skip. |
444 | return null; |
445 | } |
446 | |
447 | $sectionItems = []; |
448 | |
449 | $courseURL = $toolUserInfo['tool-event-url']; |
450 | $sectionItems[] = new HtmlSnippet( Linker::makeExternalLink( $courseURL, $courseURL ) ); |
451 | |
452 | if ( $syncStatus === TrackingToolAssociation::SYNC_STATUS_SYNCED ) { |
453 | $msgType = 'success'; |
454 | $msgStatus = $this->msgFormatter->format( |
455 | MessageValue::new( 'campaignevents-event-details-tracking-tool-p&e-dashboard-synced' ) |
456 | ); |
457 | $msgLastSync = $this->msgFormatter->format( |
458 | MessageValue::new( 'campaignevents-event-details-tracking-tool-last-update' ) |
459 | ->dateTimeParams( $lastSyncTS ) |
460 | ->dateParams( $lastSyncTS ) |
461 | ->timeParams( $lastSyncTS ) |
462 | ); |
463 | |
464 | } else { |
465 | $msgType = 'error'; |
466 | $msgStatus = $this->msgFormatter->format( |
467 | MessageValue::new( 'campaignevents-event-details-tracking-tool-p&e-dashboard-desynced' ) |
468 | ); |
469 | $msgLastSync = $this->msgFormatter->format( |
470 | MessageValue::new( 'campaignevents-event-details-tracking-tool-last-successful-update' ) |
471 | ->dateTimeParams( $lastSyncTS ) |
472 | ->dateParams( $lastSyncTS ) |
473 | ->timeParams( $lastSyncTS ) |
474 | ); |
475 | } |
476 | $syncDetailsRawParagraph = ( new Tag( 'p' ) ) |
477 | ->addClasses( [ 'ext-campaignevents-event-details-tracking-tool-sync-details' ] ) |
478 | ->appendContent( htmlspecialchars( $msgLastSync ) ); |
479 | $sectionItems[] = new MessageWidget( [ |
480 | 'type' => $msgType, |
481 | 'label' => new HtmlSnippet( htmlspecialchars( $msgStatus ) . $syncDetailsRawParagraph ), |
482 | 'classes' => [ 'ext-campaignevents-event-details-tracking-tool-sync' ], |
483 | 'inline' => true, |
484 | ] ); |
485 | |
486 | return self::makeSection( |
487 | 'chart', |
488 | $sectionItems, |
489 | $this->msgFormatter->format( MessageValue::new( $toolUserInfo['display-name-msg'] ) ) |
490 | ); |
491 | } |
492 | |
493 | private function getFooter(): Tag { |
494 | return new ButtonWidget( [ |
495 | 'flags' => [ 'progressive' ], |
496 | 'label' => $this->msgFormatter->format( |
497 | MessageValue::new( 'campaignevents-event-details-view-event-page' ) |
498 | ), |
499 | 'classes' => [ 'ext-campaignevents-event-details-view-event-page-button' ], |
500 | 'href' => $this->pageURLResolver->getUrl( $this->registration->getPage() ) |
501 | ] ); |
502 | } |
503 | |
504 | /** |
505 | * Builds a section for the info panel. This method might be called in handlers of the |
506 | * CampaignEventsGetEventDetails hook. |
507 | * |
508 | * @param string $icon |
509 | * @param string|Tag|array $content |
510 | * @param string $label |
511 | * @return Tag |
512 | */ |
513 | public static function makeSection( string $icon, $content, string $label ): Tag { |
514 | $iconWidget = new IconWidget( [ |
515 | 'icon' => $icon, |
516 | 'classes' => [ 'ext-campaignevents-event-details-icon' ] |
517 | ] ); |
518 | $header = ( new Tag( 'h3' ) ) |
519 | ->appendContent( $iconWidget, $label ) |
520 | ->addClasses( [ 'ext-campaignevents-event-details-section-header' ] ); |
521 | |
522 | $contentTag = ( new Tag( 'div' ) ) |
523 | ->appendContent( $content ) |
524 | ->addClasses( [ 'ext-campaignevents-event-details-section-content' ] ); |
525 | |
526 | return ( new Tag( 'div' ) ) |
527 | ->appendContent( $header, $contentTag ); |
528 | } |
529 | } |