public function execute()
 {
     $params = $this->extractRequestParams();
     $title = Title::newFromText($params['title']);
     if (!$title) {
         $this->dieUsage('Invalid title', 'invalidtitle');
     }
     $handle = new MessageHandle($title);
     if (!$handle->isValid()) {
         $this->dieUsage('Title does not correspond to a translatable message', 'nomessagefortitle');
     }
     if (strval($params['group']) !== '') {
         $group = MessageGroups::getGroup($params['group']);
     } else {
         $group = $handle->getGroup();
     }
     if (!$group) {
         $this->dieUsage('Invalid group', 'invalidgroup');
     }
     $data = array();
     $times = array();
     $props = $params['prop'];
     $aggregator = new QueryAggregator();
     // Figure out the intersection of supported and requested aids
     $types = $group->getTranslationAids();
     $props = array_intersect($props, array_keys($types));
     $result = $this->getResult();
     // Create list of aids, populate web services queries
     $aids = array();
     foreach ($props as $type) {
         $class = $types[$type];
         $obj = new $class($group, $handle, $this);
         if ($obj instanceof QueryAggregatorAware) {
             $obj->setQueryAggregator($aggregator);
             $obj->populateQueries();
         }
         $aids[$type] = $obj;
     }
     // Execute all web service queries asynchronously to save time
     $start = microtime(true);
     $aggregator->run();
     $times['query_aggregator'] = round(microtime(true) - $start, 3);
     // Construct the result data structure
     foreach ($aids as $type => $obj) {
         $start = microtime(true);
         try {
             $aid = $obj->getData();
         } catch (TranslationHelperException $e) {
             $aid = array('error' => $e->getMessage());
         }
         if (isset($aid['**'])) {
             $result->setIndexedTagName($aid, $aid['**']);
             unset($aid['**']);
         }
         $data[$type] = $aid;
         $times[$type] = round(microtime(true) - $start, 3);
     }
     $result->addValue(null, 'helpers', $data);
     $result->addValue(null, 'times', $times);
 }
	/**
	 * Shovels the new translation into translation memory.
	 * Hook: Translate:newTranslation
	 *
	 * @param $handle MessageHandle
	 * @param $revision
	 * @param $text string
	 * @param $user User
	 *
	 * @return bool
	 */
	public static function update( MessageHandle $handle, $revision, $text, User $user ) {
		global $wgContLang;

		$dbw = self::getDatabaseHandle();
		// Not in use or misconfigured
		if ( $dbw === null ) {
			return true;
		}

		// Skip definitions to not slow down mass imports etc.
		// These will be added when first translation is made
		if ( $handle->getCode() === 'en' ) {
			return true;
		}

		$group = $handle->getGroup();
		$key = $handle->getKey();
		$code = $handle->getCode();
		$ns_text = $wgContLang->getNsText( $group->getNamespace() );
		$definition = $group->getMessage( $key, 'en' );
		if ( !is_string( $definition ) || !strlen( $definition ) ) {
			wfDebugLog( 'tmserver', "Unable to get definition for $ns_text:$key/$code" );
			return true;
		}

		$tmDefinition = array(
			'text' => $definition,
			'context' => "$ns_text:$key",
			'length' => strlen( $definition ),
			'lang' => 'en'
		);

		// Check that the definition exists, add it if not
		$source_id = $dbw->selectField( '`sources`', 'sid', $tmDefinition, __METHOD__ );
		if ( $source_id === false ) {
			$dbw->insert( '`sources`', $tmDefinition, __METHOD__ );
			$source_id = $dbw->insertId();
			wfDebugLog( 'tmserver', "Inserted new tm-definition for $ns_text:$key:\n$definition\n----------" );
		}

		$delete = array(
			'sid' => $source_id,
			'lang' => $code,
		);

		$insert = $delete + array(
			'text' => $text,
			'time' => wfTimestamp(),
		);

		// Purge old translations for this message
		$dbw->delete( '`targets`', $delete, __METHOD__ );
		// We only do SQlite which does not need to know unique indexes
		$dbw->replace( '`targets`', null, $insert, __METHOD__ );
		wfDebugLog( 'tmserver', "Inserted new tm-translation for $ns_text:$key/$code" );

		return true;
	}
 public function getCheckBox()
 {
     $this->mustBeKnownMessage();
     global $wgTranslateDocumentationLanguageCode;
     $context = RequestContext::getMain();
     $title = $context->getOutput()->getTitle();
     list($alias, ) = SpecialPageFactory::resolveAlias($title->getText());
     $tux = SpecialTranslate::isBeta($context->getRequest()) && $title->isSpecialPage() && $alias === 'Translate';
     $formattedChecks = $tux ? FormatJson::encode(array()) : Html::element('div', array('class' => 'mw-translate-messagechecks'));
     $page = $this->handle->getKey();
     $translation = $this->getTranslation();
     $code = $this->handle->getCode();
     $en = $this->getDefinition();
     if (strval($translation) === '') {
         return $formattedChecks;
     }
     if ($code === $wgTranslateDocumentationLanguageCode) {
         return $formattedChecks;
     }
     // We need to get the primary group of the message. It may differ from
     // the supplied group (aggregate groups, dynamic groups).
     $checker = $this->handle->getGroup()->getChecker();
     if (!$checker) {
         return $formattedChecks;
     }
     $message = new FatMessage($page, $en);
     // Take the contents from edit field as a translation
     $message->setTranslation($translation);
     $checks = $checker->checkMessage($message, $code);
     if (!count($checks)) {
         return $formattedChecks;
     }
     $checkMessages = array();
     foreach ($checks as $checkParams) {
         $key = array_shift($checkParams);
         $checkMessages[] = $context->msg($key, $checkParams)->parse();
     }
     if ($tux) {
         $formattedChecks = FormatJson::encode($checkMessages);
     } else {
         $formattedChecks = Html::rawElement('div', array('class' => 'mw-translate-messagechecks'), TranslateUtils::fieldset($context->msg('translate-edit-warnings')->escaped(), implode('<hr />', $checkMessages), array('class' => 'mw-sp-translate-edit-warnings')));
     }
     return $formattedChecks;
 }
