msg( 'managemessagegroups' )->text();
}
public function execute( $par ) {
$this->setHeaders();
$out = $this->getOutput();
$out->addModuleStyles( 'ext.translate.special.managegroups' );
$out->addHelpLink( 'Help:Extension:Translate/Group_management' );
$name = $par ?: MessageChangeStorage::DEFAULT_NAME;
$this->cdb = MessageChangeStorage::getCdbPath( $name );
if ( !MessageChangeStorage::isValidCdbName( $name ) || !file_exists( $this->cdb ) ) {
// @todo Tell them when changes was last checked/process
// or how to initiate recheck.
$out->addWikiMsg( 'translate-smg-nochanges' );
return;
}
$user = $this->getUser();
$allowed = $user->isAllowed( self::RIGHT );
$req = $this->getRequest();
if ( !$req->wasPosted() ) {
$this->showChanges( $allowed, $this->getLimit() );
return;
}
$token = $req->getVal( 'token' );
if ( !$allowed || !$user->matchEditToken( $token ) ) {
throw new PermissionsError( self::RIGHT );
}
$this->processSubmit();
}
/**
* How many changes can be shown per page.
* @return int
*/
protected function getLimit() {
$limits = [
1000, // Default max
ini_get( 'max_input_vars' ),
ini_get( 'suhosin.post.max_vars' ),
ini_get( 'suhosin.request.max_vars' )
];
// Ignore things not set
$limits = array_filter( $limits );
return min( $limits );
}
protected function getLegend() {
$text = $this->diff->addHeader(
'',
$this->msg( 'translate-smg-left' )->escaped(),
$this->msg( 'translate-smg-right' )->escaped()
);
return Html::rawElement( 'div', [ 'class' => 'mw-translate-smg-header' ], $text );
}
protected function showChanges( $allowed, $limit ) {
global $wgContLang;
$diff = new DifferenceEngine( $this->getContext() );
$diff->showDiffStyle();
$diff->setReducedLineNumbers();
$this->diff = $diff;
$out = $this->getOutput();
$out->addHTML(
'' .
Html::openElement( 'form', [ 'method' => 'post' ] ) .
Html::hidden( 'title', $this->getPageTitle()->getPrefixedText() ) .
Html::hidden( 'token', $this->getUser()->getEditToken() ) .
$this->getLegend()
);
// The above count as two
$limit = $limit - 2;
$reader = \Cdb\Reader::open( $this->cdb );
$groups = unserialize( $reader->get( '#keys' ) );
foreach ( $groups as $id ) {
$group = MessageGroups::getGroup( $id );
if ( !$group ) {
continue;
}
$changes = unserialize( $reader->get( $id ) );
$out->addHTML( Html::element( 'h2', [], $group->getLabel() ) );
// Reduce page existance queries to one per group
$lb = new LinkBatch();
$ns = $group->getNamespace();
$isCap = MWNamespace::isCapitalized( $ns );
foreach ( $changes as $code => $subchanges ) {
foreach ( $subchanges as $messages ) {
foreach ( $messages as $params ) {
// Constructing title objects is way slower
$key = $params['key'];
if ( $isCap ) {
$key = $wgContLang->ucfirst( $key );
}
$lb->add( $ns, "$key/$code" );
}
}
}
$lb->execute();
foreach ( $changes as $code => $subchanges ) {
foreach ( $subchanges as $type => $messages ) {
foreach ( $messages as $params ) {
$change = $this->formatChange( $group, $code, $type, $params, $limit );
$out->addHTML( $change );
if ( $limit <= 0 ) {
// We need to restrict the changes per page per form submission
// limitations as well as performance.
$out->wrapWikiMsg( "
\n$1\n
", 'translate-smg-more' );
break 4;
}
}
}
}
}
$attribs = [ 'type' => 'submit', 'class' => 'mw-translate-smg-submit' ];
if ( !$allowed ) {
$attribs['disabled'] = 'disabled';
$attribs['title'] = $this->msg( 'translate-smg-notallowed' )->text();
}
$button = Html::element( 'button', $attribs, $this->msg( 'translate-smg-submit' )->text() );
$out->addHTML( $button );
$out->addHTML( Html::closeElement( 'form' ) );
}
/**
* @param MessageGroup $group
* @param string $code
* @param string $type
* @param array $params
* @param int &$limit
* @return string HTML
*/
protected function formatChange( MessageGroup $group, $code, $type, $params, &$limit ) {
$key = $params['key'];
$title = Title::makeTitleSafe( $group->getNamespace(), "$key/$code" );
$id = self::changeId( $group->getId(), $code, $type, $key );
if ( $title && $type === 'addition' && $title->exists() ) {
// The message has for some reason dropped out from cache
// or perhaps it is being reused. In any case treat it
// as a change for display, so the admin can see if
// action is needed and let the message be processed.
// Otherwise it will end up in the postponed category
// forever and will prevent rebuilding the cache, which
// leads to many other annoying problems.
$type = 'change';
} elseif ( $title && ( $type === 'deletion' || $type === 'change' ) && !$title->exists() ) {
return '';
}
$text = '';
$titleLink = $this->getLinkRenderer()->makeLink( $title );
if ( $type === 'deletion' ) {
$wiki = ContentHandler::getContentText( Revision::newFromTitle( $title )->getContent() );
$oldContent = ContentHandler::makeContent( $wiki, $title );
$newContent = ContentHandler::makeContent( '', $title );
$this->diff->setContent( $oldContent, $newContent );
$text = $this->diff->getDiff( $titleLink, '' );
} elseif ( $type === 'addition' ) {
$oldContent = ContentHandler::makeContent( '', $title );
$newContent = ContentHandler::makeContent( $params['content'], $title );
$this->diff->setContent( $oldContent, $newContent );
$text = $this->diff->getDiff( '', $titleLink );
} elseif ( $type === 'change' ) {
$wiki = ContentHandler::getContentText( Revision::newFromTitle( $title )->getContent() );
$handle = new MessageHandle( $title );
if ( $handle->isFuzzy() ) {
$wiki = '!!FUZZY!!' . str_replace( TRANSLATE_FUZZY, '', $wiki );
}
$label = $this->msg( 'translate-manage-action-ignore' )->text();
$actions = Xml::checkLabel( $label, "i/$id", "i/$id" );
$limit--;
if ( $group->getSourceLanguage() === $code ) {
$label = $this->msg( 'translate-manage-action-fuzzy' )->text();
$actions .= ' ' . Xml::checkLabel( $label, "f/$id", "f/$id", true );
$limit--;
}
$oldContent = ContentHandler::makeContent( $wiki, $title );
$newContent = ContentHandler::makeContent( $params['content'], $title );
$this->diff->setContent( $oldContent, $newContent );
$text .= $this->diff->getDiff( $titleLink, $actions );
}
$hidden = Html::hidden( $id, 1 );
$limit--;
$text .= $hidden;
$classes = "mw-translate-smg-change smg-change-$type";
if ( $limit < 0 ) {
// Don't add if one of the fields might get dropped of at submission
return '';
}
return Html::rawElement( 'div', [ 'class' => $classes ], $text );
}
protected function processSubmit() {
$req = $this->getRequest();
$out = $this->getOutput();
$jobs = [];
$jobs[] = MessageIndexRebuildJob::newJob();
$reader = \Cdb\Reader::open( $this->cdb );
$groups = unserialize( $reader->get( '#keys' ) );
$postponed = [];
foreach ( $groups as $groupId ) {
$group = MessageGroups::getGroup( $groupId );
$changes = unserialize( $reader->get( $groupId ) );
foreach ( $changes as $code => $subchanges ) {
foreach ( $subchanges as $type => $messages ) {
foreach ( $messages as $index => $params ) {
$id = self::changeId( $groupId, $code, $type, $params['key'] );
if ( $req->getVal( $id ) === null ) {
// We probably hit the limit with number of post parameters.
$postponed[$groupId][$code][$type][$index] = $params;
continue;
}
if ( $type === 'deletion' || $req->getCheck( "i/$id" ) ) {
continue;
}
$fuzzy = $req->getCheck( "f/$id" ) ? 'fuzzy' : false;
$key = $params['key'];
$title = Title::makeTitleSafe( $group->getNamespace(), "$key/$code" );
$jobs[] = MessageUpdateJob::newJob( $title, $params['content'], $fuzzy );
}
}
if ( !isset( $postponed[$groupId][$code] ) ) {
$cache = new MessageGroupCache( $groupId, $code );
$cache->create();
}
}
}
JobQueueGroup::singleton()->push( $jobs );
$reader->close();
rename( $this->cdb, $this->cdb . '-' . wfTimestamp() );
if ( count( $postponed ) ) {
MessageChangeStorage::writeChanges( $postponed, $this->cdb );
$this->showChanges( true, $this->getLimit() );
} else {
$out->addWikiMsg( 'translate-smg-submitted' );
}
}
protected static function changeId( $groupId, $code, $type, $key ) {
return 'smg/' . substr( sha1( "$groupId/$code/$type/$key" ), 0, 7 );
}
/**
* Adds the task-based tabs on Special:Translate and few other special pages.
* Hook: SkinTemplateNavigation::SpecialPage
* @since 2012-05-14
* @param Skin $skin
* @param array &$tabs
* @return true
*/
public static function tabify( Skin $skin, array &$tabs ) {
$title = $skin->getTitle();
list( $alias, ) = TranslateUtils::resolveSpecialPageAlias( $title->getText() );
$pagesInGroup = [
'ManageMessageGroups' => 'namespaces',
'AggregateGroups' => 'namespaces',
'SupportedLanguages' => 'views',
'TranslationStats' => 'views',
];
if ( !isset( $pagesInGroup[$alias] ) ) {
return true;
}
$skin->getOutput()->addModuleStyles( 'ext.translate.tabgroup' );
$tabs['namespaces'] = [];
foreach ( $pagesInGroup as $spName => $section ) {
$spClass = TranslateUtils::getSpecialPage( $spName );
// DisabledSpecialPage was added in MW 1.33
if ( $spClass === null || $spClass instanceof DisabledSpecialPage ) {
continue; // Page explicitly disabled
}
$spTitle = $spClass->getPageTitle();
$tabs[$section][strtolower( $spName )] = [
'text' => $spClass->getDescription(),
'href' => $spTitle->getLocalURL(),
'class' => $alias === $spName ? 'selected' : '',
];
}
return true;
}
}