Merge "Add SVG version of user icon in Vector personal portlet"
[lhc/web/wiklou.git] / includes / specials / SpecialVersion.php
1 <?php
2 /**
3 * Implements Special:Version
4 *
5 * Copyright © 2005 Ævar Arnfjörð Bjarmason
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License along
18 * with this program; if not, write to the Free Software Foundation, Inc.,
19 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 * http://www.gnu.org/copyleft/gpl.html
21 *
22 * @file
23 * @ingroup SpecialPage
24 */
25
26 /**
27 * Give information about the version of MediaWiki, PHP, the DB and extensions
28 *
29 * @ingroup SpecialPage
30 */
31 class SpecialVersion extends SpecialPage {
32
33 protected $firstExtOpened = false;
34
35 protected static $extensionTypes = false;
36
37 protected static $viewvcUrls = array(
38 'svn+ssh://svn.wikimedia.org/svnroot/mediawiki' => 'http://svn.wikimedia.org/viewvc/mediawiki',
39 'http://svn.wikimedia.org/svnroot/mediawiki' => 'http://svn.wikimedia.org/viewvc/mediawiki',
40 'https://svn.wikimedia.org/svnroot/mediawiki' => 'https://svn.wikimedia.org/viewvc/mediawiki',
41 );
42
43 public function __construct() {
44 parent::__construct( 'Version' );
45 }
46
47 /**
48 * main()
49 */
50 public function execute( $par ) {
51 global $wgSpecialVersionShowHooks, $IP;
52
53 $this->setHeaders();
54 $this->outputHeader();
55 $out = $this->getOutput();
56 $out->allowClickjacking();
57
58 if( $par !== 'Credits' ) {
59 $text =
60 $this->getMediaWikiCredits() .
61 $this->softwareInformation() .
62 $this->getEntryPointInfo() .
63 $this->getExtensionCredits();
64 if ( $wgSpecialVersionShowHooks ) {
65 $text .= $this->getWgHooks();
66 }
67
68 $out->addWikiText( $text );
69 $out->addHTML( $this->IPInfo() );
70
71 if ( $this->getRequest()->getVal( 'easteregg' ) ) {
72 // TODO: put something interesting here
73 }
74 } else {
75 // Credits sub page
76
77 // Header
78 $out->addHTML( wfMessage( 'version-credits-summary' )->parseAsBlock() );
79
80 $wikiText = file_get_contents( $IP . '/CREDITS' );
81
82 // Take everything from the first section onwards, to remove the (not localized) header
83 $wikiText = substr( $wikiText, strpos( $wikiText, '==' ) );
84
85 $out->addWikiText( $wikiText );
86 }
87 }
88
89 /**
90 * Returns wiki text showing the license information.
91 *
92 * @return string
93 */
94 private static function getMediaWikiCredits() {
95 $ret = Xml::element( 'h2', array( 'id' => 'mw-version-license' ), wfMessage( 'version-license' )->text() );
96
97 // This text is always left-to-right.
98 $ret .= '<div class="plainlinks">';
99 $ret .= "__NOTOC__
100 " . self::getCopyrightAndAuthorList() . "\n
101 " . wfMessage( 'version-license-info' )->text();
102 $ret .= '</div>';
103
104 return str_replace( "\t\t", '', $ret ) . "\n";
105 }
106
107 /**
108 * Get the "MediaWiki is copyright 2001-20xx by lots of cool guys" text
109 *
110 * @return String
111 */
112 public static function getCopyrightAndAuthorList() {
113 global $wgLang;
114
115 if ( defined( 'MEDIAWIKI_INSTALL' ) ) {
116 $othersLink = '[http://www.mediawiki.org/wiki/Special:Version/Credits ' . wfMessage( 'version-poweredby-others' )->text() . ']';
117 } else {
118 $othersLink = '[[Special:Version/Credits|' . wfMessage( 'version-poweredby-others' )->text() . ']]';
119 }
120
121 $authorList = array(
122 'Magnus Manske', 'Brion Vibber', 'Lee Daniel Crocker',
123 'Tim Starling', 'Erik Möller', 'Gabriel Wicke', 'Ævar Arnfjörð Bjarmason',
124 'Niklas Laxström', 'Domas Mituzas', 'Rob Church', 'Yuri Astrakhan',
125 'Aryeh Gregor', 'Aaron Schulz', 'Andrew Garrett', 'Raimond Spekking',
126 'Alexandre Emsenhuber', 'Siebrand Mazeland', 'Chad Horohoe',
127 'Roan Kattouw', 'Trevor Parscal', 'Bryan Tong Minh', 'Sam Reed',
128 'Victor Vasiliev', 'Rotem Liss', 'Platonides', 'Antoine Musso',
129 'Timo Tijhof', 'Daniel Kinzler', 'Jeroen De Dauw', $othersLink
130 );
131
132 return wfMessage( 'version-poweredby-credits', date( 'Y' ),
133 $wgLang->listToText( $authorList ) )->text();
134 }
135
136 /**
137 * Returns wiki text showing the third party software versions (apache, php, mysql).
138 *
139 * @return string
140 */
141 static function softwareInformation() {
142 $dbr = wfGetDB( DB_SLAVE );
143
144 // Put the software in an array of form 'name' => 'version'. All messages should
145 // be loaded here, so feel free to use wfMessage in the 'name'. Raw HTML or
146 // wikimarkup can be used.
147 $software = array();
148 $software['[https://www.mediawiki.org/ MediaWiki]'] = self::getVersionLinked();
149 $software['[http://www.php.net/ PHP]'] = phpversion() . " (" . PHP_SAPI . ")";
150 $software[$dbr->getSoftwareLink()] = $dbr->getServerInfo();
151
152 // Allow a hook to add/remove items.
153 wfRunHooks( 'SoftwareInfo', array( &$software ) );
154
155 $out = Xml::element( 'h2', array( 'id' => 'mw-version-software' ), wfMessage( 'version-software' )->text() ) .
156 Xml::openElement( 'table', array( 'class' => 'wikitable plainlinks', 'id' => 'sv-software' ) ) .
157 "<tr>
158 <th>" . wfMessage( 'version-software-product' )->text() . "</th>
159 <th>" . wfMessage( 'version-software-version' )->text() . "</th>
160 </tr>\n";
161
162 foreach( $software as $name => $version ) {
163 $out .= "<tr>
164 <td>" . $name . "</td>
165 <td dir=\"ltr\">" . $version . "</td>
166 </tr>\n";
167 }
168
169 return $out . Xml::closeElement( 'table' );
170 }
171
172 /**
173 * Return a string of the MediaWiki version with SVN revision if available.
174 *
175 * @param $flags String
176 * @return mixed
177 */
178 public static function getVersion( $flags = '' ) {
179 global $wgVersion, $IP;
180 wfProfileIn( __METHOD__ );
181
182 $gitInfo = self::getGitHeadSha1( $IP );
183 $svnInfo = self::getSvnInfo( $IP );
184 if ( !$svnInfo && !$gitInfo ) {
185 $version = $wgVersion;
186 } elseif ( $gitInfo && $flags === 'nodb' ) {
187 $shortSha1 = substr( $gitInfo, 0, 7 );
188 $version = "$wgVersion ($shortSha1)";
189 } elseif ( $gitInfo ) {
190 $shortSha1 = substr( $gitInfo, 0, 7 );
191 $shortSha1 = wfMessage( 'parentheses' )->params( $shortSha1 )->escaped();
192 $version = "$wgVersion $shortSha1";
193 } elseif ( $flags === 'nodb' ) {
194 $version = "$wgVersion (r{$svnInfo['checkout-rev']})";
195 } else {
196 $version = $wgVersion . ' ' .
197 wfMessage(
198 'version-svn-revision',
199 isset( $info['directory-rev'] ) ? $info['directory-rev'] : '',
200 $info['checkout-rev']
201 )->text();
202 }
203
204 wfProfileOut( __METHOD__ );
205 return $version;
206 }
207
208 /**
209 * Return a wikitext-formatted string of the MediaWiki version with a link to
210 * the SVN revision or the git SHA1 of head if available.
211 * Git is prefered over Svn
212 * The fallback is just $wgVersion
213 *
214 * @return mixed
215 */
216 public static function getVersionLinked() {
217 global $wgVersion;
218 wfProfileIn( __METHOD__ );
219
220 $gitVersion = self::getVersionLinkedGit();
221 if( $gitVersion ) {
222 $v = $gitVersion;
223 } else {
224 $svnVersion = self::getVersionLinkedSvn();
225 if( $svnVersion ) {
226 $v = $svnVersion;
227 } else {
228 $v = $wgVersion; // fallback
229 }
230 }
231
232 wfProfileOut( __METHOD__ );
233 return $v;
234 }
235
236 /**
237 * @return string wgVersion + a link to subversion revision of svn BASE
238 */
239 private static function getVersionLinkedSvn() {
240 global $IP;
241
242 $info = self::getSvnInfo( $IP );
243 if( !isset( $info['checkout-rev'] ) ) {
244 return false;
245 }
246
247 $linkText = wfMessage(
248 'version-svn-revision',
249 isset( $info['directory-rev'] ) ? $info['directory-rev'] : '',
250 $info['checkout-rev']
251 )->text();
252
253 if ( isset( $info['viewvc-url'] ) ) {
254 $version = "[{$info['viewvc-url']} $linkText]";
255 } else {
256 $version = $linkText;
257 }
258
259 return self::getwgVersionLinked() . " $version";
260 }
261
262 /**
263 * @return string
264 */
265 private static function getwgVersionLinked() {
266 global $wgVersion;
267 $versionUrl = "";
268 if( wfRunHooks( 'SpecialVersionVersionUrl', array( $wgVersion, &$versionUrl ) ) ) {
269 $versionParts = array();
270 preg_match( "/^(\d+\.\d+)/", $wgVersion, $versionParts );
271 $versionUrl = "https://www.mediawiki.org/wiki/MediaWiki_{$versionParts[1]}";
272 }
273 return "[$versionUrl $wgVersion]";
274 }
275
276 /**
277 * @return bool|string wgVersion + HEAD sha1 stripped to the first 7 chars. False on failure
278 */
279 private static function getVersionLinkedGit() {
280 global $IP;
281
282 $gitInfo = new GitInfo( $IP );
283 $headSHA1 = $gitInfo->getHeadSHA1();
284 if( !$headSHA1 ) {
285 return false;
286 }
287
288 $shortSHA1 = '(' . substr( $headSHA1, 0, 7 ) . ')';
289 $viewerUrl = $gitInfo->getHeadViewUrl();
290 if ( $viewerUrl !== false ) {
291 $shortSHA1 = "[$viewerUrl $shortSHA1]";
292 }
293 return self::getwgVersionLinked() . " $shortSHA1";
294 }
295
296 /**
297 * Returns an array with the base extension types.
298 * Type is stored as array key, the message as array value.
299 *
300 * TODO: ideally this would return all extension types, including
301 * those added by SpecialVersionExtensionTypes. This is not possible
302 * since this hook is passing along $this though.
303 *
304 * @since 1.17
305 *
306 * @return array
307 */
308 public static function getExtensionTypes() {
309 if ( self::$extensionTypes === false ) {
310 self::$extensionTypes = array(
311 'specialpage' => wfMessage( 'version-specialpages' )->text(),
312 'parserhook' => wfMessage( 'version-parserhooks' )->text(),
313 'variable' => wfMessage( 'version-variables' )->text(),
314 'media' => wfMessage( 'version-mediahandlers' )->text(),
315 'antispam' => wfMessage( 'version-antispam' )->text(),
316 'skin' => wfMessage( 'version-skins' )->text(),
317 'api' => wfMessage( 'version-api' )->text(),
318 'other' => wfMessage( 'version-other' )->text(),
319 );
320
321 wfRunHooks( 'ExtensionTypes', array( &self::$extensionTypes ) );
322 }
323
324 return self::$extensionTypes;
325 }
326
327 /**
328 * Returns the internationalized name for an extension type.
329 *
330 * @since 1.17
331 *
332 * @param $type String
333 *
334 * @return string
335 */
336 public static function getExtensionTypeName( $type ) {
337 $types = self::getExtensionTypes();
338 return isset( $types[$type] ) ? $types[$type] : $types['other'];
339 }
340
341 /**
342 * Generate wikitext showing extensions name, URL, author and description.
343 *
344 * @return String: Wikitext
345 */
346 function getExtensionCredits() {
347 global $wgExtensionCredits, $wgExtensionFunctions, $wgParser;
348
349 if ( !count( $wgExtensionCredits ) && !count( $wgExtensionFunctions ) ) {
350 return '';
351 }
352
353 $extensionTypes = self::getExtensionTypes();
354
355 /**
356 * @deprecated as of 1.17, use hook ExtensionTypes instead.
357 */
358 wfRunHooks( 'SpecialVersionExtensionTypes', array( &$this, &$extensionTypes ) );
359
360 $out = Xml::element( 'h2', array( 'id' => 'mw-version-ext' ), $this->msg( 'version-extensions' )->text() ) .
361 Xml::openElement( 'table', array( 'class' => 'wikitable plainlinks', 'id' => 'sv-ext' ) );
362
363 // Make sure the 'other' type is set to an array.
364 if ( !array_key_exists( 'other', $wgExtensionCredits ) ) {
365 $wgExtensionCredits['other'] = array();
366 }
367
368 // Find all extensions that do not have a valid type and give them the type 'other'.
369 foreach ( $wgExtensionCredits as $type => $extensions ) {
370 if ( !array_key_exists( $type, $extensionTypes ) ) {
371 $wgExtensionCredits['other'] = array_merge( $wgExtensionCredits['other'], $extensions );
372 }
373 }
374
375 // Loop through the extension categories to display their extensions in the list.
376 foreach ( $extensionTypes as $type => $message ) {
377 if ( $type != 'other' ) {
378 $out .= $this->getExtensionCategory( $type, $message );
379 }
380 }
381
382 // We want the 'other' type to be last in the list.
383 $out .= $this->getExtensionCategory( 'other', $extensionTypes['other'] );
384
385 if ( count( $wgExtensionFunctions ) ) {
386 $out .= $this->openExtType( $this->msg( 'version-extension-functions' )->text(), 'extension-functions' );
387 $out .= '<tr><td colspan="4">' . $this->listToText( $wgExtensionFunctions ) . "</td></tr>\n";
388 }
389
390 $tags = $wgParser->getTags();
391 $cnt = count( $tags );
392
393 if ( $cnt ) {
394 for ( $i = 0; $i < $cnt; ++$i ) {
395 $tags[$i] = "&lt;{$tags[$i]}&gt;";
396 }
397 $out .= $this->openExtType( $this->msg( 'version-parser-extensiontags' )->text(), 'parser-tags' );
398 $out .= '<tr><td colspan="4">' . $this->listToText( $tags ). "</td></tr>\n";
399 }
400
401 $fhooks = $wgParser->getFunctionHooks();
402 if( count( $fhooks ) ) {
403 $out .= $this->openExtType( $this->msg( 'version-parser-function-hooks' )->text(), 'parser-function-hooks' );
404 $out .= '<tr><td colspan="4">' . $this->listToText( $fhooks ) . "</td></tr>\n";
405 }
406
407 $out .= Xml::closeElement( 'table' );
408
409 return $out;
410 }
411
412 /**
413 * Creates and returns the HTML for a single extension category.
414 *
415 * @since 1.17
416 *
417 * @param $type String
418 * @param $message String
419 *
420 * @return string
421 */
422 protected function getExtensionCategory( $type, $message ) {
423 global $wgExtensionCredits;
424
425 $out = '';
426
427 if ( array_key_exists( $type, $wgExtensionCredits ) && count( $wgExtensionCredits[$type] ) > 0 ) {
428 $out .= $this->openExtType( $message, 'credits-' . $type );
429
430 usort( $wgExtensionCredits[$type], array( $this, 'compare' ) );
431
432 foreach ( $wgExtensionCredits[$type] as $extension ) {
433 $out .= $this->getCreditsForExtension( $extension );
434 }
435 }
436
437 return $out;
438 }
439
440 /**
441 * Callback to sort extensions by type.
442 * @param $a array
443 * @param $b array
444 * @return int
445 */
446 function compare( $a, $b ) {
447 if( $a['name'] === $b['name'] ) {
448 return 0;
449 } else {
450 return $this->getLanguage()->lc( $a['name'] ) > $this->getLanguage()->lc( $b['name'] )
451 ? 1
452 : -1;
453 }
454 }
455
456 /**
457 * Creates and formats the credits for a single extension and returns this.
458 *
459 * @param $extension Array
460 *
461 * @return string
462 */
463 function getCreditsForExtension( array $extension ) {
464 $name = isset( $extension['name'] ) ? $extension['name'] : '[no name]';
465
466 $vcsText = false;
467
468 if ( isset( $extension['path'] ) ) {
469 $gitInfo = new GitInfo( dirname( $extension['path'] ) );
470 $gitHeadSHA1 = $gitInfo->getHeadSHA1();
471 if ( $gitHeadSHA1 !== false ) {
472 $vcsText = '(' . substr( $gitHeadSHA1, 0, 7 ) . ')';
473 $gitViewerUrl = $gitInfo->getHeadViewUrl();
474 if ( $gitViewerUrl !== false ) {
475 $vcsText = "[$gitViewerUrl $vcsText]";
476 }
477 } else {
478 $svnInfo = self::getSvnInfo( dirname( $extension['path'] ) );
479 # Make subversion text/link.
480 if ( $svnInfo !== false ) {
481 $directoryRev = isset( $svnInfo['directory-rev'] ) ? $svnInfo['directory-rev'] : null;
482 $vcsText = $this->msg( 'version-svn-revision', $directoryRev, $svnInfo['checkout-rev'] )->text();
483 $vcsText = isset( $svnInfo['viewvc-url'] ) ? '[' . $svnInfo['viewvc-url'] . " $vcsText]" : $vcsText;
484 }
485 }
486 }
487
488 # Make main link (or just the name if there is no URL).
489 if ( isset( $extension['url'] ) ) {
490 $mainLink = "[{$extension['url']} $name]";
491 } else {
492 $mainLink = $name;
493 }
494
495 if ( isset( $extension['version'] ) ) {
496 $versionText = '<span class="mw-version-ext-version">' .
497 $this->msg( 'version-version', $extension['version'] )->text() .
498 '</span>';
499 } else {
500 $versionText = '';
501 }
502
503 # Make description text.
504 $description = isset ( $extension['description'] ) ? $extension['description'] : '';
505
506 if( isset ( $extension['descriptionmsg'] ) ) {
507 # Look for a localized description.
508 $descriptionMsg = $extension['descriptionmsg'];
509
510 if( is_array( $descriptionMsg ) ) {
511 $descriptionMsgKey = $descriptionMsg[0]; // Get the message key
512 array_shift( $descriptionMsg ); // Shift out the message key to get the parameters only
513 array_map( "htmlspecialchars", $descriptionMsg ); // For sanity
514 $description = $this->msg( $descriptionMsgKey, $descriptionMsg )->text();
515 } else {
516 $description = $this->msg( $descriptionMsg )->text();
517 }
518 }
519
520 if ( $vcsText !== false ) {
521 $extNameVer = "<tr>
522 <td><em>$mainLink $versionText</em></td>
523 <td><em>$vcsText</em></td>";
524 } else {
525 $extNameVer = "<tr>
526 <td colspan=\"2\"><em>$mainLink $versionText</em></td>";
527 }
528
529 $author = isset ( $extension['author'] ) ? $extension['author'] : array();
530 $extDescAuthor = "<td>$description</td>
531 <td>" . $this->listAuthors( $author, false ) . "</td>
532 </tr>\n";
533
534 return $extNameVer . $extDescAuthor;
535 }
536
537 /**
538 * Generate wikitext showing hooks in $wgHooks.
539 *
540 * @return String: wikitext
541 */
542 private function getWgHooks() {
543 global $wgHooks;
544
545 if ( count( $wgHooks ) ) {
546 $myWgHooks = $wgHooks;
547 ksort( $myWgHooks );
548
549 $ret = Xml::element( 'h2', array( 'id' => 'mw-version-hooks' ), $this->msg( 'version-hooks' )->text() ) .
550 Xml::openElement( 'table', array( 'class' => 'wikitable', 'id' => 'sv-hooks' ) ) .
551 "<tr>
552 <th>" . $this->msg( 'version-hook-name' )->text() . "</th>
553 <th>" . $this->msg( 'version-hook-subscribedby' )->text() . "</th>
554 </tr>\n";
555
556 foreach ( $myWgHooks as $hook => $hooks ) {
557 $ret .= "<tr>
558 <td>$hook</td>
559 <td>" . $this->listToText( $hooks ) . "</td>
560 </tr>\n";
561 }
562
563 $ret .= Xml::closeElement( 'table' );
564 return $ret;
565 } else
566 return '';
567 }
568
569 private function openExtType( $text, $name = null ) {
570 $opt = array( 'colspan' => 4 );
571 $out = '';
572
573 if( $this->firstExtOpened ) {
574 // Insert a spacing line
575 $out .= '<tr class="sv-space">' . Html::element( 'td', $opt ) . "</tr>\n";
576 }
577 $this->firstExtOpened = true;
578
579 if( $name ) {
580 $opt['id'] = "sv-$name";
581 }
582
583 $out .= "<tr>" . Xml::element( 'th', $opt, $text ) . "</tr>\n";
584
585 return $out;
586 }
587
588 /**
589 * Get information about client's IP address.
590 *
591 * @return String: HTML fragment
592 */
593 private function IPInfo() {
594 $ip = str_replace( '--', ' - ', htmlspecialchars( $this->getRequest()->getIP() ) );
595 return "<!-- visited from $ip -->\n<span style='display:none'>visited from $ip</span>";
596 }
597
598 /**
599 * Return a formatted unsorted list of authors
600 *
601 * @param $authors mixed: string or array of strings
602 * @return String: HTML fragment
603 */
604 function listAuthors( $authors ) {
605 $list = array();
606 foreach( (array)$authors as $item ) {
607 if ( $item == '...' ) {
608 $list[] = $this->msg( 'version-poweredby-others' )->text();
609 } elseif ( substr( $item, -5 ) == ' ...]' ) {
610 $list[] = substr( $item, 0, -4 ) . $this->msg( 'version-poweredby-others' )->text() . "]";
611 } else {
612 $list[] = $item;
613 }
614 }
615 return $this->listToText( $list, false );
616 }
617
618 /**
619 * Convert an array of items into a list for display.
620 *
621 * @param array $list of elements to display
622 * @param $sort Boolean: whether to sort the items in $list
623 *
624 * @return String
625 */
626 function listToText( $list, $sort = true ) {
627 $cnt = count( $list );
628
629 if ( $cnt == 1 ) {
630 // Enforce always returning a string
631 return (string)self::arrayToString( $list[0] );
632 } elseif ( $cnt == 0 ) {
633 return '';
634 } else {
635 if ( $sort ) {
636 sort( $list );
637 }
638 return $this->getLanguage()->listToText( array_map( array( __CLASS__, 'arrayToString' ), $list ) );
639 }
640 }
641
642 /**
643 * Convert an array or object to a string for display.
644 *
645 * @param $list Mixed: will convert an array to string if given and return
646 * the paramater unaltered otherwise
647 *
648 * @return Mixed
649 */
650 public static function arrayToString( $list ) {
651 if( is_array( $list ) && count( $list ) == 1 ) {
652 $list = $list[0];
653 }
654 if( is_object( $list ) ) {
655 $class = wfMessage( 'parentheses' )->params( get_class( $list ) )->escaped();
656 return $class;
657 } elseif ( !is_array( $list ) ) {
658 return $list;
659 } else {
660 if( is_object( $list[0] ) ) {
661 $class = get_class( $list[0] );
662 } else {
663 $class = $list[0];
664 }
665 return wfMessage( 'parentheses' )->params( "$class, {$list[1]}" )->escaped();
666 }
667 }
668
669 /**
670 * Get an associative array of information about a given path, from its .svn
671 * subdirectory. Returns false on error, such as if the directory was not
672 * checked out with subversion.
673 *
674 * Returned keys are:
675 * Required:
676 * checkout-rev The revision which was checked out
677 * Optional:
678 * directory-rev The revision when the directory was last modified
679 * url The subversion URL of the directory
680 * repo-url The base URL of the repository
681 * viewvc-url A ViewVC URL pointing to the checked-out revision
682 * @param $dir string
683 * @return array|bool
684 */
685 public static function getSvnInfo( $dir ) {
686 // http://svnbook.red-bean.com/nightly/en/svn.developer.insidewc.html
687 $entries = $dir . '/.svn/entries';
688
689 if( !file_exists( $entries ) ) {
690 return false;
691 }
692
693 $lines = file( $entries );
694 if ( !count( $lines ) ) {
695 return false;
696 }
697
698 // check if file is xml (subversion release <= 1.3) or not (subversion release = 1.4)
699 if( preg_match( '/^<\?xml/', $lines[0] ) ) {
700 // subversion is release <= 1.3
701 if( !function_exists( 'simplexml_load_file' ) ) {
702 // We could fall back to expat... YUCK
703 return false;
704 }
705
706 // SimpleXml whines about the xmlns...
707 wfSuppressWarnings();
708 $xml = simplexml_load_file( $entries );
709 wfRestoreWarnings();
710
711 if( $xml ) {
712 foreach( $xml->entry as $entry ) {
713 if( $xml->entry[0]['name'] == '' ) {
714 // The directory entry should always have a revision marker.
715 if( $entry['revision'] ) {
716 return array( 'checkout-rev' => intval( $entry['revision'] ) );
717 }
718 }
719 }
720 }
721
722 return false;
723 }
724
725 // Subversion is release 1.4 or above.
726 if ( count( $lines ) < 11 ) {
727 return false;
728 }
729
730 $info = array(
731 'checkout-rev' => intval( trim( $lines[3] ) ),
732 'url' => trim( $lines[4] ),
733 'repo-url' => trim( $lines[5] ),
734 'directory-rev' => intval( trim( $lines[10] ) )
735 );
736
737 if ( isset( self::$viewvcUrls[$info['repo-url']] ) ) {
738 $viewvc = str_replace(
739 $info['repo-url'],
740 self::$viewvcUrls[$info['repo-url']],
741 $info['url']
742 );
743
744 $viewvc .= '/?pathrev=';
745 $viewvc .= urlencode( $info['checkout-rev'] );
746 $info['viewvc-url'] = $viewvc;
747 }
748
749 return $info;
750 }
751
752 /**
753 * Retrieve the revision number of a Subversion working directory.
754 *
755 * @param string $dir directory of the svn checkout
756 *
757 * @return Integer: revision number as int
758 */
759 public static function getSvnRevision( $dir ) {
760 $info = self::getSvnInfo( $dir );
761
762 if ( $info === false ) {
763 return false;
764 } elseif ( isset( $info['checkout-rev'] ) ) {
765 return $info['checkout-rev'];
766 } else {
767 return false;
768 }
769 }
770
771 /**
772 * @param string $dir directory of the git checkout
773 * @return bool|String sha1 of commit HEAD points to
774 */
775 public static function getGitHeadSha1( $dir ) {
776 $repo = new GitInfo( $dir );
777 return $repo->getHeadSHA1();
778 }
779
780 /**
781 * Get the list of entry points and their URLs
782 * @return string Wikitext
783 */
784 public function getEntryPointInfo() {
785 global $wgArticlePath, $wgScriptPath;
786 $scriptPath = $wgScriptPath ? $wgScriptPath : "/";
787 $entryPoints = array(
788 'version-entrypoints-articlepath' => $wgArticlePath,
789 'version-entrypoints-scriptpath' => $scriptPath,
790 'version-entrypoints-index-php' => wfScript( 'index' ),
791 'version-entrypoints-api-php' => wfScript( 'api' ),
792 'version-entrypoints-load-php' => wfScript( 'load' ),
793 );
794
795 $language = $this->getLanguage();
796 $thAttribures = array(
797 'dir' => $language->getDir(),
798 'lang' => $language->getCode()
799 );
800 $out = Html::element( 'h2', array( 'id' => 'mw-version-entrypoints' ), $this->msg( 'version-entrypoints' )->text() ) .
801 Html::openElement( 'table',
802 array(
803 'class' => 'wikitable plainlinks',
804 'id' => 'mw-version-entrypoints-table',
805 'dir' => 'ltr',
806 'lang' => 'en'
807 )
808 ) .
809 Html::openElement( 'tr' ) .
810 Html::element( 'th', $thAttribures, $this->msg( 'version-entrypoints-header-entrypoint' )->text() ) .
811 Html::element( 'th', $thAttribures, $this->msg( 'version-entrypoints-header-url' )->text() ) .
812 Html::closeElement( 'tr' );
813
814 foreach ( $entryPoints as $message => $value ) {
815 $url = wfExpandUrl( $value, PROTO_RELATIVE );
816 $out .= Html::openElement( 'tr' ) .
817 // ->text() looks like it should be ->parse(), but this function
818 // returns wikitext, not HTML, boo
819 Html::rawElement( 'td', array(), $this->msg( $message )->text() ) .
820 Html::rawElement( 'td', array(), Html::rawElement( 'code', array(), "[$url $value]" ) ) .
821 Html::closeElement( 'tr' );
822 }
823
824 $out .= Html::closeElement( 'table' );
825 return $out;
826 }
827
828 protected function getGroupName() {
829 return 'wiki';
830 }
831
832 }