コード例 #1
0
 public function run()
 {
     $scope = RequestContext::importScopedSession($this->params['session']);
     $context = RequestContext::getMain();
     try {
         $user = $context->getUser();
         if (!$user->isLoggedIn()) {
             $this->setLastError("Could not load the author user from session.");
             return false;
         }
         if (count($_SESSION) === 0) {
             // Empty session probably indicates that we didn't associate
             // with the session correctly. Note that being able to load
             // the user does not necessarily mean the session was loaded.
             // Most likely cause by suhosin.session.encrypt = On.
             $this->setLastError("Error associating with user session. " . "Try setting suhosin.session.encrypt = Off");
             return false;
         }
         UploadBase::setSessionStatus($this->params['filekey'], array('result' => 'Poll', 'stage' => 'publish', 'status' => Status::newGood()));
         $upload = new UploadFromStash($user);
         // @todo initialize() causes a GET, ideally we could frontload the antivirus
         // checks and anything else to the stash stage (which includes concatenation and
         // the local file is thus already there). That way, instead of GET+PUT, there could
         // just be a COPY operation from the stash to the public zone.
         $upload->initialize($this->params['filekey'], $this->params['filename']);
         // Check if the local file checks out (this is generally a no-op)
         $verification = $upload->verifyUpload();
         if ($verification['status'] !== UploadBase::OK) {
             $status = Status::newFatal('verification-error');
             $status->value = array('verification' => $verification);
             UploadBase::setSessionStatus($this->params['filekey'], array('result' => 'Failure', 'stage' => 'publish', 'status' => $status));
             $this->setLastError("Could not verify upload.");
             return false;
         }
         // Upload the stashed file to a permanent location
         $status = $upload->performUpload($this->params['comment'], $this->params['text'], $this->params['watch'], $user);
         if (!$status->isGood()) {
             UploadBase::setSessionStatus($this->params['filekey'], array('result' => 'Failure', 'stage' => 'publish', 'status' => $status));
             $this->setLastError($status->getWikiText());
             return false;
         }
         // Build the image info array while we have the local reference handy
         $apiMain = new ApiMain();
         // dummy object (XXX)
         $imageInfo = $upload->getImageInfo($apiMain->getResult());
         // Cleanup any temporary local file
         $upload->cleanupTempFile();
         // Cache the info so the user doesn't have to wait forever to get the final info
         UploadBase::setSessionStatus($this->params['filekey'], array('result' => 'Success', 'stage' => 'publish', 'filename' => $upload->getLocalFile()->getName(), 'imageinfo' => $imageInfo, 'status' => Status::newGood()));
     } catch (MWException $e) {
         UploadBase::setSessionStatus($this->params['filekey'], array('result' => 'Failure', 'stage' => 'publish', 'status' => Status::newFatal('api-error-publishfailed')));
         $this->setLastError(get_class($e) . ": " . $e->getText());
         // To prevent potential database referential integrity issues.
         // See bug 32551.
         MWExceptionHandler::rollbackMasterChangesAndLog($e);
         return false;
     }
     return true;
 }
