Index: trunk/extensions/WebDAV/WebDav.php |
— | — | @@ -1,779 +0,0 @@ |
2 | | -<?php |
3 | | - |
4 | | -define( 'MW_SEARCH_LIMIT', 50 ); |
5 | | - |
6 | | -require_once( './lib/HTTP/WebDAV/Server.php' ); |
7 | | - |
8 | | -class WebDavServer extends HTTP_WebDAV_Server { |
9 | | - |
10 | | - function init() { |
11 | | - parent::init(); |
12 | | - |
13 | | - # Prepend script path component to path components |
14 | | - array_unshift( $this->pathComponents, array_pop( $this->baseUrlComponents['pathComponents'] ) ); |
15 | | - } |
16 | | - |
17 | | - function getAllowedMethods() { |
18 | | - return array( 'OPTIONS', 'PROPFIND', 'GET', 'HEAD', 'DELETE', 'PUT', 'REPORT', 'SEARCH' ); |
19 | | - } |
20 | | - |
21 | | - function options( &$serverOptions ) { |
22 | | - parent::options( &$serverOptions ); |
23 | | - |
24 | | - if ( $serverOptions['xpath']->evaluate( 'boolean(/D:options/D:activity-collection-set)' ) ) { |
25 | | - $this->setResponseHeader( 'Content-Type: text/xml; charset="utf-8"' ); |
26 | | - |
27 | | - echo "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n"; |
28 | | - echo "<D:options-response xmlns:D=\"DAV:\">\n"; |
29 | | - echo ' <D:activity-collection-set><D:href>' . $this->getUrl( array( 'path' => 'deltav.php/act' ) ) . "</D:href></D:activity-collection-set>\n"; |
30 | | - echo "</D:options-response>\n"; |
31 | | - } |
32 | | - |
33 | | - return true; |
34 | | - } |
35 | | - |
36 | | - function propfind( &$serverOptions ) { |
37 | | - if ( empty( $this->pathComponents ) ) { |
38 | | - return; |
39 | | - } |
40 | | - $pathComponent = array_shift( $this->pathComponents ); |
41 | | - if ( $pathComponent != 'deltav.php' && $pathComponent != 'webdav.php' ) { |
42 | | - return; |
43 | | - } |
44 | | - |
45 | | - if ( $pathComponent == 'deltav.php' ) { |
46 | | - if ( empty( $this->pathComponents ) ) { |
47 | | - return; |
48 | | - } |
49 | | - $pathComponent = array_shift( $this->pathComponents ); |
50 | | - if ( $pathComponent != 'bc' && $pathComponent != 'bln' && $pathComponent != 'vcc' && $pathComponent != 'ver' ) { |
51 | | - return; |
52 | | - } |
53 | | - |
54 | | - if ( $pathComponent == 'vcc' ) { |
55 | | - if ( empty( $this->pathComponents ) ) { |
56 | | - return; |
57 | | - } |
58 | | - $pathComponent = array_shift( $this->pathComponents ); |
59 | | - if ( $pathComponent != 'default' ) { |
60 | | - return; |
61 | | - } |
62 | | - if ( !empty( $this->pathComponents ) ) { |
63 | | - return; |
64 | | - } |
65 | | - |
66 | | - if ( isset( $serverOptions['label'] ) ) { |
67 | | - return $this->propfindBln( $serverOptions, $serverOptions['label'] ); |
68 | | - } |
69 | | - |
70 | | - return $this->propfindVcc( $serverOptions ); |
71 | | - } |
72 | | - |
73 | | - if ( empty( $this->pathComponents ) ) { |
74 | | - return; |
75 | | - } |
76 | | - $revisionId = array_shift( $this->pathComponents ); |
77 | | - |
78 | | - if ( $pathComponent == 'bc' ) { |
79 | | - return $this->propfindBc( $serverOptions, $revisionId, $this->pathComponents ); |
80 | | - } |
81 | | - |
82 | | - if ( !empty( $this->pathComponents ) ) { |
83 | | - return; |
84 | | - } |
85 | | - |
86 | | - if ( $pathComponent == 'bln' ) { |
87 | | - return $this->propfindBln( $serverOptions, $revisionId ); |
88 | | - } |
89 | | - |
90 | | - return $this->propfindVer( $serverOptions, $revisionId ); |
91 | | - } |
92 | | - |
93 | | - if ( isset( $serverOptions['label'] ) ) { |
94 | | - # TODO: Verify revision belongs to this resource, or should we care? |
95 | | - return $this->propfindVer( $serverOptions, $serverOptions['label'] ); |
96 | | - } |
97 | | - |
98 | | - $serverOptions['namespaces']['http://subversion.tigris.org/xmlns/dav/'] = 'V'; |
99 | | - |
100 | | - $status = array(); |
101 | | - |
102 | | - # Handle root collection |
103 | | - if ( empty( $this->pathComponents ) ) { |
104 | | - global $wgSitename; |
105 | | - |
106 | | - $response = array(); |
107 | | - $response['path'] = 'webdav.php/'; |
108 | | - |
109 | | - # TODO: Use Main_Page revision? |
110 | | - $response['props'][] = WebDavServer::mkprop( 'checked-in', $this->getUrl( array( 'path' => 'deltav.php/ver' ) ) ); |
111 | | - |
112 | | - $response['props'][] = WebDavServer::mkprop( 'displayname', $wgSitename ); |
113 | | - $response['props'][] = WebDavServer::mkprop( 'getcontentlength', 0 ); |
114 | | - $response['props'][] = WebDavServer::mkprop( 'getcontenttype', 'httpd/unix-directory' ); |
115 | | - $response['props'][] = WebDavServer::mkprop( 'resourcetype', 'collection' ); |
116 | | - $response['props'][] = WebDavServer::mkprop( 'version-controlled-configuration', $this->getUrl( array( 'path' => 'deltav.php/vcc/default' ) ) ); |
117 | | - |
118 | | - $response['props'][] = WebDavServer::mkprop( 'http://subversion.tigris.org/xmlns/dav/', 'baseline-relative-path', null ); |
119 | | - |
120 | | - # TODO: Don't hardcode this |
121 | | - $response['props'][] = WebDavServer::mkprop( 'http://subversion.tigris.org/xmlns/dav/', 'repository-uuid', '87a9137c-6795-46f8-83b9-2ee953e66e08' ); |
122 | | - |
123 | | - $status[] = $response; |
124 | | - |
125 | | - # Don't descend if depth is zero |
126 | | - if ( empty( $serverOptions['depth'] ) ) { |
127 | | - return $status; |
128 | | - } |
129 | | - } |
130 | | - |
131 | | - # TODO: Use $wgMemc |
132 | | - $dbr =& wfGetDB( DB_SLAVE ); |
133 | | - |
134 | | - # TODO: Think harder about pages' hierarchical structure. The trouble is most filesystems don't support directories which themselves have file content, which is a problem for making pages descendents of other pages. |
135 | | - $where = array(); |
136 | | - if ( !empty( $this->pathComponents ) ) { |
137 | | - $where[] = 'page_title = ' . $dbr->addQuotes( implode( '/', $this->pathComponents ) ); |
138 | | - } |
139 | | - |
140 | | - $whereClause = null; |
141 | | - if ( !empty( $where ) ) { |
142 | | - $whereClause = ' WHERE ' . implode( ' AND ', $where ); |
143 | | - } |
144 | | - $results = $dbr->query( ' |
145 | | - SELECT page_title, page_latest, page_len, page_touched |
146 | | - FROM page' . $whereClause ); |
147 | | - |
148 | | - while ( ( $result = $dbr->fetchRow( $results ) ) !== false ) { |
149 | | - # TODO: Should maybe not be using page_title as URL component, but it's currently what we do elsewhere |
150 | | - $title = Title::newFromUrl( $result[0] ); |
151 | | - |
152 | | - $response = array(); |
153 | | - $response['path'] = 'webdav.php/' . $result[0]; |
154 | | - $response['props'][] = WebDavServer::mkprop( 'checked-in', $this->getUrl( array( 'path' => 'deltav.php/ver/' . $result[1] ) ) ); |
155 | | - $response['props'][] = WebDavServer::mkprop( 'displayname', $title->getText() ); |
156 | | - $response['props'][] = WebDavServer::mkprop( 'getcontentlength', $result[2] ); |
157 | | - $response['props'][] = WebDavServer::mkprop( 'getcontenttype', 'text/x-wiki' ); |
158 | | - $response['props'][] = WebDavServer::mkprop( 'getlastmodified', wfTimestamp( TS_UNIX, $result[3] ) ); |
159 | | - $response['props'][] = WebDavServer::mkprop( 'resourcetype', null ); |
160 | | - $response['props'][] = WebDavServer::mkprop( 'version-controlled-configuration', $this->getUrl( array( 'path' => 'deltav.php/vcc/default' ) ) ); |
161 | | - |
162 | | - $response['props'][] = WebDavServer::mkprop( 'http://subversion.tigris.org/xmlns/dav/', 'baseline-relative-path', $result[0] ); |
163 | | - |
164 | | - $status[] = $response; |
165 | | - } |
166 | | - |
167 | | - return $status; |
168 | | - } |
169 | | - |
170 | | - function propfindBc( &$serverOptions, $revisionId, $pathComponents ) { |
171 | | - $serverOptions['namespaces']['http://subversion.tigris.org/xmlns/dav/'] = 'V'; |
172 | | - |
173 | | - $status = array(); |
174 | | - |
175 | | - # TODO: Verify $revisionId is valid |
176 | | - # Handle root collection |
177 | | - if ( empty( $pathComponents ) ) { |
178 | | - global $wgSitename; |
179 | | - |
180 | | - $response = array(); |
181 | | - $response['path'] = 'deltav.php/bc/' . $revisionId . '/'; |
182 | | - # TODO: Use Main_Page revision? |
183 | | - $response['props'][] = WebDavServer::mkprop( 'checked-in', $this->getUrl( array( 'path' => 'deltav.php/ver' ) ) ); |
184 | | - |
185 | | - $response['props'][] = WebDavServer::mkprop( 'displayname', $wgSitename ); |
186 | | - $response['props'][] = WebDavServer::mkprop( 'getcontentlength', 0 ); |
187 | | - $response['props'][] = WebDavServer::mkprop( 'getcontenttype', 'httpd/unix-directory' ); |
188 | | - $response['props'][] = WebDavServer::mkprop( 'resourcetype', 'collection' ); |
189 | | - $response['props'][] = WebDavServer::mkprop( 'version-controlled-configuration', $this->getUrl( array( 'path' => 'deltav.php/vcc/default' ) ) ); |
190 | | - $response['props'][] = WebDavServer::mkprop( 'version-name', null ); |
191 | | - |
192 | | - $response['props'][] = WebDavServer::mkprop( 'http://subversion.tigris.org/xmlns/dav/', 'baseline-relative-path', null ); |
193 | | - |
194 | | - # TODO: Don't hardcode this |
195 | | - $response['props'][] = WebDavServer::mkprop( 'http://subversion.tigris.org/xmlns/dav/', 'repository-uuid', '87a9137c-6795-46f8-83b9-2ee953e66e08' ); |
196 | | - |
197 | | - $status[] = $response; |
198 | | - |
199 | | - # Don't descend if depth is zero |
200 | | - if ( empty( $serverOptions['depth'] ) ) { |
201 | | - return $status; |
202 | | - } |
203 | | - } |
204 | | - |
205 | | - # TODO: Use $wgMemc |
206 | | - $dbr =& wfGetDB( DB_SLAVE ); |
207 | | - |
208 | | - # TODO: Think harder about pages' hierarchical structure. The trouble is most filesystems don't support directories which themselves have file content, which is a problem for making pages descendents of other pages. |
209 | | - $where = array(); |
210 | | - $where[] = 'rev_page = page_id'; |
211 | | - $where[] = 'rev_id <= ' . $dbr->addQuotes( $revisionId ); |
212 | | - if ( !empty( $pathComponents ) ) { |
213 | | - $where[] = 'page_title = ' . $dbr->addQuotes( implode( '/', $pathComponents ) ); |
214 | | - } |
215 | | - |
216 | | - $whereClause = null; |
217 | | - if ( !empty( $where ) ) { |
218 | | - $whereClause = ' WHERE ' . implode( ' AND ', $where ); |
219 | | - } |
220 | | - $results = $dbr->query( ' |
221 | | - SELECT page_title, MAX(rev_id), page_len, page_touched |
222 | | - FROM page, revision' . $whereClause . ' |
223 | | - GROUP BY page_id' ); |
224 | | - |
225 | | - while ( ( $result = $dbr->fetchRow( $results ) ) !== false ) { |
226 | | - # TODO: Should maybe not be using page_title as URL component, but it's currently what we do elsewhere |
227 | | - $title = Title::newFromUrl( $result[0] ); |
228 | | - |
229 | | - $response = array(); |
230 | | - $response['path'] = 'deltav.php/bc/' . $revisionId . '/' . $result[0]; |
231 | | - $response['props'][] = WebDavServer::mkprop( 'checked-in', $this->getUrl( array( 'path' => 'deltav.php/ver/' . $result[1] ) ) ); |
232 | | - $response['props'][] = WebDavServer::mkprop( 'displayname', $title->getText() ); |
233 | | - $response['props'][] = WebDavServer::mkprop( 'getcontentlength', $result[2] ); |
234 | | - $response['props'][] = WebDavServer::mkprop( 'getcontenttype', 'text/x-wiki' ); |
235 | | - $response['props'][] = WebDavServer::mkprop( 'getlastmodified', wfTimestamp( TS_UNIX, $result[3] ) ); |
236 | | - $response['props'][] = WebDavServer::mkprop( 'resourcetype', null ); |
237 | | - $response['props'][] = WebDavServer::mkprop( 'version-controlled-configuration', $this->getUrl( array( 'path' => 'deltav.php/vcc/default' ) ) ); |
238 | | - $response['props'][] = WebDavServer::mkprop( 'version-name', $result[1] ); |
239 | | - |
240 | | - $response['props'][] = WebDavServer::mkprop( 'http://subversion.tigris.org/xmlns/dav/', 'baseline-relative-path', $result[0] ); |
241 | | - |
242 | | - # TODO: Don't hardcode this |
243 | | - $response['props'][] = WebDavServer::mkprop( 'http://subversion.tigris.org/xmlns/dav/', 'repository-uuid', '87a9137c-6795-46f8-83b9-2ee953e66e08' ); |
244 | | - |
245 | | - $status[] = $response; |
246 | | - } |
247 | | - |
248 | | - return $status; |
249 | | - } |
250 | | - |
251 | | - function propfindBln( &$serverOptions, $revisionId ) { |
252 | | - $response = array(); |
253 | | - $response['path'] = 'bln/' . $revisionId; |
254 | | - $response['props'][] = WebDavServer::mkprop( 'baseline-collection', $this->getUrl( array( 'path' => 'deltav.php/bc/' . $revisionId . '/' ) ) ); |
255 | | - $response['props'][] = WebDavServer::mkprop( 'version-name', $revisionId ); |
256 | | - |
257 | | - return array( $response ); |
258 | | - } |
259 | | - |
260 | | - function propfindVcc( &$serverOptions ) { |
261 | | - # TODO: Use $wgMemc |
262 | | - $dbr =& wfGetDB( DB_SLAVE ); |
263 | | - |
264 | | - $results = $dbr->query( ' |
265 | | - SELECT MAX(rev_id) |
266 | | - FROM revision' ); |
267 | | - |
268 | | - if ( ( $result = $dbr->fetchRow( $results ) ) === false ) { |
269 | | - return; |
270 | | - } |
271 | | - |
272 | | - $response = array(); |
273 | | - $response['path'] = 'deltav.php/vcc/default'; |
274 | | - $response['props'][] = WebDavServer::mkprop( 'checked-in', $this->getUrl( array( 'path' => 'deltav.php/bln/' . $result[0] ) ) ); |
275 | | - |
276 | | - return array( $response ); |
277 | | - } |
278 | | - |
279 | | - function propfindVer( &$serverOptions, $revisionId ) { |
280 | | - $response = array(); |
281 | | - $response['path'] = 'deltav.php/ver/' . $revisionId; |
282 | | - $response['props'][] = WebDavServer::mkprop( 'resourcetype', null ); |
283 | | - #$response['props'][] = WebDavServer::mkprop( 'version-controlled-configuration', $this->getUrl( array( 'path' => 'deltav.php/vcc/default' ) ) ); |
284 | | - |
285 | | - #$response['props'][] = WebDavServer::mkprop( 'http://subversion.tigris.org/xmlns/dav/', 'baseline-relative-path', null ); |
286 | | - |
287 | | - return array( $response ); |
288 | | - } |
289 | | - |
290 | | - function getRawPage() { |
291 | | - if ( empty( $this->pathComponents ) ) { |
292 | | - return; |
293 | | - } |
294 | | - $pathComponent = array_shift( $this->pathComponents ); |
295 | | - if ( $pathComponent != 'deltav.php' && $pathComponent != 'webdav.php' ) { |
296 | | - return; |
297 | | - } |
298 | | - |
299 | | - if ( $pathComponent == 'deltav.php' ) { |
300 | | - if ( empty( $this->pathComponents ) ) { |
301 | | - return; |
302 | | - } |
303 | | - $pathComponent = array_shift( $this->pathComponents ); |
304 | | - if ( $pathComponent != 'bc' && $pathComponent != 'ver' ) { |
305 | | - return; |
306 | | - } |
307 | | - |
308 | | - if ( empty( $this->pathComponents ) ) { |
309 | | - return; |
310 | | - } |
311 | | - $revisionId = array_shift( $this->pathComponents ); |
312 | | - |
313 | | - if ( $pathComponent == 'bc' ) { |
314 | | - $title = Title::newFromUrl( implode( '/', $this->pathComponents ) ); |
315 | | - if (!isset( $title )) { |
316 | | - $title = Title::newMainPage(); |
317 | | - } |
318 | | - } else { |
319 | | - if ( !empty( $this->pathComponents ) ) { |
320 | | - return; |
321 | | - } |
322 | | - |
323 | | - $revision = Revision::newFromId( $revisionId ); |
324 | | - $title = $revision->getTitle(); |
325 | | - } |
326 | | - } else { |
327 | | - if ( isset( $serverOptions['label'] ) ) { |
328 | | - # TODO: Verify revision belongs to this resource, or should we care? |
329 | | - $revisionId = $serverOptions['label']; |
330 | | - } |
331 | | - |
332 | | - $title = Title::newFromUrl( implode( '/', $this->pathComponents ) ); |
333 | | - if (!isset( $title )) { |
334 | | - $title = Title::newMainPage(); |
335 | | - } |
336 | | - } |
337 | | - |
338 | | - $mediaWiki = new MediaWiki(); |
339 | | - $article = $mediaWiki->articleFromTitle( $title ); |
340 | | - |
341 | | - $rawPage = new RawPage( $article ); |
342 | | - |
343 | | - if ( isset( $revisionId ) ) { |
344 | | - $rawPage->mOldId = $revisionId; |
345 | | - } |
346 | | - |
347 | | - return $rawPage; |
348 | | - } |
349 | | - |
350 | | - # RawPage::view handles Content-Type, Cache-Control, etc. and we don't want get_response_helper to overwrite, but MediaWiki doesn't let us get response headers. It could work if we kept setResponseHeader updated with headers_list on PHP 5. |
351 | | - function get_wrapper() { |
352 | | - $rawPage = $this->getRawPage(); |
353 | | - if ( !isset( $rawPage ) ) { |
354 | | - $this->setResponseStatus( false, false ); |
355 | | - return; |
356 | | - } |
357 | | - |
358 | | - $rawPage->view(); |
359 | | - } |
360 | | - |
361 | | - function head_wrapper() { |
362 | | - $rawPage = $this->getRawPage(); |
363 | | - if ( !isset( $rawPage ) ) { |
364 | | - $this->setResponseStatus( false, false ); |
365 | | - return; |
366 | | - } |
367 | | - |
368 | | - # TODO: Does MediaWiki handle HEAD requests specially? |
369 | | - ob_start(); |
370 | | - $rawPage->view(); |
371 | | - ob_end_clean(); |
372 | | - } |
373 | | - |
374 | | - function delete( $serverOptions ) { |
375 | | - global $wgUser; |
376 | | - |
377 | | - if ( !$wgUser->isAllowed( 'delete' ) ) { |
378 | | - $this->setResponseStatus( '401 Unauthorized' ); |
379 | | - return; |
380 | | - } |
381 | | - |
382 | | - if ( wfReadOnly() ) { |
383 | | - $this->setResponseStatus( '403 Forbidden' ); |
384 | | - return; |
385 | | - } |
386 | | - |
387 | | - if ( empty( $this->pathComponents ) ) { |
388 | | - return; |
389 | | - } |
390 | | - $pathComponent = array_shift( $this->pathComponents ); |
391 | | - if ( $pathComponent != 'webdav.php' ) { |
392 | | - return; |
393 | | - } |
394 | | - |
395 | | - $title = Title::newFromUrl( implode( '/', $this->pathComponents ) ); |
396 | | - if (!isset( $title )) { |
397 | | - $title = Title::newMainPage(); |
398 | | - } |
399 | | - |
400 | | - $mediaWiki = new MediaWiki(); |
401 | | - $article = $mediaWiki->articleFromTitle( $title ); |
402 | | - |
403 | | - # Must check if article exists to avoid 500 Internal Server Error |
404 | | - |
405 | | - # No way to get reason for deletion. Can't use null: MySQL returned error "<tt>1048: Column 'log_comment' cannot be null (localhost)</tt>". |
406 | | - $article->doDelete( null ); |
407 | | - } |
408 | | - |
409 | | - function put( $serverOptions ) { |
410 | | - global $wgUser; |
411 | | - |
412 | | - if ( !$wgUser->isAllowed( 'edit' ) ) { |
413 | | - $this->setResponseStatus( '401 Unauthorized' ); |
414 | | - return; |
415 | | - } |
416 | | - |
417 | | - if ( wfReadOnly() ) { |
418 | | - $this->setResponseStatus( '403 Forbidden' ); |
419 | | - return; |
420 | | - } |
421 | | - |
422 | | - if ( empty( $this->pathComponents ) ) { |
423 | | - return; |
424 | | - } |
425 | | - $pathComponent = array_shift( $this->pathComponents ); |
426 | | - if ( $pathComponent != 'webdav.php' ) { |
427 | | - return; |
428 | | - } |
429 | | - |
430 | | - $title = Title::newFromUrl( implode( '/', $this->pathComponents ) ); |
431 | | - if (!isset( $title )) { |
432 | | - $title = Title::newMainPage(); |
433 | | - } |
434 | | - |
435 | | - if ( !$title->exists() && !$title->userCan( 'create' ) ) { |
436 | | - $this->setResponseStatus( '401 Unauthorized' ); |
437 | | - return; |
438 | | - } |
439 | | - |
440 | | - $mediaWiki = new MediaWiki(); |
441 | | - $article = $mediaWiki->articleFromTitle( $title ); |
442 | | - |
443 | | - if ( ( $handle = $this->openRequestBody() ) === false ) { |
444 | | - return; |
445 | | - } |
446 | | - |
447 | | - $text = null; |
448 | | - while ( !feof( $handle ) ) { |
449 | | - if ( ( $buffer = fread( $handle, 4096 ) ) === false ) { |
450 | | - return; |
451 | | - } |
452 | | - |
453 | | - $text .= $buffer; |
454 | | - } |
455 | | - |
456 | | - $article->doEdit( $text, null ); |
457 | | - |
458 | | - return true; |
459 | | - } |
460 | | - |
461 | | - function versionTreeReport( &$serverOptions ) { |
462 | | - if ( empty( $this->pathComponents ) ) { |
463 | | - return; |
464 | | - } |
465 | | - $pathComponent = array_shift( $this->pathComponents ); |
466 | | - if ( $pathComponent != 'deltav.php' && $pathComponent != 'webdav.php' ) { |
467 | | - return; |
468 | | - } |
469 | | - |
470 | | - $serverOptions['props'] = array(); |
471 | | - foreach ( $serverOptions['xpath']->query( '/D:version-tree/D:prop/*' ) as $node) { |
472 | | - $serverOptions['props'][] = $this->mkprop( $node->namespaceURI, $node->localName, null ); |
473 | | - |
474 | | - # Namespace handling |
475 | | - if ( empty( $node->namespaceURI ) || empty( $node->prefix ) ) { |
476 | | - continue; |
477 | | - } |
478 | | - |
479 | | - # http://bugs.php.net/bug.php?id=42082 |
480 | | - #$serverOptions['namespaces'][$node->namespaceURI] = $node->prefix; |
481 | | - } |
482 | | - |
483 | | - if (empty($serverOptions['props'])) { |
484 | | - $serverOptions['props'] = $serverOptions['xpath']->evaluate( 'local-name(/D:version-tree/*)' ); |
485 | | - } |
486 | | - |
487 | | - $status = array(); |
488 | | - |
489 | | - # Handle root collection |
490 | | - if ( empty( $this->pathComponents ) ) { |
491 | | - $response = array(); |
492 | | - $response['props'][] = WebDavServer::mkprop( 'getcontentlength', 0 ); |
493 | | - $response['props'][] = WebDavServer::mkprop( 'getcontenttype', 'httpd/unix-directory' ); |
494 | | - $response['props'][] = WebDavServer::mkprop( 'resourcetype', 'collection' ); |
495 | | - |
496 | | - $status[] = $response; |
497 | | - |
498 | | - # Don't descend if depth is zero |
499 | | - if ( empty( $serverOptions['depth'] ) ) { |
500 | | - return $status; |
501 | | - } |
502 | | - } |
503 | | - |
504 | | - # TODO: Use $wgMemc |
505 | | - $dbr =& wfGetDB( DB_SLAVE ); |
506 | | - |
507 | | - # TODO: Think harder about pages' hierarchical structure. The trouble is most filesystems don't support directories which themselves have file content, which is a problem for making pages descendents of other pages. |
508 | | - $where = array(); |
509 | | - $where[] = 'rev_page = page_id'; |
510 | | - if ( !empty( $this->pathComponents ) ) { |
511 | | - $where[] = 'page_title = ' . $dbr->addQuotes( implode( '/', $this->pathComponents ) ); |
512 | | - } |
513 | | - |
514 | | - $whereClause = null; |
515 | | - if ( !empty( $where ) ) { |
516 | | - $whereClause = ' WHERE ' . implode( ' AND ', $where ); |
517 | | - } |
518 | | - $results = $dbr->query( ' |
519 | | - SELECT page_title, rev_id, rev_comment, rev_user_text, rev_len, rev_timestamp, rev_parent_id |
520 | | - FROM page, revision' . $whereClause ); |
521 | | - |
522 | | - $successors = array(); |
523 | | - while ( ( $result = $dbr->fetchRow( $results ) ) !== false ) { |
524 | | - $response = array(); |
525 | | - $response['path'] = 'deltav.php/ver/' . $result[1]; |
526 | | - $response['props'][] = WebDavServer::mkprop( 'comment', $result[2] ); |
527 | | - $response['props'][] = WebDavServer::mkprop( 'creator-displayname', $result[3] ); |
528 | | - $response['props'][] = WebDavServer::mkprop( 'getcontentlength', $result[4] ); |
529 | | - $response['props'][] = WebDavServer::mkprop( 'getcontenttype', 'text/x-wiki' ); |
530 | | - $response['props'][] = WebDavServer::mkprop( 'getlastmodified', wfTimestamp( TS_UNIX, $result[5] ) ); |
531 | | - $response['props'][] = WebDavServer::mkprop( 'predecessor-set', array( $result[6] ) ); |
532 | | - $response['props'][] = WebDavServer::mkprop( 'resourcetype', null ); |
533 | | - $response['props'][] = WebDavServer::mkprop( 'successor-set', array() ); |
534 | | - $response['props'][] = WebDavServer::mkprop( 'version-name', $result[1] ); |
535 | | - |
536 | | - $status[$result[1]] = $response; |
537 | | - |
538 | | - # Build successor-set |
539 | | - $successors[$result[6]][] = $result[1]; |
540 | | - } |
541 | | - |
542 | | - return $status; |
543 | | - } |
544 | | - |
545 | | - function updateReport( &$serverOptions ) { |
546 | | - if ( empty( $this->pathComponents ) ) { |
547 | | - return; |
548 | | - } |
549 | | - $pathComponent = array_shift( $this->pathComponents ); |
550 | | - if ( $pathComponent != 'deltav.php' ) { |
551 | | - return; |
552 | | - } |
553 | | - |
554 | | - if ( empty( $this->pathComponents ) ) { |
555 | | - return; |
556 | | - } |
557 | | - $pathComponent = array_shift( $this->pathComponents ); |
558 | | - if ( $pathComponent != 'vcc' ) { |
559 | | - return; |
560 | | - } |
561 | | - |
562 | | - if ( empty( $this->pathComponents ) ) { |
563 | | - return; |
564 | | - } |
565 | | - $pathComponent = array_shift( $this->pathComponents ); |
566 | | - if ( $pathComponent != 'default' ) { |
567 | | - return; |
568 | | - } |
569 | | - if ( !empty( $this->pathComponents ) ) { |
570 | | - return; |
571 | | - } |
572 | | - |
573 | | - # TODO: Can we ignore this? |
574 | | - if ( isset( $serverOptions['label'] ) ) { |
575 | | - return; |
576 | | - } |
577 | | - |
578 | | - $serverOptions['xpath']->registerNamespace( 'S', 'svn:' ); |
579 | | - |
580 | | - # TODO: Error checking? |
581 | | - $targetRevision = $serverOptions['xpath']->evaluate( 'string(/S:update-report/S:target-revision)' ); |
582 | | - |
583 | | - # src-path is a misnomer, it's a URL |
584 | | - $srcPath = $serverOptions['xpath']->evaluate( 'string(/S:update-report/S:src-path)' ); |
585 | | - $srcComponents = $this->parseUrl( $srcPath ); |
586 | | - $srcComponents['pathComponents'] = array_slice( $srcComponents['pathComponents'], count( $this->baseUrlComponents['pathComponents'] ) + 1 ); |
587 | | - |
588 | | - # TODO: Use $wgMemc |
589 | | - $dbr =& wfGetDB( DB_SLAVE ); |
590 | | - |
591 | | - $entryConditions = array(); |
592 | | - foreach ( $serverOptions['xpath']->query( '/S:update-report/S:entry' ) as $node ) { |
593 | | - $entryConditions[$node->textContent] = null; |
594 | | - if ( !$node->hasAttribute( 'start-empty' ) ) { |
595 | | - |
596 | | - # TODO: Error checking? |
597 | | - $entryConditions[$node->textContent] = 'new.rev_id > ' . $dbr->addQuotes( $node->getAttribute( 'rev' ) ); |
598 | | - } |
599 | | - } |
600 | | - |
601 | | - function cmp( $a, $b ) { |
602 | | - return strlen( $a ) - strlen( $b ); |
603 | | - } |
604 | | - uksort( $entryConditions, 'cmp' ); |
605 | | - |
606 | | - $entryCondition = null; |
607 | | - foreach ( $entryConditions as $path => $revisionCondition ) { |
608 | | - if ( !empty( $path ) ) { |
609 | | - $pathCondition = '(page_title = ' . $dbr->addQuotes( $path ) . ' OR page_title LIKE \'' . $dbr->escapeLike( $path ) . '/%\')'; |
610 | | - |
611 | | - if ( !empty( $revisionCondition ) ) { |
612 | | - $revisionCondition = ' AND ' . $revisionCondition; |
613 | | - } |
614 | | - $revisionCondition = $pathCondition . $revisionCondition; |
615 | | - |
616 | | - if ( !empty( $entryCondition ) ) { |
617 | | - $entryCondition = ' AND ' . $entryCondition; |
618 | | - } |
619 | | - $entryCondition = 'NOT ' . $pathCondition . $entryCondition; |
620 | | - } |
621 | | - |
622 | | - if ( !empty( $revisionCondition ) ) { |
623 | | - if ( !empty( $entryCondition ) ) { |
624 | | - $revisionCondition = '(' . $revisionCondition; |
625 | | - $entryCondition = ' OR ' . $entryCondition . ')'; |
626 | | - } |
627 | | - $entryCondition = $revisionCondition . $entryCondition; |
628 | | - } |
629 | | - } |
630 | | - if ( !empty( $entryCondition ) ) { |
631 | | - $entryCondition = ' AND ' . $entryCondition; |
632 | | - } |
633 | | - |
634 | | - $where = array(); |
635 | | - if ( !empty( $targetRevision ) ) { |
636 | | - $where[] = 'old.rev_id <= ' . $dbr->addQuotes( $targetRevision ); |
637 | | - } |
638 | | - if ( !empty( $srcComponents['pathComponents'] ) ) { |
639 | | - $where[] = 'page_title = ' . $dbr->addQuotes( implode( '/', $srcComponents['pathComponents'] ) ); |
640 | | - } |
641 | | - |
642 | | - if ( empty( $targetRevision ) ) { |
643 | | - $results = $dbr->query( ' |
644 | | - SELECT MAX(rev_id) |
645 | | - FROM revision' ); |
646 | | - |
647 | | - if ( ( $result = $dbr->fetchRow( $results ) ) === false ) { |
648 | | - return; |
649 | | - } |
650 | | - |
651 | | - $targetRevision = $result[0]; |
652 | | - } |
653 | | - |
654 | | - $this->setResponseHeader( 'Content-Type: text/xml; charset="utf-8"', false ); |
655 | | - |
656 | | - echo "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n"; |
657 | | - echo "<S:update-report xmlns:D=\"DAV:\" xmlns:S=\"svn:\" xmlns:V=\"http://subversion.tigris.org/xmlns/dav\" send-all=\"true\">\n"; |
658 | | - |
659 | | - # TODO: Get the revision from the report request |
660 | | - echo " <S:target-revision rev=\"$targetRevision\"/>\n"; |
661 | | - |
662 | | - # TODO: Use Main_Page revision? |
663 | | - echo " <S:open-directory rev=\"$targetRevision\">\n"; |
664 | | - echo ' <D:checked-in><D:href>' . $this->getUrl( array( 'path' => 'deltav.php/ver' ) ) . "</D:href></D:checked-in>\n"; |
665 | | - |
666 | | - $whereClause = null; |
667 | | - if ( !empty( $where ) ) { |
668 | | - $whereClause = ' WHERE ' . implode( ' AND ', $where ); |
669 | | - } |
670 | | - |
671 | | - # SUM(new.rev_id IS NULL) is the number of revisions which didn't match the entry condition |
672 | | - # TODO: Invert entry condition to make getting the base revision cleaner |
673 | | - $results = $dbr->query( ' |
674 | | - SELECT page_title, SUM(new.rev_id IS NULL), MAX(CASE WHEN new.rev_id IS NULL THEN old.rev_id ELSE NULL END), MAX(new.rev_id) |
675 | | - FROM page |
676 | | - JOIN revision AS old |
677 | | - ON page_id = old.rev_page |
678 | | - LEFT JOIN revision AS new |
679 | | - ON old.rev_id = new.rev_id' . $entryCondition . $whereClause . ' |
680 | | - GROUP BY page_id |
681 | | - HAVING COUNT(new.rev_id)' ); |
682 | | - |
683 | | - while ( ( $result = $dbr->fetchRow( $results ) ) !== false ) { |
684 | | - $addOrOpen = 'add'; |
685 | | - $baseRev = null; |
686 | | - |
687 | | - $newText = Revision::newFromId( $result[3] )->revText(); |
688 | | - $oldText = null; |
689 | | - |
690 | | - if ( $result[1] > 0 ) { |
691 | | - $addOrOpen = 'open'; |
692 | | - $baseRev = ' rev="' . $result[2] . '"'; |
693 | | - |
694 | | - $oldText = Revision::newFromId( $result[2] )->revText(); |
695 | | - } |
696 | | - |
697 | | - # TODO: Use only last path component |
698 | | - echo " <S:$addOrOpen-file name=\"$result[0]\"$baseRev>\n"; |
699 | | - |
700 | | - echo ' <D:checked-in><D:href>' . $this->getUrl( array( 'path' => 'deltav.php/ver/' . $result[3] ) ) . "</D:href></D:checked-in>\n"; |
701 | | - echo ' <S:txdelta>' . base64_encode( $this->getSvnDiff( $oldText, $newText ) ) . "\n</S:txdelta>\n"; |
702 | | - echo ' <S:prop><V:md5-checksum>' . md5( $newText ) . "</V:md5-checksum></S:prop>\n"; |
703 | | - echo " </S:$addOrOpen-file>\n"; |
704 | | - } |
705 | | - |
706 | | - echo " </S:open-directory>\n"; |
707 | | - echo "</S:update-report>\n"; |
708 | | - |
709 | | - return true; |
710 | | - } |
711 | | - |
712 | | - function getSvnDiff( $oldText, $newText ) { |
713 | | - $instructions = chr( 0x80 | strlen( $newText ) ); |
714 | | - if ( strlen( $newText ) > 0x37 ) { |
715 | | - $instructions = "\x80" . $this->encodeInt( strlen( $newText ) ); |
716 | | - } |
717 | | - |
718 | | - return "SVN\x00\x00" |
719 | | - . $this->encodeInt( strlen( $oldText ) ) |
720 | | - . $this->encodeInt( strlen( $newText ) ) |
721 | | - . $this->encodeInt( strlen( $instructions ) ) |
722 | | - . $this->encodeInt( strlen( $newText ) ) |
723 | | - . $instructions |
724 | | - . $newText; |
725 | | - } |
726 | | - |
727 | | - function encodeInt( $int ) { |
728 | | - # Least seven bits |
729 | | - $bytes = chr( $int & 0x7f ); |
730 | | - |
731 | | - # Shift by seven bits until nothing remains |
732 | | - while ( 0 < $int >>= 7 ) { |
733 | | - # Prepend seven bits with the eighth bit, the continuation bit, set, to the string of bytes |
734 | | - $bytes = chr( $int & 0x7f | 0x80 ) . $bytes; |
735 | | - } |
736 | | - |
737 | | - return $bytes; |
738 | | - } |
739 | | - |
740 | | - function search( &$serverOptions ) { |
741 | | - $serverOptions['namespaces']['http://subversion.tigris.org/xmlns/dav/'] = 'V'; |
742 | | - |
743 | | - $status = array(); |
744 | | - |
745 | | - $search = SearchEngine::create(); |
746 | | - |
747 | | - # TODO: Use (int)$wgUser->getOption( 'searchlimit' ); |
748 | | - $search->setLimitOffset( MW_SEARCH_LIMIT ); |
749 | | - |
750 | | - $results = $search->searchText( $serverOptions['xpath']->evaluate( 'string(/D:searchrequest/D:basicsearch/D:where/D:contains)' ) ); |
751 | | - |
752 | | - while ( ( $result = $results->next() ) !== false ) { |
753 | | - $title = $result->getTitle(); |
754 | | - $revision = Revision::newFromTitle( $title ); |
755 | | - |
756 | | - $response = array(); |
757 | | - $response['path'] = 'webdav.php/' . $title->getPrefixedUrl(); |
758 | | - $response['props'][] = WebDavServer::mkprop( 'checked-in', $this->getUrl( array( 'path' => 'deltav.php/ver/' . $revision->getId() ) ) ); |
759 | | - $response['props'][] = WebDavServer::mkprop( 'displayname', $title->getText() ); |
760 | | - $response['props'][] = WebDavServer::mkprop( 'getcontentlength', $revision->getSize() ); |
761 | | - $response['props'][] = WebDavServer::mkprop( 'getcontenttype', 'text/x-wiki' ); |
762 | | - $response['props'][] = WebDavServer::mkprop( 'getlastmodified', wfTimestamp( TS_UNIX, $revision->mTimestamp ) ); |
763 | | - $response['props'][] = WebDavServer::mkprop( 'resourcetype', null ); |
764 | | - $response['props'][] = WebDavServer::mkprop( 'version-controlled-configuration', $this->getUrl( array( 'path' => 'deltav.php/vcc/default' ) ) ); |
765 | | - |
766 | | - $response['props'][] = WebDavServer::mkprop( 'http://subversion.tigris.org/xmlns/dav/', 'baseline-relative-path', $title->getFullUrl() ); |
767 | | - $response['score'] = $result->getScore(); |
768 | | - |
769 | | - $status[] = $response; |
770 | | - } |
771 | | - |
772 | | - # TODO: Check if we exceed our limit |
773 | | - #$response = array(); |
774 | | - #$response['status'] = '507 Insufficient Storage'; |
775 | | - |
776 | | - #$status[] = $response; |
777 | | - |
778 | | - return $status; |
779 | | - } |
780 | | -} |