setId( $id );
return $obj->exists()
? $obj
: null;
}
/**
* Instantiate a brand new captcha, never seen before.
*
* @return Captcha
*/
public final static function newRandom(){
$obj = self::factory();
$obj->generateNew();
return $obj;
}
/**
* Protected constructor - use only the factory methods above to instantiate captchas,
* or you may end up with the wrong type of object
*/
protected function __construct(){}
/**
* Get the captcha Id
*
* @return String
*/
public function getId(){
return $this->id;
}
/**
* Set the Id internally. Don't include wierd things like entities or characters that
* need to be HTML-escaped, you'll just be creating more work and pain for yourself...
*
* @param $id String
*/
protected function setId( $id ){
$this->id = $id;
}
/**
* Initialise $this->info etc with information needed to make this object a new,
* (ideally) never-seen-before captcha. Implementations should not save the data in
* the store in this function, as the captcha may not ever be used.
*
* @return Array of captcha info
*/
# FIXME: detail
protected abstract function generateNew();
/**
* Save a generated captcha in storage somewhere where it won't be lost between
* requests. A random ID is used so legit users can make edits in multiple tabs
* or windows without being unnecessarily hobbled by a serial order requirement.
*/
protected function store() {
// Assign random index if we're not udpating
if ( !isset( $this->info['index'] ) ) {
if( !$this->getId() ){
$this->setId( strval( mt_rand() ) );
}
$this->info['index'] = $this->getId();
}
CaptchaStore::get()->store( $this->info['index'], $this->info );
}
/**
* Fetch the data for this captcha from the CaptchaStore. This requires $this->id
* to be set.
*
* @return Array|Bool: Array of info, or false if missing
*/
protected function retrieve() {
if( $this->getId() === null ){
return null;
}
if( $this->info === null ){
$this->info = CaptchaStore::get()->retrieve( $this->getId() );
$this->exists = $this->info !== false;
}
return $this->info;
}
/**
* Clear the information about this captcha from the CaptchaStore, so it cannot
* be reused at a later date.
*/
protected function delete() {
if( $this->getId() !== null ){
CaptchaStore::get()->clear( $this->getId() );
}
}
/**
* Whether this captcha exists. $this->setId() must have been called from some context
*
* @return Bool
*/
public function exists(){
if( $this->exists === null ){
$this->retrieve();
}
return $this->exists;
}
/**
* Load some data from a WebRequest. Implementations must load all data they need
* from the request in this function, they must not use the global $wgRequest, as
* in the post-1.18 environment they may not necessarily be the same.
*
* @param $request WebRequest
* @param $field HTMLCaptchaField will be passed if the captcha is part of an HTMLForm
*/
public abstract function loadFromRequest( WebRequest $request, HTMLCaptchaField $field = null );
/**
* Return the data that would be needed to pass the captcha challenge through the API.
* Implementations must return an array with at least the following parameters:
* 'type' - a unique description of the type of challenge. This could be
* the class name
* 'mime' - the MIME type of the challenge
* 'id' - the captcha Id produced by getId()
* Implementations should document how the user should use the provided data to answer
* the captcha.
*
* Implementations may return False to indicate that it is not possible to represent
* the challenge via the API. API actions protected by such a captcha will be disabled.
*
* @return Array|Bool
*/
public abstract function getApiParams();
/**
* Return the HTML which will be placed in the 'input' table cell of an HTMLForm.
* Implementations must include input fields which will perpetuate the captcha Id and
* any special data, as well as providing a means for the user to answer the captcha.
* Implementations should not include any help or label text, as these will be set in
* the label-message and help-message attributes of the HTMLCaptchafield.
* Implementations should honour the options set in the HTMLFormField such as
* $field->mName and $field->mReadonly.
*
* @param $field HTMLCaptchaField
* @return String raw HTML
*/
public abstract function getFormHTML( HTMLCaptchaField $field );
/**
* Return the HTML which will be used in legacy forms which do not implement HTMLForm
* Implementations must include input fields which will perpetuate the captcha Id and
* any other necessary data, as well as providing a means for the user to answer the
* captcha, and any relevant descriptions and instructions.
*
* @return String raw HTML
*/
public abstract function getFreeflowHTML();
/**
* Using the parameters loaded from the web request, check the captcha, maybe delete
* it if that's desirable, do any other necessary cleanup, and return Bool
* @return Bool whether the captcha was successfully answered
*/
public abstract function checkCaptcha();
}
class SimpleCaptcha {
function getCaptcha() {
$a = mt_rand( 0, 100 );
$b = mt_rand( 0, 10 );
/* Minus sign is used in the question. UTF-8,
since the api uses text/plain, not text/html */
$op = mt_rand( 0, 1 ) ? '+' : '−';
$test = "$a $op $b";
$answer = ( $op == '+' ) ? ( $a + $b ) : ( $a - $b );
return array( 'question' => $test, 'answer' => $answer );
}
function addCaptchaAPI( &$resultArr ) {
$captcha = $this->getCaptcha();
$index = $this->storeCaptcha( $captcha );
$resultArr['captcha']['type'] = 'simple';
$resultArr['captcha']['mime'] = 'text/plain';
$resultArr['captcha']['id'] = $index;
$resultArr['captcha']['question'] = $captcha['question'];
}
/**
* Insert a captcha prompt into the edit form.
* This sample implementation generates a simple arithmetic operation;
* it would be easy to defeat by machine.
*
* Override this!
*
* @return string HTML
*/
function getForm() {
$captcha = $this->getCaptcha();
$index = $this->storeCaptcha( $captcha );
return "
= " .
Xml::element( 'input', array(
'name' => 'wpCaptchaWord',
'id' => 'wpCaptchaWord',
'tabindex' => 1 ) ) . // tab in before the edit textarea
"
\n" .
Xml::element( 'input', array(
'type' => 'hidden',
'name' => 'wpCaptchaId',
'id' => 'wpCaptchaId',
'value' => $index ) );
}
/**
* Insert the captcha prompt into an edit form.
* @param OutputPage $out
*/
function editCallback( &$out ) {
$out->addWikiText( $this->getMessage( $this->action ) );
$out->addHTML( $this->getForm() );
}
/**
* Show a message asking the user to enter a captcha on edit
* The result will be treated as wiki text
*
* @param $action Action being performed
* @return string
*/
function getMessage( $action ) {
$name = 'captcha-' . $action;
$text = wfMsg( $name );
# Obtain a more tailored message, if possible, otherwise, fall back to
# the default for edits
return wfEmptyMsg( $name, $text ) ? wfMsg( 'captcha-edit' ) : $text;
}
/**
* Inject whazawhoo
* @fixme if multiple thingies insert a header, could break
* @param $form HTMLForm
* @return bool true to keep running callbacks
*/
function injectEmailUser( &$form ) {
global $wgCaptchaTriggers, $wgOut, $wgUser;
if ( $wgCaptchaTriggers['sendemail'] ) {
if ( $wgUser->isAllowed( 'skipcaptcha' ) ) {
wfDebug( "ConfirmEdit: user group allows skipping captcha on email sending\n" );
return true;
}
$form->addFooterText(
"
\n" );
}
return true;
}
/**
* Inject a captcha into the user login form after a failed
* password attempt as a speedbump for mass attacks.
* @fixme if multiple thingies insert a header, could break
* @param $template QuickTemplate
* @return bool true to keep running callbacks
*/
function injectUserLogin( &$template ) {
if ( $this->isBadLoginTriggered() ) {
global $wgOut;
$template->set( 'header',
"