API: (bug 19004) Add support for tags. Patch by Matthew Britton
authorRoan Kattouw <catrope@users.mediawiki.org>
Sun, 1 Nov 2009 10:42:41 +0000 (10:42 +0000)
committerRoan Kattouw <catrope@users.mediawiki.org>
Sun, 1 Nov 2009 10:42:41 +0000 (10:42 +0000)
CREDITS
RELEASE-NOTES
includes/AutoLoader.php
includes/User.php
includes/api/ApiQuery.php
includes/api/ApiQueryLogEvents.php
includes/api/ApiQueryRecentChanges.php
includes/api/ApiQueryRevisions.php
includes/api/ApiQueryTags.php [new file with mode: 0644]
includes/api/ApiQueryUserContributions.php

diff --git a/CREDITS b/CREDITS
index 86a1e3e..61dea69 100644 (file)
--- a/CREDITS
+++ b/CREDITS
@@ -86,6 +86,7 @@ following names for their contribution to the product.
 * Marcin Cieślak
 * Marcus Buck
 * Marooned
+* Matthew Britton
 * Matěj Grabovský
 * mati
 * Max Sikström
index 9acda73..fb95ca6 100644 (file)
@@ -674,6 +674,7 @@ it from source control: http://www.mediawiki.org/wiki/Download_from_SVN
   drcontinue param is passed
 * (bug 21106) Deprecated parameters now tagged in action=paraminfo
 * (bug 13453) rebuildrecentchanges maintenance script works on PG again
+* (bug 19004) Added support for tags
 
 === Languages updated in 1.16 ===
 
