/** * test strip of subpages to avoid unnecessary queries * against the never existing username * * @dataProvider provideUserGenders * @covers GenderCache::getGenderOf */ public function testStripSubpages($userKey, $expectedGender) { $username = isset(self::$nameMap[$userKey]) ? self::$nameMap[$userKey] : $userKey; $genderCache = GenderCache::singleton(); $gender = $genderCache->getGenderOf("{$username}/subpage"); $this->assertEquals($gender, $expectedGender, "GenderCache must strip of subpages"); }
/** * Initialize any services we'll need (unless it has already been provided via a setter). * This allows for dependency injection even though we don't control object creation. */ private function initServices() { global $wgContLang; if (!$this->linkRenderer) { $titleFormatter = new MediaWikiTitleCodec($wgContLang, GenderCache::singleton()); $this->linkRenderer = new MediaWikiPageLinkRenderer($titleFormatter); } }
/** * Initialize any services we'll need (unless it has already been provided via a setter). * This allows for dependency injection even though we don't control object creation. */ private function initServices() { if (!$this->linkRenderer) { $lang = $this->getContext()->getLanguage(); $titleFormatter = new MediaWikiTitleCodec($lang, GenderCache::singleton()); $this->linkRenderer = new MediaWikiPageLinkRenderer($titleFormatter); } }
/** * Initialize any services we'll need (unless it has already been provided via a setter). * This allows for dependency injection even though we don't control object creation. */ private function initServices() { global $wgLanguageCode; if (!$this->linkRenderer) { $lang = Language::factory($wgLanguageCode); $titleFormatter = new MediaWikiTitleCodec($lang, GenderCache::singleton()); $this->linkRenderer = new MediaWikiPageLinkRenderer($titleFormatter); } }
/** * Do (and cache) {{GENDER:...}} information for userpages in this LinkBatch * * @return bool whether the query was successful */ public function doGenderQuery() { if ($this->isEmpty()) { return false; } global $wgContLang; if (!$wgContLang->needsGenderDistinction()) { return false; } $genderCache = GenderCache::singleton(); $genderCache->doLinkBatch($this->data, $this->caller); return true; }
/** * Prepare a list of titles on a user's watchlist (excluding talk pages) * and return an array of (prefixed) strings * * @return array */ private function getWatchlist() { $list = array(); $index = $this->getRequest()->wasPosted() ? DB_MASTER : DB_SLAVE; $dbr = wfGetDB($index); $res = $dbr->select('watchlist', array('wl_namespace', 'wl_title'), array('wl_user' => $this->getUser()->getId()), __METHOD__); if ($res->numRows() > 0) { /** @var Title[] $titles */ $titles = array(); foreach ($res as $row) { $title = Title::makeTitleSafe($row->wl_namespace, $row->wl_title); if ($this->checkTitle($title, $row->wl_namespace, $row->wl_title) && !$title->isTalkPage()) { $titles[] = $title; } } $res->free(); GenderCache::singleton()->doTitlesArray($titles); foreach ($titles as $title) { $list[] = $title->getPrefixedText(); } } $this->cleanupWatchlist(); return $list; }
/** * @param Parser $parser * @param string $username * @return string */ public static function gender($parser, $username) { $forms = array_slice(func_get_args(), 2); // Some shortcuts to avoid loading user data unnecessarily if (count($forms) === 0) { return ''; } elseif (count($forms) === 1) { return $forms[0]; } $username = trim($username); // default $gender = User::getDefaultOption('gender'); // allow prefix. $title = Title::newFromText($username); if ($title && $title->getNamespace() == NS_USER) { $username = $title->getText(); } // check parameter, or use the ParserOptions if in interface message $user = User::newFromName($username); if ($user) { $gender = GenderCache::singleton()->getGenderOf($user, __METHOD__); } elseif ($username === '' && $parser->getOptions()->getInterfaceMessage()) { $gender = GenderCache::singleton()->getGenderOf($parser->getOptions()->getUser(), __METHOD__); } $ret = $parser->getFunctionLang()->gender($gender, $forms); return $ret; }
/** * Get the namespace text * * @return String: Namespace text */ public function getNsText() { global $wgContLang; if ($this->mInterwiki != '') { // This probably shouldn't even happen. ohh man, oh yuck. // But for interwiki transclusion it sometimes does. // Shit. Shit shit shit. // // Use the canonical namespaces if possible to try to // resolve a foreign namespace. if (MWNamespace::exists($this->mNamespace)) { return MWNamespace::getCanonicalName($this->mNamespace); } } // Strip off subpages $pagename = $this->getText(); if (strpos($pagename, '/') !== false) { list($username, ) = explode('/', $pagename, 2); } else { $username = $pagename; } if ($wgContLang->needsGenderDistinction() && MWNamespace::hasGenderDistinction($this->mNamespace)) { $gender = GenderCache::singleton()->getGenderOf($username, __METHOD__); return $wgContLang->getGenderNsText($this->mNamespace, $gender); } return $wgContLang->getNsText($this->mNamespace); }
/** * @param $resultPageSet ApiPageSet * @return void */ private function run($resultPageSet = null) { $db = $this->getDB(); $params = $this->extractRequestParams(); // Page filters $this->addTables('page'); if (!is_null($params['continue'])) { $cont = explode('|', $params['continue']); $this->dieContinueUsageIf(count($cont) != 1); $op = $params['dir'] == 'descending' ? '<' : '>'; $cont_from = $db->addQuotes($cont[0]); $this->addWhere("page_title {$op}= {$cont_from}"); } if ($params['filterredir'] == 'redirects') { $this->addWhereFld('page_is_redirect', 1); } elseif ($params['filterredir'] == 'nonredirects') { $this->addWhereFld('page_is_redirect', 0); } $this->addWhereFld('page_namespace', $params['namespace']); $dir = $params['dir'] == 'descending' ? 'older' : 'newer'; $from = $params['from'] === null ? null : $this->titlePartToKey($params['from'], $params['namespace']); $to = $params['to'] === null ? null : $this->titlePartToKey($params['to'], $params['namespace']); $this->addWhereRange('page_title', $dir, $from, $to); if (isset($params['prefix'])) { $this->addWhere('page_title' . $db->buildLike($this->titlePartToKey($params['prefix'], $params['namespace']), $db->anyString())); } if (is_null($resultPageSet)) { $selectFields = array('page_namespace', 'page_title', 'page_id'); } else { $selectFields = $resultPageSet->getPageTableFields(); } $this->addFields($selectFields); $forceNameTitleIndex = true; if (isset($params['minsize'])) { $this->addWhere('page_len>=' . intval($params['minsize'])); $forceNameTitleIndex = false; } if (isset($params['maxsize'])) { $this->addWhere('page_len<=' . intval($params['maxsize'])); $forceNameTitleIndex = false; } // Page protection filtering if (count($params['prtype']) || $params['prexpiry'] != 'all') { $this->addTables('page_restrictions'); $this->addWhere('page_id=pr_page'); $this->addWhere("pr_expiry > {$db->addQuotes($db->timestamp())} OR pr_expiry IS NULL"); if (count($params['prtype'])) { $this->addWhereFld('pr_type', $params['prtype']); if (isset($params['prlevel'])) { // Remove the empty string and '*' from the prlevel array $prlevel = array_diff($params['prlevel'], array('', '*')); if (count($prlevel)) { $this->addWhereFld('pr_level', $prlevel); } } if ($params['prfiltercascade'] == 'cascading') { $this->addWhereFld('pr_cascade', 1); } elseif ($params['prfiltercascade'] == 'noncascading') { $this->addWhereFld('pr_cascade', 0); } } $forceNameTitleIndex = false; if ($params['prexpiry'] == 'indefinite') { $this->addWhere("pr_expiry = {$db->addQuotes($db->getInfinity())} OR pr_expiry IS NULL"); } elseif ($params['prexpiry'] == 'definite') { $this->addWhere("pr_expiry != {$db->addQuotes($db->getInfinity())}"); } $this->addOption('DISTINCT'); } elseif (isset($params['prlevel'])) { $this->dieUsage('prlevel may not be used without prtype', 'params'); } if ($params['filterlanglinks'] == 'withoutlanglinks') { $this->addTables('langlinks'); $this->addJoinConds(array('langlinks' => array('LEFT JOIN', 'page_id=ll_from'))); $this->addWhere('ll_from IS NULL'); $forceNameTitleIndex = false; } elseif ($params['filterlanglinks'] == 'withlanglinks') { $this->addTables('langlinks'); $this->addWhere('page_id=ll_from'); $this->addOption('STRAIGHT_JOIN'); // We have to GROUP BY all selected fields to stop // PostgreSQL from whining $this->addOption('GROUP BY', $selectFields); $forceNameTitleIndex = false; } if ($forceNameTitleIndex) { $this->addOption('USE INDEX', 'name_title'); } $limit = $params['limit']; $this->addOption('LIMIT', $limit + 1); $res = $this->select(__METHOD__); //Get gender information if (MWNamespace::hasGenderDistinction($params['namespace'])) { $users = array(); foreach ($res as $row) { $users[] = $row->page_title; } GenderCache::singleton()->doQuery($users, __METHOD__); $res->rewind(); //reset } $count = 0; $result = $this->getResult(); foreach ($res as $row) { if (++$count > $limit) { // We've reached the one extra which shows that there are // additional pages to be had. Stop here... $this->setContinueEnumParameter('continue', $row->page_title); break; } if (is_null($resultPageSet)) { $title = Title::makeTitle($row->page_namespace, $row->page_title); $vals = array('pageid' => intval($row->page_id), 'ns' => intval($title->getNamespace()), 'title' => $title->getPrefixedText()); $fit = $result->addValue(array('query', $this->getModuleName()), null, $vals); if (!$fit) { $this->setContinueEnumParameter('continue', $row->page_title); break; } } else { $resultPageSet->processDbRow($row); } } if (is_null($resultPageSet)) { $result->setIndexedTagName_internal(array('query', $this->getModuleName()), 'p'); } }
/** * Given an array of title strings, convert them into Title objects. * Alternatively, an array of Title objects may be given. * This method validates access rights for the title, * and appends normalization values to the output. * * @param array $titles Array of Title objects or strings * @return LinkBatch */ private function processTitlesArray($titles) { $usernames = array(); $linkBatch = new LinkBatch(); foreach ($titles as $title) { if (is_string($title)) { try { $titleObj = Title::newFromTextThrow($title, $this->mDefaultNamespace); } catch (MalformedTitleException $ex) { // Handle invalid titles gracefully $this->mAllPages[0][$title] = $this->mFakePageId; $this->mInvalidTitles[$this->mFakePageId] = array('title' => $title, 'invalidreason' => $ex->getMessage()); $this->mFakePageId--; continue; // There's nothing else we can do } } else { $titleObj = $title; } $unconvertedTitle = $titleObj->getPrefixedText(); $titleWasConverted = false; if ($titleObj->isExternal()) { // This title is an interwiki link. $this->mInterwikiTitles[$unconvertedTitle] = $titleObj->getInterwiki(); } else { // Variants checking global $wgContLang; if ($this->mConvertTitles && count($wgContLang->getVariants()) > 1 && !$titleObj->exists()) { // Language::findVariantLink will modify titleText and titleObj into // the canonical variant if possible $titleText = is_string($title) ? $title : $titleObj->getPrefixedText(); $wgContLang->findVariantLink($titleText, $titleObj); $titleWasConverted = $unconvertedTitle !== $titleObj->getPrefixedText(); } if ($titleObj->getNamespace() < 0) { // Handle Special and Media pages $titleObj = $titleObj->fixSpecialName(); $this->mSpecialTitles[$this->mFakePageId] = $titleObj; $this->mFakePageId--; } else { // Regular page $linkBatch->addObj($titleObj); } } // Make sure we remember the original title that was // given to us. This way the caller can correlate new // titles with the originally requested when e.g. the // namespace is localized or the capitalization is // different if ($titleWasConverted) { $this->mConvertedTitles[$unconvertedTitle] = $titleObj->getPrefixedText(); // In this case the page can't be Special. if (is_string($title) && $title !== $unconvertedTitle) { $this->mNormalizedTitles[$title] = $unconvertedTitle; } } elseif (is_string($title) && $title !== $titleObj->getPrefixedText()) { $this->mNormalizedTitles[$title] = $titleObj->getPrefixedText(); } // Need gender information if (MWNamespace::hasGenderDistinction($titleObj->getNamespace())) { $usernames[] = $titleObj->getText(); } } // Get gender information $genderCache = GenderCache::singleton(); $genderCache->doQuery($usernames, __METHOD__); return $linkBatch; }
private function getTitleFormatter() { return new MediaWikiTitleCodec(Language::factory('en'), GenderCache::singleton()); }
/** * gender handler */ function gender($lang, $args) { $this->checkType('gender', 1, $args[0], 'string'); $username = trim(array_shift($args)); if (is_array($args[0])) { $args = $args[0]; } $forms = array_values(array_map('strval', $args)); // Shortcuts if (count($forms) === 0) { return ''; } elseif (count($forms) === 1) { return $forms[0]; } if ($username === 'male' || $username === 'female') { $gender = $username; } else { // default $gender = User::getDefaultOption('gender'); // Check for "User:" prefix $title = Title::newFromText($username); if ($title && $title->getNamespace() == NS_USER) { $username = $title->getText(); } // check parameter, or use the ParserOptions if in interface message $user = User::newFromName($username); if ($user) { $gender = GenderCache::singleton()->getGenderOf($user, __METHOD__); } elseif ($username === '') { $parserOptions = $this->getParserOptions(); if ($parserOptions->getInterfaceMessage()) { $gender = GenderCache::singleton()->getGenderOf($parserOptions->getUser(), __METHOD__); } } } return array($lang->gender($gender, $forms)); }
/** * Get the namespace text * * @return String: Namespace text */ public function getNsText() { global $wgContLang; if ($this->isExternal()) { // This probably shouldn't even happen. ohh man, oh yuck. // But for interwiki transclusion it sometimes does. // Shit. Shit shit shit. // // Use the canonical namespaces if possible to try to // resolve a foreign namespace. if (MWNamespace::exists($this->mNamespace)) { return MWNamespace::getCanonicalName($this->mNamespace); } } if ($wgContLang->needsGenderDistinction() && MWNamespace::hasGenderDistinction($this->mNamespace)) { $gender = GenderCache::singleton()->getGenderOf($this->getText(), __METHOD__); return $wgContLang->getGenderNsText($this->mNamespace, $gender); } return $wgContLang->getNsText($this->mNamespace); }
/** * Given an array of title strings, convert them into Title objects. * Alternativelly, an array of Title objects may be given. * This method validates access rights for the title, * and appends normalization values to the output. * * @param $titles array of Title objects or strings * @return LinkBatch */ private function processTitlesArray($titles) { $genderCache = GenderCache::singleton(); $genderCache->doTitlesArray($titles, __METHOD__); $linkBatch = new LinkBatch(); foreach ($titles as $title) { $titleObj = is_string($title) ? Title::newFromText($title) : $title; if (!$titleObj) { // Handle invalid titles gracefully $this->mAllpages[0][$title] = $this->mFakePageId; $this->mInvalidTitles[$this->mFakePageId] = $title; $this->mFakePageId--; continue; // There's nothing else we can do } $unconvertedTitle = $titleObj->getPrefixedText(); $titleWasConverted = false; $iw = $titleObj->getInterwiki(); if (strval($iw) !== '') { // This title is an interwiki link. $this->mInterwikiTitles[$titleObj->getPrefixedText()] = $iw; } else { // Variants checking global $wgContLang; if ($this->mConvertTitles && count($wgContLang->getVariants()) > 1 && !$titleObj->exists()) { // Language::findVariantLink will modify titleObj into // the canonical variant if possible $wgContLang->findVariantLink($title, $titleObj); $titleWasConverted = $unconvertedTitle !== $titleObj->getPrefixedText(); } if ($titleObj->getNamespace() < 0) { // Handle Special and Media pages $titleObj = $titleObj->fixSpecialName(); $this->mSpecialTitles[$this->mFakePageId] = $titleObj; $this->mFakePageId--; } else { // Regular page $linkBatch->addObj($titleObj); } } // Make sure we remember the original title that was // given to us. This way the caller can correlate new // titles with the originally requested when e.g. the // namespace is localized or the capitalization is // different if ($titleWasConverted) { $this->mConvertedTitles[$title] = $titleObj->getPrefixedText(); } elseif (is_string($title) && $title !== $titleObj->getPrefixedText()) { $this->mNormalizedTitles[$title] = $titleObj->getPrefixedText(); } } return $linkBatch; }
/** * @param ApiPageSet $resultPageSet * @return void */ private function run($resultPageSet = null) { $db = $this->getDB(); $params = $this->extractRequestParams(); // Page filters $this->addTables('page'); if (!is_null($params['continue'])) { $cont = explode('|', $params['continue']); $this->dieContinueUsageIf(count($cont) != 1); $op = $params['dir'] == 'descending' ? '<' : '>'; $cont_from = $db->addQuotes($cont[0]); $this->addWhere("page_title {$op}= {$cont_from}"); } if ($params['filterredir'] == 'redirects') { $this->addWhereFld('page_is_redirect', 1); } elseif ($params['filterredir'] == 'nonredirects') { $this->addWhereFld('page_is_redirect', 0); } $this->addWhereFld('page_namespace', $params['namespace']); $dir = $params['dir'] == 'descending' ? 'older' : 'newer'; $from = $params['from'] === null ? null : $this->titlePartToKey($params['from'], $params['namespace']); $to = $params['to'] === null ? null : $this->titlePartToKey($params['to'], $params['namespace']); $this->addWhereRange('page_title', $dir, $from, $to); if (isset($params['prefix'])) { $this->addWhere('page_title' . $db->buildLike($this->titlePartToKey($params['prefix'], $params['namespace']), $db->anyString())); } if (is_null($resultPageSet)) { $selectFields = ['page_namespace', 'page_title', 'page_id']; } else { $selectFields = $resultPageSet->getPageTableFields(); } $this->addFields($selectFields); $forceNameTitleIndex = true; if (isset($params['minsize'])) { $this->addWhere('page_len>=' . intval($params['minsize'])); $forceNameTitleIndex = false; } if (isset($params['maxsize'])) { $this->addWhere('page_len<=' . intval($params['maxsize'])); $forceNameTitleIndex = false; } // Page protection filtering if (count($params['prtype']) || $params['prexpiry'] != 'all') { $this->addTables('page_restrictions'); $this->addWhere('page_id=pr_page'); $this->addWhere("pr_expiry > {$db->addQuotes($db->timestamp())} OR pr_expiry IS NULL"); if (count($params['prtype'])) { $this->addWhereFld('pr_type', $params['prtype']); if (isset($params['prlevel'])) { // Remove the empty string and '*' from the prlevel array $prlevel = array_diff($params['prlevel'], ['', '*']); if (count($prlevel)) { $this->addWhereFld('pr_level', $prlevel); } } if ($params['prfiltercascade'] == 'cascading') { $this->addWhereFld('pr_cascade', 1); } elseif ($params['prfiltercascade'] == 'noncascading') { $this->addWhereFld('pr_cascade', 0); } } $forceNameTitleIndex = false; if ($params['prexpiry'] == 'indefinite') { $this->addWhere("pr_expiry = {$db->addQuotes($db->getInfinity())} OR pr_expiry IS NULL"); } elseif ($params['prexpiry'] == 'definite') { $this->addWhere("pr_expiry != {$db->addQuotes($db->getInfinity())}"); } $this->addOption('DISTINCT'); } elseif (isset($params['prlevel'])) { $this->dieUsage('prlevel may not be used without prtype', 'params'); } if ($params['filterlanglinks'] == 'withoutlanglinks') { $this->addTables('langlinks'); $this->addJoinConds(['langlinks' => ['LEFT JOIN', 'page_id=ll_from']]); $this->addWhere('ll_from IS NULL'); $forceNameTitleIndex = false; } elseif ($params['filterlanglinks'] == 'withlanglinks') { $this->addTables('langlinks'); $this->addWhere('page_id=ll_from'); $this->addOption('STRAIGHT_JOIN'); // MySQL filesorts if we use a GROUP BY that works with the rules // in the 1992 SQL standard (it doesn't like having the // constant-in-WHERE page_namespace column in there). Using the // 1999 rules works fine, but that breaks other DBs. Sigh. /// @todo Once we drop support for 1992-rule DBs, we can simplify this. $dbType = $db->getType(); if ($dbType === 'mysql' || $dbType === 'sqlite') { // Ignore the rules, or 1999 rules if you count unique keys // over non-NULL columns as satisfying the requirement for // "functional dependency" and don't require including // constant-in-WHERE columns in the GROUP BY. $this->addOption('GROUP BY', ['page_title']); } elseif ($dbType === 'postgres' && $db->getServerVersion() >= 9.1) { // 1999 rules only counting primary keys $this->addOption('GROUP BY', ['page_title', 'page_id']); } else { // 1992 rules $this->addOption('GROUP BY', $selectFields); } $forceNameTitleIndex = false; } if ($forceNameTitleIndex) { $this->addOption('USE INDEX', 'name_title'); } $limit = $params['limit']; $this->addOption('LIMIT', $limit + 1); $res = $this->select(__METHOD__); // Get gender information if (MWNamespace::hasGenderDistinction($params['namespace'])) { $users = []; foreach ($res as $row) { $users[] = $row->page_title; } GenderCache::singleton()->doQuery($users, __METHOD__); $res->rewind(); // reset } $count = 0; $result = $this->getResult(); foreach ($res as $row) { if (++$count > $limit) { // We've reached the one extra which shows that there are // additional pages to be had. Stop here... $this->setContinueEnumParameter('continue', $row->page_title); break; } if (is_null($resultPageSet)) { $title = Title::makeTitle($row->page_namespace, $row->page_title); $vals = ['pageid' => intval($row->page_id), 'ns' => intval($title->getNamespace()), 'title' => $title->getPrefixedText()]; $fit = $result->addValue(['query', $this->getModuleName()], null, $vals); if (!$fit) { $this->setContinueEnumParameter('continue', $row->page_title); break; } } else { $resultPageSet->processDbRow($row); } } if (is_null($resultPageSet)) { $result->addIndexedTagName(['query', $this->getModuleName()], 'p'); } }
/** * B/C kludge: provide a TitleParser for use by Title. * Ideally, Title would have no methods that need this. * Avoid usage of this singleton by using TitleValue * and the associated services when possible. * * @return TitleParser */ private static function getTitleParser() { global $wgContLang, $wgLocalInterwikis; static $titleCodec = null; static $titleCodecFingerprint = null; // $wgContLang and $wgLocalInterwikis may change (especially while testing), // make sure we are using the right one. To detect changes over the course // of a request, we remember a fingerprint of the config used to create the // codec singleton, and re-create it if the fingerprint doesn't match. $fingerprint = spl_object_hash($wgContLang) . '|' . join('+', $wgLocalInterwikis); if ($fingerprint !== $titleCodecFingerprint) { $titleCodec = null; } if (!$titleCodec) { $titleCodec = new MediaWikiTitleCodec($wgContLang, GenderCache::singleton(), $wgLocalInterwikis); $titleCodecFingerprint = $fingerprint; } return $titleCodec; }
/** * test strip of subpages to avoid unnecessary queries * against the never existing username * * @dataProvider dataStripSubpages */ function testStripSubpages($pageWithSubpage, $expectedGender) { $genderCache = GenderCache::singleton(); $gender = $genderCache->getGenderOf($pageWithSubpage); $this->assertEquals($gender, $expectedGender, "GenderCache must strip of subpages"); }
/** * Prepare a list of titles on a user's watchlist (excluding talk pages) * and return an array of (prefixed) strings * * @return array */ private function getWatchlist() { $list = []; $watchedItems = MediaWikiServices::getInstance()->getWatchedItemStore()->getWatchedItemsForUser($this->getUser(), ['forWrite' => $this->getRequest()->wasPosted()]); if ($watchedItems) { /** @var Title[] $titles */ $titles = []; foreach ($watchedItems as $watchedItem) { $namespace = $watchedItem->getLinkTarget()->getNamespace(); $dbKey = $watchedItem->getLinkTarget()->getDBkey(); $title = Title::makeTitleSafe($namespace, $dbKey); if ($this->checkTitle($title, $namespace, $dbKey) && !$title->isTalkPage()) { $titles[] = $title; } } GenderCache::singleton()->doTitlesArray($titles); foreach ($titles as $title) { $list[] = $title->getPrefixedText(); } } $this->cleanupWatchlist(); return $list; }