From: Brad Jorsch Date: Mon, 18 Jun 2012 20:33:19 +0000 (-0400) Subject: Allow manipulation of wl_notificationtimestamp via the API X-Git-Tag: 1.31.0-rc.0~22869 X-Git-Url: http://git.cyclocoop.org/%22.%24info%5B?a=commitdiff_plain;h=fdad41156c04f50a67655cc6b1411bad5163a7eb;p=lhc%2Fweb%2Fwiklou.git Allow manipulation of wl_notificationtimestamp via the API It should be possible to query the notificationtimestamp as a page info property, rather than only by querying the recent changes for the watchlist. It should also be possible to clear or adjust the notificationtimestamp via the API. This patch does just that. Change-Id: I8e2c0769e93802a6a09936899a41c07f9c4c9f25 --- diff --git a/RELEASE-NOTES-1.20 b/RELEASE-NOTES-1.20 index 50f2673010..6f595715d1 100644 --- a/RELEASE-NOTES-1.20 +++ b/RELEASE-NOTES-1.20 @@ -219,6 +219,7 @@ upgrade PHP if you have not done so prior to upgrading MediaWiki. * (bug 27567) Add file repo support to prop=duplicatefiles. * (bug 27610) Add archivename for non-latest image version to list=filearchive * (bug 38231) Add xml parse tree to action=parse. +* Watchlist notification timestamp may be queried by page and may be updated via the API. === Languages updated in 1.20 === diff --git a/includes/AutoLoader.php b/includes/AutoLoader.php index 7e11f3ef64..bdd6316c2d 100644 --- a/includes/AutoLoader.php +++ b/includes/AutoLoader.php @@ -397,6 +397,7 @@ $wgAutoloadLocalClasses = array( 'ApiResult' => 'includes/api/ApiResult.php', 'ApiRollback' => 'includes/api/ApiRollback.php', 'ApiRsd' => 'includes/api/ApiRsd.php', + 'ApiSetNotificationTimestamp' => 'includes/api/ApiSetNotificationTimestamp.php', 'ApiTokens' => 'includes/api/ApiTokens.php', 'ApiUnblock' => 'includes/api/ApiUnblock.php', 'ApiUndelete' => 'includes/api/ApiUndelete.php', diff --git a/includes/api/ApiMain.php b/includes/api/ApiMain.php index 05f6652c2d..6b5d6d1887 100644 --- a/includes/api/ApiMain.php +++ b/includes/api/ApiMain.php @@ -65,6 +65,7 @@ class ApiMain extends ApiBase { // Write modules 'purge' => 'ApiPurge', + 'setnotificationtimestamp' => 'ApiSetNotificationTimestamp', 'rollback' => 'ApiRollback', 'delete' => 'ApiDelete', 'undelete' => 'ApiUndelete', diff --git a/includes/api/ApiQueryInfo.php b/includes/api/ApiQueryInfo.php index 4a85b0b403..5d4f0346ed 100644 --- a/includes/api/ApiQueryInfo.php +++ b/includes/api/ApiQueryInfo.php @@ -33,7 +33,7 @@ class ApiQueryInfo extends ApiQueryBase { private $fld_protection = false, $fld_talkid = false, $fld_subjectid = false, $fld_url = false, - $fld_readable = false, $fld_watched = false, + $fld_readable = false, $fld_watched = false, $fld_notificationtimestamp = false, $fld_preload = false, $fld_displaytitle = false; private $params, $titles, $missing, $everything, $pageCounter; @@ -41,7 +41,7 @@ class ApiQueryInfo extends ApiQueryBase { private $pageRestrictions, $pageIsRedir, $pageIsNew, $pageTouched, $pageLatest, $pageLength; - private $protections, $watched, $talkids, $subjectids, $displaytitles; + private $protections, $watched, $notificationtimestamps, $talkids, $subjectids, $displaytitles; private $tokenFunctions; @@ -248,6 +248,7 @@ class ApiQueryInfo extends ApiQueryBase { $prop = array_flip( $this->params['prop'] ); $this->fld_protection = isset( $prop['protection'] ); $this->fld_watched = isset( $prop['watched'] ); + $this->fld_notificationtimestamp = isset( $prop['notificationtimestamp'] ); $this->fld_talkid = isset( $prop['talkid'] ); $this->fld_subjectid = isset( $prop['subjectid'] ); $this->fld_url = isset( $prop['url'] ); @@ -303,7 +304,7 @@ class ApiQueryInfo extends ApiQueryBase { $this->getProtectionInfo(); } - if ( $this->fld_watched ) { + if ( $this->fld_watched || $this->fld_notificationtimestamp ) { $this->getWatchedInfo(); } @@ -386,6 +387,13 @@ class ApiQueryInfo extends ApiQueryBase { $pageInfo['watched'] = ''; } + if ( $this->fld_notificationtimestamp ) { + $pageInfo['notificationtimestamp'] = ''; + if ( isset( $this->notificationtimestamps[$ns][$dbkey] ) ) { + $pageInfo['notificationtimestamp'] = wfTimestamp( TS_ISO_8601, $this->notificationtimestamps[$ns][$dbkey] ); + } + } + if ( $this->fld_talkid && isset( $this->talkids[$ns][$dbkey] ) ) { $pageInfo['talkid'] = $this->talkids[$ns][$dbkey]; } @@ -634,6 +642,7 @@ class ApiQueryInfo extends ApiQueryBase { /** * Get information about watched status and put it in $this->watched + * and $this->notificationtimestamps */ private function getWatchedInfo() { $user = $this->getUser(); @@ -643,6 +652,7 @@ class ApiQueryInfo extends ApiQueryBase { } $this->watched = array(); + $this->notificationtimestamps = array(); $db = $this->getDB(); $lb = new LinkBatch( $this->everything ); @@ -650,6 +660,7 @@ class ApiQueryInfo extends ApiQueryBase { $this->resetQueryParams(); $this->addTables( array( 'watchlist' ) ); $this->addFields( array( 'wl_title', 'wl_namespace' ) ); + $this->addFieldsIf( 'wl_notificationtimestamp', $this->fld_notificationtimestamp ); $this->addWhere( array( $lb->constructSet( 'wl', $db ), 'wl_user' => $user->getID() @@ -658,7 +669,12 @@ class ApiQueryInfo extends ApiQueryBase { $res = $this->select( __METHOD__ ); foreach ( $res as $row ) { - $this->watched[$row->wl_namespace][$row->wl_title] = true; + if ( $this->fld_watched ) { + $this->watched[$row->wl_namespace][$row->wl_title] = true; + } + if ( $this->fld_notificationtimestamp ) { + $this->notificationtimestamps[$row->wl_namespace][$row->wl_title] = $row->wl_notificationtimestamp; + } } } @@ -693,6 +709,7 @@ class ApiQueryInfo extends ApiQueryBase { 'protection', 'talkid', 'watched', # private + 'notificationtimestamp', # private 'subjectid', 'url', 'readable', # private @@ -714,14 +731,15 @@ class ApiQueryInfo extends ApiQueryBase { return array( 'prop' => array( 'Which additional properties to get:', - ' protection - List the protection level of each page', - ' talkid - The page ID of the talk page for each non-talk page', - ' watched - List the watched status of each page', - ' subjectid - The page ID of the parent page for each talk page', - ' url - Gives a full URL to the page, and also an edit URL', - ' readable - Whether the user can read this page', - ' preload - Gives the text returned by EditFormPreloadText', - ' displaytitle - Gives the way the page title is actually displayed', + ' protection - List the protection level of each page', + ' talkid - The page ID of the talk page for each non-talk page', + ' watched - List the watched status of each page', + ' notificationtimestamp - The watchlist notification timestamp of each page', + ' subjectid - The page ID of the parent page for each talk page', + ' url - Gives a full URL to the page, and also an edit URL', + ' readable - Whether the user can read this page', + ' preload - Gives the text returned by EditFormPreloadText', + ' displaytitle - Gives the way the page title is actually displayed', ), 'token' => 'Request a token to perform a data-modifying action on a page', 'continue' => 'When more results are available, use this to continue', @@ -749,6 +767,12 @@ class ApiQueryInfo extends ApiQueryBase { 'watched' => array( 'watched' => 'boolean' ), + 'notificationtimestamp' => array( + 'notificationtimestamp' => array( + ApiBase::PROP_TYPE => 'timestamp', + ApiBase::PROP_NULLABLE => true + ) + ), 'talkid' => array( 'talkid' => array( ApiBase::PROP_TYPE => 'integer', diff --git a/includes/api/ApiSetNotificationTimestamp.php b/includes/api/ApiSetNotificationTimestamp.php new file mode 100644 index 0000000000..f9ecfec7be --- /dev/null +++ b/includes/api/ApiSetNotificationTimestamp.php @@ -0,0 +1,287 @@ +getUser(); + + if ( $user->isAnon() ) { + $this->dieUsage( 'Anonymous users cannot use watchlist change notifications', 'notloggedin' ); + } + + $params = $this->extractRequestParams(); + $this->requireMaxOneParameter( $params, 'timestamp', 'torevid', 'newerthanrevid' ); + + $pageSet = new ApiPageSet( $this ); + $args = array_merge( array( $params, 'entirewatchlist' ), array_keys( $pageSet->getAllowedParams() ) ); + call_user_func_array( array( $this, 'requireOnlyOneParameter' ), $args ); + + $db = $this->getDB(); + $dbw = $this->getDB( DB_MASTER ); + + $timestamp = null; + if ( isset( $params['timestamp'] ) ) { + $timestamp = $dbw->timestamp( $params['timestamp'] ); + } + + if ( !$params['entirewatchlist'] ) { + $pageSet->execute(); + } + + if ( isset( $params['torevid'] ) ) { + if ( $params['entirewatchlist'] || $pageSet->getGoodTitleCount() > 1 ) { + $this->dieUsage( 'torevid may only be used with a single page', 'multpages' ); + } + $title = reset( $pageSet->getGoodTitles() ); + $timestamp = Revision::getTimestampFromId( $title, $params['torevid'] ); + if ( $timestamp ) { + $timestamp = $dbw->timestamp( $timestamp ); + } else { + $timestamp = null; + } + } elseif ( isset( $params['newerthanrevid'] ) ) { + if ( $params['entirewatchlist'] || $pageSet->getGoodTitleCount() > 1 ) { + $this->dieUsage( 'newerthanrevid may only be used with a single page', 'multpages' ); + } + $title = reset( $pageSet->getGoodTitles() ); + $revid = $title->getNextRevisionID( $params['newerthanrevid'] ); + if ( $revid ) { + $timestamp = $dbw->timestamp( Revision::getTimestampFromId( $title, $revid ) ); + } else { + $timestamp = null; + } + } + + $apiResult = $this->getResult(); + $result = array(); + if ( $params['entirewatchlist'] ) { + // Entire watchlist mode: Just update the thing and return a success indicator + $dbw->update( 'watchlist', array( 'wl_notificationtimestamp' => $timestamp ), + array( 'wl_user' => $user->getID() ), + __METHOD__ + ); + + $result['notificationtimestamp'] = ( is_null( $timestamp ) ? '' : wfTimestamp( TS_ISO_8601, $timestamp ) ); + } else { + // First, log the invalid titles + foreach( $pageSet->getInvalidTitles() as $title ) { + $r = array(); + $r['title'] = $title; + $r['invalid'] = ''; + $result[] = $r; + } + foreach( $pageSet->getMissingPageIDs() as $p ) { + $page = array(); + $page['pageid'] = $p; + $page['missing'] = ''; + $page['notwatched'] = ''; + $result[] = $page; + } + foreach( $pageSet->getMissingRevisionIDs() as $r ) { + $rev = array(); + $rev['revid'] = $r; + $rev['missing'] = ''; + $rev['notwatched'] = ''; + $result[] = $rev; + } + + // Now process the valid titles + $lb = new LinkBatch( $pageSet->getTitles() ); + $dbw->update( 'watchlist', array( 'wl_notificationtimestamp' => $timestamp ), + array( 'wl_user' => $user->getID(), $lb->constructSet( 'wl', $dbw ) ), + __METHOD__ + ); + + // Query the results of our update + $timestamps = array(); + $res = $dbw->select( 'watchlist', array( 'wl_namespace', 'wl_title', 'wl_notificationtimestamp' ), + array( 'wl_user' => $user->getID(), $lb->constructSet( 'wl', $dbw ) ), + __METHOD__ + ); + foreach ( $res as $row ) { + $timestamps[$row->wl_namespace][$row->wl_title] = $row->wl_notificationtimestamp; + } + + // Now, put the valid titles into the result + $pages = array(); + foreach ( $pageSet->getTitles() as $title ) { + $ns = $title->getNamespace(); + $dbkey = $title->getDBkey(); + $r = array( + 'ns' => intval( $ns ), + 'title' => $title->getPrefixedText(), + ); + if ( !$title->exists() ) { + $r['missing'] = ''; + } + if ( isset( $timestamps[$ns] ) && array_key_exists( $dbkey, $timestamps[$ns] ) ) { + $r['notificationtimestamp'] = ''; + if ( $timestamps[$ns][$dbkey] !== null ) { + $r['notificationtimestamp'] = wfTimestamp( TS_ISO_8601, $timestamps[$ns][$dbkey] ); + } + } else { + $r['notwatched'] = ''; + } + $result[] = $r; + } + + $apiResult->setIndexedTagName( $result, 'page' ); + } + $apiResult->addValue( null, $this->getModuleName(), $result ); + } + + public function mustBePosted() { + return true; + } + + public function isWriteMode() { + return true; + } + + public function needsToken() { + return true; + } + + public function getTokenSalt() { + return ''; + } + + public function getAllowedParams() { + $psModule = new ApiPageSet( $this ); + return $psModule->getAllowedParams() + array( + 'entirewatchlist' => array( + ApiBase::PARAM_TYPE => 'boolean' + ), + 'token' => null, + 'timestamp' => array( + ApiBase::PARAM_TYPE => 'timestamp' + ), + 'torevid' => array( + ApiBase::PARAM_TYPE => 'integer' + ), + 'newerthanrevid' => array( + ApiBase::PARAM_TYPE => 'integer' + ), + ); + } + + public function getParamDescription() { + $psModule = new ApiPageSet( $this ); + return $psModule->getParamDescription() + array( + 'entirewatchlist' => 'Work on all watched pages', + 'timestamp' => 'Timestamp to which to set the notification timestamp', + 'torevid' => 'Revision to set the notification timestamp to (one page only)', + 'newerthanrevid' => 'Revision to set the notification timestamp newer than (one page only)', + 'token' => 'A token previously acquired via prop=info', + ); + } + + public function getResultProperties() { + return array( + ApiBase::PROP_LIST => true, + ApiBase::PROP_ROOT => array( + 'notificationtimestamp' => array( + ApiBase::PROP_TYPE => 'timestamp', + ApiBase::PROP_NULLABLE => true + ) + ), + '' => array( + 'ns' => array( + ApiBase::PROP_TYPE => 'namespace', + ApiBase::PROP_NULLABLE => true + ), + 'title' => array( + ApiBase::PROP_TYPE => 'string', + ApiBase::PROP_NULLABLE => true + ), + 'pageid' => array( + ApiBase::PROP_TYPE => 'integer', + ApiBase::PROP_NULLABLE => true + ), + 'revid' => array( + ApiBase::PROP_TYPE => 'integer', + ApiBase::PROP_NULLABLE => true + ), + 'invalid' => 'boolean', + 'missing' => 'boolean', + 'notwatched' => 'boolean', + 'notificationtimestamp' => array( + ApiBase::PROP_TYPE => 'timestamp', + ApiBase::PROP_NULLABLE => true + ) + ) + ); + } + + public function getDescription() { + return array( 'Update the notification timestamp for watched pages.', + 'This affects the highlighting of changed pages in the watchlist and history,', + 'and the sending of email when the "E-mail me when a page on my watchlist is', + 'changed" preference is enabled.' + ); + } + + public function getPossibleErrors() { + $psModule = new ApiPageSet( $this ); + return array_merge( + parent::getPossibleErrors(), + $psModule->getPossibleErrors(), + $this->getRequireMaxOneParameterErrorMessages( array( 'timestamp', 'torevid', 'newerthanrevid' ) ), + $this->getRequireOnlyOneParameterErrorMessages( array_merge( array( 'entirewatchlist' ), array_keys( $psModule->getAllowedParams() ) ) ), + array( + array( 'code' => 'notloggedin', 'info' => 'Anonymous users cannot use watchlist change notifications' ), + array( 'code' => 'multpages', 'info' => 'torevid may only be used with a single page' ), + array( 'code' => 'multpages', 'info' => 'newerthanrevid may only be used with a single page' ), + ) + ); + } + + public function getExamples() { + return array( + 'api.php?action=setnotificationtimestamp&entirewatchlist=&token=ABC123' => 'Reset the notification status for the entire watchlist', + 'api.php?action=setnotificationtimestamp&titles=Main_page&token=ABC123' => 'Reset the notification status for "Main page"', + 'api.php?action=setnotificationtimestamp&titles=Main_page×tamp=2012-01-01T00:00:00Z&token=ABC123' => 'Set the notification timestamp for "Main page" so all edits since 1 January 2012 are unviewed', + ); + } + + public function getHelpUrls() { + return 'https://www.mediawiki.org/wiki/API:SetNotificationTimestamp'; + } + + public function getVersion() { + return __CLASS__ . ': $Id$'; + } +}