r26937 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r26936‎ | r26937 | r26938 >
Date:02:51, 25 October 2007
Author:aaron
Status:old
Tags:
Comment:
* Kill $wgSaveRejectedAccountReqs
* Allow flagging requests as "held"
* Show reject noticed when viewing a request
* Allow basic attachment mechanism for CVs/resumes and updated pruning to clear the relevant files out
* Use autoupdate hook for schema changes
* Add right that lets users see the IP of account requesters
Modified paths:
  • /trunk/extensions/ConfirmAccount/ConfirmAccount.i18n.php (modified) (history)
  • /trunk/extensions/ConfirmAccount/ConfirmAccount.pg.sql (modified) (history)
  • /trunk/extensions/ConfirmAccount/ConfirmAccount.sql (modified) (history)
  • /trunk/extensions/ConfirmAccount/ConfirmAccount_body.php (modified) (history)
  • /trunk/extensions/ConfirmAccount/SpecialConfirmAccount.php (modified) (history)
  • /trunk/extensions/ConfirmAccount/archives (added) (history)
  • /trunk/extensions/ConfirmAccount/archives/patch-acr_filename.sql (added) (history)

Diff [purge]

Index: trunk/extensions/ConfirmAccount/SpecialConfirmAccount.php
@@ -28,15 +28,22 @@
2929 $wgAutoWelcomeNewUsers = true;
3030 # Make the username of the real name?
3131 $wgUseRealNamesOnly = true;
32 -$wgSaveRejectedAccountReqs = true;
3332 $wgRejectedAccountMaxAge = 7 * 24 * 3600; // One week
3433 # How many requests can an IP make at once?
3534 $wgAccountRequestThrottle = 1;
3635 # Minimum biography specs
3736 $wgAccountRequestMinWords = 50;
3837
 38+# Location of attached files
 39+$wgAllowAccountRequestFiles = true;
 40+$wgAccountRequestExts = array('txt','pdf','doc','latex','rtf','text','wp','wpd' );
 41+$wgFileStore['accountreqs']['directory'] = "{$wgUploadDirectory}/accountreqs";
 42+$wgFileStore['accountreqs']['url'] = null; // Private
 43+$wgFileStore['accountreqs']['hash'] = 3;
 44+
3945 $wgGroupPermissions['*']['createaccount'] = false;
4046 $wgGroupPermissions['bureaucrat']['confirmaccount'] = true;
 47+$wgGroupPermissions['bureaucrat']['requestips'] = true;
4148
4249 # Internationalisation
4350 function efLoadConfirmAccountsMessages() {
@@ -88,3 +95,21 @@
8996 $wgHooks['UserLoginForm'][] = 'efAddRequestLoginText';
9097 # Check for collisions
9198 $wgHooks['AbortNewAccount'][] = 'efCheckIfAccountNameIsPending';
 99+$wgHooks['LoadExtensionSchemaUpdates'][] = 'efConfirmAccountSchemaUpdates';
 100+
 101+function efConfirmAccountSchemaUpdates() {
 102+ global $wgDBtype, $wgExtNewFields, $wgExtPGNewFields;
 103+
 104+ $base = dirname(__FILE__);
 105+ if ($wgDBtype == 'mysql') {
 106+ $wgExtNewFields[] = array('account_requests', 'acr_filename',
 107+ "$base/archives/patch-acr_filename.sql" );
 108+ } else {
 109+ $wgExtPGNewFields[] = array('account_requests', 'acr_filename', "TEXT" );
 110+ $wgExtPGNewFields[] = array('account_requests', 'acr_held', "TIMESTAMPTZ" );
 111+ $wgExtPGNewFields[] = array('account_requests', 'acr_storage_key', "TEXT" );
 112+ $wgExtPGNewFields[] = array('account_requests', 'acr_comment', "TEXT" );
 113+ }
 114+
 115+ return true;
 116+}
Index: trunk/extensions/ConfirmAccount/archives/patch-acr_filename.sql
@@ -0,0 +1,7 @@
 2+-- (c) Aaron Schulz, 2007
 3+
 4+ALTER TABLE /*$wgDBprefix*/account_requests
 5+ ADD acr_filename VARCHAR(255) NULL,
 6+ ADD acr_storage_key VARCHAR(64) NULL,
 7+ ADD acr_held binary(14),
 8+ ADD acr_reason VARCHAR(255) NULL;
Property changes on: trunk/extensions/ConfirmAccount/archives/patch-acr_filename.sql
___________________________________________________________________
Added: svn:eol-style
19 + native
Index: trunk/extensions/ConfirmAccount/ConfirmAccount.sql
@@ -35,6 +35,9 @@
3636 acr_urls mediumblob NOT NULL,
3737 -- IP address
3838 acr_ip VARCHAR(255) NULL default '',
 39+ --Name of attached file (.pdf,.doc,.txt ect...)
 40+ acr_filename VARCHAR(255) NULL,
 41+ acr_storage_key VARCHAR(64) NULL,
3942
4043 -- Timestamp of account registration.
4144 acr_registration char(14) NOT NULL,
@@ -43,8 +46,12 @@
4447 acr_deleted bool NOT NULL,
4548 -- Time of rejection (if rejected)
4649 acr_rejected binary(14),
47 - -- The user who rejected it
 50+ -- Time request was put on hold (if held)
 51+ acr_held binary(14),
 52+ -- The user who rejected/held it
4853 acr_user int unsigned NOT NULL default 0,
 54+ -- Reason
 55+ acr_comment varchar(255) NOT NULL default '',
4956
5057 PRIMARY KEY (acr_id),
5158 UNIQUE KEY (acr_name),
Index: trunk/extensions/ConfirmAccount/ConfirmAccount.pg.sql
@@ -18,9 +18,13 @@
1919 acr_notes TEXT,
2020 acr_urls TEXT,
2121 acr_ip CIDR,
 22+ acr_filename TEXT,
 23+ acr_storage_key TEXT,
2224 acr_deleted BOOL NOT NULL DEFAULT 'false',
2325 acr_rejected TIMESTAMPTZ,
24 - acr_user INTEGER REFERENCES mwuser(user_id) ON DELETE SET NULL
 26+ acr_held TIMESTAMPTZ,
 27+ acr_user INTEGER REFERENCES mwuser(user_id) ON DELETE SET NULL,
 28+ acr_comment TEXT NOT NULL default ''
2529 );
2630
2731 CREATE INDEX acr_deleted_reg ON account_requests (acr_deleted,acr_registration);
Index: trunk/extensions/ConfirmAccount/ConfirmAccount_body.php
@@ -33,6 +33,10 @@
3434 $this->mUsername = $this->mRealName;
3535 else
3636 $this->mUsername = $wgRequest->getText( 'wpUsername' );
 37+ # Attachments...
 38+ $this->initializeUpload( $wgRequest );
 39+ $this->mPrevAttachment = $wgRequest->getText( 'attachment' );
 40+ $this->mForgotAttachment = $wgRequest->getBool( 'forgotAttachment' );
