public function execute()
 {
     $params = $this->extractRequestParams();
     $rev1 = $this->revisionOrTitleOrId($params['fromrev'], $params['fromtitle'], $params['fromid']);
     $rev2 = $this->revisionOrTitleOrId($params['torev'], $params['totitle'], $params['toid']);
     $revision = Revision::newFromId($rev1);
     if (!$revision) {
         $this->dieUsage('The diff cannot be retrieved, ' . 'one revision does not exist or you do not have permission to view it.', 'baddiff');
     }
     $contentHandler = $revision->getContentHandler();
     $de = $contentHandler->createDifferenceEngine($this->getContext(), $rev1, $rev2, null, true, false);
     $vals = array();
     if (isset($params['fromtitle'])) {
         $vals['fromtitle'] = $params['fromtitle'];
     }
     if (isset($params['fromid'])) {
         $vals['fromid'] = $params['fromid'];
     }
     $vals['fromrevid'] = $rev1;
     if (isset($params['totitle'])) {
         $vals['totitle'] = $params['totitle'];
     }
     if (isset($params['toid'])) {
         $vals['toid'] = $params['toid'];
     }
     $vals['torevid'] = $rev2;
     $difftext = $de->getDiffBody();
     if ($difftext === false) {
         $this->dieUsage('The diff cannot be retrieved. Maybe one or both revisions do ' . 'not exist or you do not have permission to view them.', 'baddiff');
     }
     ApiResult::setContentValue($vals, 'body', $difftext);
     $this->getResult()->addValue(null, $this->getModuleName(), $vals);
 }
 public function execute()
 {
     if ($this->getPageSet()->getGoodTitleCount() == 0) {
         return;
     }
     $params = $this->extractRequestParams();
     $query = $params['query'];
     $protocol = ApiQueryExtLinksUsage::getProtocolPrefix($params['protocol']);
     $this->addFields(array('el_from', 'el_to'));
     $this->addTables('externallinks');
     $this->addWhereFld('el_from', array_keys($this->getPageSet()->getGoodTitles()));
     $whereQuery = $this->prepareUrlQuerySearchString($query, $protocol);
     if ($whereQuery !== null) {
         $this->addWhere($whereQuery);
     }
     // Don't order by el_from if it's constant in the WHERE clause
     if (count($this->getPageSet()->getGoodTitles()) != 1) {
         $this->addOption('ORDER BY', 'el_from');
     }
     // If we're querying all protocols, use DISTINCT to avoid repeating protocol-relative links twice
     if ($protocol === null) {
         $this->addOption('DISTINCT');
     }
     $this->addOption('LIMIT', $params['limit'] + 1);
     $offset = isset($params['offset']) ? $params['offset'] : 0;
     if ($offset) {
         $this->addOption('OFFSET', $params['offset']);
     }
     $res = $this->select(__METHOD__);
     $count = 0;
     foreach ($res as $row) {
         if (++$count > $params['limit']) {
             // We've reached the one extra which shows that
             // there are additional pages to be had. Stop here...
             $this->setContinueEnumParameter('offset', $offset + $params['limit']);
             break;
         }
         $entry = array();
         $to = $row->el_to;
         // expand protocol-relative urls
         if ($params['expandurl']) {
             $to = wfExpandUrl($to, PROTO_CANONICAL);
         }
         ApiResult::setContentValue($entry, 'url', $to);
         $fit = $this->addPageSubItem($row->el_from, $entry);
         if (!$fit) {
             $this->setContinueEnumParameter('offset', $offset + $count - 1);
             break;
         }
     }
 }
 public function execute()
 {
     $params = $this->extractRequestParams();
     $title = Title::newFromText($params['title']);
     if (!$title) {
         $this->dieUsage('Invalid title', 'invalidtitle');
     }
     $handle = new MessageHandle($title);
     if (!$handle->isValid()) {
         $this->dieUsage('Title does not correspond to a translatable message', 'nomessagefortitle');
     }
     $namespace = $title->getNamespace();
     $pageInfo = self::getTranslations($handle);
     $result = $this->getResult();
     $count = 0;
     foreach ($pageInfo as $key => $info) {
         if (++$count <= $params['offset']) {
             continue;
         }
         $tTitle = Title::makeTitle($namespace, $key);
         $tHandle = new MessageHandle($tTitle);
         $data = array('title' => $tTitle->getPrefixedText(), 'language' => $tHandle->getCode(), 'lasttranslator' => $info[1]);
         $fuzzy = MessageHandle::hasFuzzyString($info[0]) || $tHandle->isFuzzy();
         if ($fuzzy) {
             $data['fuzzy'] = 'fuzzy';
         }
         $translation = str_replace(TRANSLATE_FUZZY, '', $info[0]);
         if (defined('ApiResult::META_CONTENT')) {
             ApiResult::setContentValue($data, 'translation', $translation);
         } else {
             ApiResult::setContent($data, $translation);
         }
         $fit = $result->addValue(array('query', $this->getModuleName()), null, $data);
         if (!$fit) {
             $this->setContinueEnumParameter('offset', $count);
             break;
         }
     }
     if (defined('ApiResult::META_CONTENT')) {
         $result->addIndexedTagName(array('query', $this->getModuleName()), 'message');
     } else {
         $result->setIndexedTagName_internal(array('query', $this->getModuleName()), 'message');
     }
 }
 /**
  * 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;
 }
 public function execute()
 {
     if ($this->getPageSet()->getGoodTitleCount() == 0) {
         return;
     }
     $params = $this->extractRequestParams();
     $prop = array_flip((array) $params['prop']);
     if (isset($params['title']) && !isset($params['lang'])) {
         $this->dieUsageMsg(array('missingparam', 'lang'));
     }
     // Handle deprecated param
     $this->requireMaxOneParameter($params, 'url', 'prop');
     if ($params['url']) {
         $prop = array('url' => 1);
     }
     $this->addFields(array('ll_from', 'll_lang', 'll_title'));
     $this->addTables('langlinks');
     $this->addWhereFld('ll_from', array_keys($this->getPageSet()->getGoodTitles()));
     if (!is_null($params['continue'])) {
         $cont = explode('|', $params['continue']);
         $this->dieContinueUsageIf(count($cont) != 2);
         $op = $params['dir'] == 'descending' ? '<' : '>';
         $llfrom = intval($cont[0]);
         $lllang = $this->getDB()->addQuotes($cont[1]);
         $this->addWhere("ll_from {$op} {$llfrom} OR " . "(ll_from = {$llfrom} AND " . "ll_lang {$op}= {$lllang})");
     }
     // FIXME: (follow-up) To allow extensions to add to the language links, we need
     //       to load them all, add the extra links, then apply paging.
     //       Should not be terrible, it's not going to be more than a few hundred links.
     // Note that, since (ll_from, ll_lang) is a unique key, we don't need
     // to sort by ll_title to ensure deterministic ordering.
     $sort = $params['dir'] == 'descending' ? ' DESC' : '';
     if (isset($params['lang'])) {
         $this->addWhereFld('ll_lang', $params['lang']);
         if (isset($params['title'])) {
             $this->addWhereFld('ll_title', $params['title']);
         }
         $this->addOption('ORDER BY', 'll_from' . $sort);
     } else {
         // Don't order by ll_from if it's constant in the WHERE clause
         if (count($this->getPageSet()->getGoodTitles()) == 1) {
             $this->addOption('ORDER BY', 'll_lang' . $sort);
         } else {
             $this->addOption('ORDER BY', array('ll_from' . $sort, 'll_lang' . $sort));
         }
     }
     $this->addOption('LIMIT', $params['limit'] + 1);
     $res = $this->select(__METHOD__);
     $count = 0;
     foreach ($res as $row) {
         if (++$count > $params['limit']) {
             // We've reached the one extra which shows that
             // there are additional pages to be had. Stop here...
             $this->setContinueEnumParameter('continue', "{$row->ll_from}|{$row->ll_lang}");
             break;
         }
         $entry = array('lang' => $row->ll_lang);
         if (isset($prop['url'])) {
             $title = Title::newFromText("{$row->ll_lang}:{$row->ll_title}");
             if ($title) {
                 $entry['url'] = wfExpandUrl($title->getFullURL(), PROTO_CURRENT);
             }
         }
         if (isset($prop['langname'])) {
             $entry['langname'] = Language::fetchLanguageName($row->ll_lang, $params['inlanguagecode']);
         }
         if (isset($prop['autonym'])) {
             $entry['autonym'] = Language::fetchLanguageName($row->ll_lang);
         }
         ApiResult::setContentValue($entry, 'title', $row->ll_title);
         $fit = $this->addPageSubItem($row->ll_from, $entry);
         if (!$fit) {
             $this->setContinueEnumParameter('continue', "{$row->ll_from}|{$row->ll_lang}");
             break;
         }
     }
 }
 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;
 }
 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);
 }
Example #8
0
 private function formatHeadItems($headItems)
 {
     $result = [];
     foreach ($headItems as $tag => $content) {
         $entry = [];
         $entry['tag'] = $tag;
         ApiResult::setContentValue($entry, 'content', $content);
         $result[] = $entry;
     }
     return $result;
 }
 protected function getCurrentUserInfo()
 {
     $user = $this->getUser();
     $vals = [];
     $vals['id'] = intval($user->getId());
     $vals['name'] = $user->getName();
     if ($user->isAnon()) {
         $vals['anon'] = true;
     }
     if (isset($this->prop['blockinfo']) && $user->isBlocked()) {
         $vals = array_merge($vals, self::getBlockInfo($user->getBlock()));
     }
     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 = [];
         foreach ($langs as $lang => $val) {
             $r = ['q' => $val];
             ApiResult::setContentValue($r, 'code', $lang);
             $acceptLang[] = $r;
         }
         ApiResult::setIndexedTagName($acceptLang, 'lang');
         $vals['acceptlang'] = $acceptLang;
     }
     if (isset($this->prop['unreadcount'])) {
         $store = MediaWikiServices::getInstance()->getWatchedItemStore();
         $unreadNotifications = $store->countUnreadNotifications($user, self::WL_UNREAD_LIMIT);
         if ($unreadNotifications === true) {
             $vals['unreadcount'] = self::WL_UNREAD_LIMIT . '+';
         } else {
             $vals['unreadcount'] = $unreadNotifications;
         }
     }
     if (isset($this->prop['centralids'])) {
         $vals += self::getCentralUserInfo($this->getConfig(), $this->getUser(), $this->params['attachedwiki']);
     }
     return $vals;
 }
Example #10
0
 /**
  * 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;
 }
Example #11
0
 /**
  * @covers ApiResult
  */
 public function testStaticDataMethods()
 {
     $arr = array();
     ApiResult::setValue($arr, 'setValue', '1');
     ApiResult::setValue($arr, null, 'unnamed 1');
     ApiResult::setValue($arr, null, 'unnamed 2');
     ApiResult::setValue($arr, 'deleteValue', '2');
     ApiResult::unsetValue($arr, 'deleteValue');
     ApiResult::setContentValue($arr, 'setContentValue', '3');
     $this->assertSame(array('setValue' => '1', 'unnamed 1', 'unnamed 2', ApiResult::META_CONTENT => 'setContentValue', 'setContentValue' => '3'), $arr);
     try {
         ApiResult::setValue($arr, 'setValue', '99');
         $this->fail('Expected exception not thrown');
     } catch (RuntimeException $ex) {
         $this->assertSame('Attempting to add element setValue=99, existing value is 1', $ex->getMessage(), 'Expected exception');
     }
     try {
         ApiResult::setContentValue($arr, 'setContentValue2', '99');
         $this->fail('Expected exception not thrown');
     } catch (RuntimeException $ex) {
         $this->assertSame('Attempting to set content element as setContentValue2 when setContentValue ' . 'is already set as the content element', $ex->getMessage(), 'Expected exception');
     }
     ApiResult::setValue($arr, 'setValue', '99', ApiResult::OVERRIDE);
     $this->assertSame('99', $arr['setValue']);
     ApiResult::setContentValue($arr, 'setContentValue2', '99', ApiResult::OVERRIDE);
     $this->assertSame('setContentValue2', $arr[ApiResult::META_CONTENT]);
     $arr = array('foo' => 1, 'bar' => 1);
     ApiResult::setValue($arr, 'top', '2', ApiResult::ADD_ON_TOP);
     ApiResult::setValue($arr, null, '2', ApiResult::ADD_ON_TOP);
     ApiResult::setValue($arr, 'bottom', '2');
     ApiResult::setValue($arr, 'foo', '2', ApiResult::OVERRIDE);
     ApiResult::setValue($arr, 'bar', '2', ApiResult::OVERRIDE | ApiResult::ADD_ON_TOP);
     $this->assertSame(array(0, 'top', 'foo', 'bar', 'bottom'), array_keys($arr));
     $arr = array();
     ApiResult::setValue($arr, 'sub', array('foo' => 1));
     ApiResult::setValue($arr, 'sub', array('bar' => 1));
     $this->assertSame(array('sub' => array('foo' => 1, 'bar' => 1)), $arr);
     try {
         ApiResult::setValue($arr, 'sub', array('foo' => 2, 'baz' => 2));
         $this->fail('Expected exception not thrown');
     } catch (RuntimeException $ex) {
         $this->assertSame('Conflicting keys (foo) when attempting to merge element sub', $ex->getMessage(), 'Expected exception');
     }
     $arr = array();
     $title = Title::newFromText("MediaWiki:Foobar");
     $obj = new stdClass();
     $obj->foo = 1;
     $obj->bar = 2;
     ApiResult::setValue($arr, 'title', $title);
     ApiResult::setValue($arr, 'obj', $obj);
     $this->assertSame(array('title' => (string) $title, 'obj' => array('foo' => 1, 'bar' => 2, ApiResult::META_TYPE => 'assoc')), $arr);
     $fh = tmpfile();
     try {
         ApiResult::setValue($arr, 'file', $fh);
         $this->fail('Expected exception not thrown');
     } catch (InvalidArgumentException $ex) {
         $this->assertSame('Cannot add resource(stream) to ApiResult', $ex->getMessage(), 'Expected exception');
     }
     try {
         ApiResult::setValue($arr, null, $fh);
         $this->fail('Expected exception not thrown');
     } catch (InvalidArgumentException $ex) {
         $this->assertSame('Cannot add resource(stream) to ApiResult', $ex->getMessage(), 'Expected exception');
     }
     try {
         $obj->file = $fh;
         ApiResult::setValue($arr, 'sub', $obj);
         $this->fail('Expected exception not thrown');
     } catch (InvalidArgumentException $ex) {
         $this->assertSame('Cannot add resource(stream) to ApiResult', $ex->getMessage(), 'Expected exception');
     }
     try {
         $obj->file = $fh;
         ApiResult::setValue($arr, null, $obj);
         $this->fail('Expected exception not thrown');
     } catch (InvalidArgumentException $ex) {
         $this->assertSame('Cannot add resource(stream) to ApiResult', $ex->getMessage(), 'Expected exception');
     }
     fclose($fh);
     try {
         ApiResult::setValue($arr, 'inf', INF);
         $this->fail('Expected exception not thrown');
     } catch (InvalidArgumentException $ex) {
         $this->assertSame('Cannot add non-finite floats to ApiResult', $ex->getMessage(), 'Expected exception');
     }
     try {
         ApiResult::setValue($arr, null, INF);
         $this->fail('Expected exception not thrown');
     } catch (InvalidArgumentException $ex) {
         $this->assertSame('Cannot add non-finite floats to ApiResult', $ex->getMessage(), 'Expected exception');
     }
     try {
         ApiResult::setValue($arr, 'nan', NAN);
         $this->fail('Expected exception not thrown');
     } catch (InvalidArgumentException $ex) {
         $this->assertSame('Cannot add non-finite floats to ApiResult', $ex->getMessage(), 'Expected exception');
     }
     try {
         ApiResult::setValue($arr, null, NAN);
         $this->fail('Expected exception not thrown');
     } catch (InvalidArgumentException $ex) {
         $this->assertSame('Cannot add non-finite floats to ApiResult', $ex->getMessage(), 'Expected exception');
     }
     ApiResult::setValue($arr, null, NAN, ApiResult::NO_VALIDATE);
     try {
         ApiResult::setValue($arr, null, NAN, ApiResult::NO_SIZE_CHECK);
         $this->fail('Expected exception not thrown');
     } catch (InvalidArgumentException $ex) {
         $this->assertSame('Cannot add non-finite floats to ApiResult', $ex->getMessage(), 'Expected exception');
     }
     $arr = array();
     $result2 = new ApiResult(8388608);
     $result2->addValue(null, 'foo', 'bar');
     ApiResult::setValue($arr, 'baz', $result2);
     $this->assertSame(array('baz' => array(ApiResult::META_TYPE => 'assoc', 'foo' => 'bar')), $arr);
     $arr = array();
     ApiResult::setValue($arr, 'foo', "foo�bar");
     ApiResult::setValue($arr, 'bar', "á");
     ApiResult::setValue($arr, 'baz', 74);
     ApiResult::setValue($arr, null, "foo�bar");
     ApiResult::setValue($arr, null, "á");
     $this->assertSame(array('foo' => "foo�bar", 'bar' => "á", 'baz' => 74, 0 => "foo�bar", 1 => "á"), $arr);
 }
