* …
=== Breaking changes in 1.34 ===
+* Preferences class, deprecated in 1.31, has been removed.
+* The following parts of code, deprecated in 1.32, were removed in favor of
+ built-in PHP functions:
+ * CryptRand class
+ * CryptRand service
+ * Functions of the MWCryptRand class: singleton(), wasStrong() and generate().
+* Language::setCode, deprecated in 1.32, was removed. Use Language::factory to
+ create a new Language object with a different language code.
* …
=== Deprecations in 1.34 ===
'CreditsAction' => __DIR__ . '/includes/actions/CreditsAction.php',
'CrhConverter' => __DIR__ . '/languages/classes/LanguageCrh.php',
'CryptHKDF' => __DIR__ . '/includes/libs/CryptHKDF.php',
- 'CryptRand' => __DIR__ . '/includes/libs/CryptRand.php',
'CssContent' => __DIR__ . '/includes/content/CssContent.php',
'CssContentHandler' => __DIR__ . '/includes/content/CssContentHandler.php',
'CsvStatsOutput' => __DIR__ . '/maintenance/language/StatOutputs.php',
'DatabaseUpdater' => __DIR__ . '/includes/installer/DatabaseUpdater.php',
'DateFormats' => __DIR__ . '/maintenance/language/date-formats.php',
'DateFormatter' => __DIR__ . '/includes/parser/DateFormatter.php',
+ 'DateFormatterFactory' => __DIR__ . '/includes/parser/DateFormatterFactory.php',
'DeadendPagesPage' => __DIR__ . '/includes/specials/SpecialDeadendpages.php',
'DeduplicateArchiveRevId' => __DIR__ . '/maintenance/deduplicateArchiveRevId.php',
'DeferrableCallback' => __DIR__ . '/includes/deferred/DeferrableCallback.php',
'PostgreSqlLockManager' => __DIR__ . '/includes/libs/lockmanager/PostgreSqlLockManager.php',
'PostgresInstaller' => __DIR__ . '/includes/installer/PostgresInstaller.php',
'PostgresUpdater' => __DIR__ . '/includes/installer/PostgresUpdater.php',
- 'Preferences' => __DIR__ . '/includes/Preferences.php',
'PreferencesForm' => __DIR__ . '/includes/specials/forms/PreferencesFormLegacy.php',
'PreferencesFormLegacy' => __DIR__ . '/includes/specials/forms/PreferencesFormLegacy.php',
'PreferencesFormOOUI' => __DIR__ . '/includes/specials/forms/PreferencesFormOOUI.php',
use Config;
use ConfigFactory;
use CryptHKDF;
-use CryptRand;
+use DateFormatterFactory;
use EventRelayerGroup;
use GenderCache;
use GlobalVarConfig;
}
/**
- * @since 1.28
- * @deprecated since 1.32, use random_bytes()/random_int()
- * @return CryptRand
+ * @since 1.33
+ * @return DateFormatterFactory
*/
- public function getCryptRand() {
- wfDeprecated( __METHOD__, '1.32' );
- return $this->getService( 'CryptRand' );
+ public function getDateFormatterFactory() {
+ return $this->getService( 'DateFormatterFactory' );
}
/**
$response->header( 'Content-language: ' .
MediaWikiServices::getInstance()->getContentLanguage()->getHtmlCode() );
- if ( !$this->mArticleBodyOnly ) {
- $sk = $this->getSkin();
- }
-
$linkHeader = $this->getLinkHeader();
if ( $linkHeader ) {
$response->header( $linkHeader );
use MediaWiki\Special\SpecialPageFactory;
use MessageSpecifier;
use MWException;
-use MWNamespace;
+use NamespaceInfo;
use RequestContext;
use SpecialPage;
use Title;
$whitelistRead,
$whitelistReadRegexp,
$emailConfirmToEdit,
- $blockDisablesLogin
+ $blockDisablesLogin,
+ NamespaceInfo $nsInfo
) {
$this->specialPageFactory = $specialPageFactory;
$this->whitelistRead = $whitelistRead;
$this->whitelistReadRegexp = $whitelistReadRegexp;
$this->emailConfirmToEdit = $emailConfirmToEdit;
$this->blockDisablesLogin = $blockDisablesLogin;
+ $this->nsInfo = $nsInfo;
}
/**
return $errors;
}
- $isSubPage = MWNamespace::hasSubpages( $page->getNamespace() ) ?
+ $isSubPage = $this->nsInfo->hasSubpages( $page->getNamespace() ) ?
strpos( $page->getText(), '/' ) !== false : false;
if ( $action == 'create' ) {
if (
- ( MWNamespace::isTalk( $page->getNamespace() ) && !$user->isAllowed( 'createtalk' ) ) ||
- ( !MWNamespace::isTalk( $page->getNamespace() ) && !$user->isAllowed( 'createpage' ) )
+ ( $this->nsInfo->isTalk( $page->getNamespace() ) &&
+ !$user->isAllowed( 'createtalk' ) ) ||
+ ( !$this->nsInfo->isTalk( $page->getNamespace() ) &&
+ !$user->isAllowed( 'createpage' ) )
) {
$errors[] = $user->isAnon() ? [ 'nocreatetext' ] : [ 'nocreate-loggedin' ];
}
}
} elseif ( $action == 'move' ) {
// Check for immobile pages
- if ( !MWNamespace::isMovable( $page->getNamespace() ) ) {
+ if ( !$this->nsInfo->isMovable( $page->getNamespace() ) ) {
// Specific message for this case
$errors[] = [ 'immobile-source-namespace', $page->getNsText() ];
} elseif ( !$page->isMovable() ) {
$errors[] = [ 'immobile-source-page' ];
}
} elseif ( $action == 'move-target' ) {
- if ( !MWNamespace::isMovable( $page->getNamespace() ) ) {
+ if ( !$this->nsInfo->isMovable( $page->getNamespace() ) ) {
$errors[] = [ 'immobile-target-namespace', $page->getNsText() ];
} elseif ( !$page->isMovable() ) {
$errors[] = [ 'immobile-target-page' ];
+++ /dev/null
-<?php
-/**
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- */
-
-use MediaWiki\Auth\AuthManager;
-use MediaWiki\MediaWikiServices;
-use MediaWiki\Preferences\DefaultPreferencesFactory;
-
-/**
- * This class has been replaced by the PreferencesFactory service.
- *
- * @deprecated since 1.31 use the PreferencesFactory service instead.
- */
-class Preferences {
-
- /**
- * A shim to maintain backwards-compatibility of this class, basically replicating the
- * default behaviour of the PreferencesFactory service but not permitting overriding.
- * @return DefaultPreferencesFactory
- */
- protected static function getDefaultPreferencesFactory() {
- $services = MediaWikiServices::getInstance();
- $authManager = AuthManager::singleton();
- $linkRenderer = $services->getLinkRenderer();
- $config = $services->getMainConfig();
- $preferencesFactory = new DefaultPreferencesFactory(
- $config, $services->getContentLanguage(), $authManager,
- $linkRenderer
- );
- return $preferencesFactory;
- }
-
- /**
- * @throws MWException
- * @param User $user
- * @param IContextSource $context
- * @return array|null
- */
- public static function getPreferences( $user, IContextSource $context ) {
- wfDeprecated( __METHOD__, '1.31' );
- $preferencesFactory = self::getDefaultPreferencesFactory();
- return $preferencesFactory->getFormDescriptor( $user, $context );
- }
-
- /**
- * Loads existing values for a given array of preferences
- * @throws MWException
- * @param User $user
- * @param IContextSource $context
- * @param array &$defaultPreferences Array to load values for
- * @return array|null
- */
- public static function loadPreferenceValues( $user, $context, &$defaultPreferences ) {
- throw new Exception( __METHOD__ . '() is deprecated and does nothing' );
- }
-
- /**
- * Pull option from a user account. Handles stuff like array-type preferences.
- *
- * @param string $name
- * @param array $info
- * @param User $user
- * @return array|string
- */
- public static function getOptionFromUser( $name, $info, $user ) {
- throw new Exception( __METHOD__ . '() is deprecated and does nothing' );
- }
-
- /**
- * @param User $user
- * @param IContextSource $context
- * @param array &$defaultPreferences
- * @return void
- */
- public static function profilePreferences(
- $user, IContextSource $context, &$defaultPreferences
- ) {
- wfDeprecated( __METHOD__, '1.31' );
- $defaultPreferences = self::getPreferences( $user, $context );
- }
-
- /**
- * @param User $user
- * @param IContextSource $context
- * @param array &$defaultPreferences
- * @return void
- */
- public static function skinPreferences( $user, IContextSource $context, &$defaultPreferences ) {
- wfDeprecated( __METHOD__, '1.31' );
- $defaultPreferences = self::getPreferences( $user, $context );
- }
-
- /**
- * @param User $user
- * @param IContextSource $context
- * @param array &$defaultPreferences
- */
- public static function filesPreferences(
- $user, IContextSource $context, &$defaultPreferences
- ) {
- wfDeprecated( __METHOD__, '1.31' );
- $defaultPreferences = self::getPreferences( $user, $context );
- }
-
- /**
- * @param User $user
- * @param IContextSource $context
- * @param array &$defaultPreferences
- * @return void
- */
- public static function datetimePreferences(
- $user, IContextSource $context, &$defaultPreferences
- ) {
- wfDeprecated( __METHOD__, '1.31' );
- $defaultPreferences = self::getPreferences( $user, $context );
- }
-
- /**
- * @param User $user
- * @param IContextSource $context
- * @param array &$defaultPreferences
- */
- public static function renderingPreferences(
- $user, IContextSource $context, &$defaultPreferences
- ) {
- wfDeprecated( __METHOD__, '1.31' );
- $defaultPreferences = self::getPreferences( $user, $context );
- }
-
- /**
- * @param User $user
- * @param IContextSource $context
- * @param array &$defaultPreferences
- */
- public static function editingPreferences(
- $user, IContextSource $context, &$defaultPreferences
- ) {
- wfDeprecated( __METHOD__, '1.31' );
- $defaultPreferences = self::getPreferences( $user, $context );
- }
-
- /**
- * @param User $user
- * @param IContextSource $context
- * @param array &$defaultPreferences
- */
- public static function rcPreferences( $user, IContextSource $context, &$defaultPreferences ) {
- wfDeprecated( __METHOD__, '1.31' );
- $defaultPreferences = self::getPreferences( $user, $context );
- }
-
- /**
- * @param User $user
- * @param IContextSource $context
- * @param array &$defaultPreferences
- */
- public static function watchlistPreferences(
- $user, IContextSource $context, &$defaultPreferences
- ) {
- wfDeprecated( __METHOD__, '1.31' );
- $defaultPreferences = self::getPreferences( $user, $context );
- }
-
- /**
- * @param User $user
- * @param IContextSource $context
- * @param array &$defaultPreferences
- */
- public static function searchPreferences(
- $user, IContextSource $context, &$defaultPreferences
- ) {
- wfDeprecated( __METHOD__, '1.31' );
- $defaultPreferences = self::getPreferences( $user, $context );
- }
-
- /**
- * Dummy, kept for backwards-compatibility.
- * @param User $user
- * @param IContextSource $context
- * @param array &$defaultPreferences
- */
- public static function miscPreferences( $user, IContextSource $context, &$defaultPreferences ) {
- wfDeprecated( __METHOD__, '1.31' );
- }
-
- /**
- * @param User $user
- * @param IContextSource $context
- * @return array Text/links to display as key; $skinkey as value
- */
- public static function generateSkinOptions( $user, IContextSource $context ) {
- wfDeprecated( __METHOD__, '1.31' );
- return self::getPreferences( $user, $context );
- }
-
- /**
- * @param IContextSource $context
- * @return array
- */
- static function getDateOptions( IContextSource $context ) {
- throw new Exception( __METHOD__ . '() is deprecated and does nothing' );
- }
-
- /**
- * @param IContextSource $context
- * @return array
- */
- public static function getImageSizes( IContextSource $context ) {
- throw new Exception( __METHOD__ . '() is deprecated and does nothing' );
- }
-
- /**
- * @param IContextSource $context
- * @return array
- */
- public static function getThumbSizes( IContextSource $context ) {
- throw new Exception( __METHOD__ . '() is deprecated and does nothing' );
- }
-
- /**
- * @param string $signature
- * @param array $alldata
- * @param HTMLForm $form
- * @return bool|string
- */
- public static function validateSignature( $signature, $alldata, $form ) {
- throw new Exception( __METHOD__ . '() is deprecated and does nothing' );
- }
-
- /**
- * @param string $signature
- * @param array $alldata
- * @param HTMLForm $form
- * @return string
- */
- public static function cleanSignature( $signature, $alldata, $form ) {
- throw new Exception( __METHOD__ . '() is deprecated and does nothing now' );
- }
-
- /**
- * @param User $user
- * @param IContextSource $context
- * @param string $formClass
- * @param array $remove Array of items to remove
- * @return PreferencesFormLegacy|HTMLForm
- */
- public static function getFormObject(
- $user,
- IContextSource $context,
- $formClass = PreferencesFormLegacy::class,
- array $remove = []
- ) {
- wfDeprecated( __METHOD__, '1.31' );
- $preferencesFactory = self::getDefaultPreferencesFactory();
- return $preferencesFactory->getForm( $user, $context, $formClass, $remove );
- }
-}
return new CryptHKDF( $secret, $config->get( 'HKDFAlgorithm' ), $cache, $context );
},
- 'CryptRand' => function () : CryptRand {
- return new CryptRand();
+ 'DateFormatterFactory' => function () : DateFormatterFactory {
+ return new DateFormatterFactory;
},
'DBLoadBalancer' => function ( MediaWikiServices $services ) : Wikimedia\Rdbms\LoadBalancer {
wfUrlProtocols(),
$services->getSpecialPageFactory(),
$services->getMainConfig(),
- $services->getLinkRendererFactory()
+ $services->getLinkRendererFactory(),
+ $services->getNamespaceInfo()
);
},
$config->get( 'WhitelistRead' ),
$config->get( 'WhitelistReadRegexp' ),
$config->get( 'EmailConfirmToEdit' ),
- $config->get( 'BlockDisablesLogin' ) );
+ $config->get( 'BlockDisablesLogin' ),
+ $services->getNamespaceInfo()
+ );
},
'PreferencesFactory' => function ( MediaWikiServices $services ) : PreferencesFactory {
*/
public function enableRecursivePartials( $enable ) {
if ( $enable ) {
- $this->compileFlags = $this->compileFlags | LightnCandy::FLAG_RUNTIMEPARTIAL;
+ $this->compileFlags |= LightnCandy::FLAG_RUNTIMEPARTIAL;
} else {
- $this->compileFlags = $this->compileFlags & ~LightnCandy::FLAG_RUNTIMEPARTIAL;
+ $this->compileFlags &= ~LightnCandy::FLAG_RUNTIMEPARTIAL;
}
}
* @ingroup Database
*/
-use MediaWiki\MediaWikiServices;
+use Wikimedia\Timestamp\ConvertibleTimestamp;
use Wikimedia\Rdbms\Database;
use Wikimedia\Rdbms\DatabaseDomain;
use Wikimedia\Rdbms\Blob;
function __construct( array $p ) {
$p['tablePrefix'] = strtoupper( $p['tablePrefix'] );
parent::__construct( $p );
+
+ // @TODO: dependency inject
Hooks::run( 'DatabaseOraclePostInit', [ $this ] );
}
}
protected function open( $server, $user, $password, $dbName, $schema, $tablePrefix ) {
- global $wgDBOracleDRCP;
-
if ( !function_exists( 'oci_connect' ) ) {
throw new DBConnectionError(
$this,
return null;
}
- if ( $wgDBOracleDRCP ) {
- $this->setFlag( DBO_PERSISTENT );
- }
-
$session_mode = ( $this->flags & DBO_SYSDBA ) ? OCI_SYSDBA : OCI_DEFAULT;
Wikimedia\suppressWarnings();
*/
protected function doQuery( $sql ) {
wfDebug( "SQL: [$sql]\n" );
- if ( !StringUtils::isUtf8( $sql ) ) {
- throw new InvalidArgumentException( "SQL encoding is invalid\n$sql" );
+ if ( !mb_check_encoding( (string)$sql, 'UTF-8' ) ) {
+ throw new DBUnexpectedError( $this, "SQL encoding is invalid\n$sql" );
}
// handle some oracle specifics
}
if ( $val === null ) {
- if ( $col_info != false && $col_info->isNullable() == 0 && $col_info->defaultValue() != null ) {
+ if (
+ $col_info != false &&
+ $col_info->isNullable() == 0 &&
+ $col_info->defaultValue() != null
+ ) {
$bind .= 'DEFAULT';
} else {
$bind .= 'NULL';
}
// backward compatibility
- if ( preg_match( '/^timestamp.*/i', $col_type ) == 1 && strtolower( $val ) == 'infinity' ) {
+ if (
+ preg_match( '/^timestamp.*/i', $col_type ) == 1 &&
+ strtolower( $val ) == 'infinity'
+ ) {
$val = $this->getInfinity();
}
- $val = MediaWikiServices::getInstance()->getContentLanguage()->
- checkTitleEncoding( $val );
+ $val = $this->getVerifiedUTF8( $val );
if ( oci_bind_by_name( $stmt, ":$col", $val, -1, SQLT_CHR ) === false ) {
$e = oci_error( $stmt );
$this->reportQueryError( $e['message'], $e['code'], $sql, __METHOD__ );
$lob[$col] = oci_new_descriptor( $this->conn, OCI_D_LOB );
if ( $lob[$col] === false ) {
$e = oci_error( $stmt );
- throw new DBUnexpectedError( $this, "Cannot create LOB descriptor: " . $e['message'] );
+ throw new DBUnexpectedError(
+ $this,
+ "Cannot create LOB descriptor: " . $e['message']
+ );
}
if ( is_object( $val ) ) {
if ( $sequenceData !== false &&
!isset( $varMap[$sequenceData['column']] )
) {
- $varMap[$sequenceData['column']] = 'GET_SEQUENCE_VALUE(\'' . $sequenceData['sequence'] . '\')';
+ $varMap[$sequenceData['column']] =
+ 'GET_SEQUENCE_VALUE(\'' . $sequenceData['sequence'] . '\')';
}
// count-alias subselect fields to avoid abigious definition errors
$selectJoinConds
);
- $sql = "INSERT INTO $destTable (" . implode( ',', array_keys( $varMap ) ) . ') ' . $selectSql;
+ $sql = "INSERT INTO $destTable (" .
+ implode( ',', array_keys( $varMap ) ) . ') ' . $selectSql;
if ( in_array( 'IGNORE', $insertOptions ) ) {
$this->ignoreDupValOnIndex = true;
return $this->doQuery( "DROP TABLE $tableName CASCADE CONSTRAINTS PURGE" );
}
- function timestamp( $ts = 0 ) {
- return wfTimestamp( TS_ORACLE, $ts );
+ public function timestamp( $ts = 0 ) {
+ $t = new ConvertibleTimestamp( $ts );
+ // Let errors bubble up to avoid putting garbage in the DB
+ return $t->getTimestamp( TS_ORACLE );
}
/**
*/
function fieldInfo( $table, $field ) {
if ( is_array( $table ) ) {
- throw new DBUnexpectedError( $this, 'DatabaseOracle::fieldInfo called with table array!' );
+ throw new DBUnexpectedError(
+ $this,
+ 'DatabaseOracle::fieldInfo called with table array!'
+ );
}
return $this->fieldInfoMulti( $table, $field );
}
function addQuotes( $s ) {
- $contLang = MediaWikiServices::getInstance()->getContentLanguage();
- if ( isset( $contLang->mLoaded ) && $contLang->mLoaded ) {
- $s = $contLang->checkTitleEncoding( $s );
- }
-
- return "'" . $this->strencode( $s ) . "'";
+ return "'" . $this->strencode( $this->getVerifiedUTF8( $s ) ) . "'";
}
public function addIdentifierQuotes( $s ) {
$col_type = $col_info != false ? $col_info->type() : 'CONSTANT';
if ( $col_type == 'CLOB' ) {
$col = 'TO_CHAR(' . $col . ')';
- $val =
- MediaWikiServices::getInstance()->getContentLanguage()->checkTitleEncoding( $val );
+ $val = $this->getVerifiedUTF8( $val );
} elseif ( $col_type == 'VARCHAR2' ) {
- $val =
- MediaWikiServices::getInstance()->getContentLanguage()->checkTitleEncoding( $val );
+ $val = $this->getVerifiedUTF8( $val );
}
}
$val = $val->getData();
}
- if ( preg_match( '/^timestamp.*/i', $col_type ) == 1 && strtolower( $val ) == 'infinity' ) {
+ if (
+ preg_match( '/^timestamp.*/i', $col_type ) == 1 &&
+ strtolower( $val ) == 'infinity'
+ ) {
$val = '31-12-2030 12:00:00.000000';
}
- $val = MediaWikiServices::getInstance()->getContentLanguage()->
- checkTitleEncoding( $val );
+ $val = $this->getVerifiedUTF8( $val );
if ( oci_bind_by_name( $stmt, ":$col", $val ) === false ) {
$e = oci_error( $stmt );
$this->reportQueryError( $e['message'], $e['code'], $sql, __METHOD__ );
$lob[$col] = oci_new_descriptor( $this->conn, OCI_D_LOB );
if ( $lob[$col] === false ) {
$e = oci_error( $stmt );
- throw new DBUnexpectedError( $this, "Cannot create LOB descriptor: " . $e['message'] );
+ throw new DBUnexpectedError(
+ $this,
+ "Cannot create LOB descriptor: " . $e['message']
+ );
}
if ( is_object( $val ) ) {
public function getInfinity() {
return '31-12-2030 12:00:00.000000';
}
+
+ /**
+ * @param string $s
+ * @return string
+ */
+ private function getVerifiedUTF8( $s ) {
+ if ( mb_check_encoding( (string)$s, 'UTF-8' ) ) {
+ return $s; // valid
+ }
+
+ throw new DBUnexpectedError( $this, "Non BLOB/CLOB field must be UTF-8." );
+ }
}
$flags = DBO_DEFAULT;
$flags |= $mainConfig->get( 'DebugDumpSql' ) ? DBO_DEBUG : 0;
+ if ( $server['type'] === 'oracle' ) {
+ $flags |= $mainConfig->get( 'DBOracleDRCP' ) ? DBO_PERSISTENT : 0;
+ }
$server += [
'tablePrefix' => $mainConfig->get( 'DBprefix' ),
+++ /dev/null
-<?php
-/**
- * A cryptographic random generator class used for generating secret keys
- *
- * This is based in part on Drupal code as well as what we used in our own code
- * prior to introduction of this class.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @author Daniel Friesen
- * @file
- */
-
-/**
- * @deprecated since 1.32, use random_bytes()/random_int()
- */
-class CryptRand {
- /**
- * @deprecated since 1.32, unused
- */
- const MIN_ITERATIONS = 1000;
-
- /**
- * @deprecated since 1.32, unused
- */
- const MSEC_PER_BYTE = 0.5;
-
- /**
- * Initialize an initial random state based off of whatever we can find
- *
- * @deprecated since 1.32, unused and does nothing
- *
- * @return string
- */
- protected function initialRandomState() {
- wfDeprecated( __METHOD__, '1.32' );
- return '';
- }
-
- /**
- * Randomly hash data while mixing in clock drift data for randomness
- *
- * @deprecated since 1.32, unused and does nothing
- *
- * @param string $data The data to randomly hash.
- * @return string The hashed bytes
- * @author Tim Starling
- */
- protected function driftHash( $data ) {
- wfDeprecated( __METHOD__, '1.32' );
- return '';
- }
-
- /**
- * Return a rolling random state initially build using data from unstable sources
- *
- * @deprecated since 1.32, unused and does nothing
- *
- * @return string A new weak random state
- */
- protected function randomState() {
- wfDeprecated( __METHOD__, '1.32' );
- return '';
- }
-
- /**
- * Return a boolean indicating whether or not the source used for cryptographic
- * random bytes generation in the previously run generate* call
- * was cryptographically strong.
- *
- * @deprecated since 1.32, always returns true
- *
- * @return bool Always true
- */
- public function wasStrong() {
- wfDeprecated( __METHOD__, '1.32' );
- return true;
- }
-
- /**
- * Generate a run of cryptographically random data and return
- * it in raw binary form.
- * You can use CryptRand::wasStrong() if you wish to know if the source used
- * was cryptographically strong.
- *
- * @param int $bytes The number of bytes of random data to generate
- * @return string Raw binary random data
- */
- public function generate( $bytes ) {
- wfDeprecated( __METHOD__, '1.32' );
- $bytes = floor( $bytes );
- return random_bytes( $bytes );
- }
-
- /**
- * Generate a run of cryptographically random data and return
- * it in hexadecimal string format.
- *
- * @param int $chars The number of hex chars of random data to generate
- * @return string Hexadecimal random data
- */
- public function generateHex( $chars ) {
- wfDeprecated( __METHOD__, '1.32' );
- return MWCryptRand::generateHex( $chars );
- }
-}
*/
public static function formatDate( $parser, $date, $defaultPref = null ) {
$lang = $parser->getFunctionLang();
- $df = DateFormatter::getInstance( $lang );
+ $df = MediaWikiServices::getInstance()->getDateFormatterFactory()->get( $lang );
$date = trim( $date );
use MediaWiki\MediaWikiServices;
/**
- * Date formatter, recognises dates in plain text and formats them according to user preferences.
- * @todo preferences, OutputPage
+ * Date formatter. Recognises dates and formats them according to a specified preference.
+ *
+ * This class was originally introduced to detect and transform dates in free text. It is now
+ * only used by the {{#dateformat}} parser function. This is a very rudimentary date formatter;
+ * Language::sprintfDate() has many more features and is the correct choice for most new code.
+ * The main advantage of this date formatter is that it is able to format incomplete dates with an
+ * unspecified year.
+ *
* @ingroup Parser
*/
class DateFormatter {
- private $mSource, $mTarget;
- private $monthNames = '';
-
+ /** @var string[] Date format regexes indexed the class constants */
private $regexes;
- private $rules, $xMonths, $preferences;
- private $lang, $mLinked;
+ /**
+ * @var int[][] Array of special rules. The first key is the preference ID
+ * (one of the class constants), the second key is the detected source
+ * format, and the value is the ID of the target format that will be used
+ * in that case.
+ */
+ private $rules = [];
- /** @var string[] */
- private $keys;
+ /**
+ * @var int[] Month numbers by lowercase name
+ */
+ private $xMonths = [];
- /** @var string[] */
- private $targets;
+ /**
+ * @var string[] Month names by number
+ */
+ private $monthNames = [];
+ /**
+ * @var int[] A map of descriptive preference text to internal format ID
+ */
+ private $preferenceIDs;
+
+ /** @var string[] Format strings similar to those used by date(), indexed by ID */
+ private $targetFormats;
+
+ /** Used as a preference ID for rules that apply regardless of preference */
const ALL = -1;
+
+ /** No preference: the date may be left in the same format as the input */
const NONE = 0;
+
+ /** e.g. January 15, 2001 */
const MDY = 1;
+
+ /** e.g. 15 January 2001 */
const DMY = 2;
+
+ /** e.g. 2001 January 15 */
const YMD = 3;
- const ISO1 = 4;
+
+ /** e.g. 2001-01-15 */
+ const ISO = 4;
+
+ /** The highest ID that is a valid user preference */
const LASTPREF = 4;
- const ISO2 = 5;
- const YDM = 6;
- const DM = 7;
- const MD = 8;
- const LAST = 8;
+
+ /** e.g. 2001, 15 January */
+ const YDM = 5;
+
+ /** e.g. 15 January */
+ const DM = 6;
+
+ /** e.g. January 15 */
+ const MD = 7;
+
+ /** The highest ID that is a valid target format */
+ const LAST = 7;
/**
* @param Language $lang In which language to format the date
*/
public function __construct( Language $lang ) {
- $this->lang = $lang;
-
- $this->monthNames = $this->getMonthRegex();
+ $monthRegexParts = [];
for ( $i = 1; $i <= 12; $i++ ) {
- $this->xMonths[$this->lang->lc( $this->lang->getMonthName( $i ) )] = $i;
- $this->xMonths[$this->lang->lc( $this->lang->getMonthAbbreviation( $i ) )] = $i;
+ $monthName = $lang->getMonthName( $i );
+ $monthAbbrev = $lang->getMonthAbbreviation( $i );
+ $this->monthNames[$i] = $monthName;
+ $monthRegexParts[] = preg_quote( $monthName, '/' );
+ $monthRegexParts[] = preg_quote( $monthAbbrev, '/' );
+ $this->xMonths[mb_strtolower( $monthName )] = $i;
+ $this->xMonths[mb_strtolower( $monthAbbrev )] = $i;
}
- $this->regexTrail = '(?![a-z])/iu';
-
- # Partial regular expressions
- $this->prxDM = '\[\[(\d{1,2})[ _](' . $this->monthNames . ')\]\]';
- $this->prxMD = '\[\[(' . $this->monthNames . ')[ _](\d{1,2})\]\]';
- $this->prxY = '\[\[(\d{1,4}([ _]BC|))\]\]';
- $this->prxISO1 = '\[\[(-?\d{4})]]-\[\[(\d{2})-(\d{2})\]\]';
- $this->prxISO2 = '\[\[(-?\d{4})-(\d{2})-(\d{2})\]\]';
-
- # Real regular expressions
- $this->regexes[self::DMY] = "/{$this->prxDM}(?: *, *| +){$this->prxY}{$this->regexTrail}";
- $this->regexes[self::YDM] = "/{$this->prxY}(?: *, *| +){$this->prxDM}{$this->regexTrail}";
- $this->regexes[self::MDY] = "/{$this->prxMD}(?: *, *| +){$this->prxY}{$this->regexTrail}";
- $this->regexes[self::YMD] = "/{$this->prxY}(?: *, *| +){$this->prxMD}{$this->regexTrail}";
- $this->regexes[self::DM] = "/{$this->prxDM}{$this->regexTrail}";
- $this->regexes[self::MD] = "/{$this->prxMD}{$this->regexTrail}";
- $this->regexes[self::ISO1] = "/{$this->prxISO1}{$this->regexTrail}";
- $this->regexes[self::ISO2] = "/{$this->prxISO2}{$this->regexTrail}";
-
- # Extraction keys
- # See the comments in replace() for the meaning of the letters
- $this->keys[self::DMY] = 'jFY';
- $this->keys[self::YDM] = 'Y jF';
- $this->keys[self::MDY] = 'FjY';
- $this->keys[self::YMD] = 'Y Fj';
- $this->keys[self::DM] = 'jF';
- $this->keys[self::MD] = 'Fj';
- $this->keys[self::ISO1] = 'ymd'; # y means ISO year
- $this->keys[self::ISO2] = 'ymd';
-
- # Target date formats
- $this->targets[self::DMY] = '[[F j|j F]] [[Y]]';
- $this->targets[self::YDM] = '[[Y]], [[F j|j F]]';
- $this->targets[self::MDY] = '[[F j]], [[Y]]';
- $this->targets[self::YMD] = '[[Y]] [[F j]]';
- $this->targets[self::DM] = '[[F j|j F]]';
- $this->targets[self::MD] = '[[F j]]';
- $this->targets[self::ISO1] = '[[Y|y]]-[[F j|m-d]]';
- $this->targets[self::ISO2] = '[[y-m-d]]';
-
- # Rules
- # pref source target
+ // Partial regular expressions
+ $monthNames = implode( '|', $monthRegexParts );
+ $dm = "(?<day>\d{1,2})[ _](?<monthName>{$monthNames})";
+ $md = "(?<monthName>{$monthNames})[ _](?<day>\d{1,2})";
+ $y = '(?<year>\d{1,4}([ _]BC|))';
+ $iso = '(?<isoYear>-?\d{4})-(?<isoMonth>\d{2})-(?<isoDay>\d{2})';
+
+ $this->regexes = [
+ self::DMY => "/^{$dm}(?: *, *| +){$y}$/iu",
+ self::YDM => "/^{$y}(?: *, *| +){$dm}$/iu",
+ self::MDY => "/^{$md}(?: *, *| +){$y}$/iu",
+ self::YMD => "/^{$y}(?: *, *| +){$md}$/iu",
+ self::DM => "/^{$dm}$/iu",
+ self::MD => "/^{$md}$/iu",
+ self::ISO => "/^{$iso}$/iu",
+ ];
+
+ // Target date formats
+ $this->targetFormats = [
+ self::DMY => 'j F Y',
+ self::YDM => 'Y, j F',
+ self::MDY => 'F j, Y',
+ self::YMD => 'Y F j',
+ self::DM => 'j F',
+ self::MD => 'F j',
+ self::ISO => 'y-m-d',
+ ];
+
+ // Rules
+ // pref source target
$this->rules[self::DMY][self::MD] = self::DM;
$this->rules[self::ALL][self::MD] = self::MD;
$this->rules[self::MDY][self::DM] = self::MD;
$this->rules[self::ALL][self::DM] = self::DM;
- $this->rules[self::NONE][self::ISO2] = self::ISO1;
+ $this->rules[self::NONE][self::ISO] = self::ISO;
- $this->preferences = [
+ $this->preferenceIDs = [
'default' => self::NONE,
'dmy' => self::DMY,
'mdy' => self::MDY,
'ymd' => self::YMD,
- 'ISO 8601' => self::ISO1,
+ 'ISO 8601' => self::ISO,
];
}
/**
* Get a DateFormatter object
*
+ * @deprecated since 1.33 use MediaWikiServices::getDateFormatterFactory()
+ *
* @param Language|null $lang In which language to format the date
* Defaults to the site content language
* @return DateFormatter
*/
public static function getInstance( Language $lang = null ) {
- global $wgMainCacheType;
-
$lang = $lang ?? MediaWikiServices::getInstance()->getContentLanguage();
- $cache = ObjectCache::getLocalServerInstance( $wgMainCacheType );
-
- static $dateFormatter = false;
- if ( !$dateFormatter ) {
- $dateFormatter = $cache->getWithSetCallback(
- $cache->makeKey( 'dateformatter', $lang->getCode() ),
- $cache::TTL_HOUR,
- function () use ( $lang ) {
- return new DateFormatter( $lang );
- }
- );
- }
-
- return $dateFormatter;
+ return MediaWikiServices::getInstance()->getDateFormatterFactory()->get( $lang );
}
/**
- * @param string $preference User preference
+ * @param string $preference User preference, must be one of "default",
+ * "dmy", "mdy", "ymd" or "ISO 8601".
* @param string $text Text to reformat
- * @param array $options Array can contain 'linked' and/or 'match-whole'
+ * @param array $options Ignored. Since 1.33, 'match-whole' is implied, and
+ * 'linked' has been removed.
*
* @return string
*/
- public function reformat( $preference, $text, $options = [ 'linked' ] ) {
- $linked = in_array( 'linked', $options );
- $match_whole = in_array( 'match-whole', $options );
-
- if ( isset( $this->preferences[$preference] ) ) {
- $preference = $this->preferences[$preference];
+ public function reformat( $preference, $text, $options = [] ) {
+ if ( isset( $this->preferenceIDs[$preference] ) ) {
+ $preference = $this->preferenceIDs[$preference];
} else {
$preference = self::NONE;
}
- for ( $i = 1; $i <= self::LAST; $i++ ) {
- $this->mSource = $i;
- if ( isset( $this->rules[$preference][$i] ) ) {
+ for ( $source = 1; $source <= self::LAST; $source++ ) {
+ if ( isset( $this->rules[$preference][$source] ) ) {
# Specific rules
- $this->mTarget = $this->rules[$preference][$i];
- } elseif ( isset( $this->rules[self::ALL][$i] ) ) {
+ $target = $this->rules[$preference][$source];
+ } elseif ( isset( $this->rules[self::ALL][$source] ) ) {
# General rules
- $this->mTarget = $this->rules[self::ALL][$i];
+ $target = $this->rules[self::ALL][$source];
} elseif ( $preference ) {
# User preference
- $this->mTarget = $preference;
+ $target = $preference;
} else {
# Default
- $this->mTarget = $i;
+ $target = $source;
}
- $regex = $this->regexes[$i];
+ $regex = $this->regexes[$source];
- // Horrible hack
- if ( !$linked ) {
- $regex = str_replace( [ '\[\[', '\]\]' ], '', $regex );
- }
-
- if ( $match_whole ) {
- // Let's hope this works
- $regex = preg_replace( '!^/!', '/^', $regex );
- $regex = str_replace( $this->regexTrail,
- '$' . $this->regexTrail, $regex );
- }
+ $text = preg_replace_callback( $regex,
+ function ( $match ) use ( $target ) {
+ $format = $this->targetFormats[$target];
- // Another horrible hack
- $this->mLinked = $linked;
- $text = preg_replace_callback( $regex, [ $this, 'replace' ], $text );
- unset( $this->mLinked );
- }
- return $text;
- }
+ $text = '';
- /**
- * Regexp replacement callback
- *
- * @param array $matches
- * @return string
- */
- private function replace( $matches ) {
- # Extract information from $matches
- $linked = $this->mLinked ?? true;
-
- $bits = [];
- $key = $this->keys[$this->mSource];
- $keyLength = strlen( $key );
- for ( $p = 0; $p < $keyLength; $p++ ) {
- if ( $key[$p] != ' ' ) {
- $bits[$key[$p]] = $matches[$p + 1];
- }
- }
-
- return $this->formatDate( $bits, $matches[0], $linked );
- }
-
- /**
- * @param array $bits
- * @param string $orig Original input string, to be returned
- * on formatting failure.
- * @param bool $link
- * @return string
- */
- private function formatDate( $bits, $orig, $link = true ) {
- $format = $this->targets[$this->mTarget];
-
- if ( !$link ) {
- // strip piped links
- $format = preg_replace( '/\[\[[^|]+\|([^\]]+)\]\]/', '$1', $format );
- // strip remaining links
- $format = str_replace( [ '[[', ']]' ], '', $format );
- }
-
- # Construct new date
- $text = '';
- $fail = false;
-
- // Pre-generate y/Y stuff because we need the year for the <span> title.
- if ( !isset( $bits['y'] ) && isset( $bits['Y'] ) ) {
- $bits['y'] = $this->makeIsoYear( $bits['Y'] );
- }
- if ( !isset( $bits['Y'] ) && isset( $bits['y'] ) ) {
- $bits['Y'] = $this->makeNormalYear( $bits['y'] );
- }
-
- if ( !isset( $bits['m'] ) ) {
- $m = $this->makeIsoMonth( $bits['F'] );
- if ( $m === false ) {
- $fail = true;
- } else {
- $bits['m'] = $m;
- }
- }
-
- if ( !isset( $bits['d'] ) ) {
- $bits['d'] = sprintf( '%02d', $bits['j'] );
- }
-
- $formatLength = strlen( $format );
- for ( $p = 0; $p < $formatLength; $p++ ) {
- $char = $format[$p];
- switch ( $char ) {
- case 'd': # ISO day of month
- $text .= $bits['d'];
- break;
- case 'm': # ISO month
- $text .= $bits['m'];
- break;
- case 'y': # ISO year
- $text .= $bits['y'];
- break;
- case 'j': # ordinary day of month
- if ( !isset( $bits['j'] ) ) {
- $text .= intval( $bits['d'] );
- } else {
- $text .= $bits['j'];
+ // Pre-generate y/Y stuff because we need the year for the <span> title.
+ if ( !isset( $match['isoYear'] ) && isset( $match['year'] ) ) {
+ $match['isoYear'] = $this->makeIsoYear( $match['year'] );
+ }
+ if ( !isset( $match['year'] ) && isset( $match['isoYear'] ) ) {
+ $match['year'] = $this->makeNormalYear( $match['isoYear'] );
}
- break;
- case 'F': # long month
- if ( !isset( $bits['F'] ) ) {
- $m = intval( $bits['m'] );
- if ( $m > 12 || $m < 1 ) {
- $fail = true;
+
+ if ( !isset( $match['isoMonth'] ) ) {
+ $m = $this->makeIsoMonth( $match['monthName'] );
+ if ( $m === false ) {
+ // Fail
+ return $match[0];
} else {
- $text .= $this->lang->getMonthName( $m );
+ $match['isoMonth'] = $m;
}
- } else {
- $text .= ucfirst( $bits['F'] );
}
- break;
- case 'Y': # ordinary (optional BC) year
- $text .= $bits['Y'];
- break;
- default:
- $text .= $char;
- }
- }
- if ( $fail ) {
- // This occurs when parsing a date with day or month outside the bounds
- // of possibilities.
- return $orig;
- }
- $isoBits = [];
- if ( isset( $bits['y'] ) ) {
- $isoBits[] = $bits['y'];
- }
- $isoBits[] = $bits['m'];
- $isoBits[] = $bits['d'];
- $isoDate = implode( '-', $isoBits );
+ if ( !isset( $match['isoDay'] ) ) {
+ $match['isoDay'] = sprintf( '%02d', $match['day'] );
+ }
+
+ $formatLength = strlen( $format );
+ for ( $p = 0; $p < $formatLength; $p++ ) {
+ $char = $format[$p];
+ switch ( $char ) {
+ case 'd': // ISO day of month
+ $text .= $match['isoDay'];
+ break;
+ case 'm': // ISO month
+ $text .= $match['isoMonth'];
+ break;
+ case 'y': // ISO year
+ $text .= $match['isoYear'];
+ break;
+ case 'j': // ordinary day of month
+ if ( !isset( $match['day'] ) ) {
+ $text .= intval( $match['isoDay'] );
+ } else {
+ $text .= $match['day'];
+ }
+ break;
+ case 'F': // long month
+ $m = intval( $match['isoMonth'] );
+ if ( $m > 12 || $m < 1 ) {
+ // Fail
+ return $match[0];
+ } else {
+ $text .= $this->monthNames[$m];
+ }
+ break;
+ case 'Y': // ordinary (optional BC) year
+ $text .= $match['year'];
+ break;
+ default:
+ $text .= $char;
+ }
+ }
- // Output is not strictly HTML (it's wikitext), but <span> is whitelisted.
- $text = Html::rawElement( 'span',
- [ 'class' => 'mw-formatted-date', 'title' => $isoDate ], $text );
+ $isoBits = [];
+ if ( isset( $match['isoYear'] ) ) {
+ $isoBits[] = $match['isoYear'];
+ }
+ $isoBits[] = $match['isoMonth'];
+ $isoBits[] = $match['isoDay'];
+ $isoDate = implode( '-', $isoBits );
- return $text;
- }
+ // Output is not strictly HTML (it's wikitext), but <span> is whitelisted.
+ $text = Html::rawElement( 'span',
+ [ 'class' => 'mw-formatted-date', 'title' => $isoDate ], $text );
- /**
- * Return a regex that can be used to find month names in string
- * @return string regex to find the months with
- */
- private function getMonthRegex() {
- $names = [];
- for ( $i = 1; $i <= 12; $i++ ) {
- $names[] = preg_quote( $this->lang->getMonthName( $i ), '/' );
- $names[] = preg_quote( $this->lang->getMonthAbbreviation( $i ), '/' );
+ return $text;
+ }, $text
+ );
}
- return implode( '|', $names );
+ return $text;
}
/**
* @return string|false ISO month name, or false if the input was invalid
*/
private function makeIsoMonth( $monthName ) {
- $isoMonth = $this->xMonths[$this->lang->lc( $monthName )] ?? false;
+ $isoMonth = $this->xMonths[mb_strtolower( $monthName )] ?? false;
if ( $isoMonth === false ) {
return false;
}
* @return string ISO year name
*/
private function makeIsoYear( $year ) {
- # Assumes the year is in a nice format, as enforced by the regex
+ // Assumes the year is in a nice format, as enforced by the regex
if ( substr( $year, -2 ) == 'BC' ) {
$num = intval( substr( $year, 0, -3 ) ) - 1;
- # PHP bug note: sprintf( "%04d", -1 ) fails poorly
+ // PHP bug note: sprintf( "%04d", -1 ) fails poorly
$text = sprintf( '-%04d', $num );
-
} else {
$text = sprintf( '%04d', $year );
}
}
/**
- * Make a year one from an ISO year, for instance: '400 BC' from '-0399'.
+ * Make a year from an ISO year, for instance: '400 BC' from '-0399'.
* @param string $iso ISO year
* @return int|string int representing year number in case of AD dates, or string containing
* year number and 'BC' at the end otherwise.
--- /dev/null
+<?php
+
+class DateFormatterFactory {
+ /** @var DateFormatter[] */
+ private $instances;
+
+ /**
+ * @param Language $lang
+ * @return DateFormatter
+ */
+ public function get( Language $lang ) {
+ $code = $lang->getCode();
+ if ( !isset( $this->instances[$code] ) ) {
+ $this->instances[$code] = new DateFormatter( $lang );
+ }
+ return $this->instances[$code];
+ }
+}
/** @var LinkRendererFactory */
private $linkRendererFactory;
+ /** @var NamespaceInfo */
+ private $nsInfo;
+
/**
* @param array $parserConf See $wgParserConf documentation
* @param MagicWordFactory|null $magicWordFactory
* @param SpecialPageFactory|null $spFactory
* @param Config|null $siteConfig
* @param LinkRendererFactory|null $linkRendererFactory
+ * @param NamespaceInfo|null $nsInfo
*/
public function __construct(
array $parserConf = [], MagicWordFactory $magicWordFactory = null,
Language $contLang = null, ParserFactory $factory = null, $urlProtocols = null,
SpecialPageFactory $spFactory = null, Config $siteConfig = null,
- LinkRendererFactory $linkRendererFactory = null
+ LinkRendererFactory $linkRendererFactory = null,
+ NamespaceInfo $nsInfo = null
) {
$this->mConf = $parserConf;
$this->mUrlProtocols = $urlProtocols ?? wfUrlProtocols();
$this->factory = $factory ?? $services->getParserFactory();
$this->specialPageFactory = $spFactory ?? $services->getSpecialPageFactory();
- $this->siteConfig = $siteConfig ?? MediaWikiServices::getInstance()->getMainConfig();
-
+ $this->siteConfig = $siteConfig ?? $services->getMainConfig();
$this->linkRendererFactory =
- $linkRendererFactory ?? MediaWikiServices::getInstance()->getLinkRendererFactory();
+ $linkRendererFactory ?? $services->getLinkRendererFactory();
+ $this->nsInfo = $nsInfo ?? $services->getNamespaceInfo();
}
/**
*/
public function areSubpagesAllowed() {
# Some namespaces don't allow subpages
- return MWNamespace::hasSubpages( $this->mTitle->getNamespace() );
+ return $this->nsInfo->hasSubpages( $this->mTitle->getNamespace() );
}
/**
$this->siteConfig->get( 'MiserMode' ) &&
!$this->mOptions->getInterfaceMessage() &&
// @TODO: disallow this word on all namespaces
- MWNamespace::isContent( $this->mTitle->getNamespace() )
+ $this->nsInfo->isContent( $this->mTitle->getNamespace() )
) {
return $this->mRevisionId ? '-' : '';
};
);
}
}
- } elseif ( MWNamespace::isNonincludable( $title->getNamespace() ) ) {
+ } elseif ( $this->nsInfo->isNonincludable( $title->getNamespace() ) ) {
$found = false; # access denied
wfDebug( __METHOD__ . ": template inclusion denied for " .
$title->getPrefixedDBkey() . "\n" );
* @ingroup Parser
*/
use MediaWiki\Linker\LinkRendererFactory;
-
+use MediaWiki\MediaWikiServices;
use MediaWiki\Special\SpecialPageFactory;
/**
/** @var LinkRendererFactory */
private $linkRendererFactory;
+ /** @var NamespaceInfo */
+ private $nsInfo;
+
/**
* @param array $parserConf See $wgParserConf documentation
* @param MagicWordFactory $magicWordFactory
* @param SpecialPageFactory $spFactory
* @param Config $siteConfig
* @param LinkRendererFactory $linkRendererFactory
+ * @param NamespaceInfo|null $nsInfo
* @since 1.32
*/
public function __construct(
array $parserConf, MagicWordFactory $magicWordFactory, Language $contLang, $urlProtocols,
- SpecialPageFactory $spFactory, Config $siteConfig, LinkRendererFactory $linkRendererFactory
+ SpecialPageFactory $spFactory, Config $siteConfig,
+ LinkRendererFactory $linkRendererFactory, NamespaceInfo $nsInfo = null
) {
+ if ( !$nsInfo ) {
+ wfDeprecated( __METHOD__ . ' with no NamespaceInfo argument', '1.34' );
+ $nsInfo = MediaWikiServices::getInstance()->getNamespaceInfo();
+ }
$this->parserConf = $parserConf;
$this->magicWordFactory = $magicWordFactory;
$this->contLang = $contLang;
$this->specialPageFactory = $spFactory;
$this->siteConfig = $siteConfig;
$this->linkRendererFactory = $linkRendererFactory;
+ $this->nsInfo = $nsInfo;
}
/**
public function create() : Parser {
return new Parser( $this->parserConf, $this->magicWordFactory, $this->contLang, $this,
$this->urlProtocols, $this->specialPageFactory, $this->siteConfig,
- $this->linkRendererFactory );
+ $this->linkRendererFactory, $this->nsInfo );
}
}
* @return array
*/
protected function getFormFields() {
- global $wgBlockAllowsUTEdit;
+ $conf = $this->getConfig();
+ $enablePartialBlocks = $conf->get( 'EnablePartialBlocks' );
+ $blockAllowsUTEdit = $conf->get( 'BlockAllowsUTEdit' );
$this->getOutput()->enableOOUI();
$suggestedDurations = self::getSuggestedDurations();
- $conf = $this->getConfig();
- $enablePartialBlocks = $conf->get( 'EnablePartialBlocks' );
-
$a = [];
$a['Target'] = [
];
}
- if ( $wgBlockAllowsUTEdit ) {
+ if ( $blockAllowsUTEdit ) {
$a['DisableUTEdit'] = [
'type' => 'check',
'label-message' => 'ipb-disableusertalk',
* @file
*/
-use MediaWiki\MediaWikiServices;
-
class MWCryptRand {
- /**
- * @deprecated since 1.32
- * @return CryptRand
- */
- protected static function singleton() {
- wfDeprecated( __METHOD__, '1.32' );
- return MediaWikiServices::getInstance()->getCryptRand();
- }
-
- /**
- * Return a boolean indicating whether or not the source used for cryptographic
- * random bytes generation in the previously run generate* call
- * was cryptographically strong.
- *
- * @deprecated since 1.32, always returns true
- *
- * @return bool Always true
- */
- public static function wasStrong() {
- wfDeprecated( __METHOD__, '1.32' );
- return true;
- }
-
- /**
- * Generate a run of cryptographically random data and return
- * it in raw binary form.
- *
- * @deprecated since 1.32, use random_bytes()
- *
- * @param int $bytes The number of bytes of random data to generate
- * @return string Raw binary random data
- */
- public static function generate( $bytes ) {
- wfDeprecated( __METHOD__, '1.32' );
- return random_bytes( floor( $bytes ) );
- }
/**
* Generate a run of cryptographically random data and return
return $this->mHtmlCode;
}
- /**
- * @param string $code
- * @deprecated since 1.32, use Language::factory to create a new object instead.
- */
- public function setCode( $code ) {
- wfDeprecated( __METHOD__, '1.32' );
- $this->mCode = $code;
- // Ensure we don't leave incorrect cached data lying around
- $this->mHtmlCode = null;
- $this->mParentLanguage = false;
- }
-
/**
* Get the language code from a file name. Inverse of getFileName()
* @param string $filename $prefix . $languageCode . $suffix
require_once __DIR__ . '/../Maintenance.php';
+use Wikimedia\StaticArrayWriter;
+
/**
* Generate first letter data files for Collation.php
*
require_once __DIR__ . '/../Maintenance.php';
+use Wikimedia\StaticArrayWriter;
+
/**
* Generates the normalizer data file for Arabic.
*
require_once __DIR__ . '/../Maintenance.php';
+use Wikimedia\StaticArrayWriter;
+
/**
* Generates the normalizer data file for Malayalam.
*
],
],
'mediawiki.special.block' => [
- 'scripts' => 'resources/src/mediawiki.special.block.js',
+ 'localBasePath' => "$IP/resources/src",
+ 'remoteBasePath' => "$wgResourceBasePath/resources/src",
+ 'packageFiles' => [
+ 'mediawiki.special.block.js',
+ [ 'name' => 'config.json', 'config' => [
+ 'EnablePartialBlocks',
+ 'BlockAllowsUTEdit',
+ ] ],
+ ],
'dependencies' => [
'oojs-ui-core',
'oojs-ui.styles.icons-editing-core',
-/* Basic styles for the history page */
+/**
+ * Basic styles for the edit revision history page 'HistoryAction.php'
+ */
+
+// Trigger only when collapsible & JS is available via `.mw-collapsed`.
+#mw-history-search.mw-collapsed .oo-ui-fieldsetLayout-header .oo-ui-labelElement-label {
+ margin-bottom: 0;
+}
#pagehistory .history-user {
margin-left: 0.4em;
this.pollingRate = require( './config.json' ).StructuredChangeFiltersLiveUpdatePollingRate;
this.requestCounter = {};
- this.baseFilterState = {};
this.uriProcessor = null;
this.initialized = false;
this.wereSavedQueriesSaved = false;
return this.defaultFilters;
};
-/**
- * This is for a single_option and string_options group types
- * it returns the value of the default
- *
- * @return {string} Value of the default
- */
-FilterGroup.prototype.getDefaulParamValue = function () {
- return this.defaultParams[ this.getName() ];
-};
/**
* Get the messags defining the 'whats this' popup for this group
*
this.conflicts = conflicts;
};
-/**
- * Set conflicts for each filter item in the group based on the
- * given conflict map
- *
- * @param {Object} conflicts Object representing the conflict map,
- * keyed by the item name, where its value is an object for all its conflicts
- */
-FilterGroup.prototype.setFilterConflicts = function ( conflicts ) {
- this.getItems().forEach( function ( filterItem ) {
- if ( conflicts[ filterItem.getName() ] ) {
- filterItem.setConflicts( conflicts[ filterItem.getName() ] );
- }
- } );
-};
-
/**
* Check whether this item has a potential conflict with the given item
*
/**
* Get the prefix used for the filter names inside this group.
*
- * @param {string} [name] Filter name to prefix
* @return {string} Group prefix
*/
FilterGroup.prototype.getNamePrefix = function () {
/**
* Get the message for the display area for the currently active conflict
*
- * @private
* @return {string} Conflict result message key
*/
FilterItem.prototype.getCurrentConflictResultMessage = function () {
- var details = {};
+ var details;
// First look in filter's own conflicts
details = this.getConflictDetails( this.getOwnConflicts(), 'globalDescription' );
*/
FiltersViewModel.prototype.initializeFilters = function ( filterGroups, views ) {
var filterConflictResult, groupConflictResult,
- allViews = {},
+ allViews,
model = this,
items = [],
groupConflictMap = {},
/**
* Respond to limit choose event
- *
- * @param {string} filterName Filter name
*/
ChangesLimitAndDateButtonWidget.prototype.updateButtonLabel = function () {
var message,
this.filtersViewModel = filtersViewModel;
this.changesListViewModel = changesListViewModel;
this.controller = controller;
- this.highlightClasses = null;
// Events
this.filtersViewModel.connect( this, {
OO.inheritClass( ChangesListWrapperWidget, OO.ui.Widget );
-/**
- * Get all available highlight classes
- *
- * @return {string[]} An array of available highlight class names
- */
-ChangesListWrapperWidget.prototype.getHighlightClasses = function () {
- if ( !this.highlightClasses || !this.highlightClasses.length ) {
- this.highlightClasses = this.filtersViewModel.getItemsSupportingHighlights()
- .map( function ( filterItem ) {
- return filterItem.getCssClass();
- } );
- }
-
- return this.highlightClasses;
-};
-
/**
* Respond to the highlight feature being toggled on and off
*
*
* @constructor
* @param {Object} [config] Configuration object
- * @param {Object} [events] Events to aggregate. The object represent the
+ * @cfg {Object} [events] Events to aggregate. The object represent the
* event name to aggregate and the event value to emit on aggregate for items.
*/
var GroupWidget = function MwRcfiltersUiViewSwitchWidget( config ) {
/**
* Save the name of the query
*
- * @param {string} [value] The value to save
* @fires edit
*/
SavedLinksListItemWidget.prototype.save = function () {
}
$( function () {
- // This code is also loaded on the "block succeeded" page where there is no form,
- // so username and expiry fields might also be missing.
- var blockTargetWidget = infuseIfExists( $( '#mw-bi-target' ) ),
- anonOnlyField = infuseIfExists( $( '#mw-input-wpHardBlock' ).closest( '.oo-ui-fieldLayout' ) ),
- enableAutoblockField = infuseIfExists( $( '#mw-input-wpAutoBlock' ).closest( '.oo-ui-fieldLayout' ) ),
- hideUserWidget = infuseIfExists( $( '#mw-input-wpHideUser' ) ),
- hideUserField = infuseIfExists( $( '#mw-input-wpHideUser' ).closest( '.oo-ui-fieldLayout' ) ),
- watchUserField = infuseIfExists( $( '#mw-input-wpWatch' ).closest( '.oo-ui-fieldLayout' ) ),
- expiryWidget = infuseIfExists( $( '#mw-input-wpExpiry' ) ),
- editingWidget = infuseIfExists( $( '#mw-input-wpEditing' ) ),
- editingRestrictionWidget = infuseIfExists( $( '#mw-input-wpEditingRestriction' ) ),
- preventTalkPageEdit = infuseIfExists( $( '#mw-input-wpDisableUTEdit' ) ),
- pageRestrictionsWidget = infuseIfExists( $( '#mw-input-wpPageRestrictions' ) ),
- namespaceRestrictionsWidget = infuseIfExists( $( '#mw-input-wpNamespaceRestrictions' ) ),
- createAccountWidget = infuseIfExists( $( '#mw-input-wpCreateAccount' ) ),
- userChangedCreateAccount = mw.config.get( 'wgCreateAccountDirty' ),
- updatingBlockOptions = false;
+ var blockTargetWidget, anonOnlyWidget, enableAutoblockWidget, hideUserWidget, watchUserWidget,
+ expiryWidget, editingWidget, editingRestrictionWidget, preventTalkPageEditWidget,
+ pageRestrictionsWidget, namespaceRestrictionsWidget, createAccountWidget, data,
+ enablePartialBlocks, blockAllowsUTEdit, userChangedCreateAccount, updatingBlockOptions;
function updateBlockOptions() {
var blocktarget = blockTargetWidget.getValue().trim(),
// infinityValues are the values the SpecialBlock class accepts as infinity (sf. wfIsInfinity)
infinityValues = [ 'infinite', 'indefinite', 'infinity', 'never' ],
isIndefinite = infinityValues.indexOf( expiryValue ) !== -1,
- // editingRestrictionWidget only exists if partial blocks is enabled; if not, block must be sitewide
- editingRestrictionValue = editingRestrictionWidget ? editingRestrictionWidget.getValue() : 'sitewide',
- editingIsSelected = editingWidget ? editingWidget.isSelected() : false,
+ editingRestrictionValue = enablePartialBlocks ? editingRestrictionWidget.getValue() : 'sitewide',
+ editingIsSelected = editingWidget.isSelected(),
isSitewide = editingIsSelected && editingRestrictionValue === 'sitewide';
- if ( enableAutoblockField ) {
- enableAutoblockField.toggle( !isNonEmptyIp );
+ enableAutoblockWidget.setDisabled( isNonEmptyIp );
+ if ( enableAutoblockWidget.isDisabled() ) {
+ enableAutoblockWidget.setSelected( false );
+ }
+
+ anonOnlyWidget.setDisabled( !isIp && !isEmpty );
+ if ( anonOnlyWidget.isDisabled() ) {
+ anonOnlyWidget.setSelected( false );
}
- if ( hideUserField ) {
- hideUserField.toggle( !isNonEmptyIp && isIndefinite && isSitewide );
- if ( !hideUserField.isVisible() ) {
+
+ if ( hideUserWidget ) {
+ hideUserWidget.setDisabled( isNonEmptyIp || !isIndefinite || !isSitewide );
+ if ( hideUserWidget.isDisabled() ) {
hideUserWidget.setSelected( false );
}
}
- if ( anonOnlyField ) {
- anonOnlyField.toggle( isIp || isEmpty );
- }
- if ( watchUserField ) {
- watchUserField.toggle( !isIpRange || isEmpty );
+
+ if ( watchUserWidget ) {
+ watchUserWidget.setDisabled( isIpRange && !isEmpty );
+ if ( watchUserWidget.isDisabled() ) {
+ watchUserWidget.setSelected( false );
+ }
}
- if ( editingRestrictionWidget ) {
+
+ if ( enablePartialBlocks ) {
editingRestrictionWidget.setDisabled( !editingIsSelected );
- }
- if ( pageRestrictionsWidget ) {
pageRestrictionsWidget.setDisabled( !editingIsSelected || isSitewide );
- }
- if ( namespaceRestrictionsWidget ) {
namespaceRestrictionsWidget.setDisabled( !editingIsSelected || isSitewide );
+ if ( blockAllowsUTEdit ) {
+ // This option is disabled for partial blocks unless a namespace restriction
+ // for the User_talk namespace is in place.
+ preventTalkPageEditWidget.setDisabled(
+ editingIsSelected &&
+ editingRestrictionValue === 'partial' &&
+ namespaceRestrictionsWidget.getValue().indexOf(
+ String( mw.config.get( 'wgNamespaceIds' ).user_talk )
+ ) === -1
+ );
+ }
}
- if ( preventTalkPageEdit && namespaceRestrictionsWidget ) {
- // This option is disabled for partial blocks unless a namespace restriction
- // for the User_talk namespace is in place.
- preventTalkPageEdit.setDisabled(
- editingIsSelected &&
- editingRestrictionValue === 'partial' &&
- namespaceRestrictionsWidget.getValue().indexOf(
- String( mw.config.get( 'wgNamespaceIds' ).user_talk )
- ) === -1
- );
- }
+
if ( !userChangedCreateAccount ) {
updatingBlockOptions = true;
createAccountWidget.setSelected( isSitewide );
}
+ // This code is also loaded on the "block succeeded" page where there is no form,
+ // so check for block target widget; if it exists, the form is present
+ blockTargetWidget = infuseIfExists( $( '#mw-bi-target' ) );
+
if ( blockTargetWidget ) {
- // Bind functions so they're checked whenever stuff changes
+ data = require( './config.json' );
+ enablePartialBlocks = data.EnablePartialBlocks;
+ blockAllowsUTEdit = data.BlockAllowsUTEdit;
+ userChangedCreateAccount = mw.config.get( 'wgCreateAccountDirty' );
+ updatingBlockOptions = false;
+
+ // Always present if blockTargetWidget is present
+ editingWidget = OO.ui.infuse( $( '#mw-input-wpEditing' ) );
+ expiryWidget = OO.ui.infuse( $( '#mw-input-wpExpiry' ) );
+ createAccountWidget = OO.ui.infuse( $( '#mw-input-wpCreateAccount' ) );
+ enableAutoblockWidget = OO.ui.infuse( $( '#mw-input-wpAutoBlock' ) );
+ anonOnlyWidget = OO.ui.infuse( $( '#mw-input-wpHardBlock' ) );
blockTargetWidget.on( 'change', updateBlockOptions );
+ editingWidget.on( 'change', updateBlockOptions );
expiryWidget.on( 'change', updateBlockOptions );
- if ( editingWidget ) {
- editingWidget.on( 'change', updateBlockOptions );
- }
- if ( editingRestrictionWidget ) {
- editingRestrictionWidget.on( 'change', updateBlockOptions );
- }
- if ( namespaceRestrictionsWidget ) {
- namespaceRestrictionsWidget.on( 'change', updateBlockOptions );
- }
-
createAccountWidget.on( 'change', function () {
if ( !updatingBlockOptions ) {
userChangedCreateAccount = true;
}
} );
- // Call them now to set initial state (ie. Special:Block/Foobar?wpBlockExpiry=2+hours)
+ // Present for certain rights
+ watchUserWidget = infuseIfExists( $( '#mw-input-wpWatch' ) );
+ hideUserWidget = infuseIfExists( $( '#mw-input-wpHideUser' ) );
+
+ // Present for certain global configs
+ if ( enablePartialBlocks ) {
+ editingRestrictionWidget = OO.ui.infuse( $( '#mw-input-wpEditingRestriction' ) );
+ pageRestrictionsWidget = OO.ui.infuse( $( '#mw-input-wpPageRestrictions' ) );
+ namespaceRestrictionsWidget = OO.ui.infuse( $( '#mw-input-wpNamespaceRestrictions' ) );
+ editingRestrictionWidget.on( 'change', updateBlockOptions );
+ namespaceRestrictionsWidget.on( 'change', updateBlockOptions );
+ }
+ if ( blockAllowsUTEdit ) {
+ preventTalkPageEditWidget = infuseIfExists( $( '#mw-input-wpDisableUTEdit' ) );
+ }
+
updateBlockOptions();
}
} );
</p>
!! end
+!! test
+formatdate with invalid month
+!! wikitext
+{{#formatdate:2019-22-22|dmy}}
+!! html
+<p>2019-22-22
+</p>
+!! end
+
+!! test
+formatdate: dots in month name do not match any char (T220563)
+!! options
+language=de
+!! wikitext
+{{#formatdate:jun. 3|dmy}}
+{{#formatdate:junx 3|dmy}}
+!! html
+<p><span class="mw-formatted-date" title="06-03">3 Juni</span>
+junx 3
+</p>
+!! end
+
+!! test
+formatdate uses correct capitalisation in French
+!! options
+language=fr
+!! wikitext
+{{#formatdate:Juin 3|dmy}}
+!! html
+<p><span class="mw-formatted-date" title="06-03">3 juin</span>
+</p>
+!! end
+
+!! test
+formatdate uses correct capitalisation in English
+!! wikitext
+{{#formatdate:june 3|dmy}}
+!! html
+<p><span class="mw-formatted-date" title="06-03">3 June</span>
+</p>
+!! end
+
+
#
#
#
* @group MediaWiki
*/
class MediaWikiServicesTest extends MediaWikiTestCase {
- private $deprecatedServices = [ 'CryptRand' ];
+ private $deprecatedServices = [];
/**
* @return Config
// check
assert.strictEqual( EditPage.heading.getText(), name );
- assert.strictEqual( EditPage.displayedContent.getText(), editContent );
+ assert( EditPage.displayedContent.getText().match( new RegExp( editContent ) ) );
} );
it( 'should have history @daily', function () {