Index: trunk/extensions/ConfirmEdit/HTMLCaptchaField.php |
— | — | @@ -0,0 +1,84 @@ |
| 2 | +<?php |
| 3 | +/** |
| 4 | + * HTMLFormField for inserting Captchas into a form. |
| 5 | + * |
| 6 | + * This program is free software; you can redistribute it and/or modify |
| 7 | + * it under the terms of the GNU General Public License as published by |
| 8 | + * the Free Software Foundation; either version 2 of the License, or |
| 9 | + * (at your option) any later version. |
| 10 | + * |
| 11 | + * This program is distributed in the hope that it will be useful, |
| 12 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 13 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 14 | + * GNU General Public License for more details. |
| 15 | + * |
| 16 | + * You should have received a copy of the GNU General Public License along |
| 17 | + * with this program; if not, write to the Free Software Foundation, Inc., |
| 18 | + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
| 19 | + * http://www.gnu.org/copyleft/gpl.html |
| 20 | + * |
| 21 | + * @class |
| 22 | + */ |
| 23 | +class HTMLCaptchaField extends HTMLFormField { |
| 24 | + |
| 25 | + /** |
| 26 | + * @var Captcha |
| 27 | + */ |
| 28 | + private $captcha; |
| 29 | + |
| 30 | + public $prefix = ''; |
| 31 | + |
| 32 | + /** |
| 33 | + * @var Bool|Array |
| 34 | + */ |
| 35 | + private $validationResult; |
| 36 | + |
| 37 | + public function __construct( $params ){ |
| 38 | + parent::__construct( $params ); |
| 39 | + |
| 40 | + // For differentiating the type of form, mainly |
| 41 | + if( isset( $params['prefix'] ) ){ |
| 42 | + $this->prefix = $params['prefix']; |
| 43 | + } |
| 44 | + } |
| 45 | + |
| 46 | + /** |
| 47 | + * Get the captcha body. Don't include any of the surrounding table cells/rows |
| 48 | + * |
| 49 | + * @param $value String |
| 50 | + * @return String |
| 51 | + */ |
| 52 | + public function getInputHTML( $value ){ |
| 53 | + # TODO |
| 54 | + } |
| 55 | + |
| 56 | + public function validate( $data, $alldata ){ |
| 57 | + // We sent back the exists status of the captcha before. If it *doesn't* exist |
| 58 | + // we actually want to validate this as true, because we don't want an angry red |
| 59 | + // error message, just for the user to put the captcha in again |
| 60 | + if( $data === false ){ |
| 61 | + return true; |
| 62 | + } |
| 63 | + |
| 64 | + |
| 65 | + } |
| 66 | + |
| 67 | + /** |
| 68 | + * @param $request WebRequest |
| 69 | + * @return void |
| 70 | + */ |
| 71 | + public function loadDataFromRequest( $request ){ |
| 72 | + $this->captcha = Captcha::factory(); |
| 73 | + $this->captcha->loadFromRequest( $request, $this ); |
| 74 | + if( !$this->captcha->exists() ){ |
| 75 | + // The captcha doesn't exist; probably because it's already been used and |
| 76 | + // then deleted for security. Load the field up with a new captcha which |
| 77 | + // will be shown to the user when the validation of said new object fails |
| 78 | + $this->captcha = Captcha::newRandom(); |
| 79 | + } |
| 80 | + |
| 81 | + // This will be useful as the difference between "the captcha doesn't exist" and |
| 82 | + // "you answered the captcha wrongly" |
| 83 | + return $this->captcha->exists(); |
| 84 | + } |
| 85 | +} |
\ No newline at end of file |
Property changes on: trunk/extensions/ConfirmEdit/HTMLCaptchaField.php |
___________________________________________________________________ |
Added: svn:eol-style |
1 | 86 | + native |
Index: trunk/extensions/ConfirmEdit/Captcha.php |
— | — | @@ -1,5 +1,221 @@ |
2 | 2 | <?php |
3 | 3 | |
| 4 | +/** |
| 5 | + * Object encapsulating a captcha process. The captcha has two elements: it must be able |
| 6 | + * to generate a frontend HTML representation of itself which can be presented to the user, |
| 7 | + * which provides inputs for users to provide their interpretation of the captcha; and it |
| 8 | + * must be able to retrieve that data from a subsequently-submitted request and validate |
| 9 | + * whether the user got the data correct. |
| 10 | + */ |
| 11 | +abstract class Captcha { |
| 12 | + |
| 13 | + /** |
| 14 | + * @var String |
| 15 | + */ |
| 16 | + protected $id; |
| 17 | + |
| 18 | + /** |
| 19 | + * Information about the captcha, in array form |
| 20 | + * @var $info Array |
| 21 | + */ |
| 22 | + protected $info; |
| 23 | + |
| 24 | + /** |
| 25 | + * Whether this captcha exists in the storage |
| 26 | + * @var Bool |
| 27 | + */ |
| 28 | + protected $exists; |
| 29 | + |
| 30 | + /** |
| 31 | + * Generate a new empty Captcha. This is guaranteed to return a Captcha object if it |
| 32 | + * does not throw an exception |
| 33 | + * |
| 34 | + * @return Captcha subclass |
| 35 | + */ |
| 36 | + public final static function factory() { |
| 37 | + global $wgCaptchaClass; |
| 38 | + $obj = new $wgCaptchaClass; |
| 39 | + if ( $obj instanceof Captcha ) { |
| 40 | + return $obj; |
| 41 | + } else { |
| 42 | + throw new MWException( "Invalid Captcha class $wgCaptchaClass, must extend Captcha" ); |
| 43 | + } |
| 44 | + } |
| 45 | + |
| 46 | + /** |
| 47 | + * Instantiate a new Captcha object for a given Id |
| 48 | + * |
| 49 | + * @param $id Int |
| 50 | + * @return Captcha |
| 51 | + */ |
| 52 | + public final static function newFromId( $id ){ |
| 53 | + $obj = self::factory(); |
| 54 | + $obj->setId( $id ); |
| 55 | + return $obj->exists() |
| 56 | + ? $obj |
| 57 | + : null; |
| 58 | + } |
| 59 | + |
| 60 | + /** |
| 61 | + * Instantiate a brand new captcha, never seen before. |
| 62 | + * |
| 63 | + * @return Captcha |
| 64 | + */ |
| 65 | + public final static function newRandom(){ |
| 66 | + $obj = self::factory(); |
| 67 | + $obj->generateNew(); |
| 68 | + return $obj; |
| 69 | + } |
| 70 | + |
| 71 | + /** |
| 72 | + * Protected constructor - use only the factory methods above to instantiate captchas, |
| 73 | + * or you may end up with the wrong type of object |
| 74 | + */ |
| 75 | + protected function __construct(){} |
| 76 | + |
| 77 | + /** |
| 78 | + * Get the captcha Id |
| 79 | + * |
| 80 | + * @return String |
| 81 | + */ |
| 82 | + public function getId(){ |
| 83 | + return $this->id; |
| 84 | + } |
| 85 | + |
| 86 | + /** |
| 87 | + * Set the Id internally. Don't include wierd things like entities or characters that |
| 88 | + * need to be HTML-escaped, you'll just be creating more work and pain for yourself... |
| 89 | + * |
| 90 | + * @param $id String |
| 91 | + */ |
| 92 | + protected function setId( $id ){ |
| 93 | + $this->id = $id; |
| 94 | + } |
| 95 | + |
| 96 | + /** |
| 97 | + * Initialise $this->info etc with information needed to make this object a new, |
| 98 | + * (ideally) never-seen-before captcha. Implementations should not save the data in |
| 99 | + * the store in this function, as the captcha may not ever be used. |
| 100 | + * |
| 101 | + * @return Array of captcha info |
| 102 | + */ |
| 103 | + # FIXME: detail |
| 104 | + protected abstract function generateNew(); |
| 105 | + |
| 106 | + /** |
| 107 | + * Save a generated captcha in storage somewhere where it won't be lost between |
| 108 | + * requests. A random ID is used so legit users can make edits in multiple tabs |
| 109 | + * or windows without being unnecessarily hobbled by a serial order requirement. |
| 110 | + */ |
| 111 | + protected function store() { |
| 112 | + // Assign random index if we're not udpating |
| 113 | + if ( !isset( $this->info['index'] ) ) { |
| 114 | + if( !$this->getId() ){ |
| 115 | + $this->setId( strval( mt_rand() ) ); |
| 116 | + } |
| 117 | + $this->info['index'] = $this->getId(); |
| 118 | + } |
| 119 | + CaptchaStore::get()->store( $this->info['index'], $this->info ); |
| 120 | + } |
| 121 | + |
| 122 | + /** |
| 123 | + * Fetch the data for this captcha from the CaptchaStore. This requires $this->id |
| 124 | + * to be set. |
| 125 | + * |
| 126 | + * @return Array|Bool: Array of info, or false if missing |
| 127 | + */ |
| 128 | + protected function retrieve() { |
| 129 | + if( $this->getId() === null ){ |
| 130 | + return null; |
| 131 | + } |
| 132 | + if( $this->info === null ){ |
| 133 | + $this->info = CaptchaStore::get()->retrieve( $this->getId() ); |
| 134 | + $this->exists = $this->info !== false; |
| 135 | + } |
| 136 | + return $this->info; |
| 137 | + } |
| 138 | + |
| 139 | + /** |
| 140 | + * Clear the information about this captcha from the CaptchaStore, so it cannot |
| 141 | + * be reused at a later date. |
| 142 | + */ |
| 143 | + protected function delete() { |
| 144 | + if( $this->getId() !== null ){ |
| 145 | + CaptchaStore::get()->clear( $this->getId() ); |
| 146 | + } |
| 147 | + } |
| 148 | + |
| 149 | + /** |
| 150 | + * Whether this captcha exists. $this->setId() must have been called from some context |
| 151 | + * |
| 152 | + * @return Bool |
| 153 | + */ |
| 154 | + public function exists(){ |
| 155 | + if( $this->exists === null ){ |
| 156 | + $this->retrieve(); |
| 157 | + } |
| 158 | + return $this->exists; |
| 159 | + } |
| 160 | + |
| 161 | + /** |
| 162 | + * Load some data from a WebRequest. Implementations must load all data they need |
| 163 | + * from the request in this function, they must not use the global $wgRequest, as |
| 164 | + * in the post-1.18 environment they may not necessarily be the same. |
| 165 | + * |
| 166 | + * @param $request WebRequest |
| 167 | + * @param $field HTMLCaptchaField will be passed if the captcha is part of an HTMLForm |
| 168 | + */ |
| 169 | + public abstract function loadFromRequest( WebRequest $request, HTMLCaptchaField $field = null ); |
| 170 | + |
| 171 | + /** |
| 172 | + * Return the data that would be needed to pass the captcha challenge through the API. |
| 173 | + * Implementations must return an array with at least the following parameters: |
| 174 | + * 'type' - a unique description of the type of challenge. This could be |
| 175 | + * the class name |
| 176 | + * 'mime' - the MIME type of the challenge |
| 177 | + * 'id' - the captcha Id produced by getId() |
| 178 | + * Implementations should document how the user should use the provided data to answer |
| 179 | + * the captcha. |
| 180 | + * |
| 181 | + * Implementations may return False to indicate that it is not possible to represent |
| 182 | + * the challenge via the API. API actions protected by such a captcha will be disabled. |
| 183 | + * |
| 184 | + * @return Array|Bool |
| 185 | + */ |
| 186 | + public abstract function getApiParams(); |
| 187 | + |
| 188 | + /** |
| 189 | + * Return the HTML which will be placed in the 'input' table cell of an HTMLForm. |
| 190 | + * Implementations must include input fields which will perpetuate the captcha Id and |
| 191 | + * any special data, as well as providing a means for the user to answer the captcha. |
| 192 | + * Implementations should not include any help or label text, as these will be set in |
| 193 | + * the label-message and help-message attributes of the HTMLCaptchafield. |
| 194 | + * Implementations should honour the options set in the HTMLFormField such as |
| 195 | + * $field->mName and $field->mReadonly. |
| 196 | + * |
| 197 | + * @param $field HTMLCaptchaField |
| 198 | + * @return String raw HTML |
| 199 | + */ |
| 200 | + public abstract function getFormHTML( HTMLCaptchaField $field ); |
| 201 | + |
| 202 | + /** |
| 203 | + * Return the HTML which will be used in legacy forms which do not implement HTMLForm |
| 204 | + * Implementations must include input fields which will perpetuate the captcha Id and |
| 205 | + * any other necessary data, as well as providing a means for the user to answer the |
| 206 | + * captcha, and any relevant descriptions and instructions. |
| 207 | + * |
| 208 | + * @return String raw HTML |
| 209 | + */ |
| 210 | + public abstract function getFreeflowHTML(); |
| 211 | + |
| 212 | + /** |
| 213 | + * Using the parameters loaded from the web request, check the captcha, maybe delete |
| 214 | + * it if that's desirable, do any other necessary cleanup, and return Bool |
| 215 | + * @return Bool whether the captcha was successfully answered |
| 216 | + */ |
| 217 | + public abstract function checkCaptcha(); |
| 218 | +} |
| 219 | + |
4 | 220 | class SimpleCaptcha { |
5 | 221 | |
6 | 222 | /** |
Index: trunk/extensions/ConfirmEdit/ConfirmEdit.php |
— | — | @@ -201,11 +201,13 @@ |
202 | 202 | $wgHooks['APIEditBeforeSave'][] = 'ConfirmEditHooks::confirmEditAPI'; |
203 | 203 | |
204 | 204 | $wgAutoloadClasses['ConfirmEditHooks'] = "$wgConfirmEditIP/ConfirmEditHooks.php"; |
| 205 | +$wgAutoloadClasses['Captcha']= "$wgConfirmEditIP/Captcha.php"; |
205 | 206 | $wgAutoloadClasses['SimpleCaptcha']= "$wgConfirmEditIP/Captcha.php"; |
206 | 207 | $wgAutoloadClasses['CaptchaStore']= "$wgConfirmEditIP/CaptchaStore.php"; |
207 | 208 | $wgAutoloadClasses['CaptchaSessionStore']= "$wgConfirmEditIP/CaptchaStore.php"; |
208 | 209 | $wgAutoloadClasses['CaptchaCacheStore']= "$wgConfirmEditIP/CaptchaStore.php"; |
209 | 210 | $wgAutoloadClasses['CaptchaSpecialPage'] = "$wgConfirmEditIP/ConfirmEditHooks.php"; |
| 211 | +$wgAutoloadClasses['HTMLCaptchaField']= "$wgConfirmEditIP/HTMLCaptchaField.php"; |
210 | 212 | |
211 | 213 | /** |
212 | 214 | * Set up $wgWhitelistRead |