index d79e87d..a28ffd6 100644 (file)
@@ -320,6 +320,7 @@ $wgAutoloadLocalClasses = array(
        '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',
index e577dde..d64e3e8 100644 (file)
@@ -3577,7 +3577,7 @@ class User {
        protected function loadOptions() {
                global $wgCookiePrefix;
                $this->load();
-               if ( $this->mOptionsLoaded || !$this->getId() )
+               if ( $this->mOptionsLoaded )
                        return;
 
                $this->mOptions = self::getDefaultOptions();
@@ -3589,20 +3589,11 @@ class User {
                                $this->mOptions[$key] = $value;
                        }
                } else {
-                       wfDebug( "Loading options for user " . $this->getId() . " from database.\n" );
-                       // Load from database
-                       $dbr = wfGetDB( DB_SLAVE );
-
-                       $res = $dbr->select(
-                               'user_properties',
-                               '*',
-                               array( 'up_user' => $this->getId() ),
-                               __METHOD__
-                       );
-
-                       while( $row = $dbr->fetchObject( $res ) ) {
-                               $this->mOptionOverrides[$row->up_property] = $row->up_value;
-                               $this->mOptions[$row->up_property] = $row->up_value;
+                       $this->mOptionOverrides = array();
+                       if ( $this->getId() ) {
+                               $this->loadOptionsFromDatabase();
+                       } else {
+                               $this->loadOptionsFromCookie();
                        }
 
                        //null skin if User::mId is loaded out of session data without persistant credentials
@@ -3616,23 +3607,73 @@ class User {
                wfRunHooks( 'UserLoadOptions', array( $this, &$this->mOptions ) );
        }
 
+       protected function loadOptionsFromDatabase() {
+               wfDebug( "Loading options for user ".$this->getId()." from database.\n" );
+               // Load from database
+               $dbr = wfGetDB( DB_SLAVE );
+               
+               $res = $dbr->select(
+                       'user_properties',
+                       '*',
+                       array('up_user' => $this->getId()),
+                       __METHOD__
+               );
+               
+               while( $row = $dbr->fetchObject( $res ) ) {
+                       $this->mOptionOverrides[$row->up_property] = $row->up_value;
+                       $this->mOptions[$row->up_property] = $row->up_value;
+               }
+       }
+       
+       protected function loadOptionsFromCookie() {
+               global $wgCookiePrefix;
+               $cookie = $_COOKIE[$wgCookiePrefix."Options"];
+               
+               $overrides = json_decode($cookie, true); // Load assoc array from cookie
+               
+               foreach( $overrides as $key => $value ) {
+                       $this->mOptions[$key] = $value;
+                       $this->mOptionOverrides[$key] = $value;
+               }
+       }
+       
        protected function saveOptions() {
                global $wgAllowPrefChange;
 
-               $extuser = ExternalUser::newFromUser( $this );
-
                $this->loadOptions();
-               $dbw = wfGetDB( DB_MASTER );
-               
-               $insert_rows = array();
                
                $saveOptions = $this->mOptions;
                
+               $extuser = ExternalUser::newFromUser( $this );
+               foreach( $saveOptions as $key => $value ) {
+                       if ( $extuser && isset( $wgAllowPrefChange[$key] ) ) {
+                               switch ( $wgAllowPrefChange[$key] ) {
+                                       case 'local':
+                                       case 'message':
+                                               break;
+                                       case 'semiglobal':
+                                       case 'global':
+                                               $extuser->setPref( $key, $value );
+                               }
+                       }
+               }
+               
                // Allow hooks to abort, for instance to save to a global profile.
                // Reset options to default state before saving.
                if( !wfRunHooks( 'UserSaveOptions', array( $this, &$saveOptions ) ) )
                        return;
 
+               if ( $this->getId() ) {
+                       $this->saveOptionsToDatabase( $saveOptions );
+               } else {
+                       $this->saveOptionsToCookie( $saveOptions );
+               }
+       }
+       
+       protected function saveOptionsToDatabase( $saveOptions ) {
+               $dbw = wfGetDB( DB_MASTER );
+               $insert_rows = array();
+               
                foreach( $saveOptions as $key => $value ) {
                        # Don't bother storing default values
                        if ( ( is_null( self::getDefaultOption( $key ) ) &&
@@ -3644,16 +3685,6 @@ class User {
                                                'up_value' => $value,
                                        );
                        }
-                       if ( $extuser && isset( $wgAllowPrefChange[$key] ) ) {
-                               switch ( $wgAllowPrefChange[$key] ) {
-                                       case 'local':
-                                       case 'message':
-                                               break;
-                                       case 'semiglobal':
-                                       case 'global':
-                                               $extuser->setPref( $key, $value );
-                               }
-                       }
                }
 
                $dbw->begin();
@@ -3662,6 +3693,12 @@ class User {
                $dbw->commit();
        }
 
+       protected function saveOptionsToCookie( $saveOptions ) {
+               global $wgRequest;
+               
+               $data = json_encode( $saveOptions );
+               $wgRequest->response()->setCookie( 'Options', $data, 86400 * 360 );
+       }
        /**
         * Provide an array of HTML 5 attributes to put on an input element
         * intended for the user to enter a new password.  This may include
index e3719e0..e15edb6 100644 (file)
@@ -74,6 +74,7 @@ class ApiQuery extends ApiBase {
                'logevents' => 'ApiQueryLogEvents',
                'recentchanges' => 'ApiQueryRecentChanges',
                'search' => 'ApiQuerySearch',
+               'tags' => 'ApiQueryTags',
                'usercontribs' => 'ApiQueryContributions',
                'watchlist' => 'ApiQueryWatchlist',
                'watchlistraw' => 'ApiQueryWatchlistRaw',
@@ -584,4 +585,4 @@ class ApiQuery extends ApiBase {
                $vers[] = $psModule->getVersion();
                return $vers;
        }
-}
\ No newline at end of file
+}
index 04fc366..3f83bdc 100644 (file)
@@ -51,6 +51,7 @@ class ApiQueryLogEvents extends ApiQueryBase {
                $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');
 
@@ -85,6 +86,18 @@ class ApiQueryLogEvents extends ApiQueryBase {
                $this->addFieldsIf('log_comment', $this->fld_comment);
                $this->addFieldsIf('log_params', $this->fld_details);
                
+               if($this->fld_tags) {
+                       $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->addTables('change_tag');
+                       $this->addJoinConds(array('change_tag' => array('INNER JOIN', array('log_id=ct_log_id'))));
+                       $this->addWhereFld('ct_tag', $params['tag']);
+               }
+               
                if( !is_null($params['type']) ) {
                        $this->addWhereFld('log_type', $params['type']);
                        $index = 'type_time';
@@ -247,6 +260,16 @@ class ApiQueryLogEvents extends ApiQueryBase {
                        }
                }
 
+               if ($this->fld_tags) {
+                       if ($row->ts_tags) {
+                               $tags = explode(',', $row->ts_tags);
+                               $this->getResult()->setIndexedTagName($tags, 'tag');
+                               $vals['tags'] = $tags;
+                       } else {
+                               $vals['tags'] = array();
+                       }
+               }
+               
                return $vals;
        }
 
@@ -265,6 +288,7 @@ class ApiQueryLogEvents extends ApiQueryBase {
                                        'timestamp',
                                        'comment',
                                        'details',
+                                       'tags'
                                )
                        ),
                        'type' => array (
index 5f7ac53..8e0ad4e 100644 (file)
@@ -96,6 +96,7 @@ class ApiQueryRecentChanges extends ApiQueryBase {
                $this->fld_redirect = isset($prop['redirect']);
                $this->fld_patrolled = isset($prop['patrolled']);
                $this->fld_loginfo = isset($prop['loginfo']);
+               $this->fld_tags = isset($prop['tags']);
        }
 
        /**
@@ -211,6 +212,18 @@ class ApiQueryRecentChanges extends ApiQueryBase {
                                $this->addFields('page_is_redirect');
                        }
                }
+               
+               if($this->fld_tags) {
+                       $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->addTables('change_tag');
+                       $this->addJoinConds(array('change_tag' => array('INNER JOIN', array('rc_id=ct_rc_id'))));
+                       $this->addWhereFld('ct_tag' , $params['tag']);
+               }
                $this->token = $params['token'];
                $this->addOption('LIMIT', $params['limit'] +1);
                $this->addOption('USE INDEX', array('recentchanges' => $index));
@@ -343,6 +356,16 @@ class ApiQueryRecentChanges extends ApiQueryBase {
                                $row->rc_log_type, $row->rc_timestamp);
                }
                
+               if ($this->fld_tags) {
+                       if ($row->ts_tags) {
+                               $tags = explode(',', $row->ts_tags);
+                               $this->getResult()->setIndexedTagName($tags, 'tag');
+                               $vals['tags'] = $tags;
+                       } else {
+                               $vals['tags'] = array();
+                       }
+               }
+                       
                if(!is_null($this->token))
                {
                        $tokenFunctions = $this->getTokenFunctions();
@@ -416,6 +439,7 @@ class ApiQueryRecentChanges extends ApiQueryBase {
                                        'redirect',
                                        'patrolled',
                                        'loginfo',
+                                       'tags'
                                )
                        ),
                        'token' => array(
index 7e4f71f..5066b5c 100644 (file)
@@ -42,7 +42,7 @@ class ApiQueryRevisions extends ApiQueryBase {
        }
 
        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
@@ -121,9 +121,8 @@ class ApiQueryRevisions extends ApiQueryBase {
                }
 
                $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']);
@@ -143,6 +142,19 @@ class ApiQueryRevisions extends ApiQueryBase {
                        $this->addFields( Revision::selectPageFields() );
                }
 
+               if (isset ($prop['tags'])) {
+                       $this->fld_tags = true;
+                       $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->addTables('change_tag');
+                       $this->addJoinConds(array('change_tag' => array('INNER JOIN', array('rev_id=ct_rev_id'))));
+                       $this->addWhereFld('ct_tag' , $params['tag']);
+               }
+               
                if (isset ($prop['content'])) {
 
                        // For each page we will request, the user must have read rights for that page
@@ -293,9 +305,9 @@ class ApiQueryRevisions extends ApiQueryBase {
                                $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)
@@ -311,7 +323,8 @@ class ApiQueryRevisions extends ApiQueryBase {
                $db->freeResult($res);
        }
 
-       private function extractRowInfo( $revision ) {
+       private function extractRowInfo( $row ) {
+               $revision = new Revision( $row );
                $title = $revision->getTitle();
                $vals = array ();
 
@@ -353,6 +366,16 @@ class ApiQueryRevisions extends ApiQueryBase {
                        }
                }       
 
+               if ($this->fld_tags) {
+                       if ($row->ts_tags) {
+                               $tags = explode(',', $row->ts_tags);
+                               $this->getResult()->setIndexedTagName($tags, 'tag');
+                               $vals['tags'] = $tags;
+                       } else {
+                               $vals['tags'] = array();
+                       }
+               }
+               
                if(!is_null($this->token))
                {
                        $tokenFunctions = $this->getTokenFunctions();
@@ -427,6 +450,7 @@ class ApiQueryRevisions extends ApiQueryBase {
                                        'size',
                                        'comment',
                                        'content',
+                                       'tags'
                                )
                        ),
                        'limit' => array (
diff --git a/includes/api/ApiQueryTags.php b/includes/api/ApiQueryTags.php
new file mode 100644 (file)
index 0000000..8750b59
--- /dev/null
@@ -0,0 +1,177 @@
+<?php
+
+/*
+ * Created on Jul 9, 2009
+ *
+ * API for MediaWiki 1.8+
+ *
+ * Copyright (C) 2009
+ *
+ * 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();
+               
+               $this->addTables('change_tag');
+               $this->addFields('ct_tag');
+               
+               if($this->fld_hitcount)
+                       $this->addFields('count(*) AS hitcount');
+               
+               $this->addOption('LIMIT', $this->limit + 1);
+               $this->addOption('GROUP BY', 'ct_tag');
+               $this->addWhereRange('ct_tag', 'newer', $params['continue'], null);
+               
+               $res = $this->select(__METHOD__);
+               
+               $ok = true;
+               
+               while ( $row = $res->fetchObject() ) {
+                       if(!$ok) break;
+                       $ok = $this->doTag( $row->ct_tag, $row->hitcount );
+               }
+               
+               //include tags with no hits yet
+               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(
+                       ),
+                       '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';
+       }
+}
index 756805c..d230564 100644 (file)
@@ -42,7 +42,7 @@ class ApiQueryContributions extends ApiQueryBase {
        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() {
 
@@ -57,6 +57,7 @@ class ApiQueryContributions extends ApiQueryBase {
                $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');
@@ -141,7 +142,7 @@ class ApiQueryContributions extends ApiQueryBase {
        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');
@@ -245,6 +246,19 @@ class ApiQueryContributions extends ApiQueryBase {
                $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)
+               {
+                       $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->addTables('change_tag');
+                       $this->addJoinConds(array('change_tag' => array('INNER JOIN', array('rev_id=ct_rev_id'))));
+                       $this->addWhereFld('ct_tag', $this->params['tag']);
+               }
        }
 
        /**
@@ -292,6 +306,16 @@ class ApiQueryContributions extends ApiQueryBase {
                if ($this->fld_size && !is_null($row->rev_len))
                        $vals['size'] = intval($row->rev_len);
 
+               if ($this->fld_tags) {
+                       if ($row->ts_tags) {
+                               $tags = explode(',', $row->ts_tags);
+                               $this->getResult()->setIndexedTagName($tags, 'tag');
+                               $vals['tags'] = $tags;
+                       } else {
+                               $vals['tags'] = array();
+                       }
+               }
+               
                return $vals;
        }
        
@@ -343,6 +367,7 @@ class ApiQueryContributions extends ApiQueryBase {
                                        'size',
                                        'flags',
                                        'patrolled',
+                                       'tags'
                                )
                        ),
                        'show' => array (