<exclude name="MediaWiki.WhiteSpace.SpaceBeforeSingleLineComment.SingleSpaceBeforeSingleLineComment" />
<exclude name="MediaWiki.Usage.DbrQueryUsage.DbrQueryFound" />
<exclude name="MediaWiki.Usage.ExtendClassUsage.FunctionVarUsage" />
- <exclude name="MediaWiki.Usage.SuperGlobalsUsage.SuperGlobals" />
<exclude name="MediaWiki.Files.ClassMatchesFilename.WrongCase" />
<exclude name="MediaWiki.Files.ClassMatchesFilename.NotMatch" />
- <exclude name="Generic.Files.OneObjectStructurePerFile.MultipleFound" />
<exclude name="MediaWiki.VariableAnalysis.ForbiddenGlobalVariables.ForbiddenGlobal$wgTitle" />
- <exclude name="MediaWiki.Commenting.FunctionComment.SpacingDocStar" />
- <exclude name="MediaWiki.Commenting.FunctionComment.SpacingDocTag" />
<exclude name="Squiz.Scope.MethodScope.Missing" />
<exclude name="Squiz.Scope.MemberVarScope.Missing" />
<exclude name="MediaWiki.Commenting.MissingCovers.MissingCovers" />
- <exclude name="MediaWiki.Usage.AssignmentInReturn.AssignmentInReturn" />
</rule>
<rule ref="MediaWiki.NamingConventions.PrefixedGlobalFunctions">
<properties>
<rule ref="Generic.Files.LineLength">
<exclude-pattern>*/languages/messages/Messages*\.php</exclude-pattern>
</rule>
+ <rule ref="Generic.Files.OneObjectStructurePerFile.MultipleFound">
+ <!--
+ Whitelist existing violations, but enable the sniff to prevent
+ any new occurrences.
+ -->
+ <exclude-pattern>*/includes/actions/HistoryAction\.php</exclude-pattern>
+ <exclude-pattern>*/includes/api/ApiErrorFormatter\.php</exclude-pattern>
+ <exclude-pattern>*/includes/api/ApiImport\.php</exclude-pattern>
+ <exclude-pattern>*/includes/api/ApiMessage\.php</exclude-pattern>
+ <exclude-pattern>*/includes/api/ApiOpenSearch\.php</exclude-pattern>
+ <exclude-pattern>*/includes/api/ApiRsd\.php</exclude-pattern>
+ <exclude-pattern>*/includes/api/ApiUsageException\.php</exclude-pattern>
+ <exclude-pattern>*/includes/auth/AuthManagerAuthPlugin\.php</exclude-pattern>
+ <exclude-pattern>*/includes/AuthPlugin\.php</exclude-pattern>
+ <exclude-pattern>*/includes/cache/CacheDependency\.php</exclude-pattern>
+ <exclude-pattern>*/includes/cache/CacheHelper\.php</exclude-pattern>
+ <exclude-pattern>*/includes/deferred/CdnCacheUpdate\.php</exclude-pattern>
+ <exclude-pattern>*/includes/diff/DairikiDiff\.php</exclude-pattern>
+ <exclude-pattern>*/includes/diff/DiffEngine\.php</exclude-pattern>
+ <exclude-pattern>*/includes/exception/LocalizedException\.php</exclude-pattern>
+ <exclude-pattern>*/includes/Feed\.php</exclude-pattern>
+ <exclude-pattern>*/includes/filerepo/file/LocalFile\.php</exclude-pattern>
+ <exclude-pattern>*/includes/gallery/PackedOverlayImageGallery\.php</exclude-pattern>
+ <exclude-pattern>*/includes/HistoryBlob\.php</exclude-pattern>
+ <exclude-pattern>*/includes/htmlform/HTMLFormElement\.php</exclude-pattern>
+ <exclude-pattern>*/includes/jobqueue/aggregator/JobQueueAggregator\.php</exclude-pattern>
+ <exclude-pattern>*/includes/jobqueue/JobQueue\.php</exclude-pattern>
+ <exclude-pattern>*/includes/jobqueue/JobSpecification\.php</exclude-pattern>
+ <exclude-pattern>*/includes/libs/filebackend/FileBackendStore\.php</exclude-pattern>
+ <exclude-pattern>*/includes/libs/filebackend/FSFileBackend\.php</exclude-pattern>
+ <exclude-pattern>*/includes/libs/filebackend/SwiftFileBackend\.php</exclude-pattern>
+ <exclude-pattern>*/includes/logging/LogEntry\.php</exclude-pattern>
+ <exclude-pattern>*/includes/logging/LogFormatter\.php</exclude-pattern>
+ <exclude-pattern>*/includes/media/MediaTransformOutput\.php</exclude-pattern>
+ <exclude-pattern>*/includes/media/SVGMetadataExtractor\.php</exclude-pattern>
+ <exclude-pattern>*/includes/parser/Preprocessor_DOM\.php</exclude-pattern>
+ <exclude-pattern>*/includes/parser/Preprocessor_Hash\.php</exclude-pattern>
+ <exclude-pattern>*/includes/parser/Preprocessor\.php</exclude-pattern>
+ <exclude-pattern>*/includes/PathRouter\.php</exclude-pattern>
+ <exclude-pattern>*/includes/poolcounter/PoolCounter\.php</exclude-pattern>
+ <exclude-pattern>*/includes/PrefixSearch\.php</exclude-pattern>
+ <exclude-pattern>*/includes/profiler/SectionProfiler\.php</exclude-pattern>
+ <exclude-pattern>*/includes/RevisionList\.php</exclude-pattern>
+ <exclude-pattern>*/includes/search/SearchEngine\.php</exclude-pattern>
+ <exclude-pattern>*/includes/specialpage/LoginSignupSpecialPage\.php</exclude-pattern>
+ <exclude-pattern>*/includes/specialpage/RedirectSpecialPage\.php</exclude-pattern>
+ <exclude-pattern>*/includes/specials/SpecialListusers\.php</exclude-pattern>
+ <exclude-pattern>*/includes/specials/SpecialMyRedirectPages\.php</exclude-pattern>
+ <exclude-pattern>*/includes/specials/SpecialUploadStash\.php</exclude-pattern>
+ <exclude-pattern>*/includes/StubObject\.php</exclude-pattern>
+ <exclude-pattern>*/includes/upload/UploadFromChunks\.php</exclude-pattern>
+ <exclude-pattern>*/includes/upload/UploadStash\.php</exclude-pattern>
+ <exclude-pattern>*/includes/utils/AutoloadGenerator\.php</exclude-pattern>
+ <exclude-pattern>*/includes/WebResponse\.php</exclude-pattern>
+ <exclude-pattern>*/maintenance/dumpIterator\.php</exclude-pattern>
+ <exclude-pattern>*/maintenance/Maintenance\.php</exclude-pattern>
+ <exclude-pattern>*/maintenance/findDeprecated\.php</exclude-pattern>
+ <exclude-pattern>*/maintenance/storage/recompressTracked\.php</exclude-pattern>
+ <exclude-pattern>*/maintenance/preprocessorFuzzTest\.php</exclude-pattern>
+ <exclude-pattern>*/maintenance/language/languages.inc</exclude-pattern>
+ <exclude-pattern>*/maintenance/language/StatOutputs\.php</exclude-pattern>
+ <exclude-pattern>*/maintenance/language/checkLanguage.inc</exclude-pattern>
+ <exclude-pattern>*/maintenance/language/generateCollationData\.php</exclude-pattern>
+ <exclude-pattern>*/maintenance/term/MWTerm\.php</exclude-pattern>
+ <!-- Language converters use the pattern of 2 classes in one file -->
+ <exclude-pattern>*/languages/*\.php</exclude-pattern>
+ <!-- We don't care that much about violations in tests -->
+ <exclude-pattern>*/tests/*\.php</exclude-pattern>
+ </rule>
<rule ref="PSR2.Methods.MethodDeclaration.Underscore">
<exclude-pattern>*/includes/StubObject\.php</exclude-pattern>
</rule>
+ <rule ref="MediaWiki.Usage.AssignmentInReturn.AssignmentInReturn">
+ <exclude-pattern>*/tests/phpunit/*\.php</exclude-pattern>
+ </rule>
<file>.</file>
<arg name="encoding" value="UTF-8"/>
<arg name="extensions" value="php,php5,inc,sample"/>
use MediaWiki\MediaWikiServices;
+// Use superglobals, but since it's deprecated, it's not worth fixing
+// phpcs:disable MediaWiki.Usage.SuperGlobalsUsage.SuperGlobals
+
/**
* @defgroup Ajax Ajax
*/
*
* @param IDatabase $dbw If you have one available
* @return bool|array False on failure, assoc array on success:
- * ('id' => block ID, 'autoIds' => array of autoblock IDs)
+ * ('id' => block ID, 'autoIds' => array of autoblock IDs)
*/
public function insert( $dbw = null ) {
global $wgBlockDisablesLogin;
if ( $this->incompleteForm ) {
# If the form is incomplete, force to preview.
wfDebug( __METHOD__ . ": Form data appears to be incomplete\n" );
- wfDebug( "POST DATA: " . var_export( $_POST, true ) . "\n" );
+ wfDebug( "POST DATA: " . var_export( $request->getPostValues(), true ) . "\n" );
$this->preview = true;
} else {
$this->preview = $request->getCheck( 'wpPreview' );
if ( $cache !== null ) {
return $cache;
}
- # Check for raw action using $_GET not $wgRequest, since the latter might not be initialised yet
+ // Check for raw action using $_GET not $wgRequest, since the latter might not be initialised yet
+ // phpcs:ignore MediaWiki.Usage.SuperGlobalsUsage.SuperGlobals
if ( ( isset( $_GET['action'] ) && $_GET['action'] == 'raw' )
|| (
isset( $_SERVER['SCRIPT_NAME'] )
* @param string $format The format string (See php's docs)
* @param string $data A binary string of binary data
* @param int|bool $length The minimum length of $data or false. This is to
- * prevent reading beyond the end of $data. false to disable the check.
+ * prevent reading beyond the end of $data. false to disable the check.
*
* Also be careful when using this function to read unsigned 32 bit integer
* because php might make it negative.
* @todo FIXME: Doesn't handle sub-links as in image thumb texts like the main parser
*
* @param string $comment Text to format links in. WARNING! Since the output of this
- * function is html, $comment must be sanitized for use as html. You probably want
- * to pass $comment through Sanitizer::escapeHtmlAllowEntities() before calling
- * this function.
+ * function is html, $comment must be sanitized for use as html. You probably want
+ * to pass $comment through Sanitizer::escapeHtmlAllowEntities() before calling
+ * this function.
* @param Title|null $title An optional title object used to links to sections
* @param bool $local Whether section links should refer to local page
* @param string|null $wikiId Id of the wiki to link to (if not the local wiki),
if ( $this->mArticleBodyOnly ) {
echo $this->mBodytext;
} else {
- // Enable safe mode if requested
+ // Enable safe mode if requested (T152169)
if ( $this->getRequest()->getBool( 'safemode' ) ) {
$this->disallowUserJs();
}
$rlClient = new ResourceLoaderClientHtml( $context, [
'target' => $this->getTarget(),
'nonce' => $this->getCSPNonce(),
+ // When 'safemode', disallowUserJs(), or reduceAllowedModules() is used
+ // to only restrict modules to ORIGIN_CORE (ie. disallow ORIGIN_USER), the list of
+ // modules enqueud for loading on this page is filtered to just those.
+ // However, to make sure we also apply the restriction to dynamic dependencies and
+ // lazy-loaded modules at run-time on the client-side, pass 'safemode' down to the
+ // StartupModule so that the client-side registry will not contain any restricted
+ // modules either. (T152169, T185303)
+ 'safemode' => ( $this->getAllowedModules( ResourceLoaderModule::TYPE_COMBINED )
+ <= ResourceLoaderModule::ORIGIN_CORE_INDIVIDUAL
+ ) ? '1' : null,
] );
$rlClient->setConfig( $this->getJSVars() );
$rlClient->setModules( $this->getModules( /*filter*/ true ) );
* Create a new Title from a prefixed DB key
*
* @param string $key The database key, which has underscores
- * instead of spaces, possibly including namespace and
- * interwiki prefixes
+ * instead of spaces, possibly including namespace and
+ * interwiki prefixes
* @return Title|null Title, or null on an error
*/
public static function newFromDBkey( $key ) {
use MediaWiki\Session\SessionId;
use MediaWiki\Session\SessionManager;
+// The point of this class is to be a wrapper around super globals
+// phpcs:disable MediaWiki.Usage.SuperGlobalsUsage.SuperGlobals
+
/**
* The WebRequest class encapsulates getting at data passed in the
* URL or via a POSTed form stripping illegal input characters and
return $_GET;
}
+ /**
+ * Get the values passed via POST.
+ * No transformation is performed on the values.
+ *
+ * @since 1.32
+ * @codeCoverageIgnore
+ * @return array
+ */
+ public function getPostValues() {
+ return $_POST;
+ }
+
/**
* Return the contents of the Query with no decoding. Use when you need to
* know exactly what was sent, e.g. for an OAuth signature over the elements.
* @param string $method HTTP method. Usually GET/POST
* @param string $url Full URL to act on. If protocol-relative, will be expanded to an http:// URL
* @param array $options Options to pass to MWHttpRequest object.
- * Possible keys for the array:
+ * Possible keys for the array:
* - timeout Timeout length in seconds
* - connectTimeout Timeout for connection, in seconds (curl only)
* - postData An array of key-value pairs or a url-encoded form data
* @param string $ikm The input keying material
* @param string $salt The salt to add to the ikm, to get the prk
* @param string $info Optional context (change the output without affecting
- * the randomness properties of the output)
+ * the randomness properties of the output)
* @param int $L Number of bytes to return
* @return string Cryptographically secure pseudorandom binary string
*/
* - error : Any cURL error string
* The map also stores integer-indexed copies of these values. This lets callers do:
* @code
- * list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) = $http->run( $req );
+ * list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) = $http->run( $req );
* @endcode
* @param array $req HTTP request array
* @param array $opts
'IPAddress' => isset( $_SERVER[ 'REMOTE_ADDR' ] ) ? $_SERVER[ 'REMOTE_ADDR' ] : '',
'UserAgent' => isset( $_SERVER['HTTP_USER_AGENT'] ) ? $_SERVER['HTTP_USER_AGENT'] : '',
'ChronologyProtection' => 'true',
+ // phpcs:ignore MediaWiki.Usage.SuperGlobalsUsage.SuperGlobals -- library can't use $wgRequest
'ChronologyPositionIndex' => isset( $_GET['cpPosIndex'] ) ? $_GET['cpPosIndex'] : null
];
const TOC_START = '<mw:toc>';
const TOC_END = '</mw:toc>';
+ /** @var int Assume that no output will later be saved this many seconds after parsing */
+ const MAX_TTS = 900;
+
# Persistent:
public $mTagHooks = [];
public $mTransparentTagHooks = [];
}
break;
case 'revisionday':
- # Let the edit saving system know we should parse the page
- # *after* a revision ID has been assigned. This is for null edits.
- $this->mOutput->setFlag( 'vary-revision' );
- wfDebug( __METHOD__ . ": {{REVISIONDAY}} used, setting vary-revision...\n" );
- $value = intval( substr( $this->getRevisionTimestamp(), 6, 2 ) );
+ $value = (int)$this->getRevisionTimestampSubstring( 6, 2, self::MAX_TTS, $index );
break;
case 'revisionday2':
- # Let the edit saving system know we should parse the page
- # *after* a revision ID has been assigned. This is for null edits.
- $this->mOutput->setFlag( 'vary-revision' );
- wfDebug( __METHOD__ . ": {{REVISIONDAY2}} used, setting vary-revision...\n" );
- $value = substr( $this->getRevisionTimestamp(), 6, 2 );
+ $value = $this->getRevisionTimestampSubstring( 6, 2, self::MAX_TTS, $index );
break;
case 'revisionmonth':
- # Let the edit saving system know we should parse the page
- # *after* a revision ID has been assigned. This is for null edits.
- $this->mOutput->setFlag( 'vary-revision' );
- wfDebug( __METHOD__ . ": {{REVISIONMONTH}} used, setting vary-revision...\n" );
- $value = substr( $this->getRevisionTimestamp(), 4, 2 );
+ $value = $this->getRevisionTimestampSubstring( 4, 2, self::MAX_TTS, $index );
break;
case 'revisionmonth1':
- # Let the edit saving system know we should parse the page
- # *after* a revision ID has been assigned. This is for null edits.
- $this->mOutput->setFlag( 'vary-revision' );
- wfDebug( __METHOD__ . ": {{REVISIONMONTH1}} used, setting vary-revision...\n" );
- $value = intval( substr( $this->getRevisionTimestamp(), 4, 2 ) );
+ $value = (int)$this->getRevisionTimestampSubstring( 4, 2, self::MAX_TTS, $index );
break;
case 'revisionyear':
- # Let the edit saving system know we should parse the page
- # *after* a revision ID has been assigned. This is for null edits.
- $this->mOutput->setFlag( 'vary-revision' );
- wfDebug( __METHOD__ . ": {{REVISIONYEAR}} used, setting vary-revision...\n" );
- $value = substr( $this->getRevisionTimestamp(), 0, 4 );
+ $value = $this->getRevisionTimestampSubstring( 0, 4, self::MAX_TTS, $index );
break;
case 'revisiontimestamp':
# Let the edit saving system know we should parse the page
return $value;
}
+ /**
+ * @param int $start
+ * @param int $len
+ * @param int $mtts Max time-till-save; sets vary-revision if result might change by then
+ * @param string $variable Parser variable name
+ * @return string
+ */
+ private function getRevisionTimestampSubstring( $start, $len, $mtts, $variable ) {
+ global $wgContLang;
+
+ # Get the timezone-adjusted timestamp to be used for this revision
+ $resNow = substr( $this->getRevisionTimestamp(), $start, $len );
+ # Possibly set vary-revision if there is not yet an associated revision
+ if ( !$this->getRevisionObject() ) {
+ # Get the timezone-adjusted timestamp $mtts seconds in the future
+ $resThen = substr(
+ $wgContLang->userAdjust( wfTimestamp( TS_MW, time() + $mtts ), '' ),
+ $start,
+ $len
+ );
+
+ if ( $resNow !== $resThen ) {
+ # Let the edit saving system know we should parse the page
+ # *after* a revision ID has been assigned. This is for null edits.
+ $this->mOutput->setFlag( 'vary-revision' );
+ wfDebug( __METHOD__ . ": $variable used, setting vary-revision...\n" );
+ }
+ }
+
+ return $resNow;
+ }
+
/**
* initialise the magic variables (like CURRENTMONTHNAME) and substitution modifiers
*
* 'expansion-depth-exceeded-category')
* @param string|int|null $current Current value
* @param string|int|null $max Maximum allowed, when an explicit limit has been
- * exceeded, provide the values (optional)
+ * exceeded, provide the values (optional)
*/
public function limitationWarn( $limitationType, $current = '', $max = '' ) {
# does no harm if $current and $max are present but are unnecessary for the message
* * To implement hidden categories, hiding pages from category listings
* by storing a property.
*
- * * Overriding the displayed article title.
- * @see ParserOutput::setDisplayTitle()
+ * * Overriding the displayed article title (ParserOutput::setDisplayTitle()).
*
* * To implement image tagging, for example displaying an icon on an
* image thumbnail to indicate that it is listed for deletion on
* @param User $user who's policy we are checking
* @param string $password the password to check
* @return Status error to indicate the password didn't meet the policy, or fatal to
- * indicate the user shouldn't be allowed to login.
+ * indicate the user shouldn't be allowed to login.
*/
public function checkUserPassword( User $user, $password ) {
$effectivePolicy = $this->getPoliciesForUser( $user );
* @param string $password the password to check
* @param array $groups list of groups to which we assume the user belongs
* @return Status error to indicate the password didn't meet the policy, or fatal to
- * indicate the user shouldn't be allowed to login.
+ * indicate the user shouldn't be allowed to login.
*/
public function checkUserPasswordForGroups( User $user, $password, array $groups ) {
$effectivePolicy = self::getPoliciesForGroups(
);
}
- $licenseError = false;
+ $extraErrors = [];
// Check if it's a string, if not, schema validation will display an error
if ( isset( $data->{'license-name'} ) && is_string( $data->{'license-name'} ) ) {
$licenses = new SpdxLicenses();
$valid = $licenses->validate( $data->{'license-name'} );
if ( !$valid ) {
- $licenseError = '[license-name] Invalid SPDX license identifier, '
+ $extraErrors[] = '[license-name] Invalid SPDX license identifier, '
. 'see <https://spdx.org/licenses/>';
}
}
+ if ( isset( $data->url ) && is_string( $data->url ) ) {
+ $parsed = wfParseUrl( $data->url );
+ $mwoUrl = false;
+ if ( $parsed['host'] === 'www.mediawiki.org' ) {
+ $mwoUrl = true;
+ } elseif ( $parsed['host'] === 'mediawiki.org' ) {
+ $mwoUrl = true;
+ $extraErrors[] = '[url] Should use www.mediawiki.org domain';
+ }
+
+ if ( $mwoUrl && $parsed['scheme'] !== 'https' ) {
+ $extraErrors[] = '[url] Should use HTTPS for www.mediawiki.org URLs';
+ }
+ }
$validator = new Validator;
$validator->check( $data, (object)[ '$ref' => 'file://' . $schemaPath ] );
- if ( $validator->isValid() && !$licenseError ) {
+ if ( $validator->isValid() && !$extraErrors ) {
// All good.
return true;
} else {
foreach ( $validator->getErrors() as $error ) {
$out .= "[{$error['property']}] {$error['message']}\n";
}
- if ( $licenseError ) {
- $out .= "$licenseError\n";
+ if ( $extraErrors ) {
+ $out .= implode( "\n", $extraErrors ) . "\n";
}
throw new ExtensionJsonValidationError( $out );
}
/**
* @param ResourceLoaderContext $context
* @param array $options [optional] Array of options
- * - 'target': Custom parameter passed to StartupModule.
+ * - 'target': Parameter for modules=startup request, see ResourceLoaderStartUpModule.
+ * - 'safemode': Parameter for modules=startup request, see ResourceLoaderStartUpModule.
* - 'nonce': From OutputPage::getCSPNonce().
*/
public function __construct( ResourceLoaderContext $context, array $options = [] ) {
$this->resourceLoader = $context->getResourceLoader();
$this->options = $options + [
'target' => null,
+ 'safemode' => null,
'nonce' => null,
];
}
// Async scripts. Once the startup is loaded, inline RLQ scripts will run.
// Pass-through a custom 'target' from OutputPage (T143066).
- $startupQuery = $this->options['target'] !== null
- ? [ 'target' => (string)$this->options['target'] ]
- : [];
+ $startupQuery = [];
+ foreach ( [ 'target', 'safemode' ] as $param ) {
+ if ( $this->options[$param] !== null ) {
+ $startupQuery[$param] = (string)$this->options[$param];
+ }
+ }
$chunks[] = $this->getLoad(
'startup',
ResourceLoaderModule::TYPE_SCRIPTS,
}
/**
- * Get the origin of this module. Should only be overridden for foreign modules.
+ * Get the source of this module. Should only be overridden for foreign modules.
*
- * @return string Origin name, 'local' for local modules
+ * @return string Source name, 'local' for local modules
*/
public function getSource() {
// Stub, override expected
* the ability to vary based extra query parameters, in addition to those
* from ResourceLoaderContext:
*
- * - target: Only register modules in the client allowed within this target.
+ * - target: Only register modules in the client intended for this target.
* Default: "desktop".
* See also: OutputPage::setTarget(), ResourceLoaderModule::getTargets().
+ *
+ * - safemode: Only register modules that have ORIGIN_CORE as their origin.
+ * This effectively disables ORIGIN_USER modules. (T185303)
+ * See also: OutputPage::disallowUserJs()
*/
class ResourceLoaderStartUpModule extends ResourceLoaderModule {
* Optimize the dependency tree in $this->modules.
*
* The optimization basically works like this:
- * Given we have module A with the dependencies B and C
- * and module B with the dependency C.
- * Now we don't have to tell the client to explicitly fetch module
- * C as that's already included in module B.
+ * Given we have module A with the dependencies B and C
+ * and module B with the dependency C.
+ * Now we don't have to tell the client to explicitly fetch module
+ * C as that's already included in module B.
*
* This way we can reasonably reduce the amount of module registration
* data send to the client.
// Future developers: Use WebRequest::getRawVal() instead getVal().
// The getVal() method performs slow Language+UTF logic. (f303bb9360)
$target = $context->getRequest()->getRawVal( 'target', 'desktop' );
+ $safemode = $context->getRequest()->getRawVal( 'safemode' ) === '1';
// Bypass target filter if this request is Special:JavaScriptTest.
// To prevent misuse in production, this is only allowed if testing is enabled server-side.
$byPassTargetFilter = $this->getConfig()->get( 'EnableJavaScriptTest' ) && $target === 'test';
foreach ( $resourceLoader->getModuleNames() as $name ) {
$module = $resourceLoader->getModule( $name );
$moduleTargets = $module->getTargets();
- if ( !$byPassTargetFilter && !in_array( $target, $moduleTargets ) ) {
+ if (
+ ( !$byPassTargetFilter && !in_array( $target, $moduleTargets ) )
+ || ( $safemode && $module->getOrigin() > ResourceLoaderModule::ORIGIN_CORE_INDIVIDUAL )
+ ) {
continue;
}
* @param string $ikm The input keying material
* @param string $salt The salt to add to the ikm, to get the prk
* @param string $info Optional context (change the output without affecting
- * the randomness properties of the output)
+ * the randomness properties of the output)
* @param int $L Number of bytes to return
* @return string Cryptographically secure pseudorandom binary string
*/
/**
* Get an array of language names, indexed by code.
* @param null|string $inLanguage Code of language in which to return the names
- * Use null for autonyms (native names)
+ * Use null for autonyms (native names)
* @param string $include One of:
- * 'all' all available languages
- * 'mw' only if the language is defined in MediaWiki or wgExtraLanguageNames (default)
- * 'mwfile' only if the language is in 'mw' *and* has a message file
+ * 'all' all available languages
+ * 'mw' only if the language is defined in MediaWiki or wgExtraLanguageNames (default)
+ * 'mwfile' only if the language is in 'mw' *and* has a message file
* @return array Language code => language name
* @since 1.20
*/
* @param int $length Maximum length (including ellipsis)
* @param string $ellipsis String to append to the truncated text
* @param bool $adjustLength Subtract length of ellipsis from $length.
- * $adjustLength was introduced in 1.18, before that behaved as if false.
+ * $adjustLength was introduced in 1.18, before that behaved as if false.
* @return string
*/
function truncate( $string, $length, $ellipsis = '...', $adjustLength = true ) {
* Parse the conversion table stored in the cache.
*
* The tables should be in blocks of the following form:
- * -{
- * word => word ;
- * word => word ;
- * ...
- * }-
+ * -{
+ * word => word ;
+ * word => word ;
+ * ...
+ * }-
*
* To make the tables more manageable, subpages are allowed
* and will be parsed recursively if $recursive == true.
* Word order is irrelevant.
*
* Possible values specifying the grammatical case are:
- * 1, Nominative
- * 2, Genitive
- * 3, Dative
- * 4, Accusative, -omitted-
+ * 1, Nominative
+ * 2, Genitive
+ * 3, Dative
+ * 4, Accusative, -omitted-
*
* Possible values specifying the article type are:
- * Betoont focussed or stressed article
- * -omitted- unstressed or unfocussed article
+ * Betoont focussed or stressed article
+ * -omitted- unstressed or unfocussed article
*
* Possible values for the type of genitive are:
- * Sing, Iehr prepositioned genitive = possessive dative
- * Vun, Fon, -omitted- postpositioned genitive = preposition "vun" with dative
+ * Sing, Iehr prepositioned genitive = possessive dative
+ * Vun, Fon, -omitted- postpositioned genitive = preposition "vun" with dative
*
* Values of case overrides & prepositions, in the order of preceedence:
- * Sing, Iehr possessive dative = prepositioned genitive
- * Vun, Fon preposition "vun" with dative = postpositioned genitive
- * En, em preposition "en" with dative
+ * Sing, Iehr possessive dative = prepositioned genitive
+ * Vun, Fon preposition "vun" with dative = postpositioned genitive
+ * En, em preposition "en" with dative
*
* Values for object gender specifiers of the possessive dative, or
* prepositioned genitive, evaluated with "Sing, Iehr" of above only:
- * Male a singular male object follows
- * -omitted- a non-male or plural object follows
+ * Male a singular male object follows
+ * -omitted- a non-male or plural object follows
*
* We currently handle definite articles of the singular only.
* There is a full set of test cases at:
'skin' => 'vector',
'modules' => 'startup',
'only' => 'scripts',
+ 'safemode' => null,
];
$resourceLoader = $rl ?: new ResourceLoader();
$request = new FauxRequest( [
'lang' => $options['lang'],
'modules' => $options['modules'],
'only' => $options['only'],
+ 'safemode' => $options['safemode'],
'skin' => $options['skin'],
'target' => 'phpunit',
] );
-@import "test.common.mixins";
+@import "../common/test.common.mixins";
@unitTestColor: green;
--- /dev/null
+@import "test.common.mixins";
+
+/* @noflip */
+.unit-tests {
+ .test-mixin(green);
+}
--- /dev/null
+{
+ "name": "Test",
+ "url": "http://www.mediawiki.org/",
+ "manifest_version": 1
+}
--- /dev/null
+{
+ "name": "Test",
+ "url": "http://mediawiki.org/",
+ "manifest_version": 1
+}
* @param bool $collect Whether to collect logs. @see setCollect()
* @param callable $filter Filter logs before collecting/printing. Signature is
* string|null function ( string $message, string $level, array $context );
- * @param bool $collectContext Whether to keep the context passed to log.
- * @since 1.29 @see setCollectContext()
+ * @param bool $collectContext Whether to keep the context passed to log
+ * (since 1.29, @see setCollectContext()).
*/
public function __construct( $collect = false, $filter = null, $collectContext = false ) {
$this->collect = $collect;
'good.json',
true
],
+ [
+ 'bad_url.json', 'bad_url.json did not pass validation.
+[url] Should use HTTPS for www.mediawiki.org URLs'
+ ],
+ [
+ 'bad_url2.json', 'bad_url2.json did not pass validation.
+[url] Should use www.mediawiki.org domain
+[url] Should use HTTPS for www.mediawiki.org URLs'
+ ]
];
}
$this->assertEquals( $expected, $client->getHeadHtml() );
}
+ /**
+ * Confirm that 'safemode' is passed down to startup.
+ *
+ * @covers ResourceLoaderClientHtml::getHeadHtml
+ */
+ public function testGetHeadHtmlWithSafemode() {
+ $client = new ResourceLoaderClientHtml(
+ self::makeContext(),
+ [ 'safemode' => '1' ]
+ );
+
+ // phpcs:disable Generic.Files.LineLength
+ $expected = '<script>document.documentElement.className = document.documentElement.className.replace( /(^|\s)client-nojs(\s|$)/, "$1client-js$2" );</script>' . "\n"
+ . '<script async="" src="/w/load.php?debug=false&lang=nl&modules=startup&only=scripts&safemode=1&skin=fallback"></script>';
+ // phpcs:enable
+
+ $this->assertEquals( $expected, $client->getHeadHtml() );
+ }
+
/**
* Confirm that a null 'target' is the same as no target.
*
"test.blank",
"{blankVer}"
]
+] );'
+ ] ],
+ [ [
+ 'msg' => 'Safemode disabled (default; register all modules)',
+ 'modules' => [
+ // Default origin: ORIGIN_CORE_SITEWIDE
+ 'test.blank' => new ResourceLoaderTestModule(),
+ 'test.core-generated' => new ResourceLoaderTestModule( [
+ 'origin' => ResourceLoaderModule::ORIGIN_CORE_INDIVIDUAL
+ ] ),
+ 'test.sitewide' => new ResourceLoaderTestModule( [
+ 'origin' => ResourceLoaderModule::ORIGIN_USER_SITEWIDE
+ ] ),
+ 'test.user' => new ResourceLoaderTestModule( [
+ 'origin' => ResourceLoaderModule::ORIGIN_USER_INDIVIDUAL
+ ] ),
+ ],
+ 'out' => '
+mw.loader.addSource( {
+ "local": "/w/load.php"
+} );
+mw.loader.register( [
+ [
+ "test.blank",
+ "{blankVer}"
+ ],
+ [
+ "test.core-generated",
+ "{blankVer}"
+ ],
+ [
+ "test.sitewide",
+ "{blankVer}"
+ ],
+ [
+ "test.user",
+ "{blankVer}"
+ ]
+] );'
+ ] ],
+ [ [
+ 'msg' => 'Safemode enabled (filter modules with user/site origin)',
+ 'extraQuery' => [ 'safemode' => '1' ],
+ 'modules' => [
+ // Default origin: ORIGIN_CORE_SITEWIDE
+ 'test.blank' => new ResourceLoaderTestModule(),
+ 'test.core-generated' => new ResourceLoaderTestModule( [
+ 'origin' => ResourceLoaderModule::ORIGIN_CORE_INDIVIDUAL
+ ] ),
+ 'test.sitewide' => new ResourceLoaderTestModule( [
+ 'origin' => ResourceLoaderModule::ORIGIN_USER_SITEWIDE
+ ] ),
+ 'test.user' => new ResourceLoaderTestModule( [
+ 'origin' => ResourceLoaderModule::ORIGIN_USER_INDIVIDUAL
+ ] ),
+ ],
+ 'out' => '
+mw.loader.addSource( {
+ "local": "/w/load.php"
+} );
+mw.loader.register( [
+ [
+ "test.blank",
+ "{blankVer}"
+ ],
+ [
+ "test.core-generated",
+ "{blankVer}"
+ ]
] );'
] ],
[ [
$this->setMwGlobals( 'wgResourceLoaderSources', $case['sources'] );
}
- $context = $this->getResourceLoaderContext();
+ $extraQuery = isset( $case['extraQuery'] ) ? $case['extraQuery'] : [];
+ $context = $this->getResourceLoaderContext( $extraQuery );
$rl = $context->getResourceLoader();
$rl->register( $case['modules'] );
$module = new ResourceLoaderStartUpModule();
parent::setUp();
$this->setMwGlobals( [
- 'wgResourceLoaderLESSImportPaths' => [
- dirname( dirname( __DIR__ ) ) . '/data/less/common',
- ],
+ 'wgResourceLoaderLESSImportPaths' => [],
'wgResourceLoaderLESSVars' => [
'foo' => '2px',
'Foo' => '#eeeeee',
$this->assertStringEqualsFile( $basePath . '/styles.css', $styles['all'] );
}
+ /**
+ * @covers ResourceLoader::getLessCompiler
+ */
+ public function testLessImportDirs() {
+ $rl = new EmptyResourceLoader();
+ $lc = $rl->getLessCompiler();
+ $basePath = dirname( dirname( __DIR__ ) ) . '/data/less';
+ $lc->SetImportDirs( [
+ "$basePath/common" => '',
+ ] );
+ $css = $lc->parseFile( "$basePath/module/use-import-dir.less" )->getCss();
+ $this->assertStringEqualsFile( "$basePath/module/styles.css", $css );
+ }
+
public static function providePackedModules() {
return [
[
* @author Lupo
* @since 1.20
*/
+
+// This file doesn't run as part of MediaWiki
+// phpcs:disable MediaWiki.Usage.SuperGlobalsUsage.SuperGlobals
+
header( 'Content-Type: text/javascript; charset=utf-8' );
$moduleImplementations = [
* @author Timo Tijhof
* @since 1.20
*/
+
+// This file doesn't run as part of MediaWiki
+// phpcs:disable MediaWiki.Usage.SuperGlobalsUsage.SuperGlobals
+
header( 'Content-Type: text/css; charset=utf-8' );
/**
wfThumbHandle404();
} else {
// Called directly, use $_GET params
- wfStreamThumb( $_GET );
+ wfStreamThumb( $wgRequest->getQueryValues() );
}
$mediawiki = new MediaWiki();