Siebrand has submitted this change and it was merged.

Change subject: Initial commit
......................................................................


Initial commit

Change-Id: Ic79de494d4684e8ff48235c10545a246059290f0
---
A GlobalContributions.body.php
A GlobalContributions.i18n.php
A GlobalContributions.php
A SpecialGlobalContributions.php
4 files changed, 452 insertions(+), 0 deletions(-)

Approvals:
  Siebrand: Verified; Looks good to me, approved



diff --git a/GlobalContributions.body.php b/GlobalContributions.body.php
new file mode 100644
index 0000000..da501a4
--- /dev/null
+++ b/GlobalContributions.body.php
@@ -0,0 +1,335 @@
+<?php
+
+class GlobalUserContribs extends ContextSource {
+       /** @var User $user to fetch contributions for */
+       protected $user;
+       /** @var array|bool $namespaces */
+       protected $namespaces = false;
+
+       public function __construct( User $user, IContextSource $context ) {
+               $this->user = $user;
+               $this->setContext( $context );
+       }
+
+       /**
+        * return a list of databases we should check on
+        * for the current user
+        * @return array
+        */
+       protected function getWikisToQuery() {
+               $wikis = $this->getWikiList();
+               // Try to use the CA localnames table if possible
+               if ( class_exists( 'CentralAuthUser' ) && !IP::isIPAddress( 
$this->user->getName() ) ) {
+                       $caUser = CentralAuthUser::getInstance( $this->user );
+                       return array_intersect(
+                               array_merge( $caUser->listAttached(), 
$caUser->listUnattached() ),
+                               $wikis
+                       );
+               }
+
+               return $wikis;
+       }
+
+       /**
+        * @return array
+        */
+       protected function getWikiList() {
+               global $wgGUCWikis, $wgLocalDatabases;
+               if ( empty( $wgGUCWikis ) ) {
+                       $wgGUCWikis = $wgLocalDatabases;
+               }
+
+               return $wgGUCWikis;
+       }
+
+       /**
+        * Get's a user's block info
+        * @param DatabaseBase $db
+        * @return stdClass|bool false if not blocked
+        */
+       protected function getBlockInfo( $db ) {
+               // I totally stole this from CentralAuth
+               if ( !IP::isValid( $this->user->getName() ) ) {
+                       $conds = array( 'ipb_address' => $this->user->getName() 
);
+               } else {
+                       $conds = array( 'ipb_address' => IP::toHex( 
$this->user->getName() ) );
+               }
+
+               $row = $db->selectRow( 'ipblocks',
+                       array( 'ipb_expiry', 'ipb_reason', 'ipb_deleted' ),
+                       $conds,
+                       __METHOD__ );
+               if ( $row !== false
+                       && $this->getLanguage()->formatExpiry( 
$row->ipb_expiry, TS_MW ) > wfTimestampNow()
+               ) {
+                       return $row;
+               }
+
+               return false;
+       }
+
+       /**
+        * Loads revisions for the specified wiki
+        * @param string $wiki wikiid
+        * @return array|bool false if user doesn't exist or is hidden
+        */
+       protected function loadLocalData( $wiki ) {
+               $lb = wfGetLB( $wiki );
+               $db = $lb->getConnection( DB_SLAVE, array(), $wiki );
+               $data = array( 'revisions' => array(), 'block' => array() );
+
+               $conds = array(
+                       'rev_deleted' => 0, // @todo let users with rights see 
deleted stuff
+               );
+
+               $fields = array(
+                       'rev_id', 'rev_comment', 'rev_timestamp', 
'rev_minor_edit',
+                       'rev_len', 'rev_parent_id', 'rev_page',
+                       'page_title', 'page_namespace',
+               );
+               $join = array(
+                       'page' => array( 'JOIN', 'rev_page=page_id' )
+               );
+
+               if ( !IP::isIPAddress( $this->user->getName() ) ) {
+                       $row = $db->selectRow(
+                               'user',
+                               array( 'user_id', 'user_editcount' ),
+                               array( 'user_name' => $this->user->getName() ),
+                               __METHOD__
+                       );
+                       if ( $row === false ) {
+                               // This shouldn't be possible with shared user 
tables or CA
+                               // but...be safe.
+                               $lb->reuseConnection( $db );
+                               return false;
+                       }
+                       // This won't work for shared user tables but, if the 
user
+                       // has no edits, don't make the extra query and return 
early.
+                       if ( $row->user_editcount === 0 ) {
+                               $data['block'] = $this->getBlockInfo( $db );
+                               $lb->reuseConnection( $db );
+                               if ( $data['block'] && 
$data['block']->ipb_deleted !== 0 ) {
+                                       return false; // hideuser, pretend it 
doesn't exist.
+                               }
+                               return $data;
+                       }
+                       $conds['rev_user'] = $row->user_id;
+               } else {
+                       $conds['rev_user_text'] = $this->user->getName();
+               }
+               $rows = $db->select(
+                       array( 'revision', 'page' ),
+                       $fields,
+                       $conds,
+                       __METHOD__,
+                       array( 'LIMIT' => 20, 'ORDER BY' => 'rev_timestamp 
DESC' ), // @todo make limit configurable
+                       $join
+               );
+
+               $data['revisions'] = $rows;
+               $data['blocks'] = $this->getBlockInfo( $db );
+
+               $lb->reuseConnection( $db );
+
+               if ( $data['block'] && $data['block']->ipb_deleted !== 0 ) {
+                       return false; // hideuser, pretend it doesn't exist.
+               }
+
+               return $data;
+
+       }
+
+       /**
+        * Assumes whomever set up this farm was sane enough
+        * to use the same script path everywhere
+        * @param $wiki
+        * @param string $type
+        * @return string
+        */
+       protected function getForeignScript( $wiki, $type = 'index' ) {
+               return WikiMap::getWiki( $wiki )->getCanonicalServer() . 
wfScript( $type );
+       }
+
+       /**
+        * Turns a revision into a HTML row
+        * @param string $wiki
+        * @param stdClass $row
+        * @return string HTML
+        */
+       protected function formatRow( $wiki, $row ) {
+               $html = Html::openElement( 'li', array( 'class' => 
'mw-guc-changes-item plainlinks' ) );
+               $lang = $this->getLanguage();
+               $index = $this->getForeignScript( $wiki );
+               $sep = ' <span class="mw-changeslist-separator">. .</span> ';
+
+
+               $ts = $lang->userTimeAndDate( $row->rev_timestamp, 
$this->getUser() );
+               $url = wfAppendQuery( $index, array( 'oldid' => $row->rev_id ) 
);
+               $html .= Linker::makeExternalLink( $url, $ts );
+               $diff = wfAppendQuery( $index, array( 'diff' => $row->rev_id ) 
);
+               $difftext = Linker::makeExternalLink( $diff, $this->msg( 
'diff')->escaped() );
+               $hist = wfAppendQuery( $index, array( 'action' => 'history', 
'curid' => $row->rev_page ) );
+               $histtext = Linker::makeExternalLink( $hist, $this->msg( 
'hist')->escaped() );
+
+               $html .= ' ';
+               $html .= $this->msg( 'parentheses' )
+                       ->rawParams( $difftext . $this->msg( 'pipe-separator' 
)->escaped() . $histtext )
+                       ->escaped();
+               $html .= $sep; // Divider
+
+               // @todo We are missing diff size here.
+
+               if ( $row->rev_parent_id === '0' ) {
+                       $html .= ChangesList::flag( 'newpage' );
+               }
+
+               if ( $row->rev_minor_edit !== '0' ) {
+                       $html .= ChangesList::flag( 'minor' );
+               }
+
+               $html .= ' ';
+
+               // Not a fan of this...but meh.
+               $normTitle = str_replace( '_', ' ', $row->page_title );
+               $nsName = $this->getForeignNSName( $wiki, $row->page_namespace 
);
+               if ( $nsName ) {
+                       $normTitle = $nsName . ':' . $normTitle;
+               }
+               $html .= Linker::makeExternalLink(
+                       // Because our name might not be exact, link using 
page_id
+                       wfAppendQuery( $index, array( 'curid' => 
$row->rev_page) ),
+                       $normTitle
+               );
+
+               $html .= ' ';
+
+               // @todo make links here...
+               //$html .= Linker::formatComment( $row->rev_comment );
+               if ( $row->rev_comment ) {
+                       $html .= '<span class="comment">'
+                               . $this->msg( 'parentheses' )
+                                       ->rawParams( htmlspecialchars( 
$row->rev_comment ) )
+                                       ->escaped()
+                               . '</span>';
+               }
+               //$html .= htmlspecialchars( $row->rev_comment );
+
+               $html .= Html::closeElement( 'li' );
+               return $html;
+       }
+
+       protected function formatWiki( $wiki, $data ) {
+               $hostname = WikiMap::getWiki( $wiki )->getHostname();
+               $html = "<h2 class=\"mw-guc-header\">$hostname</h2>";
+               $html .= Html::openElement( 'ul', array( 'class' => 
'mw-guc-changes-list' ) );
+               foreach ( $data['revisions'] as $row ) {
+                       $html .= $this->formatRow( $wiki, $row );
+               }
+               $html .= Html::closeElement( 'ul' );
+               return $html;
+       }
+
+       public function getHtml() {
+               $html = '';
+               foreach ( $this->getContribs() as $wiki => $data ) {
+                       $html .= $this->formatWiki( $wiki, $data );
+               }
+
+               $this->saveNSToCache();
+
+               return $html;
+       }
+
+       public function getContribs() {
+               $wikis = $this->getWikisToQuery();
+               $data = array();
+               foreach ( $wikis as $wiki ) {
+                       $localData = $this->loadLocalData( $wiki );
+                       if ( $localData !== false ) {
+                               $data[$wiki] = $localData;
+                       }
+               }
+
+               return $data;
+       }
+
+       /**
+        * @param string $wiki
+        * @param $nsid
+        * @return string
+        */
+       protected function getForeignNSName( $wiki, $nsid ) {
+               global $wgConf;
+               $cache = wfGetCache( CACHE_ANYTHING );
+
+               if ( $this->namespaces === false ) {
+                       $data = $cache->get( 'guc::namespaces' );
+                       if ( $data === false ) {
+                               $this->namespaces = array();
+                       } else {
+                               $this->namespaces = $data;
+                       }
+               }
+
+               if ( isset( $this->namespaces[$wiki][$nsid] ) ) {
+                       return $this->namespaces[$wiki][$nsid];
+               }
+               if ( $nsid < 16 || $nsid >= 200 ) {
+                       // Core or extension namespace.
+                       // Some extensions are bad and use 1XX, sucks for them.
+                       $name = $this->getLanguage()->getNsText( $nsid );
+                       if ( $name !== false ) {
+                               $this->namespaces[$wiki][$nsid] = $name;
+                               return $name;
+                       }
+               }
+
+               // Lets try $wgConf now...
+               $extra = $wgConf->get( 'wgExtraNamespaces', $wiki );
+               if ( isset( $extra[$nsid] ) ) {
+                       // Remove any underscores
+                       $name = str_replace( '_', ' ', $extra[$nsid] );
+                       $this->namespaces[$wiki][$nsid] = $name;
+                       return $name;
+               }
+
+               // Blegh. At this point, we should just make an API request.
+               $params = array(
+                       'action' => 'query',
+                       'meta' => 'siteinfo',
+                       'siprop' => 'namespaces',
+                       'format' => 'json'
+               );
+
+               $api = $this->getForeignScript( $wiki, 'api' );
+               $url = wfAppendQuery( $api, $params );
+               $req = MWHttpRequest::factory( $url );
+               $req->execute();
+               $json = $req->getContent();
+               $decoded = FormatJson::decode( $json, true );
+               // Store everything we've got.
+               $map = array_map( function( $val ) {
+                       return $val['*'];
+               }, $decoded['query']['namespaces'] );
+               $this->namespaces[$wiki] = array_merge( 
$this->namespaces[$wiki], $map );
+               if ( isset( $this->namespaces[$wiki][$nsid] ) ) {
+                       return $this->namespaces[$wiki][$nsid];
+               } else {
+                       // Ok, wtf. Just return the numerical id as a string.
+                       $this->namespaces[$wiki][$nsid] = (string)$nsid;
+                       return (string)$nsid;
+               }
+       }
+
+       /**
+        * Saves the namespaces in memcached
+        * Run it after calling getForeignNSName
+        * a bunch of times.
+        */
+       protected function saveNSToCache() {
+               if ( $this->namespaces !== false ) {
+                       wfGetCache( CACHE_ANYTHING )->set( 'guc::namespaces', 
$this->namespaces );
+               }
+       }
+}
diff --git a/GlobalContributions.i18n.php b/GlobalContributions.i18n.php
new file mode 100644
index 0000000..88b082d
--- /dev/null
+++ b/GlobalContributions.i18n.php
@@ -0,0 +1,19 @@
+<?php
+
+$messages = array();
+
+$messages['en'] = array(
+       'guc-desc' => 'Allows users to view a user\'s contributions across all 
wikis',
+       'guc-form-user' => 'Username or IP address',
+       'guc-invalid-username' => 'Invalid username or IP address provided',
+       'globalcontributions' => 'Global contributions',
+       'globalcontributions-legend' => 'Global contributions',
+);
+
+$messages['qqq'] = array(
+       'guc-desc' => 
'{{desc|name=GlobalContributions|url=https://www.mediawiki.org/wiki/Extension:GlobalContributions}}',
+       'guc-form-user' => 'Label for field on form',
+       'guc-invalid-username' => 'Error message shown to user if the username 
they provided is invalid',
+       'globalcontributions' => 'Title of Special:GlobalContributions special 
page',
+       'globalcontributions-legend' => 'Legend for form on 
Special:GlobalContributions',
+);
diff --git a/GlobalContributions.php b/GlobalContributions.php
new file mode 100644
index 0000000..bb449f0
--- /dev/null
+++ b/GlobalContributions.php
@@ -0,0 +1,39 @@
+<?php
+/*
+ * Global user contributions extension
+ * Adds Special:GlobalContributions for viewing a user
+ * or IP address's contributions across a wiki farm
+ *
+ * Inspired by Luxo's tool aka GUC.
+ *
+ * @file
+ * @ingroup Extensions
+ * @author Kunal Mehta
+ * @license GPLv2 or higher
+ */
+
+if ( !defined( 'MEDIAWIKI' ) ) {
+       exit;
+}
+
+/*
+ * Wikis to search
+ * If empty, defaults to $wgLocalDatabases
+ * @var array of database names
+ */
+$wgGUCWikis = array();
+
+$wgExtensionCredits['specialpage'][] = array(
+       'path' => __FILE__,
+       'name' => 'GlobalContributions',
+       'author' => 'Kunal Mehta',
+       'url' => 'https://www.mediawiki.org/wiki/Extension:GlobalContributions',
+       'descriptionmsg' => 'guc-desc',
+       'version' => '0.1',
+);
+
+$wgAutoloadClasses['GlobalUserContribs'] = __DIR__ . 
'/GlobalContributions.body.php';
+$wgAutoloadClasses['SpecialGlobalContributions'] = __DIR__ . 
'/SpecialGlobalContributions.php';
+
+$wgSpecialPages['GlobalContributions'] = 'SpecialGlobalContributions';
+$wgExtensionMessagesFiles['GlobalContributions'] = __DIR__ . 
'/GlobalContributions.i18n.php';
diff --git a/SpecialGlobalContributions.php b/SpecialGlobalContributions.php
new file mode 100644
index 0000000..13203a1
--- /dev/null
+++ b/SpecialGlobalContributions.php
@@ -0,0 +1,59 @@
+<?php
+
+class SpecialGlobalContributions extends FormSpecialPage {
+
+       protected $title;
+
+       protected $user;
+
+       public function __construct() {
+               parent::__construct( 'GlobalContributions' );
+       }
+
+       protected function alterForm( HTMLForm $form ) {
+               $form->setMethod( 'GET' );
+               $context = new DerivativeContext( $this->getContext() );
+               $context->setTitle( $this->getPageTitle() ); // Strip subpage
+               $form->setContext( $context );
+       }
+
+       protected function getFormFields() {
+               return array(
+                       'user' => array(
+                               'type' => 'text',
+                               'name' => 'user',
+                               'default' => $this->par,
+                               'label-message' => 'guc-form-user',
+                       ),
+               );
+       }
+
+       public function onSubmit( array $data ) {
+               // @todo Given that we're overriding a lot, figure out
+               // if we should just use a normal SpecialPage
+               $form = $this->getForm();
+               $form->mFieldData = $data; // Well, this works!
+               $form->displayForm( false );
+
+               $out = $this->getOutput();
+
+               $name = $data['user'];
+               $user = User::newFromName( $name );
+               if ( !$user && !IP::isIPAddress( $name ) ) {
+                       if ( trim( $name ) ) {
+                               // If they just visit the page with no input,
+                               // don't show any error.
+                               $out->addWikiMsg( 'guc-invalid-username' );
+                       }
+                       return true;
+               }
+               $user = User::newFromName( $name, false );
+
+               $guc = new GlobalUserContribs( $user, $this->getContext() );
+               $out->addHTML( $guc->getHtml() );
+
+               return true;
+       }
+
+
+}
\ No newline at end of file

-- 
To view, visit https://gerrit.wikimedia.org/r/105911
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: merged
Gerrit-Change-Id: Ic79de494d4684e8ff48235c10545a246059290f0
Gerrit-PatchSet: 2
Gerrit-Project: mediawiki/extensions/GlobalContributions
Gerrit-Branch: master
Gerrit-Owner: Legoktm <[email protected]>
Gerrit-Reviewer: Legoktm <[email protected]>
Gerrit-Reviewer: Siebrand <[email protected]>

_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to