/**
  * 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]);
 }