Subversion-Projekte lars-tiefland.php_share

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
1 lars 1
<?php
2
/*
3
 * $Id: Phing.php 385 2008-08-19 18:09:17Z mrook $
4
 *
5
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
6
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
7
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
8
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
9
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
10
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
11
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
12
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
13
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
14
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
15
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
16
 *
17
 * This software consists of voluntary contributions made by many individuals
18
 * and is licensed under the LGPL. For more information please see
19
 * <http://phing.info>.
20
 */
21
 
22
require_once 'phing/Project.php';
23
require_once 'phing/ProjectComponent.php';
24
require_once 'phing/Target.php';
25
require_once 'phing/Task.php';
26
 
27
include_once 'phing/BuildException.php';
28
include_once 'phing/ConfigurationException.php';
29
include_once 'phing/BuildEvent.php';
30
 
31
include_once 'phing/parser/Location.php';
32
include_once 'phing/parser/ExpatParser.php';
33
include_once 'phing/parser/AbstractHandler.php';
34
include_once 'phing/parser/ProjectConfigurator.php';
35
include_once 'phing/parser/RootHandler.php';
36
include_once 'phing/parser/ProjectHandler.php';
37
include_once 'phing/parser/TaskHandler.php';
38
include_once 'phing/parser/TargetHandler.php';
39
include_once 'phing/parser/DataTypeHandler.php';
40
include_once 'phing/parser/NestedElementHandler.php';
41
 
42
include_once 'phing/system/util/Properties.php';
43
include_once 'phing/util/StringHelper.php';
44
include_once 'phing/system/io/PhingFile.php';
45
include_once 'phing/system/io/OutputStream.php';
46
include_once 'phing/system/io/FileOutputStream.php';
47
include_once 'phing/system/io/FileReader.php';
48
include_once 'phing/system/util/Register.php';
49
 
50
/**
51
 * Entry point into Phing.  This class handles the full lifecycle of a build -- from
52
 * parsing & handling commandline arguments to assembling the project to shutting down
53
 * and cleaning up in the end.
54
 *
55
 * If you are invoking Phing from an external application, this is still
56
 * the class to use.  Your applicaiton can invoke the start() method, passing
57
 * any commandline arguments or additional properties.
58
 *
59
 * @author    Andreas Aderhold <andi@binarycloud.com>
60
 * @author    Hans Lellelid <hans@xmpl.org>
61
 * @version   $Revision: 1.51 $
62
 * @package   phing
63
 */