예제 #4
0
 protected function formatGettextComments()
 {
     $this->mustBeKnownMessage();
     // We need to get the primary group to get the correct file
     // So $group can be different from $this->group
     $group = $this->handle->getGroup();
     if (!$group instanceof FileBasedMessageGroup) {
         return '';
     }
     $ffs = $group->getFFS();
     if ($ffs instanceof GettextFFS) {
         global $wgContLang;
         $mykey = $wgContLang->lcfirst($this->handle->getKey());
         $data = $ffs->read($this->group->getSourceLanguage());
         $help = $data['TEMPLATE'][$mykey]['comments'];
         // Do not display an empty comment. That's no help and takes up unnecessary space.
         $conf = $this->group->getConfiguration();
         if (isset($conf['BASIC']['codeBrowser'])) {
             $out = '';
             $pattern = $conf['BASIC']['codeBrowser'];
             $pattern = str_replace('%FILE%', '\\1', $pattern);
             $pattern = str_replace('%LINE%', '\\2', $pattern);
             $pattern = "[{$pattern} \\1:\\2]";
             foreach ($help as $type => $lines) {
                 if ($type === ':') {
                     $files = '';
                     foreach ($lines as $line) {
                         $files .= ' ' . preg_replace('/([^ :]+):(\\d+)/', $pattern, $line);
                     }
                     $out .= "<nowiki>#:</nowiki> {$files}<br />";
                 } else {
                     foreach ($lines as $line) {
                         $out .= "<nowiki>#{$type}</nowiki> {$line}<br />";
                     }
                 }
             }
             return "{$out}";
         }
     }
     return '';
 }
 public function update(MessageHandle $handle, $targetText)
 {
     if (!$handle->isValid() || $handle->getCode() === '') {
         return false;
     }
     $mkey = $handle->getKey();
     $group = $handle->getGroup();
     $targetLanguage = $handle->getCode();
     $sourceLanguage = $group->getSourceLanguage();
     // Skip definitions to not slow down mass imports etc.
     // These will be added when the first translation is made
     if ($targetLanguage === $sourceLanguage) {
         return false;
     }
     $definition = $group->getMessage($mkey, $sourceLanguage);
     if (!is_string($definition) || !strlen(trim($definition))) {
         return false;
     }
     $context = Title::makeTitle($handle->getTitle()->getNamespace(), $mkey);
     $dbw = $this->getDB(DB_MASTER);
     /* Check that the definition exists and fetch the sid. If not, add
      * the definition and retrieve the sid. If the definition changes,
      * we will create a new entry - otherwise we could at some point
      * get suggestions which do not match the original definition any
      * longer. The old translations are still kept until purged by
      * rerunning the bootstrap script. */
     $conds = array('tms_context' => $context->getPrefixedText(), 'tms_text' => $definition);
     $sid = $dbw->selectField('translate_tms', 'tms_sid', $conds, __METHOD__);
     if ($sid === false) {
         $sid = $this->insertSource($context, $sourceLanguage, $definition);
     }
     // Delete old translations for this message if any. Could also use replace
     $deleteConds = array('tmt_sid' => $sid, 'tmt_lang' => $targetLanguage);
     $dbw->delete('translate_tmt', $deleteConds, __METHOD__);
     // Insert the new translation
     if ($targetText !== null) {
         $row = $deleteConds + array('tmt_text' => $targetText);
         $dbw->insert('translate_tmt', $row, __METHOD__);
     }
     return true;
 }
