/** * @return null|string */ public function getLazySuggestionBox() { $this->mustBeKnownMessage(); if (!$this->handle->getCode()) { return null; } $url = SpecialPage::getTitleFor('Translate', 'editpage')->getLocalUrl(array('suggestions' => 'only', 'page' => $this->handle->getTitle()->getPrefixedDbKey(), 'loadgroup' => $this->group->getId())); $url = Xml::encodeJsVar($url); $id = Sanitizer::escapeId('tm-lazysug-' . $this->dialogID()); $target = self::jQueryPathId($id); $script = Html::inlineScript("jQuery({$target}).load({$url})"); $spinner = Html::element('div', array('class' => 'mw-ajax-loader')); return Html::rawElement('div', array('id' => $id), $script . $spinner); }
public function testGetId() { $this->assertEquals( $this->groupConfiguration['BASIC']['id'], $this->group->getId(), "id comes from config." ); }
protected function groupSelector() { $activeId = false; if ($this->group) { $activeId = $this->group->getId(); } $groups = MessageGroups::getAllGroups(); $dynamic = MessageGroups::getDynamicGroups(); $groups = array_keys(array_merge($groups, $dynamic)); $selected = $this->options['group']; $selector = new XmlSelect('group', 'group'); $selector->setDefault($selected); foreach ($groups as $id) { if ($id === $activeId) { $activeId = false; } $group = MessageGroups::getGroup($id); $hide = MessageGroups::getPriority($group) === 'discouraged'; if (!$group->exists() || $hide) { continue; } $selector->addOption($group->getLabel(), $id); } if ($activeId) { $selector->addOption($this->group->getLabel(), $activeId); } return $selector->getHTML(); }
public static function getState(MessageGroup $group, $code) { $dbw = wfGetDB(DB_MASTER); $table = 'translate_groupreviews'; $field = 'tgr_state'; $conds = array('tgr_group' => $group->getId(), 'tgr_lang' => $code); return $dbw->selectField($table, $field, $conds, __METHOD__); }
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; }
/** * Keeps track of recently used message groups per user. */ public static function trackGroup(MessageGroup $group, User $user) { if ($user->isAnon()) { return true; } $groups = $user->getOption('translate-recent-groups', ''); if ($groups === '') { $groups = array(); } else { $groups = explode('|', $groups); } if (isset($groups[0]) && $groups[0] === $group->getId()) { return true; } array_unshift($groups, $group->getId()); $groups = array_unique($groups); $groups = array_slice($groups, 0, 5); $user->setOption('translate-recent-groups', implode('|', $groups)); // Promise to persist the data post-send DeferredUpdates::addCallableUpdate(function () use($user) { $user->saveSettings(); }); return true; }
public function getDefinitionBox() { $this->mustHaveDefinition(); $en = $this->getDefinition(); $title = Linker::link(SpecialPage::getTitleFor('Translate'), htmlspecialchars($this->group->getLabel()), array(), array('group' => $this->group->getId(), 'language' => $this->handle->getCode())); $label = wfMessage('translate-edit-definition')->text() . wfMessage('word-separator')->text() . wfMessage('parentheses', $title)->text(); // Source language object $sl = Language::factory($this->group->getSourceLanguage()); $dialogID = $this->dialogID(); $id = Sanitizer::escapeId("def-{$dialogID}"); $msg = $this->adder($id, $sl) . "\n" . Html::rawElement('div', array('class' => 'mw-translate-edit-deftext', 'dir' => $sl->getDir(), 'lang' => $sl->getCode()), TranslateUtils::convertWhiteSpaceToHTML($en)); $msg .= $this->wrapInsert($id, $en); $class = array('class' => 'mw-sp-translate-edit-definition mw-translate-edit-definition'); return TranslateUtils::fieldset($label, $msg, $class); }
/** * Actually creates the table for single message group, unless it * is blacklisted or hidden by filters. * @param MessageGroup $group * @param array $cache * @param MessageGroup $parent * @return string */ protected function makeGroupRow(MessageGroup $group, array $cache, MessageGroup $parent = null) { $groupId = $group->getId(); if ($this->table->isBlacklisted($groupId, $this->target) !== null) { return ''; } $stats = $cache[$groupId]; $total = $stats[MessageGroupStats::TOTAL]; $translated = $stats[MessageGroupStats::TRANSLATED]; $fuzzy = $stats[MessageGroupStats::FUZZY]; // Quick checks to see whether filters apply if ($this->noComplete && $fuzzy === 0 && $translated === $total) { return ''; } if ($this->noEmpty && $translated === 0 && $fuzzy === 0) { return ''; } // Calculation of summary row values if (!$group instanceof AggregateMessageGroup) { if (!isset($this->statsCounted[$groupId])) { $this->totals = MessageGroupStats::multiAdd($this->totals, $stats); $this->statsCounted[$groupId] = true; } } $state = $this->getWorkflowStateValue($groupId); $params = $stats; $params[] = $state; $params[] = $groupId; $params[] = $this->getLanguage()->getCode(); $params[] = $this->target; $cachekey = wfMemcKey(__METHOD__, implode('-', $params)); $cacheval = wfGetCache(CACHE_ANYTHING)->get($cachekey); if (!$this->purge && is_string($cacheval)) { return $cacheval; } $extra = array(); if ($total === null) { $this->incomplete = true; } elseif ($translated === $total) { $extra = array('action' => 'proofread'); } $rowParams = array(); $rowParams['data-groupid'] = $groupId; $rowParams['class'] = get_class($group); if ($parent) { $rowParams['data-parentgroup'] = $parent->getId(); } $out = "\t" . Html::openElement('tr', $rowParams); $out .= "\n\t\t" . Html::rawElement('td', array(), $this->table->makeGroupLink($group, $this->target, $extra)); $out .= $this->table->makeNumberColumns($stats); $out .= $this->getWorkflowStateCell($groupId, $state); $out .= "\n\t" . Html::closeElement('tr') . "\n"; wfGetCache(CACHE_ANYTHING)->set($cachekey, $out, 3600 * 24); return $out; }
/** * Returns the message groups this message group is part of. * @since 2011-12-25 * @return array */ public static function getParentGroups(MessageGroup $group) { // Take the first message, get a handle for it and check // if that message belongs to other groups. Those are the // parent aggregate groups. Ideally we loop over all keys, // but this should be enough. $keys = array_keys($group->getDefinitions()); $title = Title::makeTitle($group->getNamespace(), $keys[0]); $handle = new MessageHandle($title); $ids = $handle->getGroupIds(); foreach ($ids as $index => $id) { if ($id === $group->getId()) { unset($ids[$index]); } } return $ids; }
public function __construct( MessageCollection $collection, MessageGroup $group ) { $this->collection = $collection; $this->group = $group; $this->setHeaderText( 'table', $group->getLabel() ); $this->appendEditLinkParams( 'loadgroup', $group->getId() ); }
protected function setup($parameters) { $request = $this->getRequest(); $isBeta = self::isBeta($request); $defaults = array('taction' => 'translate', 'task' => $isBeta ? 'custom' : 'untranslated', 'language' => $this->getLanguage()->getCode(), 'group' => $isBeta ? '!additions' : '', 'offset' => '', 'limit' => $isBeta ? 0 : 100, 'optional' => '0'); // Dump everything here $nondefaults = array(); $parameters = array_map('trim', explode(';', $parameters)); $pars = array(); foreach ($parameters as $_) { if ($_ === '') { continue; } if (strpos($_, '=') !== false) { list($key, $value) = array_map('trim', explode('=', $_, 2)); } else { $key = 'group'; $value = $_; } $pars[$key] = $value; } foreach ($defaults as $v => $t) { if (is_bool($t)) { $r = isset($pars[$v]) ? (bool) $pars[$v] : $defaults[$v]; $r = $request->getBool($v, $r); } elseif (is_int($t)) { $r = isset($pars[$v]) ? (int) $pars[$v] : $defaults[$v]; $r = $request->getInt($v, $r); } elseif (is_string($t)) { $r = isset($pars[$v]) ? (string) $pars[$v] : $defaults[$v]; $r = $request->getText($v, $r); } if (!isset($r)) { throw new MWException('$r was not set'); } wfAppendToArrayIfNotDefault($v, $r, $defaults, $nondefaults); } // Fix defaults based on what we got if (isset($nondefaults['taction'])) { if ($nondefaults['taction'] === 'proofread') { if ($this->getUser()->isAllowed('translate-messagereview')) { $defaults['task'] = 'acceptqueue'; } else { $defaults['task'] = 'reviewall'; } } elseif ($nondefaults['taction'] === 'export') { $defaults['task'] = ''; } } if ($isBeta) { /* @todo fix all the places in Translate to create correct links. * The least effort way is to change them once we totally drop the * old UI. The penalty is only http redirect in some cases. More * effort would be to create utilities like makeTranslationLink * and makeProofreadLink. */ $this->rewriteLegacyUrls($nondefaults); } $this->defaults = $defaults; $this->nondefaults = $nondefaults; Hooks::run('TranslateGetSpecialTranslateOptions', array(&$defaults, &$nondefaults)); $this->options = $nondefaults + $defaults; $this->group = MessageGroups::getGroup($this->options['group']); if ($this->group) { $this->options['group'] = $this->group->getId(); } $this->task = TranslateTasks::getTask($this->options['task']); if ($this->group && MessageGroups::isDynamic($this->group)) { $this->group->setLanguage($this->options['language']); } }
/** * @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 && $title->exists() && $type === 'addition') { // 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 && !$title->exists() && ($type === 'deletion' || $type === 'change')) { return ''; } $text = ''; 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(Linker::link($title), ''); } elseif ($type === 'addition') { $oldContent = ContentHandler::makeContent('', $title); $newContent = ContentHandler::makeContent($params['content'], $title); $this->diff->setContent($oldContent, $newContent); $text = $this->diff->getDiff('', Linker::link($title)); } 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(Linker::link($title), $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', array('class' => $classes), $text); }
protected function exportGroup(MessageGroup $group, $config) { $server = TTMServer::factory($config); $server->setLogger($this); $id = $group->getId(); $sourceLanguage = $group->getSourceLanguage(); $stats = MessageGroupStats::forGroup($id); $collection = $group->initCollection($sourceLanguage); $collection->filter('ignored'); $collection->initMessages(); $server->beginBatch(); $inserts = array(); foreach ($collection->keys() as $mkey => $title) { $handle = new MessageHandle($title); $inserts[] = array($handle, $sourceLanguage, $collection[$mkey]->definition()); } while ($inserts !== array()) { $batch = array_splice($inserts, 0, $this->mBatchSize); $server->batchInsertDefinitions($batch); } $inserts = array(); foreach ($stats as $targetLanguage => $numbers) { if ($targetLanguage === $sourceLanguage) { continue; } if ($numbers[MessageGroupStats::TRANSLATED] === 0) { continue; } $collection->resetForNewLanguage($targetLanguage); $collection->filter('ignored'); $collection->filter('translated', false); $collection->loadTranslations(); foreach ($collection->keys() as $mkey => $title) { $handle = new MessageHandle($title); $inserts[] = array($handle, $sourceLanguage, $collection[$mkey]->translation()); } while (count($inserts) >= $this->mBatchSize) { $batch = array_splice($inserts, 0, $this->mBatchSize); $server->batchInsertTranslations($batch); } } while ($inserts !== array()) { $batch = array_splice($inserts, 0, $this->mBatchSize); $server->batchInsertTranslations($batch); } $server->endBatch(); }
/** * Returns full path the the cache file. * @return string */ protected function getCacheFileName() { return TranslateUtils::cacheFile("translate_groupcache-{$this->group->getId()}-{$this->code}.cdb"); }
protected static function expandAggregates( MessageGroup $group ) { $flattened = array( $group->getId() ); if ( $group instanceof AggregateMessageGroup ) { foreach ( $group->getGroups() as $subgroup ) { $flattened = array_merge( $flattened, self::expandAggregates( $subgroup ) ); } } return $flattened; }
/** * Returns full path to the old cache file location. * @return string */ protected function getOldCacheFileName() { $cacheFileName = "translate_groupcache-{$this->group->getId()}-{$this->code}.cdb"; return TranslateUtils::cacheFile($cacheFileName); }
/** * @param array[] $stats * @param MessageGroup $group * @param string $code Language code * * @return null[]|int[] */ protected static function forItemInternal(&$stats, $group, $code) { $id = $group->getId(); if (self::$timeStart !== null && microtime(true) - self::$timeStart > self::$limit) { return $stats[$id][$code] = self::getUnknownStats(); } if ($group instanceof AggregateMessageGroup) { $aggregates = self::getEmptyStats(); $expanded = self::expandAggregates($group); if ($expanded === array()) { return $aggregates; } $res = self::selectRowsIdLang(array_keys($expanded), $code); $stats = self::extractResults($res, $stats); foreach ($expanded as $sid => $subgroup) { # Discouraged groups may belong to another group, usually if there # is an aggregate group for all translatable pages. In that case # calculate and store the statistics, but don't count them as part of # the aggregate group, so that the numbers in Special:LanguageStats # add up. The statistics for discouraged groups can still be viewed # through Special:MessageGroupStats. if (!isset($stats[$sid][$code])) { $stats[$sid][$code] = self::forItemInternal($stats, $subgroup, $code); } $include = Hooks::run('Translate:MessageGroupStats:isIncluded', array($sid, $code)); if ($include) { $aggregates = self::multiAdd($aggregates, $stats[$sid][$code]); } } $stats[$id][$code] = $aggregates; } else { $aggregates = self::calculateGroup($group, $code); } // Don't add nulls to the database, causes annoying warnings if ($aggregates[self::TOTAL] === null) { return $aggregates; } self::$updates[] = array('tgs_group' => $id, 'tgs_lang' => $code, 'tgs_total' => $aggregates[self::TOTAL], 'tgs_translated' => $aggregates[self::TRANSLATED], 'tgs_fuzzy' => $aggregates[self::FUZZY], 'tgs_proofread' => $aggregates[self::PROOFREAD]); return $aggregates; }
protected function exportGroup(MessageGroup $group, $multi = false) { // Make sure all existing connections are dead, // we can't use them in forked children. LBFactory::destroyInstance(); $server = TTMServer::primary(); $id = $group->getId(); $sourceLanguage = $group->getSourceLanguage(); if ($multi) { $stats = MessageGroupStats::forGroup($id); $this->statusLine("Loaded stats for {$id}\n"); } else { $this->statusLine("Loading stats... ", 4); $stats = MessageGroupStats::forGroup($id); $this->output("done!", 4); $this->statusLine("Inserting sources: ", 5); } $collection = $group->initCollection($sourceLanguage); $collection->filter('ignored'); $collection->filter('optional'); $collection->initMessages(); $sids = array(); $counter = 0; foreach ($collection->keys() as $mkey => $title) { $def = $collection[$mkey]->definition(); $sids[$mkey] = $server->insertSource($title, $sourceLanguage, $def); if (++$counter % $this->mBatchSize === 0 && !$multi) { wfWaitForSlaves(10); $this->output('.', 5); } } $total = count($sids); if ($multi) { $this->statusLine("Inserted {$total} source entries for {$id}\n"); } else { $this->output("{$total} entries", 5); $this->statusLine("Inserting translations...", 6); } $dbw = $server->getDB(DB_MASTER); foreach ($stats as $targetLanguage => $numbers) { if ($targetLanguage === $sourceLanguage) { continue; } if ($numbers[MessageGroupStats::TRANSLATED] === 0) { continue; } if (!$multi) { $this->output(sprintf("%19s ", $targetLanguage), $targetLanguage); } $collection->resetForNewLanguage($targetLanguage); $collection->filter('ignored'); $collection->filter('optional'); $collection->filter('translated', false); $collection->loadTranslations(); $inserts = array(); foreach ($collection->keys() as $mkey => $title) { $inserts[] = array('tmt_sid' => $sids[$mkey], 'tmt_lang' => $targetLanguage, 'tmt_text' => $collection[$mkey]->translation()); } do { $batch = array_splice($inserts, 0, $this->mBatchSize); $dbw->insert('translate_tmt', $batch, __METHOD__); if (!$multi) { $this->output('.', $targetLanguage); } wfWaitForSlaves(10); } while (count($inserts)); } if ($multi) { $this->statusLine("Inserted translations for {$id}\n"); } }
/** * @param MessageGroup $group * @param string $prefix * @return string */ protected function htmlIdForGroup(MessageGroup $group, $prefix = '') { $id = sha1($group->getId()); $id = substr($id, 5, 8); return $prefix . $id; }
/** * Gets the description of a group. This is a bit slow thing to do for * thousand+ groups, so some caching is involved. * @param $group MessageGroup * @return string Plain text */ public function getGroupDescription( MessageGroup $group ) { $code = $this->lang->getCode(); $cache = wfGetCache( CACHE_ANYTHING ); $key = wfMemckey( "translate-groupdesc-$code-" . $group->getId() ); $desc = $cache->get( $key ); if ( is_string( $desc ) ) { return $desc; } $realFunction = array( 'MessageCache', 'singleton' ); if ( is_callable( $realFunction ) ) { $mc = MessageCache::singleton(); } else { global $wgMessageCache; $mc = $wgMessageCache; } $desc = $mc->transform( $group->getDescription(), true, $this->lang ); $cache->set( $key, $desc ); return $desc; }