コード例 #2
0
 public function run()
 {
     $scope = RequestContext::importScopedSession($this->params['session']);
     $this->addTeardownCallback(function () use(&$scope) {
         ScopedCallback::consume($scope);
         // T126450
     });
     $context = RequestContext::getMain();
     $user = $context->getUser();
     try {
         if (!$user->isLoggedIn()) {
             $this->setLastError("Could not load the author user from session.");
             return false;
         }
         UploadBase::setSessionStatus($user, $this->params['filekey'], ['result' => 'Poll', 'stage' => 'publish', 'status' => Status::newGood()]);
         $upload = new UploadFromStash($user);
         // @todo initialize() causes a GET, ideally we could frontload the antivirus
         // checks and anything else to the stash stage (which includes concatenation and
         // the local file is thus already there). That way, instead of GET+PUT, there could
         // just be a COPY operation from the stash to the public zone.
         $upload->initialize($this->params['filekey'], $this->params['filename']);
         // Check if the local file checks out (this is generally a no-op)
         $verification = $upload->verifyUpload();
         if ($verification['status'] !== UploadBase::OK) {
             $status = Status::newFatal('verification-error');
             $status->value = ['verification' => $verification];
             UploadBase::setSessionStatus($user, $this->params['filekey'], ['result' => 'Failure', 'stage' => 'publish', 'status' => $status]);
             $this->setLastError("Could not verify upload.");
             return false;
         }
         // Upload the stashed file to a permanent location
         $status = $upload->performUpload($this->params['comment'], $this->params['text'], $this->params['watch'], $user, isset($this->params['tags']) ? $this->params['tags'] : []);
         if (!$status->isGood()) {
             UploadBase::setSessionStatus($user, $this->params['filekey'], ['result' => 'Failure', 'stage' => 'publish', 'status' => $status]);
             $this->setLastError($status->getWikiText(false, false, 'en'));
             return false;
         }
         // Build the image info array while we have the local reference handy
         $apiMain = new ApiMain();
         // dummy object (XXX)
         $imageInfo = $upload->getImageInfo($apiMain->getResult());
         // Cleanup any temporary local file
         $upload->cleanupTempFile();
         // Cache the info so the user doesn't have to wait forever to get the final info
         UploadBase::setSessionStatus($user, $this->params['filekey'], ['result' => 'Success', 'stage' => 'publish', 'filename' => $upload->getLocalFile()->getName(), 'imageinfo' => $imageInfo, 'status' => Status::newGood()]);
     } catch (Exception $e) {
         UploadBase::setSessionStatus($user, $this->params['filekey'], ['result' => 'Failure', 'stage' => 'publish', 'status' => Status::newFatal('api-error-publishfailed')]);
         $this->setLastError(get_class($e) . ": " . $e->getMessage());
         // To prevent potential database referential integrity issues.
         // See bug 32551.
         MWExceptionHandler::rollbackMasterChangesAndLog($e);
         return false;
     }
     return true;
 }
コード例 #3
0
 public function testValidRequest()
 {
     $request = new FauxRequest(array('wpFileKey' => 'foo'));
     $this->assertFalse(UploadFromStash::isValidRequest($request), 'Check failure on bad wpFileKey');
     $request = new FauxRequest(array('wpSessionKey' => 'foo'));
     $this->assertFalse(UploadFromStash::isValidRequest($request), 'Check failure on bad wpSessionKey');
     $request = new FauxRequest(array('wpFileKey' => 'testkey-test.test'));
     $this->assertTrue(UploadFromStash::isValidRequest($request), 'Check good wpFileKey');
     $request = new FauxRequest(array('wpFileKey' => 'testkey-test.test'));
     $this->assertTrue(UploadFromStash::isValidRequest($request), 'Check good wpSessionKey');
     $request = new FauxRequest(array('wpFileKey' => 'testkey-test.test', 'wpSessionKey' => 'foo'));
     $this->assertTrue(UploadFromStash::isValidRequest($request), 'Check key precedence');
 }