예제 #6
0
 public function getDefinitions()
 {
     global $wgTranslateMessageNamespaces;
     $db = wfGetDB(DB_SLAVE);
     $tables = 'recentchanges';
     $fields = array('rc_namespace', 'rc_title');
     $conds = array('rc_title ' . $db->buildLike($db->anyString(), '/' . $this->language), 'rc_namespace' => $wgTranslateMessageNamespaces, 'rc_type != ' . RC_LOG, 'rc_id > ' . $this->getRCCutoff());
     $options = array('ORDER BY' => 'rc_id DESC', 'LIMIT' => 1000);
     $res = $db->select($tables, $fields, $conds, __METHOD__, $options);
     $defs = array();
     foreach ($res as $row) {
         $title = Title::makeTitle($row->rc_namespace, $row->rc_title);
         $handle = new MessageHandle($title);
         if (!$handle->isValid()) {
             continue;
         }
         $mkey = $row->rc_namespace . ':' . $handle->getKey();
         if (!isset($defs[$mkey])) {
             $group = $handle->getGroup();
             $defs[$mkey] = $group->getMessage($handle->getKey(), $this->getSourceLanguage());
         }
     }
     return $defs;
 }
 /**
  * Hook to update source and destination translation pages on moving translation units
  * Hook: TitleMoveComplete
  * @since 2014.08
  */
 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) {
         return;
     }
     $groupLast = null;
     foreach (array($ot, $nt) as $title) {
         $handle = new MessageHandle($title);
         if (!$handle->isValid()) {
             continue;
         }
         // Documentation pages are never translation pages
         if ($handle->isDoc()) {
             continue;
         }
         $group = $handle->getGroup();
         if (!$group instanceof WikiPageMessageGroup) {
             continue;
         }
         $language = $handle->getCode();
         // Ignore pages such as Translations:Page/unit without language code
         if (strval($code) === '') {
             continue;
         }
         // Update the page only once if source and destination units
         // belong to the same page
         if ($group !== $groupLast) {
             $groupLast = $group;
             $page = TranslatablePage::newFromTitle($group->getTitle());
             self::updateTranslationPage($page, $language, $user, 0, $reason);
         }
     }
 }
 /**
  * Adds tag which identifies the revision of source message at that time.
  * This is used to show diff against current version of source message
  * when updating a translation.
  * Hook: Translate:newTranslation
  * @param MessageHandle $handle
  * @param int $revision
  * @param string $text
  * @param User $user
  * @return bool
  */
 public static function updateTransverTag(MessageHandle $handle, $revision, $text, User $user)
 {
     if ($user->isAllowed('bot')) {
         return false;
     }
     $group = $handle->getGroup();
     $title = $handle->getTitle();
     $name = $handle->getKey() . '/' . $group->getSourceLanguage();
     $definitionTitle = Title::makeTitleSafe($title->getNamespace(), $name);
     if (!$definitionTitle || !$definitionTitle->exists()) {
         return true;
     }
     $definitionRevision = $definitionTitle->getLatestRevID();
     $dbw = wfGetDB(DB_MASTER);
     $conds = array('rt_page' => $title->getArticleID(), 'rt_type' => RevTag::getType('tp:transver'), 'rt_revision' => $revision, 'rt_value' => $definitionRevision);
     $index = array('rt_type', 'rt_page', 'rt_revision');
     $dbw->replace('revtag', array($index), $conds, __METHOD__);
     return true;
 }
 public function update(MessageHandle $handle, $targetText)
 {
     if ($handle->getCode() === '') {
         return false;
     }
     /* There are various different cases here:
      * [new or updated] [fuzzy|non-fuzzy] [translation|definition]
      * 1) We don't distinguish between new or updated here.
      * 2) Delete old translation, but not definition
      * 3) Insert new translation or definition, if non-fuzzy
      * The definition should never be fuzzied anyway.
      *
      * These only apply to known messages.
      */
     $update = $this->client->createUpdate();
     $title = $handle->getTitle();
     $doDelete = true;
     $sourceLanguage = '';
     if ($handle->isValid()) {
         $sourceLanguage = $handle->getGroup()->getSourceLanguage();
         if ($handle->getCode() === $sourceLanguage) {
             $doDelete = false;
         }
     }
     if ($doDelete) {
         $base = Title::makeTitle($title->getNamespace(), $handle->getKey());
         $conds = array('wiki' => wfWikiId(), 'language' => $handle->getCode(), 'messageid' => $base->getPrefixedText());
         foreach ($conds as $key => &$value) {
             $value = "{$key}:" . $update->getHelper()->escapePhrase($value);
         }
         $update->addDeleteQuery(implode(' AND ', $conds));
     }
     if ($targetText !== null) {
         if ($handle->isValid()) {
             // Of the message definition page
             $targetTitle = $handle->getTitle();
             $sourceTitle = Title::makeTitle($targetTitle->getNamespace(), $handle->getKey() . '/' . $sourceLanguage);
             $revId = intval($sourceTitle->getLatestRevID());
             /* Note: in some cases the source page might not exist, in this case
              * we use 0 as message version identifier, to differentiate them from
              * orphan messages */
         } else {
             $revId = 'orphan';
         }
         $doc = $this->createDocument($handle, $targetText, $revId);
         // Add document and commit within X seconds.
         $update->addDocument($doc, null, self::COMMIT_WITHIN);
     }
     try {
         $this->client->update($update);
     } catch (Solarium_Exception $e) {
         error_log("SolrTTMServer update-write failed");
         return false;
     }
     return true;
 }
	/**
	 * Parser function hook
	 */
	public static function translationDialogMagicWord( Parser $parser, $title = '', $linktext = '' ) {
		$title = Title::newFromText( $title );
		if ( !$title ) return '';
		$handle = new MessageHandle( $title );
		if ( !$handle->isValid() ) return '';
		$group = $handle->getGroup();
		$callParams = array( $title->getPrefixedText(), $group->getId() );
		$call = Xml::encodeJsCall( 'mw.translate.openDialog', $callParams );
		$js = <<<JAVASCRIPT
mw.loader.using( 'ext.translate.quickedit', function() { $call; } ); return false;
JAVASCRIPT;

		$a = array(
			'href' => $title->getFullUrl( array( 'action' => 'edit' ) ),
			'onclick' => $js,
		);

		if ( $linktext === '' ) {
			$linktext = wfMessage( 'translate-edit-jsopen' )->text();
		}
		$output = Html::element( 'a', $a, $linktext );
		return $parser->insertStripItem( $output, $parser->mStripState );
	}
