59 private const LATEST_SYNTAX_VERSION =
'2';
60 private const DEFAULT_SYNTAX_VERSION =
'1';
62 private $languageNameUtils;
64 private $languageFactory;
66 private $translationUnitStoreFactory;
68 private $translatablePageParser;
70 private $linkBatchFactory;
72 private $jobQueueGroup;
74 public function __construct(
75 LanguageNameUtils $languageNameUtils,
76 LanguageFactory $languageFactory,
79 LinkBatchFactory $linkBatchFactory,
80 JobQueueGroup $jobQueueGroup
82 parent::__construct(
'PageTranslation' );
83 $this->languageNameUtils = $languageNameUtils;
84 $this->languageFactory = $languageFactory;
85 $this->translationUnitStoreFactory = $translationUnitStoreFactory;
86 $this->translatablePageParser = $translatablePageParser;
87 $this->linkBatchFactory = $linkBatchFactory;
88 $this->jobQueueGroup = $jobQueueGroup;
91 public function doesWrites():
bool {
95 protected function getGroupName():
string {
99 public function execute( $parameters ) {
102 $user = $this->getUser();
103 $request = $this->getRequest();
105 $target = $request->getText(
'target', $parameters ??
'' );
106 $revision = $request->getInt(
'revision', 0 );
107 $action = $request->getVal(
'do' );
108 $out = $this->getOutput();
109 $out->addModules(
'ext.translate.special.pagetranslation' );
110 $out->addHelpLink(
'Help:Extension:Translate/Page_translation_example' );
113 if ( $target ===
'' ) {
120 if ( !$user->isAllowed(
'pagetranslation' ) ) {
121 throw new PermissionsError(
'pagetranslation' );
124 $title = Title::newFromText( $target );
126 $out->wrapWikiMsg( Html::errorBox(
'$1' ), [
'tpt-badtitle', $target ] );
127 $out->addWikiMsg(
'tpt-list-pages-in-translations' );
130 } elseif ( !$title->exists() ) {
132 Html::errorBox(
'$1' ),
133 [
'tpt-nosuchpage', $title->getPrefixedText() ]
135 $out->addWikiMsg(
'tpt-list-pages-in-translations' );
141 $permissionManager = MediaWikiServices::getInstance()->getPermissionManager();
142 if ( $permissionManager->isBlockedFrom( $user, $title, !$request->wasPosted() ) ) {
143 $block = $user->getBlock();
145 throw new UserBlockedError(
148 $this->getLanguage(),
153 throw new PermissionsError(
'pagetranslation', [
'badaccess-group0' ] );
158 $csrfTokenSet = $this->getContext()->getCsrfTokenSet();
159 if ( $request->wasPosted() && !$csrfTokenSet->matchTokenField(
'token' ) ) {
160 throw new PermissionsError(
'pagetranslation' );
163 if ( $action ===
'mark' ) {
165 $this->onActionMark( $title, $revision );
171 if ( !$request->wasPosted() ) {
172 if ( $action ===
'unlink' ) {
173 $this->showUnlinkConfirmation( $title );
177 'target' => $title->getPrefixedText(),
178 'revision' => $revision,
180 $this->showGenericConfirmation( $params );
186 if ( $action ===
'discourage' || $action ===
'encourage' ) {
188 $current = MessageGroups::getPriority( $id );
190 if ( $action ===
'encourage' ) {
193 $new =
'discouraged';
196 if ( $new !== $current ) {
197 MessageGroups::setPriority( $id, $new );
198 $entry =
new ManualLogEntry(
'pagetranslation', $action );
199 $entry->setPerformer( $user );
200 $entry->setTarget( $title );
201 $logid = $entry->insert();
202 $entry->publish( $logid );
209 $group = MessageGroups::getGroup( $id );
210 $sharedGroupIds = MessageGroups::getSharedGroups( $group );
211 if ( $sharedGroupIds !== [] ) {
213 $this->jobQueueGroup->push( $job );
222 if ( $action ===
'unlink' ) {
225 $content = ContentHandler::makeContent(
226 $page->getStrippedSourcePageText(),
230 $status = MediaWikiServices::getInstance()->getWikiPageFactory()->newFromTitle( $title )->doUserEditContent(
233 $this->msg(
'tpt-unlink-summary' )->inContentLanguage()->text(),
234 EDIT_FORCE_BOT | EDIT_UPDATE
237 if ( !$status->isOK() ) {
239 Html::errorBox(
'$1' ),
240 [
'tpt-edit-failed', $status->getWikiText() ]
242 $out->addWikiMsg(
'tpt-list-pages-in-translations' );
248 $this->unmarkPage( $page, $user );
250 Html::successBox(
'$1' ),
251 [
'tpt-unmarked', $title->getPrefixedText() ]
253 $out->addWikiMsg(
'tpt-list-pages-in-translations' );
258 if ( $action ===
'unmark' ) {
260 $this->unmarkPage( $page, $user );
262 Html::successBox(
'$1' ),
263 [
'tpt-unmarked', $title->getPrefixedText() ]
265 $out->addWikiMsg(
'tpt-list-pages-in-translations' );
269 protected function onActionMark( Title $title,
int $revision ):
void {
270 $request = $this->getRequest();
271 $out = $this->getOutput();
273 $out->addModuleStyles(
'ext.translate.specialpages.styles' );
275 if ( $revision === 0 ) {
277 $revision = (int)$title->getLatestRevID();
281 if ( $revision !== (
int)$title->getLatestRevID() ) {
283 $target = $title->getFullURL( [
'oldid' => $revision ] );
284 $link =
"<span class='plainlinks'>[$target $revision]</span>";
286 Html::warningBox(
'$1' ),
287 [
'tpt-oldrevision', $title->getPrefixedText(), $link ]
289 $out->addWikiMsg(
'tpt-list-pages-in-translations' );
297 if ( $page->getReadyTag() !== $title->getLatestRevID() ) {
299 Html::errorBox(
'$1' ),
300 [
'tpt-notsuitable', $title->getPrefixedText(), Message::plaintextParam(
'<translate>' ) ]
302 $out->addWikiMsg(
'tpt-list-pages-in-translations' );
307 $firstMark = $page->getMarkedTag() ===
null;
309 $parse = $this->translatablePageParser->parse( $page->getText() );
310 [ $units, $deletedUnits ] = $this->prepareTranslationUnits( $page, $parse );
312 $error = $this->validateUnitIds( $units );
315 if ( !$error && $request->wasPosted() ) {
318 if ( !$request->getCheck(
'translatetitle' ) ) {
319 $units = array_filter( $units,
static function ( $s ) {
320 return $s->id !== TranslatablePage::DISPLAY_TITLE_UNIT_ID;
324 $setVersion = $firstMark || $request->getCheck(
'use-latest-syntax' );
325 $transclusion = $request->getCheck(
'transclusion' );
327 $err = $this->
markForTranslation( $page, $parse, $units, $setVersion, $transclusion );
330 call_user_func_array( [ $out,
'addWikiMsg' ], $err );
332 $this->showSuccess( $page, $firstMark, count( $units ) );
338 $this->showPage( $page, $parse, $units, $deletedUnits, $firstMark );
348 private function showSuccess(
351 $titleText = $page->
getTitle()->getPrefixedText();
352 $num = $this->getLanguage()->formatNum( $unitCount );
353 $link = SpecialPage::getTitleFor(
'Translate' )->getFullURL( [
359 $this->getOutput()->wrapWikiMsg(
360 Html::successBox(
'$1' ),
361 [
'tpt-saveok', $titleText, $num, $link ]
367 $this->getOutput()->addWikiMsg(
'tpt-saveok-first' );
372 if ( method_exists( SpecialNotifyTranslators::class,
'execute' ) &&
373 $this->getUser()->isAllowed( SpecialNotifyTranslators::$right )
375 $link = SpecialPage::getTitleFor(
'NotifyTranslators' )->getFullURL(
376 [
'tpage' => $page->
getTitle()->getArticleID() ]
378 $this->getOutput()->addWikiMsg(
'tpt-offer-notify', $link );
381 $this->getOutput()->addWikiMsg(
'tpt-list-pages-in-translations' );
384 protected function showGenericConfirmation( array $params ):
void {
387 'action' => $this->getPageTitle()->getLocalURL(),
390 $params[
'title'] = $this->getPageTitle()->getPrefixedText();
391 $params[
'token'] = $this->getContext()->getCsrfTokenSet()->getToken();
394 foreach ( $params as $key => $value ) {
395 $hidden .= Html::hidden( $key, $value );
398 $this->getOutput()->addHTML(
399 Html::openElement(
'form', $formParams ) .
401 $this->msg(
'tpt-generic-confirm' )->parseAsBlock() .
403 $this->msg(
'tpt-generic-button' )->text(),
404 [
'class' =>
'mw-ui-button mw-ui-progressive' ]
406 Html::closeElement(
'form' )
410 protected function showUnlinkConfirmation( Title $target ):
void {
413 'action' => $this->getPageTitle()->getLocalURL(),
416 $this->getOutput()->addHTML(
417 Html::openElement(
'form', $formParams ) .
418 Html::hidden(
'do',
'unlink' ) .
419 Html::hidden(
'title', $this->getPageTitle()->getPrefixedText() ) .
420 Html::hidden(
'target', $target->getPrefixedText() ) .
421 Html::hidden(
'token', $this->getContext()->getCsrfTokenSet()->getToken() ) .
422 $this->msg(
'tpt-unlink-confirm', $target->getPrefixedText() )->parseAsBlock() .
424 $this->msg(
'tpt-unlink-button' )->text(),
425 [
'class' =>
'mw-ui-button mw-ui-destructive' ]
427 Html::closeElement(
'form' )
431 protected function unmarkPage(
TranslatablePage $page, UserIdentity $user ):
void {
433 $page->
getTitle()->invalidateCache();
435 $entry =
new ManualLogEntry(
'pagetranslation',
'unmark' );
436 $entry->setPerformer( $user );
437 $entry->setTarget( $page->
getTitle() );
438 $logid = $entry->insert();
439 $entry->publish( $logid );
442 public function loadPagesFromDB(): IResultWrapper {
443 $dbr = TranslateUtils::getSafeReadDB();
444 $tables = [
'page',
'revtag' ];
450 'MAX(rt_revision) AS rt_revision',
455 'rt_type' => [ RevTagStore::TP_MARK_TAG, RevTagStore::TP_READY_TAG ],
458 'ORDER BY' =>
'page_namespace, page_title',
459 'GROUP BY' =>
'page_id, page_namespace, page_title, page_latest, rt_type',
462 return $dbr->select( $tables, $vars, $conds, __METHOD__, $options );
465 protected function buildPageArray( IResultWrapper $res ): array {
467 foreach ( $res as $r ) {
469 if ( !isset( $pages[$r->page_id] ) ) {
470 $pages[$r->page_id] = [];
471 $title = Title::newFromRow( $r );
472 $pages[$r->page_id][
'title'] = $title;
473 $pages[$r->page_id][
'latest'] = (int)$title->getLatestRevID();
477 $pages[$r->page_id][$tag] = (int)$r->rt_revision;
490 private function classifyPages( array $pages ): array {
492 $messageGroupIdsForPreload = [];
493 foreach ( $pages as $i => $page ) {
495 $messageGroupIdsForPreload[] = $id;
496 $pages[$i][
'groupid'] = $id;
499 $metadata = TranslateMetadata::loadBasicMetadataForTranslatablePages(
500 $messageGroupIdsForPreload,
501 [
'transclusion',
'version' ]
512 foreach ( $pages as $page ) {
513 $groupId = $page[
'groupid'];
514 $group = MessageGroups::getGroup( $groupId );
515 $page[
'discouraged'] = MessageGroups::getPriority( $group ) ===
'discouraged';
516 $page[
'version'] = $metadata[$groupId][
'version'] ?? self::DEFAULT_SYNTAX_VERSION;
517 $page[
'transclusion'] = $metadata[$groupId][
'transclusion'] ??
false;
519 if ( !isset( $page[RevTagStore::TP_MARK_TAG] ) ) {
521 if ( $page[RevTagStore::TP_READY_TAG] === $page[
'latest'] ) {
522 $out[
'proposed'][] = $page;
524 } elseif ( $page[RevTagStore::TP_READY_TAG] === $page[
'latest'] ) {
525 if ( $page[RevTagStore::TP_MARK_TAG] === $page[RevTagStore::TP_READY_TAG] ) {
527 $out[
'active'][] = $page;
529 $out[
'outdated'][] = $page;
533 $out[
'broken'][] = $page;
540 public function listPages():
void {
541 $out = $this->getOutput();
543 $res = $this->loadPagesFromDB();
544 $allPages = $this->buildPageArray( $res );
545 if ( !count( $allPages ) ) {
546 $out->addWikiMsg(
'tpt-list-nopages' );
551 $lb = $this->linkBatchFactory->newLinkBatch();
552 $lb->setCaller( __METHOD__ );
553 foreach ( $allPages as $page ) {
554 $lb->addObj( $page[
'title'] );
558 $types = $this->classifyPages( $allPages );
560 $pages = $types[
'proposed'];
562 $out->wrapWikiMsg(
'== $1 ==',
'tpt-new-pages-title' );
563 $out->addWikiMsg(
'tpt-new-pages', count( $pages ) );
564 $out->addHTML( $this->getPageList( $pages,
'proposed' ) );
567 $pages = $types[
'broken'];
569 $out->wrapWikiMsg(
'== $1 ==',
'tpt-other-pages-title' );
570 $out->addWikiMsg(
'tpt-other-pages', count( $pages ) );
571 $out->addHTML( $this->getPageList( $pages,
'broken' ) );
574 $pages = $types[
'outdated'];
576 $out->wrapWikiMsg(
'== $1 ==',
'tpt-outdated-pages-title' );
577 $out->addWikiMsg(
'tpt-outdated-pages', count( $pages ) );
578 $out->addHTML( $this->getPageList( $pages,
'outdated' ) );
581 $pages = $types[
'active'];
583 $out->wrapWikiMsg(
'== $1 ==',
'tpt-old-pages-title' );
584 $out->addWikiMsg(
'tpt-old-pages', count( $pages ) );
585 $out->addHTML( $this->getPageList( $pages,
'active' ) );
589 private function actionLinks( array $page,
string $type ):
string {
591 static $messageCache =
null;
592 if ( $messageCache ===
null ) {
594 'mark' => $this->msg(
'tpt-rev-mark' )->text(),
595 'mark-tooltip' => $this->msg(
'tpt-rev-mark-tooltip' )->text(),
596 'encourage' => $this->msg(
'tpt-rev-encourage' )->text(),
597 'encourage-tooltip' => $this->msg(
'tpt-rev-encourage-tooltip' )->text(),
598 'discourage' => $this->msg(
'tpt-rev-discourage' )->text(),
599 'discourage-tooltip' => $this->msg(
'tpt-rev-discourage-tooltip' )->text(),
600 'unmark' => $this->msg(
'tpt-rev-unmark' )->text(),
601 'unmark-tooltip' => $this->msg(
'tpt-rev-unmark-tooltip' )->text(),
602 'pipe-separator' => $this->msg(
'pipe-separator' )->escaped(),
608 $title = $page[
'title'];
609 $user = $this->getUser();
612 $js = [
'class' =>
'mw-translate-jspost' ];
614 if ( $user->isAllowed(
'pagetranslation' ) ) {
617 if ( $type !==
'broken' ) {
618 $actions[] = $this->getLinkRenderer()->makeKnownLink(
619 $this->getPageTitle(),
620 $messageCache[
'mark'],
621 [
'title' => $messageCache[
'mark-tooltip'] ],
624 'target' => $title->getPrefixedText(),
625 'revision' => $title->getLatestRevID(),
630 if ( $type !==
'proposed' ) {
631 if ( $page[
'discouraged'] ) {
632 $actions[] = $this->getLinkRenderer()->makeKnownLink(
633 $this->getPageTitle(),
634 $messageCache[
'encourage'],
635 [
'title' => $messageCache[
'encourage-tooltip'] ] + $js,
638 'target' => $title->getPrefixedText(),
643 $actions[] = $this->getLinkRenderer()->makeKnownLink(
644 $this->getPageTitle(),
645 $messageCache[
'discourage'],
646 [
'title' => $messageCache[
'discourage-tooltip'] ] + $js,
648 'do' =>
'discourage',
649 'target' => $title->getPrefixedText(),
655 $actions[] = $this->getLinkRenderer()->makeKnownLink(
656 $this->getPageTitle(),
657 $messageCache[
'unmark'],
658 [
'title' => $messageCache[
'unmark-tooltip'] ],
660 'do' => $type ===
'broken' ?
'unmark' :
'unlink',
661 'target' => $title->getPrefixedText(),
672 return '<div>' . implode( $messageCache[
'pipe-separator'], $actions ) .
'</div>';
680 private function validateUnitIds( array $units ):
bool {
682 $status = Status::newGood();
684 $ic = preg_quote( TranslationUnit::UNIT_MARKER_INVALID_CHARS,
'~' );
685 foreach ( $units as $s ) {
686 if ( preg_match(
"~[$ic]~", $s->id ) ) {
687 $status->fatal(
'tpt-invalid', $s->id );
693 if ( isset( $usedNames[$s->id] ) ) {
697 $status->fatal(
'tpt-duplicate', $s->id );
699 $usedNames[$s->id] =
true;
702 if ( $status->isOK() ) {
705 $this->getOutput()->addHTML(
707 $status->getHTML(
false,
false, $this->getLanguage() )
718 $store = $this->translationUnitStoreFactory->getReader( $page->
getTitle() );
719 $storedUnits = $store->getUnits();
720 $parsedUnits = $parse->
units();
724 $page->
getTitle()->getPrefixedText(),
725 TranslatablePage::DISPLAY_TITLE_UNIT_ID
727 $parsedUnits = [ TranslatablePage::DISPLAY_TITLE_UNIT_ID => $displayTitle ] + $parsedUnits;
730 foreach ( array_keys( $storedUnits ) as $key ) {
731 $highest = max( $highest, (
int)$key );
733 foreach ( $parsedUnits as $_ ) {
734 $highest = max( $highest, (
int)$_->id );
737 foreach ( $parsedUnits as $s ) {
740 if ( $s->id === TranslationUnit::NEW_UNIT_ID ) {
742 $s->id = (string)( ++$highest );
744 if ( isset( $storedUnits[$s->id] ) ) {
745 $storedText = $storedUnits[$s->id]->text;
746 if ( $s->text !== $storedText ) {
747 $s->type =
'changed';
748 $s->oldText = $storedText;
755 $deletedUnits = $storedUnits;
756 foreach ( $parsedUnits as $s ) {
757 unset( $deletedUnits[$s->id] );
760 return [ $parsedUnits, $deletedUnits ];
763 private function showPage(
770 $out = $this->getOutput();
771 $out->setSubtitle( $this->getLinkRenderer()->makeKnownLink( $page->
getTitle() ) );
772 $out->addWikiMsg(
'tpt-showpage-intro' );
776 'action' => $this->getPageTitle()->getLocalURL(),
777 'class' =>
'mw-tpt-sp-markform',
781 Xml::openElement(
'form', $formParams ) .
782 Html::hidden(
'do',
'mark' ) .
783 Html::hidden(
'title', $this->getPageTitle()->getPrefixedText() ) .
785 Html::hidden(
'target', $page->
getTitle()->getPrefixedText() ) .
786 Html::hidden(
'token', $this->getContext()->getCsrfTokenSet()->getToken() )
789 $out->wrapWikiMsg(
'==$1==',
'tpt-sections-oldnew' );
791 $diffOld = $this->msg(
'tpt-diff-old' )->escaped();
792 $diffNew = $this->msg(
'tpt-diff-new' )->escaped();
801 foreach ( $sections as $s ) {
802 if ( $s->id === TranslatablePage::DISPLAY_TITLE_UNIT_ID ) {
804 $s->type = $defaultChecked ? $s->type :
'new';
807 $checkBox =
new FieldLayout(
808 new CheckboxInputWidget( [
809 'name' =>
'translatetitle',
810 'selected' => $defaultChecked,
813 'label' => $this->msg(
'tpt-translate-title' )->text(),
815 'classes' => [
'mw-tpt-m-vertical' ]
818 $out->addHTML( $checkBox->toString() );
821 if ( $s->type ===
'new' ) {
823 $name = $this->msg(
'tpt-section-new', $s->id )->escaped();
825 $name = $this->msg(
'tpt-section', $s->id )->escaped();
828 if ( $s->type ===
'changed' ) {
830 $diff =
new DifferenceEngine();
831 $diff->setTextLanguage( $sourceLanguage );
832 $diff->setReducedLineNumbers();
834 $oldContent = ContentHandler::makeContent( $s->getOldText(), $diff->getTitle() );
835 $newContent = ContentHandler::makeContent( $s->getText(), $diff->getTitle() );
837 $diff->setContent( $oldContent, $newContent );
839 $text = $diff->getDiff( $diffOld, $diffNew );
840 $diffOld = $diffNew =
null;
841 $diff->showDiffStyle();
843 $id =
"tpt-sect-{$s->id}-action-nofuzzy";
844 $checkLabel =
new FieldLayout(
845 new CheckboxInputWidget( [
850 'label' => $this->msg(
'tpt-action-nofuzzy' )->text(),
852 'classes' => [
'mw-tpt-m-vertical' ]
855 $text = $checkLabel->toString() . $text;
857 $text = TranslateUtils::convertWhiteSpaceToHTML( $s->getText() );
860 # For changed text, the language is set by $diff->setTextLanguage()
861 $lang = $s->type ===
'changed' ? null : $sourceLanguage;
869 foreach ( $s->getIssues() as $issue ) {
870 $severity = $issue->getSeverity();
871 if ( $severity === TranslationUnitIssue::WARNING ) {
872 $box = Html::warningBox( $this->msg( $issue )->escaped() );
873 } elseif ( $severity === TranslationUnitIssue::ERROR ) {
874 $box = Html::errorBox( $this->msg( $issue )->escaped() );
876 throw new MWException(
877 "Unknown severity: $severity for key: {$issue->getKey()}"
881 $out->addHTML( $box );
885 if ( $deletedUnits ) {
887 $out->wrapWikiMsg(
'==$1==',
'tpt-sections-deleted' );
889 foreach ( $deletedUnits as $s ) {
890 $name = $this->msg(
'tpt-section-deleted', $s->id )->escaped();
891 $text = TranslateUtils::convertWhiteSpaceToHTML( $s->getText() );
909 $oldTemplate = $this->translatablePageParser
910 ->parse( $oldPage->getText() )
911 ->sourcePageTemplateForDiffs();
913 if ( $oldTemplate !== $newTemplate ) {
914 $out->wrapWikiMsg(
'==$1==',
'tpt-sections-template' );
916 $diff =
new DifferenceEngine();
917 $diff->setTextLanguage( $sourceLanguage );
919 $oldContent = ContentHandler::makeContent( $oldTemplate, $diff->getTitle() );
920 $newContent = ContentHandler::makeContent( $newTemplate, $diff->getTitle() );
922 $diff->setContent( $oldContent, $newContent );
924 $text = $diff->getDiff(
925 $this->msg(
'tpt-diff-old' )->escaped(),
926 $this->msg(
'tpt-diff-new' )->escaped()
928 $diff->showDiffStyle();
929 $diff->setReducedLineNumbers();
931 $out->addHTML( Xml::tags(
'div', [], $text ) );
935 if ( !$hasChanges ) {
936 $out->wrapWikiMsg( Html::successBox(
'$1' ),
'tpt-mark-nochanges' );
939 $this->priorityLanguagesForm( $page );
943 $this->templateTransclusionForm( $page->supportsTransclusion() ?? $firstMark );
945 $version = TranslateMetadata::getWithDefaultValue(
948 $this->syntaxVersionForm( $version, $firstMark );
950 $submitButton =
new FieldLayout(
951 new ButtonInputWidget( [
952 'label' => $this->msg(
'tpt-submit' )->text(),
954 'flags' => [
'primary',
'progressive' ],
962 $out->addHTML( $submitButton->toString() );
963 $out->addHTML(
'</form>' );
968 $interfaceLanguage = $this->getLanguage()->getCode();
969 $storedLanguages = (string)TranslateMetadata::get( $groupId,
'prioritylangs' );
970 $default = $storedLanguages !==
'' ? explode(
',', $storedLanguages ) : [];
972 $priorityReason = TranslateMetadata::get( $groupId,
'priorityreason' );
973 $priorityReason = $priorityReason !==
false ? $priorityReason :
'';
975 $form =
new FieldsetLayout( [
980 'name' =>
'prioritylangs',
981 'id' =>
'mw-translate-SpecialPageTranslation-prioritylangs',
982 'languages' => TranslateUtils::getLanguageNames( $interfaceLanguage ),
983 'default' => $default,
986 'label' => $this->msg(
'tpt-select-prioritylangs' )->text(),
991 new CheckboxInputWidget( [
992 'name' =>
'forcelimit',
993 'selected' => TranslateMetadata::get( $groupId,
'priorityforce' ) ===
'on',
996 'label' => $this->msg(
'tpt-select-prioritylangs-force' )->text(),
1001 new TextInputWidget( [
1002 'name' =>
'priorityreason',
1003 'value' => $priorityReason
1006 'label' => $this->msg(
'tpt-select-prioritylangs-reason' )->text(),
1014 $this->getOutput()->wrapWikiMsg(
'==$1==',
'tpt-sections-prioritylangs' );
1015 $this->getOutput()->addHTML( $form->toString() );
1018 private function syntaxVersionForm(
string $version,
bool $firstMark ):
void {
1019 $out = $this->getOutput();
1021 if ( $version === self::LATEST_SYNTAX_VERSION || $firstMark ) {
1025 $out->wrapWikiMsg(
'==$1==',
'tpt-sections-syntaxversion' );
1027 'tpt-syntaxversion-text',
1028 '<code>' . wfEscapeWikiText(
'<span lang="en" dir="ltr">...</span>' ) .
'</code>',
1029 '<code>' . wfEscapeWikiText(
'<translate nowrap>...</translate>' ) .
'</code>'
1032 $checkBox =
new FieldLayout(
1033 new CheckboxInputWidget( [
1034 'name' =>
'use-latest-syntax'
1037 'label' => $out->msg(
'tpt-syntaxversion-label' )->text(),
1038 'align' =>
'inline',
1042 $out->addHTML( $checkBox->toString() );
1045 private function templateTransclusionForm(
bool $supportsTransclusion ):
void {
1046 $out = $this->getOutput();
1047 $out->wrapWikiMsg(
'==$1==',
'tpt-transclusion' );
1049 $checkBox =
new FieldLayout(
1050 new CheckboxInputWidget( [
1051 'name' =>
'transclusion',
1052 'selected' => $supportsTransclusion
1055 'label' => $out->msg(
'tpt-transclusion-label' )->text(),
1056 'align' =>
'inline',
1060 $out->addHTML( $checkBox->toString() );
1083 bool $updateVersion,
1087 $wikiPage = MediaWikiServices::getInstance()->getWikiPageFactory()->newFromTitle( $page->
getTitle() );
1088 $content = ContentHandler::makeContent(
1093 $status = $wikiPage->doUserEditContent(
1096 $this->msg(
'tpt-mark-summary' )->inContentLanguage()->text(),
1097 EDIT_FORCE_BOT | EDIT_UPDATE
1100 if ( !$status->isOK() ) {
1101 return [
'tpt-edit-failed', $status->getWikiText() ];
1105 $newRevisionRecord = $status->value[
'revision-record'];
1108 if ( $newRevisionRecord instanceof RevisionRecord ) {
1109 $newRevisionId = $newRevisionRecord->getId();
1111 $newRevisionId =
null;
1117 if ( $newRevisionId ===
null ) {
1118 $newRevisionId = $page->
getTitle()->getLatestRevID();
1124 $maxid = (int)TranslateMetadata::get( $groupId,
'maxid' );
1126 $pageId = $page->
getTitle()->getArticleID();
1128 foreach ( array_values( $sections ) as $index => $s ) {
1129 $maxid = max( $maxid, (
int)$s->id );
1130 $changed[] = $s->id;
1132 if ( $this->getRequest()->getCheck(
"tpt-sect-{$s->id}-action-nofuzzy" ) ) {
1138 'trs_page' => $pageId,
1139 'trs_key' => $s->id,
1140 'trs_text' => $s->getText(),
1141 'trs_order' => $index
1145 $dbw = wfGetDB( DB_PRIMARY );
1147 'translate_sections',
1148 [
'trs_page' => $page->
getTitle()->getArticleID() ],
1151 $dbw->insert(
'translate_sections', $inserts, __METHOD__ );
1152 TranslateMetadata::set( $groupId,
'maxid', $maxid );
1153 if ( $updateVersion ) {
1154 TranslateMetadata::set( $groupId,
'version', self::LATEST_SYNTAX_VERSION );
1157 $page->setTransclusion( $transclusion );
1160 MessageGroups::singleton()->recache();
1164 $newKeys = $group->makeGroupKeys( $changed );
1165 MessageIndex::singleton()->storeInterim( $group, $newKeys );
1168 $this->jobQueueGroup->push( $job );
1173 $entry =
new ManualLogEntry(
'pagetranslation',
'mark' );
1174 $entry->setPerformer( $this->getUser() );
1175 $entry->setTarget( $page->
getTitle() );
1176 $entry->setParameters( [
1177 'revision' => $newRevisionId,
1178 'changed' => count( $changed ),
1180 $logid = $entry->insert();
1181 $entry->publish( $logid );
1184 $page->
getTitle()->invalidateCache();
1198 $npLangs = rtrim( trim( $request->getVal(
'prioritylangs',
'' ) ),
',' );
1199 $npLangs = implode(
',', explode(
"\n", $npLangs ) );
1200 $npLangs = array_map(
'trim', explode(
',', $npLangs ) );
1201 $npLangs = array_unique( $npLangs );
1203 $npForce = $request->getCheck(
'forcelimit' ) ?
'on' :
'off';
1204 $npReason = trim( $request->getText(
'priorityreason' ) );
1207 $languages = $this->languageNameUtils->getLanguageNames();
1208 foreach ( $npLangs as $index => $language ) {
1209 if ( !array_key_exists( $language, $languages ) ) {
1210 unset( $npLangs[$index] );
1213 $npLangs = implode(
',', $npLangs );
1214 if ( $npLangs ===
'' ) {
1222 $opLangs = TranslateMetadata::get( $groupId,
'prioritylangs' );
1223 $opForce = TranslateMetadata::get( $groupId,
'priorityforce' );
1224 $opReason = TranslateMetadata::get( $groupId,
'priorityreason' );
1226 TranslateMetadata::set( $groupId,
'prioritylangs', $npLangs );
1227 TranslateMetadata::set( $groupId,
'priorityforce', $npForce );
1228 TranslateMetadata::set( $groupId,
'priorityreason', $npReason );
1230 if ( $opLangs !== $npLangs || $opForce !== $npForce || $opReason !== $npReason ) {
1232 'languages' => $npLangs,
1233 'force' => $npForce,
1234 'reason' => $npReason,
1237 $entry =
new ManualLogEntry(
'pagetranslation',
'prioritylanguages' );
1238 $entry->setPerformer( $this->getUser() );
1239 $entry->setTarget( $page->
getTitle() );
1240 $entry->setParameters( $params );
1241 $entry->setComment( $npReason );
1242 $logid = $entry->insert();
1243 $entry->publish( $logid );
1247 private function getPageList( array $pages,
string $type ): string {
1249 $tagsTextCache = [];
1251 $tagDiscouraged = $this->msg(
'tpt-tag-discouraged' )->escaped();
1252 $tagOldSyntax = $this->msg(
'tpt-tag-oldsyntax' )->escaped();
1253 $tagNoTransclusionSupport = $this->msg(
'tpt-tag-no-transclusion-support' )->escaped();
1255 foreach ( $pages as $page ) {
1256 $link = $this->getLinkRenderer()->makeKnownLink( $page[
'title'] );
1257 $acts = $this->actionLinks( $page, $type );
1259 if ( $page[
'discouraged'] ) {
1260 $tags[] = $tagDiscouraged;
1262 if ( $type !==
'proposed' ) {
1263 if ( $page[
'version'] !== self::LATEST_SYNTAX_VERSION ) {
1264 $tags[] = $tagOldSyntax;
1267 if ( $page[
'transclusion'] !==
'1' ) {
1268 $tags[] = $tagNoTransclusionSupport;
1275 $tagsKey = implode(
'', $tags );
1276 $tagsTextCache[$tagsKey] = $tagsTextCache[$tagsKey] ??
1277 $this->msg(
'parentheses' )
1278 ->rawParams( $this->getLanguage()->pipeList( $tags ) )
1281 $tagList = Html::rawElement(
1283 [
'class' =>
'mw-tpt-actions' ],
1284 $tagsTextCache[$tagsKey]
1288 $items[] =
"<li>$link $tagList $acts</li>";
1291 return '<ol>' . implode(
"", $items ) .
'</ol>';