コード例 #4
0
ファイル: ApiUpload.php プロジェクト: claudinec/galan-wiki
 /**
  * Select an upload module and set it to mUpload. Dies on failure. If the
  * request was a status request and not a true upload, returns false;
  * otherwise true
  *
  * @return bool
  */
 protected function selectUploadModule()
 {
     $request = $this->getMain()->getRequest();
     // chunk or one and only one of the following parameters is needed
     if (!$this->mParams['chunk']) {
         $this->requireOnlyOneParameter($this->mParams, 'filekey', 'file', 'url');
     }
     // Status report for "upload to stash"/"upload from stash"
     if ($this->mParams['filekey'] && $this->mParams['checkstatus']) {
         $progress = UploadBase::getSessionStatus($this->getUser(), $this->mParams['filekey']);
         if (!$progress) {
             $this->dieUsage('No result in status data', 'missingresult');
         } elseif (!$progress['status']->isGood()) {
             $this->dieUsage($progress['status']->getWikiText(false, false, 'en'), 'stashfailed');
         }
         if (isset($progress['status']->value['verification'])) {
             $this->checkVerification($progress['status']->value['verification']);
         }
         unset($progress['status']);
         // remove Status object
         $this->getResult()->addValue(null, $this->getModuleName(), $progress);
         return false;
     }
     // The following modules all require the filename parameter to be set
     if (is_null($this->mParams['filename'])) {
         $this->dieUsageMsg(['missingparam', 'filename']);
     }
     if ($this->mParams['chunk']) {
         // Chunk upload
         $this->mUpload = new UploadFromChunks();
         if (isset($this->mParams['filekey'])) {
             if ($this->mParams['offset'] === 0) {
                 $this->dieUsage('Cannot supply a filekey when offset is 0', 'badparams');
             }
             // handle new chunk
             $this->mUpload->continueChunks($this->mParams['filename'], $this->mParams['filekey'], $request->getUpload('chunk'));
         } else {
             if ($this->mParams['offset'] !== 0) {
                 $this->dieUsage('Must supply a filekey when offset is non-zero', 'badparams');
             }
             // handle first chunk
             $this->mUpload->initialize($this->mParams['filename'], $request->getUpload('chunk'));
         }
     } elseif (isset($this->mParams['filekey'])) {
         // Upload stashed in a previous request
         if (!UploadFromStash::isValidKey($this->mParams['filekey'])) {
             $this->dieUsageMsg('invalid-file-key');
         }
         $this->mUpload = new UploadFromStash($this->getUser());
         // This will not download the temp file in initialize() in async mode.
         // We still have enough information to call checkWarnings() and such.
         $this->mUpload->initialize($this->mParams['filekey'], $this->mParams['filename'], !$this->mParams['async']);
     } elseif (isset($this->mParams['file'])) {
         $this->mUpload = new UploadFromFile();
         $this->mUpload->initialize($this->mParams['filename'], $request->getUpload('file'));
     } elseif (isset($this->mParams['url'])) {
         // Make sure upload by URL is enabled:
         if (!UploadFromUrl::isEnabled()) {
             $this->dieUsageMsg('copyuploaddisabled');
         }
         if (!UploadFromUrl::isAllowedHost($this->mParams['url'])) {
             $this->dieUsageMsg('copyuploadbaddomain');
         }
         if (!UploadFromUrl::isAllowedUrl($this->mParams['url'])) {
             $this->dieUsageMsg('copyuploadbadurl');
         }
         $this->mUpload = new UploadFromUrl();
         $this->mUpload->initialize($this->mParams['filename'], $this->mParams['url']);
     }
     return true;
 }
コード例 #5
0
 /**
  * This function just appends the generic comment
  */
 function performUpload($comment, $pageText, $watch, $user)
 {
     $pageText .= getResourceComment();
     return parent::performUpload($comment, $pageText, $watch, $user);
 }
コード例 #6
0
 /**
  * Select an upload module and set it to mUpload. Dies on failure. If the
  * request was a status request and not a true upload, returns false;
  * otherwise true
  *
  * @return bool
  */
 protected function selectUploadModule()
 {
     $request = $this->getMain()->getRequest();
     // chunk or one and only one of the following parameters is needed
     if (!$this->mParams['chunk']) {
         $this->requireOnlyOneParameter($this->mParams, 'filekey', 'file', 'url', 'statuskey');
     }
     if ($this->mParams['statuskey']) {
         $this->checkAsyncDownloadEnabled();
         // Status request for an async upload
         $sessionData = UploadFromUrlJob::getSessionData($this->mParams['statuskey']);
         if (!isset($sessionData['result'])) {
             $this->dieUsage('No result in session data', 'missingresult');
         }
         if ($sessionData['result'] == 'Warning') {
             $sessionData['warnings'] = $this->transformWarnings($sessionData['warnings']);
             $sessionData['sessionkey'] = $this->mParams['statuskey'];
         }
         $this->getResult()->addValue(null, $this->getModuleName(), $sessionData);
         return false;
     }
     // The following modules all require the filename parameter to be set
     if (is_null($this->mParams['filename'])) {
         $this->dieUsageMsg(array('missingparam', 'filename'));
     }
     if ($this->mParams['chunk']) {
         // Chunk upload
         $this->mUpload = new UploadFromChunks();
         if (isset($this->mParams['filekey'])) {
             // handle new chunk
             $this->mUpload->continueChunks($this->mParams['filename'], $this->mParams['filekey'], $request->getUpload('chunk'));
         } else {
             // handle first chunk
             $this->mUpload->initialize($this->mParams['filename'], $request->getUpload('chunk'));
         }
     } elseif (isset($this->mParams['filekey'])) {
         // Upload stashed in a previous request
         if (!UploadFromStash::isValidKey($this->mParams['filekey'])) {
             $this->dieUsageMsg('invalid-file-key');
         }
         $this->mUpload = new UploadFromStash($this->getUser());
         $this->mUpload->initialize($this->mParams['filekey'], $this->mParams['filename']);
     } elseif (isset($this->mParams['file'])) {
         $this->mUpload = new UploadFromFile();
         $this->mUpload->initialize($this->mParams['filename'], $request->getUpload('file'));
     } elseif (isset($this->mParams['url'])) {
         // Make sure upload by URL is enabled:
         if (!UploadFromUrl::isEnabled()) {
             $this->dieUsageMsg('copyuploaddisabled');
         }
         $async = false;
         if ($this->mParams['asyncdownload']) {
             $this->checkAsyncDownloadEnabled();
             if ($this->mParams['leavemessage'] && !$this->mParams['ignorewarnings']) {
                 $this->dieUsage('Using leavemessage without ignorewarnings is not supported', 'missing-ignorewarnings');
             }
             if ($this->mParams['leavemessage']) {
                 $async = 'async-leavemessage';
             } else {
                 $async = 'async';
             }
         }
         $this->mUpload = new UploadFromUrl();
         $this->mUpload->initialize($this->mParams['filename'], $this->mParams['url'], $async);
     }
     return true;
 }
