function execute($par) { // Global array containing names of tracking categories global $wgTrackingCategories; $this->setHeaders(); $this->outputHeader(); $this->getOutput()->allowClickjacking(); $this->getOutput()->addHTML(Html::openElement('table', array('class' => 'mw-datatable TablePager', 'id' => 'mw-trackingcategories-table')) . "\n" . "<thead><tr>\n\t\t\t<th>" . $this->msg('trackingcategories-msg')->escaped() . "\n\t\t\t</th>\n\t\t\t<th>" . $this->msg('trackingcategories-name')->escaped() . "</th>\n\t\t\t<th>" . $this->msg('trackingcategories-desc')->escaped() . "\n\t\t\t</th>\n\t\t\t</tr></thead>"); foreach ($wgTrackingCategories as $catMsg) { /* * Check if the tracking category varies by namespace * Otherwise only pages in the current namespace will be displayed * If it does vary, show pages considering all namespaces */ $msgObj = $this->msg($catMsg)->inContentLanguage(); $allMsgs = array(); $catDesc = $catMsg . '-desc'; $catMsgTitle = Title::makeTitleSafe(NS_MEDIAWIKI, $catMsg); $catMsgTitleText = Linker::link($catMsgTitle, htmlspecialchars($catMsg)); if (strpos($msgObj->plain(), '{{NAMESPACE}}') !== false) { $ns = MWNamespace::getValidNamespaces(); foreach ($ns as $namesp) { $tempTitle = Title::makeTitleSafe($namesp, $catMsg); $catName = $msgObj->title($tempTitle)->text(); if (!$msgObj->isDisabled()) { $catTitle = Title::makeTitleSafe(NS_CATEGORY, $catName); $catTitleText = Linker::link($catTitle, htmlspecialchars($catName)); $allMsgs[] = $catTitleText; } } } else { $catName = $msgObj->text(); if (!$msgObj->isDisabled()) { $catTitle = Title::makeTitleSafe(NS_CATEGORY, $catName); $catTitleText = Linker::link($catTitle, htmlspecialchars($catName)); $classes = array(); } else { $catTitleText = $this->msg('trackingcategories-disabled')->parse(); } $allMsgs[] = $catTitleText; } /* * Show category description if it exists as a system message * as category-name-desc */ $descMsg = $this->msg($catDesc); if ($descMsg->isBlank()) { $descMsg = $this->msg('trackingcategories-nodesc'); } $this->getOutput()->addHTML(Html::openElement('tr') . Html::openElement('td', array('class' => 'mw-trackingcategories-name')) . $this->getLanguage()->commaList(array_unique($allMsgs)) . Html::closeElement('td') . Html::openElement('td', array('class' => 'mw-trackingcategories-msg')) . $catMsgTitleText . Html::closeElement('td') . Html::openElement('td', array('class' => 'mw-trackingcategories-desc')) . $descMsg->parse() . Html::closeElement('td') . Html::closeElement('tr')); } $this->getOutput()->addHTML(Html::closeElement('table')); }
/** * Recursively-called function to actually construct the help * * @param IContextSource $context * @param ApiBase[] $modules * @param array $options * @param array &$haveModules * @return string */ private static function getHelpInternal(IContextSource $context, array $modules, array $options, &$haveModules) { $out = ''; $level = empty($options['headerlevel']) ? 2 : $options['headerlevel']; if (empty($options['tocnumber'])) { $tocnumber = array(2 => 0); } else { $tocnumber =& $options['tocnumber']; } foreach ($modules as $module) { $tocnumber[$level]++; $path = $module->getModulePath(); $module->setContext($context); $help = array('header' => '', 'flags' => '', 'description' => '', 'help-urls' => '', 'parameters' => '', 'examples' => '', 'submodules' => ''); if (empty($options['noheader']) || !empty($options['toc'])) { $anchor = $path; $i = 1; while (isset($haveModules[$anchor])) { $anchor = $path . '|' . ++$i; } if ($module->isMain()) { $header = $context->msg('api-help-main-header')->parse(); } else { $name = $module->getModuleName(); $header = $module->getParent()->getModuleManager()->getModuleGroup($name) . "={$name}"; if ($module->getModulePrefix() !== '') { $header .= ' ' . $context->msg('parentheses', $module->getModulePrefix())->parse(); } } $haveModules[$anchor] = array('toclevel' => count($tocnumber), 'level' => $level, 'anchor' => $anchor, 'line' => $header, 'number' => join('.', $tocnumber), 'index' => false); if (empty($options['noheader'])) { $help['header'] .= Html::element('h' . min(6, $level), array('id' => $anchor, 'class' => 'apihelp-header'), $header); } } else { $haveModules[$path] = true; } $links = array(); $any = false; for ($m = $module; $m !== null; $m = $m->getParent()) { $name = $m->getModuleName(); if ($name === 'main_int') { $name = 'main'; } if (count($modules) === 1 && $m === $modules[0] && !(!empty($options['submodules']) && $m->getModuleManager())) { $link = Html::element('b', null, $name); } else { $link = SpecialPage::getTitleFor('ApiHelp', $m->getModulePath())->getLocalURL(); $link = Html::element('a', array('href' => $link, 'class' => 'apihelp-linktrail'), $name); $any = true; } array_unshift($links, $link); } if ($any) { $help['header'] .= self::wrap($context->msg('parentheses')->rawParams($context->getLanguage()->pipeList($links)), 'apihelp-linktrail', 'div'); } $flags = $module->getHelpFlags(); $help['flags'] .= Html::openElement('div', array('class' => 'apihelp-block apihelp-flags')); $msg = $context->msg('api-help-flags'); if (!$msg->isDisabled()) { $help['flags'] .= self::wrap($msg->numParams(count($flags)), 'apihelp-block-head', 'div'); } $help['flags'] .= Html::openElement('ul'); foreach ($flags as $flag) { $help['flags'] .= Html::rawElement('li', null, self::wrap($context->msg("api-help-flag-{$flag}"), "apihelp-flag-{$flag}")); } $sourceInfo = $module->getModuleSourceInfo(); if ($sourceInfo) { if (isset($sourceInfo['namemsg'])) { $extname = $context->msg($sourceInfo['namemsg'])->text(); } else { $extname = $sourceInfo['name']; } $help['flags'] .= Html::rawElement('li', null, self::wrap($context->msg('api-help-source', $extname, $sourceInfo['name']), 'apihelp-source')); $link = SpecialPage::getTitleFor('Version', 'License/' . $sourceInfo['name']); if (isset($sourceInfo['license-name'])) { $msg = $context->msg('api-help-license', $link, $sourceInfo['license-name']); } elseif (SpecialVersion::getExtLicenseFileName(dirname($sourceInfo['path']))) { $msg = $context->msg('api-help-license-noname', $link); } else { $msg = $context->msg('api-help-license-unknown'); } $help['flags'] .= Html::rawElement('li', null, self::wrap($msg, 'apihelp-license')); } else { $help['flags'] .= Html::rawElement('li', null, self::wrap($context->msg('api-help-source-unknown'), 'apihelp-source')); $help['flags'] .= Html::rawElement('li', null, self::wrap($context->msg('api-help-license-unknown'), 'apihelp-license')); } $help['flags'] .= Html::closeElement('ul'); $help['flags'] .= Html::closeElement('div'); foreach ($module->getFinalDescription() as $msg) { $msg->setContext($context); $help['description'] .= $msg->parseAsBlock(); } $urls = $module->getHelpUrls(); if ($urls) { $help['help-urls'] .= Html::openElement('div', array('class' => 'apihelp-block apihelp-help-urls')); $msg = $context->msg('api-help-help-urls'); if (!$msg->isDisabled()) { $help['help-urls'] .= self::wrap($msg->numParams(count($urls)), 'apihelp-block-head', 'div'); } if (!is_array($urls)) { $urls = array($urls); } $help['help-urls'] .= Html::openElement('ul'); foreach ($urls as $url) { $help['help-urls'] .= Html::rawElement('li', null, Html::element('a', array('href' => $url), $url)); } $help['help-urls'] .= Html::closeElement('ul'); $help['help-urls'] .= Html::closeElement('div'); } $params = $module->getFinalParams(ApiBase::GET_VALUES_FOR_HELP); $dynamicParams = $module->dynamicParameterDocumentation(); $groups = array(); if ($params || $dynamicParams !== null) { $help['parameters'] .= Html::openElement('div', array('class' => 'apihelp-block apihelp-parameters')); $msg = $context->msg('api-help-parameters'); if (!$msg->isDisabled()) { $help['parameters'] .= self::wrap($msg->numParams(count($params)), 'apihelp-block-head', 'div'); } $help['parameters'] .= Html::openElement('dl'); $descriptions = $module->getFinalParamDescription(); foreach ($params as $name => $settings) { if (!is_array($settings)) { $settings = array(ApiBase::PARAM_DFLT => $settings); } $help['parameters'] .= Html::element('dt', null, $module->encodeParamName($name)); // Add description $description = array(); if (isset($descriptions[$name])) { foreach ($descriptions[$name] as $msg) { $msg->setContext($context); $description[] = $msg->parseAsBlock(); } } // Add usage info $info = array(); // Required? if (!empty($settings[ApiBase::PARAM_REQUIRED])) { $info[] = $context->msg('api-help-param-required')->parse(); } // Custom info? if (!empty($settings[ApiBase::PARAM_HELP_MSG_INFO])) { foreach ($settings[ApiBase::PARAM_HELP_MSG_INFO] as $i) { $tag = array_shift($i); $info[] = $context->msg("apihelp-{$path}-paraminfo-{$tag}")->numParams(count($i))->params($context->getLanguage()->commaList($i))->params($module->getModulePrefix())->parse(); } } // Type documentation if (!isset($settings[ApiBase::PARAM_TYPE])) { $dflt = isset($settings[ApiBase::PARAM_DFLT]) ? $settings[ApiBase::PARAM_DFLT] : null; if (is_bool($dflt)) { $settings[ApiBase::PARAM_TYPE] = 'boolean'; } elseif (is_string($dflt) || is_null($dflt)) { $settings[ApiBase::PARAM_TYPE] = 'string'; } elseif (is_int($dflt)) { $settings[ApiBase::PARAM_TYPE] = 'integer'; } } if (isset($settings[ApiBase::PARAM_TYPE])) { $type = $settings[ApiBase::PARAM_TYPE]; $multi = !empty($settings[ApiBase::PARAM_ISMULTI]); $hintPipeSeparated = true; $count = ApiBase::LIMIT_SML2 + 1; if (is_array($type)) { $count = count($type); $links = isset($settings[ApiBase::PARAM_VALUE_LINKS]) ? $settings[ApiBase::PARAM_VALUE_LINKS] : array(); $type = array_map(function ($v) use($links) { $ret = wfEscapeWikiText($v); if (isset($links[$v])) { $ret = "[[{$links[$v]}|{$ret}]]"; } return $ret; }, $type); $i = array_search('', $type, true); if ($i === false) { $type = $context->getLanguage()->commaList($type); } else { unset($type[$i]); $type = $context->msg('api-help-param-list-can-be-empty')->numParams(count($type))->params($context->getLanguage()->commaList($type))->parse(); } $info[] = $context->msg('api-help-param-list')->params($multi ? 2 : 1)->params($type)->parse(); $hintPipeSeparated = false; } else { switch ($type) { case 'submodule': $groups[] = $name; if (isset($settings[ApiBase::PARAM_SUBMODULE_MAP])) { $map = $settings[ApiBase::PARAM_SUBMODULE_MAP]; ksort($map); $submodules = array(); foreach ($map as $v => $m) { $submodules[] = "[[Special:ApiHelp/{$m}|{$v}]]"; } } else { $submodules = $module->getModuleManager()->getNames($name); sort($submodules); $prefix = $module->isMain() ? '' : $module->getModulePath() . '+'; $submodules = array_map(function ($name) use($prefix) { return "[[Special:ApiHelp/{$prefix}{$name}|{$name}]]"; }, $submodules); } $count = count($submodules); $info[] = $context->msg('api-help-param-list')->params($multi ? 2 : 1)->params($context->getLanguage()->commaList($submodules))->parse(); $hintPipeSeparated = false; // No type message necessary, we have a list of values. $type = null; break; case 'namespace': $namespaces = MWNamespace::getValidNamespaces(); $count = count($namespaces); $info[] = $context->msg('api-help-param-list')->params($multi ? 2 : 1)->params($context->getLanguage()->commaList($namespaces))->parse(); $hintPipeSeparated = false; // No type message necessary, we have a list of values. $type = null; break; case 'limit': if (isset($settings[ApiBase::PARAM_MAX2])) { $info[] = $context->msg('api-help-param-limit2')->numParams($settings[ApiBase::PARAM_MAX])->numParams($settings[ApiBase::PARAM_MAX2])->parse(); } else { $info[] = $context->msg('api-help-param-limit')->numParams($settings[ApiBase::PARAM_MAX])->parse(); } break; case 'integer': // Possible messages: // api-help-param-integer-min, // api-help-param-integer-max, // api-help-param-integer-minmax $suffix = ''; $min = $max = 0; if (isset($settings[ApiBase::PARAM_MIN])) { $suffix .= 'min'; $min = $settings[ApiBase::PARAM_MIN]; } if (isset($settings[ApiBase::PARAM_MAX])) { $suffix .= 'max'; $max = $settings[ApiBase::PARAM_MAX]; } if ($suffix !== '') { $info[] = $context->msg("api-help-param-integer-{$suffix}")->params($multi ? 2 : 1)->numParams($min, $max)->parse(); } break; case 'upload': $info[] = $context->msg('api-help-param-upload')->parse(); // No type message necessary, api-help-param-upload should handle it. $type = null; break; case 'string': case 'text': // Displaying a type message here would be useless. $type = null; break; } } // Add type. Messages for grep: api-help-param-type-limit // api-help-param-type-integer api-help-param-type-boolean // api-help-param-type-timestamp api-help-param-type-user // api-help-param-type-password if (is_string($type)) { $msg = $context->msg("api-help-param-type-{$type}"); if (!$msg->isDisabled()) { $info[] = $msg->params($multi ? 2 : 1)->parse(); } } if ($multi) { $extra = array(); if ($hintPipeSeparated) { $extra[] = $context->msg('api-help-param-multi-separate')->parse(); } if ($count > ApiBase::LIMIT_SML1) { $extra[] = $context->msg('api-help-param-multi-max')->numParams(ApiBase::LIMIT_SML1, ApiBase::LIMIT_SML2)->parse(); } if ($extra) { $info[] = join(' ', $extra); } } } // Add default $default = isset($settings[ApiBase::PARAM_DFLT]) ? $settings[ApiBase::PARAM_DFLT] : null; if ($default === '') { $info[] = $context->msg('api-help-param-default-empty')->parse(); } elseif ($default !== null && $default !== false) { $info[] = $context->msg('api-help-param-default')->params(wfEscapeWikiText($default))->parse(); } if (!array_filter($description)) { $description = array(self::wrap($context->msg('api-help-param-no-description'), 'apihelp-empty')); } // Add "deprecated" flag if (!empty($settings[ApiBase::PARAM_DEPRECATED])) { $help['parameters'] .= Html::openElement('dd', array('class' => 'info')); $help['parameters'] .= self::wrap($context->msg('api-help-param-deprecated'), 'apihelp-deprecated', 'strong'); $help['parameters'] .= Html::closeElement('dd'); } if ($description) { $description = join('', $description); $description = preg_replace('!\\s*</([oud]l)>\\s*<\\1>\\s*!', "\n", $description); $help['parameters'] .= Html::rawElement('dd', array('class' => 'description'), $description); } foreach ($info as $i) { $help['parameters'] .= Html::rawElement('dd', array('class' => 'info'), $i); } } if ($dynamicParams !== null) { $dynamicParams = ApiBase::makeMessage($dynamicParams, $context, array($module->getModulePrefix(), $module->getModuleName(), $module->getModulePath())); $help['parameters'] .= Html::element('dt', null, '*'); $help['parameters'] .= Html::rawElement('dd', array('class' => 'description'), $dynamicParams->parse()); } $help['parameters'] .= Html::closeElement('dl'); $help['parameters'] .= Html::closeElement('div'); } $examples = $module->getExamplesMessages(); if ($examples) { $help['examples'] .= Html::openElement('div', array('class' => 'apihelp-block apihelp-examples')); $msg = $context->msg('api-help-examples'); if (!$msg->isDisabled()) { $help['examples'] .= self::wrap($msg->numParams(count($examples)), 'apihelp-block-head', 'div'); } $help['examples'] .= Html::openElement('dl'); foreach ($examples as $qs => $msg) { $msg = ApiBase::makeMessage($msg, $context, array($module->getModulePrefix(), $module->getModuleName(), $module->getModulePath())); $link = wfAppendQuery(wfScript('api'), $qs); $help['examples'] .= Html::rawElement('dt', null, $msg->parse()); $help['examples'] .= Html::rawElement('dd', null, Html::element('a', array('href' => $link), "api.php?{$qs}")); } $help['examples'] .= Html::closeElement('dl'); $help['examples'] .= Html::closeElement('div'); } $subtocnumber = $tocnumber; $subtocnumber[$level + 1] = 0; $suboptions = array('submodules' => $options['recursivesubmodules'], 'headerlevel' => $level + 1, 'tocnumber' => &$subtocnumber, 'noheader' => false) + $options; if ($options['submodules'] && $module->getModuleManager()) { $manager = $module->getModuleManager(); $submodules = array(); foreach ($groups as $group) { $names = $manager->getNames($group); sort($names); foreach ($names as $name) { $submodules[] = $manager->getModule($name); } } $help['submodules'] .= self::getHelpInternal($context, $submodules, $suboptions, $haveModules); } $module->modifyHelp($help, $suboptions, $haveModules); Hooks::run('APIHelpModifyOutput', array($module, &$help, $suboptions, &$haveModules)); $out .= join("\n", $help); } return $out; }
/** * @param ApiPageSet $resultPageSet * @return void */ protected function run(ApiPageSet $resultPageSet = null) { $db = $this->getDB(); $params = $this->extractRequestParams(false); $result = $this->getResult(); $this->requireMaxOneParameter($params, 'user', 'excludeuser'); // Namespace check is likely to be desired, but can't be done // efficiently in SQL. $miser_ns = null; $needPageTable = false; if ($params['namespace'] !== null) { $params['namespace'] = array_unique($params['namespace']); sort($params['namespace']); if ($params['namespace'] != MWNamespace::getValidNamespaces()) { $needPageTable = true; if ($this->getConfig()->get('MiserMode')) { $miser_ns = $params['namespace']; } else { $this->addWhere(array('page_namespace' => $params['namespace'])); } } } $this->addTables('revision'); if ($resultPageSet === null) { $this->parseParameters($params); $this->addTables('page'); $this->addJoinConds(array('page' => array('INNER JOIN', array('rev_page = page_id')))); $this->addFields(Revision::selectFields()); $this->addFields(Revision::selectPageFields()); // Review this depeneding on the outcome of T113901 $this->addOption('STRAIGHT_JOIN'); } else { $this->limit = $this->getParameter('limit') ?: 10; $this->addFields(array('rev_timestamp', 'rev_id')); if ($params['generatetitles']) { $this->addFields(array('rev_page')); } if ($needPageTable) { $this->addTables('page'); $this->addJoinConds(array('page' => array('INNER JOIN', array('rev_page = page_id')))); $this->addFieldsIf(array('page_namespace'), (bool) $miser_ns); // Review this depeneding on the outcome of T113901 $this->addOption('STRAIGHT_JOIN'); } } if ($this->fld_tags) { $this->addTables('tag_summary'); $this->addJoinConds(array('tag_summary' => array('LEFT JOIN', array('rev_id=ts_rev_id')))); $this->addFields('ts_tags'); } if ($this->fetchContent) { $this->addTables('text'); $this->addJoinConds(array('text' => array('INNER JOIN', array('rev_text_id=old_id')))); $this->addFields('old_id'); $this->addFields(Revision::selectTextFields()); } if ($params['user'] !== null) { $id = User::idFromName($params['user']); if ($id) { $this->addWhereFld('rev_user', $id); } else { $this->addWhereFld('rev_user_text', $params['user']); } } elseif ($params['excludeuser'] !== null) { $id = User::idFromName($params['excludeuser']); if ($id) { $this->addWhere('rev_user != ' . $id); } else { $this->addWhere('rev_user_text != ' . $db->addQuotes($params['excludeuser'])); } } if ($params['user'] !== null || $params['excludeuser'] !== null) { // Paranoia: avoid brute force searches (bug 17342) if (!$this->getUser()->isAllowed('deletedhistory')) { $bitmask = Revision::DELETED_USER; } elseif (!$this->getUser()->isAllowedAny('suppressrevision', 'viewsuppressed')) { $bitmask = Revision::DELETED_USER | Revision::DELETED_RESTRICTED; } else { $bitmask = 0; } if ($bitmask) { $this->addWhere($db->bitAnd('rev_deleted', $bitmask) . " != {$bitmask}"); } } $dir = $params['dir']; if ($params['continue'] !== null) { $op = $dir == 'newer' ? '>' : '<'; $cont = explode('|', $params['continue']); $this->dieContinueUsageIf(count($cont) != 2); $ts = $db->addQuotes($db->timestamp($cont[0])); $rev_id = (int) $cont[1]; $this->dieContinueUsageIf(strval($rev_id) !== $cont[1]); $this->addWhere("rev_timestamp {$op} {$ts} OR " . "(rev_timestamp = {$ts} AND " . "rev_id {$op}= {$rev_id})"); } $this->addOption('LIMIT', $this->limit + 1); $sort = $dir == 'newer' ? '' : ' DESC'; $orderby = array(); // Targeting index rev_timestamp, user_timestamp, or usertext_timestamp // But 'user' is always constant for the latter two, so it doesn't matter here. $orderby[] = "rev_timestamp {$sort}"; $orderby[] = "rev_id {$sort}"; $this->addOption('ORDER BY', $orderby); $res = $this->select(__METHOD__); $pageMap = array(); // Maps rev_page to array index $count = 0; $nextIndex = 0; $generated = array(); foreach ($res as $row) { if (++$count > $this->limit) { // We've had enough $this->setContinueEnumParameter('continue', "{$row->rev_timestamp}|{$row->rev_id}"); break; } // Miser mode namespace check if ($miser_ns !== null && !in_array($row->page_namespace, $miser_ns)) { continue; } if ($resultPageSet !== null) { if ($params['generatetitles']) { $generated[$row->rev_page] = $row->rev_page; } else { $generated[] = $row->rev_id; } } else { $revision = Revision::newFromRow($row); $rev = $this->extractRevisionInfo($revision, $row); if (!isset($pageMap[$row->rev_page])) { $index = $nextIndex++; $pageMap[$row->rev_page] = $index; $title = $revision->getTitle(); $a = array('pageid' => $title->getArticleID(), 'revisions' => array($rev)); ApiResult::setIndexedTagName($a['revisions'], 'rev'); ApiQueryBase::addTitleInfo($a, $title); $fit = $result->addValue(array('query', $this->getModuleName()), $index, $a); } else { $index = $pageMap[$row->rev_page]; $fit = $result->addValue(array('query', $this->getModuleName(), $index, 'revisions'), null, $rev); } if (!$fit) { $this->setContinueEnumParameter('continue', "{$row->rev_timestamp}|{$row->rev_id}"); break; } } } if ($resultPageSet !== null) { if ($params['generatetitles']) { $resultPageSet->populateFromPageIDs($generated); } else { $resultPageSet->populateFromRevisionIDs($generated); } } else { $result->addIndexedTagName(array('query', $this->getModuleName()), 'page'); } }
/** * @param ApiPageSet $resultPageSet * @return void */ protected function run(ApiPageSet $resultPageSet = null) { $user = $this->getUser(); // Before doing anything at all, let's check permissions if (!$user->isAllowed('deletedhistory')) { $this->dieUsage('You don\'t have permission to view deleted revision information', 'permissiondenied'); } $db = $this->getDB(); $params = $this->extractRequestParams(false); $result = $this->getResult(); // If the user wants no namespaces, they get no pages. if ($params['namespace'] === []) { if ($resultPageSet === null) { $result->addValue('query', $this->getModuleName(), []); } return; } // This module operates in two modes: // 'user': List deleted revs by a certain user // 'all': List all deleted revs in NS $mode = 'all'; if (!is_null($params['user'])) { $mode = 'user'; } if ($mode == 'user') { foreach (['from', 'to', 'prefix', 'excludeuser'] as $param) { if (!is_null($params[$param])) { $p = $this->getModulePrefix(); $this->dieUsage("The '{$p}{$param}' parameter cannot be used with '{$p}user'", 'badparams'); } } } else { foreach (['start', 'end'] as $param) { if (!is_null($params[$param])) { $p = $this->getModulePrefix(); $this->dieUsage("The '{$p}{$param}' parameter may only be used with '{$p}user'", 'badparams'); } } } // If we're generating titles only, we can use DISTINCT for a better // query. But we can't do that in 'user' mode (wrong index), and we can // only do it when sorting ASC (because MySQL apparently can't use an // index backwards for grouping even though it can for ORDER BY, WTF?) $dir = $params['dir']; $optimizeGenerateTitles = false; if ($mode === 'all' && $params['generatetitles'] && $resultPageSet !== null) { if ($dir === 'newer') { $optimizeGenerateTitles = true; } else { $p = $this->getModulePrefix(); $this->setWarning("For better performance when generating titles, set {$p}dir=newer"); } } $this->addTables('archive'); if ($resultPageSet === null) { $this->parseParameters($params); $this->addFields(Revision::selectArchiveFields()); $this->addFields(['ar_title', 'ar_namespace']); } else { $this->limit = $this->getParameter('limit') ?: 10; $this->addFields(['ar_title', 'ar_namespace']); if ($optimizeGenerateTitles) { $this->addOption('DISTINCT'); } else { $this->addFields(['ar_timestamp', 'ar_rev_id', 'ar_id']); } } if ($this->fld_tags) { $this->addTables('tag_summary'); $this->addJoinConds(['tag_summary' => ['LEFT JOIN', ['ar_rev_id=ts_rev_id']]]); $this->addFields('ts_tags'); } if (!is_null($params['tag'])) { $this->addTables('change_tag'); $this->addJoinConds(['change_tag' => ['INNER JOIN', ['ar_rev_id=ct_rev_id']]]); $this->addWhereFld('ct_tag', $params['tag']); } if ($this->fetchContent) { // Modern MediaWiki has the content for deleted revs in the 'text' // table using fields old_text and old_flags. But revisions deleted // pre-1.5 store the content in the 'archive' table directly using // fields ar_text and ar_flags, and no corresponding 'text' row. So // we have to LEFT JOIN and fetch all four fields. $this->addTables('text'); $this->addJoinConds(['text' => ['LEFT JOIN', ['ar_text_id=old_id']]]); $this->addFields(['ar_text', 'ar_flags', 'old_text', 'old_flags']); // This also means stricter restrictions if (!$user->isAllowedAny('undelete', 'deletedtext')) { $this->dieUsage('You don\'t have permission to view deleted revision content', 'permissiondenied'); } } $miser_ns = null; if ($mode == 'all') { if ($params['namespace'] !== null) { $namespaces = $params['namespace']; } else { $namespaces = MWNamespace::getValidNamespaces(); } $this->addWhereFld('ar_namespace', $namespaces); // For from/to/prefix, we have to consider the potential // transformations of the title in all specified namespaces. // Generally there will be only one transformation, but wikis with // some namespaces case-sensitive could have two. if ($params['from'] !== null || $params['to'] !== null) { $isDirNewer = $dir === 'newer'; $after = $isDirNewer ? '>=' : '<='; $before = $isDirNewer ? '<=' : '>='; $where = []; foreach ($namespaces as $ns) { $w = []; if ($params['from'] !== null) { $w[] = 'ar_title' . $after . $db->addQuotes($this->titlePartToKey($params['from'], $ns)); } if ($params['to'] !== null) { $w[] = 'ar_title' . $before . $db->addQuotes($this->titlePartToKey($params['to'], $ns)); } $w = $db->makeList($w, LIST_AND); $where[$w][] = $ns; } if (count($where) == 1) { $where = key($where); $this->addWhere($where); } else { $where2 = []; foreach ($where as $w => $ns) { $where2[] = $db->makeList([$w, 'ar_namespace' => $ns], LIST_AND); } $this->addWhere($db->makeList($where2, LIST_OR)); } } if (isset($params['prefix'])) { $where = []; foreach ($namespaces as $ns) { $w = 'ar_title' . $db->buildLike($this->titlePartToKey($params['prefix'], $ns), $db->anyString()); $where[$w][] = $ns; } if (count($where) == 1) { $where = key($where); $this->addWhere($where); } else { $where2 = []; foreach ($where as $w => $ns) { $where2[] = $db->makeList([$w, 'ar_namespace' => $ns], LIST_AND); } $this->addWhere($db->makeList($where2, LIST_OR)); } } } else { if ($this->getConfig()->get('MiserMode')) { $miser_ns = $params['namespace']; } else { $this->addWhereFld('ar_namespace', $params['namespace']); } $this->addTimestampWhereRange('ar_timestamp', $dir, $params['start'], $params['end']); } if (!is_null($params['user'])) { $this->addWhereFld('ar_user_text', $params['user']); } elseif (!is_null($params['excludeuser'])) { $this->addWhere('ar_user_text != ' . $db->addQuotes($params['excludeuser'])); } if (!is_null($params['user']) || !is_null($params['excludeuser'])) { // Paranoia: avoid brute force searches (bug 17342) // (shouldn't be able to get here without 'deletedhistory', but // check it again just in case) if (!$user->isAllowed('deletedhistory')) { $bitmask = Revision::DELETED_USER; } elseif (!$user->isAllowedAny('suppressrevision', 'viewsuppressed')) { $bitmask = Revision::DELETED_USER | Revision::DELETED_RESTRICTED; } else { $bitmask = 0; } if ($bitmask) { $this->addWhere($db->bitAnd('ar_deleted', $bitmask) . " != {$bitmask}"); } } if (!is_null($params['continue'])) { $cont = explode('|', $params['continue']); $op = $dir == 'newer' ? '>' : '<'; if ($optimizeGenerateTitles) { $this->dieContinueUsageIf(count($cont) != 2); $ns = intval($cont[0]); $this->dieContinueUsageIf(strval($ns) !== $cont[0]); $title = $db->addQuotes($cont[1]); $this->addWhere("ar_namespace {$op} {$ns} OR " . "(ar_namespace = {$ns} AND ar_title {$op}= {$title})"); } elseif ($mode == 'all') { $this->dieContinueUsageIf(count($cont) != 4); $ns = intval($cont[0]); $this->dieContinueUsageIf(strval($ns) !== $cont[0]); $title = $db->addQuotes($cont[1]); $ts = $db->addQuotes($db->timestamp($cont[2])); $ar_id = (int) $cont[3]; $this->dieContinueUsageIf(strval($ar_id) !== $cont[3]); $this->addWhere("ar_namespace {$op} {$ns} OR " . "(ar_namespace = {$ns} AND " . "(ar_title {$op} {$title} OR " . "(ar_title = {$title} AND " . "(ar_timestamp {$op} {$ts} OR " . "(ar_timestamp = {$ts} AND " . "ar_id {$op}= {$ar_id})))))"); } else { $this->dieContinueUsageIf(count($cont) != 2); $ts = $db->addQuotes($db->timestamp($cont[0])); $ar_id = (int) $cont[1]; $this->dieContinueUsageIf(strval($ar_id) !== $cont[1]); $this->addWhere("ar_timestamp {$op} {$ts} OR " . "(ar_timestamp = {$ts} AND " . "ar_id {$op}= {$ar_id})"); } } $this->addOption('LIMIT', $this->limit + 1); $sort = $dir == 'newer' ? '' : ' DESC'; $orderby = []; if ($optimizeGenerateTitles) { // Targeting index name_title_timestamp if ($params['namespace'] === null || count(array_unique($params['namespace'])) > 1) { $orderby[] = "ar_namespace {$sort}"; } $orderby[] = "ar_title {$sort}"; } elseif ($mode == 'all') { // Targeting index name_title_timestamp if ($params['namespace'] === null || count(array_unique($params['namespace'])) > 1) { $orderby[] = "ar_namespace {$sort}"; } $orderby[] = "ar_title {$sort}"; $orderby[] = "ar_timestamp {$sort}"; $orderby[] = "ar_id {$sort}"; } else { // Targeting index usertext_timestamp // 'user' is always constant. $orderby[] = "ar_timestamp {$sort}"; $orderby[] = "ar_id {$sort}"; } $this->addOption('ORDER BY', $orderby); $res = $this->select(__METHOD__); $pageMap = []; // Maps ns&title to array index $count = 0; $nextIndex = 0; $generated = []; foreach ($res as $row) { if (++$count > $this->limit) { // We've had enough if ($optimizeGenerateTitles) { $this->setContinueEnumParameter('continue', "{$row->ar_namespace}|{$row->ar_title}"); } elseif ($mode == 'all') { $this->setContinueEnumParameter('continue', "{$row->ar_namespace}|{$row->ar_title}|{$row->ar_timestamp}|{$row->ar_id}"); } else { $this->setContinueEnumParameter('continue', "{$row->ar_timestamp}|{$row->ar_id}"); } break; } // Miser mode namespace check if ($miser_ns !== null && !in_array($row->ar_namespace, $miser_ns)) { continue; } if ($resultPageSet !== null) { if ($params['generatetitles']) { $key = "{$row->ar_namespace}:{$row->ar_title}"; if (!isset($generated[$key])) { $generated[$key] = Title::makeTitle($row->ar_namespace, $row->ar_title); } } else { $generated[] = $row->ar_rev_id; } } else { $revision = Revision::newFromArchiveRow($row); $rev = $this->extractRevisionInfo($revision, $row); if (!isset($pageMap[$row->ar_namespace][$row->ar_title])) { $index = $nextIndex++; $pageMap[$row->ar_namespace][$row->ar_title] = $index; $title = $revision->getTitle(); $a = ['pageid' => $title->getArticleID(), 'revisions' => [$rev]]; ApiResult::setIndexedTagName($a['revisions'], 'rev'); ApiQueryBase::addTitleInfo($a, $title); $fit = $result->addValue(['query', $this->getModuleName()], $index, $a); } else { $index = $pageMap[$row->ar_namespace][$row->ar_title]; $fit = $result->addValue(['query', $this->getModuleName(), $index, 'revisions'], null, $rev); } if (!$fit) { if ($mode == 'all') { $this->setContinueEnumParameter('continue', "{$row->ar_namespace}|{$row->ar_title}|{$row->ar_timestamp}|{$row->ar_id}"); } else { $this->setContinueEnumParameter('continue', "{$row->ar_timestamp}|{$row->ar_id}"); } break; } } } if ($resultPageSet !== null) { if ($params['generatetitles']) { $resultPageSet->populateFromTitles($generated); } else { $resultPageSet->populateFromRevisionIDs($generated); } } else { $result->addIndexedTagName(['query', $this->getModuleName()], 'page'); } }
/** * Generates the parameter descriptions for this module, to be displayed in the * module's help. * @deprecated since 1.25 * @return string|bool */ public function makeHelpMsgParameters() { wfDeprecated(__METHOD__, '1.25'); $params = $this->getFinalParams(ApiBase::GET_VALUES_FOR_HELP); if ($params) { $paramsDescription = $this->getFinalParamDescription(); $msg = ''; $paramPrefix = "\n" . str_repeat(' ', 24); $descWordwrap = "\n" . str_repeat(' ', 28); foreach ($params as $paramName => $paramSettings) { $desc = isset($paramsDescription[$paramName]) ? $paramsDescription[$paramName] : ''; if (is_array($desc)) { $desc = implode($paramPrefix, $desc); } // handle shorthand if (!is_array($paramSettings)) { $paramSettings = array(self::PARAM_DFLT => $paramSettings); } // handle missing type if (!isset($paramSettings[ApiBase::PARAM_TYPE])) { $dflt = isset($paramSettings[ApiBase::PARAM_DFLT]) ? $paramSettings[ApiBase::PARAM_DFLT] : null; if (is_bool($dflt)) { $paramSettings[ApiBase::PARAM_TYPE] = 'boolean'; } elseif (is_string($dflt) || is_null($dflt)) { $paramSettings[ApiBase::PARAM_TYPE] = 'string'; } elseif (is_int($dflt)) { $paramSettings[ApiBase::PARAM_TYPE] = 'integer'; } } if (isset($paramSettings[self::PARAM_DEPRECATED]) && $paramSettings[self::PARAM_DEPRECATED]) { $desc = "DEPRECATED! {$desc}"; } if (isset($paramSettings[self::PARAM_REQUIRED]) && $paramSettings[self::PARAM_REQUIRED]) { $desc .= $paramPrefix . "This parameter is required"; } $type = isset($paramSettings[self::PARAM_TYPE]) ? $paramSettings[self::PARAM_TYPE] : null; if (isset($type)) { $hintPipeSeparated = true; $multi = isset($paramSettings[self::PARAM_ISMULTI]) ? $paramSettings[self::PARAM_ISMULTI] : false; if ($multi) { $prompt = 'Values (separate with \'|\'): '; } else { $prompt = 'One value: '; } if ($type === 'submodule') { if (isset($paramSettings[self::PARAM_SUBMODULE_MAP])) { $type = array_keys($paramSettings[self::PARAM_SUBMODULE_MAP]); } else { $type = $this->getModuleManager()->getNames($paramName); } sort($type); } if (is_array($type)) { $choices = array(); $nothingPrompt = ''; foreach ($type as $t) { if ($t === '') { $nothingPrompt = 'Can be empty, or '; } else { $choices[] = $t; } } $desc .= $paramPrefix . $nothingPrompt . $prompt; $choicesstring = implode(', ', $choices); $desc .= wordwrap($choicesstring, 100, $descWordwrap); $hintPipeSeparated = false; } else { switch ($type) { case 'namespace': // Special handling because namespaces are // type-limited, yet they are not given $desc .= $paramPrefix . $prompt; $desc .= wordwrap(implode(', ', MWNamespace::getValidNamespaces()), 100, $descWordwrap); $hintPipeSeparated = false; break; case 'limit': $desc .= $paramPrefix . "No more than {$paramSettings[self::PARAM_MAX]}"; if (isset($paramSettings[self::PARAM_MAX2])) { $desc .= " ({$paramSettings[self::PARAM_MAX2]} for bots)"; } $desc .= ' allowed'; break; case 'integer': $s = $multi ? 's' : ''; $hasMin = isset($paramSettings[self::PARAM_MIN]); $hasMax = isset($paramSettings[self::PARAM_MAX]); if ($hasMin || $hasMax) { if (!$hasMax) { $intRangeStr = "The value{$s} must be no less than " . "{$paramSettings[self::PARAM_MIN]}"; } elseif (!$hasMin) { $intRangeStr = "The value{$s} must be no more than " . "{$paramSettings[self::PARAM_MAX]}"; } else { $intRangeStr = "The value{$s} must be between " . "{$paramSettings[self::PARAM_MIN]} and {$paramSettings[self::PARAM_MAX]}"; } $desc .= $paramPrefix . $intRangeStr; } break; case 'upload': $desc .= $paramPrefix . "Must be posted as a file upload using multipart/form-data"; break; } } if ($multi) { if ($hintPipeSeparated) { $desc .= $paramPrefix . "Separate values with '|'"; } $isArray = is_array($type); if (!$isArray || $isArray && count($type) > self::LIMIT_SML1) { $desc .= $paramPrefix . "Maximum number of values " . self::LIMIT_SML1 . " (" . self::LIMIT_SML2 . " for bots)"; } } } $default = isset($paramSettings[self::PARAM_DFLT]) ? $paramSettings[self::PARAM_DFLT] : null; if (!is_null($default) && $default !== false) { $desc .= $paramPrefix . "Default: {$default}"; } $msg .= sprintf(" %-19s - %s\n", $this->encodeParamName($paramName), $desc); } return $msg; } return false; }
/** * List all namespace indices which are considered talks, aka not a subject * or special namespace. See also MWNamespace::isTalk * * @return array Array of namespace indices */ public static function getTalkNamespaces() { return array_filter(MWNamespace::getValidNamespaces(), 'MWNamespace::isTalk'); }
/** * Returns the ID of a namespace that defaults to Wikitext. * * @throws MWException If there is none. * @return int The ID of the wikitext Namespace * @since 1.21 */ protected function getDefaultWikitextNS() { global $wgNamespaceContentModels; static $wikitextNS = null; // this is not going to change if ($wikitextNS !== null) { return $wikitextNS; } // quickly short out on most common case: if (!isset($wgNamespaceContentModels[NS_MAIN])) { return NS_MAIN; } // NOTE: prefer content namespaces $namespaces = array_unique(array_merge(MWNamespace::getContentNamespaces(), array(NS_MAIN, NS_HELP, NS_PROJECT), MWNamespace::getValidNamespaces())); $namespaces = array_diff($namespaces, array(NS_FILE, NS_CATEGORY, NS_MEDIAWIKI, NS_USER)); $talk = array_filter($namespaces, function ($ns) { return MWNamespace::isTalk($ns); }); // prefer non-talk pages $namespaces = array_diff($namespaces, $talk); $namespaces = array_merge($namespaces, $talk); // check default content model of each namespace foreach ($namespaces as $ns) { if (!isset($wgNamespaceContentModels[$ns]) || $wgNamespaceContentModels[$ns] === CONTENT_MODEL_WIKITEXT) { $wikitextNS = $ns; return $wikitextNS; } } // give up // @todo Inside a test, we could skip the test as incomplete. // But frequently, this is used in fixture setup. throw new MWException("No namespace defaults to wikitext!"); }
/** * @param User $user * @param IContextSource $context * @param array $defaultPreferences */ static function searchPreferences($user, IContextSource $context, &$defaultPreferences) { foreach (MWNamespace::getValidNamespaces() as $n) { $defaultPreferences['searchNs' . $n] = array('type' => 'api'); } }
/** * Save namespace preferences when we're supposed to * * @return bool Whether we wrote something */ protected function saveNamespaces() { $user = $this->getUser(); $request = $this->getRequest(); if ($user->isLoggedIn() && $user->matchEditToken($request->getVal('nsRemember'), 'searchnamespace', $request)) { // Reset namespace preferences: namespaces are not searched // when they're not mentioned in the URL parameters. foreach (MWNamespace::getValidNamespaces() as $n) { $user->setOption('searchNs' . $n, false); } // The request parameters include all the namespaces to be searched. // Even if they're the same as an existing profile, they're not eaten. foreach ($this->namespaces as $n) { $user->setOption('searchNs' . $n, true); } $user->saveSettings(); return true; } return false; }
/** * Using the settings determine the value for the given parameter * * @param $paramName String: parameter name * @param $paramSettings array|mixed default value or an array of settings * using PARAM_* constants. * @param $parseLimit Boolean: parse limit? * @return mixed Parameter value */ protected function getParameterFromSettings($paramName, $paramSettings, $parseLimit) { // Some classes may decide to change parameter names $encParamName = $this->encodeParamName($paramName); if (!is_array($paramSettings)) { $default = $paramSettings; $multi = false; $type = gettype($paramSettings); $dupes = false; $deprecated = false; $required = false; } else { $default = isset($paramSettings[self::PARAM_DFLT]) ? $paramSettings[self::PARAM_DFLT] : null; $multi = isset($paramSettings[self::PARAM_ISMULTI]) ? $paramSettings[self::PARAM_ISMULTI] : false; $type = isset($paramSettings[self::PARAM_TYPE]) ? $paramSettings[self::PARAM_TYPE] : null; $dupes = isset($paramSettings[self::PARAM_ALLOW_DUPLICATES]) ? $paramSettings[self::PARAM_ALLOW_DUPLICATES] : false; $deprecated = isset($paramSettings[self::PARAM_DEPRECATED]) ? $paramSettings[self::PARAM_DEPRECATED] : false; $required = isset($paramSettings[self::PARAM_REQUIRED]) ? $paramSettings[self::PARAM_REQUIRED] : false; // When type is not given, and no choices, the type is the same as $default if (!isset($type)) { if (isset($default)) { $type = gettype($default); } else { $type = 'NULL'; // allow everything } } } if ($type == 'boolean') { if (isset($default) && $default !== false) { // Having a default value of anything other than 'false' is not allowed ApiBase::dieDebug(__METHOD__, "Boolean param {$encParamName}'s default is set to '{$default}'. Boolean parameters must default to false."); } $value = $this->getRequest()->getCheck($encParamName); } else { $value = $this->getRequest()->getVal($encParamName, $default); if (isset($value) && $type == 'namespace') { $type = MWNamespace::getValidNamespaces(); } } if (isset($value) && ($multi || is_array($type))) { $value = $this->parseMultiValue($encParamName, $value, $multi, is_array($type) ? $type : null); } // More validation only when choices were not given // choices were validated in parseMultiValue() if (isset($value)) { if (!is_array($type)) { switch ($type) { case 'NULL': // nothing to do break; case 'string': if ($required && $value === '') { $this->dieUsageMsg(array('missingparam', $paramName)); } break; case 'integer': // Force everything using intval() and optionally validate limits $min = isset($paramSettings[self::PARAM_MIN]) ? $paramSettings[self::PARAM_MIN] : null; $max = isset($paramSettings[self::PARAM_MAX]) ? $paramSettings[self::PARAM_MAX] : null; $enforceLimits = isset($paramSettings[self::PARAM_RANGE_ENFORCE]) ? $paramSettings[self::PARAM_RANGE_ENFORCE] : false; if (is_array($value)) { $value = array_map('intval', $value); if (!is_null($min) || !is_null($max)) { foreach ($value as &$v) { $this->validateLimit($paramName, $v, $min, $max, null, $enforceLimits); } } } else { $value = intval($value); if (!is_null($min) || !is_null($max)) { $this->validateLimit($paramName, $value, $min, $max, null, $enforceLimits); } } break; case 'limit': if (!$parseLimit) { // Don't do any validation whatsoever break; } if (!isset($paramSettings[self::PARAM_MAX]) || !isset($paramSettings[self::PARAM_MAX2])) { ApiBase::dieDebug(__METHOD__, "MAX1 or MAX2 are not defined for the limit {$encParamName}"); } if ($multi) { ApiBase::dieDebug(__METHOD__, "Multi-values not supported for {$encParamName}"); } $min = isset($paramSettings[self::PARAM_MIN]) ? $paramSettings[self::PARAM_MIN] : 0; if ($value == 'max') { $value = $this->getMain()->canApiHighLimits() ? $paramSettings[self::PARAM_MAX2] : $paramSettings[self::PARAM_MAX]; $this->getResult()->setParsedLimit($this->getModuleName(), $value); } else { $value = intval($value); $this->validateLimit($paramName, $value, $min, $paramSettings[self::PARAM_MAX], $paramSettings[self::PARAM_MAX2]); } break; case 'boolean': if ($multi) { ApiBase::dieDebug(__METHOD__, "Multi-values not supported for {$encParamName}"); } break; case 'timestamp': if (is_array($value)) { foreach ($value as $key => $val) { $value[$key] = $this->validateTimestamp($val, $encParamName); } } else { $value = $this->validateTimestamp($value, $encParamName); } break; case 'user': if (!is_array($value)) { $value = array($value); } foreach ($value as $key => $val) { $title = Title::makeTitleSafe(NS_USER, $val); if (is_null($title)) { $this->dieUsage("Invalid value for user parameter {$encParamName}", "baduser_{$encParamName}"); } $value[$key] = $title->getText(); } if (!$multi) { $value = $value[0]; } break; default: ApiBase::dieDebug(__METHOD__, "Param {$encParamName}'s type is unknown - {$type}"); } } // Throw out duplicates if requested if (is_array($value) && !$dupes) { $value = array_unique($value); } // Set a warning if a deprecated parameter has been passed if ($deprecated && $value !== false) { $this->setWarning("The {$encParamName} parameter has been deprecated."); } } elseif ($required) { $this->dieUsageMsg(array('missingparam', $paramName)); } return $value; }
private function outputNamespaceProtectionInfo() { global $wgParser, $wgContLang; $out = $this->getOutput(); $namespaceProtection = $this->getConfig()->get('NamespaceProtection'); if (count($namespaceProtection) == 0) { return; } $header = $this->msg('listgrouprights-namespaceprotection-header')->parse(); $out->addHTML(Html::rawElement('h2', array(), Html::element('span', array('class' => 'mw-headline', 'id' => $wgParser->guessSectionNameFromWikiText($header)), $header)) . Xml::openElement('table', array('class' => 'wikitable')) . Html::element('th', array(), $this->msg('listgrouprights-namespaceprotection-namespace')->text()) . Html::element('th', array(), $this->msg('listgrouprights-namespaceprotection-restrictedto')->text())); ksort($namespaceProtection); foreach ($namespaceProtection as $namespace => $rights) { if (!in_array($namespace, MWNamespace::getValidNamespaces())) { continue; } if ($namespace == NS_MAIN) { $namespaceText = $this->msg('blanknamespace')->text(); } else { $namespaceText = $wgContLang->convertNamespace($namespace); } $out->addHTML(Xml::openElement('tr') . Html::rawElement('td', array(), Linker::link(SpecialPage::getTitleFor('Allpages'), $namespaceText, array(), array('namespace' => $namespace))) . Xml::openElement('td') . Xml::openElement('ul')); if (!is_array($rights)) { $rights = array($rights); } foreach ($rights as $right) { $out->addHTML(Html::rawElement('li', array(), $this->msg('listgrouprights-right-display', User::getRightDescription($right), Html::element('span', array('class' => 'mw-listgrouprights-right-name'), $right))->parse())); } $out->addHTML(Xml::closeElement('ul') . Xml::closeElement('td') . Xml::closeElement('tr')); } $out->addHTML(Xml::closeElement('table')); }
/** * Read the global and extract title objects from the corresponding messages * @return array Array( 'msg' => Title, 'cats' => Title[] ) */ private function prepareTrackingCategoriesData() { $categories = array_merge(self::$coreTrackingCategories, ExtensionRegistry::getInstance()->getAttribute('TrackingCategories'), $this->getConfig()->get('TrackingCategories')); $trackingCategories = []; foreach ($categories as $catMsg) { /* * Check if the tracking category varies by namespace * Otherwise only pages in the current namespace will be displayed * If it does vary, show pages considering all namespaces */ $msgObj = $this->msg($catMsg)->inContentLanguage(); $allCats = []; $catMsgTitle = Title::makeTitleSafe(NS_MEDIAWIKI, $catMsg); if (!$catMsgTitle) { continue; } // Match things like {{NAMESPACE}} and {{NAMESPACENUMBER}}. // False positives are ok, this is just an efficiency shortcut if (strpos($msgObj->plain(), '{{') !== false) { $ns = MWNamespace::getValidNamespaces(); foreach ($ns as $namesp) { $tempTitle = Title::makeTitleSafe($namesp, $catMsg); if (!$tempTitle) { continue; } $catName = $msgObj->title($tempTitle)->text(); # Allow tracking categories to be disabled by setting them to "-" if ($catName !== '-') { $catTitle = Title::makeTitleSafe(NS_CATEGORY, $catName); if ($catTitle) { $allCats[] = $catTitle; } } } } else { $catName = $msgObj->text(); # Allow tracking categories to be disabled by setting them to "-" if ($catName !== '-') { $catTitle = Title::makeTitleSafe(NS_CATEGORY, $catName); if ($catTitle) { $allCats[] = $catTitle; } } } $trackingCategories[$catMsg] = ['cats' => $allCats, 'msg' => $catMsgTitle]; } return $trackingCategories; }
/** * Using the settings determine the value for the given parameter * * @param string $paramName parameter name * @param array|mixed $paramSettings default value or an array of settings * using PARAM_* constants. * @param $parseLimit Boolean: parse limit? * @return mixed Parameter value */ protected function getParameterFromSettings($paramName, $paramSettings, $parseLimit) { // Some classes may decide to change parameter names $encParamName = $this->encodeParamName($paramName); if (!is_array($paramSettings)) { $default = $paramSettings; $multi = false; $type = gettype($paramSettings); $dupes = false; $deprecated = false; $required = false; } else { $default = isset($paramSettings[self::PARAM_DFLT]) ? $paramSettings[self::PARAM_DFLT] : null; $multi = isset($paramSettings[self::PARAM_ISMULTI]) ? $paramSettings[self::PARAM_ISMULTI] : false; $type = isset($paramSettings[self::PARAM_TYPE]) ? $paramSettings[self::PARAM_TYPE] : null; $dupes = isset($paramSettings[self::PARAM_ALLOW_DUPLICATES]) ? $paramSettings[self::PARAM_ALLOW_DUPLICATES] : false; $deprecated = isset($paramSettings[self::PARAM_DEPRECATED]) ? $paramSettings[self::PARAM_DEPRECATED] : false; $required = isset($paramSettings[self::PARAM_REQUIRED]) ? $paramSettings[self::PARAM_REQUIRED] : false; // When type is not given, and no choices, the type is the same as $default if (!isset($type)) { if (isset($default)) { $type = gettype($default); } else { $type = 'NULL'; // allow everything } } } if ($type == 'boolean') { if (isset($default) && $default !== false) { // Having a default value of anything other than 'false' is not allowed ApiBase::dieDebug(__METHOD__, "Boolean param {$encParamName}'s default is set to '{$default}'. " . "Boolean parameters must default to false."); } $value = $this->getMain()->getCheck($encParamName); } elseif ($type == 'upload') { if (isset($default)) { // Having a default value is not allowed ApiBase::dieDebug(__METHOD__, "File upload param {$encParamName}'s default is set to " . "'{$default}'. File upload parameters may not have a default."); } if ($multi) { ApiBase::dieDebug(__METHOD__, "Multi-values not supported for {$encParamName}"); } $value = $this->getMain()->getUpload($encParamName); if (!$value->exists()) { // This will get the value without trying to normalize it // (because trying to normalize a large binary file // accidentally uploaded as a field fails spectacularly) $value = $this->getMain()->getRequest()->unsetVal($encParamName); if ($value !== null) { $this->dieUsage("File upload param {$encParamName} is not a file upload; " . "be sure to use multipart/form-data for your POST and include " . "a filename in the Content-Disposition header.", "badupload_{$encParamName}"); } } } else { $value = $this->getMain()->getVal($encParamName, $default); if (isset($value) && $type == 'namespace') { $type = MWNamespace::getValidNamespaces(); } } if (isset($value) && ($multi || is_array($type))) { $value = $this->parseMultiValue($encParamName, $value, $multi, is_array($type) ? $type : null); } // More validation only when choices were not given // choices were validated in parseMultiValue() if (isset($value)) { if (!is_array($type)) { switch ($type) { case 'NULL': // nothing to do break; case 'string': if ($required && $value === '') { $this->dieUsageMsg(array('missingparam', $paramName)); } break; case 'integer': // Force everything using intval() and optionally validate limits $min = isset($paramSettings[self::PARAM_MIN]) ? $paramSettings[self::PARAM_MIN] : null; $max = isset($paramSettings[self::PARAM_MAX]) ? $paramSettings[self::PARAM_MAX] : null; $enforceLimits = isset($paramSettings[self::PARAM_RANGE_ENFORCE]) ? $paramSettings[self::PARAM_RANGE_ENFORCE] : false; if (is_array($value)) { $value = array_map('intval', $value); if (!is_null($min) || !is_null($max)) { foreach ($value as &$v) { $this->validateLimit($paramName, $v, $min, $max, null, $enforceLimits); } } } else { $value = intval($value); if (!is_null($min) || !is_null($max)) { $this->validateLimit($paramName, $value, $min, $max, null, $enforceLimits); } } break; case 'limit': if (!$parseLimit) { // Don't do any validation whatsoever break; } if (!isset($paramSettings[self::PARAM_MAX]) || !isset($paramSettings[self::PARAM_MAX2])) { ApiBase::dieDebug(__METHOD__, "MAX1 or MAX2 are not defined for the limit {$encParamName}"); } if ($multi) { ApiBase::dieDebug(__METHOD__, "Multi-values not supported for {$encParamName}"); } $min = isset($paramSettings[self::PARAM_MIN]) ? $paramSettings[self::PARAM_MIN] : 0; if ($value == 'max') { $value = $this->getMain()->canApiHighLimits() ? $paramSettings[self::PARAM_MAX2] : $paramSettings[self::PARAM_MAX]; $this->getResult()->setParsedLimit($this->getModuleName(), $value); } else { $value = intval($value); $this->validateLimit($paramName, $value, $min, $paramSettings[self::PARAM_MAX], $paramSettings[self::PARAM_MAX2]); } break; case 'boolean': if ($multi) { ApiBase::dieDebug(__METHOD__, "Multi-values not supported for {$encParamName}"); } break; case 'timestamp': if (is_array($value)) { foreach ($value as $key => $val) { $value[$key] = $this->validateTimestamp($val, $encParamName); } } else { $value = $this->validateTimestamp($value, $encParamName); } break; case 'user': if (is_array($value)) { foreach ($value as $key => $val) { $value[$key] = $this->validateUser($val, $encParamName); } } else { $value = $this->validateUser($value, $encParamName); } break; case 'upload': // nothing to do break; default: ApiBase::dieDebug(__METHOD__, "Param {$encParamName}'s type is unknown - {$type}"); } } // Throw out duplicates if requested if (!$dupes && is_array($value)) { $value = array_unique($value); } // Set a warning if a deprecated parameter has been passed if ($deprecated && $value !== false) { $this->setWarning("The {$encParamName} parameter has been deprecated."); } } elseif ($required) { $this->dieUsageMsg(array('missingparam', $paramName)); } return $value; }
/** * Using the settings determine the value for the given parameter * * @param string $paramName Parameter name * @param array|mixed $paramSettings Default value or an array of settings * using PARAM_* constants. * @param bool $parseLimit Parse limit? * @return mixed Parameter value */ protected function getParameterFromSettings($paramName, $paramSettings, $parseLimit) { // Some classes may decide to change parameter names $encParamName = $this->encodeParamName($paramName); if (!is_array($paramSettings)) { $default = $paramSettings; $multi = false; $type = gettype($paramSettings); $dupes = false; $deprecated = false; $required = false; } else { $default = isset($paramSettings[self::PARAM_DFLT]) ? $paramSettings[self::PARAM_DFLT] : null; $multi = isset($paramSettings[self::PARAM_ISMULTI]) ? $paramSettings[self::PARAM_ISMULTI] : false; $type = isset($paramSettings[self::PARAM_TYPE]) ? $paramSettings[self::PARAM_TYPE] : null; $dupes = isset($paramSettings[self::PARAM_ALLOW_DUPLICATES]) ? $paramSettings[self::PARAM_ALLOW_DUPLICATES] : false; $deprecated = isset($paramSettings[self::PARAM_DEPRECATED]) ? $paramSettings[self::PARAM_DEPRECATED] : false; $required = isset($paramSettings[self::PARAM_REQUIRED]) ? $paramSettings[self::PARAM_REQUIRED] : false; // When type is not given, and no choices, the type is the same as $default if (!isset($type)) { if (isset($default)) { $type = gettype($default); } else { $type = 'NULL'; // allow everything } } } if ($type == 'boolean') { if (isset($default) && $default !== false) { // Having a default value of anything other than 'false' is not allowed ApiBase::dieDebug(__METHOD__, "Boolean param {$encParamName}'s default is set to '{$default}'. " . 'Boolean parameters must default to false.'); } $value = $this->getMain()->getCheck($encParamName); } elseif ($type == 'upload') { if (isset($default)) { // Having a default value is not allowed ApiBase::dieDebug(__METHOD__, "File upload param {$encParamName}'s default is set to " . "'{$default}'. File upload parameters may not have a default."); } if ($multi) { ApiBase::dieDebug(__METHOD__, "Multi-values not supported for {$encParamName}"); } $value = $this->getMain()->getUpload($encParamName); if (!$value->exists()) { // This will get the value without trying to normalize it // (because trying to normalize a large binary file // accidentally uploaded as a field fails spectacularly) $value = $this->getMain()->getRequest()->unsetVal($encParamName); if ($value !== null) { $this->dieUsage("File upload param {$encParamName} is not a file upload; " . 'be sure to use multipart/form-data for your POST and include ' . 'a filename in the Content-Disposition header.', "badupload_{$encParamName}"); } } } else { $value = $this->getMain()->getVal($encParamName, $default); if (isset($value) && $type == 'namespace') { $type = MWNamespace::getValidNamespaces(); } if (isset($value) && $type == 'submodule') { if (isset($paramSettings[self::PARAM_SUBMODULE_MAP])) { $type = array_keys($paramSettings[self::PARAM_SUBMODULE_MAP]); } else { $type = $this->getModuleManager()->getNames($paramName); } } $request = $this->getMain()->getRequest(); $rawValue = $request->getRawVal($encParamName); if ($rawValue === null) { $rawValue = $default; } // Preserve U+001F for self::parseMultiValue(), or error out if that won't be called if (isset($value) && substr($rawValue, 0, 1) === "") { if ($multi) { // This loses the potential $wgContLang->checkTitleEncoding() transformation // done by WebRequest for $_GET. Let's call that a feature. $value = join("", $request->normalizeUnicode(explode("", $rawValue))); } else { $this->dieUsage("U+001F multi-value separation may only be used for multi-valued parameters.", 'badvalue_notmultivalue'); } } // Check for NFC normalization, and warn if ($rawValue !== $value) { $this->handleParamNormalization($paramName, $value, $rawValue); } } if (isset($value) && ($multi || is_array($type))) { $value = $this->parseMultiValue($encParamName, $value, $multi, is_array($type) ? $type : null); } // More validation only when choices were not given // choices were validated in parseMultiValue() if (isset($value)) { if (!is_array($type)) { switch ($type) { case 'NULL': // nothing to do break; case 'string': case 'text': case 'password': if ($required && $value === '') { $this->dieUsageMsg(['missingparam', $paramName]); } break; case 'integer': // Force everything using intval() and optionally validate limits $min = isset($paramSettings[self::PARAM_MIN]) ? $paramSettings[self::PARAM_MIN] : null; $max = isset($paramSettings[self::PARAM_MAX]) ? $paramSettings[self::PARAM_MAX] : null; $enforceLimits = isset($paramSettings[self::PARAM_RANGE_ENFORCE]) ? $paramSettings[self::PARAM_RANGE_ENFORCE] : false; if (is_array($value)) { $value = array_map('intval', $value); if (!is_null($min) || !is_null($max)) { foreach ($value as &$v) { $this->validateLimit($paramName, $v, $min, $max, null, $enforceLimits); } } } else { $value = intval($value); if (!is_null($min) || !is_null($max)) { $this->validateLimit($paramName, $value, $min, $max, null, $enforceLimits); } } break; case 'limit': if (!$parseLimit) { // Don't do any validation whatsoever break; } if (!isset($paramSettings[self::PARAM_MAX]) || !isset($paramSettings[self::PARAM_MAX2])) { ApiBase::dieDebug(__METHOD__, "MAX1 or MAX2 are not defined for the limit {$encParamName}"); } if ($multi) { ApiBase::dieDebug(__METHOD__, "Multi-values not supported for {$encParamName}"); } $min = isset($paramSettings[self::PARAM_MIN]) ? $paramSettings[self::PARAM_MIN] : 0; if ($value == 'max') { $value = $this->getMain()->canApiHighLimits() ? $paramSettings[self::PARAM_MAX2] : $paramSettings[self::PARAM_MAX]; $this->getResult()->addParsedLimit($this->getModuleName(), $value); } else { $value = intval($value); $this->validateLimit($paramName, $value, $min, $paramSettings[self::PARAM_MAX], $paramSettings[self::PARAM_MAX2]); } break; case 'boolean': if ($multi) { ApiBase::dieDebug(__METHOD__, "Multi-values not supported for {$encParamName}"); } break; case 'timestamp': if (is_array($value)) { foreach ($value as $key => $val) { $value[$key] = $this->validateTimestamp($val, $encParamName); } } else { $value = $this->validateTimestamp($value, $encParamName); } break; case 'user': if (is_array($value)) { foreach ($value as $key => $val) { $value[$key] = $this->validateUser($val, $encParamName); } } else { $value = $this->validateUser($value, $encParamName); } break; case 'upload': // nothing to do break; case 'tags': // If change tagging was requested, check that the tags are valid. if (!is_array($value) && !$multi) { $value = [$value]; } $tagsStatus = ChangeTags::canAddTagsAccompanyingChange($value); if (!$tagsStatus->isGood()) { $this->dieStatus($tagsStatus); } break; default: ApiBase::dieDebug(__METHOD__, "Param {$encParamName}'s type is unknown - {$type}"); } } // Throw out duplicates if requested if (!$dupes && is_array($value)) { $value = array_unique($value); } // Set a warning if a deprecated parameter has been passed if ($deprecated && $value !== false) { $this->setWarning("The {$encParamName} parameter has been deprecated."); $feature = $encParamName; $m = $this; while (!$m->isMain()) { $p = $m->getParent(); $name = $m->getModuleName(); $param = $p->encodeParamName($p->getModuleManager()->getModuleGroup($name)); $feature = "{$param}={$name}&{$feature}"; $m = $p; } $this->logFeatureUsage($feature); } } elseif ($required) { $this->dieUsageMsg(['missingparam', $paramName]); } return $value; }
function execute($par) { $this->setHeaders(); $this->outputHeader(); $this->getOutput()->allowClickjacking(); $this->getOutput()->addHTML(Html::openElement('table', array('class' => 'mw-datatable', 'id' => 'mw-trackingcategories-table')) . "\n" . "<thead><tr>\n\t\t\t<th>" . $this->msg('trackingcategories-msg')->escaped() . "\n\t\t\t</th>\n\t\t\t<th>" . $this->msg('trackingcategories-name')->escaped() . "</th>\n\t\t\t<th>" . $this->msg('trackingcategories-desc')->escaped() . "\n\t\t\t</th>\n\t\t\t</tr></thead>"); foreach ($this->getConfig()->get('TrackingCategories') as $catMsg) { /* * Check if the tracking category varies by namespace * Otherwise only pages in the current namespace will be displayed * If it does vary, show pages considering all namespaces */ $msgObj = $this->msg($catMsg)->inContentLanguage(); $allMsgs = array(); $catDesc = $catMsg . '-desc'; $catMsgTitle = Title::makeTitleSafe(NS_MEDIAWIKI, $catMsg); if (!$catMsgTitle) { continue; } $catMsgTitleText = Linker::link($catMsgTitle, htmlspecialchars($catMsg)); // Match things like {{NAMESPACE}} and {{NAMESPACENUMBER}}. // False positives are ok, this is just an efficiency shortcut if (strpos($msgObj->plain(), '{{') !== false) { $ns = MWNamespace::getValidNamespaces(); foreach ($ns as $namesp) { $tempTitle = Title::makeTitleSafe($namesp, $catMsg); if (!$tempTitle) { continue; } $catName = $msgObj->title($tempTitle)->text(); # Allow tracking categories to be disabled by setting them to "-" if ($catName !== '-') { $catTitle = Title::makeTitleSafe(NS_CATEGORY, $catName); if ($catTitle) { $catTitleText = Linker::link($catTitle, htmlspecialchars($catName)); $allMsgs[] = $catTitleText; } } } } else { $catName = $msgObj->text(); # Allow tracking categories to be disabled by setting them to "-" if ($catName !== '-') { $catTitle = Title::makeTitleSafe(NS_CATEGORY, $catName); if ($catTitle) { $catTitleText = Linker::link($catTitle, htmlspecialchars($catName)); $allMsgs[] = $catTitleText; } } } # Extra message, when no category was found if (!count($allMsgs)) { $allMsgs[] = $this->msg('trackingcategories-disabled')->parse(); } /* * Show category description if it exists as a system message * as category-name-desc */ $descMsg = $this->msg($catDesc); if ($descMsg->isBlank()) { $descMsg = $this->msg('trackingcategories-nodesc'); } $this->getOutput()->addHTML(Html::openElement('tr') . Html::openElement('td', array('class' => 'mw-trackingcategories-name')) . $this->getLanguage()->commaList(array_unique($allMsgs)) . Html::closeElement('td') . Html::openElement('td', array('class' => 'mw-trackingcategories-msg')) . $catMsgTitleText . Html::closeElement('td') . Html::openElement('td', array('class' => 'mw-trackingcategories-desc')) . $descMsg->parse() . Html::closeElement('td') . Html::closeElement('tr')); } $this->getOutput()->addHTML(Html::closeElement('table')); }