3741 # Other fields...
3842 $this->mEmail = $wgRequest->getText( 'wpEmail' );
3943 $this->mBio = $wgRequest->getText( 'wpBio', '' );
@@ -43,6 +47,8 @@
4448 $emailCode = $wgRequest->getText( 'wpEmailToken' );
4549
4650 if ( $wgRequest->wasPosted() && $wgUser->matchEditToken( $wgRequest->getVal( 'wpEditToken' ) ) ) {
 51+ if( !$this->mPrevAttachment )
 52+ $this->mPrevAttachment = $this->mSrcName;
4753 $this->doSubmit();
4854 } else if( $action == 'confirmemail' ) {
4955 $this->confirmEmailToken( $emailCode );
@@ -51,13 +57,15 @@
5258 }
5359 }
5460
55 - function showForm( $msg='' ) {
 61+ function showForm( $msg='', $forgetFile=0 ) {
5662 global $wgOut, $wgUser, $wgTitle, $wgAuth, $wgUseRealNamesOnly;
5763
 64+ $this->mForgotAttachment = $forgetFile;
 65+
5866 $wgOut->setPagetitle( wfMsgHtml( "requestaccount" ) );
59 - # Output failure message
 67+ # Output failure message if any
6068 if( $msg ) {
61 - $wgOut->addHTML( '<div class="errorbox">' . $msg . '</div><div class="visualClear"></div>' );
 69+ $wgOut->addHTML( '<div class="errorbox">'.$msg.'</div><div class="visualClear"></div>' );
6270 }
6371 # Give notice to users that are logged in
6472 if( $wgUser->getID() ) {
@@ -67,8 +75,8 @@
6876 $wgOut->addWikiText( wfMsgHtml( "requestaccount-text" ) );
6977
7078 $action = $wgTitle->escapeLocalUrl( 'action=submit' );
71 - $form = "<form name='accountrequest' action='$action' method='post'><fieldset>";
72 - $form .= '<legend>' . wfMsgHtml('requestaccount-legend1') . '</legend>';
 79+ $form = "<form name='accountrequest' action='$action' enctype='multipart/form-data' method='post'>";
 80+ $form .= '<fieldset><legend>' . wfMsgHtml('requestaccount-legend1') . '</legend>';
7381 $form .= wfMsgExt( 'requestaccount-acc-text', array('parse') )."\n";
7482 $form .= '<table cellpadding=\'4\'>';
7583 if( $wgUseRealNamesOnly ) {
@@ -99,6 +107,9 @@
100108 $form .= '<legend>' . wfMsgHtml('requestaccount-legend3') . '</legend>';
101109 $form .= wfMsgExt( 'requestaccount-ext-text', array('parse') )."\n";
102110
 111+ $form .= "<p>".wfMsgHtml('requestaccount-attach')." ";
 112+ $form .= Xml::input( 'wpUploadFile', 35, '', array('id' => 'wpUploadFile', 'type' => 'file') )."</p>\n";
 113+
103114 $form .= "<p>".wfMsgHtml('requestaccount-notes')."</p>\n";
104115 $form .= "<p><textarea tabindex='1' name='wpNotes' id='wpNotes' rows='3' cols='80' style='width:100%'>" .
105116 htmlspecialchars($this->mNotes) .
@@ -124,10 +135,12 @@
125136 }
126137 $form .= "<p>".Xml::check( 'wpToS', $this->mToS, array('id' => 'wpToS') ).
127138 ' <label for="wpToS">'.wfMsgExt( 'requestaccount-tos', array('parseinline') )."</label></p>\n";
128 - $form .= Xml::hidden( 'title', $wgTitle->getPrefixedText() )."\n";
 139+ $form .= Xml::hidden( 'title', $wgTitle->getPrefixedUrl() )."\n";
129140 $form .= Xml::hidden( 'wpEditToken', $wgUser->editToken() )."\n";
130 - $form .= "<p>".Xml::submitButton( wfMsgHtml( 'requestaccount-submit') ) . "</p>";
131 - $form .= '</form>';
 141+ $form .= Xml::hidden( 'attachment', $this->mPrevAttachment )."\n";
 142+ $form .= Xml::hidden( 'forgotAttachment', $this->mForgotAttachment )."\n";
 143+ $form .= "<p>".Xml::submitButton( wfMsgHtml( 'requestaccount-submit') )."</p>";
 144+ $form .= '</form>';
132145
133146 $wgOut->addHTML( $form );
134147 }
@@ -184,7 +197,7 @@
185198 # used for more than just username validation
186199 $u->setEmail( $this->mEmail );
187200 $u->setRealName( $this->mRealName );
188 - # Let captchas confirm
 201+ # Let captchas deny request...
189202 global $wgCaptcha;
190203 if( isset($wgCaptcha) ) {
191204 $abortError = '';
@@ -194,10 +207,66 @@
195208 return false;
196209 }
197210 }
 211+ # Per security reasons, file dir cannot be pulled from client,
 212+ # so ask them to resubmit it then...
 213+ global $wgAllowAccountRequestFiles;
 214+ if( $wgAllowAccountRequestFiles && $this->mPrevAttachment && !$this->mSrcName ) {
 215+ # If the user is submitting forgotAttachment as true with no file,
 216+ # then they saw the notice and choose not to re-select the file.
 217+ # Assume that they don't want to send one anymore.
 218+ if( !$this->mForgotAttachment ) {
 219+ $this->mPrevAttachment = '';
 220+ $this->showForm( wfMsgHtml('requestaccount-resub'), 1 );
 221+ return false;
 222+ }
 223+ }
 224+ # Process upload...
 225+ if( $wgAllowAccountRequestFiles && $this->mSrcName ) {
 226+ $ext = explode('.',$this->mSrcName);
 227+ $finalExt = $ext[count($ext)-1];
 228+ # File must have size.
 229+ if( trim( $this->mSrcName ) == '' || empty( $this->mFileSize ) ) {
 230+ $this->mPrevAttachment = '';
 231+ $this->showForm( wfMsgHtml( 'emptyfile' ) );
 232+ return false;
 233+ }
 234+ # Look at the contents of the file; if we can recognize the
 235+ # type but it's corrupt or data of the wrong type, we should
 236+ # probably not accept it.
 237+ global $wgAccountRequestExts;
 238+ if( !in_array($finalExt,$wgAccountRequestExts) ) {
 239+ $this->mPrevAttachment = '';
 240+ $this->showForm( wfMsgHtml( 'requestaccount-exts' ) );
 241+ return false;
 242+ }
 243+ $fileProps = File::getPropsFromPath( $this->mTempPath, $finalExt );
 244+ $veri = $this->verify( $this->mTempPath, $finalExt );
 245+ if( $veri !== true ) {
 246+ $this->mPrevAttachment = '';
 247+ $this->showForm( wfMsgHtml( 'uploadcorrupt' ) );
 248+ return false;
 249+ }
 250+ # Start a transaction, move file from temp to account request directory.
 251+ $transaction = new FSTransaction();
 252+ if( !FileStore::lock() ) {
 253+ wfDebug( __METHOD__.": failed to acquire file store lock, aborting\n" );
 254+ return false;
 255+ }
 256+ $store = FileStore::get( 'accountreqs' );
 257+ $key = FileStore::calculateKey( $this->mTempPath, $finalExt );
 258+
 259+ $transaction->add( $store->insert( $key, $this->mTempPath, FileStore::DELETE_ORIGINAL ) );
 260+ if( $transaction === false ) {
 261+ // Failed to move?
 262+ wfDebug( __METHOD__.": import to file store failed, aborting\n" );
 263+ throw new MWException( "Could not archive and delete file $oldpath" );
 264+ return false;
 265+ }
 266+ }
198267 # Insert into pending requests...
199268 $dbw->begin();
200269 $dbw->insert( 'account_requests',
201 - array(
 270+ array(
202271 'acr_name' => $u->mName,
203272 'acr_email' => $u->mEmail,
204273 'acr_real_name' => $u->mRealName,
@@ -205,6 +274,8 @@
206275 'acr_bio' => $this->mBio,
207276 'acr_notes' => $this->mNotes,
208277 'acr_urls' => $this->mUrls,
 278+ 'acr_filename' => isset($this->mSrcName) ? $this->mSrcName : null,
 279+ 'acr_storage_key' => isset($key) ? $key : null,
209280 'acr_ip' => wfGetIP() // Possible use for spam blocking
210281 ),
211282 __METHOD__
@@ -212,12 +283,18 @@
213284 # Send confirmation, required!
214285 $result = $this->sendConfirmationMail( $u );
215286 if( WikiError::isError( $result ) ) {
 287+ $dbw->rollback(); // Nevermind
 288+ #$transaction->rollback();
216289 $error = wfMsg( 'mailerror', htmlspecialchars( $result->getMessage() ) );
217290 $this->showForm( $error );
218 - $dbw->rollback(); // Nevermind
219291 return false;
220292 }
221293 $dbw->commit();
 294+ if( isset($transaction) ) {
 295+ wfDebug( __METHOD__.": set db items, applying file transactions\n" );
 296+ $transaction->commit();
 297+ FileStore::unlock();
 298+ }
222299 # No request spamming...
223300 # BC: check if isPingLimitable() exists
224301 if( $wgAccountRequestThrottle && ( !method_exists($wgUser,'isPingLimitable') || $wgUser->isPingLimitable() ) ) {
@@ -241,6 +318,101 @@
242319 }
243320
244321 /**
 322+ * Initialize the uploaded file from PHP data
 323+ * @access private
 324+ */
 325+ function initializeUpload( $request ) {
 326+ $this->mTempPath = $request->getFileTempName( 'wpUploadFile' );
 327+ $this->mFileSize = $request->getFileSize( 'wpUploadFile' );
 328+ $this->mSrcName = $request->getFileName( 'wpUploadFile' );
 329+ $this->mRemoveTempFile = false; // PHP will handle this
 330+ }
 331+
 332+ /**
 333+ * Verifies that it's ok to include the uploaded file
 334+ *
 335+ * @param string $tmpfile the full path of the temporary file to verify
 336+ * @param string $extension The filename extension that the file is to be served with
 337+ * @return mixed true of the file is verified, a WikiError object otherwise.
 338+ */
 339+ function verify( $tmpfile, $extension ) {
 340+ #magically determine mime type
 341+ $magic=& MimeMagic::singleton();
 342+ $mime = $magic->guessMimeType($tmpfile,false);
 343+
 344+ #check mime type, if desired
 345+ global $wgVerifyMimeType;
 346+ if ($wgVerifyMimeType) {
 347+
 348+ wfDebug ( "\n\nmime: <$mime> extension: <$extension>\n\n");
 349+ #check mime type against file extension
 350+ if( !$this->verifyExtension( $mime, $extension ) ) {
 351+ return new WikiErrorMsg( 'uploadcorrupt' );
 352+ }
 353+
 354+ #check mime type blacklist
 355+ global $wgMimeTypeBlacklist;
 356+ if( isset($wgMimeTypeBlacklist) && !is_null($wgMimeTypeBlacklist)
 357+ && $this->checkFileExtension( $mime, $wgMimeTypeBlacklist ) ) {
 358+ return new WikiErrorMsg( 'filetype-badmime', htmlspecialchars( $mime ) );
 359+ }
 360+ }
 361+
 362+ wfDebug( __METHOD__.": all clear; passing.\n" );
 363+ return true;
 364+ }
 365+
 366+ /**
 367+ * Checks if the mime type of the uploaded file matches the file extension.
 368+ *
 369+ * @param string $mime the mime type of the uploaded file
 370+ * @param string $extension The filename extension that the file is to be served with
 371+ * @return bool
 372+ */
 373+ function verifyExtension( $mime, $extension ) {
 374+ $magic =& MimeMagic::singleton();
 375+
 376+ if ( ! $mime || $mime == 'unknown' || $mime == 'unknown/unknown' )
 377+ if ( ! $magic->isRecognizableExtension( $extension ) ) {
 378+ wfDebug( __METHOD__.": passing file with unknown detected mime type; " .
 379+ "unrecognized extension '$extension', can't verify\n" );
 380+ return true;
 381+ } else {
 382+ wfDebug( __METHOD__.": rejecting file with unknown detected mime type; ".
 383+ "recognized extension '$extension', so probably invalid file\n" );
 384+ return false;
 385+ }
 386+
 387+ $match = $magic->isMatchingExtension($extension,$mime);
 388+
 389+ if ($match===NULL) {
 390+ wfDebug( __METHOD__.": no file extension known for mime type $mime, passing file\n" );
 391+ return true;
 392+ } elseif ($match===true) {
 393+ wfDebug( __METHOD__.": mime type $mime matches extension $extension, passing file\n" );
 394+
 395+ #TODO: if it's a bitmap, make sure PHP or ImageMagic resp. can handle it!
 396+ return true;
 397+
 398+ } else {
 399+ wfDebug( __METHOD__.": mime type $mime mismatches file extension $extension, rejecting file\n" );
 400+ return false;
 401+ }
 402+ }
 403+
 404+ /**
 405+ * Perform case-insensitive match against a list of file extensions.
 406+ * Returns true if the extension is in the list.
 407+ *
 408+ * @param string $ext
 409+ * @param array $list
 410+ * @return bool
 411+ */
 412+ function checkFileExtension( $ext, $list ) {
 413+ return in_array( strtolower( $ext ), $list );
 414+ }
 415+
 416+ /**
245417 * @private
246418 */
247419 function throttleHit( $limit ) {
@@ -267,7 +439,7 @@
268440 $wgOut->addWikiText( wfMsg( $message ) );
269441 if( !$wgUser->isLoggedIn() ) {
270442 $title = SpecialPage::getTitleFor( 'Userlogin' );
271 - $wgOut->returnToMain( true, $title->getPrefixedText() );
 443+ $wgOut->returnToMain( true, $title->getPrefixedUrl() );
272444 }
273445 } else {
274446 $wgOut->addWikiText( wfMsg( 'confirmemail_error' ) );
@@ -383,6 +555,8 @@
384556 $this->setHeaders();
385557 # A target user
386558 $this->acrID = $wgRequest->getIntOrNull( 'acrid' );
 559+ # Attachments
 560+ $this->file = $wgRequest->getVal( 'file' );
387561 # For renaming to alot for collisions with other local requests
388562 # that were added to some global $wgAuth system first.
389563 $this->mUsername = $wgRequest->getText( 'wpNewName' );
@@ -396,6 +570,8 @@
397571
398572 if ( $wgRequest->wasPosted() && $wgUser->matchEditToken( $wgRequest->getVal( 'wpEditToken' ) ) ) {
399573 $this->doSubmit();
 574+ } else if( $this->file ) {
 575+ $this->showFile( $this->file );
400576 } else if( $this->acrID ) {
401577 $this->showForm();
402578 } else {
@@ -403,6 +579,25 @@
404580 }
405581 }
406582
 583+ /**
 584+ * Show a private file requested by the visitor.
 585+ */
 586+ function showFile( $key ) {
 587+ global $wgOut, $wgRequest;
 588+ $wgOut->disable();
 589+
 590+ # We mustn't allow the output to be Squid cached, otherwise
 591+ # if an admin previews a private image, and it's cached, then
 592+ # a user without appropriate permissions can toddle off and
 593+ # nab the image, and Squid will serve it
 594+ $wgRequest->response()->header( 'Expires: ' . gmdate( 'D, d M Y H:i:s', 0 ) . ' GMT' );
 595+ $wgRequest->response()->header( 'Cache-Control: no-cache, no-store, max-age=0, must-revalidate' );
 596+ $wgRequest->response()->header( 'Pragma: no-cache' );
 597+
 598+ $store = FileStore::get( 'accountreqs' );
 599+ $store->stream( $key );
 600+ }
 601+
407602 function doSubmit() {
408603 global $wgOut, $wgTitle, $wgAuth;
409604
@@ -414,7 +609,6 @@
415610 }
416611
417612 if( $this->submitType == 'reject' ) {
418 - global $wgSaveRejectedAccountReqs;
419613 # Make proxy user to email a rejection message :(
420614 $u = User::newFromName( $row->acr_name, 'creatable' );
421615 $u->setEmail( $row->acr_email );
@@ -434,19 +628,15 @@
435629 }
436630 }
437631 $dbw = wfGetDB( DB_MASTER );
438 - # Either mark off the row as deleted or wipe it completely
439 - if( $wgSaveRejectedAccountReqs ) {
440 - global $wgUser;
441 - # Request can later be recovered
442 - $dbw->update( 'account_requests',
443 - array( 'acr_rejected' => $dbw->timestamp(),
444 - 'acr_user' => $wgUser->getID(),
445 - 'acr_deleted' => 1 ),
446 - array( 'acr_id' => $this->acrID, 'acr_deleted' => 0 ),
447 - __METHOD__ );
448 - } else {
449 - $dbw->delete( 'account_requests', array('acr_id' => $this->acrID), __METHOD__ );
450 - }
 632+ # Mark off the row as deleted
 633+ global $wgUser;
 634+ # Request can later be recovered
 635+ $dbw->update( 'account_requests',
 636+ array( 'acr_rejected' => $dbw->timestamp(),
 637+ 'acr_user' => $wgUser->getID(),
 638+ 'acr_deleted' => 1 ),
 639+ array( 'acr_id' => $this->acrID, 'acr_deleted' => 0 ),
 640+ __METHOD__ );
451641
452642 $this->showSuccess( $action );
453643 } else if( $this->submitType == 'accept' ) {
@@ -516,17 +706,49 @@
517707 }
518708 if( $wgAutoWelcomeNewUsers ) {
519709 $utalk = new Article( $user->getTalkPage() );
520 - $utalk->doEdit( wfMsg('confirmaccount-welc') . ' ~~~~', wfMsg('confirmaccount-wsum'), EDIT_MINOR );
 710+ $utalk->doEdit( wfMsg('confirmaccount-welc') . ' ~~~~',
 711+ wfMsg('confirmaccount-wsum'), EDIT_MINOR );
521712 }
522713
523714 $this->showSuccess( $action, $user->getName() );
 715+ } else if( $this->submitType == 'hold' ) {
 716+ global $wgUser;
 717+ # Make proxy user to email a message
 718+ $u = User::newFromName( $row->acr_name, 'creatable' );
 719+ $u->setEmail( $row->acr_email );
 720+ # Pointless without a summary...
 721+ if( !$this->reason ) {
 722+ $error = wfMsg( 'confirmaccount-needreason' );
 723+ $this->showForm( $error );
 724+ return false;
 725+ }
 726+ # If not already held or deleted, mark as held
 727+ $dbw = wfGetDB( DB_MASTER );
 728+ $dbw->begin();
 729+ $dbw->update( 'account_requests',
 730+ array( 'acr_held' => $dbw->timestamp(),
 731+ 'acr_user' => $wgUser->getID() ),
 732+ array( 'acr_id' => $this->acrID, 'acr_held IS NULL', 'acr_deleted' => 0 ),
 733+ __METHOD__ );
 734+ # Do not send multiple times
 735+ if( !$row->acr_held && !$row->acr_deleted ) {
 736+ $result = $u->sendMail( wfMsg( 'confirmaccount-email-subj' ),
 737+ wfMsgExt( 'confirmaccount-email-body5', array('parsemag'), $u->getName(), $this->reason ) );
 738+ if( WikiError::isError( $result ) ) {
 739+ $dbw->rollback();
 740+ $error = wfMsg( 'mailerror', htmlspecialchars( $result->getMessage() ) );
 741+ $this->showForm( $error );
 742+ return false;
 743+ }
 744+ }
 745+ $this->showSuccess( $action );
524746 } else {
525747 $this->showForm();
526748 }
527749 }
528750
529751 function showForm( $msg='' ) {
530 - global $wgOut, $wgTitle, $wgUser;
 752+ global $wgOut, $wgTitle, $wgUser, $wgLang;
531753
532754 # Output failure message
533755 if( $msg ) {
@@ -549,6 +771,16 @@
550772
551773 $wgOut->addWikiText( wfMsg( "confirmaccount-text" ) );
552774
 775+ if( $this->showRejects ) {
 776+ $time = $wgLang->timeanddate( wfTimestamp(TS_MW, $row->acr_rejected), true );
 777+ $wgOut->addHTML('<b>'.wfMsgExt( 'confirmaccount-reject', array('parseinline'),
 778+ User::whoIs($row->acr_user), $time ).'</b>');
 779+ } else if( $row->acr_held ) {
 780+ $time = $wgLang->timeanddate( wfTimestamp(TS_MW, $row->acr_held), true );
 781+ $wgOut->addHTML('<b>'.wfMsgExt( 'confirmaccount-held', array('parseinline'),
 782+ User::whoIs($row->acr_user), $time ).'</b>');
 783+ }
 784+
553785 $action = $wgTitle->escapeLocalUrl( 'action=submit' );
554786 $form = "<form name='accountconfirm' action='$action' method='post'><fieldset>";
555787 $form .= '<legend>' . wfMsgHtml('requestaccount-legend1') . '</legend>';
@@ -575,12 +807,22 @@
576808
577809 $form .= '<fieldset>';
578810 $form .= '<legend>' . wfMsgHtml('requestaccount-legend3') . '</legend>';
 811+ $form .= '<p>'.wfMsgHtml('confirmaccount-attach') . ' ' .
 812+ $this->skin->makeKnownLinkObj( $wgTitle, htmlspecialchars($row->acr_filename),
 813+ 'file=' . $row->acr_storage_key );
 814+
579815 $form .= "<p>".wfMsgHtml('confirmaccount-notes')."</p>\n";
580816 $form .= "<p><textarea tabindex='1' readonly name='wpNotes' id='wpNotes' rows='3' cols='80' style='width:100%'>" .
581817 htmlspecialchars($row->acr_notes) .
582818 "</textarea></p>\n";
583819 $form .= "<p>".wfMsgHtml('confirmaccount-urls')."</p>\n";
584820 $form .= "<p>".$this->parseLinks($row->acr_urls)."</p>";
 821+ if( $wgUser->isAllowed( 'requestips' ) ) {
 822+ $blokip = SpecialPage::getTitleFor( 'blockip' );
 823+ $form .= "<p>".wfMsgHtml('confirmaccount-ip')." ".htmlspecialchars($row->acr_ip).
 824+ " (" . $this->skin->makeKnownLinkObj( $blokip, wfMsgHtml('blockip'),
 825+ 'ip=' . $row->acr_ip ).")</p>\n";
 826+ }
585827 $form .= '</fieldset>';
586828
587829 $form .= "<p>".wfMsgExt( 'confirmaccount-confirm', array('parse') )."</p>\n";
@@ -588,13 +830,15 @@
589831 $form .= ' '.Xml::label( wfMsg('confirmaccount-create'), 'submitCreate' )."</p>\n";
590832 $form .= "<p>".Xml::radio( 'wpSubmitType', 'reject', $this->submitType=='reject', array('id' => 'submitDeny') );
591833 $form .= ' '.Xml::label( wfMsg('confirmaccount-deny'), 'submitDeny' )."</p>\n";
 834+ $form .= "<p>".Xml::radio( 'wpSubmitType', 'hold', $this->submitType=='hold', array('id' => 'submitHold') );
 835+ $form .= ' '.Xml::label( wfMsg('confirmaccount-hold'), 'submitHold' )."</p>\n";
592836
593837 $form .= "<p>".wfMsgHtml('confirmaccount-reason')."</p>\n";
594838 $form .= "<p><textarea tabindex='1' name='wpReason' id='wpReason' rows='3' cols='80' style='width:80%'>" .
595839 htmlspecialchars($this->reason) .
596840 "</textarea></p>\n";
597841 $form .= "<p>".Xml::submitButton( wfMsgHtml( 'confirmaccount-submit') )."</p>\n";
598 - $form .= Xml::hidden( 'title', $wgTitle->getPrefixedText() )."\n";
 842+ $form .= Xml::hidden( 'title', $wgTitle->getPrefixedUrl() )."\n";
599843 $form .= Xml::hidden( 'action', 'reject' );
600844 $form .= Xml::hidden( 'acrid', $row->acr_id );
601845 $form .= Xml::hidden( 'wpShowRejects', $this->showRejects );
@@ -655,33 +899,53 @@
656900 $wgOut->addWikiText( wfMsg( "confirmaccount-acc", $name ) );
657901 else if( $this->submitType == 'reject' )
658902 $wgOut->addWikiText( wfMsg( "confirmaccount-rej" ) );
 903+ else
 904+ $wgOut->redirect( $wgTitle->getFullUrl() );
659905
660906 $wgOut->returnToMain( true, $wgTitle );
661907 }
662908
663909 function showList() {
664 - global $wgOut, $wgUser, $wgTitle, $wgLang, $wgSaveRejectedAccountReqs;
 910+ global $wgOut, $wgUser, $wgTitle, $wgLang;
665911
666912 if( $this->showRejects ) {
667913 $listLink = $this->skin->makeKnownLinkObj( $wgTitle, wfMsgHtml( 'confirmaccount-back' ) );
668 - } else if( $wgSaveRejectedAccountReqs ) {
 914+ } else {
669915 $listLink = $this->skin->makeKnownLinkObj( $wgTitle, wfMsgHtml( 'confirmaccount-back2' ),
670916 wfArrayToCGI( array('wpShowRejects' => 1 ) ) );
671917 }
672918 $wgOut->setSubtitle( '<p>'.$listLink.'</p>' );
673919
674 - if( $wgSaveRejectedAccountReqs ) {
675 - # Every 100th view, prune old deleted items
676 - wfSeedRandom();
677 - if( 0 == mt_rand( 0, 99 ) ) {
678 - global $wgRejectedAccountMaxAge;
 920+ # Every 100th view, prune old deleted items
 921+ wfSeedRandom();
 922+ if( 0 == mt_rand( 0, 99 ) ) {
 923+ global $wgRejectedAccountMaxAge;
679924
680 - $dbw = wfGetDB( DB_MASTER );
681 - $cutoff = $dbw->timestamp( time() - $wgRejectedAccountMaxAge );
682 - $accountrequests = $dbw->tableName( 'account_requests' );
683 - $sql = "DELETE FROM $accountrequests WHERE acr_rejected < '{$cutoff}'";
684 - $dbw->query( $sql );
 925+ $dbw = wfGetDB( DB_MASTER );
 926+ $transaction = new FSTransaction();
 927+ if( !FileStore::lock() ) {
 928+ wfDebug( __METHOD__.": failed to acquire file store lock, aborting\n" );
 929+ return false;
685930 }
 931+ # Select all items older than time $cutoff
 932+ $cutoff = $dbw->timestamp( time() - $wgRejectedAccountMaxAge );
 933+ $accountrequests = $dbw->tableName( 'account_requests' );
 934+ $sql = "SELECT acr_storage_key,acr_id FROM $accountrequests WHERE acr_rejected < '{$cutoff}'";
 935+ $res = $dbw->query( $sql );
 936+
 937+ $store = FileStore::get( 'accountreqs' );
 938+ # Clear out any associated attachments and delete those rows
 939+ while( $row = $dbw->fetchObject( $res ) ) {
 940+ $key = $row->acr_storage_key;
 941+ if( $key ) {
 942+ $path = $store->filePath( $key );
 943+ if( $path && file_exists($path) ) {
 944+ $transaction->addCommit( FSTransaction::DELETE_FILE, $path );
 945+ $dbw->query( "DELETE FROM $tbl_arch WHERE acr_id = {$row->acr_id}" );
 946+ }
 947+ }
 948+ }
 949+ $transaction->commit();
686950 }
687951
688952 $pager = new ConfirmAccountsPager( $this, array(), $this->showRejects );
@@ -710,15 +974,23 @@
711975 $link = $this->skin->makeKnownLinkObj( $title, wfMsgHtml('confirmaccount-review'),
712976 'acrid='.$row->acr_id.'&wpShowRejects=1' );
713977 } else {
714 - $link = $this->skin->makeKnownLinkObj( $title, wfMsgHtml('confirmaccount-review'), 'acrid='.$row->acr_id );
 978+ $link = $this->skin->makeKnownLinkObj( $title, wfMsgHtml('confirmaccount-review'),
 979+ 'acrid='.$row->acr_id );
715980 }
716981 $time = $wgLang->timeanddate( wfTimestamp(TS_MW, $row->acr_registration), true );
717982
718983 $r = '<li>';
 984+ if( $row->acr_held ) {
 985+ $r .= '<span class="confirmaccount-held">';
 986+ }
 987+
719988 $r .= $time." ($link)";
720989 if( $this->showRejects ) {
721990 $time = $wgLang->timeanddate( wfTimestamp(TS_MW, $row->acr_rejected), true );
722991 $r .= ' <b>'.wfMsgExt( 'confirmaccount-reject', array('parseinline'), $row->user_name, $time ).'</b>';
 992+ } else if( $row->acr_held ) {
 993+ $time = $wgLang->timeanddate( wfTimestamp(TS_MW, $row->acr_held), true );
 994+ $r .= ' <b>'.wfMsgExt( 'confirmaccount-held', array('parseinline'), User::whoIs($row->acr_user), $time ).'</b>';
723995 }
724996 $r .= '<br/><table cellspacing=\'1\' cellpadding=\'3\' border=\'1\' width=\'100%\'>';
725997 if( !$wgUseRealNamesOnly ) {
@@ -737,8 +1009,13 @@
7381010 $preview = substr( $preview, 0, strrpos($preview,' ') );
7391011 $preview .= " . . .";
7401012 }
741 - $r .= '<tr><td><strong>'.wfMsgHtml('confirmaccount-bio-q').'</strong></td><td width=\'100%\'><i>'.$preview.'</i></td></tr>';
742 - $r .= '</table></li>';
 1013+ $r .= '<tr><td><strong>'.wfMsgHtml('confirmaccount-bio-q') .
 1014+ '</strong></td><td width=\'100%\'><i>'.$preview.'</i></td></tr>';
 1015+ $r .= '</table>';
 1016+ if( $row->acr_held ) {
 1017+ $r .= '</span>';
 1018+ }
 1019+ $r .= '</li>';
7431020
7441021 return $r;
7451022 }
@@ -772,8 +1049,8 @@
7731050 function getQueryInfo() {
7741051 $conds = $this->mConds;
7751052 $tables = array('account_requests');
776 - $fields = array('acr_id','acr_name','acr_real_name','acr_registration',
777 - 'acr_email','acr_email_authenticated','acr_bio','acr_notes','acr_urls');
 1053+ $fields = array('acr_id','acr_name','acr_real_name','acr_registration','acr_held',
 1054+ 'acr_email','acr_email_authenticated','acr_bio','acr_notes','acr_urls','acr_user');
7781055 if( $this->rejects ) {
7791056 $tables[] = 'user';
7801057 $conds[] = 'acr_user = user_id';
Index: trunk/extensions/ConfirmAccount/ConfirmAccount.i18n.php
@@ -30,14 +30,18 @@
3131 'requestaccount-same' => '(same as real name)',
3232 'requestaccount-email' => 'Email address:',
3333 'requestaccount-bio' => 'Personal biography:',
 34+ 'requestaccount-attach' => 'Resume or CV (optional):',
3435 'requestaccount-notes' => 'Additional notes:',
3536 'requestaccount-urls' => 'List of websites, if any (separate with newlines):',
3637 'requestaccount-agree' => 'You must certify that your real name is correct and that you agree to our Terms of Service.',
3738 'requestaccount-inuse' => 'Username is already in use in a pending account request.',
3839 'requestaccount-tooshort' => 'Your biography must be at least be $1 words long.',
 40+ 'requestaccount-exts' => 'Attachment file type is disallowed.',
 41+ 'requestaccount-resub' => 'Your CV/resume file must be re-selected for security reasons. Leave the field blank
 42+ if you no longer want to include one.',
3943 'requestaccount-tos' => 'I have read and agree to abide by the [[{{NS:PROJECT}}:Terms of Service|Terms of Service]] of {{SITENAME}}.
4044 The name I have specified under "Real name" is in fact my own real name.',
41 - 'requestaccount-submit' => 'Request account',
 45+ 'requestaccount-submit' => 'Request account',
4246 'requestaccount-sent' => 'Your account request has successfully been sent and is now pending review.',
4347 'request-account-econf' => 'Your e-mail address has been confirmed and will be listed as such in your account
4448 request.',
@@ -82,17 +86,22 @@
8387 'confirmaccount-email-q' => 'Email',
8488 'confirmaccount-bio' => 'Biography:',
8589 'confirmaccount-bio-q' => 'Biography',
 90+ 'confirmaccount-attach' => 'Resume/CV:',
8691 'confirmaccount-notes' => 'Additional notes:',
8792 'confirmaccount-urls' => 'List of websites:',
8893 'confirmaccount-nourls' => '(None provided)',
8994 'confirmaccount-review' => 'Approve/Reject',
90 - 'confirmaccount-confirm' => 'Use the buttons below to accept this request or deny it.',
 95+ 'confirmaccount-confirm' => 'Use the options below to accept, deny, or hold this request:',
9196 'confirmaccount-econf' => '(confirmed)',
9297 'confirmaccount-reject' => '(rejected by [[User:$1|$1]] on $2)',
 98+ 'confirmaccount-held' => '(marked "on hold" by [[User:$1|$1]] on $2)',
9399 'confirmaccount-create' => 'Accept (create account)',
94100 'confirmaccount-deny' => 'Reject (delist)',
 101+ 'confirmaccount-hold' => 'Mark as "on hold"',
95102 'confirmaccount-reason' => 'Comment (will be included in email):',
 103+ 'confirmaccount-ip' => 'IP address:',
96104 'confirmaccount-submit' => 'Confirm',
 105+ 'confirmaccount-needreason' => 'You must provide a reason in the comment box below.',
97106 'confirmaccount-acc' => 'Account request confirmed successfully; created new user account [[User:$1]].',
98107 'confirmaccount-rej' => 'Account request rejected successfully.',
99108 'confirmaccount-summary' => 'Creating user page with biography of new user.',
@@ -128,6 +137,12 @@
129138 $2
130139
131140 There may be contact lists on site that you can use if you want to know more about user account policy.',
 141+ 'confirmaccount-email-body5' => 'Before your request for an account "$1" can be accepted on {{SITENAME}}
 142+ you must first provide some additional information.
 143+
 144+$2
 145+
 146+There may be contact lists on site that you can use if you want to know more about user account policy.',
132147 );
133148
134149 $wgConfirmAccountMessages['ar'] = array(

Status & tagging log