diff --git a/ApiFancyCaptchaReload.php b/ApiFancyCaptchaReload.php new file mode 100644 index 000000000..1de1947da --- /dev/null +++ b/ApiFancyCaptchaReload.php @@ -0,0 +1,34 @@ +getCaptchaIndex(); + + $result = $this->getResult(); + $result->addValue( null, $this->getModuleName(), array ( 'index' => $captchaIndex ) ); + return true; + } + + public function getDescription() { + return 'Get a new FancyCaptcha.'; + } + + public function getAllowedParams() { + return array(); + } + + public function getParamDescription() { + return array(); + } + + public function getExamples() { + return array( 'api.php?action=fancycaptchareload&format=xml' ); + } +} diff --git a/Asirra.class.php b/Asirra.class.php index cbe51e262..bd58eb3a0 100644 --- a/Asirra.class.php +++ b/Asirra.class.php @@ -17,7 +17,7 @@ class Asirra extends SimpleCaptcha { function getForm() { global $wgOut; - $wgOut->addModules( 'ext.confirmedit.asirra' ); + $wgOut->addModules( 'ext.confirmEdit.asirra' ); $js = Html::linkedScript( $this->asirra_clientscript ); $message = Xml::encodeJsVar( wfMessage( 'asirra-createaccount-fail' )->plain() ); diff --git a/Asirra.php b/Asirra.php index ff4cee9a6..923b0b9e8 100644 --- a/Asirra.php +++ b/Asirra.php @@ -24,17 +24,17 @@ if ( !defined( 'MEDIAWIKI' ) ) { exit; } -$dir = __DIR__ . '/'; -require_once( "$dir/ConfirmEdit.php" ); +$dir = __DIR__; +require_once( $dir . '/ConfirmEdit.php' ); $wgCaptchaClass = 'Asirra'; -$wgExtensionMessagesFiles['Asirra'] = "$dir/Asirra.i18n.php"; -$wgAutoloadClasses['Asirra'] = "$dir/Asirra.class.php"; +$wgExtensionMessagesFiles['Asirra'] = $dir . '/Asirra.i18n.php'; +$wgAutoloadClasses['Asirra'] = $dir . '/Asirra.class.php'; -$wgResourceModules['ext.confirmedit.asirra'] = array( - 'localBasePath' => $dir, - 'remoteExtPath' => 'ConfirmEdit', - 'scripts' => 'ext.confirmedit.asirra.js', +$wgResourceModules['ext.confirmEdit.asirra'] = array( + 'localBasePath' => $dir . '/resources', + 'remoteExtPath' => 'ConfirmEdit/resources', + 'scripts' => 'ext.confirmEdit.asirra.js', 'messages' => array( 'asirra-failed', ), diff --git a/FancyCaptcha.class.php b/FancyCaptcha.class.php index 45c48d2aa..02ad777c5 100644 --- a/FancyCaptcha.class.php +++ b/FancyCaptcha.class.php @@ -89,30 +89,40 @@ class FancyCaptcha extends SimpleCaptcha { * Insert the captcha prompt into the edit form. */ function getForm() { - $info = $this->pickImage(); - if ( !$info ) { - throw new MWException( "Ran out of captcha images" ); - } + global $wgOut, $wgExtensionAssetsPath, $wgEnableAPI; - // Generate a random key for use of this captcha image in this session. - // This is needed so multiple edits in separate tabs or windows can - // go through without extra pain. - $index = $this->storeCaptcha( $info ); - - wfDebug( "Captcha id $index using hash ${info['hash']}, salt ${info['salt']}.\n" ); + // Uses addModuleStyles so it is loaded when JS is disabled. + $wgOut->addModuleStyles( 'ext.confirmEdit.fancyCaptcha.styles' ); $title = SpecialPage::getTitleFor( 'Captcha', 'image' ); + $index = $this->getCaptchaIndex(); - return "

" . + if ( $wgEnableAPI ) { + $reloadText = wfMessage( 'fancycaptcha-reload-text' )->text(); + + // Loaded only if JS is enabled + $wgOut->addModules( 'ext.confirmEdit.fancyCaptcha' ); + + $captchaReload = "" . Html::element( 'img', array( + 'class' => 'fancycaptcha-reload-button', + 'src' => $wgExtensionAssetsPath . '/ConfirmEdit/images/fancycaptcha-reload-icon.png', + 'alt' => wfMessage( 'fancycaptcha-reload-button' )->text(), + 'title' => $reloadText ) ) . + Html::rawElement( 'small', array( + 'class' => 'fancycaptcha-reload-text' ), + $reloadText ) . + "\n"; + } else { + $captchaReload = ''; + } + + return "

" . + Html::element( 'img', array( + 'class' => 'fancycaptcha-image', 'src' => $title->getLocalUrl( 'wpCaptchaId=' . urlencode( $index ) ), 'alt' => '' ) ) . - "

\n" . - Html::element( 'input', array( - 'type' => 'hidden', - 'name' => 'wpCaptchaId', - 'id' => 'wpCaptchaId', - 'value' => $index ) ) . + "
\n" . '

' . Html::element( 'label', array( 'for' => 'wpCaptchaWord', @@ -125,7 +135,32 @@ class FancyCaptcha extends SimpleCaptcha { 'autocapitalize' => 'off', 'required' => 'required', 'tabindex' => 1 ) ) . // tab in before the edit textarea - "

\n"; + $captchaReload . + Html::element( 'input', array( + 'type' => 'hidden', + 'name' => 'wpCaptchaId', + 'id' => 'wpCaptchaId', + 'value' => $index ) ) . + "

\n" . + "
\n";; + } + + /** + * Get captcha index key + * @return string captcha ID key + */ + function getCaptchaIndex() { + $info = $this->pickImage(); + if ( !$info ) { + throw new MWException( "Ran out of captcha images" ); + } + + // Generate a random key for use of this captcha image in this session. + // This is needed so multiple edits in separate tabs or windows can + // go through without extra pain. + $index = $this->storeCaptcha( $info ); + + return $index; } /** diff --git a/FancyCaptcha.i18n.php b/FancyCaptcha.i18n.php index 66143f7a2..52a192260 100644 --- a/FancyCaptcha.i18n.php +++ b/FancyCaptcha.i18n.php @@ -19,6 +19,8 @@ To help protect against automated spam, please enter the words that appear below 'fancycaptcha-create' => 'To create the page, please enter the words that appear below in the box ([[Special:Captcha/help|more info]]):', 'fancycaptcha-edit' => 'To edit this page, please enter the words that appear below in the box ([[Special:Captcha/help|more info]]):', 'fancycaptcha-sendemail' => 'To help protect against automated spamming, please enter the words that appear below in the box ([[Special:Captcha/help|more info]]):', + 'fancycaptcha-reload-button' => 'Refresh', + 'fancycaptcha-reload-text' => 'Refresh', ); /** Message documentation (Message documentation) @@ -36,6 +38,8 @@ $messages['qqq'] = array( 'fancycaptcha-create' => '{{Related|ConfirmEdit-create}}', 'fancycaptcha-edit' => '{{Related|ConfirmEdit-edit}}', 'fancycaptcha-sendemail' => '{{Related|ConfirmEdit-sendemail}}', + 'fancycaptcha-reload-button' => 'Alternative (alt) text for reload button to get a new FancyCaptcha image.', + 'fancycaptcha-reload-text' => 'Prompts a click to get a new FancyCaptcha image. Used as regular element text and title text of button', ); /** Gheg Albanian (Gegë) diff --git a/FancyCaptcha.php b/FancyCaptcha.php index 69e3be012..46002e22e 100644 --- a/FancyCaptcha.php +++ b/FancyCaptcha.php @@ -58,3 +58,19 @@ $wgCaptchaDeleteOnSolve = false; $wgExtensionMessagesFiles['FancyCaptcha'] = $dir . '/FancyCaptcha.i18n.php'; $wgAutoloadClasses['FancyCaptcha'] = $dir . '/FancyCaptcha.class.php'; + +$wgResourceModules['ext.confirmEdit.fancyCaptcha.styles'] = array( + 'localBasePath' => $dir . '/resources', + 'remoteExtPath' => 'ConfirmEdit/resources', + 'styles' => 'ext.confirmEdit.fancyCaptcha.css', +); + +$wgResourceModules['ext.confirmEdit.fancyCaptcha'] = array( + 'localBasePath' => $dir . '/resources', + 'remoteExtPath' => 'ConfirmEdit/resources', + 'scripts' => 'ext.confirmEdit.fancyCaptcha.js', + 'dependencies' => 'mediawiki.api', +); + +$wgAutoloadClasses['ApiFancyCaptchaReload'] = $dir . '/ApiFancyCaptchaReload.php'; +$wgAPIModules['fancycaptchareload'] = 'ApiFancyCaptchaReload'; diff --git a/images/ajax-loader-10x10.gif b/images/ajax-loader-10x10.gif new file mode 100644 index 000000000..772e9a555 Binary files /dev/null and b/images/ajax-loader-10x10.gif differ diff --git a/images/fancycaptcha-reload-icon.png b/images/fancycaptcha-reload-icon.png new file mode 100644 index 000000000..6cbde303c Binary files /dev/null and b/images/fancycaptcha-reload-icon.png differ diff --git a/ext.confirmedit.asirra.js b/resources/ext.confirmEdit.asirra.js similarity index 100% rename from ext.confirmedit.asirra.js rename to resources/ext.confirmEdit.asirra.js diff --git a/resources/ext.confirmEdit.fancyCaptcha.css b/resources/ext.confirmEdit.fancyCaptcha.css new file mode 100644 index 000000000..5115957aa --- /dev/null +++ b/resources/ext.confirmEdit.fancyCaptcha.css @@ -0,0 +1,38 @@ +.fancycaptcha-wrapper { + display: table; + background-color: #FFF; +} + +/* Prevents the size of the container from changing, affecting page +layout, for normal CAPTCHA sizes. */ +.fancycaptcha-image-container { + min-height: 95px; +} + +.fancycaptcha-reload { + padding: 0 5px; +} + +.client-nojs .fancycaptcha-reload { + display: none; +} + +.fancycaptcha-reload-button { + padding: 5px; +} + +.fancycaptcha-reload-button:hover { + cursor: pointer; + cursor: hand; +} + +.fancycaptcha-reload-text { + color: #0645AD; + display: inline !important; +} + +.fancycaptcha-reload-text:hover { + text-decoration: underline; + cursor: pointer; + cursor: hand; +} diff --git a/resources/ext.confirmEdit.fancyCaptcha.js b/resources/ext.confirmEdit.fancyCaptcha.js new file mode 100644 index 000000000..7640df3e6 --- /dev/null +++ b/resources/ext.confirmEdit.fancyCaptcha.js @@ -0,0 +1,40 @@ +( function ( $, mw ) { + var api = new mw.Api(); + $( document ).on( 'click', '.fancycaptcha-reload', function () { + var staticImageDirectory = mw.config.get( 'wgExtensionAssetsPath' ) + '/ConfirmEdit/images/', + $this = $( this ), reloadButtonImage, captchaImage; + + reloadButtonImage = $this + .find( '.fancycaptcha-reload-button' ) + .attr( 'src', staticImageDirectory + 'ajax-loader-10x10.gif' ); + + captchaImage = $( '.fancycaptcha-image' ); + + // AJAX request to get captcha index key + api.post( { + action: 'fancycaptchareload', + format: 'xml' + }, { + dataType: 'xml' + } ) + .done( function ( xmldata ) { + var imgSrc, captchaIndex; + captchaIndex = $( xmldata ).find( 'fancycaptchareload' ).attr( 'index' ); + if ( typeof captchaIndex === 'string' ) { + // replace index key with a new one for captcha image + imgSrc = captchaImage.attr( 'src' ) + .replace( /(wpCaptchaId=)\w+/, '$1' + captchaIndex ); + captchaImage.attr( 'src', imgSrc ); + + // replace index key with a new one for hidden tag + $( '#wpCaptchaId' ).val( captchaIndex ); + $( '#wpCaptchaWord' ).val( '' ).focus(); + } + } ) + .always( function () { + reloadButtonImage.attr( 'src', staticImageDirectory + 'fancycaptcha-reload-icon.png' ); + } ); + + return false; + } ); +} )( jQuery, mediaWiki );