コード例 #7
0
ファイル: ApiUpload.php プロジェクト: nahoj/mediawiki_ynh
	/**
	 * Select an upload module and set it to mUpload. Dies on failure. If the
	 * request was a status request and not a true upload, returns false;
	 * otherwise true
	 *
	 * @return bool
	 */
	protected function selectUploadModule() {
		$request = $this->getMain()->getRequest();

		// chunk or one and only one of the following parameters is needed
		if ( !$this->mParams['chunk'] ) {
			$this->requireOnlyOneParameter( $this->mParams,
				'filekey', 'file', 'url', 'statuskey' );
		}

		// Status report for "upload to stash"/"upload from stash"
		if ( $this->mParams['filekey'] && $this->mParams['checkstatus'] ) {
			$progress = UploadBase::getSessionStatus( $this->mParams['filekey'] );
			if ( !$progress ) {
				$this->dieUsage( 'No result in status data', 'missingresult' );
			} elseif ( !$progress['status']->isGood() ) {
				$this->dieUsage( $progress['status']->getWikiText(), 'stashfailed' );
			}
			if ( isset( $progress['status']->value['verification'] ) ) {
				$this->checkVerification( $progress['status']->value['verification'] );
			}
			unset( $progress['status'] ); // remove Status object
			$this->getResult()->addValue( null, $this->getModuleName(), $progress );
			return false;
		}

		if ( $this->mParams['statuskey'] ) {
			$this->checkAsyncDownloadEnabled();

			// Status request for an async upload
			$sessionData = UploadFromUrlJob::getSessionData( $this->mParams['statuskey'] );
			if ( !isset( $sessionData['result'] ) ) {
				$this->dieUsage( 'No result in session data', 'missingresult' );
			}
			if ( $sessionData['result'] == 'Warning' ) {
				$sessionData['warnings'] = $this->transformWarnings( $sessionData['warnings'] );
				$sessionData['sessionkey'] = $this->mParams['statuskey'];
			}
			$this->getResult()->addValue( null, $this->getModuleName(), $sessionData );
			return false;
		}

		// The following modules all require the filename parameter to be set
		if ( is_null( $this->mParams['filename'] ) ) {
			$this->dieUsageMsg( array( 'missingparam', 'filename' ) );
		}

		if ( $this->mParams['chunk'] ) {
			// Chunk upload
			$this->mUpload = new UploadFromChunks();
			if ( isset( $this->mParams['filekey'] ) ) {
				// handle new chunk
				$this->mUpload->continueChunks(
					$this->mParams['filename'],
					$this->mParams['filekey'],
					$request->getUpload( 'chunk' )
				);
			} else {
				// handle first chunk
				$this->mUpload->initialize(
					$this->mParams['filename'],
					$request->getUpload( 'chunk' )
				);
			}
		} elseif ( isset( $this->mParams['filekey'] ) ) {
			// Upload stashed in a previous request
			if ( !UploadFromStash::isValidKey( $this->mParams['filekey'] ) ) {
				$this->dieUsageMsg( 'invalid-file-key' );
			}

			$this->mUpload = new UploadFromStash( $this->getUser() );
			// This will not download the temp file in initialize() in async mode.
			// We still have enough information to call checkWarnings() and such.
			$this->mUpload->initialize(
				$this->mParams['filekey'], $this->mParams['filename'], !$this->mParams['async']
			);
		} elseif ( isset( $this->mParams['file'] ) ) {
			$this->mUpload = new UploadFromFile();
			$this->mUpload->initialize(
				$this->mParams['filename'],
				$request->getUpload( 'file' )
			);
		} elseif ( isset( $this->mParams['url'] ) ) {
			// Make sure upload by URL is enabled:
			if ( !UploadFromUrl::isEnabled() ) {
				$this->dieUsageMsg( 'copyuploaddisabled' );
			}

			if ( !UploadFromUrl::isAllowedHost( $this->mParams['url'] ) ) {
				$this->dieUsageMsg( 'copyuploadbaddomain' );
			}

			if ( !UploadFromUrl::isAllowedUrl( $this->mParams['url'] ) ) {
				$this->dieUsageMsg( 'copyuploadbadurl' );
			}

			$async = false;
			if ( $this->mParams['asyncdownload'] ) {
				$this->checkAsyncDownloadEnabled();

				if ( $this->mParams['leavemessage'] && !$this->mParams['ignorewarnings'] ) {
					$this->dieUsage( 'Using leavemessage without ignorewarnings is not supported',
						'missing-ignorewarnings' );
				}

				if ( $this->mParams['leavemessage'] ) {
					$async = 'async-leavemessage';
				} else {
					$async = 'async';
				}
			}
			$this->mUpload = new UploadFromUrl;
			$this->mUpload->initialize( $this->mParams['filename'],
				$this->mParams['url'], $async );
		}

		return true;
	}
