3 * Copyright (C) 2006 Daniel Kinzler, brightbyte.de
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 * http://www.gnu.org/copyleft/gpl.html
21 * @subpackage Maintenance
24 $optionsWithArgs = array( 'target' );
26 require_once( 'commandLine.inc' );
28 class ExtensionInstaller
{
34 function ExtensionInstaller( $name, $source, $target ) {
36 $this->source
= $source;
37 $this->target
= realpath( $target );
38 $this->extdir
= "$target/extensions";
39 $this->dir
= "{$this->extdir}/$name";
40 $this->incpath
= "extensions/$name";
42 #TODO: allow a subdir different from "extensions"
43 #TODO: allow a config file different from "LocalSettings.php"
46 function note( $msg ) {
50 function warn( $msg ) {
51 print "WARNING: $msg\n";
54 function error( $msg ) {
55 print "ERROR: $msg\n";
58 function prompt( $msg ) {
59 if ( function_exists( 'readline' ) ) {
60 $s = readline( $msg );
63 if ( !@$this->stdin
) $this->stdin
= fopen( 'php://stdin', 'r' );
64 if ( !$this->stdin
) die( "Failed to open stdin for user interaction!\n" );
69 $s = fgets( $this->stdin
);
76 function confirm( $msg ) {
78 $s = $this->prompt( $msg . " [yes/no]: ");
79 $s = strtolower( trim($s) );
81 if ( $s == 'yes' ||
$s == 'y' ) return true;
82 else if ( $s == 'no' ||
$s == 'n' ) return false;
83 else print "bad response: $s\n";
87 function deleteContents( $dir ) {
88 $ff = glob( $dir . "/*" );
91 foreach ( $ff as $f ) {
92 if ( is_dir( $f ) ) $this->deleteContents( $f );
97 function copyDir( $dir, $tgt ) {
98 $d = $tgt . '/' . basename( $dir );
100 if ( !file_exists( $d ) ) {
103 $this->error( "failed to create director $d" );
108 $ff = glob( $dir . "/*" );
109 if ( $ff === false ||
$ff === NULL ) return false;
111 foreach ( $ff as $f ) {
112 if ( is_dir( $f ) ) {
113 $ok = $this->copyDir( $f, $d );
114 if ( !$ok ) return false;
117 $t = $d . '/' . basename( $f );
118 $ok = copy( $f, $t );
121 $this->error( "failed to copy $f to $t" );
130 function fetchExtension( ) {
131 if ( file_exists( $this->dir
) && glob( $this->dir
. "/*" )
132 && realpath( $this->source
) != $this->dir
) {
134 if ( $this->confirm( "{$this->dir} exists and is not empty.\nDelete all files in that directory?" ) ) {
135 $this->deleteContents( $this->dir
);
142 preg_match( '!([-\w]+://)?.*?(\.[-\w\d.]+)?$!', $this->source
, $m );
145 if ( $ext ) $ext = strtolower( $ext );
147 $src = $this->source
;
149 if ( $proto && $ext ) { #remote file
150 $tmp = wfTempDir() . '/' . basename( $src );
152 $this->note( "fetching {$this->source}..." );
153 $ok = copy( $src, $tmp );
156 $this->error( "failed to download {$src}" );
164 if ( $proto ) { #assume SVN repository
165 $this->note( "SVN checkout of $src..." );
166 wfShellExec( 'svn co ' . escapeshellarg( $src ) . ' ' . escapeshellarg( $this->dir
), $code );
169 $this->error( "checkout failed for $src!" );
173 else { #local file or directory
174 $src = realpath ( $src );
176 if ( !file_exists( $src ) ) {
177 $this->error( "file not found: {$this->source}" );
181 if ( $ext === NULL ||
$ext === '') { #local dir
182 if ( $src == $this->dir
) {
183 $this->note( "files are already in the extension dir" );
187 $this->copyDir( $src, $this->extdir
);
189 else if ( $ext == '.tgz' ||
$ext == '.tar.gz' ) { #tgz file
190 $this->note( "extracting $src..." );
191 wfShellExec( 'tar zxvf ' . escapeshellarg( $src ) . ' -C ' . escapeshellarg( $this->extdir
), $code );
194 $this->error( "failed to extract $src!" );
198 else if ( $ext == '.zip' ) { #zip file
199 $this->note( "extracting $src..." );
200 wfShellExec( 'unzip ' . escapeshellarg( $src ) . ' -d ' . escapeshellarg( $this->extdir
) , $code );
203 $this->error( "failed to extract $src!" );
208 $this->error( "unknown file extension: $ext" );
213 if ( !file_exists( $this->dir
) && glob( $this->dir
. "/*" ) ) {
214 $this->error( "{$this->dir} does not exist or is empty. Something went wrong, sorry." );
218 #TODO: set permissions.... somehow. Copy from extension dir??
220 $this->note( "fetched extension to {$this->dir}" );
224 function patchLocalSettings( ) {
225 $f = $this->dir
. '/install.settings';
226 $t = $this->target
. '/LocalSettings.php';
228 #TODO: assert version ?!
229 #TODO: allow custom installer scripts
231 if ( !file_exists( $f ) ) {
232 $this->warn( "No install.settings file provided! Please read the instructions and edit LocalSettings.php manually." );
236 $settings = file_get_contents( $f );
239 $this->error( "failed to read settings from $f!" );
243 $settings = str_replace( '{{path}}', $this->incpath
, $settings );
245 #NOTE: keep php extension for backup file!
246 $bak = $this->target
. '/LocalSettings.install-' . $this->name
. '-' . wfTimestamp(TS_MW
) . '.bak.php';
248 $ok = copy( $t, $bak );
251 $this->warn( "failed to create backup of LocalSettings.php!" );
255 $this->note( "created backup of LocalSettings.php at $bak" );
258 $localsettings = file_get_contents( $t );
261 $this->error( "failed to read $t for patching!" );
265 $marker = "<@< extension {$this->name} >@>";
266 $blockpattern = "/\n\s*#\s*BEGIN\s*$marker.*END\s*$marker\s*/smi";
268 if ( preg_match( $blockpattern, $localsettings ) ) {
269 $localsettings = preg_replace( $blockpattern, "\n", $localsettings );
270 $this->warn( "removed old configuration block for extension {$this->name}!" );
273 $newblock= "\n# BEGIN $marker\n$settings\n# END $marker\n";
275 $localsettings = preg_replace( "/\?>\s*$/si", "$newblock?>", $localsettings );
277 $ok = file_put_contents( $t, $localsettings );
280 $this->error( "failed to patch $t!" );
284 $this->note( "successfully patched LocalSettings.php" );
290 function printNotices( ) {
293 if ( file_exists( $this->dir
. '/README' ) ) $files[] = 'README';
294 if ( file_exists( $this->dir
. '/INSTALL' ) ) $files[] = 'INSTALL';
297 $this->note( "no information files found in {$this->dir}" );
302 $this->note( "Please have a look at the following files in {$this->dir}," );
303 $this->note( "they may contain important information about {$this->name}." );
307 foreach ( $files as $f ) {
308 $this->note ( "\t* $f" );
318 if( !isset( $args[0] ) ) {
319 die( "USAGE: installExtension.php [options] name [source]\n" .
321 " --target <dir> mediawiki installation directory\n" .
323 " May be a local file (tgz or zip) or directory.\n" .
324 " May be the URL of a remote file (tgz or zip).\n" .
325 " May be a SVN repository\n"
331 # Default to SVN trunk. Perhaps change that to use the version of the present install,
332 # and/or use bundles at an official download location.
333 # Also, perhaps use the local systems versioin to select the right branch
334 $defsrc = "http://svn.wikimedia.org/svnroot/mediawiki/trunk/extensions/" . urlencode($name);
336 $src = isset ( $args[1] ) ?
$args[1] : $defsrc;
338 $tgt = isset ( $options['target'] ) ?
$options['target'] : $IP;
340 if ( !file_exists( "$tgt/LocalSettings.php" ) ) {
341 die("can't find $tgt/LocalSettings.php\n");
344 if ( !is_writable( "$tgt/LocalSettings.php" ) ) {
345 die("can't write to $tgt/LocalSettings.php\n");
348 if ( !file_exists( "$tgt/extensions" ) ) {
349 die("can't find $tgt/extensions\n");
352 if ( !is_writable( "$tgt/extensions" ) ) {
353 die("can't write to $tgt/extensions\n");
356 $installer = new ExtensionInstaller( $name, $src, $tgt );
358 $installer->note( "Installing extension {$installer->name} from {$installer->source} to {$installer->dir}" );
361 print "\tTHIS TOOL IS EXPERIMENTAL!\n";
362 print "\tEXPECT THE UNEXPECTED!\n";
365 if ( !$installer->confirm("continue") ) die("aborted\n");
367 $ok = $installer->fetchExtension();
368 if ( $ok ) $ok = $installer->patchLocalSettings();
369 if ( $ok ) $ok = $installer->printNotices();
370 if ( $ok ) $installer->note( "$name extension was installed successfully" );