r78296 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r78295‎ | r78296 | r78297 >
Date:12:29, 13 December 2010
Author:jeroendedauw
Status:deferred
Tags:
Comment:
Added preliminary file pushing support
Modified paths:
  • /trunk/extensions/Push/Push.i18n.php (modified) (history)
  • /trunk/extensions/Push/Push.php (modified) (history)
  • /trunk/extensions/Push/Push_Settings.php (modified) (history)
  • /trunk/extensions/Push/RELEASE-NOTES (modified) (history)
  • /trunk/extensions/Push/api/ApiPush.php (modified) (history)
  • /trunk/extensions/Push/api/ApiPushImages.php (added) (history)
  • /trunk/extensions/Push/includes/Push_Tab.php (modified) (history)
  • /trunk/extensions/Push/includes/ext.push.tab.js (modified) (history)

Diff [purge]

Index: trunk/extensions/Push/Push.i18n.php
@@ -50,6 +50,7 @@
5151 'push-tab-inc-templates' => 'Include templates',
5252 'push-tab-used-templates' => '(Used {{PLURAL:$2|template|templates}}: $1)',
5353 'push-tab-no-used-templates' => '(No templates are used on this page.)',
 54+ 'push-tab-inc-files' => 'Include files',
5455
5556 // Special page
5657 'special-push' => 'Push pages',
Index: trunk/extensions/Push/Push.php
@@ -22,7 +22,7 @@
2323 die( 'Not an entry point.' );
2424 }
2525
26 -define( 'Push_VERSION', '0.4 alpha' );
 26+define( 'Push_VERSION', '0.5 alpha' );
2727
2828 $wgExtensionCredits['other'][] = array(
2929 'path' => __FILE__,
@@ -45,6 +45,7 @@
4646
4747 $wgAutoloadClasses['PushHooks'] = $egPushIP . '/Push.hooks.php';
4848 $wgAutoloadClasses['ApiPush'] = $egPushIP . '/api/ApiPush.php';
 49+$wgAutoloadClasses['ApiPushImages'] = $egPushIP . '/api/ApiPushImages.php';
4950 $wgAutoloadClasses['PushTab'] = $egPushIP . '/includes/Push_Tab.php';
5051 $wgAutoloadClasses['PushFunctions'] = $egPushIP . '/includes/Push_Functions.php';
5152 $wgAutoloadClasses['SpecialPush'] = $egPushIP . '/specials/Push_Body.php';
@@ -53,6 +54,7 @@
5455 $wgSpecialPageGroups['Push'] = 'pagetools';
5556
5657 $wgAPIModules['push'] = 'ApiPush';
 58+$wgAPIModules['pushimages'] = 'ApiPushImages';
5759
5860 $wgHooks['UnknownAction'][] = 'PushTab::onUnknownAction';
5961 $wgHooks['SkinTemplateTabs'][] = 'PushTab::displayTab';
Index: trunk/extensions/Push/Push_Settings.php
@@ -28,6 +28,8 @@
2929 $wgGroupPermissions['autoconfirmed']['bulkpush'] = true;
3030 $wgGroupPermissions['sysop']['bulkpush'] = true;
3131 $wgGroupPermissions['sysop']['pushadmin'] = true;
 32+$wgGroupPermissions['autoconfirmed']['filepush'] = true;
 33+$wgGroupPermissions['sysop']['filepush'] = true;
3234
3335 # Show the push action as a tab (if not, it's displayed in the actions dropdown).
3436 # This only works for skins with an actions dropdown. For others push will always appear as a tab.
@@ -37,6 +39,10 @@
3840 # This is the default choice.
3941 $egPushIncTemplates = false;
4042
 43+# You can choose to push files used in a page.
 44+# This is the default choice.
 45+$egPushIncFiles = false;
 46+
4147 # Indicated if login options should be added to the push interface or not.
4248 $egPushAllowLogin = true;
4349
Index: trunk/extensions/Push/RELEASE-NOTES
@@ -4,6 +4,11 @@
55 Latest version of the release notes: http://svn.wikimedia.org/viewvc/mediawiki/trunk/extensions/Push/RELEASE-NOTES?view=co
66
77
 8+=== Version 0.5 ===
 9+2010-12-xx
 10+
 11+* Added support for image-pushing.
 12+
813 === Version 0.4 ===
914 2010-12-12
1015
Index: trunk/extensions/Push/includes/Push_Tab.php
@@ -270,10 +270,25 @@
271271 * @since 0.4
272272 */
273273 protected static function displayPushOptions() {
274 - global $wgOut, $wgTitle, $wgLang, $egPushIncTemplates;
 274+ global $wgOut, $wgUser;
275275
276276 $wgOut->addHTML( '<h3>' . htmlspecialchars( wfMsg( 'push-tab-push-options' ) ) . '</h3>' );
277277
 278+ self::displayIncTemplatesOption();
 279+
 280+ if ( $wgUser->isAllowed( 'filepush' ) ) {
 281+ self::displayIncFilesOption();
 282+ }
 283+ }
 284+
 285+ /**
 286+ * Outputs the HTML for the "include templates" option.
 287+ *
 288+ * @since 0.4
 289+ */
 290+ protected static function displayIncTemplatesOption() {
 291+ global $wgOut, $wgTitle, $wgLang, $egPushIncTemplates;
 292+
278293 $usedTemplates = array_keys(
279294 PushFunctions::getTemplates(
280295 array( $wgTitle->getFullText() ),
@@ -310,7 +325,29 @@
311326 htmlspecialchars( wfMsg( 'push-tab-no-used-templates' ) )
312327 )
313328 )
314 - );
 329+ );
