* Marcin Cieślak
* Marcus Buck
* Marooned
+* Matthew Britton
* Max Semenik
* Michael De La Rue
* Michael Walsh
* (bug 19907) $wgCrossSiteAJAXdomains added to allow specified (or all)
external domains to access api.php via AJAX, if the browser supports the
Access-Control-Allow-Origin HTTP header
+* (bug 19004) Added support for tags to the API.
=== Languages updated in 1.16 ===
'ApiQueryRevisions' => 'includes/api/ApiQueryRevisions.php',
'ApiQuerySearch' => 'includes/api/ApiQuerySearch.php',
'ApiQuerySiteinfo' => 'includes/api/ApiQuerySiteinfo.php',
+ 'ApiQueryTags' => 'includes/api/ApiQueryTags.php',
'ApiQueryUserInfo' => 'includes/api/ApiQueryUserInfo.php',
'ApiQueryUsers' => 'includes/api/ApiQueryUsers.php',
'ApiQueryWatchlist' => 'includes/api/ApiQueryWatchlist.php',
$wgMemc->set( $key, $emptyTags, 300 );
return $emptyTags;
}
+
+ /** Returns associative array of tag names and hitcounts */
+ static function getHitCounts() {
+
+ global $wgMemc;
+ $key = wfMemcKey( 'hitcounts' );
+
+ if ($hitcounts = $wgMemc->get( $key ))
+ return $hitcounts;
+
+ $dbr = wfGetDB( DB_SLAVE );
+ $hitcounts = array();
+
+ // Fetch defined tags
+ $res = $dbr->select( 'valid_tag', 'vt_tag', array(), __METHOD__ );
+ while( $row = $res->fetchObject() ) {
+ $hitcounts[$row->vt_tag] = 0;
+ }
+
+ // Fetch hit counts
+ $res = $dbr->select( 'change_tag', array('ct_tag', 'count(*) AS hitcount'), array(), __METHOD__, array('GROUP BY' => 'ct_tag', 'ORDER BY' => 'hitcount DESC') );
+
+ while( $row = $res->fetchObject() ) {
+ $hitcounts[$row->ct_tag] = $row->hitcount;
+ }
+
+ // Short-term caching
+ $wgMemc->set( $key, $hitcounts, 300 );
+ return $hitcounts;
+ }
+
}
'logevents' => 'ApiQueryLogEvents',
'recentchanges' => 'ApiQueryRecentChanges',
'search' => 'ApiQuerySearch',
+ 'tags' => 'ApiQueryTags',
'usercontribs' => 'ApiQueryContributions',
'watchlist' => 'ApiQueryWatchlist',
'watchlistraw' => 'ApiQueryWatchlistRaw',
$this->fld_timestamp = in_array('timestamp', $prop);
$this->fld_comment = in_array('comment', $prop);
$this->fld_details = in_array('details', $prop);
+ $this->fld_tags = in_array('tags', $prop);
list($tbl_logging, $tbl_page, $tbl_user) = $db->tableNamesN('logging', 'page', 'user');
$this->addFieldsIf('log_comment', $this->fld_comment);
$this->addFieldsIf('log_params', $this->fld_details);
+ if($this->fld_tags || !is_null($params['tag'])) {
+ $this->addTables('tag_summary');
+ $this->addJoinConds(array('tag_summary' => array('LEFT JOIN', 'log_id=ts_log_id')));
+ $this->addFields('ts_tags');
+ }
+
+ if( !is_null($params['tag']) ) {
+ $this->addWhereFld('ts_tags', $params['tag']);
+ }
+
if( !is_null($params['type']) ) {
$this->addWhereFld('log_type', $params['type']);
$index = 'type_time';
}
}
+ if ($this->fld_tags && isset($row->ts_tags)) {
+ $vals['tags'] = $row->ts_tags;
+ }
+
return $vals;
}
'timestamp',
'comment',
'details',
+ 'tags'
)
),
'type' => array (
),
'user' => null,
'title' => null,
+ 'tag' => null,
'limit' => array (
ApiBase :: PARAM_DFLT => 10,
ApiBase :: PARAM_TYPE => 'limit',
'dir' => 'In which direction to enumerate.',
'user' => 'Filter entries to those made by the given user.',
'title' => 'Filter entries to those related to a page.',
+ 'tag' => 'Only list entries with this tag',
'limit' => 'How many total event entries to return.'
);
}
private $fld_comment = false, $fld_user = false, $fld_flags = false,
$fld_timestamp = false, $fld_title = false, $fld_ids = false,
- $fld_sizes = false;
+ $fld_sizes = false, $fld_tags = false;
/**
* Get an array mapping token names to their handler functions.
* The prototype for a token function is func($pageid, $title, $rc)
$this->fld_redirect = isset($prop['redirect']);
$this->fld_patrolled = isset($prop['patrolled']);
$this->fld_loginfo = isset($prop['loginfo']);
+ $this->fld_tags = isset($prop['tags']);
global $wgUser;
if($this->fld_patrolled && !$wgUser->useRCPatrol() && !$wgUser->useNPPatrol())
$this->addFields('page_is_redirect');
}
}
+
+ if($this->fld_tags || !is_null($params['tag'])) {
+ $this->addTables('tag_summary');
+ $this->addJoinConds(array('tag_summary' => array('LEFT JOIN', array('rc_id=ts_rc_id'))));
+ $this->addFields('ts_tags');
+ }
+
+ if(!is_null($params['tag'])) {
+ $this->addWhereFld('ts_tags' , $params['tag']);
+ }
+
$this->token = $params['token'];
$this->addOption('LIMIT', $params['limit'] +1);
$this->addOption('USE INDEX', array('recentchanges' => $index));
$row->rc_log_type, $row->rc_timestamp);
}
+ if ($this->fld_tags && isset($row->ts_tags)) {
+ $vals['tags'] = $row->ts_tags;
+ }
+
if(!is_null($this->token))
{
$tokenFunctions = $this->getTokenFunctions();
'redirect',
'patrolled',
'loginfo',
+ 'tags',
)
),
'token' => array(
'new',
'log'
)
- )
+ ),
+ 'tag' => null,
);
}
'For example, to see only minor edits done by logged-in users, set show=minor|!anon'
),
'type' => 'Which types of changes to show.',
+ 'tag' => 'Only list changes with this tag',
'limit' => 'How many total changes to return.'
);
}
}
private $fld_ids = false, $fld_flags = false, $fld_timestamp = false, $fld_size = false,
- $fld_comment = false, $fld_user = false, $fld_content = false;
+ $fld_comment = false, $fld_user = false, $fld_content = false, $fld_tags = false;
protected function getTokenFunctions() {
// tokenname => function
}
$db = $this->getDB();
- $this->addTables('revision');
+ $this->addTables(array('page', 'revision'));
$this->addFields(Revision::selectFields());
- $this->addTables('page');
$this->addWhere('page_id = rev_page');
$prop = array_flip($params['prop']);
$this->fld_timestamp = isset ($prop['timestamp']);
$this->fld_comment = isset ($prop['comment']);
$this->fld_size = isset ($prop['size']);
+ $this->fld_tags = isset ($prop['tags']);
$this->fld_user = isset ($prop['user']);
$this->token = $params['token'];
$this->diffto = $params['diffto'];
$this->addFields( Revision::selectPageFields() );
}
+ if ($this->fld_tags || !is_null($params['tag'])) {
+ $this->addTables('tag_summary');
+ $this->addJoinConds(array('tag_summary' => array('LEFT JOIN', array('rev_id=ts_rev_id'))));
+ $this->addFields('ts_tags');
+ }
+
+ if( !is_null($params['tag']) ) {
+ $this->addWhereFld('ts_tags', $params['tag']);
+ }
+
if (isset ($prop['content'])) {
// For each page we will request, the user must have read rights for that page
$this->setContinueEnumParameter('startid', intval($row->rev_id));
break;
}
- $revision = new Revision( $row );
+
//
- $fit = $this->addPageSubItem($revision->getPage(), $this->extractRowInfo($revision), 'rev');
+ $fit = $this->addPageSubItem($row->rev_page, $this->extractRowInfo($row), 'rev');
if(!$fit)
{
if($enumRevMode)
$db->freeResult($res);
}
- private function extractRowInfo( $revision ) {
+ private function extractRowInfo( $row ) {
+ $revision = new Revision( $row );
$title = $revision->getTitle();
$vals = array ();
}
}
+ if ($this->fld_tags && $row->ts_tags)
+ $vals['tags'] = $row->ts_tags;
+
if(!is_null($this->token))
{
$tokenFunctions = $this->getTokenFunctions();
'size',
'comment',
'content',
+ 'tags'
)
),
'limit' => array (
'excludeuser' => array(
ApiBase :: PARAM_TYPE => 'user'
),
+ 'tag' => null,
'expandtemplates' => false,
'generatexml' => false,
'section' => null,
'dir' => 'direction of enumeration - towards "newer" or "older" revisions (enum)',
'user' => 'only include revisions made by user',
'excludeuser' => 'exclude revisions made by user',
+ 'tag' => 'only list revisions with this tag',
'expandtemplates' => 'expand templates in revision content',
'generatexml' => 'generate XML parse tree for revision content',
'section' => 'only retrieve the content of this section',
--- /dev/null
+<?php
+
+/*
+ * Created on Jul 9, 2009
+ *
+ * API for MediaWiki 1.8+
+ *
+ * Copyright (C) 2009 Matthew Britton <firstname>.<lastname>@btinternet.com
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+
+if (!defined('MEDIAWIKI')) {
+ // Eclipse helper - will be ignored in production
+ require_once ('ApiQueryBase.php');
+}
+
+/**
+ * Query module to enumerate change tags.
+ *
+ * @ingroup API
+ */
+class ApiQueryTags extends ApiQueryBase {
+
+ private $limit, $result;
+ private $fld_displayname = false, $fld_description = false,
+ $fld_hitcount = false;
+
+ public function __construct($query, $moduleName) {
+ parent :: __construct($query, $moduleName, 'tg');
+ }
+
+ public function execute() {
+ $params = $this->extractRequestParams();
+
+ $prop = array_flip($params['prop']);
+
+ $this->fld_displayname = isset($prop['displayname']);
+ $this->fld_description = isset($prop['description']);
+ $this->fld_hitcount = isset($prop['hitcount']);
+
+ $this->limit = $params['limit'];
+ $this->result = $this->getResult();
+
+ $pageSet = $this->getPageSet();
+ $titles = $pageSet->getTitles();
+ $data = array();
+ $ok = true;
+
+ if($this->fld_hitcount) {
+ foreach( ChangeTags::getHitCounts() as $tag => $count ) {
+ if(!$ok) break;
+ $ok = $this->doTag( $tag, $count );
+ }
+ } else {
+ foreach( ChangeTags::listDefinedTags() as $tag ) {
+ if(!$ok) break;
+ $ok = $this->doTag( $tag, 0 );
+ }
+ }
+
+ $this->result->setIndexedTagName_internal(array('query', $this->getModuleName()), 'tag');
+ }
+
+ private function doTag( $tagName, $hitcount ) {
+ static $count = 0;
+ static $doneTags = array();
+
+ if ( in_array( $tagName, $doneTags ) ) {
+ return true;
+ }
+
+ if(++$count > $this->limit)
+ {
+ $this->setContinueEnumParameter('continue', $tagName);
+ return false;
+ }
+
+ $tag = array();
+ $tag['name'] = $tagName;
+
+ if($this->fld_displayname)
+ $tag['displayname'] = ChangeTags::tagDescription( $tagName );
+
+ if($this->fld_description)
+ {
+ $msg = wfMsg( "tag-$tagName-description" );
+ $msg = wfEmptyMsg( "tag-$tagName-description", $msg ) ? '' : $msg;
+ $tag['description'] = $msg;
+ }
+
+ if($this->fld_hitcount)
+ $tag['hitcount'] = $hitcount;
+
+ $doneTags[] = $tagName;
+
+ $fit = $this->result->addValue(array('query', $this->getModuleName()), null, $tag);
+ if(!$fit)
+ {
+ $this->setContinueEnumParameter('continue', $tagName);
+ return false;
+ }
+
+ return true;
+ }
+
+ public function getAllowedParams() {
+ return array (
+ 'continue' => array(
+ ),
+ 'end' => array(
+ ),
+ 'limit' => array(
+ ApiBase :: PARAM_DFLT => 10,
+ ApiBase :: PARAM_TYPE => 'limit',
+ ApiBase :: PARAM_MIN => 1,
+ ApiBase :: PARAM_MAX => ApiBase :: LIMIT_BIG1,
+ ApiBase :: PARAM_MAX2 => ApiBase :: LIMIT_BIG2
+ ),
+ 'prop' => array(
+ ApiBase :: PARAM_DFLT => 'name',
+ ApiBase :: PARAM_TYPE => array(
+ 'name',
+ 'displayname',
+ 'description',
+ 'hitcount'
+ ),
+ ApiBase :: PARAM_ISMULTI => true
+ )
+ );
+ }
+
+ public function getParamDescription() {
+ return array (
+ 'continue' => 'When more results are available, use this to continue',
+ 'limit' => 'The maximum number of tags to list',
+ 'prop' => 'Which properties to get',
+ );
+ }
+
+ public function getDescription() {
+ return 'List change tags.';
+ }
+
+ protected function getExamples() {
+ return array (
+ 'api.php?action=query&list=tags&tgprop=displayname|description|hitcount'
+ );
+ }
+
+ public function getVersion() {
+ return __CLASS__ . ': $Id: ApiQueryTags.php';
+ }
+}
private $params, $username;
private $fld_ids = false, $fld_title = false, $fld_timestamp = false,
$fld_comment = false, $fld_flags = false,
- $fld_patrolled = false;
+ $fld_patrolled = false, $fld_tags = false;
public function execute() {
$this->fld_flags = isset($prop['flags']);
$this->fld_timestamp = isset($prop['timestamp']);
$this->fld_patrolled = isset($prop['patrolled']);
+ $this->fld_tags = isset($prop['tags']);
// TODO: if the query is going only against the revision table, should this be done?
$this->selectNamedDB('contributions', DB_SLAVE, 'contributions');
private function prepareQuery() {
// We're after the revision table, and the corresponding page
// row for anything we retrieve. We may also need the
- // recentchanges row.
+ // recentchanges row and/or tag summary row.
global $wgUser;
$tables = array('page', 'revision'); // Order may change
$this->addWhere('page_id=rev_page');
$this->addFieldsIf('rev_minor_edit', $this->fld_flags);
$this->addFieldsIf('rev_parent_id', $this->fld_flags);
$this->addFieldsIf('rc_patrolled', $this->fld_patrolled);
+
+ if($this->fld_tags || !is_null($this->params['tag'])) {
+ $this->addTables('tag_summary');
+ $this->addJoinConds(array('tag_summary' => array('LEFT JOIN', array('rev_id=ts_rev_id'))));
+ $this->addFields('ts_tags');
+ }
+
+ if( !is_null($this->params['tag']) ) {
+ $this->addWhereFld('ts_tags', $this->params['tag']);
+ }
}
/**
if ($this->fld_size && !is_null($row->rev_len))
$vals['size'] = intval($row->rev_len);
+ if ($this->fld_tags && $row->ts_tags)
+ $vals['tags'] = $row->ts_tags;
+
return $vals;
}
'size',
'flags',
'patrolled',
+ 'tags',
)
),
'show' => array (
'!patrolled',
)
),
+ 'tag' => null,
);
}
'prop' => 'Include additional pieces of information',
'show' => array('Show only items that meet this criteria, e.g. non minor edits only: show=!minor',
'NOTE: if show=patrolled or show=!patrolled is set, revisions older than $wgRCMaxAge won\'t be shown',),
+ 'tag' => 'Only list contributions with this tag',
);
}
Xml::tags( 'th', null, wfMsgExt( 'tags-description-header', 'parseinline' ) ) .
Xml::tags( 'th', null, wfMsgExt( 'tags-hitcount-header', 'parseinline' ) )
);
- $dbr = wfGetDB( DB_SLAVE );
- $res = $dbr->select( 'change_tag', array( 'ct_tag', 'count(*) as hitcount' ), array(), __METHOD__, array( 'GROUP BY' => 'ct_tag', 'ORDER BY' => 'hitcount DESC' ) );
- while ( $row = $res->fetchObject() ) {
- $html .= $this->doTagRow( $row->ct_tag, $row->hitcount );
- }
-
- foreach( ChangeTags::listDefinedTags() as $tag ) {
- $html .= $this->doTagRow( $tag, 0 );
+ foreach( ChangeTags::getHitCounts() as $tag => $hitcount ) {
+ $html .= $this->doTagRow( $tag, $hitcount );
}
$wgOut->addHTML( Xml::tags( 'table', array( 'class' => 'wikitable mw-tags-table' ), $html ) );