summaryrefslogtreecommitdiff
path: root/www/wiki/extensions/Translate/tag/PageTranslationHooks.php
diff options
context:
space:
mode:
Diffstat (limited to 'www/wiki/extensions/Translate/tag/PageTranslationHooks.php')
-rw-r--r--www/wiki/extensions/Translate/tag/PageTranslationHooks.php851
1 files changed, 608 insertions, 243 deletions
diff --git a/www/wiki/extensions/Translate/tag/PageTranslationHooks.php b/www/wiki/extensions/Translate/tag/PageTranslationHooks.php
index c173e449..607440df 100644
--- a/www/wiki/extensions/Translate/tag/PageTranslationHooks.php
+++ b/www/wiki/extensions/Translate/tag/PageTranslationHooks.php
@@ -4,9 +4,11 @@
*
* @file
* @author Niklas Laxström
- * @license GPL-2.0+
+ * @license GPL-2.0-or-later
*/
+use Wikimedia\ScopedCallback;
+
/**
* Hooks for page translation.
*
@@ -19,11 +21,17 @@ class PageTranslationHooks {
// Check if job queue is running
public static $jobQueueRunning = false;
+ // Check if we are just rendering tags or such
+ public static $renderingContext = false;
+
+ // Used to communicate data between LanguageLinks and SkinTemplateGetLanguageLink hooks.
+ private static $languageLinkData = [];
+
/**
* Hook: ParserBeforeStrip
- * @param $parser Parser
- * @param $text
- * @param $state
+ * @param Parser $parser
+ * @param string &$text
+ * @param string $state
* @return bool
*/
public static function renderTagPage( $parser, &$text, $state ) {
@@ -33,13 +41,17 @@ class PageTranslationHooks {
try {
$parse = TranslatablePage::newFromText( $parser->getTitle(), $text )->getParse();
$text = $parse->getTranslationPageText( null );
+ $parser->getOutput()->addModuleStyles( 'ext.translate' );
} catch ( TPException $e ) {
- // Show ugly preview without processed <translate> tags
wfDebug( 'TPException caught; expected' );
}
}
- $parser->getOutput()->addModules( 'ext.translate' );
+ // For section previews, perform additional clean-up, given tags are often
+ // unbalanced when we preview one section only.
+ if ( $parser->getOptions()->getIsSectionPreview() ) {
+ $text = TranslatablePage::cleanupTags( $text );
+ }
// Set display title
$page = TranslatablePage::isTranslationPage( $title );
@@ -47,37 +59,89 @@ class PageTranslationHooks {
return true;
}
+ self::$renderingContext = true;
list( , $code ) = TranslateUtils::figureMessage( $title->getText() );
$name = $page->getPageDisplayTitle( $code );
-
if ( $name ) {
$name = $parser->recursivePreprocess( $name );
$parser->getOutput()->setDisplayTitle( $name );
}
+ self::$renderingContext = false;
+
+ $parser->getOutput()->setExtensionData(
+ 'translate-translation-page',
+ [
+ 'sourcepagetitle' => $page->getTitle(),
+ 'languagecode' => $code,
+ 'messagegroupid' => $page->getMessageGroupId()
+ ]
+ );
// Disable edit section links
- $parser->getOptions()->setEditSection( false );
+ $parser->getOutput()->setExtensionData( 'Translate-noeditsection', true );
+ if ( !defined( 'ParserOutput::SUPPORTS_STATELESS_TRANSFORMS' ) ) {
+ $parser->getOptions()->setEditSection( false );
+ }
return true;
}
/**
+ * Hook: ParserOutputPostCacheTransform
+ * @param ParserOutput $out
+ * @param string &$text
+ * @param array &$options
+ */
+ public static function onParserOutputPostCacheTransform(
+ ParserOutput $out, &$text, array &$options
+ ) {
+ if ( $out->getExtensionData( 'Translate-noeditsection' ) ) {
+ $options['enableSectionEditLinks'] = false;
+ }
+ }
+
+ /**
* Set the right page content language for translated pages ("Page/xx").
* Hook: PageContentLanguage
+ *
+ * @param Title $title
+ * @param Language|StubUserLang|string &$pageLang
+ * @return true
*/
- public static function onPageContentLanguage( Title $title, /*string*/&$pageLang ) {
+ public static function onPageContentLanguage( Title $title, &$pageLang ) {
// For translation pages, parse plural, grammar etc with correct language,
// and set the right direction
if ( TranslatablePage::isTranslationPage( $title ) ) {
list( , $code ) = TranslateUtils::figureMessage( $title->getText() );
- $pageLang = $code;
+ $pageLang = Language::factory( $code );
}
return true;
}
- /// Hook: OutputPageBeforeHTML
- public static function injectCss( OutputPage $out, /*string*/$text ) {
+ /**
+ * Display an edit notice for translatable source pages if it's enabled
+ * Hook: TitleGetEditNotices
+ *
+ * @param Title $title
+ * @param int $oldid
+ * @param array &$notices
+ */
+ public static function onTitleGetEditNotices( Title $title, $oldid, array &$notices ) {
+ $msg = wfMessage( 'translate-edit-tag-warning' )->inContentLanguage();
+
+ if ( !$msg->isDisabled() && TranslatablePage::isSourcePage( $title ) ) {
+ $notices['translate-tag'] = $msg->parseAsBlock();
+ }
+ }
+
+ /**
+ * Hook: OutputPageBeforeHTML
+ * @param OutputPage $out
+ * @param string $text
+ * @return true
+ */
+ public static function injectCss( OutputPage $out, /*string*/ $text ) {
global $wgTranslatePageTranslationULS;
$title = $out->getTitle();
@@ -85,8 +149,6 @@ class PageTranslationHooks {
$isTranslation = TranslatablePage::isTranslationPage( $title );
if ( $isSource || $isTranslation ) {
- $out->addModuleStyles( 'ext.translate' );
-
if ( $wgTranslatePageTranslationULS ) {
$out->addModules( 'ext.translate.pagetranslation.uls' );
}
@@ -106,6 +168,15 @@ class PageTranslationHooks {
/**
* This is triggered after saves to translation unit pages
+ * @param WikiPage $wikiPage
+ * @param User $user
+ * @param TextContent $content
+ * @param string $summary
+ * @param bool $minor
+ * @param int $flags
+ * @param Revision $revision
+ * @param MessageHandle $handle
+ * @return true
*/
public static function onSectionSave( WikiPage $wikiPage, User $user, TextContent $content,
$summary, $minor, $flags, $revision, MessageHandle $handle
@@ -115,12 +186,6 @@ class PageTranslationHooks {
return true;
}
- // Do not trigger renders for fuzzy
- $text = $content->getNativeData();
- if ( strpos( $text, TRANSLATE_FUZZY ) !== false ) {
- return true;
- }
-
$group = $handle->getGroup();
if ( !$group instanceof WikiPageMessageGroup ) {
return true;
@@ -165,52 +230,36 @@ class PageTranslationHooks {
}
/**
- * @param $data
- * @param $params
- * @param $parser Parser
+ * @param string $data
+ * @param array $params
+ * @param Parser $parser
* @return string
*/
public static function languages( $data, $params, $parser ) {
- $currentTitle = $parser->getTitle();
+ global $wgPageTranslationLanguageList;
- // Check if this is a source page or a translation page
- $page = TranslatablePage::newFromTitle( $currentTitle );
- if ( $page->getMarkedTag() === false ) {
- $page = TranslatablePage::isTranslationPage( $currentTitle );
- }
-
- if ( $page === false || $page->getMarkedTag() === false ) {
+ if ( $wgPageTranslationLanguageList === 'sidebar-only' ) {
return '';
}
- $status = $page->getTranslationPercentages();
- if ( !$status ) {
- return '';
- }
+ self::$renderingContext = true;
+ $context = new ScopedCallback( function () {
+ self::$renderingContext = false;
+ } );
- // If priority languages have been set always show those languages
- $priorityLangs = TranslateMetadata::get( $page->getMessageGroupId(), 'prioritylangs' );
- $priorityForce = TranslateMetadata::get( $page->getMessageGroupId(), 'priorityforce' );
- $filter = null;
- if ( strlen( $priorityLangs ) > 0 ) {
- $filter = array_flip( explode( ',', $priorityLangs ) );
+ // Add a dummy language link that is removed in self::addLanguageLinks.
+ if ( $wgPageTranslationLanguageList === 'sidebar-fallback' ) {
+ $parser->getOutput()->addLanguageLink( 'x-pagetranslation-tag' );
}
- if ( $filter !== null ) {
- // If translation is restricted to some languages, only show them
- if ( $priorityForce === 'on' ) {
- // Do not filter the source language link
- $filter[$page->getMessageGroup()->getSourceLanguage()] = true;
- $status = array_intersect_key( $status, $filter );
- }
- foreach ( $filter as $langCode => $value ) {
- if ( !isset( $status[$langCode] ) ) {
- // We need to show all priority languages even if no translation started
- $status[$langCode] = 0;
- }
- }
+
+ $currentTitle = $parser->getTitle();
+ $pageStatus = self::getTranslatablePageStatus( $currentTitle );
+ if ( !$pageStatus ) {
+ return '';
}
- // Fix title
+ $page = $pageStatus[ 'page' ];
+ $status = $pageStatus[ 'languages' ];
$pageTitle = $page->getTitle();
// Sort by language code, which seems to be the only sane method
@@ -224,7 +273,7 @@ class PageTranslationHooks {
// This should do the same thing for now.
$sourceLanguage = $pageTitle->getPageLanguage()->getCode();
- $languages = array();
+ $languages = [];
foreach ( $status as $code => $percent ) {
// Get autonyms (null)
$name = TranslateUtils::getLanguageName( $code, null );
@@ -235,7 +284,7 @@ class PageTranslationHooks {
$targetTitleString = $pageTitle->getDBkey() . $suffix;
$subpage = Title::makeTitle( $pageTitle->getNamespace(), $targetTitleString );
- $classes = array();
+ $classes = [];
if ( $code === $userLangCode ) {
$classes[] = 'mw-pt-languages-ui';
}
@@ -243,7 +292,11 @@ class PageTranslationHooks {
if ( $currentTitle->equals( $subpage ) ) {
$classes[] = 'mw-pt-languages-selected';
$classes = array_merge( $classes, self::tpProgressIcon( $percent ) );
- $name = Html::rawElement( 'span', array( 'class' => $classes ), $name );
+ $element = Html::rawElement(
+ 'span',
+ [ 'class' => $classes , 'lang' => LanguageCode::bcp47( $code ) ],
+ $name
+ );
} elseif ( $subpage->isKnown() ) {
$pagename = $page->getPageDisplayTitle( $code );
if ( !is_string( $pagename ) ) {
@@ -257,51 +310,56 @@ class PageTranslationHooks {
->params( $pagename )
->numParams( 100 * $percent )
->text();
- $attribs = array(
+ $attribs = [
'title' => $title,
'class' => $classes,
- );
+ 'lang' => LanguageCode::bcp47( $code ),
+ ];
- $name = Linker::linkKnown( $subpage, $name, $attribs );
+ $element = Linker::linkKnown( $subpage, $name, $attribs );
} else {
/* When language is included because it is a priority language,
* but translation does not yet exists, link directly to the
* translation view. */
$specialTranslateTitle = SpecialPage::getTitleFor( 'Translate' );
- $params = array(
+ $params = [
'group' => $page->getMessageGroupId(),
'language' => $code,
'task' => 'view'
- );
+ ];
$classes[] = 'new'; // For red link color
- $attribs = array(
+ $attribs = [
'title' => wfMessage( 'tpt-languages-zero' )->inLanguage( $userLang )->text(),
'class' => $classes,
- );
- $name = Linker::link( $specialTranslateTitle, $name, $attribs, $params );
+ ];
+ $element = Linker::linkKnown( $specialTranslateTitle, $name, $attribs, $params );
}
- $languages[] = $name;
+ $languages[ $name ] = $element;
}
+ // Sort languages by autonym
+ ksort( $languages );
+ $languages = array_values( $languages );
+
// dirmark (rlm/lrm) is added, because languages with RTL names can
// mess the display
$sep = wfMessage( 'tpt-languages-separator' )->inLanguage( $userLang )->escaped();
$sep .= $userLang->getDirMark();
$languages = implode( $sep, $languages );
- $out = Html::openElement( 'div', array(
+ $out = Html::openElement( 'div', [
'class' => 'mw-pt-languages noprint',
'lang' => $userLang->getHtmlCode(),
'dir' => $userLang->getDir()
- ) );
- $out .= Html::rawElement( 'div', array( 'class' => 'mw-pt-languages-label' ),
+ ] );
+ $out .= Html::rawElement( 'div', [ 'class' => 'mw-pt-languages-label' ],
wfMessage( 'tpt-languages-legend' )->inLanguage( $userLang )->escaped()
);
$out .= Html::rawElement(
'div',
- array( 'class' => 'mw-pt-languages-list autonym' ),
+ [ 'class' => 'mw-pt-languages-list autonym' ],
$languages
);
$out .= Html::closeElement( 'div' );
@@ -314,11 +372,11 @@ class PageTranslationHooks {
/**
* Return icon CSS class for given progress status: percentages
* are too accurate and take more space than simple images.
- * @param $percent float
+ * @param float $percent
* @return string[]
*/
protected static function tpProgressIcon( $percent ) {
- $classes = array( 'mw-pt-progress' );
+ $classes = [ 'mw-pt-progress' ];
$percent *= 100;
if ( $percent < 20 ) {
$classes[] = 'mw-pt-progress--stub';
@@ -335,8 +393,181 @@ class PageTranslationHooks {
}
/**
+ * Returns translatable page and language stats for given title.
+ * @param Title $title
+ * @return array|null Returns null if not a translatable page.
+ */
+ private static function getTranslatablePageStatus( Title $title ) {
+ // Check if this is a source page or a translation page
+ $page = TranslatablePage::newFromTitle( $title );
+ if ( $page->getMarkedTag() === false ) {
+ $page = TranslatablePage::isTranslationPage( $title );
+ }
+
+ if ( $page === false || $page->getMarkedTag() === false ) {
+ return null;
+ }
+
+ $status = $page->getTranslationPercentages();
+ if ( !$status ) {
+ return null;
+ }
+
+ // If priority languages have been set always show those languages
+ $priorityLangs = TranslateMetadata::get( $page->getMessageGroupId(), 'prioritylangs' );
+ $priorityForce = TranslateMetadata::get( $page->getMessageGroupId(), 'priorityforce' );
+ $filter = null;
+ if ( strlen( $priorityLangs ) > 0 ) {
+ $filter = array_flip( explode( ',', $priorityLangs ) );
+ }
+ if ( $filter !== null ) {
+ // If translation is restricted to some languages, only show them
+ if ( $priorityForce === 'on' ) {
+ // Do not filter the source language link
+ $filter[$page->getMessageGroup()->getSourceLanguage()] = true;
+ $status = array_intersect_key( $status, $filter );
+ }
+ foreach ( $filter as $langCode => $value ) {
+ if ( !isset( $status[$langCode] ) ) {
+ // We need to show all priority languages even if no translation started
+ $status[$langCode] = 0;
+ }
+ }
+ }
+
+ return [
+ 'page' => $page,
+ 'languages' => $status
+ ];
+ }
+
+ /**
+ * Hooks: LanguageLinks
+ * @param Title $title Title of the page for which links are needed.
+ * @param array &$languageLinks List of language links to modify.
+ */
+ public static function addLanguageLinks( Title $title, array &$languageLinks ) {
+ global $wgPageTranslationLanguageList;
+
+ $hasLanguagesTag = false;
+ foreach ( $languageLinks as $index => $name ) {
+ if ( $name === 'x-pagetranslation-tag' ) {
+ $hasLanguagesTag = true;
+ unset( $languageLinks[ $index ] );
+ }
+ }
+
+ if ( $wgPageTranslationLanguageList === 'tag-only' ) {
+ return;
+ }
+
+ if ( $wgPageTranslationLanguageList === 'sidebar-fallback' && $hasLanguagesTag ) {
+ return;
+ }
+
+ // $wgPageTranslationLanguageList === 'sidebar-always' OR 'sidebar-only'
+
+ $status = self::getTranslatablePageStatus( $title );
+ if ( !$status ) {
+ return;
+ }
+
+ self::$renderingContext = true;
+ $context = new ScopedCallback( function () {
+ self::$renderingContext = false;
+ } );
+
+ $page = $status[ 'page' ];
+ $languages = $status[ 'languages' ];
+ $en = Language::factory( 'en' );
+
+ $newLanguageLinks = [];
+
+ // Batch the Title::exists queries used below
+ $lb = new LinkBatch();
+ foreach ( array_keys( $languages ) as $code ) {
+ $title = $page->getTitle()->getSubpage( $code );
+ $lb->addObj( $title );
+ }
+ $lb->execute();
+
+ foreach ( $languages as $code => $percentage ) {
+ $title = $page->getTitle()->getSubpage( $code );
+ $key = "x-pagetranslation:{$title->getPrefixedText()}";
+ $translatedName = $page->getPageDisplayTitle( $code ) ?: $title->getPrefixedText();
+
+ if ( $title->exists() ) {
+ $href = $title->getLocalURL();
+ $classes = self::tpProgressIcon( $percentage );
+ $title = wfMessage( 'tpt-languages-nonzero' )
+ ->params( $translatedName )
+ ->numParams( 100 * $percentage );
+ } else {
+ $href = SpecialPage::getTitleFor( 'Translate' )->getLocalURL( [
+ 'group' => $page->getMessageGroupId(),
+ 'language' => $code,
+ ] );
+ $classes = [ 'mw-pt-progress--none' ];
+ $title = wfMessage( 'tpt-languages-zero' );
+ }
+
+ self::$languageLinkData[ $key ] = [
+ 'href' => $href,
+ 'language' => $code,
+ 'percentage' => $percentage,
+ 'classes' => $classes,
+ 'autonym' => $en->ucfirst( Language::fetchLanguageName( $code ) ),
+ 'title' => $title,
+ ];
+
+ $newLanguageLinks[ $key ] = self::$languageLinkData[ $key ][ 'autonym' ];
+ }
+
+ asort( $newLanguageLinks );
+ $languageLinks = array_merge( array_keys( $newLanguageLinks ), $languageLinks );
+ }
+
+ /**
+ * Hooks: SkinTemplateGetLanguageLink
+ * @param array &$link
+ * @param Title $linkTitle
+ * @param Title $pageTitle
+ * @param OutputPage $out
+ */
+ public static function formatLanguageLink(
+ array &$link,
+ Title $linkTitle,
+ Title $pageTitle,
+ OutputPage $out
+ ) {
+ if ( substr( $link[ 'text' ], 0, 18 ) !== 'x-pagetranslation:' ) {
+ return;
+ }
+
+ if ( !isset( self::$languageLinkData[ $link[ 'text' ] ] ) ) {
+ return;
+ }
+
+ $data = self::$languageLinkData[ $link[ 'text' ] ];
+
+ $link[ 'class' ] .= ' ' . implode( ' ', $data[ 'classes' ] );
+ $link[ 'href' ] = $data[ 'href' ];
+ $link[ 'text' ] = $data[ 'autonym' ];
+ $link[ 'title' ] = $data[ 'title' ]->inLanguage( $out->getLanguage()->getCode() )->text();
+ $link[ 'lang'] = LanguageCode::bcp47( $data[ 'language' ] );
+ $link[ 'hreflang'] = LanguageCode::bcp47( $data[ 'language' ] );
+
+ $out->addModuleStyles( 'ext.translate.tag.languages' );
+ }
+
+ /**
* Display nice error when editing content.
* Hook: EditFilterMergedContent
+ * @param IContextSource $context
+ * @param Content $content
+ * @param Status $status
+ * @param string $summary
+ * @return true
*/
public static function tpSyntaxCheckForEditContent( $context, $content, $status, $summary ) {
if ( !$content instanceof TextContent ) {
@@ -344,6 +575,8 @@ class PageTranslationHooks {
}
$text = $content->getNativeData();
+ // See T154500
+ $text = str_replace( [ "\r\n", "\r" ], "\n", rtrim( $text ) );
$title = $context->getTitle();
$e = self::tpSyntaxError( $title, $text );
@@ -353,7 +586,7 @@ class PageTranslationHooks {
// $msg is an array containing a message key followed by any parameters.
// @todo Use Message object instead.
- call_user_func_array( array( $status, 'fatal' ), $msg );
+ call_user_func_array( [ $status, 'fatal' ], $msg );
}
return true;
@@ -361,6 +594,9 @@ class PageTranslationHooks {
/**
* Returns any syntax error.
+ * @param Title $title
+ * @param string $text
+ * @return null|TPException
*/
protected static function tpSyntaxError( $title, $text ) {
if ( strpos( $text, '<translate>' ) === false ) {
@@ -381,12 +617,24 @@ class PageTranslationHooks {
* When attempting to save, last resort. Edit page would only display
* edit conflict if there wasn't tpSyntaxCheckForEditPage.
* Hook: PageContentSave
+ * @param WikiPage $wikiPage
+ * @param User $user
+ * @param Content $content
+ * @param string $summary
+ * @param bool $minor
+ * @param string $_1
+ * @param bool $_2
+ * @param int $flags
+ * @param Status $status
+ * @return true
*/
- public static function tpSyntaxCheck( $wikiPage, $user, $content, $summary,
+ public static function tpSyntaxCheck( WikiPage $wikiPage, $user, $content, $summary,
$minor, $_1, $_2, $flags, $status
) {
if ( $content instanceof TextContent ) {
$text = $content->getNativeData();
+ // See T154500
+ $text = str_replace( [ "\r\n", "\r" ], "\n", rtrim( $text ) );
} else {
// Screw it, not interested
return true;
@@ -401,7 +649,7 @@ class PageTranslationHooks {
try {
$page->getParse();
} catch ( TPException $e ) {
- call_user_func_array( array( $status, 'fatal' ), $e->getMsg() );
+ call_user_func_array( [ $status, 'fatal' ], $e->getMsg() );
return false;
}
@@ -411,8 +659,18 @@ class PageTranslationHooks {
/**
* Hook: PageContentSaveComplete
+ * @param WikiPage $wikiPage
+ * @param User $user
+ * @param Content $content
+ * @param string $summary
+ * @param bool $minor
+ * @param string $_1
+ * @param bool $_2
+ * @param int $flags
+ * @param Revision $revision
+ * @return true
*/
- public static function addTranstag( $wikiPage, $user, $content, $summary,
+ public static function addTranstag( WikiPage $wikiPage, $user, $content, $summary,
$minor, $_1, $_2, $flags, $revision
) {
// We are not interested in null revisions
@@ -454,6 +712,10 @@ class PageTranslationHooks {
* at the moment.
* Hook: RevisionInsertComplete
* @since 2012-05-08
+ * @param Revision $rev
+ * @param string $text
+ * @param int $flags
+ * @return true
*/
public static function updateTranstagOnNullRevisions( Revision $rev, $text, $flags ) {
$title = $rev->getTitle();
@@ -467,10 +729,10 @@ class PageTranslationHooks {
$dbw = wfGetDB( DB_MASTER );
$table = 'revision';
$field = 'rev_text_id';
- $conds = array(
+ $conds = [
'rev_page' => $rev->getPage(),
'rev_id' => $oldRevId,
- );
+ ];
// FIXME: optimize away this query. Bug T38588.
$oldTextId = $dbw->selectField( $table, $field, $conds, __METHOD__ );
@@ -488,18 +750,34 @@ class PageTranslationHooks {
}
/**
- * Prevent editing of unknown pages in Translations namespace.
+ * Prevent editing of certain pages in Translations namespace.
* Hook: getUserPermissionsErrorsExpensive
+ *
+ * @param Title $title
+ * @param User $user
+ * @param string $action
+ * @param mixed &$result
+ * @return bool
*/
- public static function preventUnknownTranslations( Title $title, User $user,
+ public static function onGetUserPermissionsErrorsExpensive( Title $title, User $user,
$action, &$result
) {
$handle = new MessageHandle( $title );
- if ( $action === 'edit' && $handle->isPageTranslation() &&
- !$handle->isValid()
- ) {
- $result = array( 'tpt-unknown-page' );
+ // Check only when someone tries to edit (or create) page translation messages
+ if ( $action !== 'edit' || !$handle->isPageTranslation() ) {
+ return true;
+ }
+
+ if ( !$handle->isValid() ) {
+ // Don't allow editing invalid messages that do not belong to any translatable page
+ $result = [ 'tpt-unknown-page' ];
+ return false;
+ }
+
+ $error = self::getTranslationRestrictions( $handle );
+ if ( count( $error ) ) {
+ $result = $error;
return false;
}
@@ -507,22 +785,17 @@ class PageTranslationHooks {
}
/**
- * Prevent editing of restricted languages.
- * Hook: getUserPermissionsErrorsExpensive
- * @since 2012-03-01
+ * Prevent editing of restricted languages when prioritized.
+ *
+ * @param MessageHandle $handle
+ * @return array array containing error message if restricted, empty otherwise
*/
- public static function preventRestrictedTranslations( Title $title, User $user,
- $action, &$result
- ) {
+ private static function getTranslationRestrictions( MessageHandle $handle ) {
global $wgTranslateDocumentationLanguageCode;
- // Preventing editing (includes creation) should be enough
- if ( $action !== 'edit' ) {
- return true;
- }
- $handle = new MessageHandle( $title );
- if ( !$handle->isValid() ) {
- return true;
+ // Allow adding message documentation even when translation is restricted
+ if ( $handle->getCode() === $wgTranslateDocumentationLanguageCode ) {
+ return [];
}
// Get the primary group id
@@ -532,12 +805,7 @@ class PageTranslationHooks {
// Check if anything is prevented for the group in the first place
$force = TranslateMetadata::get( $groupId, 'priorityforce' );
if ( $force !== 'on' ) {
- return true;
- }
-
- // Allow adding message documentation even when translation is restricted
- if ( $handle->getCode() === $wgTranslateDocumentationLanguageCode ) {
- return true;
+ return [];
}
// And finally check whether the language is not included in whitelist
@@ -546,42 +814,45 @@ class PageTranslationHooks {
if ( !isset( $filter[$handle->getCode()] ) ) {
// @todo Default reason if none provided
$reason = TranslateMetadata::get( $groupId, 'priorityreason' );
- $result = array( 'tpt-translation-restricted', $reason );
-
- return false;
+ return [ 'tpt-translation-restricted', $reason ];
}
- return true;
+ return [];
}
/**
* Prevent editing of translation pages directly.
* Hook: getUserPermissionsErrorsExpensive
+ * @param Title $title
+ * @param User $user
+ * @param string $action
+ * @param bool &$result
+ * @return bool
*/
public static function preventDirectEditing( Title $title, User $user, $action, &$result ) {
- $page = TranslatablePage::isTranslationPage( $title );
- $whitelist = array(
- 'read' => true,
- 'delete' => true,
- 'review' => true, // FlaggedRevs
- );
+ if ( self::$allowTargetEdit ) {
+ return true;
+ }
- if ( $page !== false && !isset( $whitelist[$action] ) ) {
- if ( self::$allowTargetEdit ) {
- return true;
- }
+ $whitelist = [
+ 'read', 'delete', 'undelete', 'deletedtext', 'deletedhistory',
+ 'review', // FlaggedRevs
+ ];
+ if ( in_array( $action, $whitelist ) ) {
+ return true;
+ }
- if ( $page->getMarkedTag() ) {
- list( , $code ) = TranslateUtils::figureMessage( $title->getText() );
- $result = array(
- 'tpt-target-page',
- ':' . $page->getTitle()->getPrefixedText(),
- // This url shouldn't get cached
- wfExpandUrl( $page->getTranslationUrl( $code ) )
- );
+ $page = TranslatablePage::isTranslationPage( $title );
+ if ( $page !== false && $page->getMarkedTag() ) {
+ list( , $code ) = TranslateUtils::figureMessage( $title->getText() );
+ $result = [
+ 'tpt-target-page',
+ ':' . $page->getTitle()->getPrefixedText(),
+ // This url shouldn't get cached
+ wfExpandUrl( $page->getTranslationUrl( $code ) )
+ ];
- return false;
- }
+ return false;
}
return true;
@@ -594,7 +865,7 @@ class PageTranslationHooks {
* @param Title $title
* @param User $user
* @param string $action
- * @param mixed $result
+ * @param mixed &$result
*
* @return bool
*/
@@ -617,15 +888,18 @@ class PageTranslationHooks {
* Redirects the delete action to our own for translatable pages.
* Hook: ArticleConfirmDelete
*
- * @param $article Article
- * @param $out OutputPage
- * @param $reason
+ * @param Article $article
+ * @param OutputPage $out
+ * @param string &$reason
*
* @return bool
*/
public static function disableDelete( $article, $out, &$reason ) {
$title = $article->getTitle();
- if ( TranslatablePage::isSourcePage( $title ) ||
+ $translatablePage = TranslatablePage::newFromTitle( $title );
+
+ if (
+ $translatablePage->getMarkedTag() !== false ||
TranslatablePage::isTranslationPage( $title )
) {
$new = SpecialPage::getTitleFor(
@@ -641,9 +915,9 @@ class PageTranslationHooks {
/**
* Hook: ArticleViewHeader
*
- * @param $article Article
- * @param $outputDone
- * @param $pcache
+ * @param Article &$article
+ * @param bool &$outputDone
+ * @param bool &$pcache
* @return bool
*/
public static function translatablePageHeader( &$article, &$outputDone, &$pcache ) {
@@ -651,58 +925,47 @@ class PageTranslationHooks {
return true;
}
- $title = $article->getTitle();
-
- if ( TranslatablePage::isTranslationPage( $title ) ) {
- self::translationPageHeader( $title );
+ $transPage = TranslatablePage::isTranslationPage( $article->getTitle() );
+ $context = $article->getContext();
+ if ( $transPage ) {
+ self::translationPageHeader( $context, $transPage );
} else {
// Check for pages that are tagged or marked
- self::sourcePageHeader( $title );
+ self::sourcePageHeader( $context );
}
return true;
}
- protected static function sourcePageHeader( Title $title ) {
- $context = RequestContext::getMain();
+ protected static function sourcePageHeader( IContextSource $context ) {
+ $language = $context->getLanguage();
+ $title = $context->getTitle();
$page = TranslatablePage::newFromTitle( $title );
$marked = $page->getMarkedTag();
$ready = $page->getReadyTag();
-
- $title = $page->getTitle();
-
$latest = $title->getLatestRevID();
- $canmark = $ready === $latest && $marked !== $latest;
-
- $actions = array();
+ $actions = [];
if ( $marked && $context->getUser()->isAllowed( 'translate' ) ) {
- $par = array(
- 'group' => $page->getMessageGroupId(),
- 'language' => $context->getLanguage()->getCode(),
- 'action' => 'page',
- 'filter' => '',
- );
-
- $translate = SpecialPage::getTitleFor( 'Translate' );
- $linkDesc = $context->msg( 'translate-tag-translate-link-desc' )->escaped();
- $actions[] = Linker::link( $translate, $linkDesc, array(), $par );
+ $actions[] = self::getTranslateLink( $context, $page, $language->getCode() );
}
- if ( $canmark ) {
- $diffUrl = $title->getFullURL( array( 'oldid' => $marked, 'diff' => $latest ) );
- $par = array( 'target' => $title->getPrefixedText(), 'do' => 'mark' );
- $translate = SpecialPage::getTitleFor( 'PageTranslation' );
+ $hasChanges = $ready === $latest && $marked !== $latest;
+ if ( $hasChanges ) {
+ $diffUrl = $title->getFullURL( [ 'oldid' => $marked, 'diff' => $latest ] );
if ( $context->getUser()->isAllowed( 'pagetranslation' ) ) {
- // This page has never been marked
+ $pageTranslation = SpecialPage::getTitleFor( 'PageTranslation' );
+ $params = [ 'target' => $title->getPrefixedText(), 'do' => 'mark' ];
+
if ( $marked === false ) {
+ // This page has never been marked
$linkDesc = $context->msg( 'translate-tag-markthis' )->escaped();
- $actions[] = Linker::link( $translate, $linkDesc, array(), $par );
+ $actions[] = Linker::linkKnown( $pageTranslation, $linkDesc, [], $params );
} else {
- $markUrl = $translate->getFullURL( $par );
+ $markUrl = $pageTranslation->getFullURL( $params );
$actions[] = $context->msg( 'translate-tag-markthisagain', $diffUrl, $markUrl )
->parse();
}
@@ -715,28 +978,42 @@ class PageTranslationHooks {
return;
}
- $language = $context->getLanguage();
- $legend = Html::rawElement(
+ $header = Html::rawElement(
'div',
- array(
+ [
'class' => 'mw-pt-translate-header noprint nomobile',
'dir' => $language->getDir(),
'lang' => $language->getHtmlCode(),
- ),
- $context->getLanguage()->semicolonList( $actions )
+ ],
+ $language->semicolonList( $actions )
) . Html::element( 'hr' );
- $context->getOutput()->addHTML( $legend );
+ $context->getOutput()->addHTML( $header );
}
- protected static function translationPageHeader( Title $title ) {
- if ( !$title->exists() ) {
- return;
- }
+ private static function getTranslateLink(
+ IContextSource $context, TranslatablePage $page, $langCode
+ ) {
+ return Linker::linkKnown(
+ SpecialPage::getTitleFor( 'Translate' ),
+ $context->msg( 'translate-tag-translate-link-desc' )->escaped(),
+ [],
+ [
+ 'group' => $page->getMessageGroupId(),
+ 'language' => $langCode,
+ 'action' => 'page',
+ 'filter' => '',
+ ]
+ );
+ }
- // Check if applicable
- $page = TranslatablePage::isTranslationPage( $title );
- if ( $page === false ) {
+ protected static function translationPageHeader(
+ IContextSource $context, TranslatablePage $page
+ ) {
+ global $wgTranslateKeepOutdatedTranslations;
+
+ $title = $context->getTitle();
+ if ( !$title->exists() ) {
return;
}
@@ -748,46 +1025,65 @@ class PageTranslationHooks {
if ( isset( $pers[$code] ) ) {
$per = $pers[$code] * 100;
}
- $titleText = $page->getTitle()->getPrefixedText();
- // This url might get cached
- $url = wfExpandUrl( $page->getTranslationUrl( $code ), PROTO_RELATIVE );
-
- // Output
- $context = RequestContext::getMain();
$language = $context->getLanguage();
- $wrap = Html::rawElement(
+ $output = $context->getOutput();
+
+ if ( $page->getSourceLanguageCode() === $code ) {
+ // If we are on the source language page, link to translate for user's language
+ $msg = self::getTranslateLink( $context, $page, $language->getCode() );
+ } else {
+ $url = wfExpandUrl( $page->getTranslationUrl( $code ), PROTO_RELATIVE );
+ $msg = $context->msg( 'tpt-translation-intro',
+ $url,
+ ':' . $page->getTitle()->getPrefixedText(),
+ $language->formatNum( $per )
+ )->parse();
+ }
+
+ $header = Html::rawElement(
'div',
- array(
+ [
'class' => 'mw-pt-translate-header noprint',
'dir' => $language->getDir(),
'lang' => $language->getHtmlCode(),
- ),
- '$1'
- );
+ ],
+ $msg
+ ) . Html::element( 'hr' );
- $out = $context->getOutput();
+ $output->addHTML( $header );
- $out->wrapWikiMsg(
- $wrap,
- array(
- 'tpt-translation-intro',
- $url,
- ':' . $titleText,
- $language->formatNum( $per )
- )
- );
- $out->addHTML( '<hr />' );
+ if ( $wgTranslateKeepOutdatedTranslations ) {
+ $groupId = $page->getMessageGroupId();
+ // This is already calculated and cached by above call to getTranslationPercentages
+ $stats = MessageGroupStats::forItem( $groupId, $code );
+ if ( $stats[MessageGroupStats::FUZZY] ) {
+ // Only show if there is fuzzy messages
+ $wrap = '<div class="mw-pt-translate-header"><span class="mw-translate-fuzzy">$1</span></div>';
+ $output->wrapWikiMsg( $wrap, [ 'tpt-translation-intro-fuzzy' ] );
+ }
+ }
}
- /// Hook: SpecialPage_initList
+ /**
+ * Hook: SpecialPage_initList
+ * @param array &$list
+ * @return true
+ */
public static function replaceMovePage( &$list ) {
$list['Movepage'] = 'SpecialPageTranslationMovePage';
return true;
}
- /// Hook: getUserPermissionsErrorsExpensive
+ /**
+ * Hook: getUserPermissionsErrorsExpensive
+ * @param Title $title
+ * @param User $user
+ * @param string $action
+ * @param array &$result
+ * @return bool
+ */
public static function lockedPagesCheck( Title $title, User $user, $action, &$result ) {
if ( $action === 'read' ) {
return true;
@@ -795,9 +1091,8 @@ class PageTranslationHooks {
$cache = wfGetCache( CACHE_ANYTHING );
$key = wfMemcKey( 'pt-lock', sha1( $title->getPrefixedText() ) );
- // At least memcached mangles true to "1"
- if ( $cache->get( $key ) !== false ) {
- $result = array( 'pt-locked-page' );
+ if ( $cache->get( $key ) === 'locked' ) {
+ $result = [ 'pt-locked-page' ];
return false;
}
@@ -805,9 +1100,16 @@ class PageTranslationHooks {
return true;
}
- /// Hook: SkinSubPageSubtitle
- public static function replaceSubtitle( &$subpages, $skin = null, OutputPage $out ) {
- if ( !TranslatablePage::isTranslationPage( $out->getTitle() )
+ /**
+ * Hook: SkinSubPageSubtitle
+ * @param array &$subpages
+ * @param Skin|null $skin
+ * @param OutputPage $out
+ * @return bool
+ */
+ public static function replaceSubtitle( &$subpages, Skin $skin = null, OutputPage $out ) {
+ $isTranslationPage = TranslatablePage::isTranslationPage( $out->getTitle() );
+ if ( !$isTranslationPage
&& !TranslatablePage::isSourcePage( $out->getTitle() )
) {
return true;
@@ -816,23 +1118,24 @@ class PageTranslationHooks {
// Copied from Skin::subPageSubtitle()
if ( $out->isArticle() && MWNamespace::hasSubpages( $out->getTitle()->getNamespace() ) ) {
$ptext = $out->getTitle()->getPrefixedText();
- if ( preg_match( '/\//', $ptext ) ) {
+ if ( strpos( $ptext, '/' ) !== false ) {
$links = explode( '/', $ptext );
array_pop( $links );
- // Also pop of one extra for language code is needed
- if ( TranslatablePage::isTranslationPage( $out->getTitle() ) ) {
+ if ( $isTranslationPage ) {
+ // Also remove language code page
array_pop( $links );
}
$c = 0;
$growinglink = '';
$display = '';
+ $lang = $skin->getLanguage();
foreach ( $links as $link ) {
$growinglink .= $link;
$display .= $link;
$linkObj = Title::newFromText( $growinglink );
- if ( is_object( $linkObj ) && $linkObj->exists() ) {
+ if ( is_object( $linkObj ) && $linkObj->isKnown() ) {
$getlink = Linker::linkKnown(
SpecialPage::getTitleFor( 'MyLanguage', $growinglink ),
htmlspecialchars( $display )
@@ -841,10 +1144,9 @@ class PageTranslationHooks {
$c++;
if ( $c > 1 ) {
- $subpages .= wfMessage( 'pipe-separator' )->plain();
+ $subpages .= $lang->getDirMarkEntity() . $skin->msg( 'pipe-separator' )->escaped();
} else {
- // This one is stupid imho, doesn't work with chihuahua
- // $subpages .= '&lt; ';
+ $subpages .= '&lt; ';
}
$subpages .= $getlink;
@@ -863,34 +1165,13 @@ class PageTranslationHooks {
return true;
}
- /// Hook: SpecialTranslate::executeTask
- public static function sourceExport( RequestContext $context,
- TranslateTask $task = null, MessageGroup $group, array $options
- ) {
- if ( $task || $options['taction'] !== 'export'
- || !$group instanceof WikiPageMessageGroup
- ) {
- return true;
- }
-
- $page = TranslatablePage::newFromTitle( $group->getTitle() );
- $collection = $group->initCollection( $options['language'] );
- $collection->loadTranslations( DB_MASTER );
- $text = $page->getParse()->getTranslationPageText( $collection );
- $display = $page->getPageDisplayTitle( $options['language'] );
- if ( $display ) {
- $text = "{{DISPLAYTITLE:$display}}$text";
- }
- $output = Html::element( 'textarea', array( 'rows' => 25 ), $text );
- $context->getOutput()->addHTML( $output );
-
- return false;
- }
-
/**
* Converts the edit tab (if exists) for translation pages to translate tab.
* Hook: SkinTemplateNavigation
* @since 2013.06
+ * @param Skin $skin
+ * @param array &$tabs
+ * @return true
*/
public static function translateTab( Skin $skin, array &$tabs ) {
$title = $skin->getTitle();
@@ -917,17 +1198,26 @@ class PageTranslationHooks {
* Hook to update source and destination translation pages on moving translation units
* Hook: TitleMoveComplete
* @since 2014.08
+ * @param Title $ot
+ * @param Title $nt
+ * @param User $user
+ * @param int $oldid
+ * @param int $newid
+ * @param string $reason
*/
public static function onMoveTranslationUnits( Title $ot, Title $nt, User $user,
$oldid, $newid, $reason
) {
- // Do the update only once. In case running by job queue, the update is not done here
- if ( self::$jobQueueRunning ) {
+ // TranslatablePageMoveJob takes care of handling updates because it performs
+ // a lot of moves at once. As a performance optimization, skip this hook if
+ // we detect moves from that job. As there isn't a good way to pass information
+ // to this hook what originated the move, we use some heuristics.
+ if ( defined( 'MEDIAWIKI_JOB_RUNNER' ) && $user->equals( FuzzyBot::getUser() ) ) {
return;
}
$groupLast = null;
- foreach ( array( $ot, $nt ) as $title ) {
+ foreach ( [ $ot, $nt ] as $title ) {
$handle = new MessageHandle( $title );
if ( !$handle->isValid() ) {
continue;
@@ -959,4 +1249,79 @@ class PageTranslationHooks {
}
}
}
+
+ /**
+ * Hook to update translation page on deleting a translation unit
+ * Hook: ArticleDeleteComplete
+ * @since 2016.05
+ * @param WikiPage &$unit
+ * @param User &$user
+ * @param string $reason
+ * @param int $id
+ * @param Content $content
+ * @param ManualLogEntry $logEntry
+ */
+ public static function onDeleteTranslationUnit( WikiPage &$unit, User &$user, $reason,
+ $id, $content, $logEntry
+ ) {
+ // Do the update. In case job queue is doing the work, the update is not done here
+ if ( self::$jobQueueRunning ) {
+ return;
+ }
+ $title = $unit->getTitle();
+
+ $handle = new MessageHandle( $title );
+ if ( !$handle->isValid() ) {
+ return;
+ }
+
+ $group = $handle->getGroup();
+ if ( !$group instanceof WikiPageMessageGroup ) {
+ return;
+ }
+
+ // There could be interfaces which may allow mass deletion (eg. Nuke). Since they could
+ // delete many units in one request, it may do several unnecessary edits and cause several
+ // other unnecessary updates to be done slowing down the user. To avoid that, we push this
+ // to a queue that is run after the current transaction is committed so that we can see the
+ // version that is after all the deletions has been done. This allows us to do just one edit
+ // per translation page after the current deletions has been done. This is sort of hackish
+ // but this is better user experience and is also more efficent.
+ static $queuedPages = [];
+ $target = $group->getTitle();
+ $langCode = $handle->getCode();
+ $targetPage = $target->getSubpage( $langCode )->getPrefixedText();
+
+ if ( !isset( $queuedPages[ $targetPage ] ) ) {
+ $queuedPages[ $targetPage ] = true;
+ $fname = __METHOD__;
+
+ $dbw = wfGetDB( DB_MASTER );
+ $dbw->onTransactionIdle( function () use ( $dbw, $queuedPages, $targetPage,
+ $target, $handle, $langCode, $user, $reason, $fname
+ ) {
+ $dbw->startAtomic( $fname );
+
+ $page = TranslatablePage::newFromTitle( $target );
+
+ MessageGroupStats::forItem(
+ $page->getMessageGroupId(),
+ $langCode,
+ MessageGroupStats::FLAG_NO_CACHE
+ );
+
+ if ( !$handle->isDoc() ) {
+ // Assume that $user and $reason for the first deletion is the same for all
+ self::updateTranslationPage( $page, $langCode, $user, 0, $reason );
+ }
+
+ // If a unit was deleted after the edit here is done, this allows us
+ // to add the page back to the queue again and so we can make another
+ // edit here with the latest changes.
+ unset( $queuedPages[ $targetPage ] );
+
+ $dbw->endAtomic( $fname );
+ } );
+ }
+ }
}