Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 172
0.00% covered (danger)
0.00%
0 / 7
CRAP
0.00% covered (danger)
0.00%
0 / 1
SpecialCreateHubFeature
0.00% covered (danger)
0.00%
0 / 172
0.00% covered (danger)
0.00%
0 / 7
930
0.00% covered (danger)
0.00%
0 / 1
 __construct
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
 execute
0.00% covered (danger)
0.00%
0 / 13
0.00% covered (danger)
0.00%
0 / 1
6
 getFormFields
0.00% covered (danger)
0.00%
0 / 48
0.00% covered (danger)
0.00%
0 / 1
30
 onSubmit
0.00% covered (danger)
0.00%
0 / 107
0.00% covered (danger)
0.00%
0 / 1
380
 onSuccess
0.00% covered (danger)
0.00%
0 / 1
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
1<?php
2
3/**
4 * Form to create features for Collaboration Hubs
5 * Based on code from MassMessage
6 *
7 * @file
8 */
9
10use MediaWiki\MediaWikiServices;
11
12class SpecialCreateHubFeature extends FormSpecialPage {
13
14    public function __construct() {
15        parent::__construct( 'CreateHubFeature' );
16    }
17
18    public function doesWrites() {
19        return true;
20    }
21
22    /**
23     * @param string $par
24     */
25    public function execute( $par ) {
26        if ( !$this->getUser()->isAllowed( 'createpage' ) ) {
27            throw new PermissionsError( 'createpage' );
28        }
29
30        $output = $this->getContext()->getOutput();
31        $output->addModules( 'ext.CollaborationKit.iconbrowser' );
32        $output->addModuleStyles( [
33            'ext.CollaborationKit.createhubfeature.styles',
34            'ext.CollaborationKit.edit.styles'
35        ] );
36        $output->addJsConfigVars(
37            'wgCollaborationKitIconList',
38            CollaborationKitImage::getCannedIcons()
39        );
40        parent::execute( $par );
41    }
42
43    /**
44     * @return array
45     */
46    protected function getFormFields() {
47        // Allow collaboration hub to be passed via parameter (full page title) ?collaborationhub=
48        // Allow feature name to be passed via parameter (subpage title) ?feature=
49        if ( $this->getRequest()->getVal( 'collaborationhub' ) ) {
50            $defaultCollabHub = $this->getRequest()->getVal( 'collaborationhub' );
51        } else {
52            $defaultCollabHub = '';
53        }
54
55        if ( $this->getRequest()->getVal( 'feature' ) ) {
56            $defaultFeatureName = $this->getRequest()->getVal( 'feature' );
57        } else {
58            $defaultFeatureName = '';
59        }
60
61        $icons = CollaborationKitImage::getCannedIcons();
62        $iconChoices = array_combine( $icons, $icons );
63
64        $fields = [
65            'collaborationhub' => [
66                'type' => 'title',
67                'cssclass' => 'mw-ck-fulltitle-input',
68                'label-message' => 'collaborationkit-createhubfeature-collaborationhub',
69                'default' => $defaultCollabHub
70            ],
71            'featurename' => [
72                'type' => 'text',
73                'cssclass' => 'mw-ck-display-input',
74                'label-message' => 'collaborationkit-createhubfeature-featurename',
75                'default' => $defaultFeatureName
76            ],
77            // TODO replace with subclassed image selector
78            'icon' => [
79                'type' => 'combobox',
80                'cssclass' => 'mw-ck-icon-input',
81                'label-message' => 'collaborationkit-createhubfeature-icon',
82                'help-message' => 'collaborationkit-createhubfeature-icon-help',
83                'options' => $iconChoices,
84                'default' => 'circlestar'
85            ],
86            'contenttype' => [
87                'type' => 'radio',
88                'cssclass' => 'mw-ck-content-type-input',
89                'label-message' => 'collaborationkit-createhubfeature-contenttype',
90                'options' => [
91                    $this
92                        ->msg( 'collaborationkit-createhubfeature-freetext' )
93                        ->escaped() => 'wikitext',
94                    $this
95                        ->msg( 'collaborationkit-createhubfeature-articlelist' )
96                        ->escaped() => 'CollaborationListContent'
97                ]
98            ]
99        ];
100
101        // If either of these fields is set, that means the user came to the
102        // special page by way of a special workflow, meaning that the name of
103        // the hub and/or the feature is already known. Changing it would cause
104        // problems (e.g. the hub saying the feature is called one thing and
105        // then the user changes their mind) so we disable further edits in the
106        // middle of the workflow.
107        if ( $defaultCollabHub != '' ) {
108            $fields['collaborationhub']['readonly'] = true;
109        }
110        if ( $defaultFeatureName != '' ) {
111            $fields['featurename']['readonly'] = true;
112        }
113
114        return $fields;
115    }
116
117    /**
118     * The result of submitting the form
119     *
120     * @param array $data
121     * @return Status
122     */
123    public function onSubmit( array $data ) {
124        $collaborationHub = $data['collaborationhub'];
125        $hubTitleObject = Title::newFromText( $collaborationHub );
126        $permissionManager = MediaWiki\MediaWikiServices::getInstance()->getPermissionManager();
127        $user = $this->getUser();
128
129        // This special page can only be used to create subpages of Collaboration
130        // Hubs. This checks if the parent page is one.
131
132        if ( !$hubTitleObject->exists() ) {
133            return Status::newFatal( 'collaborationkit-createhubfeature-hubdoesnotexist' );
134        }
135
136        if ( $hubTitleObject->getContentModel() != 'CollaborationHubContent' ) {
137            return Status::newFatal( 'collaborationkit-createhubfeature-hubisnotahub' );
138        }
139
140        $featureName = $data['featurename'];
141        $titleText = $collaborationHub . '/' . $featureName;
142        $title = Title::newFromText( $titleText );
143        $contentModel = $data['contenttype'];
144
145        if ( !$title ) {
146            return Status::newFatal( 'collaborationkit-createhubfeature-invalidtitle' );
147        } elseif ( $title->exists() ) {
148            return Status::newFatal( 'collaborationkit-createhubfeature-exists' );
149        } elseif (
150            !$permissionManager->userCan( 'edit', $user, $title )
151        ) {
152            return Status::newFatal( 'collaborationkit-createhubfeature-nopermission' );
153        } elseif ( $contentModel == 'CollaborationListContent' &&
154            !$permissionManager->userCan( 'editcontentmodel', $user, $title )
155        ) {
156            return Status::newFatal( 'collaborationkit-createhubfeature-nopermission-contentmodel' );
157        }
158
159        // Create feature
160        if ( $contentModel != 'wikitext'
161            && $contentModel != 'CollaborationListContent' ) {
162            return Status::newFatal( 'collaborationkit-createhubfeature-invalidcontenttype' );
163        }
164
165        if ( $contentModel == 'wikitext' ) {
166            $contentFormat = 'text/x-wiki';
167        } elseif ( $contentModel == 'CollaborationListContent' ) {
168            $contentFormat = 'application/json';
169        } else {
170            return Status::newFatal( 'collaborationkit-createhubfeature-invalidcontenttype' );
171        }
172
173        // Create empty page by default; exception is if there needs to be
174        // something such as JSON.
175        $initialContent = '';
176        if ( $contentModel == 'CollaborationListContent' ) {
177            $contentModelObj = ContentHandler::getForModelID( $contentModel );
178             // ^ Roan recommends renaming $contentModel to $contentModelID
179            $initialContent = $contentModelObj->serializeContent(
180                $contentModelObj->makeEmptyContent()
181            );
182        }
183
184        $summary = $this
185            ->msg( 'collaborationkit-createhubfeature-editsummary' )
186            ->plain();
187
188        $context = $this->getContext();
189        $der = new DerivativeContext( $context );
190        $request = new DerivativeRequest(
191            $context->getRequest(),
192            [
193                'action' => 'edit',
194                'title' => $title->getFullText(),
195                'contentmodel' => $contentModel,
196                'contentformat' => $contentFormat,
197                'text' => $initialContent,
198                'summary' => $summary,
199                'token' => $context->getUser()->getEditToken(),
200            ],
201            true // Treat data as POSTed
202        );
203        $der->setRequest( $request );
204        try {
205            $api = new ApiMain( $der, true );
206            $api->execute();
207        } catch ( ApiUsageException $e ) {
208            return Status::newFatal(
209                $context->msg(
210                    'collaborationkit-hub-edit-apierror',
211                    $e->getMessageObject()
212                )
213            );
214        }
215
216        // Update hub with link to new feature
217        $newFeature = [ 'title' => $titleText, 'display_title' => $featureName ];
218
219        if ( $data[ 'icon' ] ) {
220            $newFeature['image'] = $data[ 'icon' ];
221        }
222
223        $hubWikiPageObject = MediaWikiServices::getInstance()->getWikiPageFactory()->newFromTitle( $hubTitleObject );
224        $hubRawContent = $hubWikiPageObject->getContent();
225        $hubContent = json_decode( $hubRawContent->serialize(), true );
226
227        // Don't actually update the hub if the hub includes the feature.
228        $found = false;
229        // @phan-suppress-next-line PhanTypeArraySuspiciousNullable
230        foreach ( $hubContent['content'] as $c ) {
231            if ( $c['title'] === $titleText ) {
232                $found = true;
233                break;
234            }
235        }
236
237        if ( !$found ) {
238            $hubContent['content'][] = $newFeature;
239            $newHubRawContent = json_encode( $hubContent );
240            $editHubSummary = $this
241                ->msg( 'collaborationkit-createhubfeature-hubeditsummary', $featureName )
242                ->plain();
243
244            $context = $this->getContext();
245            $der = new DerivativeContext( $context );
246            $request = new DerivativeRequest(
247                $context->getRequest(),
248                [
249                    'action' => 'edit',
250                    'title' => $hubTitleObject->getFullText(),
251                    'contentmodel' => 'CollaborationHubContent',
252                    'text' => $newHubRawContent,
253                    'summary' => $editHubSummary,
254                    'token' => $context->getUser()->getEditToken(),
255                ],
256                true // Treat data as POSTed
257            );
258            $der->setRequest( $request );
259            try {
260                $api = new ApiMain( $der, true );
261                $api->execute();
262            } catch ( ApiUsageException $e ) {
263                return Status::newFatal(
264                    $context->msg(
265                        'collaborationkit-hub-edit-apierror',
266                        $e->getMessageObject()
267                    )
268                );
269            }
270        }
271
272        // Purge the hub's cache so that it doesn't say "feature does not exist"
273        $hubTitleObject->invalidateCache();
274
275        $this->getOutput()->redirect( $title->getFullURL() );
276
277        return Status::newGood();
278    }
279
280    public function onSuccess() {
281        // No-op: We have already redirected.
282    }
283
284    /** @inheritDoc */
285    protected function getDisplayFormat() {
286        return 'ooui';
287    }
288}