From 09a21c4af8dd53644edd31fe731638b60f91594a Mon Sep 17 00:00:00 2001 From: Federico Leva Date: Mon, 2 Mar 2015 17:16:58 +0100 Subject: [PATCH] Attempt to count actual watchers in the info action Proposed threshold to be considered an "active" watcher: two times $wgRCMaxAge, configurable with the new configuration setting $wgWatchersMaxAge. The information is not displayed when the number given would be 1 or 0, so that the number (or absence thereof) doesn't "disclose" that the page is (potentially) unpatrolled or completely unwatched and hence easier to vandalise. Configurable with $wgUnwatchedPageSecret too. Also, we don't display this row at all when the user doesn't have the right to see the count of total watchers. Bug: T51506 Change-Id: I10d294a339b131eee94839ed7088ab20d746d881 --- RELEASE-NOTES-1.26 | 3 +++ includes/DefaultSettings.php | 15 +++++++++++++ includes/actions/InfoAction.php | 38 ++++++++++++++++++++++++++++++++- languages/i18n/en.json | 2 ++ languages/i18n/qqq.json | 2 ++ 5 files changed, 59 insertions(+), 1 deletion(-) diff --git a/RELEASE-NOTES-1.26 b/RELEASE-NOTES-1.26 index 940adc9998..5675956d73 100644 --- a/RELEASE-NOTES-1.26 +++ b/RELEASE-NOTES-1.26 @@ -16,6 +16,9 @@ production. new style is encouraged as it's harder to implement incorrectly. === New features in 1.26 === +* (T51506) Now action=info gives estimates of actual watchers for a page. + See $wgRCMaxAge, $wgWatchersMaxAge and $wgUnwatchedPageSecret + to learn how to configure if needed. * Change tags can now be hidden in the interface by disabling the associated "tag-" interface message. * ':' (colon) is now invalid in usernames for new accounts. Existing accounts diff --git a/includes/DefaultSettings.php b/includes/DefaultSettings.php index a755029da5..a239e68b5c 100644 --- a/includes/DefaultSettings.php +++ b/includes/DefaultSettings.php @@ -6025,6 +6025,21 @@ $wgGitRepositoryViewers = array( */ $wgRCMaxAge = 90 * 24 * 3600; +/** + * Page watchers inactive for more than this many seconds are considered inactive. + * Used mainly by action=info. Default: 180 days = about six months. + * @since 1.26 + */ +$wgWatchersMaxAge = 180 * 24 * 3600; + +/** + * If active watchers (per above) are this number or less, do not disclose it. + * Left to 1, prevents unprivileged users from knowing for sure that there are 0. + * Set to -1 if you want to always complement watchers count with this info. + * @since 1.26 + */ +$wgUnwatchedPageSecret = 1; + /** * Filter $wgRCLinkDays by $wgRCMaxAge to avoid showing links for numbers * higher than what will be stored. Note that this is disabled by default diff --git a/includes/actions/InfoAction.php b/includes/actions/InfoAction.php index 0c34ddb4e5..6d43ec55b5 100644 --- a/includes/actions/InfoAction.php +++ b/includes/actions/InfoAction.php @@ -325,8 +325,24 @@ class InfoAction extends FormlessAction { ) { // Number of page watchers $pageInfo['header-basic'][] = array( - $this->msg( 'pageinfo-watchers' ), $lang->formatNum( $pageCounts['watchers'] ) + $this->msg( 'pageinfo-watchers' ), + $lang->formatNum( $pageCounts['watchers'] ) ); + if ( $config->get( 'ShowUpdatedMarker' ) ) { + $minToDisclose = $config->get( 'UnwatchedPageSecret' ); + if ( $pageCounts['visitingWatchers'] > $minToDisclose || + $user->isAllowed( 'unwatchedpages' ) ) { + $pageInfo['header-basic'][] = array( + $this->msg( 'pageinfo-visiting-watchers' ), + $lang->formatNum( $pageCounts['visitingWatchers'] ) + ); + } else { + $pageInfo['header-basic'][] = array( + $this->msg( 'pageinfo-visiting-watchers' ), + $this->msg( 'pageinfo-few-visiting-watchers' ) + ); + } + } } elseif ( $unwatchedPageThreshold !== false ) { $pageInfo['header-basic'][] = array( $this->msg( 'pageinfo-watchers' ), @@ -671,6 +687,26 @@ class InfoAction extends FormlessAction { ); $result['watchers'] = $watchers; + if ( $config->get( 'ShowUpdatedMarker' ) ) { + // Threshold: last visited about 26 weeks before latest edit + $updated = wfTimestamp( TS_UNIX, $this->page->getTimestamp() ); + $age = $config->get( 'WatchersMaxAge' ); + $threshold = $dbr->timestamp( $updated - $age ); + // Number of page watchers who also visited a "recent" edit + $visitingWatchers = (int)$dbr->selectField( + 'watchlist', + 'COUNT(*)', + array( + 'wl_namespace' => $title->getNamespace(), + 'wl_title' => $title->getDBkey(), + 'wl_notificationtimestamp >= ' . $dbr->addQuotes( $threshold ) . + ' OR wl_notificationtimestamp IS NULL' + ), + __METHOD__ + ); + $result['visitingWatchers'] = $visitingWatchers; + } + // Total number of edits $edits = (int)$dbr->selectField( 'revision', diff --git a/languages/i18n/en.json b/languages/i18n/en.json index 0cf41d27c0..509946dd53 100644 --- a/languages/i18n/en.json +++ b/languages/i18n/en.json @@ -2593,7 +2593,9 @@ "pageinfo-robot-index": "Allowed", "pageinfo-robot-noindex": "Disallowed", "pageinfo-watchers": "Number of page watchers", + "pageinfo-visiting-watchers": "Number of page watchers visiting recent edits", "pageinfo-few-watchers": "Fewer than $1 {{PLURAL:$1|watcher|watchers}}", + "pageinfo-few-visiting-watchers": "There may or may not be a watching user visiting recent edits", "pageinfo-redirects-name": "Number of redirects to this page", "pageinfo-redirects-value": "$1", "pageinfo-subpages-name": "Number of subpages of this page", diff --git a/languages/i18n/qqq.json b/languages/i18n/qqq.json index 3bcab84e57..ca2c96f560 100644 --- a/languages/i18n/qqq.json +++ b/languages/i18n/qqq.json @@ -2763,7 +2763,9 @@ "pageinfo-robot-index": "An indication that the page is indexable by search engines, that is listed in their search results.\n\nPreceded by the label {{msg-mw|Pageinfo-robot-policy}}.\n{{Identical|Allowed}}", "pageinfo-robot-noindex": "An indication that the page is not indexable (that is, is not listed on the results page of a search engine).\n\nPreceded by the label {{msg-mw|Pageinfo-robot-policy}}.", "pageinfo-watchers": "Header of the row in the first table of the info action.", + "pageinfo-visiting-watchers": "Header of the row in the first table of the info action. More explicitly, the number counts how many users have last visited the page 26 weeks or less (by default) before the latest edit to the page; in other words, watching users who may see a future edit within about 6 months.", "pageinfo-few-watchers": "Message displayed when there are fewer than $wgUnwatchedPageThreshold watchers. $1 is the value of $wgUnwatchedPageThreshold.", + "pageinfo-few-visiting-watchers": "Message displayed when there are fewer than 2 \"active\" watchers.", "pageinfo-redirects-name": "Header of the row in the first table of the info action.\n\nFollowed by {{msg-mw|Pageinfo-redirects-value}}.\n\nUsed as link text. The link points to \"{{int:Whatlinkshere-title}}\" page ([[Special:WhatLinksHere]]).\n\nSee example: [{{canonicalurl:Main page|action=info}} Main page?action=info]", "pageinfo-redirects-value": "{{Optional}}\nParameters:\n* $1 - the number of redirects to the page", "pageinfo-subpages-name": "Header of the row in the first table of the info action.\n\nFollowed by {{msg-mw|Pageinfo-subpages-value}}.\n\nUsed as link text. The link points to the \"{{int:Prefixindex}}\" page ([[Special:PrefixIndex]]).\n\nSee example: [{{canonicalurl:Main page|action=info}} Main page?action=info]", -- 2.20.1