* @param object[]|IResultWrapper $slotRows
* @param int $queryFlags
* @param Title $title
+ * @param array|null $slotContents a map from blobAddress to slot
+ * content blob or Content object.
*
* @return SlotRecord[]
*/
- private function constructSlotRecords( $revId, $slotRows, $queryFlags, Title $title ) {
+ private function constructSlotRecords(
+ $revId,
+ $slotRows,
+ $queryFlags,
+ Title $title,
+ $slotContents = null
+ ) {
$slots = [];
foreach ( $slotRows as $row ) {
= $this->emulateContentId( intval( $row->rev_text_id ) );
}
- $contentCallback = function ( SlotRecord $slot ) use ( $queryFlags ) {
- return $this->loadSlotContent( $slot, null, null, null, $queryFlags );
+ $contentCallback = function ( SlotRecord $slot ) use ( $slotContents, $queryFlags ) {
+ $blob = null;
+ if ( isset( $slotContents[$slot->getAddress()] ) ) {
+ $blob = $slotContents[$slot->getAddress()];
+ if ( $blob instanceof Content ) {
+ return $blob;
+ }
+ }
+ return $this->loadSlotContent( $slot, $blob, null, null, $queryFlags );
};
$slots[$row->role_name] = new SlotRecord( $row, $contentCallback );
/**
* @param object $row A database row generated from a query based on getQueryInfo()
- * @param null|object[] $slotRows Database rows generated from a query based on
- * getSlotsQueryInfo with the 'content' flag set.
+ * @param null|object[]|RevisionSlots $slots
+ * - Database rows generated from a query based on getSlotsQueryInfo
+ * with the 'content' flag set. Or
+ * - RevisionSlots instance
* @param int $queryFlags
* @param Title|null $title
* @param bool $fromCache if true, the returned RevisionRecord will ensure that no stale
* @see RevisionFactory::newRevisionFromRow
*
* MCR migration note: this replaces Revision::newFromRow
- *
*/
public function newRevisionFromRowAndSlots(
$row,
- $slotRows,
+ $slots,
$queryFlags = 0,
Title $title = null,
$fromCache = false
// Legacy because $row may have come from self::selectFields()
$comment = $this->commentStore->getCommentLegacy( $db, 'rev_comment', $row, true );
- $slots = $this->newRevisionSlots( $row->rev_id, $row, $slotRows, $queryFlags, $title );
+ if ( !( $slots instanceof RevisionSlots ) ) {
+ $slots = $this->newRevisionSlots( $row->rev_id, $row, $slots, $queryFlags, $title );
+ }
// If this is a cached row, instantiate a cache-aware revision class to avoid stale data.
if ( $fromCache ) {
* loaded immediately. Supports falsy or truthy value as well
* as an explicit list of slot role names.
* 'content'- whether the actual content of the slots should be
- * preloaded. TODO: no supported yet.
+ * preloaded.
* @param int $queryFlags
* @param Title|null $title
* @return StatusValue a status with a RevisionRecord[] of successfully fetched revisions
}, $options['slots'] );
}
- // TODO: Support optional fetching of the content
- $queryInfo = self::getSlotsQueryInfo( [ 'content' ] );
+ // We need to set the `content` flag because newRevisionFromRowAndSlots requires content
+ // metadata to be loaded.
+ $slotQueryInfo = self::getSlotsQueryInfo( [ 'content' ] );
$db = $this->getDBConnectionRefForQueryFlags( $queryFlags );
$slotRows = $db->select(
- $queryInfo['tables'],
- $queryInfo['fields'],
+ $slotQueryInfo['tables'],
+ $slotQueryInfo['fields'],
$slotQueryConds,
__METHOD__,
[],
- $queryInfo['joins']
+ $slotQueryInfo['joins']
);
$slotRowsByRevId = [];
foreach ( $slotRows as $slotRow ) {
$slotRowsByRevId[$slotRow->slot_revision_id][] = $slotRow;
}
+
+ $slotContents = null;
+ if ( $options['content'] ?? false ) {
+ $blobAddresses = [];
+ foreach ( $slotRows as $slotRow ) {
+ $blobAddresses[] = $slotRow->content_address;
+ }
+ $slotContentFetchStatus = $this->blobStore
+ ->getBlobBatch( $blobAddresses, $queryFlags );
+ foreach ( $slotContentFetchStatus->getErrors() as $error ) {
+ $result->warning( $error['message'], ...$error['params'] );
+ }
+ $slotContents = $slotContentFetchStatus->getValue();
+ }
+
$result->setResult( true, array_map( function ( $row ) use
- ( $slotRowsByRevId, $queryFlags, $titlesByPageId, $result ) {
+ ( $slotRowsByRevId, $queryFlags, $titlesByPageId, $slotContents, $result ) {
if ( !isset( $slotRowsByRevId[$row->rev_id] ) ) {
$result->warning(
'internalerror',
try {
return $this->newRevisionFromRowAndSlots(
$row,
- $slotRowsByRevId[$row->rev_id],
+ new RevisionSlots(
+ $this->constructSlotRecords(
+ $row->rev_id,
+ $slotRowsByRevId[$row->rev_id],
+ $queryFlags,
+ $titlesByPageId[$row->rev_page],
+ $slotContents
+ )
+ ),
$queryFlags,
$titlesByPageId[$row->rev_page]
);
*
* @see self::getLocalURL for the arguments.
* @see wfExpandUrl
- * @param string|string[] $query
+ * @param string|array $query
* @param string|string[]|bool $query2
* @param string|int|null $proto Protocol type to use in URL
* @return string The URL
* valid to link, locally, to the current Title.
* @see self::newFromText to produce a Title object.
*
- * @param string|string[] $query An optional query string,
+ * @param string|array $query An optional query string,
* not used for interwiki links. Can be specified as an associative array as well,
* e.g., [ 'action' => 'edit' ] (keys and values will be URL-escaped).
* Some query patterns will trigger various shorturl path replacements.
* protocol-relative, the URL will be expanded to http://
*
* @see self::getLocalURL for the arguments.
- * @param string|string[] $query
+ * @param string|array $query
* @param string|bool $query2 Deprecated
* @return string The URL
*/
* NOTE: Unlike getInternalURL(), the canonical URL includes the fragment
*
* @see self::getLocalURL for the arguments.
- * @param string|string[] $query
+ * @param string|array $query
* @param string|bool $query2 Deprecated
* @return string The URL
* @since 1.18
"authors": [
"Zygimantus",
"Eitvys200",
- "Hugo.arg"
+ "Hugo.arg",
+ "Homo"
]
},
"apihelp-main-param-action": "Kurį veiksmą atlikti.",
"apihelp-createaccount-summary": "Kurti naują vartotojo paskyrą.",
"apihelp-delete-summary": "Ištrinti puslapį.",
"apihelp-delete-param-watch": "Pridėti puslapį prie dabartinio vartotojo stebimųjų sąrašo.",
- "apihelp-delete-param-unwatch": "Pašalinti puslapį iš dabartinio vartotojo stebimųjų sąrašo.",
+ "apihelp-delete-param-unwatch": "Pašalinti puslapį iš dabartinio naudotojo stebimųjų sąrašo.",
"apihelp-delete-example-simple": "Ištrinti <kbd>Main Page</kbd>.",
"apihelp-delete-example-reason": "Ištrinti <kbd>Main Page</kbd> su priežastimi <kbd>Preparing for move</kbd>.",
"apihelp-disabled-summary": "Šis modulis buvo išjungtas.",
"apihelp-edit-param-createonly": "Neredaguoti puslapio jei jis jau egzistuoja.",
"apihelp-edit-param-nocreate": "Parodyti klaidą, jei puslapis neegzistuoja.",
"apihelp-edit-param-watch": "Pridėti puslapį į dabartinio vartotojo stebimųjų sąrašą.",
- "apihelp-edit-param-unwatch": "Pašalinti puslapį iš dabartinio vartotojo stebimųjų sąrašo.",
+ "apihelp-edit-param-unwatch": "Pašalinti puslapį iš dabartinio naudotojo stebimųjų sąrašo.",
"apihelp-edit-param-redirect": "Automatiškai išspręsti peradresavimus.",
"apihelp-edit-param-contentmodel": "Naujam turiniui taikomas turinio modelis.",
"apihelp-edit-example-edit": "Redaguoti puslapį.",
"apihelp-move-param-movetalk": "Pervadinti aptarimo puslapį, jei jis egzistuoja.",
"apihelp-move-param-noredirect": "Nekurti nukreipimo.",
"apihelp-move-param-watch": "Pridėti puslapį ir nukreipimą į dabartinio vartotojo stebimųjų sąrašą.",
- "apihelp-move-param-unwatch": "Pašalinti puslapį ir nukreipimą iš dabartinio vartotojo stebimųjų sąrašo.",
+ "apihelp-move-param-unwatch": "Pašalinti puslapį ir nukreipimą iš dabartinio naudotojo stebimųjų sąrašo.",
"apihelp-move-param-ignorewarnings": "Ignuoruoti bet kokius įspėjimus.",
"apihelp-move-example-move": "Perkelti <kbd>Badtitle</kbd> į <kbd>Goodtitle</kbd> nepaliekant nukreipimo.",
"apihelp-opensearch-summary": "Ieškoti viki naudojant OpenSearch protokolą.",
"apihelp-query+watchlist-paramvalue-type-new": "Puslapio sukūrimai.",
"apihelp-query+watchlist-paramvalue-type-log": "Žurnalo įrašai.",
"apihelp-resetpassword-param-user": "Iš naujo nustatomas vartotojas.",
- "apihelp-resetpassword-param-email": "Iš naujo nustatomo vartotojo el. pašto adresas.",
+ "apihelp-resetpassword-param-email": "Iš naujo nustatomo naudotojo el. pašto adresas.",
"apihelp-setpagelanguage-summary": "Keisti puslapio kalbą.",
"apihelp-setpagelanguage-param-reason": "Keitimo priežastis.",
"apihelp-stashedit-param-title": "Puslapio pavadinimas buvo redaguotas.",
* @param string &$s HTML to update
* @param Title $title
* @param string $logtype
+ * @param bool $useParentheses (optional) Wrap log entry in parentheses where needed
*/
- public function insertLog( &$s, $title, $logtype ) {
+ public function insertLog( &$s, $title, $logtype, $useParentheses = true ) {
$page = new LogPage( $logtype );
$logname = $page->getName()->setContext( $this->getContext() )->text();
- $s .= Html::rawElement( 'span', [
- 'class' => 'mw-changeslist-links'
- ], $this->linkRenderer->makeKnownLink( $title, $logname ) );
+ $link = $this->linkRenderer->makeKnownLink( $title, $logname, [
+ 'class' => $useParentheses ? '' : 'mw-changeslist-links'
+ ] );
+ if ( $useParentheses ) {
+ $s .= $this->msg( 'parentheses' )->rawParams(
+ $link
+ )->escaped();
+ } else {
+ $s .= $link;
+ }
}
/**
if ( $rc->mAttribs['rc_log_type'] ) {
$logtitle = SpecialPage::getTitleFor( 'Log', $rc->mAttribs['rc_log_type'] );
- $this->insertLog( $html, $logtitle, $rc->mAttribs['rc_log_type'] );
+ $this->insertLog( $html, $logtitle, $rc->mAttribs['rc_log_type'], false );
$flags = $this->recentChangesFlags( [ 'unpatrolled' => $unpatrolled,
'bot' => $rc->mAttribs['rc_bot'] ], '' );
if ( $flags !== '' ) {
list( $name, $htmlubpage ) = MediaWikiServices::getInstance()->getSpecialPageFactory()->
resolveAlias( $rc->mAttribs['rc_title'] );
if ( $name == 'Log' ) {
- $this->insertLog( $html, $rc->getTitle(), $htmlubpage );
+ $this->insertLog( $html, $rc->getTitle(), $htmlubpage, false );
}
// Regular entries
} else {
/** @var bool */
private $mNoUpdates = false;
- /** @var Config $config */
+ /**
+ * @deprecated since 1.31, along with self::downloadSource()
+ * @var Config $config
+ */
private $config;
+ /**
+ * @param Config $config Deprecated since 1.31, along with self::downloadSource(). Just pass an
+ * empty HashConfig.
+ */
public function __construct( Config $config ) {
$this->config = $config;
}
$this->mLogEventsList = $list;
$this->limitType( $types ); // also excludes hidden types
+ $this->limitLogId( $logId );
+ $this->limitFilterTypes();
$this->limitPerformer( $performer );
$this->limitTitle( $title, $pattern );
$this->limitAction( $action );
$this->getDateCond( $year, $month, $day );
$this->mTagFilter = $tagFilter;
- $this->limitLogId( $logId );
$this->mDb = wfGetDB( DB_REPLICA, 'logpager' );
}
return $query;
}
- // Call ONLY after calling $this->limitType() already!
+ private function limitFilterTypes() {
+ if ( $this->hasEqualsClause( 'log_id' ) ) { // T220834
+ return;
+ }
+ $filterTypes = $this->getFilterParams();
+ foreach ( $filterTypes as $type => $hide ) {
+ if ( $hide ) {
+ $this->mConds[] = 'log_type != ' . $this->mDb->addQuotes( $type );
+ }
+ }
+ }
+
public function getFilterParams() {
global $wgFilterLogTypes;
$filters = [];
}
$filters[$type] = $hide;
- if ( $hide ) {
- $this->mConds[] = 'log_type != ' . $this->mDb->addQuotes( $type );
- }
}
return $filters;
private $existingPropNames = null;
/**
- * @var string|null
+ * @var int|null
*/
private $ns;
$request = $this->getRequest();
$propname = $request->getVal( 'propname', $par );
+ $this->ns = $request->getIntOrNull( 'namespace' );
$this->reverse = $request->getBool( 'reverse' );
$this->sortByValue = $request->getBool( 'sortbyvalue' );
'type' => 'namespaceselect',
'name' => 'namespace',
'label-message' => 'namespace',
- 'all' => null,
- 'default' => null,
+ 'all' => '',
+ 'default' => $this->ns,
],
'reverse' => [
'type' => 'check',
public function onSubmit( $data, $form ) {
$this->propName = $data['propname'];
- $this->ns = $data['namespace'];
parent::execute( $data['propname'] );
}
'options' => []
];
- if ( $this->ns && isset( $this->ns ) ) {
+ if ( $this->ns !== null ) {
$query['conds']['page_namespace'] = $this->ns;
}
"tog-watchmoves": "Přidávat mnou přesouvané stránky a soubory mezi sledované",
"tog-watchdeletion": "Přidávat stránky a soubory, které smažu, mezi sledované",
"tog-watchuploads": "Přidávat mnou načtené soubory ke sledovaným",
- "tog-watchrollback": "Přidávat stránky, které jsem {{GENDER:|vrátil|vrátila}} zpět, ke sledovaným",
+ "tog-watchrollback": "Přidávat stránky, kde jsem {{GENDER:|použil|použila}} vrácení zpět, ke sledovaným",
"tog-minordefault": "Označovat editace implicitně jako malé",
"tog-previewontop": "Zobrazovat náhled před editačním oknem (ne za ním)",
"tog-previewonfirst": "Zobrazit při první editaci náhled",
"tog-norollbackdiff": "Po vrácení změny nezobrazovat porovnání rozdílů",
"tog-useeditwarning": "Upozornit, když budu opouštět editaci bez uložení změn",
"tog-prefershttps": "Po přihlášení vždy používat zabezpečené připojení",
- "tog-showrollbackconfirmation": "Při kliknutí na odkaz pro vrácení editace zobrazit žádost o potvrzení",
+ "tog-showrollbackconfirmation": "Při kliknutí na odkaz pro rychlý revert zobrazit žádost o potvrzení",
"tog-requireemail": "Pro obnovu hesla vyžadovat e-mail",
"underline-always": "Vždy",
"underline-never": "Nikdy",
"category_header": "Stránky v kategorii „$1“",
"subcategories": "Podkategorie",
"category-media-header": "Soubory v kategorii „$1“",
- "category-empty": "''Tato kategorie neobsahuje žádné stránky či soubory.''",
+ "category-empty": "<em>Tato kategorie neobsahuje žádné stránky či soubory.</em>",
"hidden-categories": "{{PLURAL:$1|Skrytá kategorie|Skryté kategorie|Skryté kategorie}}",
"hidden-category-category": "Skryté kategorie",
"category-subcat-count": "{{PLURAL:$2|V této kategorii je pouze následující podkategorie.|{{PLURAL:$1|Zobrazuje se jedna podkategorie|Zobrazují se $1 podkategorie|Zobrazuje se $1 podkategorií}} z celkového počtu $2 podkategorií v této kategorii.|{{PLURAL:$1|Zobrazuje se jedna podkategorie|Zobrazují se $1 podkategorie|Zobrazuje se $1 podkategorií}} z celkového počtu $2 podkategorií v této kategorii.}}",
"listfiles-userdoesnotexist": "Das Benutzerkonto „$1“ ist nicht registriert.",
"imgfile": "Datei",
"listfiles": "Dateiliste",
+ "listfiles_subpage": "Von $1 hochgeladene Dateien",
"listfiles_thumb": "Vorschaubild",
"listfiles_date": "Datum",
"listfiles_name": "Name",
"mycustomjsredirectprotected": "Du hast keine Berechtigung, diese JavaScript-Seite zu bearbeiten, da sie eine Weiterleitung, die nicht in deinen Benutzernamensraum zeigt, enthält.",
"easydeflate-invaliddeflate": "Der angegebene Inhalt ist nicht ordnungsgemäß komprimiert",
"unprotected-js": "Aus Sicherheitsgründen kann JavaScript-Code nicht mehr von ungeschützten Seiten geladen werden. Erstelle die JavaScript-Seite bitte ausschließlich im Namensraum „MediaWiki“ oder als Benutzerunterseite.",
- "userlogout-continue": "Möchtest du dich abmelden?"
+ "userlogout-continue": "Möchtest du dich abmelden?",
+ "rest-prefix-mismatch": "Der angeforderte Pfad ($1) kannte nicht innerhalb des REST-API-Root-Pfades ($2) gefunden werden"
}
"upload_directory_read_only": "Direktorê dosyayê ($1)î webserver de nieşkeno binuse.",
"uploaderror": "Ğeletê bar kerdişî",
"upload-recreate-warning": "'''Diqet: Yew dosya pê ena name wedariya ya zi vurniya.'''\n\nLogê wedariyayiş u berdişi seba ena pele a ti ra xezir kerda:",
- "uploadtext": "Qey barkerdişê dosyayî, formê cêrinî bişuxulne.\nDosyayê ke vera cû bar biyê eke şima qayîl e ney dosyayan bivînê ya zî bigerî biewnê[[Special:FileList|listeyê dosyayê bar bîyaye]] (tekrar) bar bîyaye [[Special:Log/upload|rocaneyê barkerdişî]] de, hewn a şîyaye zî tîya de [[Special:Log/delete|rocaneyê hewn a kerdişî]] pawiyene.\n\nwexta şima qayîl e yew peli re dosya bierzî, formanê cêrinan ra yewi bişuxulne;\n* Qey xebitnayişê dosyayî: '''<code><nowiki>[[</nowiki>{{ns:file}}<nowiki>:Dosya.jpg]]</nowiki></code>'''\n*Heto çep de zerreyê yew qutî de, qey xebitnayişi 'nuşteyê binîn' û 200 pikseli: '''<code><nowiki>[[</nowiki>{{ns:file}}<nowiki>:Dosya.png|200px|thumb|left|alt metin]]</nowiki></code>'''\n* Dosya memocın, dosya te direk gırey bıerz: '''<code><nowiki>[[</nowiki>{{ns:media}}<nowiki>:Dosya.ogg]]</nowiki></code>'''",
+ "uploadtext": "Seba '''dosya''' barkerdışi rê formê cerênê bıkarnê. Veri ra bar bıyaye dosyaya vınayış u cıgeyrayışi rê bıewni rê [[Special:FileList|lista dosyayan]], seba (fına ) barbıyayan rê [[Special:Log/upload|roceka barkerdışi ]] u, esterıtan zi pela [[Special:Log/delete|roceka esterıtışi]] de tepışiyeno.\n\nYew pela rê Dosya cıkerdışi rê formanê cêrênan ra yewi bıkarnê;\n* Versiyonê pêroyiya Dosya karnayışi rê: '''<code><nowiki>[[</nowiki>{{ns:file}}<nowiki>:Dosya.jpg]]</nowiki></code>'''\n* Kışta çepa yew dorek miyan de, vıniyao cı de 'metinê bıni' ya, 200 piksel ebatiya Dosya karnayışi rê : '''<code><nowiki>[[</nowiki>{{ns:file}}<nowiki>:Dosya.png|200px|thumb|left|alt metin]]</nowiki></code>'''\n* Dosyay nêmusnayışi ra, Dosya rê direkt Link dayışi rê : '''<code><nowiki>[[</nowiki>{{ns:media}}<nowiki>:Dosya.ogg]]</nowiki></code>'''",
"upload-permitted": "{{PLURAL:$2|Babetê|Babetên}} dosyayanê vêrdeyan: $1.",
"upload-preferred": "{{PLURAL:$2|Babetê|Babetên}} dosyayanê tercihbiyayeyan: $1.",
"upload-prohibited": "{{PLURAL:$2|Babetê|Babetên}} dosyayanê tometebiyayeyan: $1.",
"exif-gpsspeed": "Хәрәкәт тизлеге",
"exif-gpsdatestamp": "GPS датасы",
"exif-keywords": "Иң мөһиме",
+ "exif-countrydest": "Күрсәтелгән ил",
+ "exif-countrycodedest": "Күрсәтелгән илнең коды",
+ "exif-provinceorstatedest": "Күрсәтелгән өлкә яки штат",
+ "exif-citydest": "Күрсәтелгән шәһәр",
+ "exif-sublocationdest": "Күрсәтелгән шәһәрнең җире",
+ "exif-objectname": "Кыска исем",
"exif-headline": "Башисем",
"exif-source": "Чыганак",
"exif-contact": "Элемтә өчен мәгълүмат",
"exif-iimversion": "IIM юрамасы",
"exif-iimcategory": "Төркем",
"exif-iimsupplementalcategory": "Өстәмә төркемнәр",
- "exif-datetimereleased": "ЧÑ\8bгаÑ\80Ñ\8bлÑ\83 вакыты",
+ "exif-datetimereleased": "ЧÑ\8bгаÑ\80Ñ\8bлÑ\8bÑ\88 вакыты",
"exif-identifier": "Идентификатор",
"exif-cameraownername": "Камера иясе",
"exif-label": "Билгеләү",
"createaccountmail": "Átmeneti, véletlenszerű jelszó beállítása és kiküldése a megadott e-mail-címre",
"createaccountmail-help": "A jelszó megismerése nélkül készíthető valaki másnak fiók.",
"createacct-realname": "Igazi neved (nem kötelező)",
- "createacct-reason": "Indoklás",
+ "createacct-reason": "Indoklás (nyilvánosan naplózva)",
"createacct-reason-ph": "Miért hozol létre egy másik fiókot",
"createacct-reason-help": "A fióklétrehozási naplóban megjelenő üzenet",
"createacct-submit": "Felhasználói fiók létrehozása",
"uploadstash-zero-length": "A fájl nulla méretű.",
"invalid-chunk-offset": "Érvénytelen darab eltolás",
"img-auth-accessdenied": "Hozzáférés megtagadva",
- "img-auth-nopathinfo": "Hiányzó PATH_INFO.\nA szerver nincs beállítva, hogy továbbítsa ezt az információt.\nLehet, hogy CGI-alapú, és nem támogatja az img_auth-ot.\nLásd https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Image_Authorization!",
+ "img-auth-nopathinfo": "Hiányzó elérési út információ.\nA szerveredet úgy kell beállítanod, hogy továbbítsa a REQUEST_URI és/vagy a PATH_INFO változókat.\nHa már be vannak, próbáld engedélyezni a $wgUsePathInfo-t.\nLásd https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Image_Authorization!",
"img-auth-notindir": "A kért elérési út nincs a beállított feltöltési könyvtárban.",
"img-auth-badtitle": "Nem sikerült érvényes címet készíteni a(z) „$1” szövegből.",
"img-auth-nofile": "A fájl („$1”) nem létezik.",
"listfiles-userdoesnotexist": "A(z) „$1” felhasználó nincs regisztrálva.",
"imgfile": "fájl",
"listfiles": "Fájllista",
+ "listfiles_subpage": "$1 feltöltései",
"listfiles_thumb": "Bélyegkép",
"listfiles_date": "Dátum",
"listfiles_name": "Név",
"listfiles-userdoesnotexist": "L'utenza \"$1\" non è registrata.",
"imgfile": "file",
"listfiles": "Elenco dei file",
+ "listfiles_subpage": "Caricamenti di $1",
"listfiles_thumb": "Miniatura",
"listfiles_date": "Data",
"listfiles_name": "Nome",
"emailuser-title-target": "Siųsti el. pašto žinutę {{GENDER:$1|naudotojui|naudotojai}}",
"emailuser-title-notarget": "El. pašto vartotojas",
"emailpagetext": "Jūs galite pasinaudoti šiuo pavyzdžiu, norėdami nusiųsti elektroninį laišką {{GENDER:$1|šiam naudotojui|šiai naudotojai}}.\nElektroninio pašto adresas, kurį įvedėte [[Special:Preferences|savo naudotojo nustatymuose]], bus rodomas kaip el. pašto siuntėjo adresas tam, kad gavėjas galėtų tiesiogiai jums atsakyti.",
- "defemailsubject": "{{SITENAME}} el. pašto iš vartotojo \" $1 \"",
+ "defemailsubject": "{{SITENAME}} laiškas iš naudotojo „$1“",
"usermaildisabled": "Naudotojo elektroninis paštas išjungtas",
"usermaildisabledtext": "Jūs negalite siūlsti el. laiško kitiems šio wiki projekto naudotojams.",
"noemailtitle": "Nėra el. pašto adreso",
"listfiles-userdoesnotexist": "A conta de usuário \"$1\" não está registrada.",
"imgfile": "arquivo",
"listfiles": "Lista de arquivos",
+ "listfiles_subpage": "Enviado por $1",
"listfiles_thumb": "Miniatura",
"listfiles_date": "Data",
"listfiles_name": "Nome",
"mycustomjsredirectprotected": "Você não tem permissão para editar esta página JavaScript porque é um redirecionamento e não aponta dentro do seu espaço do usuário.",
"easydeflate-invaliddeflate": "O conteúdo fornecido não está devidamente comprimido",
"unprotected-js": "Por razões de segurança o JavaScript não pode ser carregado de páginas desprotegidas. Por favor, crie apenas javascript no MediaWiki: namespace ou como uma subpágina do usuário",
- "userlogout-continue": "Você quer sair?"
+ "userlogout-continue": "Você quer sair?",
+ "rest-prefix-mismatch": "O caminho pedido ($1) não estava no interior do caminho raiz da API REST ($2)",
+ "rest-wrong-method": "O método pedido ($1) não era {{PLURAL:$3|o método permitido para este caminho|um dos métodos permitidos para este caminho}} ($2)",
+ "rest-no-match": "O caminho relativo pedido ($1) não corresponde a nenhuma rotina de tratamento conhecida"
}
"tog-useeditwarning": "Avvisave quanne jie lasse 'na pàgene cangiate senze ca agghie sarvate le cangiaminde",
"tog-prefershttps": "Ause sembre 'na connessione secure quanne trase",
"tog-showrollbackconfirmation": "Fà 'ndrucà 'na richieste de conferme quanne ste cazze sus a 'nu collegamende de annullamende",
+ "tog-requireemail": "Richeiste email pe l'azzeramende d'a passuord",
"underline-always": "Sembre",
"underline-never": "Maje",
"underline-default": "Valore de default d'u browser o scheme",
"content-model-css": "CSS",
"content-json-empty-object": "Oggette vacande",
"content-json-empty-array": "Matrice vacande",
+ "unsupported-content-diff": "Le differenze non ge sò supportate pu modelle de condenute $1.",
"deprecated-self-close-category": "Lè pàggene ca ausane le tag HTML auto-achiuse invalide",
"duplicate-args-warning": "<strong>Attenziò:</strong> [[:$1]] ste chiame [[:$2]] cu cchiù de 'nu valore pu parametre \"$3\". Sulamende l'urteme valore date avène ausate.",
"duplicate-args-category": "Pàggene ca ausane le argumende a doppie jndr'à le chiamate d'u template",
"listfiles-userdoesnotexist": "Nome utende \"$1\" non g'è reggistrate.",
"imgfile": "file",
"listfiles": "Liste de le fail",
+ "listfiles_subpage": "Carecaminde da $1",
"listfiles_thumb": "Miniature",
"listfiles_date": "Sciurne",
"listfiles_name": "Nome",
"createaccountmail": "Использовать сгенерированный случайным образом временный пароль и выслать его на указанный адрес электронной почты",
"createaccountmail-help": "Может использоваться, чтобы создать учётную запись для другого лица, не узнавая пароль.",
"createacct-realname": "Настоящее имя (необязательно)",
- "createacct-reason": "Причина",
+ "createacct-reason": "Причина (публично видимая)",
"createacct-reason-ph": "Зачем вы создаёте другую учётную запись",
"createacct-reason-help": "Сообщение, отображаемое в журнале создания учётных записей",
"createacct-submit": "Создать учётную запись",
"createaccountmail": "Використати тимчасовий випадковий пароль і надіслати його на вказану адресу електронної пошти",
"createaccountmail-help": "Може використовуватися, щоб створити обліковий запис для іншої особи, не дізнаючись пароль.",
"createacct-realname": "Справжнє ім'я (не обов'язково)",
- "createacct-reason": "Причина",
+ "createacct-reason": "Причина (публічно видима)",
"createacct-reason-ph": "Чому ви створюєте інший обліковий запис",
"createacct-reason-help": "Повідомлення, що показується в журналі створення облікових записів",
"createacct-submit": "Створіть ваш обліковий запис",
"ipblocklist-legend": "查找被封禁用户",
"blocklist-userblocks": "隐藏账户封禁",
"blocklist-tempblocks": "隐藏临时封禁",
+ "blocklist-indefblocks": "隐藏无限期封禁",
"blocklist-addressblocks": "隐藏单个IP封禁",
"blocklist-type": "类型:",
"blocklist-type-opt-all": "全部",
"tog-useeditwarning": "在我離開未儲存的編輯頁面時警告我",
"tog-prefershttps": "登入時永遠使用安全連線",
"tog-showrollbackconfirmation": "當點擊回退連結時顯示確認提示",
+ "tog-requireemail": "需要電子郵件用來重設密碼",
"underline-always": "永遠使用",
"underline-never": "永不使用",
"underline-default": "佈景主題或瀏覽器預設",
"createaccountmail": "使用臨時的隨機密碼,並將它寄至指定的電子郵件地址",
"createaccountmail-help": "可用來建立其他人的帳號 (不需要知道密碼)。",
"createacct-realname": "真實姓名 (選填)",
- "createacct-reason": "原因",
+ "createacct-reason": "原因(公開記錄)",
"createacct-reason-ph": "您為什麼要建立另一個帳號",
"createacct-reason-help": "顯示於帳號建立日誌的訊息",
"createacct-submit": "建立您的帳號",
"changeemail-oldemail": "目前的電子郵件地址:",
"changeemail-newemail": "新的電子郵件地址:",
"changeemail-newemail-help": "若您想移除您的電子郵件地址,此欄位應留空。若移除電子郵件地址您將無法重設忘記的密碼並且將不會再收到來自此 wiki 的電子郵件。",
- "changeemail-none": "(無)",
+ "changeemail-none": "(無)",
"changeemail-password": "您於 {{SITENAME}} 的密碼:",
"changeemail-submit": "變更電子郵件",
"changeemail-throttled": "您最近嘗試了太多次登入。\n請等待 $1 後再試。",
"undo-main-slot-only": "編輯無法還原,因為有包含到在主分配之外的內容。",
"undo-norev": "此編輯不存在或已被刪除,無法還原。",
"undo-nochange": "此編輯已被還原。",
- "undo-summary": "取消由 [[Special:Contributions/$2|$2]] ([[User talk:$2|討論]]) 所作出的修訂 $1",
+ "undo-summary": "取消由[[Special:Contributions/$2|$2]]([[User talk:$2|討論]])所作出的修訂$1",
+ "undo-summary-anon": "取消由[[Special:Contributions/$2|$2]]所作出的修訂$1",
"undo-summary-username-hidden": "還原隱藏使用者的修訂 $1",
"cantcreateaccount-text": "自這個 IP 位址 (<strong>$1</strong>) 建立帳號已經被 [[User:$3|$3]] 封鎖。\n\n$3 封鎖的原因是$2",
"cantcreateaccount-range-text": "來自 IP 位址範圍 <strong>$1</strong>,包含您的 IP 位址 (<strong>$4</strong>) 所建立的帳號已經被 [[User:$3|$3]] 封鎖。\n\n$3 封鎖的原因是 <em>$2</em>",
"prefs-help-gender": "此偏好設定為選填欄位。\n系統會使用您選擇的方式稱呼您,對他人提及您時也會使用適當語法稱呼。\n此項資訊會被公開。",
"email": "Email",
"prefs-help-realname": "真實姓名為選填欄位。\n若提供,真實姓名可能會用來作為您的作品的署名。",
- "prefs-help-email": "電子郵件地址為選填欄位。\n但在重設密碼時會使用,而您很有可能會忘記密碼。",
+ "prefs-help-email": "電子郵件地址為選填欄位,但是當您忘記密碼而要重設時需要此資訊。",
"prefs-help-email-others": "您亦可以選擇讓其他使用者透過您的電子郵件、使用者頁面或討論頁面的連結與您聯絡。\n您的電子郵件地址不會洩漏給其他要聯絡您的使用者。",
"prefs-help-email-required": "電子郵件地址是必填項目。",
+ "prefs-help-requireemail": "如果選取,則只有在重設密碼的人同時提供此帳戶的使用者名稱和電子郵件時,才會發送重設密碼的電子郵件。",
"prefs-info": "基本資訊",
"prefs-i18n": "國際化",
"prefs-signature": "簽名",
"listfiles-userdoesnotexist": "使用者帳號 \"$1\" 尚未註冊。",
"imgfile": "檔案",
"listfiles": "檔案清單",
+ "listfiles_subpage": "由 $1 上傳",
"listfiles_thumb": "縮圖",
"listfiles_date": "日期",
"listfiles_name": "名稱",
"alreadyrolled": "無法回退由[[User:$2|$2]]([[User talk:$2|討論]]{{int:pipe-separator}}[[Special:Contributions/$2|{{int:contribslink}}]])所作的最後一次編輯[[:$1]],已有其他人編輯或回退了該頁面。\n\n最後一次編輯該頁面的使用者是[[User:$3|$3]]([[User talk:$3|討論]]{{int:pipe-separator}}[[Special:Contributions/$3|{{int:contribslink}}]])。",
"editcomment": "編輯摘要為:<em>$1</em>。",
"revertpage": "已還原[[Special:Contributions/$2|$2]]([[User talk:$2|討論]])的編輯至最後由[[User:$1|$1]]所修訂的版本",
+ "revertpage-anon": "已還原[[Special:Contributions/$2|$2]]的編輯至最後由[[User:$1|$1]]所修訂的版本",
"revertpage-nouser": "已還原隱藏使用者的編輯為最後 {{GENDER:$1|[[User:$1|$1]]}} 修訂的版本",
"rollback-success": "已還原{{GENDER:$3|$1}}所做的編輯;變更回由{{GENDER:$4|$2}}修訂的最後一個版本。",
"sessionfailure-title": "連線階段失敗",
"ipblocklist-legend": "搜尋已封鎖的使用者",
"blocklist-userblocks": "隱藏帳號封鎖",
"blocklist-tempblocks": "隱藏暫時封鎖",
+ "blocklist-indefblocks": "隱藏無限期封鎖",
"blocklist-addressblocks": "隱藏單一 IP 封鎖",
"blocklist-type": "類型:",
"blocklist-type-opt-all": "所有",
"mycustomjsredirectprotected": "您無權編輯此JavaScript頁面,因為它是重新導向,且不是重新導向到您的用戶空間。",
"easydeflate-invaliddeflate": "提供的內容未被正常的壓縮",
"unprotected-js": "基於安全因素,JavaScript 不能從未保護的頁面來載入。請僅在 MediaWiki:命名空間或使用者子頁面中建立 JavaScript。",
- "userlogout-continue": "您想要登出嗎?"
+ "userlogout-continue": "您想要登出嗎?",
+ "rest-prefix-mismatch": "請求的路徑($1)不在REST API的根路徑($2)內",
+ "rest-wrong-method": "請求的方法($1)不是{{PLURAL:$3|此路徑允許的方法|此路徑允許的方法之一}}($2)",
+ "rest-no-match": "請求的相對路徑($1)不符合任何已知的處理器"
}
$this->mergeMwGlobalArrayValue( 'wgHooks', [ $hookName => [ $handler ] ] );
}
- /**
- * Check whether file contains given data.
- * @param string $fileName
- * @param string $actualData
- * @param bool $createIfMissing If true, and file does not exist, create it with given data
- * and skip the test.
- * @param string $msg
- * @since 1.30
- */
- protected function assertFileContains(
- $fileName,
- $actualData,
- $createIfMissing = false,
- $msg = ''
- ) {
- if ( $createIfMissing ) {
- if ( !file_exists( $fileName ) ) {
- file_put_contents( $fileName, $actualData );
- $this->markTestSkipped( "Data file $fileName does not exist" );
- }
- } else {
- self::assertFileExists( $fileName );
- }
- self::assertEquals( file_get_contents( $fileName ), $actualData, $msg );
- }
-
/**
* Edits or creates a page/revision
* @param string $pageName Page title
$mock->expects( $this->never() )->method( $this->anythingBut( '__destruct' ) );
return $mock;
}
+
+ /**
+ * Check whether file contains given data.
+ * @param string $fileName
+ * @param string $actualData
+ * @param bool $createIfMissing If true, and file does not exist, create it with given data
+ * and skip the test.
+ * @param string $msg
+ * @since 1.30
+ */
+ protected function assertFileContains(
+ $fileName,
+ $actualData,
+ $createIfMissing = false,
+ $msg = ''
+ ) {
+ if ( $createIfMissing ) {
+ if ( !file_exists( $fileName ) ) {
+ file_put_contents( $fileName, $actualData );
+ $this->markTestSkipped( "Data file $fileName does not exist" );
+ }
+ } else {
+ self::assertFileExists( $fileName );
+ }
+ self::assertEquals( file_get_contents( $fileName ), $actualData, $msg );
+ }
}
namespace MediaWiki\Tests\Revision;
use CommentStoreComment;
+use ContentHandler;
use MediaWiki\MediaWikiServices;
use MediaWiki\Revision\MutableRevisionRecord;
use MediaWiki\Revision\RevisionRecord;
use MediaWiki\Revision\SlotRecord;
+use MediaWiki\Storage\SqlBlobStore;
+use Revision;
+use StatusValue;
use TextContent;
use Title;
+use Wikimedia\TestingAccessWrapper;
use WikitextContent;
/**
]
] ], $result->getErrors() );
}
+
+ /**
+ * @covers \MediaWiki\Revision\RevisionStore::newRevisionsFromBatch
+ */
+ public function testNewRevisionFromBatchUsesGetBlobBatch() {
+ $page1 = $this->getTestPage();
+ $text = __METHOD__ . 'b-ä';
+ $editStatus = $this->editPage( $page1->getTitle()->getPrefixedDBkey(), $text . '1' );
+ $this->assertTrue( $editStatus->isGood(), 'Sanity: must create revision 1' );
+ /** @var Revision $rev1 */
+ $rev1 = $editStatus->getValue()['revision'];
+
+ $contentAddress = $rev1->getRevisionRecord()->getSlot( SlotRecord::MAIN )->getAddress();
+ $mockBlobStore = $this->getMockBuilder( SqlBlobStore::class )
+ ->disableOriginalConstructor()
+ ->getMock();
+ $mockBlobStore
+ ->expects( $this->once() )
+ ->method( 'getBlobBatch' )
+ ->with( [ $contentAddress ], $this->anything() )
+ ->willReturn( StatusValue::newGood( [
+ $contentAddress => 'Content_From_Mock'
+ ] ) );
+ $mockBlobStore
+ ->expects( $this->never() )
+ ->method( 'getBlob' );
+
+ $revStore = MediaWikiServices::getInstance()
+ ->getRevisionStoreFactory()
+ ->getRevisionStore();
+ $wrappedRevStore = TestingAccessWrapper::newFromObject( $revStore );
+ $wrappedRevStore->blobStore = $mockBlobStore;
+
+ $result = $revStore->newRevisionsFromBatch(
+ [ $this->revisionToRow( $rev1 ) ],
+ [
+ 'slots' => [ SlotRecord::MAIN ],
+ 'content' => true
+ ]
+ );
+ $this->assertTrue( $result->isGood() );
+ $this->assertSame( 'Content_From_Mock',
+ ContentHandler::getContentText( $result->getValue()[$rev1->getId()]
+ ->getContent( SlotRecord::MAIN ) ) );
+ }
}
use CommentStoreComment;
use Content;
+use ContentHandler;
use Exception;
use HashBagOStuff;
+use IDBAccessObject;
use InvalidArgumentException;
use Language;
use MediaWiki\Linker\LinkTarget;
* @return WikiPage
*/
protected function getTestPage( $pageTitle = null ) {
- if ( !is_null( $pageTitle ) && $this->testPage ) {
+ if ( is_null( $pageTitle ) && $this->testPage ) {
return $this->testPage;
}
) {
$page1 = $this->getTestPage();
$text = __METHOD__ . 'b-ä';
+ $editStatus = $this->editPage( $page1->getTitle()->getPrefixedDBkey(), $text . '1' );
+ $this->assertTrue( $editStatus->isGood(), 'Sanity: must create revision 1' );
/** @var Revision $rev1 */
- $rev1 = $page1->doEditContent(
- new WikitextContent( $text . '1' ),
- __METHOD__ . 'b',
- 0,
- false,
- $this->getTestUser()->getUser()
- )->value['revision'];
+ $rev1 = $editStatus->getValue()['revision'];
+
$page2 = $this->getTestPage( $otherPageTitle );
+ $editStatus = $this->editPage( $page2->getTitle()->getPrefixedDBkey(), $text . '2' );
+ $this->assertTrue( $editStatus->isGood(), 'Sanity: must create revision 2' );
/** @var Revision $rev2 */
- $rev2 = $page2->doEditContent(
- new WikitextContent( $text . '2' ),
- __METHOD__ . 'b',
- 0,
- false,
- $this->getTestUser()->getUser()
- )->value['revision'];
+ $rev2 = $editStatus->getValue()['revision'];
+
$store = MediaWikiServices::getInstance()->getRevisionStore();
$result = $store->newRevisionsFromBatch(
[ $this->revisionToRow( $rev1 ), $this->revisionToRow( $rev2 ) ],
);
$this->assertTrue( $result->isGood() );
$this->assertEmpty( $result->getErrors() );
+ /** @var RevisionRecord[] $records */
$records = $result->getValue();
$this->assertRevisionRecordMatchesRevision( $rev1, $records[$rev1->getId()] );
$this->assertRevisionRecordMatchesRevision( $rev2, $records[$rev2->getId()] );
$this->assertSame( $text . '1',
- $records[$rev1->getId()]->getContent( SlotRecord::MAIN )->serialize() );
+ ContentHandler::getContentText( $records[$rev1->getId()]->getContent( SlotRecord::MAIN ) ) );
$this->assertSame( $text . '2',
- $records[$rev2->getId()]->getContent( SlotRecord::MAIN )->serialize() );
+ ContentHandler::getContentText( $records[$rev2->getId()]->getContent( SlotRecord::MAIN ) ) );
$this->assertEquals( $page1->getTitle()->getDBkey(),
$records[$rev1->getId()]->getPageAsLinkTarget()->getDBkey() );
$this->assertEquals( $page2->getTitle()->getDBkey(),
$this->assertEmpty( $result->getValue() );
$this->assertEmpty( $result->getErrors() );
}
+
+ /**
+ * @covers \MediaWiki\Revision\RevisionStore::newRevisionsFromBatch
+ */
+ public function testNewRevisionsFromBatch_wrongTitle() {
+ $page1 = $this->getTestPage();
+ $text = __METHOD__ . 'b-ä';
+ $editStatus = $this->editPage( $page1->getTitle()->getPrefixedDBkey(), $text . '1' );
+ $this->assertTrue( $editStatus->isGood(), 'Sanity: must create revision 1' );
+ /** @var Revision $rev1 */
+ $rev1 = $editStatus->getValue()['revision'];
+
+ $this->setExpectedException( InvalidArgumentException::class );
+ MediaWikiServices::getInstance()->getRevisionStore()
+ ->newRevisionsFromBatch(
+ [ $this->revisionToRow( $rev1 ) ],
+ [],
+ IDBAccessObject::READ_NORMAL,
+ $this->getTestPage( 'Title_Other_Then_The_One_Revision_Belongs_To' )->getTitle()
+ );
+ }
+
+ /**
+ * @covers \MediaWiki\Revision\RevisionStore::newRevisionsFromBatch
+ */
+ public function testNewRevisionsFromBatch_DuplicateRows() {
+ $page1 = $this->getTestPage();
+ $text = __METHOD__ . 'b-ä';
+ $editStatus = $this->editPage( $page1->getTitle()->getPrefixedDBkey(), $text . '1' );
+ $this->assertTrue( $editStatus->isGood(), 'Sanity: must create revision 1' );
+ /** @var Revision $rev1 */
+ $rev1 = $editStatus->getValue()['revision'];
+
+ $this->setExpectedException( InvalidArgumentException::class );
+ MediaWikiServices::getInstance()->getRevisionStore()
+ ->newRevisionsFromBatch( [ $this->revisionToRow( $rev1 ), $this->revisionToRow( $rev1 ) ] );
+ }
}