public function execute() { $prop = null; extract($this->extractRequestParams()); foreach ($prop as $p) { switch ($p) { case 'general': global $wgSitename, $wgVersion, $wgCapitalLinks; $data = array(); $mainPage = Title::newFromText(wfMsgForContent('mainpage')); $data['mainpage'] = $mainPage->getText(); $data['base'] = $mainPage->getFullUrl(); $data['sitename'] = $wgSitename; $data['generator'] = "MediaWiki {$wgVersion}"; $data['case'] = $wgCapitalLinks ? 'first-letter' : 'case-sensitive'; // 'case-insensitive' option is reserved for future $this->getResult()->addValue('query', $p, $data); break; case 'namespaces': global $wgContLang; $data = array(); foreach ($wgContLang->getFormattedNamespaces() as $ns => $title) { $data[$ns] = array('id' => $ns); ApiResult::setContent($data[$ns], $title); } ApiResult::setIndexedTagName($data, 'ns'); $this->getResult()->addValue('query', $p, $data); break; default: ApiBase::dieDebug(__METHOD__, "Unknown prop={$p}"); } } }
public function execute() { if (!$this->hasAnyRoutes()) { $this->dieUsage('No password reset routes are available.', 'moduledisabled'); } $params = $this->extractRequestParams() + ['user' => null, 'email' => null]; $this->requireOnlyOneParameter($params, 'user', 'email'); $passwordReset = new PasswordReset($this->getConfig(), AuthManager::singleton()); $status = $passwordReset->isAllowed($this->getUser(), $params['capture']); if (!$status->isOK()) { $this->dieStatus(Status::wrap($status)); } $status = $passwordReset->execute($this->getUser(), $params['user'], $params['email'], $params['capture']); if (!$status->isOK()) { $status->value = null; $this->dieStatus(Status::wrap($status)); } $result = $this->getResult(); $result->addValue(['resetpassword'], 'status', 'success'); if ($params['capture']) { $passwords = $status->getValue() ?: []; ApiResult::setArrayType($passwords, 'kvp', 'user'); ApiResult::setIndexedTagName($passwords, 'p'); $result->addValue(['resetpassword'], 'passwords', $passwords); } }
/** * Purges the cache of a page */ public function execute() { $params = $this->extractRequestParams(); $continuationManager = new ApiContinuationManager($this, array(), array()); $this->setContinuationManager($continuationManager); $forceLinkUpdate = $params['forcelinkupdate']; $forceRecursiveLinkUpdate = $params['forcerecursivelinkupdate']; $pageSet = $this->getPageSet(); $pageSet->execute(); $result = $pageSet->getInvalidTitlesAndRevisions(); foreach ($pageSet->getGoodTitles() as $title) { $r = array(); ApiQueryBase::addTitleInfo($r, $title); $page = WikiPage::factory($title); $page->doPurge(); // Directly purge and skip the UI part of purge(). $r['purged'] = true; if ($forceLinkUpdate || $forceRecursiveLinkUpdate) { if (!$this->getUser()->pingLimiter('linkpurge')) { $popts = $page->makeParserOptions('canonical'); # Parse content; note that HTML generation is only needed if we want to cache the result. $content = $page->getContent(Revision::RAW); $enableParserCache = $this->getConfig()->get('EnableParserCache'); $p_result = $content->getParserOutput($title, $page->getLatest(), $popts, $enableParserCache); # Update the links tables $updates = $content->getSecondaryDataUpdates($title, null, $forceRecursiveLinkUpdate, $p_result); DataUpdate::runUpdates($updates); $r['linkupdate'] = true; if ($enableParserCache) { $pcache = ParserCache::singleton(); $pcache->save($p_result, $page, $popts); } } else { $error = $this->parseMsg(array('actionthrottledtext')); $this->setWarning($error['info']); $forceLinkUpdate = false; } } $result[] = $r; } $apiResult = $this->getResult(); ApiResult::setIndexedTagName($result, 'page'); $apiResult->addValue(null, $this->getModuleName(), $result); $values = $pageSet->getNormalizedTitlesAsResult($apiResult); if ($values) { $apiResult->addValue(null, 'normalized', $values); } $values = $pageSet->getConvertedTitlesAsResult($apiResult); if ($values) { $apiResult->addValue(null, 'converted', $values); } $values = $pageSet->getRedirectTitlesAsResult($apiResult); if ($values) { $apiResult->addValue(null, 'redirects', $values); } $this->setContinuationManager(null); $continuationManager->setContinuationIntoResult($apiResult); }
public function execute() { $params = $this->extractRequestParams(); $user = $this->getUrUser($params); $form = $this->getUserRightsPage(); $form->setContext($this->getContext()); $r['user'] = $user->getName(); $r['userid'] = $user->getId(); list($r['added'], $r['removed']) = $form->doSaveUserGroups($user, (array) $params['add'], (array) $params['remove'], $params['reason']); $result = $this->getResult(); ApiResult::setIndexedTagName($r['added'], 'group'); ApiResult::setIndexedTagName($r['removed'], 'group'); $result->addValue(null, $this->getModuleName(), $r); }
public function execute() { $user = $this->getUser(); if (!$user->isLoggedIn()) { $this->dieUsage('You must be logged-in to have a watchlist', 'notloggedin'); } if (!$user->isAllowed('editmywatchlist')) { $this->dieUsage('You don\'t have permission to edit your watchlist', 'permissiondenied'); } $params = $this->extractRequestParams(); $continuationManager = new ApiContinuationManager($this, array(), array()); $this->setContinuationManager($continuationManager); $pageSet = $this->getPageSet(); // by default we use pageset to extract the page to work on. // title is still supported for backward compatibility if (!isset($params['title'])) { $pageSet->execute(); $res = $pageSet->getInvalidTitlesAndRevisions(array('invalidTitles', 'special', 'missingIds', 'missingRevIds', 'interwikiTitles')); foreach ($pageSet->getMissingTitles() as $title) { $r = $this->watchTitle($title, $user, $params); $r['missing'] = 1; $res[] = $r; } foreach ($pageSet->getGoodTitles() as $title) { $r = $this->watchTitle($title, $user, $params); $res[] = $r; } ApiResult::setIndexedTagName($res, 'w'); } else { // dont allow use of old title parameter with new pageset parameters. $extraParams = array_keys(array_filter($pageSet->extractRequestParams(), function ($x) { return $x !== null && $x !== false; })); if ($extraParams) { $p = $this->getModulePrefix(); $this->dieUsage("The parameter {$p}title can not be used with " . implode(", ", $extraParams), 'invalidparammix'); } $this->logFeatureUsage('action=watch&title'); $title = Title::newFromText($params['title']); if (!$title || !$title->isWatchable()) { $this->dieUsageMsg(array('invalidtitle', $params['title'])); } $res = $this->watchTitle($title, $user, $params, true); } $this->getResult()->addValue(null, $this->getModuleName(), $res); $this->setContinuationManager(null); $continuationManager->setContinuationIntoResult($this->getResult()); }
public function execute() { $params = $this->extractRequestParams(); $props = array_flip($params['prop']); $repos = array(); $repoGroup = $this->getInitialisedRepoGroup(); $repoGroup->forEachForeignRepo(function ($repo) use(&$repos, $props) { $repos[] = array_intersect_key($repo->getInfo(), $props); }); $repos[] = array_intersect_key($repoGroup->getLocalRepo()->getInfo(), $props); $result = $this->getResult(); ApiResult::setIndexedTagName($repos, 'repo'); ApiResult::setArrayTypeRecursive($repos, 'assoc'); ApiResult::setArrayType($repos, 'array'); $result->addValue(array('query'), 'repos', $repos); }
public function execute() { $this->useTransactionalTimeLimit(); $user = $this->getUser(); $params = $this->extractRequestParams(); $this->requireMaxOneParameter($params, 'namespace', 'rootpage'); $isUpload = false; if (isset($params['interwikisource'])) { if (!$user->isAllowed('import')) { $this->dieUsageMsg('cantimport'); } if (!isset($params['interwikipage'])) { $this->dieUsageMsg(array('missingparam', 'interwikipage')); } $source = ImportStreamSource::newFromInterwiki($params['interwikisource'], $params['interwikipage'], $params['fullhistory'], $params['templates']); } else { $isUpload = true; if (!$user->isAllowed('importupload')) { $this->dieUsageMsg('cantimport-upload'); } $source = ImportStreamSource::newFromUpload('xml'); } if (!$source->isOK()) { $this->dieStatus($source); } $importer = new WikiImporter($source->value, $this->getConfig()); if (isset($params['namespace'])) { $importer->setTargetNamespace($params['namespace']); } elseif (isset($params['rootpage'])) { $statusRootPage = $importer->setTargetRootPage($params['rootpage']); if (!$statusRootPage->isGood()) { $this->dieStatus($statusRootPage); } } $reporter = new ApiImportReporter($importer, $isUpload, $params['interwikisource'], $params['summary']); try { $importer->doImport(); } catch (Exception $e) { $this->dieUsageMsg(array('import-unknownerror', $e->getMessage())); } $resultData = $reporter->getData(); $result = $this->getResult(); ApiResult::setIndexedTagName($resultData, 'page'); $result->addValue(null, $this->getModuleName(), $resultData); }
/** * Format a message for output * @param array &$res Result array * @param string $key Result key * @param Message $message */ private function formatMessage(array &$res, $key, Message $message) { switch ($this->messageFormat) { case 'none': break; case 'wikitext': $res[$key] = $message->setContext($this->module)->text(); break; case 'html': $res[$key] = $message->setContext($this->module)->parseAsBlock(); $res[$key] = Parser::stripOuterParagraph($res[$key]); break; case 'raw': $res[$key] = ['key' => $message->getKey(), 'params' => $message->getParams()]; ApiResult::setIndexedTagName($res[$key]['params'], 'param'); break; } }
public function execute() { $pUser = $this->getUser(); // Deny if the user is blocked and doesn't have the full 'userrights' permission. // This matches what Special:UserRights does for the web UI. if ($pUser->isBlocked() && !$pUser->isAllowed('userrights')) { $this->dieBlocked($pUser->getBlock()); } $params = $this->extractRequestParams(); $user = $this->getUrUser($params); $form = $this->getUserRightsPage(); $form->setContext($this->getContext()); $r['user'] = $user->getName(); $r['userid'] = $user->getId(); list($r['added'], $r['removed']) = $form->doSaveUserGroups($user, (array) $params['add'], (array) $params['remove'], $params['reason']); $result = $this->getResult(); ApiResult::setIndexedTagName($r['added'], 'group'); ApiResult::setIndexedTagName($r['removed'], 'group'); $result->addValue(null, $this->getModuleName(), $r); }
public function execute() { $conf = $this->getConfig(); $params = $this->extractRequestParams(); $props = array_flip($params['prop']); $repos = []; $repoGroup = $this->getInitialisedRepoGroup(); $foreignTargets = $conf->get('ForeignUploadTargets'); $repoGroup->forEachForeignRepo(function ($repo) use(&$repos, $props, $foreignTargets) { $repoProps = $repo->getInfo(); $repoProps['canUpload'] = in_array($repoProps['name'], $foreignTargets); $repos[] = array_intersect_key($repoProps, $props); }); $localInfo = $repoGroup->getLocalRepo()->getInfo(); $localInfo['canUpload'] = $conf->get('EnableUploads'); $repos[] = array_intersect_key($localInfo, $props); $result = $this->getResult(); ApiResult::setIndexedTagName($repos, 'repo'); ApiResult::setArrayTypeRecursive($repos, 'assoc'); ApiResult::setArrayType($repos, 'array'); $result->addValue(['query'], 'repos', $repos); }
/** * * @param array $metadata * @param ApiResult $result * @return array */ public static function processMetaData($metadata, $result) { $retval = array(); if (is_array($metadata)) { foreach ($metadata as $key => $value) { $r = array('name' => $key); if (is_array($value)) { $r['value'] = self::processMetaData($value, $result); } else { $r['value'] = $value; } $retval[] = $r; } } ApiResult::setIndexedTagName($retval, 'metadata'); return $retval; }
public function execute() { $this->useTransactionalTimeLimit(); $params = $this->extractRequestParams(); $rotation = $params['rotation']; $continuationManager = new ApiContinuationManager($this, [], []); $this->setContinuationManager($continuationManager); $pageSet = $this->getPageSet(); $pageSet->execute(); $result = []; $result = $pageSet->getInvalidTitlesAndRevisions(['invalidTitles', 'special', 'missingIds', 'missingRevIds', 'interwikiTitles']); foreach ($pageSet->getTitles() as $title) { $r = []; $r['id'] = $title->getArticleID(); ApiQueryBase::addTitleInfo($r, $title); if (!$title->exists()) { $r['missing'] = true; if ($title->isKnown()) { $r['known'] = true; } } $file = wfFindFile($title, ['latest' => true]); if (!$file) { $r['result'] = 'Failure'; $r['errormessage'] = 'File does not exist'; $result[] = $r; continue; } $handler = $file->getHandler(); if (!$handler || !$handler->canRotate()) { $r['result'] = 'Failure'; $r['errormessage'] = 'File type cannot be rotated'; $result[] = $r; continue; } // Check whether we're allowed to rotate this file $permError = $this->checkPermissions($this->getUser(), $file->getTitle()); if ($permError !== null) { $r['result'] = 'Failure'; $r['errormessage'] = $permError; $result[] = $r; continue; } $srcPath = $file->getLocalRefPath(); if ($srcPath === false) { $r['result'] = 'Failure'; $r['errormessage'] = 'Cannot get local file path'; $result[] = $r; continue; } $ext = strtolower(pathinfo("{$srcPath}", PATHINFO_EXTENSION)); $tmpFile = TempFSFile::factory('rotate_', $ext, wfTempDir()); $dstPath = $tmpFile->getPath(); $err = $handler->rotate($file, ['srcPath' => $srcPath, 'dstPath' => $dstPath, 'rotation' => $rotation]); if (!$err) { $comment = wfMessage('rotate-comment')->numParams($rotation)->inContentLanguage()->text(); $status = $file->upload($dstPath, $comment, $comment, 0, false, false, $this->getUser()); if ($status->isGood()) { $r['result'] = 'Success'; } else { $r['result'] = 'Failure'; $r['errormessage'] = $this->getErrorFormatter()->arrayFromStatus($status); } } else { $r['result'] = 'Failure'; $r['errormessage'] = $err->toText(); } $result[] = $r; } $apiResult = $this->getResult(); ApiResult::setIndexedTagName($result, 'page'); $apiResult->addValue(null, $this->getModuleName(), $result); $this->setContinuationManager(null); $continuationManager->setContinuationIntoResult($apiResult); }
public function execute() { $params = $this->extractRequestParams(); if (!is_null($params['prop'])) { $this->prop = array_flip($params['prop']); } else { $this->prop = []; } $users = (array) $params['users']; $goodNames = $done = []; $result = $this->getResult(); // Canonicalize user names foreach ($users as $u) { $n = User::getCanonicalName($u); if ($n === false || $n === '') { $vals = ['name' => $u, 'invalid' => true]; $fit = $result->addValue(['query', $this->getModuleName()], null, $vals); if (!$fit) { $this->setContinueEnumParameter('users', implode('|', array_diff($users, $done))); $goodNames = []; break; } $done[] = $u; } else { $goodNames[] = $n; } } $result = $this->getResult(); if (count($goodNames)) { $this->addTables('user'); $this->addFields(User::selectFields()); $this->addWhereFld('user_name', $goodNames); $this->showHiddenUsersAddBlockInfo(isset($this->prop['blockinfo'])); $data = []; $res = $this->select(__METHOD__); $this->resetQueryParams(); // get user groups if needed if (isset($this->prop['groups']) || isset($this->prop['rights'])) { $userGroups = []; $this->addTables('user'); $this->addWhereFld('user_name', $goodNames); $this->addTables('user_groups'); $this->addJoinConds(['user_groups' => ['INNER JOIN', 'ug_user=user_id']]); $this->addFields(['user_name', 'ug_group']); $userGroupsRes = $this->select(__METHOD__); foreach ($userGroupsRes as $row) { $userGroups[$row->user_name][] = $row->ug_group; } } foreach ($res as $row) { // create user object and pass along $userGroups if set // that reduces the number of database queries needed in User dramatically if (!isset($userGroups)) { $user = User::newFromRow($row); } else { if (!isset($userGroups[$row->user_name]) || !is_array($userGroups[$row->user_name])) { $userGroups[$row->user_name] = []; } $user = User::newFromRow($row, ['user_groups' => $userGroups[$row->user_name]]); } $name = $user->getName(); $data[$name]['userid'] = $user->getId(); $data[$name]['name'] = $name; if (isset($this->prop['editcount'])) { $data[$name]['editcount'] = $user->getEditCount(); } if (isset($this->prop['registration'])) { $data[$name]['registration'] = wfTimestampOrNull(TS_ISO_8601, $user->getRegistration()); } if (isset($this->prop['groups'])) { $data[$name]['groups'] = $user->getEffectiveGroups(); } if (isset($this->prop['implicitgroups'])) { $data[$name]['implicitgroups'] = $user->getAutomaticGroups(); } if (isset($this->prop['rights'])) { $data[$name]['rights'] = $user->getRights(); } if ($row->ipb_deleted) { $data[$name]['hidden'] = true; } if (isset($this->prop['blockinfo']) && !is_null($row->ipb_by_text)) { $data[$name]['blockid'] = (int) $row->ipb_id; $data[$name]['blockedby'] = $row->ipb_by_text; $data[$name]['blockedbyid'] = (int) $row->ipb_by; $data[$name]['blockedtimestamp'] = wfTimestamp(TS_ISO_8601, $row->ipb_timestamp); $data[$name]['blockreason'] = $row->ipb_reason; $data[$name]['blockexpiry'] = $row->ipb_expiry; } if (isset($this->prop['emailable'])) { $data[$name]['emailable'] = $user->canReceiveEmail(); } if (isset($this->prop['gender'])) { $gender = $user->getOption('gender'); if (strval($gender) === '') { $gender = 'unknown'; } $data[$name]['gender'] = $gender; } if (isset($this->prop['centralids'])) { $data[$name] += ApiQueryUserInfo::getCentralUserInfo($this->getConfig(), $user, $params['attachedwiki']); } if (!is_null($params['token'])) { $tokenFunctions = $this->getTokenFunctions(); foreach ($params['token'] as $t) { $val = call_user_func($tokenFunctions[$t], $user); if ($val === false) { $this->setWarning("Action '{$t}' is not allowed for the current user"); } else { $data[$name][$t . 'token'] = $val; } } } } } $context = $this->getContext(); // Second pass: add result data to $retval foreach ($goodNames as $u) { if (!isset($data[$u])) { $data[$u] = ['name' => $u]; $urPage = new UserrightsPage(); $urPage->setContext($context); $iwUser = $urPage->fetchUser($u); if ($iwUser instanceof UserRightsProxy) { $data[$u]['interwiki'] = true; if (!is_null($params['token'])) { $tokenFunctions = $this->getTokenFunctions(); foreach ($params['token'] as $t) { $val = call_user_func($tokenFunctions[$t], $iwUser); if ($val === false) { $this->setWarning("Action '{$t}' is not allowed for the current user"); } else { $data[$u][$t . 'token'] = $val; } } } } else { $data[$u]['missing'] = true; if (isset($this->prop['cancreate'])) { $status = MediaWiki\Auth\AuthManager::singleton()->canCreateAccount($u); $data[$u]['cancreate'] = $status->isGood(); if (!$status->isGood()) { $data[$u]['cancreateerror'] = $this->getErrorFormatter()->arrayFromStatus($status); } } } } else { if (isset($this->prop['groups']) && isset($data[$u]['groups'])) { ApiResult::setArrayType($data[$u]['groups'], 'array'); ApiResult::setIndexedTagName($data[$u]['groups'], 'g'); } if (isset($this->prop['implicitgroups']) && isset($data[$u]['implicitgroups'])) { ApiResult::setArrayType($data[$u]['implicitgroups'], 'array'); ApiResult::setIndexedTagName($data[$u]['implicitgroups'], 'g'); } if (isset($this->prop['rights']) && isset($data[$u]['rights'])) { ApiResult::setArrayType($data[$u]['rights'], 'array'); ApiResult::setIndexedTagName($data[$u]['rights'], 'r'); } } $fit = $result->addValue(['query', $this->getModuleName()], null, $data[$u]); if (!$fit) { $this->setContinueEnumParameter('users', implode('|', array_diff($users, $done))); break; } $done[] = $u; } $result->addIndexedTagName(['query', $this->getModuleName()], 'user'); }
/** * @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'); } }
public function arrayFromStatus(Status $status, $type = 'error', $format = null) { if ($status->isGood() || !$status->errors) { return []; } $result = []; foreach ($status->getErrorsByType($type) as $error) { if ($error['message'] instanceof Message) { $error = ['message' => $error['message']->getKey(), 'params' => $error['message']->getParams()] + $error; } ApiResult::setIndexedTagName($error['params'], 'param'); $result[] = $error; } ApiResult::setIndexedTagName($result, $type); return $result; }
/** * @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'); } }
/** * @dataProvider provideApiParamFormatting * @covers LogFormatter::formatParametersForApi * @covers LogFormatter::formatParameterValueForApi */ public function testApiParamFormatting($key, $value, $expected) { $entry = $this->newLogEntry('param', array($key => $value)); $formatter = LogFormatter::newFromEntry($entry); $formatter->setContext($this->context); ApiResult::setIndexedTagName($expected, 'param'); ApiResult::setArrayType($expected, 'assoc'); $this->assertEquals($expected, $formatter->formatParametersForApi()); }
/** * Perform the actual upload. Returns a suitable result array on success; * dies on failure. * * @param array $warnings Array of Api upload warnings * @return array */ protected function performUpload($warnings) { // Use comment as initial page text by default if (is_null($this->mParams['text'])) { $this->mParams['text'] = $this->mParams['comment']; } /** @var $file File */ $file = $this->mUpload->getLocalFile(); // For preferences mode, we want to watch if 'watchdefault' is set or // if the *file* doesn't exist and 'watchcreations' is set. But // getWatchlistValue()'s automatic handling checks if the *title* // exists or not, so we need to check both prefs manually. $watch = $this->getWatchlistValue($this->mParams['watchlist'], $file->getTitle(), 'watchdefault'); if (!$watch && $this->mParams['watchlist'] == 'preferences' && !$file->exists()) { $watch = $this->getWatchlistValue($this->mParams['watchlist'], $file->getTitle(), 'watchcreations'); } // Deprecated parameters if ($this->mParams['watch']) { $this->logFeatureUsage('action=upload&watch'); $watch = true; } // No errors, no warnings: do the upload if ($this->mParams['async']) { $progress = UploadBase::getSessionStatus($this->getUser(), $this->mParams['filekey']); if ($progress && $progress['result'] === 'Poll') { $this->dieUsage("Upload from stash already in progress.", 'publishfailed'); } UploadBase::setSessionStatus($this->getUser(), $this->mParams['filekey'], array('result' => 'Poll', 'stage' => 'queued', 'status' => Status::newGood())); JobQueueGroup::singleton()->push(new PublishStashedFileJob(Title::makeTitle(NS_FILE, $this->mParams['filename']), array('filename' => $this->mParams['filename'], 'filekey' => $this->mParams['filekey'], 'comment' => $this->mParams['comment'], 'text' => $this->mParams['text'], 'watch' => $watch, 'session' => $this->getContext()->exportSession()))); $result['result'] = 'Poll'; $result['stage'] = 'queued'; } else { /** @var $status Status */ $status = $this->mUpload->performUpload($this->mParams['comment'], $this->mParams['text'], $watch, $this->getUser()); if (!$status->isGood()) { $error = $status->getErrorsArray(); if (count($error) == 1 && $error[0][0] == 'async') { // The upload can not be performed right now, because the user // requested so return array('result' => 'Queued', 'statuskey' => $error[0][1]); } ApiResult::setIndexedTagName($error, 'error'); $this->dieUsage('An internal error occurred', 'internal-error', 0, $error); } $result['result'] = 'Success'; } $result['filename'] = $file->getName(); if ($warnings && count($warnings) > 0) { $result['warnings'] = $warnings; } return $result; }
/** * Perform the actual upload. Returns a suitable result array on success; * dies on failure. * * @param array $warnings Array of Api upload warnings * @return array */ protected function performUpload($warnings) { // Use comment as initial page text by default if (is_null($this->mParams['text'])) { $this->mParams['text'] = $this->mParams['comment']; } /** @var $file File */ $file = $this->mUpload->getLocalFile(); // For preferences mode, we want to watch if 'watchdefault' is set, // or if the *file* doesn't exist, and either 'watchuploads' or // 'watchcreations' is set. But getWatchlistValue()'s automatic // handling checks if the *title* exists or not, so we need to check // all three preferences manually. $watch = $this->getWatchlistValue($this->mParams['watchlist'], $file->getTitle(), 'watchdefault'); if (!$watch && $this->mParams['watchlist'] == 'preferences' && !$file->exists()) { $watch = $this->getWatchlistValue('preferences', $file->getTitle(), 'watchuploads') || $this->getWatchlistValue('preferences', $file->getTitle(), 'watchcreations'); } // Deprecated parameters if ($this->mParams['watch']) { $watch = true; } if ($this->mParams['tags']) { $status = ChangeTags::canAddTagsAccompanyingChange($this->mParams['tags'], $this->getUser()); if (!$status->isOK()) { $this->dieStatus($status); } } // No errors, no warnings: do the upload if ($this->mParams['async']) { $progress = UploadBase::getSessionStatus($this->getUser(), $this->mParams['filekey']); if ($progress && $progress['result'] === 'Poll') { $this->dieUsage('Upload from stash already in progress.', 'publishfailed'); } UploadBase::setSessionStatus($this->getUser(), $this->mParams['filekey'], ['result' => 'Poll', 'stage' => 'queued', 'status' => Status::newGood()]); JobQueueGroup::singleton()->push(new PublishStashedFileJob(Title::makeTitle(NS_FILE, $this->mParams['filename']), ['filename' => $this->mParams['filename'], 'filekey' => $this->mParams['filekey'], 'comment' => $this->mParams['comment'], 'tags' => $this->mParams['tags'], 'text' => $this->mParams['text'], 'watch' => $watch, 'session' => $this->getContext()->exportSession()])); $result['result'] = 'Poll'; $result['stage'] = 'queued'; } else { /** @var $status Status */ $status = $this->mUpload->performUpload($this->mParams['comment'], $this->mParams['text'], $watch, $this->getUser(), $this->mParams['tags']); if (!$status->isGood()) { $error = $status->getErrorsArray(); ApiResult::setIndexedTagName($error, 'error'); $this->dieUsage('An internal error occurred', 'internal-error', 0, $error); } $result['result'] = 'Success'; } $result['filename'] = $file->getName(); if ($warnings && count($warnings) > 0) { $result['warnings'] = $warnings; } return $result; }
private function extractRowInfo($row) { $logEntry = DatabaseLogEntry::newFromRow($row); $vals = array(ApiResult::META_TYPE => 'assoc'); $anyHidden = false; $user = $this->getUser(); if ($this->fld_ids) { $vals['logid'] = intval($row->log_id); } if ($this->fld_title || $this->fld_parsedcomment) { $title = Title::makeTitle($row->log_namespace, $row->log_title); } if ($this->fld_title || $this->fld_ids || $this->fld_details && $row->log_params !== '') { if (LogEventsList::isDeleted($row, LogPage::DELETED_ACTION)) { $vals['actionhidden'] = true; $anyHidden = true; } if (LogEventsList::userCan($row, LogPage::DELETED_ACTION, $user)) { if ($this->fld_title) { ApiQueryBase::addTitleInfo($vals, $title); } if ($this->fld_ids) { $vals['pageid'] = intval($row->page_id); $vals['logpage'] = intval($row->log_page); } if ($this->fld_details) { $vals['params'] = LogFormatter::newFromEntry($logEntry)->formatParametersForApi(); } } } if ($this->fld_type) { $vals['type'] = $row->log_type; $vals['action'] = $row->log_action; } if ($this->fld_user || $this->fld_userid) { if (LogEventsList::isDeleted($row, LogPage::DELETED_USER)) { $vals['userhidden'] = true; $anyHidden = true; } if (LogEventsList::userCan($row, LogPage::DELETED_USER, $user)) { if ($this->fld_user) { $vals['user'] = $row->user_name === null ? $row->log_user_text : $row->user_name; } if ($this->fld_userid) { $vals['userid'] = intval($row->log_user); } if (!$row->log_user) { $vals['anon'] = true; } } } if ($this->fld_timestamp) { $vals['timestamp'] = wfTimestamp(TS_ISO_8601, $row->log_timestamp); } if (($this->fld_comment || $this->fld_parsedcomment) && isset($row->log_comment)) { if (LogEventsList::isDeleted($row, LogPage::DELETED_COMMENT)) { $vals['commenthidden'] = true; $anyHidden = true; } if (LogEventsList::userCan($row, LogPage::DELETED_COMMENT, $user)) { if ($this->fld_comment) { $vals['comment'] = $row->log_comment; } if ($this->fld_parsedcomment) { $vals['parsedcomment'] = Linker::formatComment($row->log_comment, $title); } } } if ($this->fld_tags) { if ($row->ts_tags) { $tags = explode(',', $row->ts_tags); ApiResult::setIndexedTagName($tags, 'tag'); $vals['tags'] = $tags; } else { $vals['tags'] = array(); } } if ($anyHidden && LogEventsList::isDeleted($row, LogPage::DELETED_RESTRICTED)) { $vals['suppressed'] = true; } return $vals; }
/** * Add a sub-element under the page element with the given page ID * @param int $pageId Page ID * @param array $data Data array à la ApiResult * @return bool Whether the element fit in the result */ protected function addPageSubItems($pageId, $data) { $result = $this->getResult(); ApiResult::setIndexedTagName($data, $this->getModulePrefix()); return $result->addValue(array('query', 'pages', intval($pageId)), $this->getModuleName(), $data); }
protected function getCurrentUserInfo() { $user = $this->getUser(); $result = $this->getResult(); $vals = array(); $vals['id'] = intval($user->getId()); $vals['name'] = $user->getName(); if ($user->isAnon()) { $vals['anon'] = true; } if (isset($this->prop['blockinfo'])) { if ($user->isBlocked()) { $block = $user->getBlock(); $vals['blockid'] = $block->getId(); $vals['blockedby'] = $block->getByName(); $vals['blockedbyid'] = $block->getBy(); $vals['blockreason'] = $user->blockedFor(); $vals['blockedtimestamp'] = wfTimestamp(TS_ISO_8601, $block->mTimestamp); $vals['blockexpiry'] = $block->getExpiry() === 'infinity' ? 'infinite' : wfTimestamp(TS_ISO_8601, $block->getExpiry()); } } if (isset($this->prop['hasmsg'])) { $vals['messages'] = $user->getNewtalk(); } if (isset($this->prop['groups'])) { $vals['groups'] = $user->getEffectiveGroups(); ApiResult::setArrayType($vals['groups'], 'array'); // even if empty ApiResult::setIndexedTagName($vals['groups'], 'g'); // even if empty } if (isset($this->prop['implicitgroups'])) { $vals['implicitgroups'] = $user->getAutomaticGroups(); ApiResult::setArrayType($vals['implicitgroups'], 'array'); // even if empty ApiResult::setIndexedTagName($vals['implicitgroups'], 'g'); // even if empty } if (isset($this->prop['rights'])) { // User::getRights() may return duplicate values, strip them $vals['rights'] = array_values(array_unique($user->getRights())); ApiResult::setArrayType($vals['rights'], 'array'); // even if empty ApiResult::setIndexedTagName($vals['rights'], 'r'); // even if empty } if (isset($this->prop['changeablegroups'])) { $vals['changeablegroups'] = $user->changeableGroups(); ApiResult::setIndexedTagName($vals['changeablegroups']['add'], 'g'); ApiResult::setIndexedTagName($vals['changeablegroups']['remove'], 'g'); ApiResult::setIndexedTagName($vals['changeablegroups']['add-self'], 'g'); ApiResult::setIndexedTagName($vals['changeablegroups']['remove-self'], 'g'); } if (isset($this->prop['options'])) { $vals['options'] = $user->getOptions(); $vals['options'][ApiResult::META_BC_BOOLS] = array_keys($vals['options']); } if (isset($this->prop['preferencestoken'])) { $p = $this->getModulePrefix(); $this->setWarning("{$p}prop=preferencestoken has been deprecated. Please use action=query&meta=tokens instead."); } if (isset($this->prop['preferencestoken']) && !$this->lacksSameOriginSecurity() && $user->isAllowed('editmyoptions')) { $vals['preferencestoken'] = $user->getEditToken('', $this->getMain()->getRequest()); } if (isset($this->prop['editcount'])) { // use intval to prevent null if a non-logged-in user calls // api.php?format=jsonfm&action=query&meta=userinfo&uiprop=editcount $vals['editcount'] = intval($user->getEditCount()); } if (isset($this->prop['ratelimits'])) { $vals['ratelimits'] = $this->getRateLimits(); } if (isset($this->prop['realname']) && !in_array('realname', $this->getConfig()->get('HiddenPrefs'))) { $vals['realname'] = $user->getRealName(); } if ($user->isAllowed('viewmyprivateinfo')) { if (isset($this->prop['email'])) { $vals['email'] = $user->getEmail(); $auth = $user->getEmailAuthenticationTimestamp(); if (!is_null($auth)) { $vals['emailauthenticated'] = wfTimestamp(TS_ISO_8601, $auth); } } } if (isset($this->prop['registrationdate'])) { $regDate = $user->getRegistration(); if ($regDate !== false) { $vals['registrationdate'] = wfTimestamp(TS_ISO_8601, $regDate); } } if (isset($this->prop['acceptlang'])) { $langs = $this->getRequest()->getAcceptLang(); $acceptLang = array(); foreach ($langs as $lang => $val) { $r = array('q' => $val); ApiResult::setContentValue($r, 'code', $lang); $acceptLang[] = $r; } ApiResult::setIndexedTagName($acceptLang, 'lang'); $vals['acceptlang'] = $acceptLang; } if (isset($this->prop['unreadcount'])) { $dbr = $this->getQuery()->getNamedDB('watchlist', DB_SLAVE, 'watchlist'); $count = $dbr->selectRowCount('watchlist', '1', array('wl_user' => $user->getId(), 'wl_notificationtimestamp IS NOT NULL'), __METHOD__, array('LIMIT' => self::WL_UNREAD_LIMIT)); if ($count >= self::WL_UNREAD_LIMIT) { $vals['unreadcount'] = self::WL_UNREAD_LIMIT . '+'; } else { $vals['unreadcount'] = $count; } } return $vals; }
/** * Format parameters for API output * * The result array should generally map named keys to values. Index and * type should be omitted, e.g. "4::foo" should be returned as "foo" in the * output. Values should generally be unformatted. * * Renames or removals of keys besides from the legacy numeric format to * modern named style should be avoided. Any renames should be announced to * the mediawiki-api-announce mailing list. * * @since 1.25 * @return array */ public function formatParametersForApi() { $logParams = array(); foreach ($this->getParametersForApi() as $key => $value) { $vals = explode(':', $key, 3); if (count($vals) !== 3) { $logParams[$key] = $value; continue; } $logParams += $this->formatParameterValueForApi($vals[2], $vals[1], $value); } ApiResult::setIndexedTagName($logParams, 'param'); ApiResult::setArrayType($logParams, 'assoc'); return $logParams; }
private function setIndexedTagNames(&$array, $mapping) { foreach ($mapping as $key => $name) { if (isset($array[$key])) { ApiResult::setIndexedTagName($array[$key], $name); } } }
/** * Formats the internal list of exposed APIs into an array suitable * to pass to the API's XML formatter. * * @return array */ protected function formatRsdApiList() { $apis = $this->getRsdApiList(); $outputData = array(); foreach ($apis as $name => $info) { $data = array('name' => $name, 'preferred' => wfBoolToStr($name == 'MediaWiki'), 'apiLink' => $info['apiLink'], 'blogID' => isset($info['blogID']) ? $info['blogID'] : ''); $settings = array(); if (isset($info['docs'])) { $settings['docs'] = $info['docs']; ApiResult::setSubelementsList($settings, 'docs'); } if (isset($info['settings'])) { foreach ($info['settings'] as $setting => $val) { if (is_bool($val)) { $xmlVal = wfBoolToStr($val); } else { $xmlVal = $val; } $setting = array('name' => $setting); ApiResult::setContentValue($setting, 'value', $xmlVal); $settings[] = $setting; } } if (count($settings)) { ApiResult::setIndexedTagName($settings, 'setting'); $data['settings'] = $settings; } $outputData[] = $data; } return $outputData; }
public function execute() { $user = $this->getUser(); $params = $this->extractRequestParams(); if (is_null($params['text']) && is_null($params['appendtext']) && is_null($params['prependtext']) && $params['undo'] == 0) { $this->dieUsageMsg('missingtext'); } $pageObj = $this->getTitleOrPageId($params); $titleObj = $pageObj->getTitle(); $apiResult = $this->getResult(); if ($params['redirect']) { if ($params['prependtext'] === null && $params['appendtext'] === null && $params['section'] !== 'new') { $this->dieUsage('You have attempted to edit using the "redirect"-following' . ' mode, which must be used in conjuction with section=new, prependtext' . ', or appendtext.', 'redirect-appendonly'); } if ($titleObj->isRedirect()) { $oldTitle = $titleObj; $titles = Revision::newFromTitle($oldTitle, false, Revision::READ_LATEST)->getContent(Revision::FOR_THIS_USER, $user)->getRedirectChain(); // array_shift( $titles ); $redirValues = array(); /** @var $newTitle Title */ foreach ($titles as $id => $newTitle) { if (!isset($titles[$id - 1])) { $titles[$id - 1] = $oldTitle; } $redirValues[] = array('from' => $titles[$id - 1]->getPrefixedText(), 'to' => $newTitle->getPrefixedText()); $titleObj = $newTitle; } ApiResult::setIndexedTagName($redirValues, 'r'); $apiResult->addValue(null, 'redirects', $redirValues); // Since the page changed, update $pageObj $pageObj = WikiPage::factory($titleObj); } } if (!isset($params['contentmodel']) || $params['contentmodel'] == '') { $contentHandler = $pageObj->getContentHandler(); } else { $contentHandler = ContentHandler::getForModelID($params['contentmodel']); } $name = $titleObj->getPrefixedDBkey(); $model = $contentHandler->getModelID(); if ($contentHandler->supportsDirectApiEditing() === false) { $this->dieUsage("Direct editing via API is not supported for content model {$model} used by {$name}", 'no-direct-editing'); } if (!isset($params['contentformat']) || $params['contentformat'] == '') { $params['contentformat'] = $contentHandler->getDefaultFormat(); } $contentFormat = $params['contentformat']; if (!$contentHandler->isSupportedFormat($contentFormat)) { $this->dieUsage("The requested format {$contentFormat} is not supported for content model " . " {$model} used by {$name}", 'badformat'); } if ($params['createonly'] && $titleObj->exists()) { $this->dieUsageMsg('createonly-exists'); } if ($params['nocreate'] && !$titleObj->exists()) { $this->dieUsageMsg('nocreate-missing'); } // Now let's check whether we're even allowed to do this $errors = $titleObj->getUserPermissionsErrors('edit', $user); if (!$titleObj->exists()) { $errors = array_merge($errors, $titleObj->getUserPermissionsErrors('create', $user)); } if (count($errors)) { if (is_array($errors[0])) { switch ($errors[0][0]) { case 'blockedtext': $this->dieUsage('You have been blocked from editing', 'blocked', 0, array('blockinfo' => ApiQueryUserInfo::getBlockInfo($user->getBlock()))); break; case 'autoblockedtext': $this->dieUsage('Your IP address has been blocked automatically, because it was used by a blocked user', 'autoblocked', 0, array('blockinfo' => ApiQueryUserInfo::getBlockInfo($user->getBlock()))); break; default: $this->dieUsageMsg($errors[0]); } } else { $this->dieUsageMsg($errors[0]); } } $toMD5 = $params['text']; if (!is_null($params['appendtext']) || !is_null($params['prependtext'])) { $content = $pageObj->getContent(); if (!$content) { if ($titleObj->getNamespace() == NS_MEDIAWIKI) { # If this is a MediaWiki:x message, then load the messages # and return the message value for x. $text = $titleObj->getDefaultMessageText(); if ($text === false) { $text = ''; } try { $content = ContentHandler::makeContent($text, $this->getTitle()); } catch (MWContentSerializationException $ex) { $this->dieUsage($ex->getMessage(), 'parseerror'); return; } } else { # Otherwise, make a new empty content. $content = $contentHandler->makeEmptyContent(); } } // @todo Add support for appending/prepending to the Content interface if (!$content instanceof TextContent) { $mode = $contentHandler->getModelID(); $this->dieUsage("Can't append to pages using content model {$mode}", 'appendnotsupported'); } if (!is_null($params['section'])) { if (!$contentHandler->supportsSections()) { $modelName = $contentHandler->getModelID(); $this->dieUsage("Sections are not supported for this content model: {$modelName}.", 'sectionsnotsupported'); } if ($params['section'] == 'new') { // DWIM if they're trying to prepend/append to a new section. $content = null; } else { // Process the content for section edits $section = $params['section']; $content = $content->getSection($section); if (!$content) { $this->dieUsage("There is no section {$section}.", 'nosuchsection'); } } } if (!$content) { $text = ''; } else { $text = $content->serialize($contentFormat); } $params['text'] = $params['prependtext'] . $text . $params['appendtext']; $toMD5 = $params['prependtext'] . $params['appendtext']; } if ($params['undo'] > 0) { if ($params['undoafter'] > 0) { if ($params['undo'] < $params['undoafter']) { list($params['undo'], $params['undoafter']) = array($params['undoafter'], $params['undo']); } $undoafterRev = Revision::newFromId($params['undoafter']); } $undoRev = Revision::newFromId($params['undo']); if (is_null($undoRev) || $undoRev->isDeleted(Revision::DELETED_TEXT)) { $this->dieUsageMsg(array('nosuchrevid', $params['undo'])); } if ($params['undoafter'] == 0) { $undoafterRev = $undoRev->getPrevious(); } if (is_null($undoafterRev) || $undoafterRev->isDeleted(Revision::DELETED_TEXT)) { $this->dieUsageMsg(array('nosuchrevid', $params['undoafter'])); } if ($undoRev->getPage() != $pageObj->getID()) { $this->dieUsageMsg(array('revwrongpage', $undoRev->getID(), $titleObj->getPrefixedText())); } if ($undoafterRev->getPage() != $pageObj->getID()) { $this->dieUsageMsg(array('revwrongpage', $undoafterRev->getID(), $titleObj->getPrefixedText())); } $newContent = $contentHandler->getUndoContent($pageObj->getRevision(), $undoRev, $undoafterRev); if (!$newContent) { $this->dieUsageMsg('undo-failure'); } $params['text'] = $newContent->serialize($params['contentformat']); // If no summary was given and we only undid one rev, // use an autosummary if (is_null($params['summary']) && $titleObj->getNextRevisionID($undoafterRev->getID()) == $params['undo']) { $params['summary'] = wfMessage('undo-summary')->params($params['undo'], $undoRev->getUserText())->inContentLanguage()->text(); } } // See if the MD5 hash checks out if (!is_null($params['md5']) && md5($toMD5) !== $params['md5']) { $this->dieUsageMsg('hashcheckfailed'); } // EditPage wants to parse its stuff from a WebRequest // That interface kind of sucks, but it's workable $requestArray = array('wpTextbox1' => $params['text'], 'format' => $contentFormat, 'model' => $contentHandler->getModelID(), 'wpEditToken' => $params['token'], 'wpIgnoreBlankSummary' => true, 'wpIgnoreBlankArticle' => true, 'wpIgnoreSelfRedirect' => true, 'bot' => $params['bot']); if (!is_null($params['summary'])) { $requestArray['wpSummary'] = $params['summary']; } if (!is_null($params['sectiontitle'])) { $requestArray['wpSectionTitle'] = $params['sectiontitle']; } // TODO: Pass along information from 'undoafter' as well if ($params['undo'] > 0) { $requestArray['wpUndidRevision'] = $params['undo']; } // Watch out for basetimestamp == '' or '0' // It gets treated as NOW, almost certainly causing an edit conflict if ($params['basetimestamp'] !== null && (bool) $this->getMain()->getVal('basetimestamp')) { $requestArray['wpEdittime'] = $params['basetimestamp']; } else { $requestArray['wpEdittime'] = $pageObj->getTimestamp(); } if ($params['starttimestamp'] !== null) { $requestArray['wpStarttime'] = $params['starttimestamp']; } else { $requestArray['wpStarttime'] = wfTimestampNow(); // Fake wpStartime } if ($params['minor'] || !$params['notminor'] && $user->getOption('minordefault')) { $requestArray['wpMinoredit'] = ''; } if ($params['recreate']) { $requestArray['wpRecreate'] = ''; } if (!is_null($params['section'])) { $section = $params['section']; if (!preg_match('/^((T-)?\\d+|new)$/', $section)) { $this->dieUsage("The section parameter must be a valid section id or 'new'", "invalidsection"); } $content = $pageObj->getContent(); if ($section !== '0' && $section != 'new' && (!$content || !$content->getSection($section))) { $this->dieUsage("There is no section {$section}.", 'nosuchsection'); } $requestArray['wpSection'] = $params['section']; } else { $requestArray['wpSection'] = ''; } $watch = $this->getWatchlistValue($params['watchlist'], $titleObj); // Deprecated parameters if ($params['watch']) { $this->logFeatureUsage('action=edit&watch'); $watch = true; } elseif ($params['unwatch']) { $this->logFeatureUsage('action=edit&unwatch'); $watch = false; } if ($watch) { $requestArray['wpWatchthis'] = ''; } // Apply change tags if (count($params['tags'])) { if ($user->isAllowed('applychangetags')) { $requestArray['wpChangeTags'] = implode(',', $params['tags']); } else { $this->dieUsage('You don\'t have permission to set change tags.', 'taggingnotallowed'); } } // Pass through anything else we might have been given, to support extensions // This is kind of a hack but it's the best we can do to make extensions work $requestArray += $this->getRequest()->getValues(); global $wgTitle, $wgRequest; $req = new DerivativeRequest($this->getRequest(), $requestArray, true); // Some functions depend on $wgTitle == $ep->mTitle // TODO: Make them not or check if they still do $wgTitle = $titleObj; $articleContext = new RequestContext(); $articleContext->setRequest($req); $articleContext->setWikiPage($pageObj); $articleContext->setUser($this->getUser()); /** @var $articleObject Article */ $articleObject = Article::newFromWikiPage($pageObj, $articleContext); $ep = new EditPage($articleObject); $ep->setApiEditOverride(true); $ep->setContextTitle($titleObj); $ep->importFormData($req); $content = $ep->textbox1; // The following is needed to give the hook the full content of the // new revision rather than just the current section. (Bug 52077) if (!is_null($params['section']) && $contentHandler->supportsSections() && $titleObj->exists()) { // If sectiontitle is set, use it, otherwise use the summary as the section title (for // backwards compatibility with old forms/bots). if ($ep->sectiontitle !== '') { $sectionTitle = $ep->sectiontitle; } else { $sectionTitle = $ep->summary; } $contentObj = $contentHandler->unserializeContent($content, $contentFormat); $fullContentObj = $articleObject->replaceSectionContent($params['section'], $contentObj, $sectionTitle); if ($fullContentObj) { $content = $fullContentObj->serialize($contentFormat); } else { // This most likely means we have an edit conflict which means that the edit // wont succeed anyway. $this->dieUsageMsg('editconflict'); } } // Run hooks // Handle APIEditBeforeSave parameters $r = array(); if (!Hooks::run('APIEditBeforeSave', array($ep, $content, &$r))) { if (count($r)) { $r['result'] = 'Failure'; $apiResult->addValue(null, $this->getModuleName(), $r); return; } $this->dieUsageMsg('hookaborted'); } // Do the actual save $oldRevId = $articleObject->getRevIdFetched(); $result = null; // Fake $wgRequest for some hooks inside EditPage // @todo FIXME: This interface SUCKS $oldRequest = $wgRequest; $wgRequest = $req; $status = $ep->attemptSave($result); $wgRequest = $oldRequest; switch ($status->value) { case EditPage::AS_HOOK_ERROR: case EditPage::AS_HOOK_ERROR_EXPECTED: if (isset($status->apiHookResult)) { $r = $status->apiHookResult; $r['result'] = 'Failure'; $apiResult->addValue(null, $this->getModuleName(), $r); return; } else { $this->dieUsageMsg('hookaborted'); } case EditPage::AS_PARSE_ERROR: $this->dieUsage($status->getMessage(), 'parseerror'); case EditPage::AS_IMAGE_REDIRECT_ANON: $this->dieUsageMsg('noimageredirect-anon'); case EditPage::AS_IMAGE_REDIRECT_LOGGED: $this->dieUsageMsg('noimageredirect-logged'); case EditPage::AS_SPAM_ERROR: $this->dieUsageMsg(array('spamdetected', $result['spam'])); case EditPage::AS_BLOCKED_PAGE_FOR_USER: $this->dieUsage('You have been blocked from editing', 'blocked', 0, array('blockinfo' => ApiQueryUserInfo::getBlockInfo($user->getBlock()))); case EditPage::AS_MAX_ARTICLE_SIZE_EXCEEDED: case EditPage::AS_CONTENT_TOO_BIG: $this->dieUsageMsg(array('contenttoobig', $this->getConfig()->get('MaxArticleSize'))); case EditPage::AS_READ_ONLY_PAGE_ANON: $this->dieUsageMsg('noedit-anon'); case EditPage::AS_READ_ONLY_PAGE_LOGGED: $this->dieUsageMsg('noedit'); case EditPage::AS_READ_ONLY_PAGE: $this->dieReadOnly(); case EditPage::AS_RATE_LIMITED: $this->dieUsageMsg('actionthrottledtext'); case EditPage::AS_ARTICLE_WAS_DELETED: $this->dieUsageMsg('wasdeleted'); case EditPage::AS_NO_CREATE_PERMISSION: $this->dieUsageMsg('nocreate-loggedin'); case EditPage::AS_NO_CHANGE_CONTENT_MODEL: $this->dieUsageMsg('cantchangecontentmodel'); case EditPage::AS_BLANK_ARTICLE: $this->dieUsageMsg('blankpage'); case EditPage::AS_CONFLICT_DETECTED: $this->dieUsageMsg('editconflict'); case EditPage::AS_TEXTBOX_EMPTY: $this->dieUsageMsg('emptynewsection'); case EditPage::AS_CHANGE_TAG_ERROR: $this->dieStatus($status); case EditPage::AS_SUCCESS_NEW_ARTICLE: $r['new'] = true; // fall-through // fall-through case EditPage::AS_SUCCESS_UPDATE: $r['result'] = 'Success'; $r['pageid'] = intval($titleObj->getArticleID()); $r['title'] = $titleObj->getPrefixedText(); $r['contentmodel'] = $articleObject->getContentModel(); $newRevId = $articleObject->getLatest(); if ($newRevId == $oldRevId) { $r['nochange'] = true; } else { $r['oldrevid'] = intval($oldRevId); $r['newrevid'] = intval($newRevId); $r['newtimestamp'] = wfTimestamp(TS_ISO_8601, $pageObj->getTimestamp()); } break; case EditPage::AS_SUMMARY_NEEDED: // Shouldn't happen since we set wpIgnoreBlankSummary, but just in case $this->dieUsageMsg('summaryrequired'); case EditPage::AS_END: default: // $status came from WikiPage::doEdit() $errors = $status->getErrorsArray(); $this->dieUsageMsg($errors[0]); // TODO: Add new errors to message map break; } $apiResult->addValue(null, $this->getModuleName(), $r); }
public function execute() { $this->useTransactionalTimeLimit(); $user = $this->getUser(); $params = $this->extractRequestParams(); $this->requireOnlyOneParameter($params, 'from', 'fromid'); if (isset($params['from'])) { $fromTitle = Title::newFromText($params['from']); if (!$fromTitle || $fromTitle->isExternal()) { $this->dieUsageMsg(array('invalidtitle', $params['from'])); } } elseif (isset($params['fromid'])) { $fromTitle = Title::newFromID($params['fromid']); if (!$fromTitle) { $this->dieUsageMsg(array('nosuchpageid', $params['fromid'])); } } if (!$fromTitle->exists()) { $this->dieUsageMsg('notanarticle'); } $fromTalk = $fromTitle->getTalkPage(); $toTitle = Title::newFromText($params['to']); if (!$toTitle || $toTitle->isExternal()) { $this->dieUsageMsg(array('invalidtitle', $params['to'])); } $toTalk = $toTitle->getTalkPage(); if ($toTitle->getNamespace() == NS_FILE && !RepoGroup::singleton()->getLocalRepo()->findFile($toTitle) && wfFindFile($toTitle)) { if (!$params['ignorewarnings'] && $user->isAllowed('reupload-shared')) { $this->dieUsageMsg('sharedfile-exists'); } elseif (!$user->isAllowed('reupload-shared')) { $this->dieUsageMsg('cantoverwrite-sharedfile'); } } // Rate limit if ($user->pingLimiter('move')) { $this->dieUsageMsg('actionthrottledtext'); } // Move the page $toTitleExists = $toTitle->exists(); $status = $this->movePage($fromTitle, $toTitle, $params['reason'], !$params['noredirect']); if (!$status->isOK()) { $this->dieStatus($status); } $r = array('from' => $fromTitle->getPrefixedText(), 'to' => $toTitle->getPrefixedText(), 'reason' => $params['reason']); //NOTE: we assume that if the old title exists, it's because it was re-created as // a redirect to the new title. This is not safe, but what we did before was // even worse: we just determined whether a redirect should have been created, // and reported that it was created if it should have, without any checks. // Also note that isRedirect() is unreliable because of bug 37209. $r['redirectcreated'] = $fromTitle->exists(); $r['moveoverredirect'] = $toTitleExists; // Move the talk page if ($params['movetalk'] && $fromTalk->exists() && !$fromTitle->isTalkPage()) { $toTalkExists = $toTalk->exists(); $status = $this->movePage($fromTalk, $toTalk, $params['reason'], !$params['noredirect']); if ($status->isOK()) { $r['talkfrom'] = $fromTalk->getPrefixedText(); $r['talkto'] = $toTalk->getPrefixedText(); $r['talkmoveoverredirect'] = $toTalkExists; } else { // We're not gonna dieUsage() on failure, since we already changed something $error = $this->getErrorFromStatus($status); $r['talkmove-error-code'] = $error[0]; $r['talkmove-error-info'] = $error[1]; } } $result = $this->getResult(); // Move subpages if ($params['movesubpages']) { $r['subpages'] = $this->moveSubpages($fromTitle, $toTitle, $params['reason'], $params['noredirect']); ApiResult::setIndexedTagName($r['subpages'], 'subpage'); if ($params['movetalk']) { $r['subpages-talk'] = $this->moveSubpages($fromTalk, $toTalk, $params['reason'], $params['noredirect']); ApiResult::setIndexedTagName($r['subpages-talk'], 'subpage'); } } $watch = 'preferences'; if (isset($params['watchlist'])) { $watch = $params['watchlist']; } elseif ($params['watch']) { $watch = 'watch'; $this->logFeatureUsage('action=move&watch'); } elseif ($params['unwatch']) { $watch = 'unwatch'; $this->logFeatureUsage('action=move&unwatch'); } // Watch pages $this->setWatch($watch, $fromTitle, 'watchmoves'); $this->setWatch($watch, $toTitle, 'watchmoves'); $result->addValue(null, $this->getModuleName(), $r); }
public function execute() { // Cache may vary on $wgUser because ParserOptions gets data from it $this->getMain()->setCacheMode('anon-public-user-private'); // Get parameters $params = $this->extractRequestParams(); $this->requireMaxOneParameter($params, 'prop', 'generatexml'); if ($params['prop'] === null) { $this->logFeatureUsage('action=expandtemplates&!prop'); $this->setWarning('Because no values have been specified for the prop parameter, a ' . 'legacy format has been used for the output. This format is deprecated, and in ' . 'the future, a default value will be set for the prop parameter, causing the new' . 'format to always be used.'); $prop = array(); } else { $prop = array_flip($params['prop']); } // Get title and revision ID for parser $revid = $params['revid']; if ($revid !== null) { $rev = Revision::newFromId($revid); if (!$rev) { $this->dieUsage("There is no revision ID {$revid}", 'missingrev'); } $title_obj = $rev->getTitle(); } else { $title_obj = Title::newFromText($params['title']); if (!$title_obj || $title_obj->isExternal()) { $this->dieUsageMsg(array('invalidtitle', $params['title'])); } } $result = $this->getResult(); // Parse text global $wgParser; $options = ParserOptions::newFromContext($this->getContext()); if ($params['includecomments']) { $options->setRemoveComments(false); } $retval = array(); if (isset($prop['parsetree']) || $params['generatexml']) { if (!isset($prop['parsetree'])) { $this->logFeatureUsage('action=expandtemplates&generatexml'); } $wgParser->startExternalParse($title_obj, $options, Parser::OT_PREPROCESS); $dom = $wgParser->preprocessToDom($params['text']); if (is_callable(array($dom, 'saveXML'))) { $xml = $dom->saveXML(); } else { $xml = $dom->__toString(); } if (isset($prop['parsetree'])) { unset($prop['parsetree']); $retval['parsetree'] = $xml; } else { // the old way $result->addValue(null, 'parsetree', $xml); $result->addValue(null, ApiResult::META_BC_SUBELEMENTS, array('parsetree')); } } // if they didn't want any output except (probably) the parse tree, // then don't bother actually fully expanding it if ($prop || $params['prop'] === null) { $wgParser->startExternalParse($title_obj, $options, Parser::OT_PREPROCESS); $frame = $wgParser->getPreprocessor()->newFrame(); $wikitext = $wgParser->preprocess($params['text'], $title_obj, $options, $revid, $frame); if ($params['prop'] === null) { // the old way ApiResult::setContentValue($retval, 'wikitext', $wikitext); } else { if (isset($prop['categories'])) { $categories = $wgParser->getOutput()->getCategories(); if ($categories) { $categories_result = array(); foreach ($categories as $category => $sortkey) { $entry = array(); $entry['sortkey'] = $sortkey; ApiResult::setContentValue($entry, 'category', $category); $categories_result[] = $entry; } ApiResult::setIndexedTagName($categories_result, 'category'); $retval['categories'] = $categories_result; } } if (isset($prop['properties'])) { $properties = $wgParser->getOutput()->getProperties(); if ($properties) { ApiResult::setArrayType($properties, 'BCkvp', 'name'); ApiResult::setIndexedTagName($properties, 'property'); $retval['properties'] = $properties; } } if (isset($prop['volatile'])) { $retval['volatile'] = $frame->isVolatile(); } if (isset($prop['ttl']) && $frame->getTTL() !== null) { $retval['ttl'] = $frame->getTTL(); } if (isset($prop['wikitext'])) { $retval['wikitext'] = $wikitext; } } } ApiResult::setSubelementsList($retval, array('wikitext', 'parsetree')); $result->addValue(null, $this->getModuleName(), $retval); }
/** * Extracts from a single sql row the data needed to describe one recent change. * * @param stdClass $row The row from which to extract the data. * @return array An array mapping strings (descriptors) to their respective string values. * @access public */ public function extractRowInfo($row) { /* Determine the title of the page that has been changed. */ $title = Title::makeTitle($row->rc_namespace, $row->rc_title); $user = $this->getUser(); /* Our output data. */ $vals = array(); $type = intval($row->rc_type); $vals['type'] = RecentChange::parseFromRCType($type); $anyHidden = false; /* Create a new entry in the result for the title. */ if ($this->fld_title || $this->fld_ids) { if ($type === RC_LOG && $row->rc_deleted & LogPage::DELETED_ACTION) { $vals['actionhidden'] = true; $anyHidden = true; } if ($type !== RC_LOG || LogEventsList::userCanBitfield($row->rc_deleted, LogPage::DELETED_ACTION, $user)) { if ($this->fld_title) { ApiQueryBase::addTitleInfo($vals, $title); } if ($this->fld_ids) { $vals['pageid'] = intval($row->rc_cur_id); $vals['revid'] = intval($row->rc_this_oldid); $vals['old_revid'] = intval($row->rc_last_oldid); } } } if ($this->fld_ids) { $vals['rcid'] = intval($row->rc_id); } /* Add user data and 'anon' flag, if user is anonymous. */ if ($this->fld_user || $this->fld_userid) { if ($row->rc_deleted & Revision::DELETED_USER) { $vals['userhidden'] = true; $anyHidden = true; } if (Revision::userCanBitfield($row->rc_deleted, Revision::DELETED_USER, $user)) { if ($this->fld_user) { $vals['user'] = $row->rc_user_text; } if ($this->fld_userid) { $vals['userid'] = $row->rc_user; } if (!$row->rc_user) { $vals['anon'] = true; } } } /* Add flags, such as new, minor, bot. */ if ($this->fld_flags) { $vals['bot'] = (bool) $row->rc_bot; $vals['new'] = $row->rc_type == RC_NEW; $vals['minor'] = (bool) $row->rc_minor; } /* Add sizes of each revision. (Only available on 1.10+) */ if ($this->fld_sizes) { $vals['oldlen'] = intval($row->rc_old_len); $vals['newlen'] = intval($row->rc_new_len); } /* Add the timestamp. */ if ($this->fld_timestamp) { $vals['timestamp'] = wfTimestamp(TS_ISO_8601, $row->rc_timestamp); } /* Add edit summary / log summary. */ if ($this->fld_comment || $this->fld_parsedcomment) { if ($row->rc_deleted & Revision::DELETED_COMMENT) { $vals['commenthidden'] = true; $anyHidden = true; } if (Revision::userCanBitfield($row->rc_deleted, Revision::DELETED_COMMENT, $user)) { if ($this->fld_comment && isset($row->rc_comment)) { $vals['comment'] = $row->rc_comment; } if ($this->fld_parsedcomment && isset($row->rc_comment)) { $vals['parsedcomment'] = Linker::formatComment($row->rc_comment, $title); } } } if ($this->fld_redirect) { $vals['redirect'] = (bool) $row->page_is_redirect; } /* Add the patrolled flag */ if ($this->fld_patrolled) { $vals['patrolled'] = $row->rc_patrolled == 1; $vals['unpatrolled'] = ChangesList::isUnpatrolled($row, $user); } if ($this->fld_loginfo && $row->rc_type == RC_LOG) { if ($row->rc_deleted & LogPage::DELETED_ACTION) { $vals['actionhidden'] = true; $anyHidden = true; } if (LogEventsList::userCanBitfield($row->rc_deleted, LogPage::DELETED_ACTION, $user)) { $vals['logid'] = intval($row->rc_logid); $vals['logtype'] = $row->rc_log_type; $vals['logaction'] = $row->rc_log_action; $vals['logparams'] = LogFormatter::newFromRow($row)->formatParametersForApi(); } } if ($this->fld_tags) { if ($row->ts_tags) { $tags = explode(',', $row->ts_tags); ApiResult::setIndexedTagName($tags, 'tag'); $vals['tags'] = $tags; } else { $vals['tags'] = array(); } } if ($this->fld_sha1 && $row->rev_sha1 !== null) { if ($row->rev_deleted & Revision::DELETED_TEXT) { $vals['sha1hidden'] = true; $anyHidden = true; } if (Revision::userCanBitfield($row->rev_deleted, Revision::DELETED_TEXT, $user)) { if ($row->rev_sha1 !== '') { $vals['sha1'] = wfBaseConvert($row->rev_sha1, 36, 16, 40); } else { $vals['sha1'] = ''; } } } if (!is_null($this->token)) { $tokenFunctions = $this->getTokenFunctions(); foreach ($this->token as $t) { $val = call_user_func($tokenFunctions[$t], $row->rc_cur_id, $title, RecentChange::newFromRow($row)); if ($val === false) { $this->setWarning("Action '{$t}' is not allowed for the current user"); } else { $vals[$t . 'token'] = $val; } } } if ($anyHidden && $row->rc_deleted & Revision::DELETED_RESTRICTED) { $vals['suppressed'] = true; } return $vals; }
/** * Extract information from the Revision * * @param Revision $revision * @param object $row Should have a field 'ts_tags' if $this->fld_tags is set * @return array */ protected function extractRevisionInfo(Revision $revision, $row) { $title = $revision->getTitle(); $user = $this->getUser(); $vals = array(); $anyHidden = false; if ($this->fld_ids) { $vals['revid'] = intval($revision->getId()); if (!is_null($revision->getParentId())) { $vals['parentid'] = intval($revision->getParentId()); } } if ($this->fld_flags) { $vals['minor'] = $revision->isMinor(); } if ($this->fld_user || $this->fld_userid) { if ($revision->isDeleted(Revision::DELETED_USER)) { $vals['userhidden'] = true; $anyHidden = true; } if ($revision->userCan(Revision::DELETED_USER, $user)) { if ($this->fld_user) { $vals['user'] = $revision->getUserText(Revision::RAW); } $userid = $revision->getUser(Revision::RAW); if (!$userid) { $vals['anon'] = true; } if ($this->fld_userid) { $vals['userid'] = $userid; } } } if ($this->fld_timestamp) { $vals['timestamp'] = wfTimestamp(TS_ISO_8601, $revision->getTimestamp()); } if ($this->fld_size) { if (!is_null($revision->getSize())) { $vals['size'] = intval($revision->getSize()); } else { $vals['size'] = 0; } } if ($this->fld_sha1) { if ($revision->isDeleted(Revision::DELETED_TEXT)) { $vals['sha1hidden'] = true; $anyHidden = true; } if ($revision->userCan(Revision::DELETED_TEXT, $user)) { if ($revision->getSha1() != '') { $vals['sha1'] = wfBaseConvert($revision->getSha1(), 36, 16, 40); } else { $vals['sha1'] = ''; } } } if ($this->fld_contentmodel) { $vals['contentmodel'] = $revision->getContentModel(); } if ($this->fld_comment || $this->fld_parsedcomment) { if ($revision->isDeleted(Revision::DELETED_COMMENT)) { $vals['commenthidden'] = true; $anyHidden = true; } if ($revision->userCan(Revision::DELETED_COMMENT, $user)) { $comment = $revision->getComment(Revision::RAW); if ($this->fld_comment) { $vals['comment'] = $comment; } if ($this->fld_parsedcomment) { $vals['parsedcomment'] = Linker::formatComment($comment, $title); } } } if ($this->fld_tags) { if ($row->ts_tags) { $tags = explode(',', $row->ts_tags); ApiResult::setIndexedTagName($tags, 'tag'); $vals['tags'] = $tags; } else { $vals['tags'] = array(); } } $content = null; global $wgParser; if ($this->fetchContent) { $content = $revision->getContent(Revision::FOR_THIS_USER, $this->getUser()); // Expand templates after getting section content because // template-added sections don't count and Parser::preprocess() // will have less input if ($content && $this->section !== false) { $content = $content->getSection($this->section, false); if (!$content) { $this->dieUsage("There is no section {$this->section} in r" . $revision->getId(), 'nosuchsection'); } } if ($revision->isDeleted(Revision::DELETED_TEXT)) { $vals['texthidden'] = true; $anyHidden = true; } elseif (!$content) { $vals['textmissing'] = true; } } if ($this->fld_content && $content) { $text = null; if ($this->generateXML) { if ($content->getModel() === CONTENT_MODEL_WIKITEXT) { $t = $content->getNativeData(); # note: don't set $text $wgParser->startExternalParse($title, ParserOptions::newFromContext($this->getContext()), Parser::OT_PREPROCESS); $dom = $wgParser->preprocessToDom($t); if (is_callable(array($dom, 'saveXML'))) { $xml = $dom->saveXML(); } else { $xml = $dom->__toString(); } $vals['parsetree'] = $xml; } else { $vals['badcontentformatforparsetree'] = true; $this->setWarning("Conversion to XML is supported for wikitext only, " . $title->getPrefixedDBkey() . " uses content model " . $content->getModel()); } } if ($this->expandTemplates && !$this->parseContent) { #XXX: implement template expansion for all content types in ContentHandler? if ($content->getModel() === CONTENT_MODEL_WIKITEXT) { $text = $content->getNativeData(); $text = $wgParser->preprocess($text, $title, ParserOptions::newFromContext($this->getContext())); } else { $this->setWarning("Template expansion is supported for wikitext only, " . $title->getPrefixedDBkey() . " uses content model " . $content->getModel()); $vals['badcontentformat'] = true; $text = false; } } if ($this->parseContent) { $po = $content->getParserOutput($title, $revision->getId(), ParserOptions::newFromContext($this->getContext())); $text = $po->getText(); } if ($text === null) { $format = $this->contentFormat ? $this->contentFormat : $content->getDefaultFormat(); $model = $content->getModel(); if (!$content->isSupportedFormat($format)) { $name = $title->getPrefixedDBkey(); $this->setWarning("The requested format {$this->contentFormat} is not " . "supported for content model {$model} used by {$name}"); $vals['badcontentformat'] = true; $text = false; } else { $text = $content->serialize($format); // always include format and model. // Format is needed to deserialize, model is needed to interpret. $vals['contentformat'] = $format; $vals['contentmodel'] = $model; } } if ($text !== false) { ApiResult::setContentValue($vals, 'content', $text); } } if ($content && (!is_null($this->diffto) || !is_null($this->difftotext))) { static $n = 0; // Number of uncached diffs we've had if ($n < $this->getConfig()->get('APIMaxUncachedDiffs')) { $vals['diff'] = array(); $context = new DerivativeContext($this->getContext()); $context->setTitle($title); $handler = $revision->getContentHandler(); if (!is_null($this->difftotext)) { $model = $title->getContentModel(); if ($this->contentFormat && !ContentHandler::getForModelID($model)->isSupportedFormat($this->contentFormat)) { $name = $title->getPrefixedDBkey(); $this->setWarning("The requested format {$this->contentFormat} is not " . "supported for content model {$model} used by {$name}"); $vals['diff']['badcontentformat'] = true; $engine = null; } else { $difftocontent = ContentHandler::makeContent($this->difftotext, $title, $model, $this->contentFormat); $engine = $handler->createDifferenceEngine($context); $engine->setContent($content, $difftocontent); } } else { $engine = $handler->createDifferenceEngine($context, $revision->getID(), $this->diffto); $vals['diff']['from'] = $engine->getOldid(); $vals['diff']['to'] = $engine->getNewid(); } if ($engine) { $difftext = $engine->getDiffBody(); ApiResult::setContentValue($vals['diff'], 'body', $difftext); if (!$engine->wasCacheHit()) { $n++; } } } else { $vals['diff']['notcached'] = true; } } if ($anyHidden && $revision->isDeleted(Revision::DELETED_RESTRICTED)) { $vals['suppressed'] = true; } return $vals; }