64
class Phing {
65
 
66
	/** The default build file name */
67
	const DEFAULT_BUILD_FILENAME = "build.xml";
68
 
69
	/** Our current message output status. Follows Project::MSG_XXX */
70
	private static $msgOutputLevel = Project::MSG_INFO;
71
 
72
	/** PhingFile that we are using for configuration */
73
	private $buildFile = null;
74
 
75
	/** The build targets */
76
	private $targets = array();
77
 
78
	/**
79
	 * Set of properties that are passed in from commandline or invoking code.
80
	 * @var Properties
81
	 */
82
	private static $definedProps;
83
 
84
	/** Names of classes to add as listeners to project */
85
	private $listeners = array();
86
 
87
	private $loggerClassname = null;
88
 
89
	/** The class to handle input (can be only one). */
90
	private $inputHandlerClassname;
91
 
92
	/** Indicates if this phing should be run */
93
	private $readyToRun = false;
94
 
95
	/** Indicates we should only parse and display the project help information */
96
	private $projectHelp = false;
97
 
98
	/** Used by utility function getResourcePath() */
99
	private static $importPaths;
100
 
101
	/** System-wide static properties (moved from System) */
102
	private static $properties = array();
103
 
104
	/** Static system timer. */
105
	private static $timer;
106
 
107
	/** The current Project */
108
	private static $currentProject;
109
 
110
	/** Whether to capture PHP errors to buffer. */
111
	private static $phpErrorCapture = false;
112
 
113
	/** Array of captured PHP errors */
114
	private static $capturedPhpErrors = array();
115
 
116
	/**
117
	 * @var OUtputStream Stream for standard output.
118
	 */
119
	private static $out;
120
 
121
	/**
122
	 * @var OutputStream Stream for error output.
123
	 */
124
	private static $err;
125
 
126
	/**
127
	 * @var boolean Whether we are using a logfile.
128
	 */
129
	private static $isLogFileUsed = false;
130
 
131
	/**
132
	 * Array to hold original ini settings that Phing changes (and needs
133
	 * to restore in restoreIni() method).
134
	 *
135
	 * @var array Struct of array(setting-name => setting-value)
136
	 * @see restoreIni()
137
	 */
138
	private static $origIniSettings = array();
139
 
140
	/**
141
	 * Entry point allowing for more options from other front ends.
142
	 *
143
	 * This method encapsulates the complete build lifecycle.
144
	 *
145
	 * @param array $args The commandline args passed to phing shell script.
146
	 * @param array $additionalUserProperties   Any additional properties to be passed to Phing (alternative front-end might implement this).
147
	 *                                          These additional properties will be available using the getDefinedProperty() method and will
148
	 *                                          be added to the project's "user" properties
149
	 * @see execute()
150
	 * @see runBuild()
151
	 * @throws Exception - if there is an error during build
152
	 */
153
	public static function start($args, array $additionalUserProperties = null) {
154
 
155
		try {
156
			$m = new Phing();
157
			$m->execute($args);
158
		} catch (Exception $exc) {
159
			self::handleLogfile();
160
			throw $exc;
161
		}
162
 
163
		if ($additionalUserProperties !== null) {
164
			foreach($additionalUserProperties as $key => $value) {
165
				$m->setDefinedProperty($key, $value);
166
			}
167
		}
168
 
169
		try {
170
			$m->runBuild();
171
		} catch(Exception $exc) {
172
			self::handleLogfile();
173
			throw $exc;
174
		}
175
 
176
		// everything fine, shutdown
177
		self::handleLogfile();
178
	}
179
 
180
	/**
181
	 * Prints the message of the Exception if it's not null.
182
	 * @param Exception $t
183
	 */
184
	public static function printMessage(Exception $t) {
185
		if (self::$err === null) { // Make sure our error output is initialized
186
			self::initializeOutputStreams();
187
		}
188
		if (self::getMsgOutputLevel() >= Project::MSG_VERBOSE) {
189
			self::$err->write($t->__toString() . PHP_EOL);
190
		} else {
191
			self::$err->write($t->getMessage() . PHP_EOL);
192
		}
193
	}
194
 
195
	/**
196
	 * Sets the stdout and stderr streams if they are not already set.
197
	 */
198
	private static function initializeOutputStreams() {
199
		if (self::$out === null) {
200
			self::$out = new OutputStream(fopen("php://stdout", "w"));
201
		}
202
		if (self::$err === null) {
203
			self::$err = new OutputStream(fopen("php://stderr", "w"));
204
		}
205
	}
206
 
207
	/**
208
	 * Sets the stream to use for standard (non-error) output.
209
	 * @param OutputStream $stream The stream to use for standard output.
210
	 */
211
	public static function setOutputStream(OutputStream $stream) {
212
		self::$out = $stream;
213
	}
214
 
215
	/**
216
	 * Gets the stream to use for standard (non-error) output.
217
	 * @return OutputStream
218
	 */
219
	public static function getOutputStream() {
220
		return self::$out;
221
	}
222
 
223
	/**
224
	 * Sets the stream to use for error output.
225
	 * @param OutputStream $stream The stream to use for error output.
226
	 */
227
	public static function setErrorStream(OutputStream $stream) {
228
		self::$err = $stream;
229
	}
230
 
231
	/**
232
	 * Gets the stream to use for error output.
233
	 * @return OutputStream
234
	 */
235
	public static function getErrorStream() {
236
		return self::$err;
237
	}
238
 
239
	/**
240
	 * Close logfiles, if we have been writing to them.
241
	 *
242
	 * @since Phing 2.3.0
243
	 */
244
	private static function handleLogfile() {
245
		if (self::$isLogFileUsed) {
246
			self::$err->close();
247
			self::$out->close();
248
		}
249
	}
250
 
251
	/**
252
	 * Making output level a static property so that this property
253
	 * can be accessed by other parts of the system, enabling
254
	 * us to display more information -- e.g. backtraces -- for "debug" level.
255
	 * @return int
256
	 */
257
	public static function getMsgOutputLevel() {
258
		return self::$msgOutputLevel;
259
	}
260
 
261
	/**
262
	 * Command line entry point. This method kicks off the building
263
	 * of a project object and executes a build using either a given
264
	 * target or the default target.
265
	 *
266
	 * @param array $args Command line args.
267
	 * @return void
268
	 */
269
	public static function fire($args) {
270
		self::start($args, null);
271
	}
272
 
273
	/**
274
	 * Setup/initialize Phing environment from commandline args.
275
	 * @param array $args commandline args passed to phing shell.
276
	 * @return void
277
	 */
278
	public function execute($args) {
279
 
280
		self::$definedProps = new Properties();
281
		$this->searchForThis = null;
282
 
283
		// 1) First handle any options which should always
284
		// Note: The order in which these are executed is important (if multiple of these options are specified)
285
 
286
		if (in_array('-help', $args) || in_array('-h', $args)) {
287
			$this->printUsage();
288
			return;
289
		}
290
 
291
		if (in_array('-version', $args) || in_array('-v', $args)) {
292
			$this->printVersion();
293
			return;
294
		}
295
 
296
		// 2) Next pull out stand-alone args.
297
		// Note: The order in which these are executed is important (if multiple of these options are specified)
298
 
299
		if (false !== ($key = array_search('-quiet', $args, true))) {
300
			self::$msgOutputLevel = Project::MSG_WARN;
301
			unset($args[$key]);
302
		}
303
 
304
		if (false !== ($key = array_search('-verbose', $args, true))) {
305
			self::$msgOutputLevel = Project::MSG_VERBOSE;
306
			unset($args[$key]);
307
		}
308
 
309
		if (false !== ($key = array_search('-debug', $args, true))) {
310
			self::$msgOutputLevel = Project::MSG_DEBUG;
311
			unset($args[$key]);
312
		}
313
 
314
		// 3) Finally, cycle through to parse remaining args
315
		//
316
		$keys = array_keys($args); // Use keys and iterate to max(keys) since there may be some gaps
317
		$max = $keys ? max($keys) : -1;
318
		for($i=0; $i <= $max; $i++) {
319
 
320
			if (!array_key_exists($i, $args)) {
321
				// skip this argument, since it must have been removed above.
322
				continue;
323
			}
324
 
325
			$arg = $args[$i];
326
 
327
			if ($arg == "-logfile") {
328
				try {
329
					// see: http://phing.info/trac/ticket/65
330
					if (!isset($args[$i+1])) {
331
						$msg = "You must specify a log file when using the -logfile argument\n";
332
						throw new ConfigurationException($msg);
333
					} else {
334
						$logFile = new PhingFile($args[++$i]);
335
						$out = new FileOutputStream($logFile); // overwrite
336
						self::setOutputStream($out);
337
						self::setErrorStream($out);
338
						self::$isLogFileUsed = true;
339
					}
340
				} catch (IOException $ioe) {
341
					$msg = "Cannot write on the specified log file. Make sure the path exists and you have write permissions.";
342
					throw new ConfigurationException($msg, $ioe);
343
				}
344
			} elseif ($arg == "-buildfile" || $arg == "-file" || $arg == "-f") {
345
				if (!isset($args[$i+1])) {
346
					$msg = "You must specify a buildfile when using the -buildfile argument.";
347
					throw new ConfigurationException($msg);
348
				} else {
349
					$this->buildFile = new PhingFile($args[++$i]);
350
				}
351
			} elseif ($arg == "-listener") {
352
				if (!isset($args[$i+1])) {
353
					$msg = "You must specify a listener class when using the -listener argument";
354
					throw new ConfigurationException($msg);
355
				} else {
356
					$this->listeners[] = $args[++$i];
357
				}
358
			} elseif (StringHelper::startsWith("-D", $arg)) {
359
				$name = substr($arg, 2);
360
				$value = null;
361
				$posEq = strpos($name, "=");
362
				if ($posEq !== false) {
363
					$value = substr($name, $posEq+1);
364
					$name  = substr($name, 0, $posEq);
365
				} elseif ($i < count($args)-1) {
366
					$value = $args[++$i];
367
				}
368
				self::$definedProps->setProperty($name, $value);
369
			} elseif ($arg == "-logger") {
370
				if (!isset($args[$i+1])) {
371
					$msg = "You must specify a classname when using the -logger argument";
372
					throw new ConfigurationException($msg);
373
				} else {
374
					$this->loggerClassname = $args[++$i];
375
				}
376
			} elseif ($arg == "-inputhandler") {
377
				if ($this->inputHandlerClassname !== null) {
378
					throw new ConfigurationException("Only one input handler class may be specified.");
379
				}
380
				if (!isset($args[$i+1])) {
381
					$msg = "You must specify a classname when using the -inputhandler argument";
382
					throw new ConfigurationException($msg);
383
				} else {
384
					$this->inputHandlerClassname = $args[++$i];
385
				}
386
			} elseif ($arg == "-projecthelp" || $arg == "-targets" || $arg == "-list" || $arg == "-l" || $arg == "-p") {
387
				// set the flag to display the targets and quit
388
				$this->projectHelp = true;
389
			} elseif ($arg == "-find") {
390
				// eat up next arg if present, default to build.xml
391
				if ($i < count($args)-1) {
392
					$this->searchForThis = $args[++$i];
393
				} else {
394
					$this->searchForThis = self::DEFAULT_BUILD_FILENAME;
395
				}
396
			} elseif (substr($arg,0,1) == "-") {
397
				// we don't have any more args
398
				self::$err->write("Unknown argument: $arg" . PHP_EOL);
399
				self::printUsage();
400
				return;
401
			} else {
402
				// if it's no other arg, it may be the target
403
				array_push($this->targets, $arg);
404
			}
405
		}
406
 
407
		// if buildFile was not specified on the command line,
408
		if ($this->buildFile === null) {
409
			// but -find then search for it
410
			if ($this->searchForThis !== null) {
411
				$this->buildFile = $this->_findBuildFile(self::getProperty("user.dir"), $this->searchForThis);
412
			} else {
413
				$this->buildFile = new PhingFile(self::DEFAULT_BUILD_FILENAME);
414
			}
415
		}
416
		// make sure buildfile exists
417
		if (!$this->buildFile->exists()) {
418
			throw new ConfigurationException("Buildfile: " . $this->buildFile->__toString() . " does not exist!");
419
		}
420
 
421
		// make sure it's not a directory
422
		if ($this->buildFile->isDirectory()) {
423
			throw new ConfigurationException("Buildfile: " . $this->buildFile->__toString() . " is a dir!");
424
		}
425
 
426
		$this->readyToRun = true;
427
	}
428
 
429
	/**
430
	 * Helper to get the parent file for a given file.
431
	 *
432
	 * @param PhingFile $file
433
	 * @return PhingFile Parent file or null if none
434
	 */
435
	private function _getParentFile(PhingFile $file) {
436
		$filename = $file->getAbsolutePath();
437
		$file     = new PhingFile($filename);
438
		$filename = $file->getParent();
439
		return ($filename === null) ? null : new PhingFile($filename);
440
	}
441
 
442
	/**
443
	 * Search parent directories for the build file.
444
	 *
445
	 * Takes the given target as a suffix to append to each
446
	 * parent directory in search of a build file.  Once the
447
	 * root of the file-system has been reached an exception
448
	 * is thrown.
449
	 *
450
	 * @param string $start Start file path.
451
	 * @param string $suffix Suffix filename to look for in parents.
452
	 * @return PhingFile A handle to the build file
453
	 *
454
	 * @throws BuildException    Failed to locate a build file
455
	 */
456
	private function _findBuildFile($start, $suffix) {
457
		$startf = new PhingFile($start);
458
		$parent = new PhingFile($startf->getAbsolutePath());
459
		$file   = new PhingFile($parent, $suffix);
460
 
461
		// check if the target file exists in the current directory
462
		while (!$file->exists()) {
463
			// change to parent directory
464
			$parent = $this->_getParentFile($parent);
465
 
466
			// if parent is null, then we are at the root of the fs,
467
			// complain that we can't find the build file.
468
			if ($parent === null) {
469
				throw new ConfigurationException("Could not locate a build file!");
470
			}
471
			// refresh our file handle
472
			$file = new PhingFile($parent, $suffix);
473
		}
474
		return $file;
475
	}
476
 
477
	/**
478
	 * Executes the build.
479
	 * @return void
480
	 */
481
	function runBuild() {
482
 
483
		if (!$this->readyToRun) {
484
			return;
485
		}
486
 
487
		$project = new Project();
488
 
489
		self::setCurrentProject($project);
490
		set_error_handler(array('Phing', 'handlePhpError'));
491
 
492
		$error = null;
493
 
494
		$this->addBuildListeners($project);
495
		$this->addInputHandler($project);
496
 
497
		// set this right away, so that it can be used in logging.
498
		$project->setUserProperty("phing.file", $this->buildFile->getAbsolutePath());
499
 
500
		try {
501
			$project->fireBuildStarted();
502
			$project->init();
503
		} catch (Exception $exc) {
504
			$project->fireBuildFinished($exc);
505
			throw $exc;
506
		}
507
 
508
		$project->setUserProperty("phing.version", $this->getPhingVersion());
509
 
510
		$e = self::$definedProps->keys();
511
		while (count($e)) {
512
			$arg   = (string) array_shift($e);
513
			$value = (string) self::$definedProps->getProperty($arg);
514
			$project->setUserProperty($arg, $value);
515
		}
516
		unset($e);
517
 
518
		$project->setUserProperty("phing.file", $this->buildFile->getAbsolutePath());
519
 
520
		// first use the Configurator to create the project object
521
		// from the given build file.
522
 
523
		try {
524
			ProjectConfigurator::configureProject($project, $this->buildFile);
525
		} catch (Exception $exc) {
526
			$project->fireBuildFinished($exc);
527
			restore_error_handler();
528
			self::unsetCurrentProject();
529
			throw $exc;
530
		}
531
 
532
		// make sure that we have a target to execute
533
		if (count($this->targets) === 0) {
534
			$this->targets[] = $project->getDefaultTarget();
535
		}
536
 
537
		// execute targets if help param was not given
538
		if (!$this->projectHelp) {
539
 
540
			try {
541
				$project->executeTargets($this->targets);
542
			} catch (Exception $exc) {
543
				$project->fireBuildFinished($exc);
544
				restore_error_handler();
545
				self::unsetCurrentProject();
546
				throw $exc;
547
			}
548
		}
549
		// if help is requested print it
550
		if ($this->projectHelp) {
551
			try {
552
				$this->printDescription($project);
553
				$this->printTargets($project);
554
			} catch (Exception $exc) {
555
				$project->fireBuildFinished($exc);
556
				restore_error_handler();
557
				self::unsetCurrentProject();
558
				throw $exc;
559
			}
560
		}
561
 
562
		// finally {
563
		if (!$this->projectHelp) {
564
			$project->fireBuildFinished(null);
565
		}
566
 
567
		restore_error_handler();
568
		self::unsetCurrentProject();
569
	}
570
 
571
	/**
572
	 * Bind any registered build listeners to this project.
573
	 *
574
	 * This means adding the logger and any build listeners that were specified
575
	 * with -listener arg.
576
	 *
577
	 * @param Project $project
578
	 * @return void
579
	 */
580
	private function addBuildListeners(Project $project) {
581
		// Add the default listener
582
		$project->addBuildListener($this->createLogger());
583
 
584
		foreach($this->listeners as $listenerClassname) {
585
			try {
586
				$clz = Phing::import($listenerClassname);
587
			} catch (Exception $x) {
588
				$msg = "Unable to instantiate specified listener "
589
				. "class " . $listenerClassname . " : "
590
				. $e->getMessage();
591
				throw new ConfigurationException($msg);
592
			}
593
 
594
			$listener = new $clz();
595
 
596
			if ($listener instanceof StreamRequiredBuildLogger) {
597
				throw new ConfigurationException("Unable to add " . $listenerClassname . " as a listener, since it requires explicit error/output streams. (You can specify it as a -logger.)");
598
			}
599
			$project->addBuildListener($listener);
600
		}
601
	}
602
 
603
	/**
604
	 * Creates the InputHandler and adds it to the project.
605
	 *
606
	 * @param Project $project the project instance.
607
	 *
608
	 * @throws BuildException if a specified InputHandler
609
	 *                           class could not be loaded.
610
	 */
611
	private function addInputHandler(Project $project) {
612
		if ($this->inputHandlerClassname === null) {
613
			$handler = new DefaultInputHandler();
614
		} else {
615
			try {
616
				$clz = Phing::import($this->inputHandlerClassname);
617
				$handler = new $clz();
618
				if ($project !== null && method_exists($handler, 'setProject')) {
619
					$handler->setProject($project);
620
				}
621
			} catch (Exception $e) {
622
				$msg = "Unable to instantiate specified input handler "
623
				. "class " . $this->inputHandlerClassname . " : "
624
				. $e->getMessage();
625
				throw new ConfigurationException($msg);
626
			}
627
		}
628
		$project->setInputHandler($handler);
629
	}
630
 
631
	/**
632
	 * Creates the default build logger for sending build events to the log.
633
	 * @return BuildLogger The created Logger
634
	 */
635
	private function createLogger() {
636
		if ($this->loggerClassname !== null) {
637
			self::import($this->loggerClassname);
638
			// get class name part
639
			$classname = self::import($this->loggerClassname);
640
			$logger = new $classname;
641
			if (!($logger instanceof BuildLogger)) {
642
				throw new BuildException($classname . ' does not implement the BuildLogger interface.');
643
			}
644
		} else {
645
			require_once 'phing/listener/DefaultLogger.php';
646
			$logger = new DefaultLogger();
647
		}
648
		$logger->setMessageOutputLevel(self::$msgOutputLevel);
649
		$logger->setOutputStream(self::$out);
650
		$logger->setErrorStream(self::$err);
651
		return $logger;
652
	}
653
 
654
	/**
655
	 * Sets the current Project
656
	 * @param Project $p
657
	 */
658
	public static function setCurrentProject($p) {
659
		self::$currentProject = $p;
660
	}
661
 
662
	/**
663
	 * Unsets the current Project
664
	 */
665
	public static function unsetCurrentProject() {
666
		self::$currentProject = null;
667
	}
668
 
669
	/**
670
	 * Gets the current Project.
671
	 * @return Project Current Project or NULL if none is set yet/still.
672
	 */
673
	public static function getCurrentProject() {
674
		return self::$currentProject;
675
	}
676
 
677
	/**
678
	 * A static convenience method to send a log to the current (last-setup) Project.
679
	 * If there is no currently-configured Project, then this will do nothing.
680
	 * @param string $message
681
	 * @param int $priority Project::MSG_INFO, etc.
682
	 */
683
	public static function log($message, $priority = Project::MSG_INFO) {
684
		$p = self::getCurrentProject();
685
		if ($p) {
686
			$p->log($message, $priority);
687
		}
688
	}
689
 
690
	/**
691
	 * Error handler for PHP errors encountered during the build.
692
	 * This uses the logging for the currently configured project.
693
	 */
694
	public static function handlePhpError($level, $message, $file, $line) {
695
 
696
		// don't want to print supressed errors
697
		if (error_reporting() > 0) {
698
 
699
			if (self::$phpErrorCapture) {
700
 
701
				self::$capturedPhpErrors[] = array('message' => $message, 'level' => $level, 'line' => $line, 'file' => $file);
702
 
703
			} else {
704
 
705
				$message = '[PHP Error] ' . $message;
706
				$message .= ' [line ' . $line . ' of ' . $file . ']';
707
 
708
				switch ($level) {
709
 
710
					case E_STRICT:
711
					case E_NOTICE:
712
					case E_USER_NOTICE:
713
						self::log($message, Project::MSG_VERBOSE);
714
						break;
715
					case E_WARNING:
716
					case E_USER_WARNING:
717
						self::log($message, Project::MSG_WARN);
718
						break;
719
					case E_ERROR:
720
					case E_USER_ERROR:
721
					default:
722
						self::log($message, Project::MSG_ERR);
723
 
724
				} // switch
725
 
726
			} // if phpErrorCapture
727
 
728
		} // if not @
729
 
730
	}
731
 
732
	/**
733
	 * Begins capturing PHP errors to a buffer.
734
	 * While errors are being captured, they are not logged.
735
	 */
736
	public static function startPhpErrorCapture() {
737
		self::$phpErrorCapture = true;
738
		self::$capturedPhpErrors = array();
739
	}
740
 
741
	/**
742
	 * Stops capturing PHP errors to a buffer.
743
	 * The errors will once again be logged after calling this method.
744
	 */
745
	public static function stopPhpErrorCapture() {
746
		self::$phpErrorCapture = false;
747
	}
748
 
749
	/**
750
	 * Clears the captured errors without affecting the starting/stopping of the capture.
751
	 */
752
	public static function clearCapturedPhpErrors() {
753
		self::$capturedPhpErrors = array();
754
	}
755
 
756
	/**
757
	 * Gets any PHP errors that were captured to buffer.
758
	 * @return array array('message' => message, 'line' => line number, 'file' => file name, 'level' => error level)
759
	 */
760
	public static function getCapturedPhpErrors() {
761
		return self::$capturedPhpErrors;
762
	}
763
 
764
	/**  Prints the usage of how to use this class */
765
	public static function printUsage() {
766
 
767
		$msg = "";
768
		$msg .= "phing [options] [target [target2 [target3] ...]]" . PHP_EOL;
769
		$msg .= "Options: " . PHP_EOL;
770
		$msg .= "  -h -help               print this message" . PHP_EOL;
771
		$msg .= "  -l -list               list available targets in this project" . PHP_EOL;
772
		$msg .= "  -v -version            print the version information and exit" . PHP_EOL;
773
		$msg .= "  -q -quiet              be extra quiet" . PHP_EOL;
774
		$msg .= "  -verbose               be extra verbose" . PHP_EOL;
775
		$msg .= "  -debug                 print debugging information" . PHP_EOL;
776
		$msg .= "  -logfile <file>        use given file for log" . PHP_EOL;
777
		$msg .= "  -logger <classname>    the class which is to perform logging" . PHP_EOL;
778
		$msg .= "  -f -buildfile <file>   use given buildfile" . PHP_EOL;
779
		$msg .= "  -D<property>=<value>   use value for given property" . PHP_EOL;
780
		$msg .= "  -find <file>           search for buildfile towards the root of the" . PHP_EOL;
781
		$msg .= "                         filesystem and use it" . PHP_EOL;
782
		$msg .= "  -inputhandler <file>   the class to use to handle user input" . PHP_EOL;
783
		//$msg .= "  -recursive <file>      search for buildfile downwards and use it" . PHP_EOL;
784
		$msg .= PHP_EOL;
785
		$msg .= "Report bugs to <dev@phing.tigris.org>".PHP_EOL;
786
		self::$err->write($msg);
787
	}
788
 
789
	/**
790
	 * Prints the current Phing version.
791
	 */
792
	public static function printVersion() {
793
		self::$out->write(self::getPhingVersion().PHP_EOL);
794
	}
795
 
796
	/**
797
	 * Gets the current Phing version based on VERSION.TXT file.
798
	 * @return string
799
	 * @throws BuildException - if unable to find version file.
800
	 */
801
	public static function getPhingVersion() {
802
		$versionPath = self::getResourcePath("phing/etc/VERSION.TXT");
803
		if ($versionPath === null) {
804
			$versionPath = self::getResourcePath("etc/VERSION.TXT");
805
		}
806
		if ($versionPath === null) {
807
			throw new ConfigurationException("No VERSION.TXT file found; try setting phing.home environment variable.");
808
		}
809
		try { // try to read file
810
			$buffer = null;
811
			$file = new PhingFile($versionPath);
812
			$reader = new FileReader($file);
813
			$reader->readInto($buffer);
814
			$buffer = trim($buffer);
815
			//$buffer = "PHING version 1.0, Released 2002-??-??";
816
			$phingVersion = $buffer;
817
		} catch (IOException $iox) {
818
			throw new ConfigurationException("Can't read version information file");
819
		}
820
		return $phingVersion;
821
	}
822
 
823
	/**
824
	 * Print the project description, if any
825
	 */
826
	public static function printDescription(Project $project) {
827
		if ($project->getDescription() !== null) {
828
			self::$out->write($project->getDescription() . PHP_EOL);
829
		}
830
	}
831
 
832
	/** Print out a list of all targets in the current buildfile */
833
	function printTargets($project) {
834
		// find the target with the longest name
835
		$maxLength = 0;
836
		$targets = $project->getTargets();
837
		$targetNames = array_keys($targets);
838
		$targetName = null;
839
		$targetDescription = null;
840
		$currentTarget = null;
841
 
842
		// split the targets in top-level and sub-targets depending
843
		// on the presence of a description
844
 
845
		$subNames = array();
846
		$topNameDescMap = array();
847
 
848
		foreach($targets as $currentTarget) {
849
			$targetName = $currentTarget->getName();
850
			$targetDescription = $currentTarget->getDescription();
851
 
852
			// subtargets are targets w/o descriptions
853
			if ($targetDescription === null) {
854
				$subNames[] = $targetName;
855
			} else {
856
				// topNames and topDescriptions are handled later
857
				// here we store in hash map (for sorting purposes)
858
				$topNameDescMap[$targetName] = $targetDescription;
859
				if (strlen($targetName) > $maxLength) {
860
					$maxLength = strlen($targetName);
861
				}
862
			}
863
		}
864
 
865
		// Sort the arrays
866
		sort($subNames); // sort array values, resetting keys (which are numeric)
867
		ksort($topNameDescMap); // sort the keys (targetName) keeping key=>val associations
868
 
869
		$topNames = array_keys($topNameDescMap);
870
		$topDescriptions = array_values($topNameDescMap);
871
 
872
		$defaultTarget = $project->getDefaultTarget();
873
 
874
		if ($defaultTarget !== null && $defaultTarget !== "") {
875
			$defaultName = array();
876
			$defaultDesc = array();
877
			$defaultName[] = $defaultTarget;
878
 
879
			$indexOfDefDesc = array_search($defaultTarget, $topNames, true);
880
			if ($indexOfDefDesc !== false && $indexOfDefDesc >= 0) {
881
				$defaultDesc = array();
882
				$defaultDesc[] = $topDescriptions[$indexOfDefDesc];
883
			}
884
 
885
			$this->_printTargets($defaultName, $defaultDesc, "Default target:", $maxLength);
886
 
887
		}
888
		$this->_printTargets($topNames, $topDescriptions, "Main targets:", $maxLength);
889
		$this->_printTargets($subNames, null, "Subtargets:", 0);
890
	}
891
 
892
	/**
893
	 * Writes a formatted list of target names with an optional description.
894
	 *
895
	 * @param array $names The names to be printed.
896
	 *              Must not be <code>null</code>.
897
	 * @param array $descriptions The associated target descriptions.
898
	 *                     May be <code>null</code>, in which case
899
	 *                     no descriptions are displayed.
900
	 *                     If non-<code>null</code>, this should have
901
	 *                     as many elements as <code>names</code>.
902
	 * @param string $heading The heading to display.
903
	 *                Should not be <code>null</code>.
904
	 * @param int $maxlen The maximum length of the names of the targets.
905
	 *               If descriptions are given, they are padded to this
906
	 *               position so they line up (so long as the names really
907
	 *               <i>are</i> shorter than this).
908
	 */
909
	private function _printTargets($names, $descriptions, $heading, $maxlen) {
910
 
911
		$spaces = '  ';
912
		while (strlen($spaces) < $maxlen) {
913
			$spaces .= $spaces;
914
		}
915
		$msg = "";
916
		$msg .= $heading . PHP_EOL;
917
		$msg .= str_repeat("-",79) . PHP_EOL;
918
 
919
		$total = count($names);
920
		for($i=0; $i < $total; $i++) {
921
			$msg .= " ";
922
			$msg .= $names[$i];
923
			if (!empty($descriptions)) {
924
				$msg .= substr($spaces, 0, $maxlen - strlen($names[$i]) + 2);
925
				$msg .= $descriptions[$i];
926
			}
927
			$msg .= PHP_EOL;
928
		}
929
		if ($total > 0) {
930
			self::$out->write($msg . PHP_EOL);
931
		}
932
	}
933
 
934
	/**
935
	 * Import a dot-path notation class path.
936
	 * @param string $dotPath
937
	 * @param mixed $classpath String or object supporting __toString()
938
	 * @return string The unqualified classname (which can be instantiated).
939
	 * @throws BuildException - if cannot find the specified file
940
	 */
941
	public static function import($dotPath, $classpath = null) {
942
 
943
		// first check to see that the class specified hasn't already been included.
944
		// (this also handles case where this method is called w/ a classname rather than dotpath)
945
		$classname = StringHelper::unqualify($dotPath);
946
		if (class_exists($classname, false)) {
947
			return $classname;
948
		}
949
 
950
		$dotClassname = basename($dotPath);
951
		$dotClassnamePos = strlen($dotPath) - strlen($dotClassname);
952
 
953
		// 1- temporarily replace escaped '.' with another illegal char (#)
954
		$tmp = str_replace('\.', '##', $dotClassname);
955
		// 2- swap out the remaining '.' with DIR_SEP
956
		$tmp = strtr($tmp, '.', DIRECTORY_SEPARATOR);
957
		// 3- swap back the escaped '.'
958
		$tmp = str_replace('##', '.', $tmp);
959
 
960
		$classFile = $tmp . ".php";
961
 
962
		$path = substr_replace($dotPath, $classFile, $dotClassnamePos);
963
 
964
		Phing::__import($path, $classpath);
965
 
966
		return $classname;
967
	}
968
 
969
	/**
970
	 * Import a PHP file
971
	 * @param string $path Path to the PHP file
972
	 * @param mixed $classpath String or object supporting __toString()
973
	 * @throws BuildException - if cannot find the specified file
974
	 */
975
	public static function __import($path, $classpath = null) {
976
 
977
		if ($classpath) {
978
 
979
			// Apparently casting to (string) no longer invokes __toString() automatically.
980
			if (is_object($classpath)) {
981
				$classpath = $classpath->__toString();
982
			}
983
 
984
			// classpaths are currently additive, but we also don't want to just
985
			// indiscriminantly prepand/append stuff to the include_path.  This means
986
			// we need to parse current incldue_path, and prepend any
987
			// specified classpath locations that are not already in the include_path.
988
			//
989
			// NOTE:  the reason why we do it this way instead of just changing include_path
990
			// and then changing it back, is that in many cases applications (e.g. Propel) will
991
			// include/require class files from within method calls.  This means that not all
992
			// necessary files will be included in this import() call, and hence we can't
993
			// change the include_path back without breaking those apps.  While this method could
994
			// be more expensive than switching & switching back (not sure, but maybe), it makes it
995
			// possible to write far less expensive run-time applications (e.g. using Propel), which is
996
			// really where speed matters more.
997
 
998
			$curr_parts = explode(PATH_SEPARATOR, get_include_path());
999
			$add_parts = explode(PATH_SEPARATOR, $classpath);
1000
			$new_parts = array_diff($add_parts, $curr_parts);
1001
			if ($new_parts) {
1002
				set_include_path(implode(PATH_SEPARATOR, array_merge($new_parts, $curr_parts)));
1003
			}
1004
		}
1005
 
1006
		$ret = include_once($path);
1007
 
1008
		if ($ret === false) {
1009
			$msg = "Error importing $path";
1010
			if (self::getMsgOutputLevel() >= Project::MSG_DEBUG) {
1011
				$x = new Exception("for-path-trace-only");
1012
				$msg .= $x->getTraceAsString();
1013
			}
1014
			throw new ConfigurationException($msg);
1015
		}
1016
	}
1017
 
1018
	/**
1019
	 * Looks on include path for specified file.
1020
	 * @return string File found (null if no file found).
1021
	 */
1022
	public static function getResourcePath($path) {
1023
 
1024
		if (self::$importPaths === null) {
1025
			$paths = get_include_path();
1026
			self::$importPaths = explode(PATH_SEPARATOR, ini_get("include_path"));
1027
		}
1028
 
1029
		$path = str_replace('\\', DIRECTORY_SEPARATOR, $path);
1030
		$path = str_replace('/', DIRECTORY_SEPARATOR, $path);
1031
 
1032
		foreach (self::$importPaths as $prefix) {
1033
			$testPath = $prefix . DIRECTORY_SEPARATOR . $path;
1034
			if (file_exists($testPath)) {
1035
				return $testPath;
1036
			}
1037
		}
1038
 
1039
		// Check for the property phing.home
1040
		$homeDir = self::getProperty('phing.home');
1041
		if ($homeDir) {
1042
			$testPath = $homeDir . DIRECTORY_SEPARATOR . $path;
1043
			if (file_exists($testPath)) {
1044
				return $testPath;
1045
			}
1046
		}
1047
 
1048
		// If we are using this via PEAR then check for the file in the data dir
1049
		// This is a bit of a hack, but works better than previous solution of assuming
1050
		// data_dir is on the include_path.
1051
		$dataDir = '@DATA-DIR@';
1052
		if ($dataDir{0} != '@') { // if we're using PEAR then the @ DATA-DIR @ token will have been substituted.
1053
			$testPath = $dataDir . DIRECTORY_SEPARATOR . $path;
1054
			if (file_exists($testPath)) {
1055
				return $testPath;
1056
			}
1057
		} else {
1058
			// We're not using PEAR, so do one additional check based on path of
1059
			// current file (Phing.php)
1060
			$maybeHomeDir = realpath(dirname(__FILE__) . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR  . '..');
1061
			$testPath = $maybeHomeDir . DIRECTORY_SEPARATOR . $path;
1062
			if (file_exists($testPath)) {
1063
				return $testPath;
1064
			}
1065
		}
1066
 
1067
		return null;
1068
	}
1069
 
1070
	// -------------------------------------------------------------------------------------------
1071
	// System-wide methods (moved from System class, which had namespace conflicts w/ PEAR System)
1072
	// -------------------------------------------------------------------------------------------
1073
 
1074
	/**
1075
	 * Set System constants which can be retrieved by calling Phing::getProperty($propName).
1076
	 * @return void
1077
	 */
1078
	private static function setSystemConstants() {
1079
 
1080
		/*
1081
		 * PHP_OS returns on
1082
		 *   WindowsNT4.0sp6  => WINNT
1083
		 *   Windows2000      => WINNT
1084
		 *   Windows ME       => WIN32
1085
		 *   Windows 98SE     => WIN32
1086
		 *   FreeBSD 4.5p7    => FreeBSD
1087
		 *   Redhat Linux     => Linux
1088
		 *   Mac OS X		  => Darwin
1089
		 */
1090
		self::setProperty('host.os', PHP_OS);
1091
 
1092
		// this is used by some tasks too
1093
		self::setProperty('os.name', PHP_OS);
1094
 
1095
		// it's still possible this won't be defined,
1096
		// e.g. if Phing is being included in another app w/o
1097
		// using the phing.php script.
1098
		if (!defined('PHP_CLASSPATH')) {
1099
			define('PHP_CLASSPATH', get_include_path());
1100
		}
1101
 
1102
		self::setProperty('php.classpath', PHP_CLASSPATH);
1103
 
1104
		// try to determine the host filesystem and set system property
1105
		// used by Fileself::getFileSystem to instantiate the correct
1106
		// abstraction layer
1107
 
1108
		switch (strtoupper(PHP_OS)) {
1109
			case 'WINNT':
1110
				self::setProperty('host.fstype', 'WINNT');
1111
				self::setProperty('php.interpreter', getenv('PHP_COMMAND'));
1112
				break;
1113
			case 'WIN32':
1114
				self::setProperty('host.fstype', 'WIN32');
1115
				break;
1116
			default:
1117
				self::setProperty('host.fstype', 'UNIX');
1118
				break;
1119
		}
1120
 
1121
		self::setProperty('line.separator', PHP_EOL);
1122
		self::setProperty('php.version', PHP_VERSION);
1123
		self::setProperty('user.home', getenv('HOME'));
1124
		self::setProperty('application.startdir', getcwd());
1125
		self::setProperty('phing.startTime', gmdate('D, d M Y H:i:s', time()) . ' GMT');
1126
 
1127
		// try to detect machine dependent information
1128
		$sysInfo = array();
1129
		if (strtoupper(substr(PHP_OS, 0, 3)) !== 'WIN' && function_exists("posix_uname")) {
1130
			$sysInfo = posix_uname();
1131
		} else {
1132
			$sysInfo['nodename'] = php_uname('n');
1133
			$sysInfo['machine']= php_uname('m') ;
1134
			//this is a not so ideal substition, but maybe better than nothing
1135
			$sysInfo['domain'] = isset($_SERVER['SERVER_NAME']) ? $_SERVER['SERVER_NAME'] : "unknown";
1136
			$sysInfo['release'] = php_uname('r');
1137
			$sysInfo['version'] = php_uname('v');
1138
		}
1139
 
1140
 
1141
		self::setProperty("host.name", isset($sysInfo['nodename']) ? $sysInfo['nodename'] : "unknown");
1142
		self::setProperty("host.arch", isset($sysInfo['machine']) ? $sysInfo['machine'] : "unknown");
1143
		self::setProperty("host.domain",isset($sysInfo['domain']) ? $sysInfo['domain'] : "unknown");
1144
		self::setProperty("host.os.release", isset($sysInfo['release']) ? $sysInfo['release'] : "unknown");
1145
		self::setProperty("host.os.version", isset($sysInfo['version']) ? $sysInfo['version'] : "unknown");
1146
		unset($sysInfo);
1147
	}
1148
 
1149
	/**
1150
	 * This gets a property that was set via command line or otherwise passed into Phing.
1151
	 * "Defined" in this case means "externally defined".  The reason this method exists is to
1152
	 * provide a public means of accessing commandline properties for (e.g.) logger or listener
1153
	 * scripts.  E.g. to specify which logfile to use, PearLogger needs to be able to access
1154
	 * the pear.log.name property.
1155
	 *
1156
	 * @param string $name
1157
	 * @return string value of found property (or null, if none found).
1158
	 */
1159
	public static function getDefinedProperty($name) {
1160
		return self::$definedProps->getProperty($name);
1161
	}
1162
 
1163
	/**
1164
	 * This sets a property that was set via command line or otherwise passed into Phing.
1165
	 *
1166
	 * @param string $name
1167
	 * @return string value of found property (or null, if none found).
1168
	 */
1169
	public static function setDefinedProperty($name, $value) {
1170
		return self::$definedProps->setProperty($name, $value);
1171
	}
1172
 
1173
	/**
1174
	 * Returns property value for a System property.
1175
	 * System properties are "global" properties like application.startdir,
1176
	 * and user.dir.  Many of these correspond to similar properties in Java
1177
	 * or Ant.
1178
	 *
1179
	 * @param string $paramName
1180
	 * @return string Value of found property (or null, if none found).
1181
	 */
1182
	public static function getProperty($propName) {
1183
 
1184
		// some properties are detemined on each access
1185
		// some are cached, see below
1186
 
1187
		// default is the cached value:
1188
		$val = isset(self::$properties[$propName]) ? self::$properties[$propName] : null;
1189
 
1190
		// special exceptions
1191
		switch($propName) {
1192
			case 'user.dir':
1193
				$val = getcwd();
1194
				break;
1195
		}
1196
 
1197
		return $val;
1198
	}
1199
 
1200
	/** Retuns reference to all properties*/
1201
	public static function &getProperties() {
1202
		return self::$properties;
1203
	}
1204
 
1205
	public static function setProperty($propName, $propValue) {
1206
		$propName = (string) $propName;
1207
		$oldValue = self::getProperty($propName);
1208
		self::$properties[$propName] = $propValue;
1209
		return $oldValue;
1210
	}
1211
 
1212
	public static function currentTimeMillis() {
1213
		list($usec, $sec) = explode(" ",microtime());
1214
		return ((float)$usec + (float)$sec);
1215
	}
1216
 
1217
	/**
1218
	 * Sets the include path to PHP_CLASSPATH constant (if this has been defined).
1219
	 * @return void
1220
	 * @throws ConfigurationException - if the include_path could not be set (for some bizarre reason)
1221
	 */
1222
	private static function setIncludePaths() {
1223
		if (defined('PHP_CLASSPATH')) {
1224
			$result = set_include_path(PHP_CLASSPATH);
1225
			if ($result === false) {
1226
				throw new ConfigurationException("Could not set PHP include_path.");
1227
			}
1228
			self::$origIniSettings['include_path'] = $result; // save original value for setting back later
1229
		}
1230
	}
1231
 
1232
	/**
1233
	 * Sets PHP INI values that Phing needs.
1234
	 * @return void
1235
	 */
1236
	private static function setIni() {
1237
 
1238
		self::$origIniSettings['error_reporting'] = error_reporting(E_ALL);
1239
 
1240
		// We won't bother storing original max_execution_time, since 1) the value in
1241
		// php.ini may be wrong (and there's no way to get the current value) and
1242
		// 2) it would mean something very strange to set it to a value less than time script
1243
		// has already been running, which would be the likely change.
1244
 
1245
		set_time_limit(0);
1246
 
1247
		self::$origIniSettings['magic_quotes_gpc'] = ini_set('magic_quotes_gpc', 'off');
1248
		self::$origIniSettings['short_open_tag'] = ini_set('short_open_tag', 'off');
1249
		self::$origIniSettings['default_charset'] = ini_set('default_charset', 'iso-8859-1');
1250
		self::$origIniSettings['register_globals'] = ini_set('register_globals', 'off');
1251
		self::$origIniSettings['allow_call_time_pass_reference'] = ini_set('allow_call_time_pass_reference', 'on');
1252
		self::$origIniSettings['track_errors'] = ini_set('track_errors', 1);
1253
 
1254
		// should return memory limit in MB
1255
		$mem_limit = (int) ini_get('memory_limit');
1256
		if ($mem_limit < 32) {
1257
			// We do *not* need to save the original value here, since we don't plan to restore
1258
			// this after shutdown (we don't trust the effectiveness of PHP's garbage collection).
1259
			ini_set('memory_limit', '32M'); // nore: this may need to be higher for many projects
1260
		}
1261
	}
1262
 
1263
	/**
1264
	 * Restores [most] PHP INI values to their pre-Phing state.
1265
	 *
1266
	 * Currently the following settings are not restored:
1267
	 * 	- max_execution_time (because getting current time limit is not possible)
1268
	 *  - memory_limit (which may have been increased by Phing)
1269
	 *
1270
	 * @return void
1271
	 */
1272
	private static function restoreIni()
1273
	{
1274
		foreach(self::$origIniSettings as $settingName => $settingValue) {
1275
			switch($settingName) {
1276
				case 'error_reporting':
1277
					error_reporting($settingValue);
1278
					break;
1279
				default:
1280
					ini_set($settingName, $settingValue);
1281
			}
1282
		}
1283
	}
1284
 
1285
	/**
1286
	 * Returns reference to Timer object.
1287
	 * @return Timer
1288
	 */
1289
	public static function getTimer() {
1290
		if (self::$timer === null) {
1291
			include_once 'phing/system/util/Timer.php';
1292
			self::$timer= new Timer();
1293
		}
1294
		return self::$timer;
1295
	}
1296
 
1297
	/**
1298
	 * Start up Phing.
1299
	 * Sets up the Phing environment but does not initiate the build process.
1300
	 * @return void
1301
	 * @throws Exception - If the Phing environment cannot be initialized.
1302
	 */
1303
	public static function startup() {
1304
 
1305
		// setup STDOUT and STDERR defaults
1306
		self::initializeOutputStreams();
1307
 
1308
		// some init stuff
1309
		self::getTimer()->start();
1310
 
1311
		self::setSystemConstants();
1312
		self::setIncludePaths();
1313
		self::setIni();
1314
	}
1315
 
1316
	/**
1317
	 * Halts the system.
1318
	 * @deprecated This method is deprecated and is no longer called by Phing internally.  Any
1319
	 * 				normal shutdown routines are handled by the shutdown() method.
1320
	 * @see shutdown()
1321
	 */
1322
	public static function halt() {
1323
		self::shutdown();
1324
	}
1325
 
1326
	/**
1327
	 * Performs any shutdown routines, such as stopping timers.
1328
	 * @return void
1329
	 */
1330
	public static function shutdown() {
1331
		self::restoreIni();
1332
		self::getTimer()->stop();
1333
	}
1334
 
1335
}