Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
3.01% covered (danger)
3.01%
5 / 166
0.00% covered (danger)
0.00%
0 / 9
CRAP
0.00% covered (danger)
0.00%
0 / 1
SpecialUploadWizard
3.01% covered (danger)
3.01%
5 / 166
0.00% covered (danger)
0.00%
0 / 9
2239.51
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 execute
0.00% covered (danger)
0.00%
0 / 44
0.00% covered (danger)
0.00%
0 / 1
156
 handleCampaign
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
42
 displayError
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
2
 addJsVars
0.00% covered (danger)
0.00%
0 / 59
0.00% covered (danger)
0.00%
0 / 1
240
 isUploadAllowed
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
6
 isUserUploadAllowed
83.33% covered (warning)
83.33%
5 / 6
0.00% covered (danger)
0.00%
0 / 1
3.04
 getWizardHtml
0.00% covered (danger)
0.00%
0 / 34
0.00% covered (danger)
0.00%
0 / 1
72
 getGroupName
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2
3namespace MediaWiki\Extension\UploadWizard\Specials;
4
5use BitmapHandler;
6use ChangeTags;
7use DerivativeContext;
8use LogicException;
9use MediaWiki\Extension\UploadWizard\Campaign;
10use MediaWiki\Extension\UploadWizard\Config;
11use MediaWiki\Extension\UploadWizard\Hooks;
12use MediaWiki\Extension\UploadWizard\Tutorial;
13use MediaWiki\Html\Html;
14use MediaWiki\Request\WebRequest;
15use MediaWiki\SpecialPage\SpecialPage;
16use MediaWiki\Title\Title;
17use MediaWiki\User\Options\UserOptionsLookup;
18use MediaWiki\User\User;
19use PermissionsError;
20use UploadBase;
21use UploadFromUrl;
22use UserBlockedError;
23
24/**
25 * Special:UploadWizard
26 *
27 * Easy to use multi-file upload page.
28 *
29 * @file
30 * @ingroup SpecialPage
31 * @ingroup Upload
32 */
33
34class SpecialUploadWizard extends SpecialPage {
35    /**
36     * The name of the upload wizard campaign, or null when none is specified.
37     *
38     * @since 1.2
39     * @var string|null
40     */
41    protected $campaign = null;
42
43    /** @var UserOptionsLookup */
44    private $userOptionsLookup;
45
46    /**
47     * @param UserOptionsLookup $userOptionsLookup
48     * @param WebRequest|null $request the request (usually wgRequest)
49     * @param string|null $par everything in the URL after Special:UploadWizard.
50     *   Not sure what we can use it for
51     */
52    public function __construct( UserOptionsLookup $userOptionsLookup, $request = null, $par = null ) {
53        $this->userOptionsLookup = $userOptionsLookup;
54        parent::__construct( 'UploadWizard', 'upload' );
55    }
56
57    /**
58     * Replaces default execute method
59     * Checks whether uploading enabled, user permissions okay,
60     * @param string|null $subPage subpage, e.g. the "foo" in Special:UploadWizard/foo.
61     */
62    public function execute( $subPage ) {
63        // side effects: if we can't upload, will print error page to wgOut
64        // and return false
65        if ( !( $this->isUploadAllowed() && $this->isUserUploadAllowed( $this->getUser() ) ) ) {
66            return;
67        }
68
69        $this->setHeaders();
70        $this->outputHeader();
71
72        $req = $this->getRequest();
73
74        $urlArgs = [ 'caption', 'description', 'lat', 'lon', 'alt' ];
75
76        $urlDefaults = [];
77        foreach ( $urlArgs as $arg ) {
78            $value = $req->getText( $arg );
79            if ( $value ) {
80                $urlDefaults[$arg] = $value;
81            }
82        }
83
84        $categories = $req->getText( 'categories' );
85        if ( $categories ) {
86            $urlDefaults['categories'] = explode( '|', $categories );
87        }
88
89        $urlDefaults['objref'] = $req->getText( 'objref' ) ?: '';
90        $urlDefaults['updateList'] = $req->getText( 'updateList' ) ?: '';
91
92        Config::setUrlSetting( 'defaults', $urlDefaults );
93
94        $fields = $req->getArray( 'fields' );
95        $fieldDefaults = [];
96
97        # Support id and id2 for field0 and field1
98        # Legacy support for old URL structure. They override fields[]
99        if ( $req->getText( 'id' ) ) {
100            $fields[0] = $req->getText( 'id' );
101        }
102
103        if ( $req->getText( 'id2' ) ) {
104            $fields[1] = $req->getText( 'id2' );
105        }
106
107        if ( $fields ) {
108            foreach ( $fields as $index => $value ) {
109                $fieldDefaults[$index]['initialValue'] = $value;
110            }
111        }
112
113        Config::setUrlSetting( 'fields', $fieldDefaults );
114
115        $this->handleCampaign();
116
117        $out = $this->getOutput();
118
119        // fallback for non-JS
120        $out->addHTML( '<div class="mwe-upwiz-unavailable">' );
121        $out->addHTML(
122            Html::errorBox(
123                $this->msg( 'mwe-upwiz-unavailable' )->parse()
124            )
125        );
126        // create a simple form for non-JS fallback, which targets the old Special:Upload page.
127        // at some point, if we completely subsume its functionality, change that to point here again,
128        // but then we'll need to process non-JS uploads in the same way Special:Upload does.
129        $derivativeContext = new DerivativeContext( $this->getContext() );
130        $derivativeContext->setTitle( SpecialPage::getTitleFor( 'Upload' ) );
131        $simpleForm = new UploadWizardSimpleForm( [], $derivativeContext, $this->getLinkRenderer() );
132        $simpleForm->show();
133        $out->addHTML( '</div>' );
134
135        // global javascript variables
136        $this->addJsVars( $subPage );
137
138        // dependencies (css, js)
139        $out->addModules( 'ext.uploadWizard.page' );
140        // load spinner styles early
141        $out->addModuleStyles( [ 'ext.uploadWizard.page.styles', 'jquery.spinner.styles' ] );
142
143        // where the uploadwizard will go
144        // TODO import more from UploadWizard's createInterface call.
145        $out->addHTML( $this->getWizardHtml() );
146    }
147
148    /**
149     * Handles the campaign parameter.
150     *
151     * @since 1.2
152     */
153    protected function handleCampaign() {
154        $campaignName = $this->getRequest()->getVal( 'campaign' );
155        if ( $campaignName === null ) {
156            $campaignName = Config::getSetting( 'defaultCampaign' );
157        }
158
159        if ( $campaignName !== null && $campaignName !== '' ) {
160            $campaign = Campaign::newFromName( $campaignName );
161
162            if ( $campaign === false ) {
163                $this->displayError( $this->msg( 'mwe-upwiz-error-nosuchcampaign', $campaignName )->text() );
164            } elseif ( $campaign->getIsEnabled() ) {
165                $this->campaign = $campaignName;
166            } else {
167                $this->displayError( $this->msg( 'mwe-upwiz-error-campaigndisabled', $campaignName )->text() );
168            }
169        }
170    }
171
172    /**
173     * Display an error message.
174     *
175     * @since 1.2
176     *
177     * @param string $message
178     */
179    protected function displayError( $message ) {
180        $this->getOutput()->addHTML(
181            Html::errorBox(
182                $message
183            )
184        );
185    }
186
187    /**
188     * Adds some global variables for our use, as well as initializes the UploadWizard
189     *
190     * TODO once bug https://bugzilla.wikimedia.org/show_bug.cgi?id=26901
191     * is fixed we should package configuration with the upload wizard instead of
192     * in uploadWizard output page.
193     *
194     * @param string $subPage subpage, e.g. the "foo" in Special:UploadWizard/foo
195     */
196    public function addJsVars( $subPage ) {
197        $config = Config::getConfig( $this->campaign );
198
199        if ( array_key_exists( 'trackingCategory', $config ) ) {
200            if ( array_key_exists( 'campaign', $config['trackingCategory'] ) ) {
201                if ( $this->campaign !== null ) {
202                    $config['trackingCategory']['campaign'] = str_replace(
203                        '$1',
204                        $this->campaign,
205                        $config['trackingCategory']['campaign']
206                    );
207                } else {
208                    unset( $config['trackingCategory']['campaign'] );
209                }
210            }
211        }
212        // UploadFromUrl parameter set to true only if the user is allowed to upload a file
213        // from a URL which we need to check in our Javascript implementation.
214        if ( UploadFromUrl::isEnabled() && UploadFromUrl::isAllowed( $this->getUser() ) === true ) {
215            $config['UploadFromUrl'] = true;
216        } else {
217            $config['UploadFromUrl'] = false;
218        }
219
220        // Get the user's default license. This will usually be 'default', but
221        // can be a specific license like 'ownwork-cc-zero'.
222        $userDefaultLicense = $this->userOptionsLookup->getOption( $this->getUser(), 'upwiz_deflicense' );
223
224        if ( $userDefaultLicense !== 'default' ) {
225            [ $userLicenseType, $userDefaultLicense ] = explode( '-', $userDefaultLicense, 2 );
226
227            // Determine if the user's default license is valid for this campaign
228            switch ( $config['licensing']['ownWorkDefault'] ) {
229                case "own":
230                    $defaultInAllowedLicenses = in_array(
231                        $userDefaultLicense, $config['licensing']['ownWork']['licenses']
232                    );
233                    break;
234                case "notown":
235                    $defaultInAllowedLicenses = in_array(
236                        $userDefaultLicense, Config::getThirdPartyLicenses()
237                    );
238                    break;
239                case "choice":
240                    $defaultInAllowedLicenses = ( in_array(
241                            $userDefaultLicense, $config['licensing']['ownWork']['licenses']
242                        ) ||
243                        in_array( $userDefaultLicense, Config::getThirdPartyLicenses() ) );
244                    break;
245                default:
246                    throw new LogicException( 'Bad ownWorkDefault config' );
247            }
248
249            if ( $defaultInAllowedLicenses ) {
250                if ( $userLicenseType === 'ownwork' ) {
251                    $userLicenseGroup = 'ownWork';
252                } else {
253                    $userLicenseGroup = 'thirdParty';
254                }
255                $config['licensing'][$userLicenseGroup]['defaults'] = [ $userDefaultLicense ];
256                $config['licensing']['defaultType'] = $userLicenseType;
257
258                if ( $userDefaultLicense === 'custom' ) {
259                    $config['licenses']['custom']['defaultText'] =
260                        $this->userOptionsLookup->getOption( $this->getUser(), 'upwiz_deflicense_custom' );
261                }
262            }
263        }
264
265        // add an 'uploadwizard' tag, but only if it'll be allowed
266        Hooks::addListDefinedTags( $tags );
267        $status = ChangeTags::canAddTagsAccompanyingChange( $tags, $this->getUser() );
268        $config['CanAddTags'] = $status->isOK();
269
270        // Upload comment should be localized with respect to the wiki's language
271        $config['uploadComment'] = [
272            'ownWork' => $this->msg( 'mwe-upwiz-upload-comment-own-work' )
273                ->inContentLanguage()->plain(),
274            'thirdParty' => $this->msg( 'mwe-upwiz-upload-comment-third-party' )
275                ->inContentLanguage()->plain()
276        ];
277
278        $bitmapHandler = new BitmapHandler();
279        $this->getOutput()->addJsConfigVars(
280            [
281                'UploadWizardConfig' => $config,
282                'wgFileCanRotate' => $bitmapHandler->canRotate(),
283            ]
284        );
285    }
286
287    /**
288     * Check if anyone can upload (or if other sitewide config prevents this)
289     * Side effect: will print error page to wgOut if cannot upload.
290     * @return bool true if can upload
291     */
292    private function isUploadAllowed() {
293        // Check uploading enabled
294        if ( !UploadBase::isEnabled() ) {
295            $this->getOutput()->showErrorPage( 'uploaddisabled', 'uploaddisabledtext' );
296            return false;
297        }
298
299        // Check whether we actually want to allow changing stuff
300        $this->checkReadOnly();
301
302        // we got all the way here, so it must be okay to upload
303        return true;
304    }
305
306    /**
307     * Check if the user can upload
308     * Side effect: will print error page to wgOut if cannot upload.
309     * @param User $user
310     * @throws PermissionsError
311     * @throws UserBlockedError
312     * @return bool true if can upload
313     */
314    private function isUserUploadAllowed( User $user ) {
315        // Check permissions
316        $permissionRequired = UploadBase::isAllowed( $user );
317        if ( $permissionRequired !== true ) {
318            throw new PermissionsError( $permissionRequired );
319        }
320
321        // Check blocks
322        if ( $user->isBlockedFromUpload() ) {
323            // If the user is blocked from uploading then there is a block
324            // @phan-suppress-next-line PhanTypeMismatchArgumentNullable
325            throw new UserBlockedError( $user->getBlock() );
326        }
327
328        // we got all the way here, so it must be okay to upload
329        return true;
330    }
331
332    /**
333     * Return the basic HTML structure for the entire page
334     * Will be enhanced by the javascript to actually do stuff
335     * @return string html
336     */
337    protected function getWizardHtml() {
338        $config = Config::getConfig( $this->campaign );
339
340        if ( array_key_exists(
341            'display', $config ) && array_key_exists( 'headerLabel', $config['display'] )
342        ) {
343            $this->getOutput()->addHTML( $config['display']['headerLabel'] );
344        }
345
346        if ( array_key_exists( 'fallbackToAltUploadForm', $config )
347            && array_key_exists( 'altUploadForm', $config )
348            && $config['altUploadForm'] != ''
349            && $config[ 'fallbackToAltUploadForm' ]
350        ) {
351            $linkHtml = '';
352            $altUploadForm = Title::newFromText( $config[ 'altUploadForm' ] );
353            if ( $altUploadForm instanceof Title ) {
354                $linkHtml = Html::rawElement( 'p', [ 'style' => 'text-align: center;' ],
355                    Html::element( 'a', [ 'href' => $altUploadForm->getLocalURL() ],
356                        $config['altUploadForm']
357                    )
358                );
359            }
360
361            return Html::rawElement(
362                'div',
363                [],
364                Html::element(
365                    'p',
366                    [ 'style' => 'text-align: center' ],
367                    $this->msg( 'mwe-upwiz-extension-disabled' )->text()
368                ) . $linkHtml
369            );
370        }
371
372        // always load the html: even if the tutorial is skipped, users can
373        // still move back to view it
374        $tutorialHtml = Tutorial::getHtml( $this->campaign );
375
376        // TODO move this into UploadWizard.js or some other javascript resource so the upload wizard
377        // can be dynamically included ( for example the add media wizard )
378        return '<div id="upload-wizard" class="upload-section">' .
379            '<div id="mwe-upwiz-tutorial-html" style="display:none;">' .
380                $tutorialHtml .
381            '</div>' .
382            '<div class="mwe-first-spinner">' .
383                new \MediaWiki\Widget\SpinnerWidget( [ 'size' => 'large' ] ) .
384            '</div>' .
385        '</div>';
386    }
387
388    /**
389     * @inheritDoc
390     */
391    protected function getGroupName() {
392        return 'media';
393    }
394}