Example #12
0
 public function execute()
 {
     if ($this->getPageSet()->getGoodTitleCount() == 0) {
         return;
     }
     $params = $this->extractRequestParams();
     $prop = array_flip((array) $params['prop']);
     if (isset($params['title']) && !isset($params['prefix'])) {
         $this->dieUsageMsg(['missingparam', 'prefix']);
     }
     // Handle deprecated param
     $this->requireMaxOneParameter($params, 'url', 'prop');
     if ($params['url']) {
         $prop = ['url' => 1];
     }
     $this->addFields(['iwl_from', 'iwl_prefix', 'iwl_title']);
     $this->addTables('iwlinks');
     $this->addWhereFld('iwl_from', array_keys($this->getPageSet()->getGoodTitles()));
     if (!is_null($params['continue'])) {
         $cont = explode('|', $params['continue']);
         $this->dieContinueUsageIf(count($cont) != 3);
         $op = $params['dir'] == 'descending' ? '<' : '>';
         $db = $this->getDB();
         $iwlfrom = intval($cont[0]);
         $iwlprefix = $db->addQuotes($cont[1]);
         $iwltitle = $db->addQuotes($cont[2]);
         $this->addWhere("iwl_from {$op} {$iwlfrom} OR " . "(iwl_from = {$iwlfrom} AND " . "(iwl_prefix {$op} {$iwlprefix} OR " . "(iwl_prefix = {$iwlprefix} AND " . "iwl_title {$op}= {$iwltitle})))");
     }
     $sort = $params['dir'] == 'descending' ? ' DESC' : '';
     if (isset($params['prefix'])) {
         $this->addWhereFld('iwl_prefix', $params['prefix']);
         if (isset($params['title'])) {
             $this->addWhereFld('iwl_title', $params['title']);
             $this->addOption('ORDER BY', 'iwl_from' . $sort);
         } else {
             $this->addOption('ORDER BY', ['iwl_from' . $sort, 'iwl_title' . $sort]);
         }
     } else {
         // Don't order by iwl_from if it's constant in the WHERE clause
         if (count($this->getPageSet()->getGoodTitles()) == 1) {
             $this->addOption('ORDER BY', 'iwl_prefix' . $sort);
         } else {
             $this->addOption('ORDER BY', ['iwl_from' . $sort, 'iwl_prefix' . $sort, 'iwl_title' . $sort]);
         }
     }
     $this->addOption('LIMIT', $params['limit'] + 1);
     $res = $this->select(__METHOD__);
     $count = 0;
     foreach ($res as $row) {
         if (++$count > $params['limit']) {
             // We've reached the one extra which shows that
             // there are additional pages to be had. Stop here...
             $this->setContinueEnumParameter('continue', "{$row->iwl_from}|{$row->iwl_prefix}|{$row->iwl_title}");
             break;
         }
         $entry = ['prefix' => $row->iwl_prefix];
         if (isset($prop['url'])) {
             $title = Title::newFromText("{$row->iwl_prefix}:{$row->iwl_title}");
             if ($title) {
                 $entry['url'] = wfExpandUrl($title->getFullURL(), PROTO_CURRENT);
             }
         }
         ApiResult::setContentValue($entry, 'title', $row->iwl_title);
         $fit = $this->addPageSubItem($row->iwl_from, $entry);
         if (!$fit) {
             $this->setContinueEnumParameter('continue', "{$row->iwl_from}|{$row->iwl_prefix}|{$row->iwl_title}");
             break;
         }
     }
 }
