'Job' => __DIR__ . '/includes/jobqueue/Job.php',
'JobQueue' => __DIR__ . '/includes/jobqueue/JobQueue.php',
'JobQueueAggregator' => __DIR__ . '/includes/jobqueue/aggregator/JobQueueAggregator.php',
- 'JobQueueAggregatorMemc' => __DIR__ . '/includes/jobqueue/aggregator/JobQueueAggregatorMemc.php',
+ 'JobQueueAggregatorNull' => __DIR__ . '/includes/jobqueue/aggregator/JobQueueAggregator.php',
'JobQueueAggregatorRedis' => __DIR__ . '/includes/jobqueue/aggregator/JobQueueAggregatorRedis.php',
'JobQueueConnectionError' => __DIR__ . '/includes/jobqueue/JobQueue.php',
'JobQueueDB' => __DIR__ . '/includes/jobqueue/JobQueueDB.php',
* are passed as parameters after $srcPath, $dstPath, $width, $height
*/
$wgSVGConverters = array(
- 'ImageMagick' => '$path/convert -background "#ffffff00" -thumbnail $widthx$height\! $input PNG:$output',
+ 'ImageMagick' =>
+ '$path/convert -background "#ffffff00" -thumbnail $widthx$height\! $input PNG:$output',
'sodipodi' => '$path/sodipodi -z -w $width -f $input -e $output',
'inkscape' => '$path/inkscape -z -w $width -f $input -e $output',
'batik' => 'java -Djava.awt.headless=true -jar $path/batik-rasterizer.jar -w $width -d '
),
"poweredby" => array(
"mediawiki" => array(
- "src" => null, // Defaults to point at
- // "$wgResourceBasePath/resources/assets/poweredby_mediawiki_88x31.png"
- // plus srcset for 1.5x, 2x resolution variants.
+ // Defaults to point at
+ // "$wgResourceBasePath/resources/assets/poweredby_mediawiki_88x31.png"
+ // plus srcset for 1.5x, 2x resolution variants.
+ "src" => null,
"url" => "//www.mediawiki.org/",
"alt" => "Powered by MediaWiki",
)
* These settings should be global to all wikis.
*/
$wgJobQueueAggregator = array(
- 'class' => 'JobQueueAggregatorMemc'
+ 'class' => 'JobQueueAggregatorNull'
);
/**
}
/**
- * Returns an HTML link element in a string styled as a button (when $wgUseMediaWikiUIEverywhere is enabled).
+ * Returns an HTML link element in a string styled as a button
+ * (when $wgUseMediaWikiUIEverywhere is enabled).
*
* @param string $contents The raw HTML contents of the element: *not*
* escaped!
}
/**
- * Returns an HTML link element in a string styled as a button (when $wgUseMediaWikiUIEverywhere is enabled).
+ * Returns an HTML link element in a string styled as a button
+ * (when $wgUseMediaWikiUIEverywhere is enabled).
*
* @param string $contents The raw HTML contents of the element: *not*
* escaped!
*
* @return string
*/
- public static function formatLinksInComment( $comment, $title = null, $local = false, $wikiId = null ) {
+ public static function formatLinksInComment(
+ $comment, $title = null, $local = false, $wikiId = null
+ ) {
return preg_replace_callback(
'/
\[\[
$dbw->commit( __METHOD__ );
- Hooks::run( 'TitleMoveComplete', array( &$this->oldTitle, &$this->newTitle, &$user, $pageid, $redirid, $reason ) );
+ Hooks::run(
+ 'TitleMoveComplete',
+ array( &$this->oldTitle, &$this->newTitle, &$user, $pageid, $redirid, $reason )
+ );
return Status::newGood();
}
* If the module may only be used with a certain format module,
* it should override this method to return an instance of that formatter.
* A value of null means the default format will be used.
+ * @note Do not use this just because you don't want to support non-json
+ * formats. This should be used only when there is a fundamental
+ * requirement for a specific format.
* @return mixed Instance of a derived class of ApiFormatBase, or null
*/
public function getCustomPrinter() {
* @return array Summary response that can easily be JSON serialized
*/
public function run( array $options ) {
+ global $wgJobClasses;
+
$response = array( 'jobs' => array(), 'reached' => 'none-ready' );
$type = isset( $options['type'] ) ? $options['type'] : false;
$maxTime = isset( $options['maxTime'] ) ? $options['maxTime'] : false;
$noThrottle = isset( $options['throttle'] ) && !$options['throttle'];
+ if ( $type !== false && !isset( $wgJobClasses[$type] ) ) {
+ $response['reached'] = 'none-possible';
+ return $response;
+ }
+
$group = JobQueueGroup::singleton();
// Handle any required periodic queue maintenance
$count = $group->executeReadyPeriodicTasks();
return $pendingDBs;
}
}
+
+class JobQueueAggregatorNull extends JobQueueAggregator {
+ protected function doNotifyQueueEmpty( $wiki, $type ) {
+ return true;
+ }
+
+ protected function doNotifyQueueNonEmpty( $wiki, $type ) {
+ return true;
+ }
+
+ protected function doGetAllReadyWikiQueues() {
+ return array();
+ }
+
+ protected function doPurge() {
+ return true;
+ }
+}
\ No newline at end of file
+++ /dev/null
-<?php
-/**
- * Job queue aggregator code that uses BagOStuff.
- *
- * 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
- * @author Aaron Schulz
- */
-
-/**
- * Class to handle tracking information about all queues using BagOStuff
- *
- * @ingroup JobQueue
- * @since 1.21
- */
-class JobQueueAggregatorMemc extends JobQueueAggregator {
- /** @var BagOStuff */
- protected $cache;
-
- protected $cacheTTL; // integer; seconds
-
- /**
- * @param array $params Possible keys:
- * - objectCache : Name of an object cache registered in $wgObjectCaches.
- * This defaults to the one specified by $wgMainCacheType.
- * - cacheTTL : Seconds to cache the aggregate data before regenerating.
- */
- protected function __construct( array $params ) {
- parent::__construct( $params );
- $this->cache = isset( $params['objectCache'] )
- ? wfGetCache( $params['objectCache'] )
- : wfGetMainCache();
- $this->cacheTTL = isset( $params['cacheTTL'] ) ? $params['cacheTTL'] : 180; // 3 min
- }
-
- /**
- * @see JobQueueAggregator::doNotifyQueueEmpty()
- */
- protected function doNotifyQueueEmpty( $wiki, $type ) {
- $key = $this->getReadyQueueCacheKey();
- // Delist the queue from the "ready queue" list
- if ( $this->cache->add( "$key:lock", 1, 60 ) ) { // lock
- $curInfo = $this->cache->get( $key );
- if ( is_array( $curInfo ) && isset( $curInfo['pendingDBs'][$type] ) ) {
- if ( in_array( $wiki, $curInfo['pendingDBs'][$type] ) ) {
- $curInfo['pendingDBs'][$type] = array_diff(
- $curInfo['pendingDBs'][$type], array( $wiki ) );
- $this->cache->set( $key, $curInfo );
- }
- }
- $this->cache->delete( "$key:lock" ); // unlock
- }
-
- return true;
- }
-
- /**
- * @see JobQueueAggregator::doNotifyQueueNonEmpty()
- */
- protected function doNotifyQueueNonEmpty( $wiki, $type ) {
- return true; // updated periodically
- }
-
- /**
- * @see JobQueueAggregator::doAllGetReadyWikiQueues()
- */
- protected function doGetAllReadyWikiQueues() {
- $key = $this->getReadyQueueCacheKey();
- // If the cache entry wasn't present, is stale, or in .1% of cases otherwise,
- // regenerate the cache. Use any available stale cache if another process is
- // currently regenerating the pending DB information.
- $pendingDbInfo = $this->cache->get( $key );
- if ( !is_array( $pendingDbInfo )
- || ( time() - $pendingDbInfo['timestamp'] ) > $this->cacheTTL
- || mt_rand( 0, 999 ) == 0
- ) {
- if ( $this->cache->add( "$key:rebuild", 1, 1800 ) ) { // lock
- $pendingDbInfo = array(
- 'pendingDBs' => $this->findPendingWikiQueues(),
- 'timestamp' => time()
- );
- for ( $attempts = 1; $attempts <= 25; ++$attempts ) {
- if ( $this->cache->add( "$key:lock", 1, 60 ) ) { // lock
- $this->cache->set( $key, $pendingDbInfo );
- $this->cache->delete( "$key:lock" ); // unlock
- break;
- }
- }
- $this->cache->delete( "$key:rebuild" ); // unlock
- }
- }
-
- return is_array( $pendingDbInfo )
- ? $pendingDbInfo['pendingDBs']
- : array(); // cache is both empty and locked
- }
-
- /**
- * @see JobQueueAggregator::doPurge()
- */
- protected function doPurge() {
- return $this->cache->delete( $this->getReadyQueueCacheKey() );
- }
-
- /**
- * @return string
- */
- private function getReadyQueueCacheKey() {
- return "jobqueue:aggregator:ready-queues:v1"; // global
- }
-}
function run() {
global $wgUpdateRowsPerJob, $wgUpdateRowsPerQuery;
- static $expected = array( 'recursive', 'pages' ); // new jobs have one of these
-
- $oldRangeJob = false;
- if ( !array_intersect( array_keys( $this->params ), $expected ) ) {
- // B/C for older job params formats that lack these fields:
- // a) base jobs with just ("table") and b) range jobs with ("table","start","end")
- if ( isset( $this->params['start'] ) && isset( $this->params['end'] ) ) {
- $oldRangeJob = true;
- } else {
- $this->params['recursive'] = true; // base job
- }
+ if ( isset( $this->params['table'] ) && !isset( $this->params['pages'] ) ) {
+ $this->params['recursive'] = true; // b/c; base job
}
// Job to purge all (or a range of) backlink pages for a page
// Job to purge pages for a set of titles
} elseif ( isset( $this->params['pages'] ) ) {
$this->invalidateTitles( $this->params['pages'] );
- // B/C for job to purge a range of backlink pages for a given page
- } elseif ( $oldRangeJob ) {
- $titleArray = $this->title->getBacklinkCache()->getLinks(
- $this->params['table'], $this->params['start'], $this->params['end'] );
-
- $pages = array(); // same format BacklinkJobUtils uses
- foreach ( $titleArray as $tl ) {
- $pages[$tl->getArticleId()] = array( $tl->getNamespace(), $tl->getDbKey() );
- }
-
- $jobs = array();
- foreach ( array_chunk( $pages, $wgUpdateRowsPerJob ) as $pageChunk ) {
- $jobs[] = new HTMLCacheUpdateJob( $this->title,
- array(
- 'table' => $this->params['table'],
- 'pages' => $pageChunk
- ) + $this->getRootJobParams() // carry over information for de-duplication
- );
- }
- JobQueueGroup::singleton()->push( $jobs );
+ // Job to update a single title
+ } else {
+ $t = $this->title;
+ $this->invalidateTitles( array(
+ $t->getArticleID() => array( $t->getNamespace(), $t->getDBkey() )
+ ) );
}
return true;
break;
case 'D':
$usedDay = true;
- $s .= $this->getWeekdayAbbreviation( Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'w' ) + 1 );
+ $s .= $this->getWeekdayAbbreviation(
+ Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'w' ) + 1
+ );
break;
case 'j':
$usedDay = true;
break;
case 'l':
$usedDay = true;
- $s .= $this->getWeekdayName( Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'w' ) + 1 );
+ $s .= $this->getWeekdayName(
+ Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'w' ) + 1
+ );
break;
case 'F':
$usedMonth = true;
} elseif ( $usedHour ) {
$ttl = 3600 - substr( $ts, 10, 2 ) * 60 - substr( $ts, 12, 2 );
} elseif ( $usedAMPM ) {
- $ttl = 43200 - ( substr( $ts, 8, 2 ) % 12 ) * 3600 - substr( $ts, 10, 2 ) * 60 - substr( $ts, 12, 2 );
- } elseif ( $usedDay || $usedHebrewMonth || $usedIranianMonth || $usedHijriMonth || $usedHebrewYear || $usedIranianYear || $usedHijriYear || $usedTennoYear ) {
- // @todo Someone who understands the non-Gregorian calendars should write proper logic for them
- // so that they don't need purged every day.
- $ttl = 86400 - substr( $ts, 8, 2 ) * 3600 - substr( $ts, 10, 2 ) * 60 - substr( $ts, 12, 2 );
+ $ttl = 43200 - ( substr( $ts, 8, 2 ) % 12 ) * 3600 -
+ substr( $ts, 10, 2 ) * 60 - substr( $ts, 12, 2 );
+ } elseif (
+ $usedDay ||
+ $usedHebrewMonth ||
+ $usedIranianMonth ||
+ $usedHijriMonth ||
+ $usedHebrewYear ||
+ $usedIranianYear ||
+ $usedHijriYear ||
+ $usedTennoYear
+ ) {
+ // @todo Someone who understands the non-Gregorian calendars
+ // should write proper logic for them so that they don't need purged every day.
+ $ttl = 86400 - substr( $ts, 8, 2 ) * 3600 -
+ substr( $ts, 10, 2 ) * 60 - substr( $ts, 12, 2 );
} else {
$possibleTtls = array();
- $timeRemainingInDay = 86400 - substr( $ts, 8, 2 ) * 3600 - substr( $ts, 10, 2 ) * 60 - substr( $ts, 12, 2 );
+ $timeRemainingInDay = 86400 - substr( $ts, 8, 2 ) * 3600 -
+ substr( $ts, 10, 2 ) * 60 - substr( $ts, 12, 2 );
if ( $usedWeek ) {
- $possibleTtls[] = ( 7 - Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'N' ) ) * 86400 + $timeRemainingInDay;
+ $possibleTtls[] =
+ ( 7 - Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'N' ) ) * 86400 +
+ $timeRemainingInDay;
} elseif ( $usedISOYear ) {
// December 28th falls on the last ISO week of the year, every year.
// The last ISO week of a year can be 52 or 53.
- $lastWeekOfISOYear = DateTime::createFromFormat( 'Ymd', substr( $ts, 0, 4 ) . '1228', $zone ?: new DateTimeZone( 'UTC' ) )->format( 'W' );
+ $lastWeekOfISOYear = DateTime::createFromFormat(
+ 'Ymd',
+ substr( $ts, 0, 4 ) . '1228',
+ $zone ?: new DateTimeZone( 'UTC' )
+ )->format( 'W' );
$currentISOWeek = Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'W' );
$weeksRemaining = $lastWeekOfISOYear - $currentISOWeek;
- $timeRemainingInWeek = ( 7 - Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'N' ) ) * 86400 + $timeRemainingInDay;
+ $timeRemainingInWeek =
+ ( 7 - Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'N' ) ) * 86400
+ + $timeRemainingInDay;
$possibleTtls[] = $weeksRemaining * 604800 + $timeRemainingInWeek;
}
if ( $usedMonth ) {
- $possibleTtls[] = ( Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 't' ) - substr( $ts, 6, 2 ) ) * 86400 + $timeRemainingInDay;
+ $possibleTtls[] =
+ ( Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 't' ) -
+ substr( $ts, 6, 2 ) ) * 86400
+ + $timeRemainingInDay;
} elseif ( $usedYear ) {
- $possibleTtls[] = ( Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'L' ) + 364 - Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'z' ) ) * 86400
+ $possibleTtls[] =
+ ( Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'L' ) + 364 -
+ Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'z' ) ) * 86400
+ $timeRemainingInDay;
} elseif ( $usedIsLeapYear ) {
$year = substr( $ts, 0, 4 );
- $timeRemainingInYear = ( Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'L' ) + 364 - Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'z' ) ) * 86400
+ $timeRemainingInYear =
+ ( Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'L' ) + 364 -
+ Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'z' ) ) * 86400
+ $timeRemainingInDay;
$mod = $year % 4;
if ( $mod || ( !( $year % 100 ) && $year % 400 ) ) {
// this isn't a leap year. see when the next one starts
$nextCandidate = $year - $mod + 4;
if ( $nextCandidate % 100 || !( $nextCandidate % 400 ) ) {
- $possibleTtls[] = ( $nextCandidate - $year - 1 ) * 365 * 86400 + $timeRemainingInYear;
+ $possibleTtls[] = ( $nextCandidate - $year - 1 ) * 365 * 86400 +
+ $timeRemainingInYear;
} else {
- $possibleTtls[] = ( $nextCandidate - $year + 3 ) * 365 * 86400 + $timeRemainingInYear;
+ $possibleTtls[] = ( $nextCandidate - $year + 3 ) * 365 * 86400 +
+ $timeRemainingInYear;
}
} else {
// this is a leap year, so the next year isn't
"newuserlog-autocreate-entry": "Account created automatically",
"rightslogentry": "changed group membership for $1 from $2 to $3",
"rightslogentry-autopromote": "was automatically promoted from $2 to $3",
+ "feedback-adding": "Adding feedback to page...",
+ "feedback-back": "Back",
+ "feedback-bugcheck": "Great! Just check that it is not already one of the [$1 known bugs].",
+ "feedback-bugnew": "I checked. Report a new bug",
"feedback-bugornote": "If you are ready to describe a technical problem in detail please [$1 report a bug].\nOtherwise, you can use the easy form below. Your comment will be added to the page \"[$3 $2]\", along with your username.",
- "feedback-subject": "Subject:",
- "feedback-message": "Message:",
"feedback-cancel": "Cancel",
- "feedback-submit": "Submit Feedback",
- "feedback-adding": "Adding feedback to page...",
+ "feedback-close": "Done",
+ "feedback-external-bug-report-button": "File a technical task",
+ "feedback-dialog-title": "Submit feedback",
+ "feedback-dialog-intro": "You can use the easy form below to submit your feedback. Your comment will be added to the page \"$1\", along with your username.",
+ "feedback-error-title": "Error",
"feedback-error1": "Error: Unrecognized result from API",
"feedback-error2": "Error: Edit failed",
"feedback-error3": "Error: No response from API",
+ "feedback-message": "Message:",
+ "feedback-subject": "Subject:",
+ "feedback-submit": "Submit",
+ "feedback-terms": "I understand that my user agent information includes information about my exact browser and operating system version and will be shared publicly alongside my feedback.",
+ "feedback-termsofuse": "I agree to provide feedback in accordance with the Terms of Use.",
"feedback-thanks": "Thanks! Your feedback has been posted to the page \"[$2 $1]\".",
- "feedback-close": "Done",
- "feedback-bugcheck": "Great! Just check that it is not already one of the [$1 known bugs].",
- "feedback-bugnew": "I checked. Report a new bug",
+ "feedback-thanks-title": "Thank you!",
+ "feedback-useragent": "User agent:",
"searchsuggest-search": "Search",
"searchsuggest-containing": "containing...",
"api-error-badaccess-groups": "You are not permitted to upload files to this wiki.",
"newuserlog-autocreate-entry": "This message is used in the [[:mw:Extension:Newuserlog|new user log]] to mark an account that was created by MediaWiki as part of a [[:mw:Extension:CentralAuth|CentralAuth]] global account.",
"rightslogentry": "This message is displayed in the [[Special:Log/rights|User Rights Log]] when a bureaucrat changes the user groups for a user.\n\nParameters:\n* $1 - the username\n* $2 - list of user groups or {{msg-mw|Rightsnone}}\n* $3 - list of user groups or {{msg-mw|Rightsnone}}\n\nThe name of the bureaucrat who did this task appears before this message.\n\nSimilar to {{msg-mw|Gur-rightslog-entry}}",
"rightslogentry-autopromote": "This message is displayed in the [[Special:Log/rights|User Rights Log]] when a user is automatically promoted to a user group.\n\nParameters:\n* $1 - (Unused)\n* $2 - a comma separated list of old user groups or {{msg-mw|Rightsnone}}\n* $3 - a comma separated list of new user groups",
+ "feedback-adding": "Progress notice",
+ "feedback-back": "Button to go back to the previous action in the feedback dialog.",
+ "feedback-bugcheck": "Message that appears before the user submits a bug, reminding them to check for known bugs.\n\nParameters:\n* $1 - bug list page URL",
+ "feedback-bugnew": "Button label - asserts that the user has checked for existing bugs. When clicked will launch an external form to add a new bug in a new tab or window",
"feedback-bugornote": "When feedback dialog box is opened, this introductory message in small print explains the options to report a bug or add simple feedback.\n\nWe expect that people in a hurry will not read this.\n\nParameters:\n* $1 - Bug note URL\n* $2 - \"Feedback\"\n* $3 - Feedback page URL",
- "feedback-subject": "Label for a text input\n{{Identical|Subject}}",
- "feedback-message": "Label for a textarea; signature referrs to a Wikitext signature.\n{{Identical|Message}}",
+ "feedback-external-bug-report-button": "A button for submitting an external technical bug report.",
"feedback-cancel": "Button label\n{{Identical|Cancel}}",
- "feedback-submit": "Button label\n{{Identical|Submit}}",
- "feedback-adding": "Progress notice",
+ "feedback-close": "Button label\n{{Identical|Done}}",
+ "feedback-dialog-title": "Title of the feedback dialog",
"feedback-error1": "Error message, appears when an unknown error occurs submitting feedback",
"feedback-error2": "Error message, appears when we could not add feedback",
"feedback-error3": "Error message, appears when we lose our connection to the wiki",
+ "feedback-dialog-intro": "An introduction at the top of the feedback dialog. $1 - Feedback page link",
+ "feedback-message": "Label for a textarea; signature refers to a Wikitext signature.\n{{Identical|Message}}",
+ "feedback-subject": "Label for a text input\n{{Identical|Subject}}",
+ "feedback-submit": "Button label\n{{Identical|Submit}}",
+ "feedback-terms": "Label for a checkbox asking for permissions to submit browser information.",
+ "feedback-termsofuse": "Label with an agreement about the terms of use.",
"feedback-thanks": "Thanks message, appears if feedback was successful. Parameters:\n* $1 - \"Feedback\"\n* $2 - Feedback page URL",
- "feedback-close": "Button label\n{{Identical|Done}}",
- "feedback-bugcheck": "Message that appears before the user submits a bug, reminding them to check for known bugs.\n\nParameters:\n* $1 - bug list page URL",
- "feedback-bugnew": "Button label - asserts that the user has checked for existing bugs. When clicked will launch a bugzilla form to add a new bug in a new tab or window",
+ "feedback-thanks-title": "The title of the message dialog at the end of the submission process that shows error in submitting the feedback.",
+ "feedback-thanks-title": "The title of the thank you dialog at the end of the submission process.",
+ "feedback-useragent": "A label denoting the user agent in the feedback that is posted to the feedback page.",
"searchsuggest-search": "Greyed out default text in the simple search box in the Vector skin. (It disappears and lets the user enter the requested search terms when the search box receives focus.)\n\n{{Identical|Search}}",
"searchsuggest-containing": "Label used in the special item of the search suggestions list which gives the user an option to perform a full text search for the term.",
"api-error-badaccess-groups": "API error message that can be used for client side localisation of API errors.",
protected $maxConsecutiveFailedTextRetrievals = 200;
protected $failureTimeout = 5; // Seconds to sleep after db failure
+ protected $bufferSize = 524288; // In bytes. Maximum size to read from the stub in on go.
+
protected $php = "php";
protected $spawn = false;
$url = $this->processFileOpt( $val, $param );
switch ( $opt ) {
+ case 'buffersize':
+ // Lower bound for xml reading buffer size is 4 KB
+ $this->bufferSize = max( intval( $val ), 4 * 1024 );
+ break;
case 'prefetch':
require_once "$IP/maintenance/backupPrefetch.inc";
$this->prefetch = new BaseDump( $url );
xml_set_character_data_handler( $parser, array( &$this, 'characterData' ) );
$offset = 0; // for context extraction on error reporting
- $bufferSize = 512 * 1024;
do {
if ( $this->checkIfTimeExceeded() ) {
$this->setTimeExceeded();
}
- $chunk = fread( $input, $bufferSize );
+ $chunk = fread( $input, $this->bufferSize );
if ( !xml_parse( $parser, $chunk, feof( $input ) ) ) {
wfDebug( "TextDumpPass::readDump encountered XML parsing error\n" );
--server=h Force reading from MySQL server h
--current Base ETA on number of pages in database instead of all revisions
--spawn Spawn a subprocess for loading text records
+ --buffersize=<size> Buffer size in bytes to use for reading the stub.
+ (Default: 512KB, Minimum: 4KB)
--help Display this help message
ENDS
);
{
"name": "Interfaces",
"classes": [
- "mw.Feedback"
+ "mw.Feedback",
+ "mw.Feedback.Dialog"
]
},
{
'mediawiki.apihelp' => array(
'styles' => 'resources/src/mediawiki/mediawiki.apihelp.css',
'targets' => array( 'desktop' ),
- 'dependencies' => array(
- 'mediawiki.hlist',
- ),
+ 'dependencies' => 'mediawiki.hlist',
'position' => 'top',
),
'mediawiki.template' => array(
),
'mediawiki.api.login' => array(
'scripts' => 'resources/src/mediawiki.api/mediawiki.api.login.js',
- 'dependencies' => array(
- 'mediawiki.api',
- ),
+ 'dependencies' => 'mediawiki.api',
),
'mediawiki.api.parse' => array(
'scripts' => 'resources/src/mediawiki.api/mediawiki.api.parse.js',
'position' => 'bottom',
),
'mediawiki.feedback' => array(
- 'templates' => array(
- 'dialog.html' => 'resources/src/mediawiki/templates/dialog.html',
- ),
'scripts' => 'resources/src/mediawiki/mediawiki.feedback.js',
'styles' => 'resources/src/mediawiki/mediawiki.feedback.css',
'dependencies' => array(
'mediawiki.api.edit',
'mediawiki.Title',
- 'mediawiki.jqueryMsg',
- 'jquery.ui.dialog',
+ 'oojs-ui',
),
'messages' => array(
+ 'feedback-adding',
+ 'feedback-back',
+ 'feedback-bugcheck',
+ 'feedback-dialog-intro',
+ 'feedback-external-bug-report-button',
+ 'feedback-bugnew',
'feedback-bugornote',
- 'feedback-subject',
- 'feedback-message',
'feedback-cancel',
- 'feedback-submit',
- 'feedback-adding',
+ 'feedback-close',
+ 'feedback-dialog-title',
+ 'feedback-error-title',
'feedback-error1',
'feedback-error2',
'feedback-error3',
+ 'feedback-message',
+ 'feedback-subject',
+ 'feedback-submit',
+ 'feedback-terms',
+ 'feedback-termsofuse',
'feedback-thanks',
- 'feedback-close',
- 'feedback-bugcheck',
- 'feedback-bugnew',
+ 'feedback-thanks-title',
+ 'feedback-useragent'
),
),
'mediawiki.hidpi' => array(
'scripts' => 'resources/src/mediawiki/mediawiki.hidpi.js',
- 'dependencies' => array(
- 'jquery.hidpi',
- ),
+ 'dependencies' => 'jquery.hidpi',
'skipFunction' => 'resources/src/mediawiki.hidpi-skip.js',
'targets' => array( 'desktop', 'mobile' ),
),
'mediawiki.hlist' => array(
'styles' => 'resources/src/mediawiki/mediawiki.hlist.css',
'scripts' => 'resources/src/mediawiki/mediawiki.hlist.js',
- 'dependencies' => array(
- 'jquery.client',
- ),
+ 'dependencies' => 'jquery.client',
),
'mediawiki.htmlform' => array(
'scripts' => 'resources/src/mediawiki/mediawiki.htmlform.js',
=> array( 'media' => 'print' ),
),
'scripts' => 'resources/src/mediawiki/mediawiki.notification.js',
- 'dependencies' => array(
- 'mediawiki.page.startup',
- ),
+ 'dependencies' => 'mediawiki.page.startup',
'targets' => array( 'desktop', 'mobile' ),
),
'mediawiki.notify' => array(
),
'mediawiki.toc' => array(
'scripts' => 'resources/src/mediawiki/mediawiki.toc.js',
- 'dependencies' => array(
- 'jquery.cookie',
- ),
+ 'dependencies' => 'jquery.cookie',
'messages' => array( 'showtoc', 'hidetoc' ),
'targets' => array( 'desktop', 'mobile' ),
),
),
'mediawiki.cookie' => array(
'scripts' => 'resources/src/mediawiki/mediawiki.cookie.js',
- 'dependencies' => array(
- 'jquery.cookie',
- ),
+ 'dependencies' => 'jquery.cookie',
'targets' => array( 'desktop', 'mobile' ),
),
'mediawiki.toolbar' => array(
),
'mediawiki.action.view.redirect' => array(
'scripts' => 'resources/src/mediawiki.action/mediawiki.action.view.redirect.js',
- 'dependencies' => array(
- 'jquery.client',
- ),
+ 'dependencies' => 'jquery.client',
'position' => 'top',
),
'mediawiki.action.view.redirectPage' => array(
),
'mediawiki.page.startup' => array(
'scripts' => 'resources/src/mediawiki.page/mediawiki.page.startup.js',
- 'dependencies' => array(
- 'mediawiki.util',
- ),
+ 'dependencies' => 'mediawiki.util',
'position' => 'top',
'targets' => array( 'desktop', 'mobile' ),
),
'mediawiki.special.block' => array(
'scripts' => 'resources/src/mediawiki.special/mediawiki.special.block.js',
'styles' => 'resources/src/mediawiki.special/mediawiki.special.block.css',
- 'dependencies' => array(
- 'mediawiki.util',
- ),
+ 'dependencies' => 'mediawiki.util',
),
'mediawiki.special.changeemail' => array(
'scripts' => 'resources/src/mediawiki.special/mediawiki.special.changeemail.js',
'styles' => 'resources/src/mediawiki.special/mediawiki.special.changeemail.css',
- 'dependencies' => array(
- 'mediawiki.util',
- ),
+ 'dependencies' => 'mediawiki.util',
'messages' => array(
'email-address-validity-valid',
'email-address-validity-invalid',
),
'mediawiki.special.recentchanges' => array(
'scripts' => 'resources/src/mediawiki.special/mediawiki.special.recentchanges.js',
- 'dependencies' => array( 'mediawiki.special' ),
+ 'dependencies' => 'mediawiki.special',
'position' => 'top',
),
'mediawiki.special.search' => array(
'colon-separator',
'javascripttest-pagetext-skins',
) ),
- 'dependencies' => array(
- 'mediawiki.Uri',
- ),
+ 'dependencies' => 'mediawiki.Uri',
'position' => 'top',
'targets' => array( 'desktop', 'mobile' ),
),
),
'mediawiki.legacy.protect' => array(
'scripts' => 'resources/src/mediawiki.legacy/protect.js',
- 'dependencies' => array(
- 'jquery.byteLimit',
- ),
+ 'dependencies' => 'jquery.byteLimit',
'messages' => array( 'protect-unchain-permissions' )
),
'mediawiki.legacy.shared' => array(
),
'mediawiki.legacy.wikibits' => array(
'scripts' => 'resources/src/mediawiki.legacy/wikibits.js',
- 'dependencies' => array(
- 'mediawiki.util',
- ),
+ 'dependencies' => 'mediawiki.util',
'position' => 'top',
),
width: 18px;
height: 18px;
}
+
+.mw-feedbackDialog-welcome-message,
+.mw-feedbackDialog-feedback-terms {
+ line-height: 1.2em;
+}
+
+.mw-feedbackDialog-feedback-form {
+ margin-top: 1em;
+}
+
+.mw-feedbackDialog-feedback-termsofuse {
+ margin-left: 2.5em;
+}
*
* @author Ryan Kaldari, 2010
* @author Neil Kandalgaonkar, 2010-11
+ * @author Moriel Schottlender, 2015
* @since 1.19
*/
+/*jshint es3:false */
+/*global OO*/
( function ( mw, $ ) {
/**
* This is a way of getting simple feedback from users. It's useful
*
* @class
* @constructor
- * @param {Object} [options]
- * @param {mw.Api} [options.api] if omitted, will just create a standard API
- * @param {mw.Title} [options.title="Feedback"] The title of the page where you collect
- * feedback.
- * @param {string} [options.dialogTitleMessageKey="feedback-submit"] Message key for the
- * title of the dialog box
- * @param {string} [options.bugsLink="//bugzilla.wikimedia.org/enter_bug.cgi"] URL where
- * bugs can be posted
- * @param {mw.Uri|string} [options.bugsListLink="//bugzilla.wikimedia.org/query.cgi"]
- * URL where bugs can be listed
+ * @param {Object} [config] Configuration object
+ * @cfg {mw.Api} [api] if omitted, will just create a standard API
+ * @cfg {mw.Title} [title="Feedback"] The title of the page where you collect
+ * feedback.
+ * @cfg {string} [dialogTitleMessageKey="feedback-dialog-title"] Message key for the
+ * title of the dialog box
+ * @cfg {mw.Uri|string} [bugsLink="//phabricator.wikimedia.org/maniphest/task/create/"] URL where
+ * bugs can be posted
+ * @cfg {mw.Uri|string} [bugsListLink="//phabricator.wikimedia.org/maniphest/query/advanced"] URL
+ * where bugs can be listed
+ * @cfg {boolean} [showUseragentCheckbox=false] Show a Useragent agreement checkbox as part of the form.
+ * @cfg {boolean} [useragentCheckboxMandatory=false] Make the Useragent checkbox mandatory.
+ * @cfg {string|jQuery} [useragentCheckboxMessage] Supply a custom message for the useragent checkbox.
+ * defaults to a combination of 'feedback-terms' and 'feedback-termsofuse' which includes a link to the
+ * wiki's Term of Use page.
*/
- mw.Feedback = function ( options ) {
- if ( options === undefined ) {
- options = {};
- }
+ mw.Feedback = function MwFeedback( config ) {
+ config = config || {};
- if ( options.api === undefined ) {
- options.api = new mw.Api();
- }
+ this.api = config.api || new mw.Api();
+ this.dialogTitleMessageKey = config.dialogTitleMessageKey || 'feedback-dialog-title';
- if ( options.title === undefined ) {
- options.title = new mw.Title( 'Feedback' );
- }
+ // Feedback page title
+ this.feedbackPageTitle = config.title || new mw.Title( 'Feedback' );
- if ( options.dialogTitleMessageKey === undefined ) {
- options.dialogTitleMessageKey = 'feedback-submit';
- }
+ // Links
+ this.bugsTaskSubmissionLink = config.bugsLink || '//phabricator.wikimedia.org/maniphest/task/create/';
+ this.bugsTaskListLink = config.bugsListLink || '//phabricator.wikimedia.org/maniphest/query/advanced';
- if ( options.bugsLink === undefined ) {
- options.bugsLink = '//bugzilla.wikimedia.org/enter_bug.cgi';
- }
+ // Terms of use
+ this.useragentCheckboxShow = !!config.showUseragentCheckbox;
+ this.useragentCheckboxMandatory = !!config.useragentCheckboxMandatory;
+ this.useragentCheckboxMessage = config.useragentCheckboxMessage ||
+ $( '<p>' )
+ .append( mw.msg( 'feedback-terms' ) )
+ .add( $( '<p>' ).append( mw.message( 'feedback-termsofuse' ).parse() ) );
+
+ // Message dialog
+ this.thankYouDialog = new OO.ui.MessageDialog();
+ };
+
+ /* Initialize */
+ OO.initClass( mw.Feedback );
+
+ /* Static Properties */
+ mw.Feedback.static.windowManager = null;
+ mw.Feedback.static.dialog = null;
+
+ /* Methods */
- if ( options.bugsListLink === undefined ) {
- options.bugsListLink = '//bugzilla.wikimedia.org/query.cgi';
+ /**
+ * Respond to dialog submit event. If the information was
+ * submitted, either successfully or with an error, open
+ * a MessageDialog to thank the user.
+ * @param {string} [status] A status of the end of operation
+ * of the main feedback dialog. Empty if the dialog was
+ * dismissed with no action or the user followed the button
+ * to the external task reporting site.
+ */
+ mw.Feedback.prototype.onDialogSubmit = function ( status ) {
+ var dialogConfig = {};
+ switch ( status ) {
+ case 'submitted':
+ dialogConfig = {
+ title: mw.msg( 'feedback-thanks-title' ),
+ message: $( '<span>' ).append(
+ mw.message(
+ 'feedback-thanks',
+ this.feedbackPageTitle.getNameText(),
+ $( '<a>' )
+ .attr( {
+ target: '_blank',
+ href: this.feedbackPageTitle.getUrl()
+ } )
+ ).parse()
+ ),
+ actions: [
+ {
+ action: 'accept',
+ label: mw.msg( 'feedback-close' ),
+ flags: 'primary'
+ }
+ ]
+ };
+ break;
+ case 'error1':
+ case 'error2':
+ case 'error3':
+ dialogConfig = {
+ title: mw.msg( 'feedback-error-title' ),
+ message: mw.msg( 'feedback-' + status ),
+ actions: [
+ {
+ action: 'accept',
+ label: mw.msg( 'feedback-close' ),
+ flags: 'primary'
+ }
+ ]
+ };
+ break;
}
- $.extend( this, options );
- this.setup();
+ // Show the message dialog
+ if ( !$.isEmptyObject( dialogConfig ) ) {
+ this.constructor.static.windowManager.openWindow(
+ this.thankYouDialog,
+ dialogConfig
+ );
+ }
};
- mw.Feedback.prototype = {
- /**
- * Sets up interface
- */
- setup: function () {
- var $feedbackPageLink,
- $bugNoteLink,
- $bugsListLink,
- fb = this;
-
- $feedbackPageLink = $( '<a>' )
- .attr( {
- href: fb.title.getUrl(),
- target: '_blank'
- } )
- .css( {
- whiteSpace: 'nowrap'
- } );
+ /**
+ * Modify the display form, and then open it, focusing interface on the subject.
+ *
+ * @param {Object} [contents] Prefilled contents for the feedback form.
+ * @param {string} [contents.subject] The subject of the feedback
+ * @param {string} [contents.message] The content of the feedback
+ */
+ mw.Feedback.prototype.launch = function ( contents ) {
+ // Dialog
+ if ( !this.constructor.static.dialog ) {
+ this.constructor.static.dialog = new mw.Feedback.Dialog();
+ this.constructor.static.dialog.connect( this, { submit: 'onDialogSubmit' } );
+ }
+ if ( !this.constructor.static.windowManager ) {
+ this.constructor.static.windowManager = new OO.ui.WindowManager();
+ this.constructor.static.windowManager.addWindows( [
+ this.constructor.static.dialog,
+ this.thankYouDialog
+ ] );
+ $( 'body' )
+ .append( this.constructor.static.windowManager.$element );
+ }
+ // Open the dialog
+ this.constructor.static.windowManager.openWindow(
+ this.constructor.static.dialog,
+ {
+ title: mw.msg( this.dialogTitleMessageKey ),
+ settings: {
+ api: this.api,
+ title: this.feedbackPageTitle,
+ dialogTitleMessageKey: this.dialogTitleMessageKey,
+ bugsTaskSubmissionLink: this.bugsTaskSubmissionLink,
+ bugsTaskListLink: this.bugsTaskListLink,
+ useragentCheckbox: {
+ show: this.useragentCheckboxShow,
+ mandatory: this.useragentCheckboxMandatory,
+ message: this.useragentCheckboxMessage
+ }
+ },
+ contents: contents
+ }
+ );
+ };
- $bugNoteLink = $( '<a>' ).attr( { href: '#' } ).click( function () {
- fb.displayBugs();
- } );
-
- $bugsListLink = $( '<a>' ).attr( {
- href: fb.bugsListLink,
- target: '_blank'
- } );
-
- // TODO: Use a stylesheet instead of these inline styles in the template
- this.$dialog = mw.template.get( 'mediawiki.feedback', 'dialog.html' ).render();
- this.$dialog.find( '.feedback-mode small p' ).msg(
- 'feedback-bugornote',
- $bugNoteLink,
- fb.title.getNameText(),
- $feedbackPageLink.clone()
- );
- this.$dialog.find( '.feedback-form .subject span' ).msg( 'feedback-subject' );
- this.$dialog.find( '.feedback-form .message span' ).msg( 'feedback-message' );
- this.$dialog.find( '.feedback-bugs p' ).msg( 'feedback-bugcheck', $bugsListLink );
- this.$dialog.find( '.feedback-submitting span' ).msg( 'feedback-adding' );
- this.$dialog.find( '.feedback-thanks' ).msg( 'feedback-thanks', fb.title.getNameText(),
- $feedbackPageLink.clone() );
-
- this.$dialog.dialog( {
- width: 500,
- autoOpen: false,
- title: mw.message( this.dialogTitleMessageKey ).escaped(),
- modal: true,
- buttons: fb.buttons
- } );
-
- this.subjectInput = this.$dialog.find( 'input.feedback-subject' ).get( 0 );
- this.messageInput = this.$dialog.find( 'textarea.feedback-message' ).get( 0 );
- },
+ /**
+ * mw.Feedback Dialog
+ *
+ * @class
+ * @extends OO.ui.ProcessDialog
+ *
+ * @constructor
+ * @param {Object} config Configuration object
+ */
+ mw.Feedback.Dialog = function mwFeedbackDialog( config ) {
+ // Parent constructor
+ mw.Feedback.Dialog.super.call( this, config );
+
+ this.status = '';
+ this.feedbackPageTitle = null;
+ // Initialize
+ this.$element.addClass( 'mwFeedback-Dialog' );
+ };
- /**
- * Displays a section of the dialog.
- *
- * @param {"form"|"bugs"|"submitting"|"thanks"|"error"} s
- * The section of the dialog to show.
- */
- display: function ( s ) {
- // Hide the buttons
- this.$dialog.dialog( { buttons: {} } );
- // Hide everything
- this.$dialog.find( '.feedback-mode' ).hide();
- // Show the desired div
- this.$dialog.find( '.feedback-' + s ).show();
+ OO.inheritClass( mw.Feedback.Dialog, OO.ui.ProcessDialog );
+
+ /* Static properties */
+ mw.Feedback.Dialog.static.name = 'mwFeedbackDialog';
+ mw.Feedback.Dialog.static.title = mw.msg( 'feedback-dialog-title' );
+ mw.Feedback.Dialog.static.size = 'medium';
+ mw.Feedback.Dialog.static.actions = [
+ {
+ action: 'submit',
+ label: mw.msg( 'feedback-submit' ),
+ flags: [ 'primary', 'constructive' ]
},
-
- /**
- * Display the submitting section.
- */
- displaySubmitting: function () {
- this.display( 'submitting' );
+ {
+ action: 'external',
+ label: mw.msg( 'feedback-external-bug-report-button' ),
+ flags: 'constructive'
},
+ {
+ action: 'cancel',
+ label: mw.msg( 'feedback-cancel' ),
+ flags: 'safe'
+ }
+ ];
- /**
- * Display the bugs section.
- */
- displayBugs: function () {
- var fb = this,
- bugsButtons = {};
-
- this.display( 'bugs' );
- bugsButtons[ mw.msg( 'feedback-bugnew' ) ] = function () {
- window.open( fb.bugsLink, '_blank' );
- };
- bugsButtons[ mw.msg( 'feedback-cancel' ) ] = function () {
- fb.cancel();
- };
- this.$dialog.dialog( {
- buttons: bugsButtons
- } );
- },
+ /**
+ * @inheritdoc
+ */
+ mw.Feedback.Dialog.prototype.initialize = function () {
+ var feedbackSubjectFieldLayout, feedbackMessageFieldLayout,
+ feedbackFieldsetLayout;
+
+ // Parent method
+ mw.Feedback.Dialog.super.prototype.initialize.call( this );
+
+ this.feedbackPanel = new OO.ui.PanelLayout( {
+ scrollable: false,
+ expanded: false,
+ padded: true
+ } );
+
+ this.$spinner = $( '<div>' )
+ .addClass( 'feedback-spinner' );
+
+ // Feedback form
+ this.feedbackMessageLabel = new OO.ui.LabelWidget( {
+ classes: [ 'mw-feedbackDialog-welcome-message' ]
+ } );
+ this.feedbackSubjectInput = new OO.ui.TextInputWidget( {
+ multiline: false
+ } );
+ this.feedbackMessageInput = new OO.ui.TextInputWidget( {
+ autosize: true,
+ multiline: true
+ } );
+ feedbackSubjectFieldLayout = new OO.ui.FieldLayout( this.feedbackSubjectInput, {
+ label: mw.msg( 'feedback-subject' )
+ } );
+ feedbackMessageFieldLayout = new OO.ui.FieldLayout( this.feedbackMessageInput, {
+ label: mw.msg( 'feedback-message' )
+ } );
+ feedbackFieldsetLayout = new OO.ui.FieldsetLayout( {
+ items: [ feedbackSubjectFieldLayout, feedbackMessageFieldLayout ],
+ classes: [ 'mw-feedbackDialog-feedback-form' ]
+ } );
+
+ // Useragent terms of use
+ this.useragentCheckbox = new OO.ui.CheckboxInputWidget();
+ this.useragentFieldLayout = new OO.ui.FieldLayout( this.useragentCheckbox, {
+ classes: [ 'mw-feedbackDialog-feedback-terms' ],
+ align: 'inline'
+ } );
+
+ this.feedbackPanel.$element.append(
+ this.feedbackMessageLabel.$element,
+ feedbackFieldsetLayout.$element,
+ this.useragentFieldLayout.$element
+ );
+
+ // Events
+ this.feedbackSubjectInput.connect( this, { change: 'validateFeedbackForm' } );
+ this.feedbackMessageInput.connect( this, { change: 'validateFeedbackForm' } );
+ this.feedbackMessageInput.connect( this, { change: 'updateSize' } );
+ this.useragentCheckbox.connect( this, { change: 'validateFeedbackForm' } );
+
+ this.$body.append( this.feedbackPanel.$element );
+ };
- /**
- * Display the thanks section.
- */
- displayThanks: function () {
- var fb = this,
- closeButton = {};
-
- this.display( 'thanks' );
- closeButton[ mw.msg( 'feedback-close' ) ] = function () {
- fb.$dialog.dialog( 'close' );
- };
- this.$dialog.dialog( {
- buttons: closeButton
- } );
- },
+ /**
+ * Validate the feedback form
+ */
+ mw.Feedback.Dialog.prototype.validateFeedbackForm = function () {
+ var isValid = (
+ (
+ !this.useragentMandatory ||
+ this.useragentCheckbox.isSelected()
+ ) &&
+ (
+ !!this.feedbackMessageInput.getValue() ||
+ !!this.feedbackSubjectInput.getValue()
+ )
+ );
- /**
- * Display the feedback form
- * @param {Object} [contents] Prefilled contents for the feedback form.
- * @param {string} [contents.subject] The subject of the feedback
- * @param {string} [contents.message] The content of the feedback
- */
- displayForm: function ( contents ) {
- var fb = this,
- formButtons = {};
-
- this.subjectInput.value = ( contents && contents.subject ) ? contents.subject : '';
- this.messageInput.value = ( contents && contents.message ) ? contents.message : '';
-
- this.display( 'form' );
-
- // Set up buttons for dialog box. We have to do it the hard way since the json keys are localized
- formButtons[ mw.msg( 'feedback-submit' ) ] = function () {
- fb.submit();
- };
- formButtons[ mw.msg( 'feedback-cancel' ) ] = function () {
- fb.cancel();
- };
- this.$dialog.dialog( { buttons: formButtons } ); // put the buttons back
- },
+ this.actions.setAbilities( { submit: isValid } );
+ };
- /**
- * Display an error on the form.
- *
- * @param {string} message Should be a valid message key.
- */
- displayError: function ( message ) {
- var fb = this,
- closeButton = {};
-
- this.display( 'error' );
- this.$dialog.find( '.feedback-error-msg' ).msg( message );
- closeButton[ mw.msg( 'feedback-close' ) ] = function () {
- fb.$dialog.dialog( 'close' );
- };
- this.$dialog.dialog( { buttons: closeButton } );
- },
+ /**
+ * @inheritdoc
+ */
+ mw.Feedback.Dialog.prototype.getBodyHeight = function () {
+ return this.feedbackPanel.$element.outerHeight( true );
+ };
- /**
- * Close the feedback form.
- */
- cancel: function () {
- this.$dialog.dialog( 'close' );
- },
+ /**
+ * @inheritdoc
+ */
+ mw.Feedback.Dialog.prototype.getSetupProcess = function ( data ) {
+ return mw.Feedback.Dialog.super.prototype.getSetupProcess.call( this, data )
+ .next( function () {
+ var plainMsg, parsedMsg,
+ settings = data.settings;
+ data.contents = data.contents || {};
+
+ // Prefill subject/message
+ this.feedbackSubjectInput.setValue( data.contents.subject );
+ this.feedbackMessageInput.setValue( data.contents.message );
+
+ this.status = '';
+ this.api = settings.api;
+ this.setBugReportLink( settings.bugsTaskSubmissionLink );
+ this.feedbackPageTitle = settings.title;
+ this.feedbackPageName = settings.title.getNameText();
+ this.feedbackPageUrl = settings.title.getUrl();
+
+ // Useragent checkbox
+ if ( settings.useragentCheckbox.show ) {
+ this.useragentFieldLayout.setLabel( settings.useragentCheckbox.message );
+ }
+ this.useragentMandatory = settings.useragentCheckbox.mandatory;
+ this.useragentFieldLayout.toggle( settings.useragentCheckbox.show );
+
+ // HACK: Setting a link in the messages doesn't work. There is already a report
+ // about this, and the bug report offers a somewhat hacky work around that
+ // includes setting a separate message to be parsed.
+ // We want to make sure the user can configure both the title of the page and
+ // a separate url, so this must be allowed to parse correctly.
+ // See https://phabricator.wikimedia.org/T49395#490610
+ mw.messages.set( {
+ 'feedback-dialog-temporary-message':
+ '<a href="' + this.feedbackPageUrl + '" target="_blank">' + this.feedbackPageName + '</a>'
+ } );
+ plainMsg = mw.message( 'feedback-dialog-temporary-message' ).plain();
+ mw.messages.set( { 'feedback-dialog-temporary-message-parsed': plainMsg } );
+ parsedMsg = mw.message( 'feedback-dialog-temporary-message-parsed' );
+ this.feedbackMessageLabel.setLabel(
+ // Double-parse
+ $( '<span>' )
+ .append( mw.message( 'feedback-dialog-intro', parsedMsg ).parse() )
+ );
+
+ this.validateFeedbackForm();
+ }, this );
+ };
- /**
- * Submit the feedback form.
- */
- submit: function () {
- var subject, message,
- fb = this;
-
- // Get the values to submit.
- subject = $.trim( this.subjectInput.value );
-
- // We used to include "mw.html.escape( navigator.userAgent )" but there are legal issues
- // with posting this without their explicit consent
- message = $.trim( this.messageInput.value );
- if ( message.indexOf( '~~~' ) === -1 ) {
- message += ' ~~~~';
- }
+ /**
+ * @inheritdoc
+ */
+ mw.Feedback.Dialog.prototype.getReadyProcess = function ( data ) {
+ return mw.Feedback.Dialog.super.prototype.getReadyProcess.call( this, data )
+ .next( function () {
+ this.feedbackSubjectInput.focus();
+ }, this );
+ };
- this.displaySubmitting();
-
- // Post the message, resolving redirects
- this.api.newSection(
- this.title,
- subject,
- message,
- { redirect: true }
- )
- .done( function ( result ) {
- if ( result.edit.result === 'Success' ) {
- fb.displayThanks();
- } else {
- // unknown API result
- fb.displayError( 'feedback-error1' );
+ /**
+ * @inheritdoc
+ */
+ mw.Feedback.Dialog.prototype.getActionProcess = function ( action ) {
+ if ( action === 'cancel' ) {
+ return new OO.ui.Process( function () {
+ this.close( { action: action } );
+ }, this );
+ } else if ( action === 'external' ) {
+ return new OO.ui.Process( function () {
+ // Open in a new window
+ window.open( this.getBugReportLink(), '_blank' );
+ // Close the dialog
+ this.close();
+ }, this );
+ } else if ( action === 'submit' ) {
+ return new OO.ui.Process( function () {
+ var fb = this,
+ userAgentMessage = ':' +
+ '<small>' +
+ mw.msg( 'feedback-useragent' ) +
+ ' ' +
+ mw.html.escape( navigator.userAgent ) +
+ '</small>\n\n',
+ subject = this.feedbackSubjectInput.getValue(),
+ message = this.feedbackMessageInput.getValue();
+
+ // Add user agent if checkbox is selected
+ if ( this.useragentCheckbox.isSelected() ) {
+ message = userAgentMessage + message;
}
- } )
- .fail( function ( code, result ) {
- if ( code === 'http' ) {
- // ajax request failed
- fb.displayError( 'feedback-error3' );
- mw.log.warn( 'Feedback report failed with HTTP error: ' + result.textStatus );
- } else {
- fb.displayError( 'feedback-error2' );
- mw.log.warn( 'Feedback report failed with API error: ' + code );
+
+ // Add signature if needed
+ if ( message.indexOf( '~~~' ) === -1 ) {
+ message += '\n\n~~~~';
}
- } );
- },
- /**
- * Modify the display form, and then open it, focusing interface on the subject.
- * @param {Object} [contents] Prefilled contents for the feedback form.
- * @param {string} [contents.subject] The subject of the feedback
- * @param {string} [contents.message] The content of the feedback
- */
- launch: function ( contents ) {
- this.displayForm( contents );
- this.$dialog.dialog( 'open' );
- this.subjectInput.focus();
+ // Post the message, resolving redirects
+ this.pushPending();
+ this.api.newSection(
+ this.feedbackPageTitle,
+ subject,
+ message,
+ { redirect: true }
+ )
+ .done( function ( result ) {
+ if ( result.edit.result === 'Success' ) {
+ fb.status = 'submitted';
+ } else {
+ fb.status = 'error1';
+ }
+ fb.popPending();
+ fb.close();
+ } )
+ .fail( function ( code, result ) {
+ if ( code === 'http' ) {
+ fb.status = 'error3';
+ // ajax request failed
+ mw.log.warn( 'Feedback report failed with HTTP error: ' + result.textStatus );
+ } else {
+ fb.status = 'error2';
+ mw.log.warn( 'Feedback report failed with API error: ' + code );
+ }
+ fb.popPending();
+ fb.close();
+ } );
+ }, this );
}
+ // Fallback to parent handler
+ return mw.Feedback.Dialog.super.prototype.getActionProcess.call( this, action );
+ };
+
+ /**
+ * @inheritdoc
+ */
+ mw.Feedback.Dialog.prototype.getTeardownProcess = function ( data ) {
+ return mw.Feedback.Dialog.super.prototype.getTeardownProcess.call( this, data )
+ .first( function () {
+ this.emit( 'submit', this.status, this.feedbackPageName, this.feedbackPageUrl );
+ // Cleanup
+ this.status = '';
+ this.feedbackPageTitle = null;
+ this.feedbackSubjectInput.setValue( '' );
+ this.feedbackMessageInput.setValue( '' );
+ this.useragentCheckbox.setSelected( false );
+ }, this );
};
+
+ /**
+ * Set the bug report link
+ * @param {string} link Link to the external bug report form
+ */
+ mw.Feedback.Dialog.prototype.setBugReportLink = function ( link ) {
+ this.bugReportLink = link;
+ };
+
+ /**
+ * Get the bug report link
+ * @returns {string} Link to the external bug report form
+ */
+ mw.Feedback.Dialog.prototype.getBugReportLink = function () {
+ return this.bugReportLink;
+ };
+
}( mediaWiki, jQuery ) );
+++ /dev/null
-<div style="position: relative; display: block;" class="ui-dialog-content ui-widget-content">
- <div class="feedback-mode feedback-form">
- <small><p></p></small>
- <div class="subject" style="margin-top: 1em;">
- <span></span><br>
- <input type="text" class="feedback-subject" name="subject" maxlength="60"
- style="width: 100%; -moz-box-sizing: border-box; -webkit-box-sizing: border-box; box-sizing: border-box;">
- </div>
- <div class="message" style="margin-top: 0.4em;">
- <span></span><br>
- <textarea name="message" class="feedback-message" rows="5" cols="60"></textarea>
- </div>
- </div>
- <div class="feedback-mode feedback-bugs">
- <p></p>
- </div>
- <div class="feedback-mode feedback-submitting" style="text-align: center; margin: 3em 0;">
- <span></span><br>
- <span class="feedback-spinner"></span>
- </div>
- <div class="feedback-mode feedback-thanks" style="text-align: center; margin:1em"></div>
- <div class="feedback-mode feedback-error" style="position: relative;">
- <div class="feedback-error-msg" style=" color:#990000; margin-top:0.4em;"></div>
- </div>
-</div>
require_once __DIR__ . "/../../../maintenance/backupTextPass.inc";
/**
- * Tests for page dumps of BackupDumper
+ * Tests for TextPassDumper that rely on the database
*
* @group Database
* @group Dump
* @covers TextPassDumper
*/
-class TextPassDumperTest extends DumpTestCase {
+class TextPassDumperDatabaseTest extends DumpTestCase {
// We'll add several pages, revision and texts. The following variables hold the
// corresponding ids.
$this->fail( "Could not open stream for stderr" );
}
- $iterations = 32; // We'll start with that many iterations of revisions in stub
+ $iterations = 32; // We'll start with that many iterations of revisions
+ // in stub. Make sure that the generated volume is above the buffer size
+ // set below. Otherwise, the checkpointing does not trigger.
$lastDuration = 0;
$minDuration = 2; // We want the dump to take at least this many seconds
$checkpointAfter = 0.5; // Generate checkpoint after this many seconds
$dumper = new TextPassDumper( array( "--stub=file:" . $nameStub,
"--output=" . $checkpointFormat . ":" . $nameOutputDir . "/full",
"--maxtime=1" /*This is in minutes. Fixup is below*/,
+ "--buffersize=32768", // The default of 32 iterations fill up 32KB about twice
"--checkpointfile=checkpoint-%s-%s.xml.gz" ) );
$dumper->setDb( $this->db );
$dumper->maxTimeAllowed = $checkpointAfter; // Patching maxTime from 1 minute
}
/**
- * Broken per T70653.
- *
* @group large
- * @group Broken
*/
function testCheckpointPlain() {
$this->checkpointHelper();
* PHP extensions, we go for gzip instead, which triggers the same relevant code
* paths while still being testable on more systems.
*
- * Broken per T70653.
- *
* @group large
- * @group Broken
*/
function testCheckpointGzip() {
$this->checkHasGzip();
}
}
+
+/**
+ * Tests for TextPassDumper that do not rely on the database
+ *
+ * (As the Database group is only detected at class level (not method level), we
+ * cannot bring this test case's tests into the above main test case.)
+ *
+ * @group Dump
+ * @covers TextPassDumper
+ */
+class TextPassDumperDatabaselessTest extends MediaWikiLangTestCase {
+ /**
+ * Ensures that setting the buffer size is effective.
+ *
+ * @dataProvider bufferSizeProvider
+ */
+ function testBufferSizeSetting( $expected, $size, $msg ) {
+ $dumper = new TextPassDumperAccessor( array( "--buffersize=" . $size ) );
+ $this->assertEquals( $expected, $dumper->getBufferSize(), $msg);
+ }
+
+ /**
+ * Ensures that setting the buffer size is effective.
+ *
+ * @dataProvider bufferSizeProvider
+ */
+ function bufferSizeProvider() {
+ // expected, bufferSize to initialize with, message
+ return array(
+ array( 512 * 1024, 512 * 1024, "Setting 512KB is not effective" ),
+ array( 8192, 8192, "Setting 8KB is not effective" ),
+ array( 4096, 2048, "Could set buffer size below lower bound" )
+ );
+ }
+}
+
+/**
+ * Accessor for internal state of TextPassDumper
+ *
+ * Do not warrentless add getters here.
+ */
+class TextPassDumperAccessor extends TextPassDumper {
+ /**
+ * Gets the bufferSize.
+ *
+ * If bufferSize setting does not work correctly, testCheckpoint... tests
+ * fail and point in the wrong direction. To aid in troubleshooting when
+ * testCheckpoint... tests break at some point in the future, we test the
+ * bufferSize setting, hence need this accessor.
+ *
+ * (Yes, bufferSize is internal state of the TextPassDumper, but aiding
+ * debugging of testCheckpoint... in the future seems to be worth testing
+ * against it nonetheless.)
+ */
+ public function getBufferSize() {
+ return $this->bufferSize;
+ }
+}