Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 78
0.00% covered (danger)
0.00%
0 / 10
CRAP
0.00% covered (danger)
0.00%
0 / 1
SpecialGenerateInvitationList
0.00% covered (danger)
0.00%
0 / 78
0.00% covered (danger)
0.00%
0 / 10
272
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
2
 execute
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
6
 getFormFields
0.00% covered (danger)
0.00%
0 / 39
0.00% covered (danger)
0.00%
0 / 1
6
 alterForm
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 onSubmit
0.00% covered (danger)
0.00%
0 / 13
0.00% covered (danger)
0.00%
0 / 1
20
 onSuccess
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 makePageMapFromInput
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
2
 getDisplayFormat
0.00% covered (danger)
0.00%
0 / 1
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
 doesWrites
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\Extension\CampaignEvents\Invitation\InvitationListGenerator;
8use MediaWiki\Extension\CampaignEvents\Invitation\WorklistParser;
9use MediaWiki\Extension\CampaignEvents\MWEntity\MWAuthorityProxy;
10use MediaWiki\Extension\CampaignEvents\Permissions\PermissionChecker;
11use MediaWiki\HTMLForm\HTMLForm;
12use MediaWiki\Message\Message;
13use MediaWiki\SpecialPage\FormSpecialPage;
14use MediaWiki\SpecialPage\SpecialPage;
15use MediaWiki\Status\Status;
16use MediaWiki\WikiMap\WikiMap;
17use RuntimeException;
18use StatusValue;
19
20class SpecialGenerateInvitationList extends FormSpecialPage {
21    use InvitationFeatureAccessTrait;
22
23    public const PAGE_NAME = 'GenerateInvitationList';
24
25    private PermissionChecker $permissionChecker;
26    private InvitationListGenerator $invitationListGenerator;
27    private WorklistParser $worklistParser;
28
29    /** @var int|null ID of the newly-generated list. Only set upon successful form submission. */
30    private ?int $listID = null;
31
32    public function __construct(
33        PermissionChecker $permissionChecker,
34        InvitationListGenerator $invitationListGenerator,
35        WorklistParser $worklistParser
36    ) {
37        parent::__construct( self::PAGE_NAME );
38        $this->permissionChecker = $permissionChecker;
39        $this->invitationListGenerator = $invitationListGenerator;
40        $this->worklistParser = $worklistParser;
41    }
42
43    /**
44     * @inheritDoc
45     */
46    public function execute( $par ): void {
47        $this->setHeaders();
48        $this->outputHeader();
49        $mwAuthority = new MWAuthorityProxy( $this->getAuthority() );
50        $isEnabledAndPermitted = $this->checkInvitationFeatureAccess(
51            $this->getOutput(),
52            $mwAuthority
53        );
54        if ( $isEnabledAndPermitted ) {
55            parent::execute( $par );
56        }
57    }
58
59    /**
60     * @inheritDoc
61     */
62    protected function getFormFields(): array {
63        return [
64            'InvitationListName' => [
65                'type' => 'text',
66                'label-message' => 'campaignevents-generateinvitationlist-name-field-label',
67                'placeholder-message' => 'campaignevents-generateinvitationlist-name-field-placeholder',
68                'filter-callback' => fn ( $name ) => trim( (string)$name ),
69                'required' => true
70            ],
71            'EventPage' => [
72                'type' => 'title',
73                'exists' => true,
74                'namespace' => NS_EVENT,
75                'label-message' => 'campaignevents-generateinvitationlist-event-page-field-label',
76                'placeholder-message' => 'campaignevents-generateinvitationlist-event-page-field-placeholder',
77                'required' => false,
78                'validation-callback' => function ( string $eventPage ): StatusValue {
79                    if ( !$eventPage ) {
80                        return StatusValue::newGood();
81                    }
82                    return $this->invitationListGenerator->validateEventPage(
83                        $eventPage,
84                        new MWAuthorityProxy( $this->getAuthority() )
85                    );
86                },
87            ],
88            'ArticleList' => [
89                'type' => 'textarea',
90                'label-message' => 'campaignevents-generateinvitationlist-article-list-field-label',
91                'placeholder-message' => 'campaignevents-generateinvitationlist-article-list-field-placeholder',
92                'help-message' => [
93                    'campaignevents-generateinvitationlist-article-list-field-help',
94                    Message::numParam( WorklistParser::ARTICLES_LIMIT )
95                ],
96                'rows' => 10,
97                'required' => true,
98                'validation-callback' => function ( $worklist ): StatusValue {
99                    return $this->worklistParser->parseWorklist( self::makePageMapFromInput( $worklist ) );
100                }
101            ]
102        ];
103    }
104
105    /**
106     * @inheritDoc
107     */
108    protected function alterForm( HTMLForm $form ): void {
109        $form->setSubmitTextMsg( 'campaignevents-generateinvitationlist-submit-button-text' );
110    }
111
112    /**
113     * @inheritDoc
114     */
115    public function onSubmit( array $data ) {
116        $eventPage = $data['EventPage'] !== '' ? $data['EventPage'] : null;
117        $worklistStatus = $this->worklistParser->parseWorklist( self::makePageMapFromInput( $data['ArticleList'] ) );
118        if ( !$worklistStatus->isGood() ) {
119            // This shouldn't actually happen in practice thanks to validation-callback
120            return Status::wrap( $worklistStatus );
121        }
122
123        $invitationListStatus = $this->invitationListGenerator->createIfAllowed(
124            $data['InvitationListName'],
125            $eventPage,
126            $worklistStatus->getValue(),
127            new MWAuthorityProxy( $this->getAuthority() )
128        );
129        if ( $invitationListStatus->isGood() ) {
130            $this->listID = $invitationListStatus->getValue();
131        }
132        return Status::wrap( $invitationListStatus );
133    }
134
135    /**
136     * @inheritDoc
137     */
138    public function onSuccess(): void {
139        if ( $this->listID === null ) {
140            throw new RuntimeException( "List ID is unset" );
141        }
142        $invitationListPage = SpecialPage::getTitleFor( SpecialInvitationList::PAGE_NAME, (string)$this->listID );
143        $this->getOutput()->redirect( $invitationListPage->getLocalURL() );
144    }
145
146    /**
147     * @param string $rawWorklist
148     * @return array<string,string[]> Maps wiki ID to a list of page titles.
149     */
150    private static function makePageMapFromInput( string $rawWorklist ): array {
151        $pageList = array_filter(
152            array_map( 'trim', explode( "\n", $rawWorklist ) ),
153            static fn ( $line ) => $line !== ''
154        );
155        return [ WikiMap::getCurrentWikiId() => $pageList ];
156    }
157
158    /**
159     * @inheritDoc
160     */
161    protected function getDisplayFormat(): string {
162        return 'ooui';
163    }
164
165    /**
166     * @inheritDoc
167     */
168    protected function getGroupName(): string {
169        return 'campaignevents';
170    }
171
172    /**
173     * @inheritDoc
174     */
175    public function doesWrites(): bool {
176        return true;
177    }
178}