'HTMLFileCache' => __DIR__ . '/includes/cache/HTMLFileCache.php',
'HTMLFloatField' => __DIR__ . '/includes/htmlform/fields/HTMLFloatField.php',
'HTMLForm' => __DIR__ . '/includes/htmlform/HTMLForm.php',
+ 'HTMLFormActionFieldLayout' => __DIR__ . '/includes/htmlform/HTMLFormElement.php',
+ 'HTMLFormElement' => __DIR__ . '/includes/htmlform/HTMLFormElement.php',
'HTMLFormField' => __DIR__ . '/includes/htmlform/HTMLFormField.php',
'HTMLFormFieldCloner' => __DIR__ . '/includes/htmlform/fields/HTMLFormFieldCloner.php',
+ 'HTMLFormFieldLayout' => __DIR__ . '/includes/htmlform/HTMLFormElement.php',
'HTMLFormFieldRequiredOptionsException' => __DIR__ . '/includes/htmlform/HTMLFormFieldRequiredOptionsException.php',
'HTMLFormFieldWithButton' => __DIR__ . '/includes/htmlform/fields/HTMLFormFieldWithButton.php',
'HTMLHiddenField' => __DIR__ . '/includes/htmlform/fields/HTMLHiddenField.php',
'MwSql' => __DIR__ . '/maintenance/sql.php',
'MySQLField' => __DIR__ . '/includes/db/DatabaseMysqlBase.php',
'MySQLMasterPos' => __DIR__ . '/includes/db/DatabaseMysqlBase.php',
- 'MySqlLockManager' => __DIR__ . '/includes/filebackend/lockmanager/DBLockManager.php',
+ 'MySqlLockManager' => __DIR__ . '/includes/filebackend/lockmanager/MySqlLockManager.php',
'MysqlInstaller' => __DIR__ . '/includes/installer/MysqlInstaller.php',
'MysqlUpdater' => __DIR__ . '/includes/installer/MysqlUpdater.php',
'NaiveForeignTitleFactory' => __DIR__ . '/includes/title/NaiveForeignTitleFactory.php',
'PopulateRecentChangesSource' => __DIR__ . '/maintenance/populateRecentChangesSource.php',
'PopulateRevisionLength' => __DIR__ . '/maintenance/populateRevisionLength.php',
'PopulateRevisionSha1' => __DIR__ . '/maintenance/populateRevisionSha1.php',
- 'PostgreSqlLockManager' => __DIR__ . '/includes/filebackend/lockmanager/DBLockManager.php',
+ 'PostgreSqlLockManager' => __DIR__ . '/includes/filebackend/lockmanager/PostgreSqlLockManager.php',
'PostgresBlob' => __DIR__ . '/includes/db/DatabasePostgres.php',
'PostgresField' => __DIR__ . '/includes/db/DatabasePostgres.php',
'PostgresInstaller' => __DIR__ . '/includes/installer/PostgresInstaller.php',
/**
* Full path on the web server where shared uploads can be found
*/
-$wgSharedUploadPath = "http://commons.wikimedia.org/shared/images";
+$wgSharedUploadPath = null;
/**
* Fetch commons image description pages and display them on the local wiki?
/**
* Path on the file system where shared uploads can be found.
*/
-$wgSharedUploadDirectory = "/var/www/wiki3/images";
+$wgSharedUploadDirectory = null;
/**
* DB name with metadata about shared directory.
if ( $protected ) {
# Protect the redirect title as the title used to be...
- $dbw->insertSelect( 'page_restrictions', 'page_restrictions',
- [
- 'pr_page' => $redirid,
- 'pr_type' => 'pr_type',
- 'pr_level' => 'pr_level',
- 'pr_cascade' => 'pr_cascade',
- 'pr_user' => 'pr_user',
- 'pr_expiry' => 'pr_expiry'
- ],
+ $res = $dbw->select(
+ 'page_restrictions',
+ '*',
[ 'pr_page' => $pageid ],
__METHOD__,
- [ 'IGNORE' ]
+ 'FOR UPDATE'
);
+ $rowsInsert = [];
+ foreach ( $res as $row ) {
+ $rowsInsert[] = [
+ 'pr_page' => $redirid,
+ 'pr_type' => $row->pr_type,
+ 'pr_level' => $row->pr_level,
+ 'pr_cascade' => $row->pr_cascade,
+ 'pr_user' => $row->pr_user,
+ 'pr_expiry' => $row->pr_expiry
+ ];
+ }
+ $dbw->insert( 'page_restrictions', $rowsInsert, __METHOD__, [ 'IGNORE' ] );
// Build comment for log
$comment = wfMessage(
],
'attachedwiki' => null,
'users' => [
- ApiBase::PARAM_TYPE => 'user',
ApiBase::PARAM_ISMULTI => true
],
'token' => [
$code = $overrideCode;
}
if ( $moreExtraData ) {
+ $extraData = $extraData ?: [];
$extraData += $moreExtraData;
}
$this->dieUsage( $msg, $code, 0, $extraData );
}
}
}
-
-/**
- * MySQL version of DBLockManager that supports shared locks.
- *
- * All lock servers must have the innodb table defined in locking/filelocks.sql.
- * All locks are non-blocking, which avoids deadlocks.
- *
- * @ingroup LockManager
- */
-class MySqlLockManager extends DBLockManager {
- /** @var array Mapping of lock types to the type actually used */
- protected $lockTypeMap = [
- self::LOCK_SH => self::LOCK_SH,
- self::LOCK_UW => self::LOCK_SH,
- self::LOCK_EX => self::LOCK_EX
- ];
-
- protected function getLocalLB() {
- // Use a separate connection so releaseAllLocks() doesn't rollback the main trx
- return wfGetLBFactory()->newMainLB( $this->domain );
- }
-
- protected function initConnection( $lockDb, IDatabase $db ) {
- # Let this transaction see lock rows from other transactions
- $db->query( "SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;" );
- }
-
- /**
- * Get a connection to a lock DB and acquire locks on $paths.
- * This does not use GET_LOCK() per http://bugs.mysql.com/bug.php?id=1118.
- *
- * @see DBLockManager::getLocksOnServer()
- * @param string $lockSrv
- * @param array $paths
- * @param string $type
- * @return Status
- */
- protected function doGetLocksOnServer( $lockSrv, array $paths, $type ) {
- $status = Status::newGood();
-
- $db = $this->getConnection( $lockSrv ); // checked in isServerUp()
-
- $keys = []; // list of hash keys for the paths
- $data = []; // list of rows to insert
- $checkEXKeys = []; // list of hash keys that this has no EX lock on
- # Build up values for INSERT clause
- foreach ( $paths as $path ) {
- $key = $this->sha1Base36Absolute( $path );
- $keys[] = $key;
- $data[] = [ 'fls_key' => $key, 'fls_session' => $this->session ];
- if ( !isset( $this->locksHeld[$path][self::LOCK_EX] ) ) {
- $checkEXKeys[] = $key;
- }
- }
-
- # Block new writers (both EX and SH locks leave entries here)...
- $db->insert( 'filelocks_shared', $data, __METHOD__, [ 'IGNORE' ] );
- # Actually do the locking queries...
- if ( $type == self::LOCK_SH ) { // reader locks
- $blocked = false;
- # Bail if there are any existing writers...
- if ( count( $checkEXKeys ) ) {
- $blocked = $db->selectField( 'filelocks_exclusive', '1',
- [ 'fle_key' => $checkEXKeys ],
- __METHOD__
- );
- }
- # Other prospective writers that haven't yet updated filelocks_exclusive
- # will recheck filelocks_shared after doing so and bail due to this entry.
- } else { // writer locks
- $encSession = $db->addQuotes( $this->session );
- # Bail if there are any existing writers...
- # This may detect readers, but the safe check for them is below.
- # Note: if two writers come at the same time, both bail :)
- $blocked = $db->selectField( 'filelocks_shared', '1',
- [ 'fls_key' => $keys, "fls_session != $encSession" ],
- __METHOD__
- );
- if ( !$blocked ) {
- # Build up values for INSERT clause
- $data = [];
- foreach ( $keys as $key ) {
- $data[] = [ 'fle_key' => $key ];
- }
- # Block new readers/writers...
- $db->insert( 'filelocks_exclusive', $data, __METHOD__ );
- # Bail if there are any existing readers...
- $blocked = $db->selectField( 'filelocks_shared', '1',
- [ 'fls_key' => $keys, "fls_session != $encSession" ],
- __METHOD__
- );
- }
- }
-
- if ( $blocked ) {
- foreach ( $paths as $path ) {
- $status->fatal( 'lockmanager-fail-acquirelock', $path );
- }
- }
-
- return $status;
- }
-
- /**
- * @see QuorumLockManager::releaseAllLocks()
- * @return Status
- */
- protected function releaseAllLocks() {
- $status = Status::newGood();
-
- foreach ( $this->conns as $lockDb => $db ) {
- if ( $db->trxLevel() ) { // in transaction
- try {
- $db->rollback( __METHOD__ ); // finish transaction and kill any rows
- } catch ( DBError $e ) {
- $status->fatal( 'lockmanager-fail-db-release', $lockDb );
- }
- }
- }
-
- return $status;
- }
-}
-
-/**
- * PostgreSQL version of DBLockManager that supports shared locks.
- * All locks are non-blocking, which avoids deadlocks.
- *
- * @ingroup LockManager
- */
-class PostgreSqlLockManager extends DBLockManager {
- /** @var array Mapping of lock types to the type actually used */
- protected $lockTypeMap = [
- self::LOCK_SH => self::LOCK_SH,
- self::LOCK_UW => self::LOCK_SH,
- self::LOCK_EX => self::LOCK_EX
- ];
-
- protected function doGetLocksOnServer( $lockSrv, array $paths, $type ) {
- $status = Status::newGood();
- if ( !count( $paths ) ) {
- return $status; // nothing to lock
- }
-
- $db = $this->getConnection( $lockSrv ); // checked in isServerUp()
- $bigints = array_unique( array_map(
- function ( $key ) {
- return Wikimedia\base_convert( substr( $key, 0, 15 ), 16, 10 );
- },
- array_map( [ $this, 'sha1Base16Absolute' ], $paths )
- ) );
-
- // Try to acquire all the locks...
- $fields = [];
- foreach ( $bigints as $bigint ) {
- $fields[] = ( $type == self::LOCK_SH )
- ? "pg_try_advisory_lock_shared({$db->addQuotes( $bigint )}) AS K$bigint"
- : "pg_try_advisory_lock({$db->addQuotes( $bigint )}) AS K$bigint";
- }
- $res = $db->query( 'SELECT ' . implode( ', ', $fields ), __METHOD__ );
- $row = $res->fetchRow();
-
- if ( in_array( 'f', $row ) ) {
- // Release any acquired locks if some could not be acquired...
- $fields = [];
- foreach ( $row as $kbigint => $ok ) {
- if ( $ok === 't' ) { // locked
- $bigint = substr( $kbigint, 1 ); // strip off the "K"
- $fields[] = ( $type == self::LOCK_SH )
- ? "pg_advisory_unlock_shared({$db->addQuotes( $bigint )})"
- : "pg_advisory_unlock({$db->addQuotes( $bigint )})";
- }
- }
- if ( count( $fields ) ) {
- $db->query( 'SELECT ' . implode( ', ', $fields ), __METHOD__ );
- }
- foreach ( $paths as $path ) {
- $status->fatal( 'lockmanager-fail-acquirelock', $path );
- }
- }
-
- return $status;
- }
-
- /**
- * @see QuorumLockManager::releaseAllLocks()
- * @return Status
- */
- protected function releaseAllLocks() {
- $status = Status::newGood();
-
- foreach ( $this->conns as $lockDb => $db ) {
- try {
- $db->query( "SELECT pg_advisory_unlock_all()", __METHOD__ );
- } catch ( DBError $e ) {
- $status->fatal( 'lockmanager-fail-db-release', $lockDb );
- }
- }
-
- return $status;
- }
-}
--- /dev/null
+<?php
+/**
+ * MySQL version of DBLockManager that supports shared locks.
+ *
+ * All lock servers must have the innodb table defined in locking/filelocks.sql.
+ * All locks are non-blocking, which avoids deadlocks.
+ *
+ * @ingroup LockManager
+ */
+class MySqlLockManager extends DBLockManager {
+ /** @var array Mapping of lock types to the type actually used */
+ protected $lockTypeMap = [
+ self::LOCK_SH => self::LOCK_SH,
+ self::LOCK_UW => self::LOCK_SH,
+ self::LOCK_EX => self::LOCK_EX
+ ];
+
+ protected function getLocalLB() {
+ // Use a separate connection so releaseAllLocks() doesn't rollback the main trx
+ return wfGetLBFactory()->newMainLB( $this->domain );
+ }
+
+ protected function initConnection( $lockDb, IDatabase $db ) {
+ # Let this transaction see lock rows from other transactions
+ $db->query( "SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;" );
+ }
+
+ /**
+ * Get a connection to a lock DB and acquire locks on $paths.
+ * This does not use GET_LOCK() per http://bugs.mysql.com/bug.php?id=1118.
+ *
+ * @see DBLockManager::getLocksOnServer()
+ * @param string $lockSrv
+ * @param array $paths
+ * @param string $type
+ * @return Status
+ */
+ protected function doGetLocksOnServer( $lockSrv, array $paths, $type ) {
+ $status = Status::newGood();
+
+ $db = $this->getConnection( $lockSrv ); // checked in isServerUp()
+
+ $keys = []; // list of hash keys for the paths
+ $data = []; // list of rows to insert
+ $checkEXKeys = []; // list of hash keys that this has no EX lock on
+ # Build up values for INSERT clause
+ foreach ( $paths as $path ) {
+ $key = $this->sha1Base36Absolute( $path );
+ $keys[] = $key;
+ $data[] = [ 'fls_key' => $key, 'fls_session' => $this->session ];
+ if ( !isset( $this->locksHeld[$path][self::LOCK_EX] ) ) {
+ $checkEXKeys[] = $key;
+ }
+ }
+
+ # Block new writers (both EX and SH locks leave entries here)...
+ $db->insert( 'filelocks_shared', $data, __METHOD__, [ 'IGNORE' ] );
+ # Actually do the locking queries...
+ if ( $type == self::LOCK_SH ) { // reader locks
+ $blocked = false;
+ # Bail if there are any existing writers...
+ if ( count( $checkEXKeys ) ) {
+ $blocked = $db->selectField( 'filelocks_exclusive', '1',
+ [ 'fle_key' => $checkEXKeys ],
+ __METHOD__
+ );
+ }
+ # Other prospective writers that haven't yet updated filelocks_exclusive
+ # will recheck filelocks_shared after doing so and bail due to this entry.
+ } else { // writer locks
+ $encSession = $db->addQuotes( $this->session );
+ # Bail if there are any existing writers...
+ # This may detect readers, but the safe check for them is below.
+ # Note: if two writers come at the same time, both bail :)
+ $blocked = $db->selectField( 'filelocks_shared', '1',
+ [ 'fls_key' => $keys, "fls_session != $encSession" ],
+ __METHOD__
+ );
+ if ( !$blocked ) {
+ # Build up values for INSERT clause
+ $data = [];
+ foreach ( $keys as $key ) {
+ $data[] = [ 'fle_key' => $key ];
+ }
+ # Block new readers/writers...
+ $db->insert( 'filelocks_exclusive', $data, __METHOD__ );
+ # Bail if there are any existing readers...
+ $blocked = $db->selectField( 'filelocks_shared', '1',
+ [ 'fls_key' => $keys, "fls_session != $encSession" ],
+ __METHOD__
+ );
+ }
+ }
+
+ if ( $blocked ) {
+ foreach ( $paths as $path ) {
+ $status->fatal( 'lockmanager-fail-acquirelock', $path );
+ }
+ }
+
+ return $status;
+ }
+
+ /**
+ * @see QuorumLockManager::releaseAllLocks()
+ * @return Status
+ */
+ protected function releaseAllLocks() {
+ $status = Status::newGood();
+
+ foreach ( $this->conns as $lockDb => $db ) {
+ if ( $db->trxLevel() ) { // in transaction
+ try {
+ $db->rollback( __METHOD__ ); // finish transaction and kill any rows
+ } catch ( DBError $e ) {
+ $status->fatal( 'lockmanager-fail-db-release', $lockDb );
+ }
+ }
+ }
+
+ return $status;
+ }
+}
--- /dev/null
+<?php
+/**
+ * PostgreSQL version of DBLockManager that supports shared locks.
+ * All locks are non-blocking, which avoids deadlocks.
+ *
+ * @ingroup LockManager
+ */
+class PostgreSqlLockManager extends DBLockManager {
+ /** @var array Mapping of lock types to the type actually used */
+ protected $lockTypeMap = [
+ self::LOCK_SH => self::LOCK_SH,
+ self::LOCK_UW => self::LOCK_SH,
+ self::LOCK_EX => self::LOCK_EX
+ ];
+
+ protected function doGetLocksOnServer( $lockSrv, array $paths, $type ) {
+ $status = Status::newGood();
+ if ( !count( $paths ) ) {
+ return $status; // nothing to lock
+ }
+
+ $db = $this->getConnection( $lockSrv ); // checked in isServerUp()
+ $bigints = array_unique( array_map(
+ function ( $key ) {
+ return Wikimedia\base_convert( substr( $key, 0, 15 ), 16, 10 );
+ },
+ array_map( [ $this, 'sha1Base16Absolute' ], $paths )
+ ) );
+
+ // Try to acquire all the locks...
+ $fields = [];
+ foreach ( $bigints as $bigint ) {
+ $fields[] = ( $type == self::LOCK_SH )
+ ? "pg_try_advisory_lock_shared({$db->addQuotes( $bigint )}) AS K$bigint"
+ : "pg_try_advisory_lock({$db->addQuotes( $bigint )}) AS K$bigint";
+ }
+ $res = $db->query( 'SELECT ' . implode( ', ', $fields ), __METHOD__ );
+ $row = $res->fetchRow();
+
+ if ( in_array( 'f', $row ) ) {
+ // Release any acquired locks if some could not be acquired...
+ $fields = [];
+ foreach ( $row as $kbigint => $ok ) {
+ if ( $ok === 't' ) { // locked
+ $bigint = substr( $kbigint, 1 ); // strip off the "K"
+ $fields[] = ( $type == self::LOCK_SH )
+ ? "pg_advisory_unlock_shared({$db->addQuotes( $bigint )})"
+ : "pg_advisory_unlock({$db->addQuotes( $bigint )})";
+ }
+ }
+ if ( count( $fields ) ) {
+ $db->query( 'SELECT ' . implode( ', ', $fields ), __METHOD__ );
+ }
+ foreach ( $paths as $path ) {
+ $status->fatal( 'lockmanager-fail-acquirelock', $path );
+ }
+ }
+
+ return $status;
+ }
+
+ /**
+ * @see QuorumLockManager::releaseAllLocks()
+ * @return Status
+ */
+ protected function releaseAllLocks() {
+ $status = Status::newGood();
+
+ foreach ( $this->conns as $lockDb => $db ) {
+ try {
+ $db->query( "SELECT pg_advisory_unlock_all()", __METHOD__ );
+ } catch ( DBError $e ) {
+ $status->fatal( 'lockmanager-fail-db-release', $lockDb );
+ }
+ }
+
+ return $status;
+ }
+}
--- /dev/null
+<?php
+
+/**
+ * Allows custom data specific to HTMLFormField to be set for OOjs UI forms. A matching JS widget
+ * (defined in htmlform.Element.js) picks up the extra config when constructed using OO.ui.infuse().
+ *
+ * Currently only supports passing 'hide-if' data.
+ */
+trait HTMLFormElement {
+
+ protected $hideIf = null;
+ protected $modules = null;
+
+ public function initializeHTMLFormElement( array $config = [] ) {
+ // Properties
+ $this->hideIf = isset( $config['hideIf'] ) ? $config['hideIf'] : null;
+ $this->modules = isset( $config['modules'] ) ? $config['modules'] : [];
+
+ // Initialization
+ if ( $this->hideIf ) {
+ $this->addClasses( [ 'mw-htmlform-hide-if' ] );
+ }
+ if ( $this->modules ) {
+ // JS code must be able to read this before infusing (before OOjs UI is even loaded),
+ // so we put this in a separate attribute (not with the rest of the config).
+ // And it's not needed anymore after infusing, so we don't put it in JS config at all.
+ $this->setAttributes( [ 'data-mw-modules' => implode( ',', $this->modules ) ] );
+ }
+ $this->registerConfigCallback( function( &$config ) {
+ if ( $this->hideIf !== null ) {
+ $config['hideIf'] = $this->hideIf;
+ }
+ } );
+ }
+}
+
+class HTMLFormFieldLayout extends OOUI\FieldLayout {
+ use HTMLFormElement;
+
+ public function __construct( $fieldWidget, array $config = [] ) {
+ // Parent constructor
+ parent::__construct( $fieldWidget, $config );
+ // Traits
+ $this->initializeHTMLFormElement( $config );
+ }
+
+ protected function getJavaScriptClassName() {
+ return 'mw.htmlform.FieldLayout';
+ }
+}
+
+class HTMLFormActionFieldLayout extends OOUI\ActionFieldLayout {
+ use HTMLFormElement;
+
+ public function __construct( $fieldWidget, $buttonWidget = false, array $config = [] ) {
+ // Parent constructor
+ parent::__construct( $fieldWidget, $buttonWidget, $config );
+ // Traits
+ $this->initializeHTMLFormElement( $config );
+ }
+
+ protected function getJavaScriptClassName() {
+ return 'mw.htmlform.ActionFieldLayout';
+ }
+}
'infusable' => $infusable,
];
+ $preloadModules = false;
+
if ( $infusable && $this->shouldInfuseOOUI() ) {
- $this->mParent->getOutput()->addModules( 'oojs-ui-core' );
+ $preloadModules = true;
$config['classes'][] = 'mw-htmlform-field-autoinfuse';
}
$config['label'] = new OOUI\HtmlSnippet( $label );
}
+ if ( $this->mHideIf ) {
+ $preloadModules = true;
+ $config['hideIf'] = $this->mHideIf;
+ }
+
+ $config['modules'] = $this->getOOUIModules();
+
+ if ( $preloadModules ) {
+ $this->mParent->getOutput()->addModules( 'mediawiki.htmlform.ooui' );
+ $this->mParent->getOutput()->addModules( $this->getOOUIModules() );
+ }
+
return $this->getFieldLayoutOOUI( $inputField, $config );
}
protected function getFieldLayoutOOUI( $inputField, $config ) {
if ( isset( $this->mClassWithButton ) ) {
$buttonWidget = $this->mClassWithButton->getInputOOUI( '' );
- return new OOUI\ActionFieldLayout( $inputField, $buttonWidget, $config );
+ return new HTMLFormActionFieldLayout( $inputField, $buttonWidget, $config );
}
- return new OOUI\FieldLayout( $inputField, $config );
+ return new HTMLFormFieldLayout( $inputField, $config );
}
/**
return $this->getHelpText() !== null;
}
+ /**
+ * Get the list of extra ResourceLoader modules which must be loaded client-side before it's
+ * possible to infuse this field's OOjs UI widget.
+ *
+ * @return string[]
+ */
+ protected function getOOUIModules() {
+ return [];
+ }
+
/**
* Get the complete raw fields for the input, including help text,
* labels, and whatever.
] );
}
+ protected function getOOUIModules() {
+ // FIXME: NamespaceInputWidget should be in its own module (probably?)
+ return [ 'mediawiki.widgets' ];
+ }
+
protected function shouldInfuseOOUI() {
return true;
}
}
protected function getInputWidget( $params ) {
- $this->mParent->getOutput()->addModules( 'mediawiki.widgets' );
if ( $this->mParams['namespace'] !== false ) {
$params['namespace'] = $this->mParams['namespace'];
}
return true;
}
+ protected function getOOUIModules() {
+ // FIXME: TitleInputWidget should be in its own module
+ return [ 'mediawiki.widgets' ];
+ }
+
public function getInputHtml( $value ) {
// add mw-searchInput class to enable search suggestions for non-OOUI, too
$this->mClass .= 'mw-searchInput';
}
protected function getInputWidget( $params ) {
- $this->mParent->getOutput()->addModules( 'mediawiki.widgets.UserInputWidget' );
-
return new UserInputWidget( $params );
}
return true;
}
+ protected function getOOUIModules() {
+ return [ 'mediawiki.widgets.UserInputWidget' ];
+ }
+
public function getInputHtml( $value ) {
// add the required module and css class for user suggestions in non-OOUI mode
$this->mParent->getOutput()->addModules( 'mediawiki.userSuggest' );
$checkReqIndexesByPrefix[$prefix][$index] = 1;
}
}
- // Update index of requests to inspect for replacement
- $replaceReqsByService = $newReplaceReqsByService;
// Run the actual work HTTP requests
foreach ( $this->http->runMulti( $executeReqs ) as $index => $ranReq ) {
$doneReqs[$index] = $ranReq;
$form->setAction( $this->getFullTitle()->getFullURL( $this->getPreservedParams() ) );
$form->addHiddenField( $this->getTokenName(), $this->getToken()->toString() );
$form->addHiddenField( 'authAction', $this->authAction );
- $form->suppressDefaultSubmit( !$this->needsSubmitButton( $formDescriptor ) );
+ $form->suppressDefaultSubmit( !$this->needsSubmitButton( $requests ) );
return $form;
}
}
/**
- * Returns true if the form has fields which take values. If all available providers use the
- * redirect flow, the form might contain nothing but submit buttons, in which case we should
- * not add an extra submit button which does nothing.
+ * Returns true if the form built from the given AuthenticationRequests has fields which take
+ * values. If all available providers use the redirect flow, the form might contain nothing
+ * but submit buttons, in which case we should not add an extra submit button which does nothing.
*
- * @param array $formDescriptor A HTMLForm descriptor
+ * @param AuthenticationRequest[] $requests An array of AuthenticationRequests from which the
+ * form will be built
* @return bool
*/
- protected function needsSubmitButton( $formDescriptor ) {
- return (bool)array_filter( $formDescriptor, function ( $item ) {
- $class = false;
- if ( array_key_exists( 'class', $item ) ) {
- $class = $item['class'];
- } elseif ( array_key_exists( 'type', $item ) ) {
- $class = HTMLForm::$typeMappings[$item['type']];
+ protected function needsSubmitButton( array $requests ) {
+ foreach ( $requests as $req ) {
+ if ( $req->required === AuthenticationRequest::PRIMARY_REQUIRED &&
+ $this->doesRequestNeedsSubmitButton( $req )
+ ) {
+ return true;
}
- return !is_a( $class, \HTMLInfoField::class, true ) &&
- !is_a( $class, \HTMLSubmitField::class, true );
- } );
+ }
+ return false;
+ }
+
+ /**
+ * Checks if the given AuthenticationRequest needs a submit button or not.
+ *
+ * @param AuthenticationRequest $req The request to check
+ * @return bool
+ */
+ protected function doesRequestNeedsSubmitButton( AuthenticationRequest $req ) {
+ foreach ( $req->getFieldInfo() as $field => $info ) {
+ if ( $info['type'] === 'button' ) {
+ return false;
+ }
+ }
+ return true;
}
/**
$this->fakeTemplate = $fakeTemplate; // FIXME there should be a saner way to pass this to the hook
// this will call onAuthChangeFormFields()
$formDescriptor = static::fieldInfoToFormDescriptor( $requests, $fieldInfo, $this->authAction );
- $this->postProcessFormDescriptor( $formDescriptor );
+ $this->postProcessFormDescriptor( $formDescriptor, $requests );
$context = $this->getContext();
if ( $context->getRequest() !== $this->getRequest() ) {
/**
* @param array $formDescriptor
*/
- protected function postProcessFormDescriptor( &$formDescriptor ) {
+ protected function postProcessFormDescriptor( &$formDescriptor, $requests ) {
// Pre-fill username (if not creating an account, T46775).
if (
isset( $formDescriptor['username'] ) &&
// don't show a submit button if there is nothing to submit (i.e. the only form content
// is other submit buttons, for redirect flows)
- if ( !$this->needsSubmitButton( $formDescriptor ) ) {
+ if ( !$this->needsSubmitButton( $requests ) ) {
unset( $formDescriptor['createaccount'], $formDescriptor['loginattempt'] );
}
return $form;
}
- protected function needsSubmitButton( $formDescriptor ) {
+ protected function needsSubmitButton( array $requests ) {
// Change/remove forms show are built from a single AuthenticationRequest and do not allow
// for redirect flow; they always need a submit button.
return true;
'buttontype' => 'submit',
'buttonname' => 'addcat',
'buttondefault' => $this->msg( 'export-addcat' )->text(),
+ 'hide-if' => [ '===', 'exportall', '1' ],
],
];
if ( $config->get( 'ExportFromNamespaces' ) ) {
'buttontype' => 'submit',
'buttonname' => 'addns',
'buttondefault' => $this->msg( 'export-addns' )->text(),
+ 'hide-if' => [ '===', 'exportall', '1' ],
],
];
}
'nodata' => true,
'rows' => 10,
'default' => $page,
+ 'hide-if' => [ '===', 'exportall', '1' ],
],
];
* @file
* @ingroup Maintenance
*/
+use \MediaWiki\MediaWikiServices;
+
class BatchRowWriter {
/**
* @var IDatabase $db The database to write to
* names to update values to apply to the row.
*/
public function write( array $updates ) {
- $this->db->begin();
+ $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
+ $ticket = $lbFactory->getEmptyTransactionTicket( __METHOD__ );
foreach ( $updates as $update ) {
$this->db->update(
);
}
- $this->db->commit();
- wfGetLBFactory()->waitForReplication();
+ $lbFactory->commitAndWaitForReplication( __METHOD__, $ticket );
}
}
],
'targets' => [ 'desktop', 'mobile' ],
],
+ 'mediawiki.htmlform.ooui' => [
+ 'scripts' => [
+ 'resources/src/mediawiki/htmlform/htmlform.Element.js',
+ ],
+ 'dependencies' => [
+ 'oojs-ui-core',
+ ],
+ 'targets' => [ 'desktop', 'mobile' ],
+ ],
'mediawiki.htmlform.styles' => [
'styles' => 'resources/src/mediawiki/htmlform/styles.css',
'position' => 'top',
}
} else if ( $collapsible.parent().is( 'li' ) &&
- $collapsible.parent().children( '.mw-collapsible' ).size() === 1
+ $collapsible.parent().children( '.mw-collapsible' ).size() === 1 &&
+ $collapsible.find( '> .mw-collapsible-toggle' ).size() === 0
) {
// special case of one collapsible in <li> tag
$toggleLink = buildDefaultToggleLink();
border-radius: 0.1em;
line-height: 1.275em;
background-color: #fff;
+
+ > .oo-ui-labelElement-label {
+ padding: 0;
+ }
}
&.oo-ui-indicatorElement .mw-widget-dateInputWidget-handle > .oo-ui-indicatorElement-indicator {
* HTMLForm enhancements:
* Infuse some OOjs UI HTMLForm fields (those which benefit from always being infused).
*/
-( function ( mw ) {
+( function ( mw, $ ) {
mw.hook( 'htmlform.enhance' ).add( function ( $root ) {
- var $oouiNodes, modules;
+ var $oouiNodes, modules, extraModules;
$oouiNodes = $root.find( '.mw-htmlform-field-autoinfuse' );
if ( $oouiNodes.length ) {
// The modules are preloaded (added server-side in HTMLFormField, and the individual fields
// which need extra ones), but this module doesn't depend on them. Wait until they're loaded.
- modules = [ 'oojs-ui-core' ];
- if ( $oouiNodes.filter( '.mw-htmlform-field-HTMLTitleTextField' ).length ) {
- // FIXME: TitleInputWidget should be in its own module
- modules.push( 'mediawiki.widgets' );
- }
- if ( $oouiNodes.filter( '.mw-htmlform-field-HTMLUserTextField' ).length ) {
- modules.push( 'mediawiki.widgets.UserInputWidget' );
- }
- if (
- $oouiNodes.filter( '.mw-htmlform-field-HTMLSelectNamespace' ).length ||
- $oouiNodes.filter( '.mw-htmlform-field-HTMLSelectNamespaceWithButton' ).length
- ) {
- // FIXME: NamespaceInputWidget should be in its own module (probably?)
- modules.push( 'mediawiki.widgets' );
- }
+ modules = [ 'mediawiki.htmlform.ooui' ];
+ $oouiNodes.each( function () {
+ var data = $( this ).data( 'mw-modules' );
+ if ( data ) {
+ // We can trust this value, 'data-mw-*' attributes are banned from user content in Sanitizer
+ extraModules = data.split( ',' );
+ modules.push.apply( modules, extraModules );
+ }
+ } );
mw.loader.using( modules ).done( function () {
$oouiNodes.each( function () {
OO.ui.infuse( this );
} );
-}( mediaWiki ) );
+}( mediaWiki, jQuery ) );
*/
( function ( mw, $ ) {
+ /*jshint -W024*/
+
/**
* Helper function for hide-if to find the nearby form field.
*
* @private
* @param {jQuery} $el
* @param {string} name
- * @return {jQuery|null}
+ * @return {jQuery|OO.ui.Widget|null}
*/
function hideIfGetField( $el, name ) {
- var $found, $p,
+ var $found, $p, $widget,
suffix = name.replace( /^([^\[]+)/, '[$1]' );
function nameFilter() {
for ( $p = $el.parent(); $p.length > 0; $p = $p.parent() ) {
$found = $p.find( '[name]' ).filter( nameFilter );
if ( $found.length ) {
+ $widget = $found.closest( '.oo-ui-widget[data-ooui]' );
+ if ( $widget.length ) {
+ return OO.ui.Widget.static.infuse( $widget );
+ }
return $found;
}
}
* @param {jQuery} $el
* @param {Array} spec
* @return {Array}
- * @return {jQuery} return.0 Dependent fields
+ * @return {Array} return.0 Dependent fields, array of jQuery objects or OO.ui.Widgets
* @return {Function} return.1 Test function
*/
function hideIfParse( $el, spec ) {
- var op, i, l, v, $field, $fields, fields, func, funcs, getVal;
+ var op, i, l, v, field, $field, fields, func, funcs, getVal;
op = spec[ 0 ];
l = spec.length;
throw new Error( op + ' parameters must be arrays' );
}
v = hideIfParse( $el, spec[ i ] );
- fields = fields.concat( v[ 0 ].toArray() );
+ fields = fields.concat( v[ 0 ] );
funcs.push( v[ 1 ] );
}
- $fields = $( fields );
l = funcs.length;
switch ( op ) {
break;
}
- return [ $fields, func ];
+ return [ fields, func ];
case 'NOT':
if ( l !== 2 ) {
throw new Error( 'NOT parameters must be arrays' );
}
v = hideIfParse( $el, spec[ 1 ] );
- $fields = v[ 0 ];
+ fields = v[ 0 ];
func = v[ 1 ];
- return [ $fields, function () {
+ return [ fields, function () {
return !func();
} ];
if ( l !== 3 ) {
throw new Error( op + ' takes exactly two parameters' );
}
- $field = hideIfGetField( $el, spec[ 1 ] );
- if ( !$field ) {
- return [ $(), function () {
+ field = hideIfGetField( $el, spec[ 1 ] );
+ if ( !field ) {
+ return [ [], function () {
return false;
} ];
}
v = spec[ 2 ];
- if ( $field.first().prop( 'type' ) === 'radio' ||
- $field.first().prop( 'type' ) === 'checkbox'
- ) {
- getVal = function () {
- var $selected = $field.filter( ':checked' );
- return $selected.length ? $selected.val() : '';
- };
+ if ( field instanceof OO.ui.Widget ) {
+ if ( field.supports( 'isSelected' ) ) {
+ getVal = function () {
+ var selected = field.isSelected();
+ return selected ? field.getValue() : '';
+ };
+ } else {
+ getVal = function () {
+ return field.getValue();
+ };
+ }
} else {
- getVal = function () {
- return $field.val();
- };
+ $field = $( field );
+ if ( $field.prop( 'type' ) === 'radio' || $field.prop( 'type' ) === 'checkbox' ) {
+ getVal = function () {
+ var $selected = $field.filter( ':checked' );
+ return $selected.length ? $selected.val() : '';
+ };
+ } else {
+ getVal = function () {
+ return $field.val();
+ };
+ }
}
switch ( op ) {
break;
}
- return [ $field, func ];
+ return [ [ field ], func ];
default:
throw new Error( 'Unrecognized operation \'' + op + '\'' );
mw.hook( 'htmlform.enhance' ).add( function ( $root ) {
$root.find( '.mw-htmlform-hide-if' ).each( function () {
- var v, $fields, test, func,
- $el = $( this ),
- spec = $el.data( 'hideIf' );
-
- if ( !spec ) {
- return;
+ var v, i, fields, test, func, spec, self, modules, data,extraModules,
+ $el = $( this );
+
+ modules = [];
+ if ( $el.is( '[data-ooui]' ) ) {
+ modules.push( 'mediawiki.htmlform.ooui' );
+ data = $el.data( 'mw-modules' );
+ if ( data ) {
+ // We can trust this value, 'data-mw-*' attributes are banned from user content in Sanitizer
+ extraModules = data.split( ',' );
+ modules.push.apply( modules, extraModules );
+ }
}
- v = hideIfParse( $el, spec );
- $fields = v[ 0 ];
- test = v[ 1 ];
- func = function () {
- if ( test() ) {
- $el.hide();
+ mw.loader.using( modules ).done( function () {
+ if ( $el.is( '[data-ooui]' ) ) {
+ // self should be a FieldLayout that mixes in mw.htmlform.Element
+ self = OO.ui.FieldLayout.static.infuse( $el );
+ spec = self.hideIf;
+ // The original element has been replaced with infused one
+ $el = self.$element;
} else {
- $el.show();
+ self = $el;
+ spec = $el.data( 'hideIf' );
+ }
+
+ if ( !spec ) {
+ return;
+ }
+
+ v = hideIfParse( $el, spec );
+ fields = v[ 0 ];
+ test = v[ 1 ];
+ // The .toggle() method works mostly the same for jQuery objects and OO.ui.Widget
+ func = function () {
+ self.toggle( !test() );
+ };
+ for ( i = 0; i < fields.length; i++ ) {
+ // The .on() method works mostly the same for jQuery objects and OO.ui.Widget
+ fields[ i ].on( 'change', func );
}
- };
- $fields.on( 'change', func );
- func();
+ func();
+ } );
} );
} );
--- /dev/null
+( function ( mw ) {
+
+ mw.htmlform = {};
+
+ /**
+ * Allows custom data specific to HTMLFormField to be set for OOjs UI forms. This picks up the
+ * extra config from a matching PHP widget (defined in HTMLFormElement.php) when constructed using
+ * OO.ui.infuse().
+ *
+ * Currently only supports passing 'hide-if' data.
+ *
+ * @ignore
+ */
+ mw.htmlform.Element = function ( config ) {
+ // Configuration initialization
+ config = config || {};
+
+ // Properties
+ this.hideIf = config.hideIf;
+
+ // Initialization
+ if ( this.hideIf ) {
+ this.$element.addClass( 'mw-htmlform-hide-if' );
+ }
+ };
+
+ mw.htmlform.FieldLayout = function ( config ) {
+ // Parent constructor
+ mw.htmlform.FieldLayout.parent.call( this, config );
+ // Mixin constructors
+ mw.htmlform.Element.call( this, config );
+ };
+ OO.inheritClass( mw.htmlform.FieldLayout, OO.ui.FieldLayout );
+ OO.mixinClass( mw.htmlform.FieldLayout, mw.htmlform.Element );
+
+ mw.htmlform.ActionFieldLayout = function ( config ) {
+ // Parent constructor
+ mw.htmlform.ActionFieldLayout.parent.call( this, config );
+ // Mixin constructors
+ mw.htmlform.Element.call( this, config );
+ };
+ OO.inheritClass( mw.htmlform.ActionFieldLayout, OO.ui.ActionFieldLayout );
+ OO.mixinClass( mw.htmlform.ActionFieldLayout, mw.htmlform.Element );
+
+}( mediaWiki ) );