diff options
Diffstat (limited to 'www/wiki/extensions/Translate/utils/MessageTable.php')
-rw-r--r-- | www/wiki/extensions/Translate/utils/MessageTable.php | 424 |
1 files changed, 424 insertions, 0 deletions
diff --git a/www/wiki/extensions/Translate/utils/MessageTable.php b/www/wiki/extensions/Translate/utils/MessageTable.php new file mode 100644 index 00000000..43eac575 --- /dev/null +++ b/www/wiki/extensions/Translate/utils/MessageTable.php @@ -0,0 +1,424 @@ +<?php +/** + * Contains classes to build tables for MessageCollection objects. + * + * @file + * @author Niklas Laxström + * @license GPL-2.0+ + */ + +/** + * Pretty formatter for MessageCollection objects. + */ +class MessageTable { + /* + * @var bool + */ + protected $reviewMode = false; + + /** + * @var MessageCollection + */ + protected $collection; + + /** + * @var MessageGroup + */ + protected $group; + + /** + * @var IContextSource + */ + protected $context; + + /** + * @var array + */ + protected $headers = array( + 'table' => array( 'msg', 'allmessagesname' ), + 'current' => array( 'msg', 'allmessagescurrent' ), + 'default' => array( 'msg', 'allmessagesdefault' ), + ); + + /** + * Use this rather than the constructor directly + * to allow alternative implementations. + * + * @since 2012-11-29 + * @param IContextSource $context + * @param MessageCollection $collection + * @param MessageGroup $group + * @return MessageTable + */ + public static function newFromContext( + IContextSource $context, + MessageCollection $collection, + MessageGroup $group + ) { + $table = new self( $collection, $group ); + $table->setContext( $context ); + + Hooks::run( 'TranslateMessageTableInit', array( &$table, $context, $collection, $group ) ); + + return $table; + } + + public function setContext( IContextSource $context ) { + $this->context = $context; + } + + /** + * Use the newFromContext() function rather than the constructor directly + * to construct the object to allow alternative implementations. + * + * @param MessageCollection $collection + * @param MessageGroup $group + */ + public function __construct( MessageCollection $collection, MessageGroup $group ) { + $this->collection = $collection; + $this->group = $group; + $this->setHeaderText( 'table', $group->getLabel() ); + } + + public function setReviewMode( $mode = true ) { + $this->reviewMode = $mode; + } + + public function setHeaderTextMessage( $type, $value ) { + if ( !isset( $this->headers[$type] ) ) { + throw new MWException( "Unexpected type $type" ); + } + + $this->headers[$type] = array( 'msg', $value ); + } + + public function setHeaderText( $type, $value ) { + if ( !isset( $this->headers[$type] ) ) { + throw new MWException( "Unexpected type $type" ); + } + + $this->headers[$type] = array( 'raw', htmlspecialchars( $value ) ); + } + + public function includeAssets() { + TranslationHelpers::addModules( $this->context->getOutput() ); + $pages = array(); + + foreach ( $this->collection->getTitles() as $title ) { + $pages[] = $title->getPrefixedDBkey(); + } + + $vars = array( 'trlKeys' => $pages ); + $this->context->getOutput()->addScript( Skin::makeVariablesScript( $vars ) ); + } + + public function header() { + $tableheader = Xml::openElement( 'table', array( + 'class' => 'mw-sp-translate-table' + ) ); + + if ( $this->reviewMode ) { + $tableheader .= Xml::openElement( 'tr' ); + $tableheader .= Xml::element( 'th', + array( 'rowspan' => '2' ), + $this->headerText( 'table' ) + ); + $tableheader .= Xml::tags( 'th', null, $this->headerText( 'default' ) ); + $tableheader .= Xml::closeElement( 'tr' ); + + $tableheader .= Xml::openElement( 'tr' ); + $tableheader .= Xml::tags( 'th', null, $this->headerText( 'current' ) ); + $tableheader .= Xml::closeElement( 'tr' ); + } else { + $tableheader .= Xml::openElement( 'tr' ); + $tableheader .= Xml::tags( 'th', null, $this->headerText( 'table' ) ); + $tableheader .= Xml::tags( 'th', null, $this->headerText( 'current' ) ); + $tableheader .= Xml::closeElement( 'tr' ); + } + + return $tableheader . "\n"; + } + + public function contents() { + $optional = $this->context->msg( 'translate-optional' )->escaped(); + + $this->doLinkBatch(); + + $sourceLang = Language::factory( $this->group->getSourceLanguage() ); + $targetLang = Language::factory( $this->collection->getLanguage() ); + $titleMap = $this->collection->keys(); + + $output = ''; + + $this->collection->initMessages(); // Just to be sure + + /** + * @var TMessage $m + */ + foreach ( $this->collection as $key => $m ) { + $tools = array(); + /** + * @var Title $title + */ + $title = $titleMap[$key]; + + $original = $m->definition(); + $translation = $m->translation(); + + $hasTranslation = $translation !== null; + + if ( $hasTranslation ) { + $message = $translation; + $extraAttribs = self::getLanguageAttributes( $targetLang ); + } else { + $message = $original; + $extraAttribs = self::getLanguageAttributes( $sourceLang ); + } + + Hooks::run( + 'TranslateFormatMessageBeforeTable', + array( &$message, $m, $this->group, $targetLang, &$extraAttribs ) + ); + + // Using Html::element( a ) because Linker::link is memory hog. + // It takes about 20 KiB per call, and that times 5000 is quite + // a lot of memory. + $niceTitle = htmlspecialchars( $this->context->getLanguage()->truncate( + $title->getPrefixedText(), + -35 + ) ); + $linkAttribs = array( + 'href' => $title->getLocalURL( array( 'action' => 'edit' ) ), + ); + $linkAttribs += TranslationEditPage::jsEdit( $title, $this->group->getId() ); + + $tools['edit'] = Html::element( 'a', $linkAttribs, $niceTitle ); + + $anchor = 'msg_' . $key; + $anchor = Xml::element( 'a', array( 'id' => $anchor, 'href' => "#$anchor" ), '↓' ); + + $extra = ''; + if ( $m->hasTag( 'optional' ) ) { + $extra = '<br />' . $optional; + } + + $tqeData = $extraAttribs + array( + 'data-title' => $title->getPrefixedText(), + 'data-group' => $this->group->getId(), + 'id' => 'tqe-anchor-' . substr( sha1( $title->getPrefixedText() ), 0, 12 ), + 'class' => 'tqe-inlineeditable ' . ( $hasTranslation ? 'translated' : 'untranslated' ) + ); + + $button = $this->getReviewButton( $m ); + $status = $this->getReviewStatus( $m ); + $leftColumn = $button . $anchor . $tools['edit'] . $extra . $status; + + if ( $this->reviewMode ) { + $output .= Xml::tags( 'tr', array( 'class' => 'orig' ), + Xml::tags( 'td', array( 'rowspan' => '2' ), $leftColumn ) . + Xml::tags( 'td', self::getLanguageAttributes( $sourceLang ), + TranslateUtils::convertWhiteSpaceToHTML( $original ) + ) + ); + + $output .= Xml::tags( 'tr', null, + Xml::tags( 'td', $tqeData, TranslateUtils::convertWhiteSpaceToHTML( $message ) ) + ); + } else { + $output .= Xml::tags( 'tr', array( 'class' => 'def' ), + Xml::tags( 'td', null, $leftColumn ) . + Xml::tags( 'td', $tqeData, TranslateUtils::convertWhiteSpaceToHTML( $message ) ) + ); + } + + $output .= "\n"; + } + + return $output; + } + + public function fullTable( $offsets, $nondefaults ) { + $this->includeAssets(); + + $content = $this->header() . $this->contents() . '</table>'; + $pager = $this->doStupidLinks( $offsets, $nondefaults ); + + if ( $offsets['count'] === 0 ) { + return $pager; + } elseif ( $offsets['count'] === $offsets['total'] ) { + return $content . $pager; + } else { + return $pager . $content . $pager; + } + } + + protected function headerText( $type ) { + if ( !isset( $this->headers[$type] ) ) { + throw new MWException( "Unexpected type $type" ); + } + + list( $format, $value ) = $this->headers[$type]; + if ( $format === 'msg' ) { + return wfMessage( $value )->escaped(); + } elseif ( $format === 'raw' ) { + return $value; + } else { + throw new MWException( "Unexcepted format $format" ); + } + } + + protected static function getLanguageAttributes( Language $language ) { + global $wgTranslateDocumentationLanguageCode; + + $code = $language->getHtmlCode(); + $dir = $language->getDir(); + + if ( $language->getCode() === $wgTranslateDocumentationLanguageCode ) { + // Should be good enough for now + $code = 'en'; + } + + return array( 'lang' => $code, 'dir' => $dir ); + } + + protected function getReviewButton( TMessage $message ) { + $revision = $message->getProperty( 'revision' ); + $user = $this->context->getUser(); + + if ( !$this->reviewMode || !$user->isAllowed( 'translate-messagereview' ) || !$revision ) { + return ''; + } + + $attribs = array( + 'type' => 'button', + 'class' => 'mw-translate-messagereviewbutton', + 'data-revision' => $revision, + 'name' => 'acceptbutton-' . $revision, // Otherwise Firefox disables buttons on page load + ); + + $reviewers = (array)$message->getProperty( 'reviewers' ); + if ( in_array( $user->getId(), $reviewers ) ) { + $attribs['value'] = wfMessage( 'translate-messagereview-done' )->text(); + $attribs['disabled'] = 'disabled'; + $attribs['title'] = wfMessage( 'translate-messagereview-doit' )->text(); + } elseif ( $message->hasTag( 'fuzzy' ) ) { + $attribs['value'] = wfMessage( 'translate-messagereview-submit' )->text(); + $attribs['disabled'] = 'disabled'; + $attribs['title'] = wfMessage( 'translate-messagereview-no-fuzzy' )->text(); + } elseif ( $user->getName() === $message->getProperty( 'last-translator-text' ) ) { + $attribs['value'] = wfMessage( 'translate-messagereview-submit' )->text(); + $attribs['disabled'] = 'disabled'; + $attribs['title'] = wfMessage( 'translate-messagereview-no-own' )->text(); + } else { + $attribs['value'] = wfMessage( 'translate-messagereview-submit' )->text(); + } + + $review = Html::element( 'input', $attribs ); + + return $review; + } + + /// For optimization + protected $reviewStatusCache = array(); + + protected function getReviewStatus( TMessage $message ) { + if ( !$this->reviewMode ) { + return ''; + } + + $reviewers = (array)$message->getProperty( 'reviewers' ); + $count = count( $reviewers ); + + if ( $count === 0 ) { + return ''; + } + + $userId = $this->context->getUser()->getId(); + $you = in_array( $userId, $reviewers ); + $key = $you ? "y$count" : "n$count"; + + // ->text() (and ->parse()) invokes the parser. Each call takes + // about 70 KiB, so it makes sense to cache these messages which + // have high repetition. + if ( isset( $this->reviewStatusCache[$key] ) ) { + return $this->reviewStatusCache[$key]; + } elseif ( $you ) { + $msg = wfMessage( 'translate-messagereview-reviewswithyou' )->numParams( $count )->text(); + } else { + $msg = wfMessage( 'translate-messagereview-reviews' )->numParams( $count )->text(); + } + + $wrap = Html::rawElement( 'div', array( 'class' => 'mw-translate-messagereviewstatus' ), $msg ); + $this->reviewStatusCache[$key] = $wrap; + + return $wrap; + } + + protected function doLinkBatch() { + $batch = new LinkBatch(); + $batch->setCaller( __METHOD__ ); + + foreach ( $this->collection->getTitles() as $title ) { + $batch->addObj( $title ); + } + + $batch->execute(); + } + + protected function doStupidLinks( $info, $nondefaults ) { + // Total number of messages for this query + $total = $info['total']; + // Messages in this page + $count = $info['count']; + + $allInThisPage = $info['start'] === 0 && $total === $count; + + if ( $info['count'] === 0 ) { + $navigation = wfMessage( 'translate-page-showing-none' )->parse(); + } elseif ( $allInThisPage ) { + $navigation = wfMessage( 'translate-page-showing-all' )->numParams( $total )->parse(); + } else { + $previous = wfMessage( 'translate-prev' )->escaped(); + + if ( $info['backwardsOffset'] !== false ) { + $previous = $this->makeOffsetLink( $previous, $info['backwardsOffset'], $nondefaults ); + } + + $nextious = wfMessage( 'translate-next' )->escaped(); + if ( $info['forwardsOffset'] !== false ) { + $nextious = $this->makeOffsetLink( $nextious, $info['forwardsOffset'], $nondefaults ); + } + + $start = $info['start'] + 1; + $stop = $start + $info['count'] - 1; + $total = $info['total']; + + $navigation = wfMessage( 'translate-page-showing' ) + ->numParams( $start, $stop, $total )->parse(); + $navigation .= ' '; + $navigation .= wfMessage( 'translate-page-paging-links' ) + ->rawParams( $previous, $nextious )->escaped(); + } + + return Html::openElement( 'fieldset' ) . + Html::element( 'legend', array(), wfMessage( 'translate-page-navigation-legend' )->text() ) . + $navigation . + Html::closeElement( 'fieldset' ); + } + + protected function makeOffsetLink( $label, $offset, $nondefaults ) { + $query = array_merge( + $nondefaults, + array( 'offset' => $offset ) + ); + + $link = Linker::link( + $this->context->getTitle(), + $label, + array(), + $query + ); + + return $link; + } +} |