Attempt to count actual watchers in the info action
authorFederico Leva <federicoleva@tiscali.it>
Mon, 2 Mar 2015 16:16:58 +0000 (17:16 +0100)
committerFederico Leva <federicoleva@tiscali.it>
Wed, 1 Jul 2015 10:39:27 +0000 (12:39 +0200)
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
includes/DefaultSettings.php
includes/actions/InfoAction.php
languages/i18n/en.json
languages/i18n/qqq.json

index 940adc9..5675956 100644 (file)
@@ -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-<id>" interface message.
 * ':' (colon) is now invalid in usernames for new accounts. Existing accounts
index a755029..a239e68 100644 (file)
@@ -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
index 0c34ddb..6d43ec5 100644 (file)
@@ -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',
index 0cf41d2..509946d 100644 (file)
        "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",
index 3bcab84..ca2c96f 100644 (file)
        "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]",