예제 #11
0
 public static function onSectionSave($article, User $user, $text, $summary, $minor, $_, $_, $flags, $revision)
 {
     $title = $article->getTitle();
     // Some checks
     $handle = new MessageHandle($title);
     // We are only interested in the translations namespace
     if (!$handle->isPageTranslation() || !$handle->isValid()) {
         return true;
     }
     // Do not trigger renders for fuzzy
     if (strpos($text, TRANSLATE_FUZZY) !== false) {
         return true;
     }
     $group = $handle->getGroup();
     if (!$group instanceof WikiPageMessageGroup) {
         return true;
     }
     // Finally we know the title and can construct a Translatable page
     $page = TranslatablePage::newFromTitle($group->getTitle());
     // Add a tracking mark
     if ($revision !== null) {
         self::addSectionTag($title, $revision->getId(), $page->getMarkedTag());
     }
     // Update the target translation page
     if (!$handle->isDoc()) {
         $code = $handle->getCode();
         self::updateTranslationPage($page, $code, $user, $flags, $summary);
     }
     return true;
 }
 /**
  * Filters out messages that should not be translated under normal
  * conditions.
  *
  * @param MessageHandle $handle Handle for the translation target.
  * @return boolean
  * @since 2013.10
  */
 public static function isTranslatableMessage(MessageHandle $handle)
 {
     static $cache = array();
     if (!$handle->isValid()) {
         return false;
     }
     $group = $handle->getGroup();
     $groupId = $group->getId();
     $language = $handle->getCode();
     $cacheKey = "{$groupId}:{$language}";
     if (!isset($cache[$cacheKey])) {
         $allowed = true;
         $discouraged = false;
         $whitelist = $group->getTranslatableLanguages();
         if (is_array($whitelist) && !isset($whitelist[$language])) {
             $allowed = false;
         }
         if (self::getPriority($group) === 'discouraged') {
             $discouraged = true;
         } else {
             $priorityLanguages = TranslateMetadata::get($groupId, 'prioritylangs');
             if ($priorityLanguages) {
                 $map = array_flip(explode(',', $priorityLanguages));
                 if (!isset($map[$language])) {
                     $discouraged = true;
                 }
             }
         }
         $cache[$cacheKey] = array('relevant' => $allowed && !$discouraged, 'tags' => array());
         $groupTags = $group->getTags();
         foreach (array('ignored', 'optional') as $tag) {
             if (isset($groupTags[$tag])) {
                 foreach ($groupTags[$tag] as $key) {
                     // TODO: ucfirst should not be here
                     $cache[$cacheKey]['tags'][ucfirst($key)] = true;
                 }
             }
         }
     }
     return $cache[$cacheKey]['relevant'] && !isset($cache[$cacheKey]['tags'][ucfirst($handle->getKey())]);
 }
 public function execute($par)
 {
     global $wgLanguageCode;
     $this->setHeaders();
     $this->checkPermissions();
     $server = TTMServer::primary();
     if (!$server instanceof SearchableTTMServer) {
         throw new ErrorPageError('tux-sst-nosolr-title', 'tux-sst-nosolr-body');
     }
     $out = $this->getOutput();
     $out->addModuleStyles('jquery.uls.grid');
     $out->addModuleStyles('ext.translate.special.searchtranslations.styles');
     $out->addModuleStyles('ext.translate.special.translate.styles');
     $out->addModules('ext.translate.special.searchtranslations');
     $out->addModules('ext.translate.special.searchtranslations.operatorsuggest');
     TranslateUtils::addSpecialHelpLink($out, 'Help:Extension:Translate#searching');
     $this->opts = $opts = new FormOptions();
     $opts->add('query', '');
     $opts->add('sourcelanguage', $wgLanguageCode);
     $opts->add('language', '');
     $opts->add('group', '');
     $opts->add('grouppath', '');
     $opts->add('filter', '');
     $opts->add('match', '');
     $opts->add('case', '');
     $opts->add('limit', $this->limit);
     $opts->add('offset', 0);
     $opts->fetchValuesFromRequest($this->getRequest());
     $queryString = $opts->getValue('query');
     if ($queryString === '') {
         $this->showEmptySearch();
         return;
     }
     $params = $opts->getAllValues();
     $filter = $opts->getValue('filter');
     try {
         $translationSearch = new CrossLanguageTranslationSearchQuery($params, $server);
         if (in_array($filter, $translationSearch->getAvailableFilters())) {
             if ($opts->getValue('language') === '') {
                 $params['language'] = $this->getLanguage()->getCode();
                 $opts->setValue('language', $params['language']);
             }
             $documents = $translationSearch->getDocuments();
             $total = $translationSearch->getTotalHits();
             $resultset = $translationSearch->getResultSet();
         } else {
             $resultset = $server->search($queryString, $params, $this->hl);
             $documents = $server->getDocuments($resultset);
             $total = $server->getTotalHits($resultset);
         }
     } catch (TTMServerException $e) {
         error_log('Translation search server unavailable:' . $e->getMessage());
         throw new ErrorPageError('tux-sst-solr-offline-title', 'tux-sst-solr-offline-body');
     }
     // Part 1: facets
     $facets = $server->getFacets($resultset);
     $facetHtml = '';
     if (count($facets['language']) > 0) {
         if ($filter !== '') {
             $facets['language'] = array_merge($facets['language'], array($opts->getValue('language') => $total));
         }
         $facetHtml = Html::element('div', array('class' => 'row facet languages', 'data-facets' => FormatJson::encode($this->getLanguages($facets['language'])), 'data-language' => $opts->getValue('language')), $this->msg('tux-sst-facet-language'));
     }
     if (count($facets['group']) > 0) {
         $facetHtml .= Html::element('div', array('class' => 'row facet groups', 'data-facets' => FormatJson::encode($this->getGroups($facets['group'])), 'data-group' => $opts->getValue('group')), $this->msg('tux-sst-facet-group'));
     }
     // Part 2: results
     $resultsHtml = '';
     $title = Title::newFromText($queryString);
     if ($title && !in_array($filter, $translationSearch->getAvailableFilters())) {
         $handle = new MessageHandle($title);
         $code = $handle->getCode();
         $language = $opts->getValue('language');
         if ($handle->isValid() && $code !== '' && $code !== $language) {
             $groupId = $handle->getGroup()->getId();
             $helpers = new TranslationHelpers($title, $groupId);
             $document['wiki'] = wfWikiId();
             $document['localid'] = $handle->getTitleForBase()->getPrefixedText();
             $document['content'] = $helpers->getTranslation();
             $document['language'] = $handle->getCode();
             array_unshift($documents, $document);
             $total++;
         }
     }
     foreach ($documents as $document) {
         $text = $document['content'];
         $text = TranslateUtils::convertWhiteSpaceToHTML($text);
         list($pre, $post) = $this->hl;
         $text = str_replace($pre, '<strong class="tux-search-highlight">', $text);
         $text = str_replace($post, '</strong>', $text);
         $title = Title::newFromText($document['localid'] . '/' . $document['language']);
         if (!$title) {
             // Should not ever happen but who knows...
             continue;
         }
         $resultAttribs = array('class' => 'row tux-message', 'data-title' => $title->getPrefixedText(), 'data-language' => $document['language']);
         $handle = new MessageHandle($title);
         if ($handle->isValid()) {
             $groupId = $handle->getGroup()->getId();
             $helpers = new TranslationHelpers($title, $groupId);
             $resultAttribs['data-definition'] = $helpers->getDefinition();
             $resultAttribs['data-translation'] = $helpers->getTranslation();
             $resultAttribs['data-group'] = $groupId;
             $uri = $title->getLocalUrl(array('action' => 'edit'));
             $link = Html::element('a', array('href' => $uri), $this->msg('tux-sst-edit')->text());
         } else {
             $url = wfParseUrl($document['uri']);
             $domain = $url['host'];
             $link = Html::element('a', array('href' => $document['uri']), $this->msg('tux-sst-view-foreign', $domain)->text());
         }
         $access = Html::rawElement('div', array('class' => 'row tux-edit tux-message-item'), $link);
         $titleText = $title->getPrefixedText();
         $titleAttribs = array('class' => 'row tux-title', 'dir' => 'ltr');
         $textAttribs = array('class' => 'row tux-text', 'lang' => wfBCP47($document['language']), 'dir' => Language::factory($document['language'])->getDir());
         $resultsHtml = $resultsHtml . Html::openElement('div', $resultAttribs) . Html::rawElement('div', $textAttribs, $text) . Html::element('div', $titleAttribs, $titleText) . $access . Html::closeElement('div');
     }
     $resultsHtml .= Html::rawElement('hr', array('class' => 'tux-pagination-line'));
     $prev = $next = '';
     $offset = $this->opts->getValue('offset');
     $params = $this->opts->getChangedValues();
     if ($total - $offset > $this->limit) {
         $newParams = array('offset' => $offset + $this->limit) + $params;
         $attribs = array('class' => 'mw-ui-button pager-next', 'href' => $this->getPageTitle()->getLocalUrl($newParams));
         $next = Html::element('a', $attribs, $this->msg('tux-sst-next')->text());
     }
     if ($offset) {
         $newParams = array('offset' => max(0, $offset - $this->limit)) + $params;
         $attribs = array('class' => 'mw-ui-button pager-prev', 'href' => $this->getPageTitle()->getLocalUrl($newParams));
         $prev = Html::element('a', $attribs, $this->msg('tux-sst-prev')->text());
     }
     $resultsHtml .= Html::rawElement('div', array('class' => 'tux-pagination-links'), "{$prev} {$next}");
     $search = $this->getSearchInput($queryString);
     $count = $this->msg('tux-sst-count')->numParams($total);
     $this->showSearch($search, $count, $facetHtml, $resultsHtml, $total);
 }
 public function update(MessageHandle $handle, $targetText)
 {
     if (!$handle->isValid() || $handle->getCode() === '') {
         return false;
     }
     /* There are various different cases here:
      * [new or updated] [fuzzy|non-fuzzy] [translation|definition]
      * 1) We don't distinguish between new or updated here.
      * 2) Delete old translation, but not definition
      * 3) Insert new translation or definition, if non-fuzzy
      * The definition should never be fuzzied anyway.
      *
      * These only apply to known messages.
      */
     $title = $handle->getTitle();
     $sourceLanguage = $handle->getGroup()->getSourceLanguage();
     // Do not delete definitions, because the translations are attached to that
     if ($handle->getCode() !== $sourceLanguage) {
         $localid = $handle->getTitleForBase()->getPrefixedText();
         $boolQuery = new \Elastica\Query\Bool();
         $boolQuery->addMust(new Elastica\Query\Term(array('wiki' => wfWikiId())));
         $boolQuery->addMust(new Elastica\Query\Term(array('language' => $handle->getCode())));
         $boolQuery->addMust(new Elastica\Query\Term(array('localid' => $localid)));
         $query = new \Elastica\Query($boolQuery);
         $this->getType()->deleteByQuery($query);
     }
     // If translation was made fuzzy, we do not need to add anything
     if ($targetText === null) {
         return true;
     }
     $revId = $handle->getTitleForLanguage($sourceLanguage)->getLatestRevID();
     $doc = $this->createDocument($handle, $targetText, $revId);
     $retries = 5;
     while ($retries-- > 0) {
         try {
             $this->getType()->addDocument($doc);
             break;
         } catch (\Elastica\Exception\ExceptionInterface $e) {
             if ($retries === 0) {
                 throw $e;
             } else {
                 $c = get_class($e);
                 $msg = $e->getMessage();
                 error_log(__METHOD__ . ": update failed ({$c}: {$msg}); retrying.");
                 sleep(10);
             }
         }
     }
     return true;
 }