includes/Specials/SpecialUploadWizard.php
<?php
namespace MediaWiki\Extension\UploadWizard\Specials;
use BitmapHandler;
use ChangeTags;
use DerivativeContext;
use LogicException;
use MediaWiki\Extension\UploadWizard\Campaign;
use MediaWiki\Extension\UploadWizard\Config;
use MediaWiki\Extension\UploadWizard\Hooks;
use MediaWiki\Extension\UploadWizard\Tutorial;
use MediaWiki\Html\Html;
use MediaWiki\Request\WebRequest;
use MediaWiki\SpecialPage\SpecialPage;
use MediaWiki\Title\Title;
use MediaWiki\User\Options\UserOptionsLookup;
use MediaWiki\User\User;
use PermissionsError;
use UploadBase;
use UploadFromUrl;
use UserBlockedError;
/**
* Special:UploadWizard
*
* Easy to use multi-file upload page.
*
* @file
* @ingroup SpecialPage
* @ingroup Upload
*/
class SpecialUploadWizard extends SpecialPage {
/**
* The name of the upload wizard campaign, or null when none is specified.
*
* @since 1.2
* @var string|null
*/
protected $campaign = null;
/** @var UserOptionsLookup */
private $userOptionsLookup;
/**
* @param UserOptionsLookup $userOptionsLookup
* @param WebRequest|null $request the request (usually wgRequest)
* @param string|null $par everything in the URL after Special:UploadWizard.
* Not sure what we can use it for
*/
public function __construct( UserOptionsLookup $userOptionsLookup, $request = null, $par = null ) {
$this->userOptionsLookup = $userOptionsLookup;
parent::__construct( 'UploadWizard', 'upload' );
}
/**
* Replaces default execute method
* Checks whether uploading enabled, user permissions okay,
* @param string|null $subPage subpage, e.g. the "foo" in Special:UploadWizard/foo.
*/
public function execute( $subPage ) {
// side effects: if we can't upload, will print error page to wgOut
// and return false
if ( !( $this->isUploadAllowed() && $this->isUserUploadAllowed( $this->getUser() ) ) ) {
return;
}
$this->setHeaders();
$this->outputHeader();
$req = $this->getRequest();
$urlArgs = [ 'caption', 'description', 'lat', 'lon', 'alt' ];
$urlDefaults = [];
foreach ( $urlArgs as $arg ) {
$value = $req->getText( $arg );
if ( $value ) {
$urlDefaults[$arg] = $value;
}
}
$categories = $req->getText( 'categories' );
if ( $categories ) {
$urlDefaults['categories'] = explode( '|', $categories );
}
$urlDefaults['objref'] = $req->getText( 'objref' ) ?: '';
$urlDefaults['updateList'] = $req->getText( 'updateList' ) ?: '';
Config::setUrlSetting( 'defaults', $urlDefaults );
$fields = $req->getArray( 'fields' );
$fieldDefaults = [];
# Support id and id2 for field0 and field1
# Legacy support for old URL structure. They override fields[]
if ( $req->getText( 'id' ) ) {
$fields[0] = $req->getText( 'id' );
}
if ( $req->getText( 'id2' ) ) {
$fields[1] = $req->getText( 'id2' );
}
if ( $fields ) {
foreach ( $fields as $index => $value ) {
$fieldDefaults[$index]['initialValue'] = $value;
}
}
Config::setUrlSetting( 'fields', $fieldDefaults );
$this->handleCampaign();
$out = $this->getOutput();
// fallback for non-JS
$out->addHTML( '<div class="mwe-upwiz-unavailable">' );
$out->addHTML(
Html::errorBox(
$this->msg( 'mwe-upwiz-unavailable' )->parse()
)
);
// create a simple form for non-JS fallback, which targets the old Special:Upload page.
// at some point, if we completely subsume its functionality, change that to point here again,
// but then we'll need to process non-JS uploads in the same way Special:Upload does.
$derivativeContext = new DerivativeContext( $this->getContext() );
$derivativeContext->setTitle( SpecialPage::getTitleFor( 'Upload' ) );
$simpleForm = new UploadWizardSimpleForm( [], $derivativeContext, $this->getLinkRenderer() );
$simpleForm->show();
$out->addHTML( '</div>' );
// global javascript variables
$this->addJsVars( $subPage );
// dependencies (css, js)
$out->addModules( 'ext.uploadWizard.page' );
// load spinner styles early
$out->addModuleStyles( [ 'ext.uploadWizard.page.styles', 'jquery.spinner.styles' ] );
// where the uploadwizard will go
// TODO import more from UploadWizard's createInterface call.
$out->addHTML( $this->getWizardHtml() );
}
/**
* Handles the campaign parameter.
*
* @since 1.2
*/
protected function handleCampaign() {
$campaignName = $this->getRequest()->getVal( 'campaign' );
if ( $campaignName === null ) {
$campaignName = Config::getSetting( 'defaultCampaign' );
}
if ( $campaignName !== null && $campaignName !== '' ) {
$campaign = Campaign::newFromName( $campaignName );
if ( $campaign === false ) {
$this->displayError( $this->msg( 'mwe-upwiz-error-nosuchcampaign', $campaignName )->text() );
} elseif ( $campaign->getIsEnabled() ) {
$this->campaign = $campaignName;
} else {
$this->displayError( $this->msg( 'mwe-upwiz-error-campaigndisabled', $campaignName )->text() );
}
}
}
/**
* Display an error message.
*
* @since 1.2
*
* @param string $message
*/
protected function displayError( $message ) {
$this->getOutput()->addHTML(
Html::errorBox(
$message
)
);
}
/**
* Adds some global variables for our use, as well as initializes the UploadWizard
*
* TODO once bug https://bugzilla.wikimedia.org/show_bug.cgi?id=26901
* is fixed we should package configuration with the upload wizard instead of
* in uploadWizard output page.
*
* @param string $subPage subpage, e.g. the "foo" in Special:UploadWizard/foo
*/
public function addJsVars( $subPage ) {
$config = Config::getConfig( $this->campaign );
if ( array_key_exists( 'trackingCategory', $config ) ) {
if ( array_key_exists( 'campaign', $config['trackingCategory'] ) ) {
if ( $this->campaign !== null ) {
$config['trackingCategory']['campaign'] = str_replace(
'$1',
$this->campaign,
$config['trackingCategory']['campaign']
);
} else {
unset( $config['trackingCategory']['campaign'] );
}
}
}
// UploadFromUrl parameter set to true only if the user is allowed to upload a file
// from a URL which we need to check in our Javascript implementation.
if ( UploadFromUrl::isEnabled() && UploadFromUrl::isAllowed( $this->getUser() ) === true ) {
$config['UploadFromUrl'] = true;
} else {
$config['UploadFromUrl'] = false;
}
// Get the user's default license. This will usually be 'default', but
// can be a specific license like 'ownwork-cc-zero'.
$userDefaultLicense = $this->userOptionsLookup->getOption( $this->getUser(), 'upwiz_deflicense' );
if ( $userDefaultLicense !== 'default' ) {
[ $userLicenseType, $userDefaultLicense ] = explode( '-', $userDefaultLicense, 2 );
// Determine if the user's default license is valid for this campaign
switch ( $config['licensing']['ownWorkDefault'] ) {
case "own":
$defaultInAllowedLicenses = in_array(
$userDefaultLicense, $config['licensing']['ownWork']['licenses']
);
break;
case "notown":
$defaultInAllowedLicenses = in_array(
$userDefaultLicense, Config::getThirdPartyLicenses()
);
break;
case "choice":
$defaultInAllowedLicenses = ( in_array(
$userDefaultLicense, $config['licensing']['ownWork']['licenses']
) ||
in_array( $userDefaultLicense, Config::getThirdPartyLicenses() ) );
break;
default:
throw new LogicException( 'Bad ownWorkDefault config' );
}
if ( $defaultInAllowedLicenses ) {
if ( $userLicenseType === 'ownwork' ) {
$userLicenseGroup = 'ownWork';
} else {
$userLicenseGroup = 'thirdParty';
}
$config['licensing'][$userLicenseGroup]['defaults'] = [ $userDefaultLicense ];
$config['licensing']['defaultType'] = $userLicenseType;
if ( $userDefaultLicense === 'custom' ) {
$config['licenses']['custom']['defaultText'] =
$this->userOptionsLookup->getOption( $this->getUser(), 'upwiz_deflicense_custom' );
}
}
}
// add an 'uploadwizard' tag, but only if it'll be allowed
Hooks::addListDefinedTags( $tags );
$status = ChangeTags::canAddTagsAccompanyingChange( $tags, $this->getUser() );
$config['CanAddTags'] = $status->isOK();
// Upload comment should be localized with respect to the wiki's language
$config['uploadComment'] = [
'ownWork' => $this->msg( 'mwe-upwiz-upload-comment-own-work' )
->inContentLanguage()->plain(),
'thirdParty' => $this->msg( 'mwe-upwiz-upload-comment-third-party' )
->inContentLanguage()->plain()
];
$bitmapHandler = new BitmapHandler();
$this->getOutput()->addJsConfigVars(
[
'UploadWizardConfig' => $config,
'wgFileCanRotate' => $bitmapHandler->canRotate(),
]
);
}
/**
* Check if anyone can upload (or if other sitewide config prevents this)
* Side effect: will print error page to wgOut if cannot upload.
* @return bool true if can upload
*/
private function isUploadAllowed() {
// Check uploading enabled
if ( !UploadBase::isEnabled() ) {
$this->getOutput()->showErrorPage( 'uploaddisabled', 'uploaddisabledtext' );
return false;
}
// Check whether we actually want to allow changing stuff
$this->checkReadOnly();
// we got all the way here, so it must be okay to upload
return true;
}
/**
* Check if the user can upload
* Side effect: will print error page to wgOut if cannot upload.
* @param User $user
* @throws PermissionsError
* @throws UserBlockedError
* @return bool true if can upload
*/
private function isUserUploadAllowed( User $user ) {
// Check permissions
$permissionRequired = UploadBase::isAllowed( $user );
if ( $permissionRequired !== true ) {
throw new PermissionsError( $permissionRequired );
}
// Check blocks
if ( $user->isBlockedFromUpload() ) {
// If the user is blocked from uploading then there is a block
// @phan-suppress-next-line PhanTypeMismatchArgumentNullable
throw new UserBlockedError( $user->getBlock() );
}
// we got all the way here, so it must be okay to upload
return true;
}
/**
* Return the basic HTML structure for the entire page
* Will be enhanced by the javascript to actually do stuff
* @return string html
*/
protected function getWizardHtml() {
$config = Config::getConfig( $this->campaign );
if ( array_key_exists(
'display', $config ) && array_key_exists( 'headerLabel', $config['display'] )
) {
$this->getOutput()->addHTML( $config['display']['headerLabel'] );
}
if ( array_key_exists( 'fallbackToAltUploadForm', $config )
&& array_key_exists( 'altUploadForm', $config )
&& $config['altUploadForm'] != ''
&& $config[ 'fallbackToAltUploadForm' ]
) {
$linkHtml = '';
$altUploadForm = Title::newFromText( $config[ 'altUploadForm' ] );
if ( $altUploadForm instanceof Title ) {
$linkHtml = Html::rawElement( 'p', [ 'style' => 'text-align: center;' ],
Html::element( 'a', [ 'href' => $altUploadForm->getLocalURL() ],
$config['altUploadForm']
)
);
}
return Html::rawElement(
'div',
[],
Html::element(
'p',
[ 'style' => 'text-align: center' ],
$this->msg( 'mwe-upwiz-extension-disabled' )->text()
) . $linkHtml
);
}
// always load the html: even if the tutorial is skipped, users can
// still move back to view it
$tutorialHtml = Tutorial::getHtml( $this->campaign );
// TODO move this into UploadWizard.js or some other javascript resource so the upload wizard
// can be dynamically included ( for example the add media wizard )
return '<div id="upload-wizard" class="upload-section">' .
'<div id="mwe-upwiz-tutorial-html" style="display:none;">' .
$tutorialHtml .
'</div>' .
'<div class="mwe-first-spinner">' .
new \MediaWiki\Widget\SpinnerWidget( [ 'size' => 'large' ] ) .
'</div>' .
'</div>';
}
/**
* @inheritDoc
*/
protected function getGroupName() {
return 'media';
}
}