315330 }
316331
 332+ /**
 333+ * Outputs the HTML for the "include files" option.
 334+ *
 335+ * @since 0.4
 336+ */
 337+ protected static function displayIncFilesOption() {
 338+ global $wgOut, $wgTitle, $egPushIncFiles;
 339+
 340+ $wgOut->addHTML(
 341+ Html::rawElement(
 342+ 'div',
 343+ array( 'id' => 'divIncFiles' ),
 344+ Xml::check( 'checkIncFiles', $egPushIncFiles, array( 'id' => 'checkIncFiles' ) ) .
 345+ Html::element(
 346+ 'label',
 347+ array( 'id' => 'lblIncFiles', 'for' => 'checkIncFiles' ),
 348+ wfMsg( 'push-tab-inc-files' )
 349+ )
 350+ )
 351+ );
 352+ }
 353+
317354 }
\ No newline at end of file
Index: trunk/extensions/Push/includes/ext.push.tab.js
@@ -23,6 +23,11 @@
2424 }
2525 }
2626
 27+ var pages;
 28+ var imageRequestMade = false;
 29+ var images = false;
 30+ var imagePushRequests = [];
 31+
2732 $.each($(".push-button"), function(i,v) {
2833 getRemoteArticleInfo( $(v).attr( 'targetid' ), $(v).attr( 'pushtarget' ) );
2934 });
@@ -31,12 +36,12 @@
3237 this.disabled = true;
3338 this.innerHTML = mediaWiki.msg( 'push-button-pushing' );
3439
35 - if ( $('#checkIncTemplates:checked').attr('checked') ) {
36 - var pages = window.wgPushTemplates;
 40+ if ( $('#checkIncTemplates').attr('checked') ) {
 41+ pages = window.wgPushTemplates;
3742 pages.unshift( $('#pageName').attr('value') );
3843 }
3944 else {
40 - var pages = [$('#pageName').attr('value')];
 45+ pages = [$('#pageName').attr('value')];
4146 }
4247
4348 initiatePush(
@@ -45,6 +50,10 @@
4651 $(this).attr( 'pushtarget' ),
4752 $(this).attr( 'targetname' )
4853 );
 54+
 55+ if ( $('#checkIncFiles').length != 0 && $('#checkIncFiles').attr('checked') && !imageRequestMade ) {
 56+ getIncludedImages();
 57+ }
4958 });
5059
5160 $('#push-all-button').click(function() {
@@ -125,13 +134,13 @@
126135 }
127136 else {
128137 sender.innerHTML = mediaWiki.msg( 'push-button-completed' );
129 - setTimeout( function() {reEnableButton( sender );}, 1000 );
 138+ setTimeout( function() {reEnableButton( sender, targetUrl, targetName );}, 1000 );
130139 }
131140 }
132141 );
133142 }
134143
135 - function reEnableButton( button ) {
 144+ function reEnableButton( button, targetUrl, targetName ) {
136145 button.innerHTML = mediaWiki.msg( 'push-button-text' );
137146 button.disabled = false;
138147
@@ -139,7 +148,11 @@
140149
141150 // If there is a "push all" button, make sure to reset it
142151 // when all other buttons have been reset.
143 - if ( typeof pushAllButton !== "undefined" ) {
 152+ if ( typeof pushAllButton === "undefined" ) {
 153+ imagePushRequests.push( { 'sender': button, 'targetUrl': targetUrl, 'targetName': targetName } );
 154+ startImagesPush();
 155+ }
 156+ else {
144157 var hasDisabled = false;
145158
146159 $.each($(".push-button"), function(i,v) {
@@ -151,10 +164,77 @@
152165 if ( !hasDisabled ) {
153166 pushAllButton.attr( "disabled", false );
154167 pushAllButton.text( mediaWiki.msg( 'push-button-all' ) );
 168+
 169+ imagePushRequests.push( { 'sender': button, 'targetUrl': targetUrl, 'targetName': targetName } );
 170+ startImagesPush();
 171+ }
 172+ }
 173+ }
 174+
 175+ function getIncludedImages() {
 176+ imageRequestMade = true;
 177+
 178+ $.getJSON(
 179+ wgScriptPath + '/api.php',
 180+ {
 181+ 'action': 'query',
 182+ 'prop': 'images',
 183+ 'format': 'json',
 184+ 'titles': pages.join( '|' ),
 185+ },
 186+ function( data ) {
 187+ if ( data.query ) {
 188+ images = [];
 189+
 190+ for ( page in data.query.pages ) {
 191+ for ( var i = data.query.pages[page].images.length - 1; i >= 0; i-- ) {
 192+ if ( $.inArray( data.query.pages[page].images[i].title, images ) == -1 ) {
 193+ images.push( data.query.pages[page].images[i].title );
 194+ }
 195+ }
 196+ }
 197+
 198+ startImagesPush();
 199+ }
 200+ else {
 201+ // TODO
 202+ }
155203 }
 204+ );
 205+ }
 206+
 207+ function startImagesPush() {
 208+ if ( images !== false ) {
 209+ var req;
 210+ while ( req = imagePushRequests.pop() ) {
 211+ initiateImagePush( req.sender, req.targetUrl, req.targetName );
 212+ }
156213 }
157214 }
158215
 216+ function initiateImagePush( sender, targetUrl, targetName ) {
 217+ $.getJSON(
 218+ wgScriptPath + '/api.php',
 219+ {
 220+ 'action': 'pushimages',
 221+ 'format': 'json',
 222+ 'images': images.join( '|' ),
 223+ 'targets': targetUrl
 224+ },
 225+ function( data ) {
 226+ if ( data.upload ) {
 227+
 228+ }
 229+ else if ( data.error ) {
 230+ // TODO
 231+ }
 232+ else {
 233+ // TODO
 234+ }
 235+ }
 236+ );
 237+ }
 238+
159239 function handleError( sender, targetUrl, error ) {
160240 alert( error.info );
161241 sender.innerHTML = mediaWiki.msg( 'push-button-failed' );
Index: trunk/extensions/Push/api/ApiPush.php
@@ -102,7 +102,7 @@
103103 *
104104 * @param string $user
105105 * @param string $password
106 - * @param string $targets
 106+ * @param string $target
107107 * @param string $token
108108 * @param CookieJar $cookie
109109 */
@@ -368,14 +368,14 @@
369369
370370 public function getParamDescription() {
371371 return array(
372 - 'page' => 'The name of the page to push.',
373 - 'targets' => 'The urls of the wikis to push to.',
 372+ 'page' => 'The names of the page to push. Delimitered by |',
 373+ 'targets' => 'The urls of the wikis to push to. Delimitered by |',
374374 );
375375 }
376376
377377 public function getDescription() {
378378 return array(
379 - 'Pushes page contnet to other wikis.'
 379+ 'Pushes the content of one ore more pages to one or more target wikis.'
380380 );
381381 }
382382
Index: trunk/extensions/Push/api/ApiPushImages.php
@@ -0,0 +1,316 @@
 2+<?php
 3+
 4+/**
 5+ * API module to push images to other MediaWiki wikis.
 6+ *
 7+ * @since 0.5
 8+ *
 9+ * @file ApiPushImages.php
 10+ * @ingroup Push
 11+ *
 12+ * @author Jeroen De Dauw < jeroendedauw@gmail.com >
 13+ */
 14+class ApiPushImages extends ApiBase {
 15+
 16+ /**
 17+ * Associative array containing CookieJar objects (values) to be passed in
 18+ * order to autenticate to the targets (keys).
 19+ *
 20+ * @since 0.5
 21+ *
 22+ * @var array
 23+ */
 24+ protected $cookieJars = array();
 25+
 26+ public function __construct( $main, $action ) {
 27+ parent::__construct( $main, $action );
 28+ }
 29+
 30+ public function execute() {
 31+ global $egPushLoginUser, $egPushLoginPass, $egPushLoginUsers, $egPushLoginPasswords;
 32+
 33+ $params = $this->extractRequestParams();
 34+
 35+ if ( !isset( $params['images'] ) ) {
 36+ $this->dieUsageMsg( array( 'missingparam', 'images' ) );
 37+ }
 38+
 39+ if ( !isset( $params['targets'] ) ) {
 40+ $this->dieUsageMsg( array( 'missingparam', 'targets' ) );
 41+ }
 42+
 43+ foreach ( $params['targets'] as &$target ) {
 44+ $user = false;
 45+ $pass = false;
 46+
 47+ if ( array_key_exists( $target, $egPushLoginUsers ) && array_key_exists( $target, $egPushLoginPasswords ) ) {
 48+ $user = $egPushLoginUsers[$target];
 49+ $pass = $egPushLoginPasswords[$target];
 50+ }
 51+ else if ( $egPushLoginUser != '' && $egPushLoginPass != '' ) {
 52+ $user = $egPushLoginUser;
 53+ $pass = $egPushLoginPass;
 54+ }
 55+
 56+ if ( substr( $target, -1 ) !== '/' ) {
 57+ $target .= '/';
 58+ }
 59+
 60+ $target .= 'api.php';
 61+
 62+ if ( $user !== false ) {
 63+ $this->doLogin( $user, $pass, $target );
 64+ }
 65+ }
 66+
 67+ foreach ( $params['images'] as $image ) {
 68+ $title = Title::newFromText( $image, NS_FILE );
 69+ if ( !is_null( $title ) && $title->getNamespace() == NS_FILE && $title->exists() ) {
 70+ $this->doPush( $title, $params['targets'] );
 71+ }
 72+ }
 73+ }
 74+
 75+ /**
 76+ * Logs in into a target wiki using the provided username and password.
 77+ *
 78+ * @since 0.5
 79+ *
 80+ * @param string $user
 81+ * @param string $password
 82+ * @param string $target
 83+ * @param string $token
 84+ * @param CookieJar $cookie
 85+ */
 86+ protected function doLogin( $user, $password, $target, $token = null, $cookieJar = null ) {
 87+ $requestData = array(
 88+ 'action' => 'login',
 89+ 'format' => 'json',
 90+ 'lgname' => $user,
 91+ 'lgpassword' => $password
 92+ );
 93+
 94+ if ( !is_null( $token ) ) {
 95+ $requestData['lgtoken'] = $token;
 96+ }
 97+
 98+ $req = MWHttpRequest::factory( $target,
 99+ array(
 100+ 'postData' => $requestData,
 101+ 'method' => 'POST',
 102+ 'timeout' => 'default'
 103+ )
 104+ );
 105+
 106+ if ( !is_null( $cookieJar ) ) {
 107+ $req->setCookieJar( $cookieJar );
 108+ }
 109+
 110+ $status = $req->execute();
 111+
 112+ if ( $status->isOK() ) {
 113+ $response = FormatJson::decode( $req->getContent() );
 114+
 115+ if ( property_exists( $response, 'login' )
 116+ && property_exists( $response->login, 'result' ) ) {
 117+
 118+ if ( $response->login->result == 'NeedToken' ) {
 119+ $this->doLogin( $user, $password, $target, $response->login->token, $req->getCookieJar() );
 120+ }
 121+ else if ( $response->login->result == 'Success' ) {
 122+ $this->cookieJars[$target] = $req->getCookieJar();
 123+ }
 124+ else {
 125+ $this->dieUsage( wfMsgExt( 'push-err-authentication', 'parsemag', $target, '' ), 'authentication-failed' );
 126+ }
 127+ }
 128+ else {
 129+ $this->dieUsage( wfMsgExt( 'push-err-authentication', 'parsemag', $target, '' ), 'authentication-failed' );
 130+ }
 131+ }
 132+ else {
 133+ $this->dieUsage( wfMsgExt( 'push-err-authentication', 'parsemag', $target, '' ), 'authentication-failed' );
 134+ }
 135+ }
 136+
 137+ /**
 138+ * Pushes the page content to the target wikis.
 139+ *
 140+ * @since 0.5
 141+ *
 142+ * @param Title $title
 143+ * @param array $targets
 144+ */
 145+ protected function doPush( Title $title, array $targets ) {
 146+ foreach ( $targets as $target ) {
 147+ $token = $this->getEditToken( $title, $target );
 148+
 149+ if ( $token !== false ) {
 150+ $this->pushToTarget( $title, $target, $token );
 151+ }
 152+ }
 153+ }
 154+
 155+ /**
 156+ * Obtains the needed edit token by making an HTTP GET request
 157+ * to the remote wikis API.
 158+ *
 159+ * @since 0.5
 160+ *
 161+ * @param Title $title
 162+ * @param string $target
 163+ *
 164+ * @return string or false
 165+ */
 166+ protected function getEditToken( Title $title, $target ) {
 167+ $requestData = array(
 168+ 'action' => 'query',
 169+ 'format' => 'json',
 170+ 'intoken' => 'edit',
 171+ 'prop' => 'info',
 172+ 'titles' => $title->getFullText(),
 173+ );
 174+
 175+ $parts = array();
 176+
 177+ foreach ( $requestData as $key => $value ) {
 178+ $parts[] = $key . '=' . urlencode( $value );
 179+ }
 180+
 181+ $req = MWHttpRequest::factory( $target . '?' . implode( '&', $parts ),
 182+ array(
 183+ 'method' => 'GET',
 184+ 'timeout' => 'default'
 185+ )
 186+ );
 187+
 188+ if ( array_key_exists( $target, $this->cookieJars ) ) {
 189+ $req->setCookieJar( $this->cookieJars[$target] );
 190+ }
 191+
 192+ $status = $req->execute();
 193+
 194+ $response = $status->isOK() ? FormatJson::decode( $req->getContent() ) : null;
 195+
 196+ $token = false;
 197+
 198+ if ( !is_null( $response )
 199+ && property_exists( $response, 'query' )
 200+ && property_exists( $response->query, 'pages' )
 201+ && count( $response->query->pages ) > 0 ) {
 202+
 203+ foreach ( $response->query->pages as $key => $value ) {
 204+ $first = $key;
 205+ break;
 206+ }
 207+
 208+ if ( property_exists( $response->query->pages->$first, 'edittoken' ) ) {
 209+ $token = $response->query->pages->$first->edittoken;
 210+ }
 211+ elseif ( !is_null( $response ) && property_exists( $response, 'query' ) && property_exists( $response->query, 'error' ) ) {
 212+ $this->dieUsage( $response->query->error->message, 'token-request-failed' );
 213+ }
 214+ else {
 215+ $this->dieUsage( wfMsg( 'push-special-err-token-failed' ), 'token-request-failed' );
 216+ }
 217+ }
 218+ else {
 219+ $this->dieUsage( wfMsg( 'push-special-err-token-failed' ), 'token-request-failed' );
 220+ }
 221+
 222+ return $token;
 223+ }
 224+
 225+ /**
 226+ * Pushes the image to the specified wiki.
 227+ *
 228+ * @since 0.5
 229+ *
 230+ * @param Title $title
 231+ * @param string $target
 232+ * @param string $token
 233+ */
 234+ protected function pushToTarget( Title $title, $target, $token ) {
 235+ $imagePage = new ImagePage( $title );
 236+
 237+ $requestData = array(
 238+ 'action' => 'upload',
 239+ 'format' => 'json',
 240+ 'token' => $token,
 241+ 'url' => $imagePage->getDisplayedFile()->getFullUrl(),
 242+ 'filename' => $title->getText(),
 243+ 'ignorewarnings' => '1'
 244+ );
 245+
 246+ $req = MWHttpRequest::factory( $target,
 247+ array(
 248+ 'method' => 'POST',
 249+ 'timeout' => 'default',
 250+ 'postData' => $requestData,
 251+ )
 252+ );
 253+
 254+ if ( array_key_exists( $target, $this->cookieJars ) ) {
 255+ $req->setCookieJar( $this->cookieJars[$target] );
 256+ }
 257+
 258+ $status = $req->execute();
 259+
 260+ if ( $status->isOK() ) {
 261+ $this->getResult()->addValue(
 262+ null,
 263+ null,
 264+ FormatJson::decode( $req->getContent() )
 265+ );
 266+ }
 267+ else {
 268+ // TODO
 269+ }
 270+ }
 271+
 272+ public function getAllowedParams() {
 273+ return array(
 274+ 'images' => array(
 275+ ApiBase::PARAM_TYPE => 'string',
 276+ ApiBase::PARAM_ISMULTI => true,
 277+ //ApiBase::PARAM_REQUIRED => true,
 278+ ),
 279+ 'targets' => array(
 280+ ApiBase::PARAM_TYPE => 'string',
 281+ ApiBase::PARAM_ISMULTI => true,
 282+ //ApiBase::PARAM_REQUIRED => true,
 283+ ),
 284+ );
 285+ }
 286+
 287+ public function getParamDescription() {
 288+ return array(
 289+ 'images' => 'The names of the images to push. Delimitered by |',
 290+ 'targets' => 'The urls of the wikis to push to. Delimitered by |',
 291+ );
 292+ }
 293+
 294+ public function getDescription() {
 295+ return array(
 296+ 'Pushes the content of one ore more pages to one or more target wikis.'
 297+ );
 298+ }
 299+
 300+ public function getPossibleErrors() {
 301+ return array_merge( parent::getPossibleErrors(), array(
 302+ array( 'missingparam', 'images' ),
 303+ array( 'missingparam', 'targets' ),
 304+ ) );
 305+ }
 306+
 307+ protected function getExamples() {
 308+ return array(
 309+ 'api.php?action=pushimages&images=File:Foo.bar&targets=http://en.wikipedia.org/w',
 310+ );
 311+ }
 312+
 313+ public function getVersion() {
 314+ return __CLASS__ . ': $Id$';
 315+ }
 316+
 317+}
Property changes on: trunk/extensions/Push/api/ApiPushImages.php
___________________________________________________________________
Added: svn:keys
1318 + Id
Added: svn:eol-style
2319 + native
Added: svn:keywords
3320 + Id

Follow-up revisions

RevisionCommit summaryAuthorDate
r78301Follow up to r78296 - improved error handlingjeroendedauw13:15, 13 December 2010

Status & tagging log