Example #13
0
 /**
  * Replace the result data with the information about an exception.
  * Returns the error code
  * @param Exception $e
  * @return string
  */
 protected function substituteResultWithError($e)
 {
     $result = $this->getResult();
     $config = $this->getConfig();
     $errMessage = $this->errorMessageFromException($e);
     if ($e instanceof UsageException) {
         // User entered incorrect parameters - generate error response
         $link = wfExpandUrl(wfScript('api'));
         ApiResult::setContentValue($errMessage, 'docref', "See {$link} for API usage");
     } else {
         // Something is seriously wrong
         if ($config->get('ShowExceptionDetails')) {
             ApiResult::setContentValue($errMessage, 'trace', MWExceptionHandler::getRedactedTraceAsString($e));
         }
     }
     // Remember all the warnings to re-add them later
     $warnings = $result->getResultData(['warnings']);
     $result->reset();
     // Re-add the id
     $requestid = $this->getParameter('requestid');
     if (!is_null($requestid)) {
         $result->addValue(null, 'requestid', $requestid, ApiResult::NO_SIZE_CHECK);
     }
     if ($config->get('ShowHostnames')) {
         // servedby is especially useful when debugging errors
         $result->addValue(null, 'servedby', wfHostname(), ApiResult::NO_SIZE_CHECK);
     }
     if ($warnings !== null) {
         $result->addValue(null, 'warnings', $warnings, ApiResult::NO_SIZE_CHECK);
     }
     $result->addValue(null, 'error', $errMessage, ApiResult::NO_SIZE_CHECK);
     return $errMessage['code'];
 }
 public function execute()
 {
     $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');
     }
     $this->setWarning('list=deletedrevs has been deprecated. Please use prop=deletedrevisions or ' . 'list=alldeletedrevisions instead.');
     $this->logFeatureUsage('action=query&list=deletedrevs');
     $db = $this->getDB();
     $params = $this->extractRequestParams(false);
     $prop = array_flip($params['prop']);
     $fld_parentid = isset($prop['parentid']);
     $fld_revid = isset($prop['revid']);
     $fld_user = isset($prop['user']);
     $fld_userid = isset($prop['userid']);
     $fld_comment = isset($prop['comment']);
     $fld_parsedcomment = isset($prop['parsedcomment']);
     $fld_minor = isset($prop['minor']);
     $fld_len = isset($prop['len']);
     $fld_sha1 = isset($prop['sha1']);
     $fld_content = isset($prop['content']);
     $fld_token = isset($prop['token']);
     $fld_tags = isset($prop['tags']);
     if (isset($prop['token'])) {
         $p = $this->getModulePrefix();
         $this->setWarning("{$p}prop=token has been deprecated. Please use action=query&meta=tokens instead.");
     }
     // If we're in a mode that breaks the same-origin policy, no tokens can
     // be obtained
     if ($this->lacksSameOriginSecurity()) {
         $fld_token = false;
     }
     // If user can't undelete, no tokens
     if (!$user->isAllowed('undelete')) {
         $fld_token = false;
     }
     $result = $this->getResult();
     $pageSet = $this->getPageSet();
     $titles = $pageSet->getTitles();
     // This module operates in three modes:
     // 'revs': List deleted revs for certain titles (1)
     // 'user': List deleted revs by a certain user (2)
     // 'all': List all deleted revs in NS (3)
     $mode = 'all';
     if (count($titles) > 0) {
         $mode = 'revs';
     } elseif (!is_null($params['user'])) {
         $mode = 'user';
     }
     if ($mode == 'revs' || $mode == 'user') {
         // Ignore namespace and unique due to inability to know whether they were purposely set
         foreach (array('from', 'to', 'prefix') as $p) {
             if (!is_null($params[$p])) {
                 $this->dieUsage("The '{$p}' parameter cannot be used in modes 1 or 2", 'badparams');
             }
         }
     } else {
         foreach (array('start', 'end') as $p) {
             if (!is_null($params[$p])) {
                 $this->dieUsage("The {$p} parameter cannot be used in mode 3", 'badparams');
             }
         }
     }
     if (!is_null($params['user']) && !is_null($params['excludeuser'])) {
         $this->dieUsage('user and excludeuser cannot be used together', 'badparams');
     }
     $this->addTables('archive');
     $this->addFields(array('ar_title', 'ar_namespace', 'ar_timestamp', 'ar_deleted', 'ar_id'));
     $this->addFieldsIf('ar_parent_id', $fld_parentid);
     $this->addFieldsIf('ar_rev_id', $fld_revid);
     $this->addFieldsIf('ar_user_text', $fld_user);
     $this->addFieldsIf('ar_user', $fld_userid);
     $this->addFieldsIf('ar_comment', $fld_comment || $fld_parsedcomment);
     $this->addFieldsIf('ar_minor_edit', $fld_minor);
     $this->addFieldsIf('ar_len', $fld_len);
     $this->addFieldsIf('ar_sha1', $fld_sha1);
     if ($fld_tags) {
         $this->addTables('tag_summary');
         $this->addJoinConds(array('tag_summary' => array('LEFT JOIN', array('ar_rev_id=ts_rev_id'))));
         $this->addFields('ts_tags');
     }
     if (!is_null($params['tag'])) {
         $this->addTables('change_tag');
         $this->addJoinConds(array('change_tag' => array('INNER JOIN', array('ar_rev_id=ct_rev_id'))));
         $this->addWhereFld('ct_tag', $params['tag']);
     }
     if ($fld_content) {
         // 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, plus ar_text_id
         // to be able to tell the difference.
         $this->addTables('text');
         $this->addJoinConds(array('text' => array('LEFT JOIN', array('ar_text_id=old_id'))));
         $this->addFields(array('ar_text', 'ar_flags', 'ar_text_id', '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');
         }
     }
     // Check limits
     $userMax = $fld_content ? ApiBase::LIMIT_SML1 : ApiBase::LIMIT_BIG1;
     $botMax = $fld_content ? ApiBase::LIMIT_SML2 : ApiBase::LIMIT_BIG2;
     $limit = $params['limit'];
     if ($limit == 'max') {
         $limit = $this->getMain()->canApiHighLimits() ? $botMax : $userMax;
         $this->getResult()->addParsedLimit($this->getModuleName(), $limit);
     }
     $this->validateLimit('limit', $limit, 1, $userMax, $botMax);
     if ($fld_token) {
         // Undelete tokens are identical for all pages, so we cache one here
         $token = $user->getEditToken('', $this->getMain()->getRequest());
     }
     $dir = $params['dir'];
     // We need a custom WHERE clause that matches all titles.
     if ($mode == 'revs') {
         $lb = new LinkBatch($titles);
         $where = $lb->constructSet('ar', $db);
         $this->addWhere($where);
     } elseif ($mode == 'all') {
         $this->addWhereFld('ar_namespace', $params['namespace']);
         $from = $params['from'] === null ? null : $this->titlePartToKey($params['from'], $params['namespace']);
         $to = $params['to'] === null ? null : $this->titlePartToKey($params['to'], $params['namespace']);
         $this->addWhereRange('ar_title', $dir, $from, $to);
         if (isset($params['prefix'])) {
             $this->addWhere('ar_title' . $db->buildLike($this->titlePartToKey($params['prefix'], $params['namespace']), $db->anyString()));
         }
     }
     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 ($mode == 'all' || $mode == 'revs') {
             $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', $limit + 1);
     $this->addOption('USE INDEX', array('archive' => $mode == 'user' ? 'usertext_timestamp' : 'name_title_timestamp'));
     if ($mode == 'all') {
         if ($params['unique']) {
             // @todo Does this work on non-MySQL?
             $this->addOption('GROUP BY', 'ar_title');
         } else {
             $sort = $dir == 'newer' ? '' : ' DESC';
             $this->addOption('ORDER BY', array('ar_title' . $sort, 'ar_timestamp' . $sort, 'ar_id' . $sort));
         }
     } else {
         if ($mode == 'revs') {
             // Sort by ns and title in the same order as timestamp for efficiency
             $this->addWhereRange('ar_namespace', $dir, null, null);
             $this->addWhereRange('ar_title', $dir, null, null);
         }
         $this->addTimestampWhereRange('ar_timestamp', $dir, $params['start'], $params['end']);
         // Include in ORDER BY for uniqueness
         $this->addWhereRange('ar_id', $dir, null, null);
     }
     $res = $this->select(__METHOD__);
     $pageMap = array();
     // Maps ns&title to (fake) pageid
     $count = 0;
     $newPageID = 0;
     foreach ($res as $row) {
         if (++$count > $limit) {
             // We've had enough
             if ($mode == 'all' || $mode == 'revs') {
                 $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;
         }
         $rev = array();
         $anyHidden = false;
         $rev['timestamp'] = wfTimestamp(TS_ISO_8601, $row->ar_timestamp);
         if ($fld_revid) {
             $rev['revid'] = intval($row->ar_rev_id);
         }
         if ($fld_parentid && !is_null($row->ar_parent_id)) {
             $rev['parentid'] = intval($row->ar_parent_id);
         }
         if ($fld_user || $fld_userid) {
             if ($row->ar_deleted & Revision::DELETED_USER) {
                 $rev['userhidden'] = true;
                 $anyHidden = true;
             }
             if (Revision::userCanBitfield($row->ar_deleted, Revision::DELETED_USER, $user)) {
                 if ($fld_user) {
                     $rev['user'] = $row->ar_user_text;
                 }
                 if ($fld_userid) {
                     $rev['userid'] = $row->ar_user;
                 }
             }
         }
         if ($fld_comment || $fld_parsedcomment) {
             if ($row->ar_deleted & Revision::DELETED_COMMENT) {
                 $rev['commenthidden'] = true;
                 $anyHidden = true;
             }
             if (Revision::userCanBitfield($row->ar_deleted, Revision::DELETED_COMMENT, $user)) {
                 if ($fld_comment) {
                     $rev['comment'] = $row->ar_comment;
                 }
                 if ($fld_parsedcomment) {
                     $title = Title::makeTitle($row->ar_namespace, $row->ar_title);
                     $rev['parsedcomment'] = Linker::formatComment($row->ar_comment, $title);
                 }
             }
         }
         if ($fld_minor) {
             $rev['minor'] = $row->ar_minor_edit == 1;
         }
         if ($fld_len) {
             $rev['len'] = $row->ar_len;
         }
         if ($fld_sha1) {
             if ($row->ar_deleted & Revision::DELETED_TEXT) {
                 $rev['sha1hidden'] = true;
                 $anyHidden = true;
             }
             if (Revision::userCanBitfield($row->ar_deleted, Revision::DELETED_TEXT, $user)) {
                 if ($row->ar_sha1 != '') {
                     $rev['sha1'] = wfBaseConvert($row->ar_sha1, 36, 16, 40);
                 } else {
                     $rev['sha1'] = '';
                 }
             }
         }
         if ($fld_content) {
             if ($row->ar_deleted & Revision::DELETED_TEXT) {
                 $rev['texthidden'] = true;
                 $anyHidden = true;
             }
             if (Revision::userCanBitfield($row->ar_deleted, Revision::DELETED_TEXT, $user)) {
                 if (isset($row->ar_text) && !$row->ar_text_id) {
                     // Pre-1.5 ar_text row (if condition from Revision::newFromArchiveRow)
                     ApiResult::setContentValue($rev, 'text', Revision::getRevisionText($row, 'ar_'));
                 } else {
                     ApiResult::setContentValue($rev, 'text', Revision::getRevisionText($row));
                 }
             }
         }
         if ($fld_tags) {
             if ($row->ts_tags) {
                 $tags = explode(',', $row->ts_tags);
                 ApiResult::setIndexedTagName($tags, 'tag');
                 $rev['tags'] = $tags;
             } else {
                 $rev['tags'] = array();
             }
         }
         if ($anyHidden && $row->ar_deleted & Revision::DELETED_RESTRICTED) {
             $rev['suppressed'] = true;
         }
         if (!isset($pageMap[$row->ar_namespace][$row->ar_title])) {
             $pageID = $newPageID++;
             $pageMap[$row->ar_namespace][$row->ar_title] = $pageID;
             $a['revisions'] = array($rev);
             ApiResult::setIndexedTagName($a['revisions'], 'rev');
             $title = Title::makeTitle($row->ar_namespace, $row->ar_title);
             ApiQueryBase::addTitleInfo($a, $title);
             if ($fld_token) {
                 $a['token'] = $token;
             }
             $fit = $result->addValue(array('query', $this->getModuleName()), $pageID, $a);
         } else {
             $pageID = $pageMap[$row->ar_namespace][$row->ar_title];
             $fit = $result->addValue(array('query', $this->getModuleName(), $pageID, 'revisions'), null, $rev);
         }
         if (!$fit) {
             if ($mode == 'all' || $mode == 'revs') {
                 $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;
         }
     }
     $result->addIndexedTagName(array('query', $this->getModuleName()), 'page');
 }
Example #15
0
 private function formatCss($css)
 {
     $result = array();
     foreach ($css as $file => $link) {
         $entry = array();
         $entry['file'] = $file;
         ApiResult::setContentValue($entry, 'link', $link);
         $result[] = $entry;
     }
     return $result;
 }
Example #16
0
 public function appendSkins($property)
 {
     $data = array();
     $allowed = Skin::getAllowedSkins();
     $default = Skin::normalizeKey('default');
     foreach (Skin::getSkinNames() as $name => $displayName) {
         $msg = $this->msg("skinname-{$name}");
         $code = $this->getParameter('inlanguagecode');
         if ($code && Language::isValidCode($code)) {
             $msg->inLanguage($code);
         } else {
             $msg->inContentLanguage();
         }
         if ($msg->exists()) {
             $displayName = $msg->text();
         }
         $skin = array('code' => $name);
         ApiResult::setContentValue($skin, 'name', $displayName);
         if (!isset($allowed[$name])) {
             $skin['unusable'] = true;
         }
         if ($name === $default) {
             $skin['default'] = true;
         }
         $data[] = $skin;
     }
     ApiResult::setIndexedTagName($data, 'skin');
     return $this->getResult()->addValue('query', $property, $data);
 }
 /**
  * @param ApiPageSet $resultPageSet
  */
 private function run($resultPageSet = null)
 {
     $db = $this->getDB();
     $params = $this->extractRequestParams();
     $this->addTables('category');
     $this->addFields('cat_title');
     if (!is_null($params['continue'])) {
         $cont = explode('|', $params['continue']);
         $this->dieContinueUsageIf(count($cont) != 1);
         $op = $params['dir'] == 'descending' ? '<' : '>';
         $cont_from = $db->addQuotes($cont[0]);
         $this->addWhere("cat_title {$op}= {$cont_from}");
     }
     $dir = $params['dir'] == 'descending' ? 'older' : 'newer';
     $from = $params['from'] === null ? null : $this->titlePartToKey($params['from'], NS_CATEGORY);
     $to = $params['to'] === null ? null : $this->titlePartToKey($params['to'], NS_CATEGORY);
     $this->addWhereRange('cat_title', $dir, $from, $to);
     $min = $params['min'];
     $max = $params['max'];
     if ($dir == 'newer') {
         $this->addWhereRange('cat_pages', 'newer', $min, $max);
     } else {
         $this->addWhereRange('cat_pages', 'older', $max, $min);
     }
     if (isset($params['prefix'])) {
         $this->addWhere('cat_title' . $db->buildLike($this->titlePartToKey($params['prefix'], NS_CATEGORY), $db->anyString()));
     }
     $this->addOption('LIMIT', $params['limit'] + 1);
     $sort = $params['dir'] == 'descending' ? ' DESC' : '';
     $this->addOption('ORDER BY', 'cat_title' . $sort);
     $prop = array_flip($params['prop']);
     $this->addFieldsIf(array('cat_pages', 'cat_subcats', 'cat_files'), isset($prop['size']));
     if (isset($prop['hidden'])) {
         $this->addTables(array('page', 'page_props'));
         $this->addJoinConds(array('page' => array('LEFT JOIN', array('page_namespace' => NS_CATEGORY, 'page_title=cat_title')), 'page_props' => array('LEFT JOIN', array('pp_page=page_id', 'pp_propname' => 'hiddencat'))));
         $this->addFields(array('cat_hidden' => 'pp_propname'));
     }
     $res = $this->select(__METHOD__);
     $pages = array();
     $result = $this->getResult();
     $count = 0;
     foreach ($res as $row) {
         if (++$count > $params['limit']) {
             // We've reached the one extra which shows that there are
             // additional cats to be had. Stop here...
             $this->setContinueEnumParameter('continue', $row->cat_title);
             break;
         }
         // Normalize titles
         $titleObj = Title::makeTitle(NS_CATEGORY, $row->cat_title);
         if (!is_null($resultPageSet)) {
             $pages[] = $titleObj;
         } else {
             $item = array();
             ApiResult::setContentValue($item, 'category', $titleObj->getText());
             if (isset($prop['size'])) {
                 $item['size'] = intval($row->cat_pages);
                 $item['pages'] = $row->cat_pages - $row->cat_subcats - $row->cat_files;
                 $item['files'] = intval($row->cat_files);
                 $item['subcats'] = intval($row->cat_subcats);
             }
             if (isset($prop['hidden'])) {
                 $item['hidden'] = (bool) $row->cat_hidden;
             }
             $fit = $result->addValue(array('query', $this->getModuleName()), null, $item);
             if (!$fit) {
                 $this->setContinueEnumParameter('continue', $row->cat_title);
                 break;
             }
         }
     }
     if (is_null($resultPageSet)) {
         $result->addIndexedTagName(array('query', $this->getModuleName()), 'c');
     } else {
         $resultPageSet->populateFromTitles($pages);
     }
 }
 public function execute()
 {
     $params = $this->extractRequestParams();
     if (is_null($params['lang'])) {
         $langObj = $this->getLanguage();
     } elseif (!Language::isValidCode($params['lang'])) {
         $this->dieUsage('Invalid language code for parameter lang', 'invalidlang');
     } else {
         $langObj = Language::factory($params['lang']);
     }
     if ($params['enableparser']) {
         if (!is_null($params['title'])) {
             $title = Title::newFromText($params['title']);
             if (!$title || $title->isExternal()) {
                 $this->dieUsageMsg(['invalidtitle', $params['title']]);
             }
         } else {
             $title = Title::newFromText('API');
         }
     }
     $prop = array_flip((array) $params['prop']);
     // Determine which messages should we print
     if (in_array('*', $params['messages'])) {
         $message_names = Language::getMessageKeysFor($langObj->getCode());
         if ($params['includelocal']) {
             $message_names = array_unique(array_merge($message_names, MessageCache::singleton()->getAllMessageKeys($this->getConfig()->get('LanguageCode'))));
         }
         sort($message_names);
         $messages_target = $message_names;
     } else {
         $messages_target = $params['messages'];
     }
     // Filter messages that have the specified prefix
     // Because we sorted the message array earlier, they will appear in a clump:
     if (isset($params['prefix'])) {
         $skip = false;
         $messages_filtered = [];
         foreach ($messages_target as $message) {
             // === 0: must be at beginning of string (position 0)
             if (strpos($message, $params['prefix']) === 0) {
                 if (!$skip) {
                     $skip = true;
                 }
                 $messages_filtered[] = $message;
             } elseif ($skip) {
                 break;
             }
         }
         $messages_target = $messages_filtered;
     }
     // Filter messages that contain specified string
     if (isset($params['filter'])) {
         $messages_filtered = [];
         foreach ($messages_target as $message) {
             // !== is used because filter can be at the beginning of the string
             if (strpos($message, $params['filter']) !== false) {
                 $messages_filtered[] = $message;
             }
         }
         $messages_target = $messages_filtered;
     }
     // Whether we have any sort of message customisation filtering
     $customiseFilterEnabled = $params['customised'] !== 'all';
     if ($customiseFilterEnabled) {
         global $wgContLang;
         $customisedMessages = AllMessagesTablePager::getCustomisedStatuses(array_map([$langObj, 'ucfirst'], $messages_target), $langObj->getCode(), !$langObj->equals($wgContLang));
         $customised = $params['customised'] === 'modified';
     }
     // Get all requested messages and print the result
     $skip = !is_null($params['from']);
     $useto = !is_null($params['to']);
     $result = $this->getResult();
     foreach ($messages_target as $message) {
         // Skip all messages up to $params['from']
         if ($skip && $message === $params['from']) {
             $skip = false;
         }
         if ($useto && $message > $params['to']) {
             break;
         }
         if (!$skip) {
             $a = ['name' => $message, 'normalizedname' => MessageCache::normalizeKey($message)];
             $args = [];
             if (isset($params['args']) && count($params['args']) != 0) {
                 $args = $params['args'];
             }
             if ($customiseFilterEnabled) {
                 $messageIsCustomised = isset($customisedMessages['pages'][$langObj->ucfirst($message)]);
                 if ($customised === $messageIsCustomised) {
                     if ($customised) {
                         $a['customised'] = true;
                     }
                 } else {
                     continue;
                 }
             }
             $msg = wfMessage($message, $args)->inLanguage($langObj);
             if (!$msg->exists()) {
                 $a['missing'] = true;
             } else {
                 // Check if the parser is enabled:
                 if ($params['enableparser']) {
                     $msgString = $msg->title($title)->text();
                 } else {
                     $msgString = $msg->plain();
                 }
                 if (!$params['nocontent']) {
                     ApiResult::setContentValue($a, 'content', $msgString);
                 }
                 if (isset($prop['default'])) {
                     $default = wfMessage($message)->inLanguage($langObj)->useDatabase(false);
                     if (!$default->exists()) {
                         $a['defaultmissing'] = true;
                     } elseif ($default->plain() != $msgString) {
                         $a['default'] = $default->plain();
                     }
                 }
             }
             $fit = $result->addValue(['query', $this->getModuleName()], null, $a);
             if (!$fit) {
                 $this->setContinueEnumParameter('from', $message);
                 break;
             }
         }
     }
     $result->addIndexedTagName(['query', $this->getModuleName()], 'message');
 }
Example #19
0
 /**
  * Replace the result data with the information about an exception.
  * Returns the error code
  * @param Exception $e
  * @return string
  */
 protected function substituteResultWithError($e)
 {
     $result = $this->getResult();
     $config = $this->getConfig();
     if ($e instanceof UsageException) {
         // User entered incorrect parameters - generate error response
         $errMessage = $e->getMessageArray();
         $link = wfExpandUrl(wfScript('api'));
         ApiResult::setContentValue($errMessage, 'docref', "See {$link} for API usage");
     } else {
         // Something is seriously wrong
         if ($e instanceof DBQueryError && !$config->get('ShowSQLErrors')) {
             $info = 'Database query error';
         } else {
             $info = "Exception Caught: {$e->getMessage()}";
         }
         $errMessage = array('code' => 'internal_api_error_' . get_class($e), 'info' => '[' . MWExceptionHandler::getLogId($e) . '] ' . $info);
         if ($config->get('ShowExceptionDetails')) {
             ApiResult::setContentValue($errMessage, 'trace', MWExceptionHandler::getRedactedTraceAsString($e));
         }
     }
     // Remember all the warnings to re-add them later
     $warnings = $result->getResultData(array('warnings'));
     $result->reset();
     // Re-add the id
     $requestid = $this->getParameter('requestid');
     if (!is_null($requestid)) {
         $result->addValue(null, 'requestid', $requestid, ApiResult::NO_SIZE_CHECK);
     }
     if ($config->get('ShowHostnames')) {
         // servedby is especially useful when debugging errors
         $result->addValue(null, 'servedby', wfHostName(), ApiResult::NO_SIZE_CHECK);
     }
     if ($warnings !== null) {
         $result->addValue(null, 'warnings', $warnings, ApiResult::NO_SIZE_CHECK);
     }
     $result->addValue(null, 'error', $errMessage, ApiResult::NO_SIZE_CHECK);
     return $errMessage['code'];
 }
Example #20
0
 /**
  * @covers ApiResult
  */
 public function testInstanceDataMethods()
 {
     $result = new ApiResult(8388608);
     $result->addValue(null, 'setValue', '1');
     $result->addValue(null, null, 'unnamed 1');
     $result->addValue(null, null, 'unnamed 2');
     $result->addValue(null, 'deleteValue', '2');
     $result->removeValue(null, 'deleteValue');
     $result->addValue(['a', 'b'], 'deleteValue', '3');
     $result->removeValue(['a', 'b', 'deleteValue'], null, '3');
     $result->addContentValue(null, 'setContentValue', '3');
     $this->assertSame(['setValue' => '1', 'unnamed 1', 'unnamed 2', 'a' => ['b' => []], 'setContentValue' => '3', ApiResult::META_TYPE => 'assoc', ApiResult::META_CONTENT => 'setContentValue'], $result->getResultData());
     $this->assertSame(20, $result->getSize());
     try {
         $result->addValue(null, 'setValue', '99');
         $this->fail('Expected exception not thrown');
     } catch (RuntimeException $ex) {
         $this->assertSame('Attempting to add element setValue=99, existing value is 1', $ex->getMessage(), 'Expected exception');
     }
     try {
         $result->addContentValue(null, 'setContentValue2', '99');
         $this->fail('Expected exception not thrown');
     } catch (RuntimeException $ex) {
         $this->assertSame('Attempting to set content element as setContentValue2 when setContentValue ' . 'is already set as the content element', $ex->getMessage(), 'Expected exception');
     }
     $result->addValue(null, 'setValue', '99', ApiResult::OVERRIDE);
     $this->assertSame('99', $result->getResultData(['setValue']));
     $result->addContentValue(null, 'setContentValue2', '99', ApiResult::OVERRIDE);
     $this->assertSame('setContentValue2', $result->getResultData([ApiResult::META_CONTENT]));
     $result->reset();
     $this->assertSame([ApiResult::META_TYPE => 'assoc'], $result->getResultData());
     $this->assertSame(0, $result->getSize());
     $result->addValue(null, 'foo', 1);
     $result->addValue(null, 'bar', 1);
     $result->addValue(null, 'top', '2', ApiResult::ADD_ON_TOP);
     $result->addValue(null, null, '2', ApiResult::ADD_ON_TOP);
     $result->addValue(null, 'bottom', '2');
     $result->addValue(null, 'foo', '2', ApiResult::OVERRIDE);
     $result->addValue(null, 'bar', '2', ApiResult::OVERRIDE | ApiResult::ADD_ON_TOP);
     $this->assertSame([0, 'top', 'foo', 'bar', 'bottom', ApiResult::META_TYPE], array_keys($result->getResultData()));
     $result->reset();
     $result->addValue(null, 'foo', ['bar' => 1]);
     $result->addValue(['foo', 'top'], 'x', 2, ApiResult::ADD_ON_TOP);
     $result->addValue(['foo', 'bottom'], 'x', 2);
     $this->assertSame(['top', 'bar', 'bottom'], array_keys($result->getResultData(['foo'])));
     $result->reset();
     $result->addValue(null, 'sub', ['foo' => 1]);
     $result->addValue(null, 'sub', ['bar' => 1]);
     $this->assertSame(['sub' => ['foo' => 1, 'bar' => 1], ApiResult::META_TYPE => 'assoc'], $result->getResultData());
     try {
         $result->addValue(null, 'sub', ['foo' => 2, 'baz' => 2]);
         $this->fail('Expected exception not thrown');
     } catch (RuntimeException $ex) {
         $this->assertSame('Conflicting keys (foo) when attempting to merge element sub', $ex->getMessage(), 'Expected exception');
     }
     $result->reset();
     $title = Title::newFromText("MediaWiki:Foobar");
     $obj = new stdClass();
     $obj->foo = 1;
     $obj->bar = 2;
     $result->addValue(null, 'title', $title);
     $result->addValue(null, 'obj', $obj);
     $this->assertSame(['title' => (string) $title, 'obj' => ['foo' => 1, 'bar' => 2, ApiResult::META_TYPE => 'assoc'], ApiResult::META_TYPE => 'assoc'], $result->getResultData());
     $fh = tmpfile();
     try {
         $result->addValue(null, 'file', $fh);
         $this->fail('Expected exception not thrown');
     } catch (InvalidArgumentException $ex) {
         $this->assertSame('Cannot add resource(stream) to ApiResult', $ex->getMessage(), 'Expected exception');
     }
     try {
         $result->addValue(null, null, $fh);
         $this->fail('Expected exception not thrown');
     } catch (InvalidArgumentException $ex) {
         $this->assertSame('Cannot add resource(stream) to ApiResult', $ex->getMessage(), 'Expected exception');
     }
     try {
         $obj->file = $fh;
         $result->addValue(null, 'sub', $obj);
         $this->fail('Expected exception not thrown');
     } catch (InvalidArgumentException $ex) {
         $this->assertSame('Cannot add resource(stream) to ApiResult', $ex->getMessage(), 'Expected exception');
     }
     try {
         $obj->file = $fh;
         $result->addValue(null, null, $obj);
         $this->fail('Expected exception not thrown');
     } catch (InvalidArgumentException $ex) {
         $this->assertSame('Cannot add resource(stream) to ApiResult', $ex->getMessage(), 'Expected exception');
     }
     fclose($fh);
     try {
         $result->addValue(null, 'inf', INF);
         $this->fail('Expected exception not thrown');
     } catch (InvalidArgumentException $ex) {
         $this->assertSame('Cannot add non-finite floats to ApiResult', $ex->getMessage(), 'Expected exception');
     }
     try {
         $result->addValue(null, null, INF);
         $this->fail('Expected exception not thrown');
     } catch (InvalidArgumentException $ex) {
         $this->assertSame('Cannot add non-finite floats to ApiResult', $ex->getMessage(), 'Expected exception');
     }
     try {
         $result->addValue(null, 'nan', NAN);
         $this->fail('Expected exception not thrown');
     } catch (InvalidArgumentException $ex) {
         $this->assertSame('Cannot add non-finite floats to ApiResult', $ex->getMessage(), 'Expected exception');
     }
     try {
         $result->addValue(null, null, NAN);
         $this->fail('Expected exception not thrown');
     } catch (InvalidArgumentException $ex) {
         $this->assertSame('Cannot add non-finite floats to ApiResult', $ex->getMessage(), 'Expected exception');
     }
     $result->addValue(null, null, NAN, ApiResult::NO_VALIDATE);
     try {
         $result->addValue(null, null, NAN, ApiResult::NO_SIZE_CHECK);
         $this->fail('Expected exception not thrown');
     } catch (InvalidArgumentException $ex) {
         $this->assertSame('Cannot add non-finite floats to ApiResult', $ex->getMessage(), 'Expected exception');
     }
     $result->reset();
     $result->addParsedLimit('foo', 12);
     $this->assertSame(['limits' => ['foo' => 12], ApiResult::META_TYPE => 'assoc'], $result->getResultData());
     $result->addParsedLimit('foo', 13);
     $this->assertSame(['limits' => ['foo' => 13], ApiResult::META_TYPE => 'assoc'], $result->getResultData());
     $this->assertSame(null, $result->getResultData(['foo', 'bar', 'baz']));
     $this->assertSame(13, $result->getResultData(['limits', 'foo']));
     try {
         $result->getResultData(['limits', 'foo', 'bar']);
         $this->fail('Expected exception not thrown');
     } catch (InvalidArgumentException $ex) {
         $this->assertSame('Path limits.foo is not an array', $ex->getMessage(), 'Expected exception');
     }
     // Add two values and some metadata, but ensure metadata is not counted
     $result = new ApiResult(100);
     $obj = ['attr' => '12345'];
     ApiResult::setContentValue($obj, 'content', '1234567890');
     $this->assertTrue($result->addValue(null, 'foo', $obj));
     $this->assertSame(15, $result->getSize());
     $result = new ApiResult(10);
     $formatter = new ApiErrorFormatter($result, Language::factory('en'), 'none', false);
     $result->setErrorFormatter($formatter);
     $this->assertFalse($result->addValue(null, 'foo', '12345678901'));
     $this->assertTrue($result->addValue(null, 'foo', '12345678901', ApiResult::NO_SIZE_CHECK));
     $this->assertSame(0, $result->getSize());
     $result->reset();
     $this->assertTrue($result->addValue(null, 'foo', '1234567890'));
     $this->assertFalse($result->addValue(null, 'foo', '1'));
     $result->removeValue(null, 'foo');
     $this->assertTrue($result->addValue(null, 'foo', '1'));
     $result = new ApiResult(10);
     $obj = new ApiResultTestSerializableObject('ok');
     $obj->foobar = 'foobaz';
     $this->assertTrue($result->addValue(null, 'foo', $obj));
     $this->assertSame(2, $result->getSize());
     $result = new ApiResult(8388608);
     $result2 = new ApiResult(8388608);
     $result2->addValue(null, 'foo', 'bar');
     $result->addValue(null, 'baz', $result2);
     $this->assertSame(['baz' => ['foo' => 'bar', ApiResult::META_TYPE => 'assoc'], ApiResult::META_TYPE => 'assoc'], $result->getResultData());
     $result = new ApiResult(8388608);
     $result->addValue(null, 'foo', "foo�bar");
     $result->addValue(null, 'bar', "á");
     $result->addValue(null, 'baz', 74);
     $result->addValue(null, null, "foo�bar");
     $result->addValue(null, null, "á");
     $this->assertSame(['foo' => "foo�bar", 'bar' => "á", 'baz' => 74, 0 => "foo�bar", 1 => "á", ApiResult::META_TYPE => 'assoc'], $result->getResultData());
     $result = new ApiResult(8388608);
     $obj = new stdClass();
     $obj->{'1'} = 'one';
     $arr = [];
     $result->addValue($arr, 'foo', $obj);
     $this->assertSame(['foo' => [1 => 'one', ApiResult::META_TYPE => 'assoc'], ApiResult::META_TYPE => 'assoc'], $result->getResultData());
 }