Index: trunk/phase3/includes/upload/UploadFromUrl.php |
— | — | @@ -10,6 +10,7 @@ |
11 | 11 | */ |
12 | 12 | class UploadFromUrl extends UploadBase { |
13 | 13 | protected $mAsync, $mUrl; |
| 14 | + protected $mIgnoreWarnings = true; |
14 | 15 | |
15 | 16 | /** |
16 | 17 | * Checks if the user is allowed to use the upload-by-URL feature. If the |
— | — | @@ -40,12 +41,12 @@ |
41 | 42 | * asynchronous download. |
42 | 43 | */ |
43 | 44 | public function initialize( $name, $url, $async = false ) { |
44 | | - global $wgUser; |
| 45 | + global $wgAllowAsyncCopyUploads; |
45 | 46 | |
46 | 47 | $this->mUrl = $url; |
47 | | - $this->mAsync = $async; |
| 48 | + $this->mAsync = $wgAllowAsyncCopyUploads ? $async : false; |
48 | 49 | |
49 | | - $tempPath = $async ? null : $this->makeTemporaryFile(); |
| 50 | + $tempPath = $this->mAsync ? null : $this->makeTemporaryFile(); |
50 | 51 | # File size and removeTempFile will be filled in later |
51 | 52 | $this->initializePathInfo( $name, $tempPath, 0, false ); |
52 | 53 | } |
— | — | @@ -88,9 +89,20 @@ |
89 | 90 | } |
90 | 91 | return Status::newGood(); |
91 | 92 | } |
| 93 | + /** |
| 94 | + * Create a new temporary file in the URL subdirectory of wfTempDir(). |
| 95 | + * |
| 96 | + * @return string Path to the file |
| 97 | + */ |
92 | 98 | protected function makeTemporaryFile() { |
93 | 99 | return tempnam( wfTempDir(), 'URL' ); |
94 | 100 | } |
| 101 | + /** |
| 102 | + * Save the result of a HTTP request to the temporary file |
| 103 | + * |
| 104 | + * @param $req HttpRequest |
| 105 | + * @return Status |
| 106 | + */ |
95 | 107 | private function saveTempFile( $req ) { |
96 | 108 | if ( $this->mTempPath === false ) { |
97 | 109 | return Status::newFatal( 'tmp-create-error' ); |
— | — | @@ -103,6 +115,10 @@ |
104 | 116 | |
105 | 117 | return Status::newGood(); |
106 | 118 | } |
| 119 | + /** |
| 120 | + * Download the file, save it to the temporary file and update the file |
| 121 | + * size and set $mRemoveTempFile to true. |
| 122 | + */ |
107 | 123 | protected function reallyFetchFile() { |
108 | 124 | $req = HttpRequest::factory( $this->mUrl ); |
109 | 125 | $status = $req->execute(); |
— | — | @@ -120,6 +136,44 @@ |
121 | 137 | return $status; |
122 | 138 | } |
123 | 139 | |
| 140 | + /** |
| 141 | + * Wrapper around the parent function in order to defer verifying the |
| 142 | + * upload until the file really has been fetched. |
| 143 | + */ |
| 144 | + public function verifyUpload() { |
| 145 | + if ( $this->mAsync ) { |
| 146 | + return array( 'status' => self::OK ); |
| 147 | + } |
| 148 | + return parent::verifyUpload(); |
| 149 | + } |
| 150 | + |
| 151 | + /** |
| 152 | + * Wrapper around the parent function in order to defer checking warnings |
| 153 | + * until the file really has been fetched. |
| 154 | + */ |
| 155 | + public function checkWarnings() { |
| 156 | + if ( $this->mAsync ) { |
| 157 | + $this->mIgnoreWarnings = false; |
| 158 | + return array(); |
| 159 | + } |
| 160 | + return parent::checkWarnings(); |
| 161 | + } |
| 162 | + |
| 163 | + /** |
| 164 | + * Wrapper around the parent function in order to defer checking protection |
| 165 | + * until we are sure that the file can actually be uploaded |
| 166 | + */ |
| 167 | + public function verifyPermissions( $user ) { |
| 168 | + if ( $this->mAsync ) { |
| 169 | + return true; |
| 170 | + } |
| 171 | + return parent::verifyPermissions( $user ); |
| 172 | + } |
| 173 | + |
| 174 | + /** |
| 175 | + * Wrapper around the parent function in order to defer uploading to the |
| 176 | + * job queue for asynchronous uploads |
| 177 | + */ |
124 | 178 | public function performUpload( $comment, $pageText, $watch, $user ) { |
125 | 179 | if ( $this->mAsync ) { |
126 | 180 | $sessionKey = $this->insertJob( $comment, $pageText, $watch, $user ); |
Index: trunk/phase3/includes/User.php |
— | — | @@ -3767,7 +3767,7 @@ |
3768 | 3768 | if ( !$template |
3769 | 3769 | || $template->getNamespace() !== NS_TEMPLATE |
3770 | 3770 | || !$template->exists() ) { |
3771 | | - $text = "== $subject ==\n\n$text\n\n-- $signature"; |
| 3771 | + $text = "\n== $subject ==\n\n$text\n\n-- $signature"; |
3772 | 3772 | } else { |
3773 | 3773 | $text = '{{'. $template->getText() |
3774 | 3774 | . " | subject=$subject | body=$text | signature=$signature }}"; |
— | — | @@ -3812,7 +3812,7 @@ |
3813 | 3813 | $flags = $article->checkFlags( $flags ); |
3814 | 3814 | |
3815 | 3815 | if ( $flags & EDIT_UPDATE ) { |
3816 | | - $text .= $article->getContent(); |
| 3816 | + $text = $article->getContent() . $text; |
3817 | 3817 | } |
3818 | 3818 | |
3819 | 3819 | $dbw = wfGetDB( DB_MASTER ); |
Index: trunk/phase3/includes/api/ApiUpload.php |
— | — | @@ -59,12 +59,6 @@ |
60 | 60 | |
61 | 61 | // First check permission to upload |
62 | 62 | $this->checkPermissions( $wgUser ); |
63 | | - // Check permission to upload this file |
64 | | - $permErrors = $this->mUpload->verifyPermissions( $wgUser ); |
65 | | - if ( $permErrors !== true ) { |
66 | | - // Todo: more specific error message |
67 | | - $this->dieUsageMsg( array( 'badaccess-groups' ) ); |
68 | | - } |
69 | 63 | |
70 | 64 | // Fetch the file |
71 | 65 | $status = $this->mUpload->fetchFile(); |
— | — | @@ -76,6 +70,13 @@ |
77 | 71 | |
78 | 72 | // Check if the uploaded file is sane |
79 | 73 | $this->verifyUpload(); |
| 74 | + |
| 75 | + // Check permission to upload this file |
| 76 | + $permErrors = $this->mUpload->verifyPermissions( $wgUser ); |
| 77 | + if ( $permErrors !== true ) { |
| 78 | + // Todo: stash the upload and allow choosing a new name |
| 79 | + $this->dieUsageMsg( array( 'badaccess-groups' ) ); |
| 80 | + } |
80 | 81 | |
81 | 82 | // Check warnings if necessary |
82 | 83 | $warnings = $this->checkForWarnings(); |
— | — | @@ -198,9 +199,6 @@ |
199 | 200 | $this->dieUsage( 'The filename is not allowed', 'illegal-filename', |
200 | 201 | 0, array( 'filename' => $verification['filtered'] ) ); |
201 | 202 | break; |
202 | | - case UploadBase::OVERWRITE_EXISTING_FILE: |
203 | | - $this->dieUsage( 'Overwriting an existing file is not allowed', 'overwrite' ); |
204 | | - break; |
205 | 203 | case UploadBase::VERIFICATION_ERROR: |
206 | 204 | $this->getResult()->setIndexedTagName( $verification['details'], 'detail' ); |
207 | 205 | $this->dieUsage( 'This file did not pass file verification', 'verification-error', |
— | — | @@ -286,9 +284,19 @@ |
287 | 285 | |
288 | 286 | if ( !$status->isGood() ) { |
289 | 287 | $error = $status->getErrorsArray(); |
290 | | - $this->getResult()->setIndexedTagName( $error, 'error' ); |
| 288 | + |
| 289 | + if ( count( $error ) == 1 && $error[0][0] == 'async' ) { |
| 290 | + // The upload can not be performed right now, because the user |
| 291 | + // requested so |
| 292 | + return array( |
| 293 | + 'result' => 'Queued', |
| 294 | + 'sessionkey' => $error[0][1], |
| 295 | + ); |
| 296 | + } else { |
| 297 | + $this->getResult()->setIndexedTagName( $error, 'error' ); |
291 | 298 | |
292 | | - $this->dieUsage( 'An internal error occurred', 'internal-error', 0, $error ); |
| 299 | + $this->dieUsage( 'An internal error occurred', 'internal-error', 0, $error ); |
| 300 | + } |
293 | 301 | } |
294 | 302 | |
295 | 303 | $file = $this->mUpload->getLocalFile(); |
— | — | @@ -331,15 +339,22 @@ |
332 | 340 | 'ignorewarnings' => false, |
333 | 341 | 'file' => null, |
334 | 342 | 'url' => null, |
335 | | - 'asyncdownload' => false, |
336 | | - 'leavemessage' => false, |
| 343 | + |
337 | 344 | 'sessionkey' => null, |
338 | 345 | ); |
| 346 | + |
| 347 | + global $wgAllowAsyncCopyUploads; |
| 348 | + if ( $wgAllowAsyncCopyUploads ) { |
| 349 | + $params += array( |
| 350 | + 'asyncdownload' => false, |
| 351 | + 'leavemessage' => false, |
| 352 | + ); |
| 353 | + } |
339 | 354 | return $params; |
340 | 355 | } |
341 | 356 | |
342 | 357 | public function getParamDescription() { |
343 | | - return array( |
| 358 | + $params = array( |
344 | 359 | 'filename' => 'Target filename', |
345 | 360 | 'token' => 'Edit token. You can get one of these through prop=info', |
346 | 361 | 'comment' => 'Upload comment. Also used as the initial page text for new files if "text" is not specified', |
— | — | @@ -349,10 +364,19 @@ |
350 | 365 | 'ignorewarnings' => 'Ignore any warnings', |
351 | 366 | 'file' => 'File contents', |
352 | 367 | 'url' => 'Url to fetch the file from', |
353 | | - 'asyncdownload' => 'Make fetching a URL asyncronous', |
354 | | - 'leavemessage' => 'If asyncdownload is used, leave a message on the user talk page if finished', |
355 | 368 | 'sessionkey' => 'Session key returned by a previous upload that failed due to warnings', |
356 | 369 | ); |
| 370 | + |
| 371 | + global $wgAllowAsyncCopyUploads; |
| 372 | + if ( $wgAllowAsyncCopyUploads ) { |
| 373 | + $params += array( |
| 374 | + 'asyncdownload' => 'Make fetching a URL asynchronous', |
| 375 | + 'leavemessage' => 'If asyncdownload is used, leave a message on the user talk page if finished', |
| 376 | + ); |
| 377 | + } |
| 378 | + |
| 379 | + return $params; |
| 380 | + |
357 | 381 | } |
358 | 382 | |
359 | 383 | public function getDescription() { |
Index: trunk/phase3/includes/job/UploadFromUrlJob.php |
— | — | @@ -10,6 +10,8 @@ |
11 | 11 | * @ingroup JobQueue |
12 | 12 | */ |
13 | 13 | class UploadFromUrlJob extends Job { |
| 14 | + public $upload; |
| 15 | + protected $user; |
14 | 16 | |
15 | 17 | public function __construct( $title, $params, $id = 0 ) { |
16 | 18 | parent::__construct( 'uploadFromUrl', $title, $params, $id ); |
— | — | @@ -17,8 +19,8 @@ |
18 | 20 | |
19 | 21 | public function run() { |
20 | 22 | # Initialize this object and the upload object |
21 | | - $upload = new UploadFromUrl(); |
22 | | - $upload->initialize( |
| 23 | + $this->upload = new UploadFromUrl(); |
| 24 | + $this->upload->initialize( |
23 | 25 | $this->title->getText(), |
24 | 26 | $this->params['url'], |
25 | 27 | false |
— | — | @@ -26,34 +28,47 @@ |
27 | 29 | $this->user = User::newFromName( $this->params['userName'] ); |
28 | 30 | |
29 | 31 | # Fetch the file |
30 | | - $status = $upload->fetchFile(); |
| 32 | + $status = $this->upload->fetchFile(); |
31 | 33 | if ( !$status->isOk() ) { |
32 | 34 | $this->leaveMessage( $status ); |
33 | 35 | return; |
34 | 36 | } |
35 | 37 | |
| 38 | + # Verify upload |
| 39 | + $result = $this->upload->verifyUpload(); |
| 40 | + if ( $result['status'] != UploadBase::OK ) { |
| 41 | + $status = $this->upload->convertVerifyErrorToStatus( $result ); |
| 42 | + $this->leaveMessage( $status ); |
| 43 | + return; |
| 44 | + } |
| 45 | + |
36 | 46 | # Check warnings |
37 | 47 | if ( !$this->params['ignoreWarnings'] ) { |
38 | | - $warnings = $this->checkWarnings(); |
| 48 | + $warnings = $this->upload->checkWarnings(); |
39 | 49 | if ( $warnings ) { |
40 | 50 | if ( $this->params['leaveMessage'] ) { |
41 | 51 | $this->user->leaveUserMessage( |
42 | 52 | wfMsg( 'upload-warning-subj' ), |
43 | | - wfMsg( 'upload-warning-msg', $this->params['sessionKey'] ) |
| 53 | + wfMsg( 'upload-warning-msg', |
| 54 | + $this->params['sessionKey'], |
| 55 | + $this->params['url'] ) |
44 | 56 | ); |
45 | 57 | } else { |
46 | 58 | $this->storeResultInSession( 'Warning', |
47 | 59 | 'warnings', $warnings ); |
48 | 60 | } |
| 61 | + |
| 62 | + // FIXME: stash in session |
49 | 63 | return; |
50 | 64 | } |
51 | 65 | } |
52 | 66 | |
53 | 67 | # Perform the upload |
54 | | - $status = $upload->performUpload( |
| 68 | + $status = $this->upload->performUpload( |
55 | 69 | $this->params['comment'], |
56 | 70 | $this->params['pageText'], |
57 | | - $this->params['watch'] |
| 71 | + $this->params['watch'], |
| 72 | + $this->user |
58 | 73 | ); |
59 | 74 | $this->leaveMessage( $status ); |
60 | 75 | } |
— | — | @@ -67,13 +82,17 @@ |
68 | 83 | protected function leaveMessage( $status ) { |
69 | 84 | if ( $this->params['leaveMessage'] ) { |
70 | 85 | if ( $status->isGood() ) { |
71 | | - $file = $this->getLocalFile(); |
72 | | - |
73 | | - $this->user->leaveUserMessage( wfMsg( 'successfulupload' ), |
74 | | - wfMsg( 'upload-success-msg', $file->getDescriptionUrl() ) ); |
| 86 | + $this->user->leaveUserMessage( wfMsg( 'upload-success-subj' ), |
| 87 | + wfMsg( 'upload-success-msg', |
| 88 | + $this->upload->getTitle()->getText(), |
| 89 | + $this->params['url'] |
| 90 | + ) ); |
75 | 91 | } else { |
76 | 92 | $this->user->leaveUserMessage( wfMsg( 'upload-failure-subj' ), |
77 | | - wfMsg( 'upload-failure-msg', $status->getWikiText() ) ); |
| 93 | + wfMsg( 'upload-failure-msg', |
| 94 | + $status->getWikiText(), |
| 95 | + $this->params['url'] |
| 96 | + ) ); |
78 | 97 | } |
79 | 98 | } else { |
80 | 99 | if ( $status->isOk() ) { |
— | — | @@ -89,6 +108,7 @@ |
90 | 109 | |
91 | 110 | /** |
92 | 111 | * Store a result in the session data |
| 112 | + * THIS IS BROKEN. $_SESSION does not exist when using runJobs.php |
93 | 113 | * |
94 | 114 | * @param $result string The result (Success|Warning|Failure) |
95 | 115 | * @param $dataKey string The key of the extra data |
Index: trunk/phase3/includes/DefaultSettings.php |
— | — | @@ -425,6 +425,11 @@ |
426 | 426 | * The timeout for copy uploads is set by $wgHTTPTimeout. |
427 | 427 | */ |
428 | 428 | $wgAllowCopyUploads = false; |
| 429 | +/** |
| 430 | + * Allow asynchronous copy uploads. |
| 431 | + * This feature is experimental. |
| 432 | + */ |
| 433 | +$wgAllowAsyncCopyUploads = false; |
429 | 434 | |
430 | 435 | /** |
431 | 436 | * Max size for uploads, in bytes. Applies to all uploads. |