コード例 #8
0
ファイル: UploadStashTest.php プロジェクト: paladox/mediawiki
 /**
  * @dataProvider provideValidRequests
  */
 public function testValidRequestWithValidRequests($request)
 {
     $this->assertTrue(UploadFromStash::isValidRequest($request));
 }
コード例 #9
0
ファイル: ApiUpload.php プロジェクト: Tjorriemorrie/app
 /**
  * Select an upload module and set it to mUpload. Dies on failure. If the
  * request was a status request and not a true upload, returns false;
  * otherwise true
  *
  * @return bool
  */
 protected function selectUploadModule()
 {
     $request = $this->getMain()->getRequest();
     // chunk or one and only one of the following parameters is needed
     if (!$this->mParams['chunk']) {
         $this->requireOnlyOneParameter($this->mParams, 'filekey', 'file', 'url', 'statuskey');
     }
     // The following modules all require the filename parameter to be set
     if (is_null($this->mParams['filename'])) {
         $this->dieUsageMsg(array('missingparam', 'filename'));
     }
     if ($this->mParams['chunk']) {
         // Chunk upload
         $this->mUpload = new UploadFromChunks();
         if (isset($this->mParams['filekey'])) {
             // handle new chunk
             $this->mUpload->continueChunks($this->mParams['filename'], $this->mParams['filekey'], $request->getUpload('chunk'));
         } else {
             // handle first chunk
             $this->mUpload->initialize($this->mParams['filename'], $request->getUpload('chunk'));
         }
     } elseif (isset($this->mParams['filekey'])) {
         // Upload stashed in a previous request
         if (!UploadFromStash::isValidKey($this->mParams['filekey'])) {
             $this->dieUsageMsg('invalid-file-key');
         }
         $this->mUpload = new UploadFromStash($this->getUser());
         $this->mUpload->initialize($this->mParams['filekey'], $this->mParams['filename']);
     } elseif (isset($this->mParams['file'])) {
         $this->mUpload = new UploadFromFile();
         $this->mUpload->initialize($this->mParams['filename'], $request->getUpload('file'));
     } elseif (isset($this->mParams['url'])) {
         // Make sure upload by URL is enabled:
         if (!UploadFromUrl::isEnabled()) {
             $this->dieUsageMsg('copyuploaddisabled');
         }
         $this->mUpload = new UploadFromUrl();
         $this->mUpload->initialize($this->mParams['filename'], $this->mParams['url']);
     }
     return true;
 }
