Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 162
0.00% covered (danger)
0.00%
0 / 5
CRAP
0.00% covered (danger)
0.00%
0 / 1
SpecialEventDetails
0.00% covered (danger)
0.00%
0 / 162
0.00% covered (danger)
0.00%
0 / 5
380
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
2
 execute
0.00% covered (danger)
0.00%
0 / 138
0.00% covered (danger)
0.00%
0 / 1
240
 outputErrorBox
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 createTab
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
2
 getGroupName
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2
3declare( strict_types=1 );
4
5namespace MediaWiki\Extension\CampaignEvents\Special;
6
7use MediaWiki\DAO\WikiAwareEntity;
8use MediaWiki\Extension\CampaignEvents\Event\ExistingEventRegistration;
9use MediaWiki\Extension\CampaignEvents\Event\Store\EventNotFoundException;
10use MediaWiki\Extension\CampaignEvents\Event\Store\IEventLookup;
11use MediaWiki\Extension\CampaignEvents\FrontendModules\EventDetailsModule;
12use MediaWiki\Extension\CampaignEvents\FrontendModules\EventDetailsParticipantsModule;
13use MediaWiki\Extension\CampaignEvents\FrontendModules\FrontendModulesFactory;
14use MediaWiki\Extension\CampaignEvents\MWEntity\CampaignsCentralUserLookup;
15use MediaWiki\Extension\CampaignEvents\MWEntity\MWAuthorityProxy;
16use MediaWiki\Extension\CampaignEvents\MWEntity\UserLinker;
17use MediaWiki\Extension\CampaignEvents\MWEntity\UserNotGlobalException;
18use MediaWiki\Extension\CampaignEvents\Organizers\OrganizersStore;
19use MediaWiki\Extension\CampaignEvents\Participants\ParticipantsStore;
20use MediaWiki\Extension\CampaignEvents\Permissions\PermissionChecker;
21use MediaWiki\Extension\CampaignEvents\Utils;
22use MediaWiki\Html\Html;
23use MediaWiki\SpecialPage\SpecialPage;
24use MediaWiki\WikiMap\WikiMap;
25use OOUI\ButtonWidget;
26use OOUI\HtmlSnippet;
27use OOUI\IndexLayout;
28use OOUI\MessageWidget;
29use OOUI\TabPanelLayout;
30use OOUI\Tag;
31use Wikimedia\Message\IMessageFormatterFactory;
32use Wikimedia\Message\MessageValue;
33
34class SpecialEventDetails extends SpecialPage {
35    public const PAGE_NAME = 'EventDetails';
36    private const MODULE_STYLES = [
37        'oojs-ui.styles.icons-movement',
38        'ext.campaignEvents.specialeventdetails.styles',
39        'oojs-ui-widgets.styles'
40    ];
41
42    public const EVENT_DETAILS_PANEL = 'EventDetailsPanel';
43    public const PARTICIPANTS_PANEL = 'ParticipantsPanel';
44    public const EMAIL_PANEL = 'EmailPanel';
45    public const STATS_PANEL = 'StatsPanel';
46
47    protected IEventLookup $eventLookup;
48    protected ?ExistingEventRegistration $event = null;
49    private ParticipantsStore $participantsStore;
50    private OrganizersStore $organizersStore;
51    private IMessageFormatterFactory $messageFormatterFactory;
52    private CampaignsCentralUserLookup $centralUserLookup;
53    private FrontendModulesFactory $frontendModulesFactory;
54    private PermissionChecker $permissionChecker;
55
56    /**
57     * @param IEventLookup $eventLookup
58     * @param ParticipantsStore $participantsStore
59     * @param OrganizersStore $organizersStore
60     * @param IMessageFormatterFactory $messageFormatterFactory
61     * @param CampaignsCentralUserLookup $centralUserLookup
62     * @param FrontendModulesFactory $frontendModulesFactory
63     * @param PermissionChecker $permissionChecker
64     */
65    public function __construct(
66        IEventLookup $eventLookup,
67        ParticipantsStore $participantsStore,
68        OrganizersStore $organizersStore,
69        IMessageFormatterFactory $messageFormatterFactory,
70        CampaignsCentralUserLookup $centralUserLookup,
71        FrontendModulesFactory $frontendModulesFactory,
72        PermissionChecker $permissionChecker
73    ) {
74        parent::__construct( self::PAGE_NAME );
75        $this->eventLookup = $eventLookup;
76        $this->participantsStore = $participantsStore;
77        $this->organizersStore = $organizersStore;
78        $this->messageFormatterFactory = $messageFormatterFactory;
79        $this->centralUserLookup = $centralUserLookup;
80        $this->frontendModulesFactory = $frontendModulesFactory;
81        $this->permissionChecker = $permissionChecker;
82    }
83
84    /**
85     * @inheritDoc
86     */
87    public function execute( $par ) {
88        $this->setHeaders();
89        if ( $par === null ) {
90            $this->outputErrorBox( 'campaignevents-event-details-no-event-id-provided' );
91            return;
92        }
93        $eventID = (int)$par;
94        if ( (string)$eventID !== $par ) {
95            $this->outputErrorBox( 'campaignevents-event-details-invalid-id' );
96            return;
97        }
98        try {
99            $this->event = $this->eventLookup->getEventByID( $eventID );
100        } catch ( EventNotFoundException $_ ) {
101            $this->outputErrorBox( 'campaignevents-event-details-not-found' );
102            return;
103        }
104
105        if ( $this->event->getDeletionTimestamp() !== null ) {
106            $this->outputErrorBox( 'campaignevents-event-details-event-deleted' );
107            return;
108        }
109
110        $out = $this->getOutput();
111        $language = $this->getLanguage();
112
113        $out->enableOOUI();
114        $out->addModuleStyles(
115            array_merge(
116                self::MODULE_STYLES,
117                EventDetailsModule::MODULE_STYLES,
118                EventDetailsParticipantsModule::MODULE_STYLES,
119                UserLinker::MODULE_STYLES
120            )
121        );
122
123        $this->addHelpLink( 'Extension:CampaignEvents' );
124
125        $out->addModules( [ 'ext.campaignEvents.specialeventdetails', 'oojs-ui-widgets' ] );
126
127        $eventID = $this->event->getID();
128        $msgFormatter = $this->messageFormatterFactory->getTextFormatter( $language->getCode() );
129
130        try {
131            $centralUser = $this->centralUserLookup->newFromAuthority( new MWAuthorityProxy( $this->getAuthority() ) );
132            $organizer = $this->organizersStore->getEventOrganizer( $eventID, $centralUser );
133            $isParticipant = $this->participantsStore->userParticipatesInEvent( $eventID, $centralUser, true );
134        } catch ( UserNotGlobalException $_ ) {
135            $organizer = null;
136            $isParticipant = false;
137        }
138        $isOrganizer = $organizer !== null;
139        $userCanEmailParticipants = false;
140        $wikiID = $this->event->getPage()->getWikiId();
141        $isLocalWiki = $wikiID === WikiAwareEntity::LOCAL;
142        if ( $isLocalWiki ) {
143            $userCanEmailParticipants = $this->permissionChecker->userCanEmailParticipants(
144                new MWAuthorityProxy( $this->getAuthority() ),
145                $this->event->getID()
146            );
147        }
148        $out->addJsConfigVars( [
149            'wgCampaignEventsEventID' => $eventID,
150            'wgCampaignEventsShowEmailTab' => $userCanEmailParticipants,
151        ] );
152        $out->setPageTitle(
153            $msgFormatter->format(
154                MessageValue::new( 'campaignevents-event-details-event' )
155                    ->params( $this->event->getName() )
156            )
157        );
158
159        if ( $isOrganizer ) {
160            // Special:MyEvents is not meaningful for participants, see T314879
161            $backLink = new ButtonWidget( [
162                'framed' => false,
163                'flags' => [ 'progressive' ],
164                'label' => $msgFormatter->format( MessageValue::new( 'campaignevents-back-to-your-events' ) ),
165                'href' => SpecialPage::getTitleFor( SpecialMyEvents::PAGE_NAME )->getLocalURL(),
166                'icon' => 'arrowPrevious',
167                'classes' => [ 'ext-campaignevents-eventdetails-back-btn' ]
168            ] );
169
170            $out->addHTML( $backLink );
171        }
172
173        $wikiName = WikiMap::getWikiName( Utils::getWikiIDString( $wikiID ) );
174        if ( $isOrganizer && !$isLocalWiki ) {
175            $foreignDetailsURL = WikiMap::getForeignURL(
176                $wikiID, 'Special:' . self::PAGE_NAME . "/$eventID"
177            );
178
179            $messageWidget = new MessageWidget( [
180                'type' => 'notice',
181                'label' => new HtmlSnippet(
182                    $this->msg( 'campaignevents-event-details-page-nonlocal' )
183                        ->params( [
184                            $foreignDetailsURL,
185                            $wikiName
186                        ] )->parse()
187                )
188            ] );
189            $out->addHTML( $messageWidget );
190        }
191
192        $main = new IndexLayout( [
193            'infusable' => true,
194            'expanded' => false,
195            'framed' => false,
196            'autoFocus' => false,
197            'id' => 'ext-campaignevents-eventdetails-tabs'
198        ] );
199        $eventDetailsModule = $this->frontendModulesFactory->newEventDetailsModule( $this->event, $language );
200        $tabs = [];
201        $tabs[] = $this->createTab(
202            self::EVENT_DETAILS_PANEL,
203            $msgFormatter->format( MessageValue::new( 'campaignevents-event-details-tab-event-details' ) ),
204            $eventDetailsModule->createContent(
205                $this->getUser(),
206                $isOrganizer,
207                $isParticipant,
208                $wikiID,
209                $out
210            )
211        );
212        $eventParticipantsModule = $this->frontendModulesFactory->newEventDetailsParticipantsModule(
213            $language,
214            $this->getFullTitle()->getFullURL( [ 'tab' => self::STATS_PANEL ] )
215        );
216        $tabs[] = $this->createTab(
217            self::PARTICIPANTS_PANEL,
218            $msgFormatter->format( MessageValue::new( 'campaignevents-event-details-tab-participants' ) ),
219            $eventParticipantsModule->createContent(
220                $this->event,
221                $this->getUser(),
222                new MWAuthorityProxy( $this->getAuthority() ),
223                $isOrganizer,
224                $userCanEmailParticipants,
225                $isLocalWiki,
226                $out
227            )
228        );
229        if ( $userCanEmailParticipants ) {
230            $emailModule = $this->frontendModulesFactory->newEmailParticipantsModule();
231            $tabs[] = $this->createTab(
232                self::EMAIL_PANEL,
233                $msgFormatter->format( MessageValue::new( 'campaignevents-event-details-tab-email' ) ),
234                $emailModule->createContent( $language )
235            );
236        }
237
238        if ( $organizer && $isLocalWiki && $this->event->isPast() && $this->event->getParticipantQuestions() ) {
239            $statsModule = $this->frontendModulesFactory->newResponseStatisticsModule( $this->event, $language );
240            $pageURL = $this->getPageTitle( (string)$this->event->getID() )
241                ->getLocalURL( [ 'tab' => $this::STATS_PANEL ] );
242            $tabs[] = $this->createTab(
243                self::STATS_PANEL,
244                $msgFormatter->format( MessageValue::new( 'campaignevents-event-details-tab-stats' ) ),
245                $statsModule->createContent( $organizer, $this->getContext(), $pageURL )
246            );
247        }
248
249        $main->addTabPanels( $tabs );
250        $selectedTab = $this->getRequest()->getRawVal( 'tab', self::EVENT_DETAILS_PANEL );
251        $main->setTabPanel( $selectedTab );
252        $out->addHTML( $main );
253    }
254
255    /**
256     * @param string $errorMsg
257     * @param mixed ...$msgParams
258     * @return void
259     */
260    protected function outputErrorBox( string $errorMsg, ...$msgParams ): void {
261        $this->getOutput()->addHTML( Html::errorBox(
262            $this->msg( $errorMsg )->params( ...$msgParams )->parseAsBlock()
263        ) );
264    }
265
266    /**
267     * @param string $name
268     * @param string $label
269     * @param Tag $content
270     * @return TabPanelLayout
271     */
272    private function createTab( string $name, string $label, Tag $content ): TabPanelLayout {
273        return new TabPanelLayout(
274            $name,
275            [
276                'id' => $name,
277                'label' => $label,
278                'expanded' => false,
279                'tabItemConfig' => [
280                    'href' => $this->getFullTitle()->getLinkURL( [ 'tab' => $name ] )
281                ],
282                'content' => $content
283            ]
284        );
285    }
286
287    /**
288     * @inheritDoc
289     */
290    protected function getGroupName(): string {
291        return 'campaignevents';
292    }
293}