/** * Set a metadata value for the given group and metadata key. Updates the * value if already existing. * @param $group string The group id * @param $key string Metadata key * @param $value string Metadata value */ public static function set($group, $key, $value) { $dbw = wfGetDB(DB_MASTER); $data = array('tmd_group' => $group, 'tmd_key' => $key, 'tmd_value' => $value); if ($value === false) { unset($data['tmd_value']); $dbw->delete('translate_metadata', $data); } else { $dbw->replace('translate_metadata', array(array('tmd_group', 'tmd_key')), $data, __METHOD__); } self::$cache = null; }
/** * @param AggregateMessageGroup $parent * @return string */ protected function listSubgroups(AggregateMessageGroup $parent) { $out = ''; $id = $this->htmlIdForGroup($parent, 'mw-tpa-grouplist-'); $out = Html::openElement('ol', array('id' => $id)); // Not calling $parent->getGroups() because it has done filtering already $subgroupIds = TranslateMetadata::getSubgroups($parent->getId()); // Get the respective groups and sort them $subgroups = MessageGroups::getGroupsById($subgroupIds); uasort($subgroups, array('MessageGroups', 'groupLabelSort')); // Add missing invalid group ids back, not returned by getGroupsById foreach ($subgroupIds as $id) { if (!isset($subgroups[$id])) { $subgroups[$id] = null; } } foreach ($subgroups as $id => $group) { $remove = ''; if ($this->hasPermission) { $remove = Html::element('span', array('class' => 'tp-aggregate-remove-button', 'data-groupid' => $id)); } if ($group) { $text = Linker::linkKnown($group->getTitle()); $note = MessageGroups::getPriority($id); } else { $text = htmlspecialchars($id); $note = $this->msg('tpt-aggregategroup-invalid-group')->escaped(); } $out .= Html::rawElement('li', array(), "{$text}{$remove} {$note}"); } $out .= Html::closeElement('ol'); return $out; }
protected function clearMetadata() { // remove the entries from metadata table. $groupId = $this->page->getMessageGroupId(); TranslateMetadata::set($groupId, 'prioritylangs', false); TranslateMetadata::set($groupId, 'priorityforce', false); TranslateMetadata::set($groupId, 'priorityreason', false); // remove the page from aggregate groups, if present in any of them. $groups = MessageGroups::getAllGroups(); foreach ($groups as $group) { if ($group instanceof AggregateMessageGroup) { $subgroups = TranslateMetadata::get($group->getId(), 'subgroups'); if ($subgroups !== false) { $subgroups = explode(',', $subgroups); $subgroups = array_flip($subgroups); if (isset($subgroups[$groupId])) { unset($subgroups[$groupId]); $subgroups = array_flip($subgroups); TranslateMetadata::set($group->getId(), 'subgroups', implode(',', $subgroups)); } } } } }
/** * Prevent editing of restricted languages. * Hook: getUserPermissionsErrorsExpensive * @since 2012-03-01 */ public static function preventRestrictedTranslations(Title $title, User $user, $action, &$result) { global $wgTranslateDocumentationLanguageCode; // Preventing editing (includes creation) should be enough if ($action !== 'edit') { return true; } $handle = new MessageHandle($title); if (!$handle->isValid()) { return true; } // Get the primary group id $ids = $handle->getGroupIds(); $groupId = $ids[0]; // 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; } // And finally check whether the language is not included in whitelist $languages = TranslateMetadata::get($groupId, 'prioritylangs'); $filter = array_flip(explode(',', $languages)); 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 true; }
/** * Access point for this special page. */ public function execute($parameters) { global $wgTranslateBlacklist, $wgContLang; $out = $this->getOutput(); $out->addModuleStyles(array('ext.translate.special.translate.styles', 'jquery.uls.grid')); $out->addModules('ext.translate.special.translate'); $this->setHeaders(); $request = $this->getRequest(); // @todo Move to api or so if ($parameters === 'editpage') { $editpage = TranslationEditPage::newFromRequest($request); if ($editpage) { $editpage->execute(); return; } } if (!defined('ULS_VERSION')) { throw new ErrorPageError('translate-ulsdep-title', 'translate-ulsdep-body'); } $this->setup($parameters); $isBeta = self::isBeta($request); if ($this->options['group'] === '' || $isBeta && !$this->group) { $this->groupInformation(); return; } $errors = $this->getFormErrors(); if ($isBeta && $this->options['taction'] !== 'export') { $out->addHTML(Html::openElement('div', array('class' => 'grid ext-translate-container'))); $out->addHTML($this->tuxSettingsForm($errors)); $out->addHTML($this->messageSelector()); } else { $out->addModuleStyles('ext.translate.legacy'); TranslateUtils::addSpecialHelpLink($out, 'Help:Extension:Translate/Translation_example'); // Show errors nicely. $out->addHTML($this->settingsForm($errors)); } if (count($errors)) { return; } else { $checks = array($this->options['group'], strtok($this->options['group'], '-'), '*'); foreach ($checks as $check) { if (isset($wgTranslateBlacklist[$check][$this->options['language']])) { $reason = $wgTranslateBlacklist[$check][$this->options['language']]; $out->addWikiMsg('translate-page-disabled', $reason); if ($isBeta) { // Close div.ext-translate-container $out->addHTML(Html::closeElement('div')); } return; } } } $params = array($this->getContext(), $this->task, $this->group, $this->options); if (!Hooks::run('SpecialTranslate::executeTask', $params)) { return; } // Initialise and get output. if (!$this->task) { return; } $this->task->init($this->group, $this->options, $this->nondefaults, $this->getContext()); $output = $this->task->execute(); if ($this->task->plainOutput()) { $out->disable(); header('Content-type: text/plain; charset=UTF-8'); echo $output; } else { $description = $this->getGroupDescription($this->group); $taskid = $this->options['task']; if (in_array($taskid, array('untranslated', 'reviewall'), true)) { $hasOptional = count($this->group->getTags('optional')); if ($hasOptional) { $linktext = $this->msg('translate-page-description-hasoptional-open')->escaped(); $params = array('task' => 'optional') + $this->nondefaults; $link = Linker::link($this->getPageTitle(), $linktext, array(), $params); $note = $this->msg('translate-page-description-hasoptional')->rawParams($link)->parseAsBlock(); if ($description) { $description .= '<br />' . $note; } else { $description = $note; } } } $groupId = $this->group->getId(); // PHP is such an awesome language $priorityLangs = TranslateMetadata::get($groupId, 'prioritylangs'); $priorityLangs = array_flip(array_filter(explode(',', $priorityLangs))); $priorityLangsCount = count($priorityLangs); if ($priorityLangsCount && !isset($priorityLangs[$this->options['language']])) { $priorityForce = TranslateMetadata::get($groupId, 'priorityforce'); if ($priorityForce === 'on') { // Hide table $priorityMessageClass = 'errorbox'; $priorityMessageKey = 'tpt-discouraged-language-force'; } else { $priorityMessageClass = 'warningbox'; $priorityMessageKey = 'tpt-discouraged-language'; } $priorityLanguageNames = array(); $languageNames = TranslateUtils::getLanguageNames($this->getLanguage()->getCode()); foreach (array_keys($priorityLangs) as $langCode) { $priorityLanguageNames[] = $languageNames[$langCode]; } $priorityReason = TranslateMetadata::get($groupId, 'priorityreason'); if ($priorityReason !== '') { $priorityReason = "\n\n" . $this->msg('tpt-discouraged-language-reason', Xml::element('span', array('lang' => $wgContLang->getCode(), 'dir' => $wgContLang->getDir()), $priorityReason))->parse(); } $description .= Html::RawElement('div', array('class' => $priorityMessageClass), $this->msg($priorityMessageKey, '', $languageNames[$this->options['language']], $this->getLanguage()->listToText($priorityLanguageNames))->parseAsBlock() . $priorityReason); } if ($description) { $description = Xml::fieldset($this->msg('translate-page-description-legend')->text(), $description, array('class' => 'mw-sp-translate-description')); } if ($isBeta) { $out->addHTML($output); } else { $out->addHTML($description . $output); } ApiTranslateUser::trackGroup($this->group, $this->getUser()); } if ($isBeta) { $out->addHTML(Html::closeElement('div')); } }
/** * @param array|MessageGroup $mixed * @param array $props List of props as the array keys * @param int $depth * @return array */ protected function formatGroup($mixed, $props, $depth = 0) { $params = $this->extractRequestParams(); // Default $g = $mixed; $subgroups = array(); // Format = tree and has subgroups if (is_array($mixed)) { $g = array_shift($mixed); $subgroups = $mixed; } wfProfileIn(__METHOD__ . '-' . get_class($g)); $a = array(); $groupId = $g->getId(); wfProfileIn(__METHOD__ . '-basic'); if (isset($props['id'])) { $a['id'] = $groupId; } if (isset($props['label'])) { $a['label'] = $g->getLabel(); } if (isset($props['description'])) { $a['description'] = $g->getDescription(); } if (isset($props['class'])) { $a['class'] = get_class($g); } if (isset($props['namespace'])) { $a['namespace'] = $g->getNamespace(); } wfProfileOut(__METHOD__ . '-basic'); wfProfileIn(__METHOD__ . '-exists'); if (isset($props['exists'])) { $a['exists'] = $g->exists(); } wfProfileOut(__METHOD__ . '-exists'); wfProfileIn(__METHOD__ . '-icon'); if (isset($props['icon'])) { $formats = TranslateUtils::getIcon($g, $params['iconsize']); if ($formats) { $a['icon'] = $formats; } } wfProfileOut(__METHOD__ . '-icon'); wfProfileIn(__METHOD__ . '-priority'); if (isset($props['priority'])) { $priority = MessageGroups::getPriority($g); $a['priority'] = $priority ?: 'default'; } if (isset($props['prioritylangs'])) { $prioritylangs = TranslateMetadata::get($groupId, 'prioritylangs'); $a['prioritylangs'] = $prioritylangs ? explode(',', $prioritylangs) : false; } if (isset($props['priorityforce'])) { $a['priorityforce'] = TranslateMetadata::get($groupId, 'priorityforce') === 'on'; } wfProfileOut(__METHOD__ . '-priority'); wfProfileIn(__METHOD__ . '-workflowstates'); if (isset($props['workflowstates'])) { $a['workflowstates'] = $this->getWorkflowStates($g); } wfProfileOut(__METHOD__ . '-workflowstates'); Hooks::run('TranslateProcessAPIMessageGroupsProperties', array(&$a, $props, $params, $g)); wfProfileOut(__METHOD__ . '-' . get_class($g)); // Depth only applies to tree format if ($depth >= $params['depth'] && $params['format'] === 'tree') { $a['groupcount'] = count($subgroups); // Prevent going further down in the three return $a; } // Always empty array for flat format, only sometimes for tree format if ($subgroups !== array()) { foreach ($subgroups as $sg) { $a['groups'][] = $this->formatGroup($sg, $props); } $result = $this->getResult(); $result->setIndexedTagName($a['groups'], 'group'); } return $a; }
/** * @param WebRequest $request * @param TranslatablePage $page */ protected function handlePriorityLanguages(WebRequest $request, TranslatablePage $page) { // new priority languages $npLangs = rtrim(trim($request->getVal('prioritylangs')), ','); $npForce = $request->getCheck('forcelimit') ? 'on' : 'off'; $npReason = trim($request->getText('priorityreason')); // Normalize $npLangs = array_map('trim', explode(',', $npLangs)); $npLangs = array_unique($npLangs); // Remove invalid language codes. $languages = Language::fetchLanguageNames(); foreach ($npLangs as $index => $language) { if (!array_key_exists($language, $languages)) { unset($npLangs[$index]); } } $npLangs = implode(',', $npLangs); if ($npLangs === '') { $npLangs = false; $npForce = false; $npReason = false; } $groupId = $page->getMessageGroupId(); // old priority languages $opLangs = TranslateMetadata::get($groupId, 'prioritylangs'); $opForce = TranslateMetadata::get($groupId, 'priorityforce'); $opReason = TranslateMetadata::get($groupId, 'priorityreason'); TranslateMetadata::set($groupId, 'prioritylangs', $npLangs); TranslateMetadata::set($groupId, 'priorityforce', $npForce); TranslateMetadata::set($groupId, 'priorityreason', $npReason); if ($opLangs !== $npLangs || $opForce !== $npForce || $opReason !== $npReason) { $params = array('languages' => $npLangs, 'force' => $npForce, 'reason' => $npReason); $entry = new ManualLogEntry('pagetranslation', 'prioritylanguages'); $entry->setPerformer($this->getUser()); $entry->setTarget($page->getTitle()); $entry->setParameters($params); $entry->setComment($npReason); $logid = $entry->insert(); $entry->publish($logid); } }
protected function moveMetadata($oldGroupId, $newGroupId) { $prioritylangs = TranslateMetadata::get($oldGroupId, 'prioritylangs'); $priorityforce = TranslateMetadata::get($oldGroupId, 'priorityforce'); $priorityreason = TranslateMetadata::get($oldGroupId, 'priorityreason'); TranslateMetadata::set($oldGroupId, 'prioritylangs', false); TranslateMetadata::set($oldGroupId, 'priorityforce', false); TranslateMetadata::set($oldGroupId, 'priorityreason', false); if ($prioritylangs) { TranslateMetadata::set($newGroupId, 'prioritylangs', $prioritylangs); } if ($priorityforce) { TranslateMetadata::set($newGroupId, 'priorityforce', $priorityforce); } if ($priorityreason !== false) { TranslateMetadata::set($newGroupId, 'priorityreason', $priorityreason); } // make the changes in aggregate groups metadata, if present in any of them. $groups = MessageGroups::getAllGroups(); foreach ($groups as $group) { if ($group instanceof AggregateMessageGroup) { $subgroups = TranslateMetadata::get($group->getId(), 'subgroups'); if ($subgroups !== false) { $subgroups = explode(',', $subgroups); $subgroups = array_flip($subgroups); if (isset($subgroups[$oldGroupId])) { $subgroups[$newGroupId] = $subgroups[$oldGroupId]; unset($subgroups[$oldGroupId]); $subgroups = array_flip($subgroups); TranslateMetadata::set($group->getId(), 'subgroups', implode(',', $subgroups)); } } } } }
public function execute() { if (!$this->getUser()->isAllowed(self::$right)) { $this->dieUsage('Permission denied', 'permissiondenied'); } $params = $this->extractRequestParams(); $action = $params['do']; $output = array(); if ($action === 'associate' || $action === 'dissociate') { // Group is mandatory only for these two actions if (!isset($params['group'])) { $this->dieUsageMsg(array('missingparam', 'group')); } if (!isset($params['aggregategroup'])) { $this->dieUsageMsg(array('missingparam', 'aggregategroup')); } $aggregateGroup = $params['aggregategroup']; $subgroups = TranslateMetadata::getSubgroups($aggregateGroup); if (count($subgroups) === 0) { // For newly created groups the subgroups value might be empty, // but check that. if (TranslateMetadata::get($aggregateGroup, 'name') === false) { $this->dieUsage('Invalid aggregate message group', 'invalidaggregategroup'); } $subgroups = array(); } $subgroupId = $params['group']; $group = MessageGroups::getGroup($subgroupId); // Add or remove from the list if ($action === 'associate') { if (!$group instanceof WikiPageMessageGroup) { $this->dieUsage('Group does not exist or invalid', 'invalidgroup'); } $subgroups[] = $subgroupId; $subgroups = array_unique($subgroups); } elseif ($action === 'dissociate') { // Allow removal of non-existing groups $subgroups = array_flip($subgroups); unset($subgroups[$subgroupId]); $subgroups = array_flip($subgroups); } TranslateMetadata::setSubgroups($aggregateGroup, $subgroups); $logParams = array('aggregategroup' => TranslateMetadata::get($aggregateGroup, 'name'), 'aggregategroup-id' => $aggregateGroup); /* Note that to allow removing no longer existing groups from * aggregate message groups, the message group object $group * might not always be available. In this case we need to fake * some title. */ $title = $group ? $group->getTitle() : Title::newFromText("Special:Translate/{$subgroupId}"); $entry = new ManualLogEntry('pagetranslation', $action); $entry->setPerformer($this->getUser()); $entry->setTarget($title); // @todo // $entry->setComment( $comment ); $entry->setParameters($logParams); $logid = $entry->insert(); $entry->publish($logid); } elseif ($action === 'remove') { if (!isset($params['aggregategroup'])) { $this->dieUsageMsg(array('missingparam', 'aggregategroup')); } TranslateMetadata::deleteGroup($params['aggregategroup']); // @todo Logging } elseif ($action === 'add') { if (!isset($params['groupname'])) { $this->dieUsageMsg(array('missingparam', 'groupname')); } $name = trim($params['groupname']); if (strlen($name) === 0) { $this->dieUsage('Invalid aggregate message group name', 'invalidaggregategroupname'); } if (!isset($params['groupdescription'])) { $this->dieUsageMsg(array('missingparam', 'groupdescription')); } $desc = trim($params['groupdescription']); $aggregateGroupId = self::generateAggregateGroupId($name); // Throw error if group already exists $nameExists = MessageGroups::labelExists($name); if ($nameExists) { $this->dieUsage('Message group already exists', 'duplicateaggregategroup'); } // ID already exists- Generate a new ID by adding a number to it. $idExists = MessageGroups::getGroup($aggregateGroupId); if ($idExists) { $i = 1; while ($idExists) { $tempId = $aggregateGroupId . "-" . $i; $idExists = MessageGroups::getGroup($tempId); $i++; } $aggregateGroupId = $tempId; } TranslateMetadata::set($aggregateGroupId, 'name', $name); TranslateMetadata::set($aggregateGroupId, 'description', $desc); TranslateMetadata::setSubgroups($aggregateGroupId, array()); // Once new aggregate group added, we need to show all the pages that can be added to that. $output['groups'] = self::getAllPages(); $output['aggregategroupId'] = $aggregateGroupId; // @todo Logging } elseif ($action === 'update') { if (!isset($params['groupname'])) { $this->dieUsageMsg(array('missingparam', 'groupname')); } $name = trim($params['groupname']); if (strlen($name) === 0) { $this->dieUsage('Invalid aggregate message group name', 'invalidaggregategroupname'); } $desc = trim($params['groupdescription']); $aggregateGroupId = $params['aggregategroup']; $oldName = TranslateMetadata::get($aggregateGroupId, 'name'); $oldDesc = TranslateMetadata::get($aggregateGroupId, 'description'); // Error if the label exists already $exists = MessageGroups::labelExists($name); if ($exists && $oldName !== $name) { $this->dieUsage('Message group name already exists', 'duplicateaggregategroup'); } if ($oldName === $name && $oldDesc === $desc) { $this->dieUsage('Invalid update', 'invalidupdate'); } TranslateMetadata::set($aggregateGroupId, 'name', $name); TranslateMetadata::set($aggregateGroupId, 'description', $desc); } // If we got this far, nothing has failed $output['result'] = 'ok'; $this->getResult()->addValue(null, $this->getModuleName(), $output); // Cache needs to be cleared after any changes to groups MessageGroups::singleton()->recache(); MessageIndexRebuildJob::newJob()->insert(); }
/** * Filter an array of languages based on whether a priority set of * languages present for the passed group. If priority languages are * present, to that list add languages with more than 0% translation. * @param $languages Array of Languages to be filtered * @param $group * @param $cache */ protected function filterPriorityLangs(&$languages, $group, $cache) { $filterLangs = TranslateMetadata::get($group, 'prioritylangs'); if (strlen($filterLangs) === 0) { // No restrictions, keep everything return; } $filter = array_flip(explode(',', $filterLangs)); foreach ($languages as $id => $code) { if (isset($filter[$code])) { continue; } $translated = $cache[$code][1]; if ($translated === 0) { unset($languages[$id]); } } }
/** * 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 static function hideRestrictedFromStats($id, $code) { $filterLangs = TranslateMetadata::get($id, 'prioritylangs'); if (strlen($filterLangs) === 0) { // No restrictions, keep everything return true; } $filter = array_flip(explode(',', $filterLangs)); // If the language is in the list, return true to not hide it return isset($filter[$code]); }