コード例 #10
0
 function approveEditById($id)
 {
     $dbw = wfGetDB(DB_MASTER);
     $row = $dbw->selectRow('moderation', array('mod_id AS id', 'mod_timestamp AS timestamp', 'mod_user AS user', 'mod_user_text AS user_text', 'mod_cur_id AS cur_id', 'mod_namespace AS namespace', 'mod_title AS title', 'mod_comment AS comment', 'mod_minor AS minor', 'mod_bot AS bot', 'mod_last_oldid AS last_oldid', 'mod_ip AS ip', 'mod_header_xff AS header_xff', 'mod_header_ua AS header_ua', 'mod_text AS text', 'mod_merged_revid AS merged_revid', 'mod_rejected AS rejected', 'mod_stash_key AS stash_key'), array('mod_id' => $id), __METHOD__);
     if (!$row) {
         throw new ModerationError('moderation-edit-not-found');
     }
     if ($row->merged_revid) {
         throw new ModerationError('moderation-already-merged');
     }
     if ($row->rejected && $row->timestamp < $this->mSpecial->earliestReapprovableTimestamp) {
         throw new ModerationError('moderation-rejected-long-ago');
     }
     # Prepare everything
     $title = Title::makeTitle($row->namespace, $row->title);
     $model = $title->getContentModel();
     $user = $row->user ? User::newFromId($row->user) : User::newFromName($row->user_text, false);
     $flags = EDIT_DEFER_UPDATES | EDIT_AUTOSUMMARY;
     if ($row->bot && $user->isAllowed('bot')) {
         $flags |= EDIT_FORCE_BOT;
     }
     if ($row->minor) {
         # doEditContent() checks the right
         $flags |= EDIT_MINOR;
     }
     # For CheckUser extension to work properly, IP, XFF and UA
     # should be set to the correct values for the original user
     # (not from the moderator)
     $cuHook = new ModerationCheckUserHook();
     $cuHook->install($row->ip, $row->header_xff, $row->header_ua);
     $approveHook = new ModerationApproveHook();
     $approveHook->install(array('rev_timestamp' => $dbw->timestamp($row->timestamp), 'rev_user' => $user->getId(), 'rev_user_text' => $user->getName()));
     $status = Status::newGood();
     if ($row->stash_key) {
         # This is the upload from stash.
         $stash = RepoGroup::singleton()->getLocalRepo()->getUploadStash($user);
         try {
             $file = $stash->getFile($row->stash_key);
         } catch (MWException $e) {
             throw new ModerationError('moderation-missing-stashed-image');
         }
         $upload = new UploadFromStash($user, $stash);
         $upload->initialize($row->stash_key, $title->getText());
         $status = $upload->performUpload($row->comment, $row->text, 0, $user);
     } else {
         # This is normal edit (not an upload).
         $new_content = ContentHandler::makeContent($row->text, null, $model);
         $page = new WikiPage($title);
         if (!$page->exists()) {
             # New page
             $status = $page->doEditContent($new_content, $row->comment, $flags, false, $user);
         } else {
             # Existing page
             $latest = $page->getLatest();
             if ($latest == $row->last_oldid) {
                 # Page hasn't changed since this edit was queued for moderation.
                 $status = $page->doEditContent($new_content, $row->comment, $flags, $row->last_oldid, $user);
             } else {
                 # Page has changed!
                 # Let's attempt merging, as MediaWiki does in private EditPage::mergeChangesIntoContent().
                 $base_content = $row->last_oldid ? Revision::newFromId($row->last_oldid)->getContent(Revision::RAW) : ContentHandler::makeContent('', null, $model);
                 $latest_content = Revision::newFromId($latest)->getContent(Revision::RAW);
                 $handler = ContentHandler::getForModelID($base_content->getModel());
                 $merged_content = $handler->merge3($base_content, $new_content, $latest_content);
                 if ($merged_content) {
                     $status = $page->doEditContent($merged_content, $row->comment, $flags, $latest, $user);
                 } else {
                     $dbw = wfGetDB(DB_MASTER);
                     $dbw->update('moderation', array('mod_conflict' => 1), array('mod_id' => $id), __METHOD__);
                     $dbw->commit(__METHOD__);
                     throw new ModerationError('moderation-edit-conflict');
                 }
             }
         }
     }
     $approveHook->deinstall();
     $cuHook->deinstall();
     if (!$status->isGood()) {
         throw new ModerationError($status->getMessage());
     }
     $logEntry = new ManualLogEntry('moderation', 'approve');
     $logEntry->setPerformer($this->moderator);
     $logEntry->setTarget($title);
     $logEntry->setParameters(array('revid' => $approveHook->lastRevId));
     $logid = $logEntry->insert();
     $logEntry->publish($logid);
     # Approved edits are removed from "moderation" table,
     # because they already exist in page history, recentchanges etc.
     $dbw = wfGetDB(DB_MASTER);
     $dbw->delete('moderation', array('mod_id' => $id), __METHOD__);
 }