Fixed icons in setup
[GitHub/WoltLab/WCF.git] / wcfsetup / install.php
1 <?php
2 /**
3 * This script tries to find the temp folder and unzip all setup files into.
4 *
5 * @author Marcel Werk
6 * @copyright 2001-2011 WoltLab GmbH
7 * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
8 */
9 // define constants
10 define('INSTALL_SCRIPT_DIR', dirname(__FILE__).'/');
11 define('SETUP_FILE', INSTALL_SCRIPT_DIR . 'WCFSetup.tar.gz');
12 define('NO_IMPORTS', 1);
13
14 // set exception handler
15 set_exception_handler('handleException');
16 // set php error handler
17 set_error_handler('handleError', E_ALL);
18
19 // define list of needed file
20 $neededFilesPattern = array(
21 '!^setup/.*!',
22 '!^install/files/acp/images/wcfLogo.*!',
23 '!^install/files/acp/style/setup/.*!',
24 '!^install/files/lib/data/.*!',
25 '!^install/files/icon/.*!',
26 '!^install/files/lib/system/.*!',
27 '!^install/files/lib/util/.*!',
28 '!^install/lang/.*!',
29 '!^install/packages/.*!');
30
31 // define needed functions and classes
32 /**
33 * WCF::handleException() calls the show method on exceptions that implement this interface.
34 *
35 * @package com.woltlab.wcf.system.exception
36 * @author Marcel Werk
37 */
38 interface IPrintableException {
39 public function show();
40 }
41
42 // define needed classes
43 // needed are:
44 // SystemException, PrintableException, BasicFileUtil, Tar, File, ZipFile
45 /**
46 * A SystemException is thrown when an unexpected error occurs.
47 *
48 * @package com.woltlab.wcf.system.exception
49 * @author Marcel Werk
50 */
51 class SystemException extends \Exception implements IPrintableException {
52 protected $description;
53 protected $information = '';
54 protected $functions = '';
55
56 /**
57 * Creates a new SystemException.
58 *
59 * @param message string error message
60 * @param code integer error code
61 * @param description string description of the error
62 */
63 public function __construct($message = '', $code = 0, $description = '') {
64 parent::__construct($message, $code);
65 $this->description = $description;
66 }
67
68 /**
69 * Returns the description of this exception.
70 *
71 * @return string
72 */
73 public function getDescription() {
74 return $this->description;
75 }
76
77 /**
78 * Prints this exception.
79 * This method is called by WCF::handleException().
80 */
81 public function show() {
82 ?>
83 <html>
84 <head>
85 <title>Fatal error: <?php echo htmlspecialchars($this->getMessage()); ?></title>
86
87 <style type="text/css">
88 body {
89 font-family: Verdana, Helvetica, sans-serif;
90 font-size: 0.8em;
91 }
92 div {
93 border: 1px outset lightgrey;
94 padding: 3px;
95 background-color: lightgrey;
96 }
97
98 div div {
99 border: 1px inset lightgrey;
100 padding: 4px;
101 }
102
103 h1 {
104 background-color: #154268;
105 padding: 4px;
106 color: #fff;
107 margin: 0 0 3px 0;
108 font-size: 1.15em;
109 }
110 h2 {
111 font-size: 1.1em;
112 margin-bottom: 0;
113 }
114
115 pre, p {
116 margin: 0;
117 }
118 </style>
119 </head>
120
121 <body>
122 <div>
123 <h1>Fatal error: <?php echo htmlspecialchars($this->getMessage()); ?></h1>
124
125 <div>
126 <p><?php echo $this->getDescription(); ?></p>
127 <?php if ($this->getCode()) { ?><p>You get more information about the problem in our knowledge base: <a href="http://www.woltlab.com/help/?code=<?php echo intval($this->getCode()); ?>">http://www.woltlab.com/help/?code=<?php echo intval($this->getCode()); ?></a></p><?php } ?>
128
129 <h2>Information:</h2>
130 <p>
131 <b>error message:</b> <?php echo htmlspecialchars($this->getMessage()); ?><br />
132 <b>error code:</b> <?php echo intval($this->getCode()); ?><br />
133 <?php echo $this->information; ?>
134 <b>file:</b> <?php echo htmlspecialchars($this->getFile()); ?> (<?php echo $this->getLine(); ?>)<br />
135 <b>php version:</b> <?php echo htmlspecialchars(phpversion()); ?><br />
136 <b>wcf version:</b> <?php if (defined('WCF_VERSION')) echo WCF_VERSION; ?><br />
137 <b>date:</b> <?php echo gmdate('r'); ?><br />
138 <b>request:</b> <?php if (isset($_SERVER['REQUEST_URI'])) echo htmlspecialchars($_SERVER['REQUEST_URI']); ?><br />
139 <b>referer:</b> <?php if (isset($_SERVER['HTTP_REFERER'])) echo htmlspecialchars($_SERVER['HTTP_REFERER']); ?><br />
140 </p>
141
142 <h2>Stacktrace:</h2>
143 <pre><?php echo htmlspecialchars($this->getTraceAsString()); ?></pre>
144 </div>
145
146 <?php echo $this->functions; ?>
147 </div>
148 </body>
149 </html>
150
151 <?php
152 }
153 }
154
155
156 /**
157 * Loads the required classes automatically.
158 */
159 function __autoload($className) {
160 $namespaces = explode('\\', $className);
161 if (count($namespaces) > 1) {
162 // remove 'wcf' component
163 array_shift($namespaces);
164
165 $className = implode('/', $namespaces);
166 $classPath = TMP_DIR . 'install/files/lib/' . $className . '.class.php';
167 if (file_exists($classPath)) {
168 require_once($classPath);
169 }
170 }
171 }
172
173 /**
174 * Escapes strings for execution in sql queries.
175 */
176 function escapeString($string) {
177 return \wcf\system\WCF::getDB()->escapeString($string);
178 }
179
180 /**
181 * Calls the show method on the given exception.
182 *
183 * @param Exception $e
184 */
185 function handleException(\Exception $e) {
186 if ($e instanceof IPrintableException || $e instanceof \wcf\system\exception\IPrintableException) {
187 $e->show();
188 exit;
189 }
190
191 print $e;
192 }
193
194 /**
195 * Catches php errors and throws instead a system exception.
196 *
197 * @param integer $errorNo
198 * @param string $message
199 * @param string $filename
200 * @param integer $lineNo
201 */
202 function handleError($errorNo, $message, $filename, $lineNo) {
203 if (error_reporting() != 0) {
204 $type = 'error';
205 switch ($errorNo) {
206 case 2: $type = 'warning';
207 break;
208 case 8: $type = 'notice';
209 break;
210 }
211
212 throw new SystemException('PHP '.$type.' in file '.$filename.' ('.$lineNo.'): '.$message, 0);
213 }
214 }
215
216 /**
217 * BasicFileUtil contains file-related functions.
218 *
219 * @package com.woltlab.wcf.util
220 * @author Marcel Werk
221 */
222 class BasicFileUtil {
223 /**
224 * Tries to find the temp folder.
225 *
226 * @return string
227 */
228 public static function getTempFolder() {
229 $tmpDirName = TMP_FILE_PREFIX.'/';
230
231 // use tmp folder in document root by default
232 if (!empty($_SERVER['DOCUMENT_ROOT'])) {
233 if (!@file_exists($_SERVER['DOCUMENT_ROOT'].'/tmp/'.$tmpDirName)) {
234 @mkdir($_SERVER['DOCUMENT_ROOT'].'/tmp/'.$tmpDirName, 0777, true);
235 @chmod($_SERVER['DOCUMENT_ROOT'].'/tmp/'.$tmpDirName, 0777);
236 }
237
238 if (@file_exists($_SERVER['DOCUMENT_ROOT'].'/tmp/'.$tmpDirName) && @is_writable($_SERVER['DOCUMENT_ROOT'].'/tmp/'.$tmpDirName)) {
239 return $_SERVER['DOCUMENT_ROOT'].'/tmp/'.$tmpDirName;
240 }
241 }
242
243 foreach (array('TMP', 'TEMP', 'TMPDIR') as $tmpDir) {
244 if (isset($_ENV[$tmpDir]) && @is_writable($_ENV[$tmpDir])) {
245 $dir = $_ENV[$tmpDir] . '/' . $tmpDirName;
246 @mkdir($dir, 0777);
247 @chmod($dir, 0777);
248
249 if (@file_exists($dir) && @is_writable($dir)) {
250 return $dir;
251 }
252 }
253 }
254
255 $dir = INSTALL_SCRIPT_DIR . 'tmp/' . $tmpDirName;
256 @mkdir($dir, 0777);
257 @chmod($dir, 0777);
258
259 if (!@file_exists($dir) || !@is_writable($dir)) {
260 $tmpDir = explode('/', $dir);
261 array_pop($tmpDir);
262 $dir = implode('/', $tmpDir);
263
264 throw new SystemException('There is no access to the system temporary folder due to an unknown reason and no user specific temporary folder exists in '.INSTALL_SCRIPT_DIR.'! This is a misconfiguration of your webserver software! Please create a folder called '.$dir.' using your favorite ftp program, make it writable and then retry this installation.');
265 }
266
267 return $dir;
268 }
269 }
270
271 /**
272 * Opens tar or tar.gz archives.
273 *
274 * Usage:
275 * ------
276 * $tar = new Tar('archive.tar');
277 * $contentList = $tar->getContentList();
278 * foreach ($contentList as $key => $val) {
279 * $tar->extract($key, DESTINATION);
280 * }
281 */
282 class Tar {
283 protected $archiveName = '';
284 protected $contentList = array();
285 protected $opened = false;
286 protected $read = false;
287 protected $file = null;
288 protected $isZipped = false;
289 protected $mode = 'rb';
290
291 /**
292 * Creates a new Tar object.
293 * archiveName must be tarball or gzipped tarball
294 *
295 * @param string $archiveName
296 */
297 public function __construct($archiveName) {
298 if (!is_file($archiveName)) {
299 throw new SystemException("unable to find tar archive '".$archiveName."'");
300 }
301
302 $this->archiveName = $archiveName;
303 $this->open();
304 $this->readContent();
305 }
306
307 /**
308 * Destructor of this class, closes tar archive.
309 */
310 public function __destruct() {
311 $this->close();
312 }
313
314 /**
315 * Opens the tar archive and stores filehandle.
316 */
317 public function open() {
318 if (!$this->opened) {
319 if ($this->isZipped) $this->file = new ZipFile($this->archiveName, $this->mode);
320 else {
321 // test compression
322 $this->file = new File($this->archiveName, $this->mode);
323 if ($this->file->read(2) == "\37\213") {
324 $this->file->close();
325 $this->isZipped = true;
326 $this->file = new ZipFile($this->archiveName, $this->mode);
327 }
328 else {
329 $this->file->seek(0);
330 }
331 }
332 $this->opened = true;
333 }
334 }
335
336 /**
337 * Closes the opened file.
338 */
339 public function close() {
340 if ($this->opened) {
341 $this->file->close();
342 $this->opened = false;
343 }
344 }
345
346 /**
347 * Returns the table of contents (TOC) list for this tar archive.
348 *
349 * @return array list of content
350 */
351 public function getContentList() {
352 if (!$this->read) {
353 $this->open();
354 $this->readContent();
355 }
356 return $this->contentList;
357 }
358
359 /**
360 * Returns an associative array with information
361 * about a specific file in the archive.
362 *
363 * @param mixed $fileindex index or name of the requested file
364 * @return array $fileInfo
365 */
366 public function getFileInfo($fileIndex) {
367 if (!is_int($fileIndex)) {
368 $fileIndex = $this->getIndexByFilename($fileIndex);
369 }
370
371 if (!isset($this->contentList[$fileIndex])) {
372 throw new SystemException("Tar: could find file '".$fileIndex."' in archive");
373 }
374 return $this->contentList[$fileIndex];
375 }
376
377 /**
378 * Searchs a file in the tar archive
379 * and returns the numeric fileindex.
380 * Returns false if not found.
381 *
382 * @param string $filename
383 * @return integer index of the requested file
384 */
385 public function getIndexByFilename($filename) {
386 foreach ($this->contentList as $index => $file) {
387 if ($file['filename'] == $filename) {
388 return $index;
389 }
390 }
391 return false;
392 }
393
394 /**
395 * Extracts a specific file and returns the content as string.
396 * Returns false if extraction failed.
397 *
398 * @param mixed $index index or name of the requested file
399 * @return string content of the requested file
400 */
401 public function extractToString($index) {
402 if (!$this->read) {
403 $this->open();
404 $this->readContent();
405 }
406 $header = $this->getFileInfo($index);
407
408 // can not extract a folder
409 if ($header['type'] != 'file') {
410 return false;
411 }
412
413 // seek to offset
414 $this->file->seek($header['offset']);
415
416 // read data
417 $content = '';
418 $n = floor($header['size'] / 512);
419 for($i = 0; $i < $n; $i++) {
420 $content .= $this->file->read(512);
421 }
422 if(($header['size'] % 512) != 0) {
423 $buffer = $this->file->read(512);
424 $content .= substr($buffer, 0, ($header['size'] % 512));
425 }
426
427 return $content;
428 }
429
430 /**
431 * Extracts a specific file and writes it's content
432 * to the file specified with $destination.
433 *
434 * @param mixed $index index or name of the requested file
435 * @param string $destination
436 * @return boolean $success
437 */
438 public function extract($index, $destination) {
439 if (!$this->read) {
440 $this->open();
441 $this->readContent();
442 }
443 $header = $this->getFileInfo($index);
444
445 // can not extract a folder
446 if ($header['type'] != 'file') {
447 return false;
448 }
449
450 // seek to offset
451 $this->file->seek($header['offset']);
452
453 $targetFile = new File($destination);
454
455 // read data
456 $n = floor($header['size'] / 512);
457 for ($i = 0; $i < $n; $i++) {
458 $content = $this->file->read(512);
459 $targetFile->write($content, 512);
460 }
461 if (($header['size'] % 512) != 0) {
462 $content = $this->file->read(512);
463 $targetFile->write($content, ($header['size'] % 512));
464 }
465
466 $targetFile->close();
467 if (function_exists('apache_get_version') || !@$targetFile->is_writable()) {
468 @$targetFile->chmod(0777);
469 }
470 else {
471 @$targetFile->chmod(0755);
472 }
473
474 if ($header['mtime']) {
475 @$targetFile->touch($header['mtime']);
476 }
477
478 // check filesize
479 if (filesize($destination) != $header['size']) {
480 throw new SystemException("Could not untar file '".$header['filename']."' to '".$destination."'. Maybe disk quota exceeded in folder '".dirname($destination)."'.");
481 }
482
483 return true;
484 }
485
486 /**
487 * Reads table of contents (TOC) from tar archive.
488 * This does not get the entire to memory but only parts of it.
489 */
490 protected function readContent() {
491 $this->contentList = array();
492 $this->read = true;
493 $i = 0;
494
495 // Read the 512 bytes header
496 while (strlen($binaryData = $this->file->read(512)) != 0) {
497 // read header
498 $header = $this->readHeader($binaryData);
499 if ($header === false) {
500 continue;
501 }
502 $this->contentList[$i] = $header;
503 $this->contentList[$i]['index'] = $i;
504 $i++;
505
506 $this->file->seek($this->file->tell() + (512 * ceil(($header['size'] / 512))));
507 }
508 }
509
510 /**
511 * Unpacks file header for one file entry.
512 *
513 * @param string $binaryData
514 * @return array $fileheader
515 */
516 protected function readHeader($binaryData) {
517 if (strlen($binaryData) != 512) {
518 return false;
519 }
520
521 $header = array();
522 $checksum = 0;
523 // First part of the header
524 for ($i = 0; $i < 148; $i++) {
525 $checksum += ord(substr($binaryData, $i, 1));
526 }
527 // Calculate the checksum
528 // Ignore the checksum value and replace it by ' ' (space)
529 for ($i = 148; $i < 156; $i++) {
530 $checksum += ord(' ');
531 }
532 // Last part of the header
533 for ($i = 156; $i < 512; $i++) {
534 $checksum += ord(substr($binaryData, $i, 1));
535 }
536
537 // Extract the values
538 //$data = unpack("a100filename/a8mode/a8uid/a8gid/a12size/a12mtime/a8checksum/a1typeflag/a100link/a6magic/a2version/a32uname/a32gname/a8devmajor/a8devminor", $binaryData);
539 $data = unpack("a100filename/a8mode/a8uid/a8gid/a12size/a12mtime/a8checksum/a1typeflag/a100link/a6magic/a2version/a32uname/a32gname/a8devmajor/a8devminor/a155prefix", $binaryData);
540
541 // Extract the properties
542 $header['checksum'] = octDec(trim($data['checksum']));
543 if ($header['checksum'] == $checksum) {
544 $header['filename'] = trim($data['filename']);
545 $header['mode'] = octDec(trim($data['mode']));
546 $header['uid'] = octDec(trim($data['uid']));
547 $header['gid'] = octDec(trim($data['gid']));
548 $header['size'] = octDec(trim($data['size']));
549 $header['mtime'] = octDec(trim($data['mtime']));
550 $header['prefix'] = trim($data['prefix']);
551 if ($header['prefix']) {
552 $header['filename'] = $header['prefix'].'/'.$header['filename'];
553 }
554 if (($header['typeflag'] = $data['typeflag']) == '5') {
555 $header['size'] = 0;
556 $header['type'] = 'folder';
557 }
558 else {
559 $header['type'] = 'file';
560 }
561 $header['offset'] = $this->file->tell();
562
563 return $header;
564 }
565 else {
566 return false;
567 }
568 }
569 }
570
571 /**
572 * The File class handles all file operations.
573 *
574 * Example:
575 * using php functions:
576 * $fp = fopen('filename', 'wb');
577 * fwrite($fp, '...');
578 * fclose($fp);
579 *
580 * using this class:
581 * $file = new File('filename');
582 * $file->write('...');
583 * $file->close();
584 *
585 * @author Marcel Werk
586 */
587 class File {
588 protected $resource = null;
589 protected $filename;
590
591 /**
592 * Opens a new file.
593 *
594 * @param string $filename
595 * @param string $mode
596 */
597 public function __construct($filename, $mode = 'wb') {
598 $this->filename = $filename;
599 $this->resource = fopen($filename, $mode);
600 if ($this->resource === false) {
601 throw new SystemException('Can not open file ' . $filename);
602 }
603 }
604
605 /**
606 * Calls the specified function on the open file.
607 * Do not call this function directly. Use $file->write('') instead.
608 *
609 * @param string $function
610 * @param array $arguments
611 */
612 public function __call($function, $arguments) {
613 if (function_exists('f' . $function)) {
614 array_unshift($arguments, $this->resource);
615 return call_user_func_array('f' . $function, $arguments);
616 }
617 else if (function_exists($function)) {
618 array_unshift($arguments, $this->filename);
619 return call_user_func_array($function, $arguments);
620 }
621 else {
622 throw new SystemException('Can not call file method ' . $function);
623 }
624 }
625 }
626
627 /**
628 * The File class handles all file operations on a zipped file.
629 *
630 * @author Marcel Werk
631 */
632 class ZipFile extends File {
633 /**
634 * Opens a new zipped file.
635 *
636 * @param string $filename
637 * @param string $mode
638 */
639 public function __construct($filename, $mode = 'wb') {
640 $this->filename = $filename;
641 if (!function_exists('gzopen')) {
642 throw new SystemException('Can not find functions of the zlib extension');
643 }
644 $this->resource = @gzopen($filename, $mode);
645 if ($this->resource === false) {
646 throw new SystemException('Can not open file ' . $filename);
647 }
648 }
649
650 /**
651 * Calls the specified function on the open file.
652 *
653 * @param string $function
654 * @param array $arguments
655 */
656 public function __call($function, $arguments) {
657 if (function_exists('gz' . $function)) {
658 array_unshift($arguments, $this->resource);
659 return call_user_func_array('gz' . $function, $arguments);
660 }
661 else if (function_exists($function)) {
662 array_unshift($arguments, $this->filename);
663 return call_user_func_array($function, $arguments);
664 }
665 else {
666 throw new SystemException('Can not call method ' . $function);
667 }
668 }
669
670 /**
671 * Returns the filesize of the unzipped file
672 */
673 public function getFileSize() {
674 $byteBlock = 1<<14;
675 $eof = $byteBlock;
676
677 // the correction is for zip files that are too small
678 // to get in the first while loop
679 $correction = 1;
680 while ($this->seek($eof) == 0) {
681 $eof += $byteBlock;
682 $correction = 0;
683 }
684
685 while ($byteBlock > 1) {
686 $byteBlock >>= 1;
687 $eof += $byteBlock * ($this->seek($eof) ? -1 : 1);
688 }
689
690 if ($this->seek($eof) == -1) $eof -= 1;
691
692 $this->rewind();
693 return $eof - $correction;
694 }
695 }
696
697 // let's go
698 // get temp file prefix
699 if (isset($_REQUEST['tmpFilePrefix'])) {
700 $prefix = preg_replace('/[^a-f0-9_]+/', '', $_REQUEST['tmpFilePrefix']);
701 }
702 else {
703 $prefix = substr(sha1(uniqid(microtime())), 0, 8);
704 }
705 define('TMP_FILE_PREFIX', $prefix);
706
707 // try to find the temp folder
708 define('TMP_DIR', BasicFileUtil::getTempFolder());
709
710 /**
711 * Reads a file resource from temp folder.
712 *
713 * @param string $key
714 * @param string $directory
715 */
716 function readFileResource($key, $directory) {
717 if (preg_match('~[\w\-]+\.(css|jpg|png|svg|eot|woff|ttf)~', $_GET[$key], $match)) {
718 switch ($match[1]) {
719 case 'css':
720 header('Content-Type: text/css');
721 break;
722
723 case 'jpg':
724 header('Content-Type: image/jpg');
725 break;
726
727 case 'png':
728 header('Content-Type: image/png');
729 break;
730
731 case 'svg':
732 header('Content-Type: image/svg+xml');
733 break;
734
735 case 'eot':
736 header('Content-Type: application/vnd.ms-fontobject');
737 break;
738
739 case 'woff':
740 header('Content-Type: application/font-woff');
741 break;
742
743 case 'ttf':
744 header('Content-Type: application/octet-stream');
745 break;
746 }
747
748 header('Expires: '.gmdate('D, d M Y H:i:s', time() + 3600).' GMT');
749 header('Last-Modified: Mon, 26 Jul 1997 05:00:00 GMT');
750 header('Cache-Control: public, max-age=3600');
751
752 readfile($directory . $_GET[$key]);
753 }
754 exit;
755 }
756
757 // show image from temp folder
758 if (isset($_GET['showImage'])) {
759 readFileResource('showImage', TMP_DIR . 'install/files/acp/images/');
760 }
761 // show icon from temp folder
762 if (isset($_GET['showIcon'])) {
763 readFileResource('showIcon', TMP_DIR . 'install/files/icon/');
764 }
765 // show css from temp folder
766 if (isset($_GET['showCSS'])) {
767 readFileResource('showCSS', TMP_DIR . 'install/files/acp/style/setup/');
768 }
769 // show fonts from temp folder
770 if (isset($_GET['showFont'])) {
771 readFileResource('showFont', TMP_DIR . 'install/files/font/');
772 }
773
774 // check whether setup files are already unzipped
775 if (!file_exists(TMP_DIR . 'install/files/lib/system/WCFSetup.class.php')) {
776 // try to unzip all setup files into temp folder
777 $tar = new Tar(SETUP_FILE);
778 $contentList = $tar->getContentList();
779 if (empty($contentList)) {
780 throw new SystemException("Can not unpack 'WCFSetup.tar.gz'. File is probably broken.");
781 }
782
783 foreach ($contentList as $file) {
784 foreach ($neededFilesPattern as $pattern) {
785 if (preg_match($pattern, $file['filename'])) {
786 // create directory if not exists
787 $dir = TMP_DIR . dirname($file['filename']);
788 if (!@is_dir($dir)) {
789 @mkdir($dir, 0777, true);
790 @chmod($dir, 0777);
791 }
792
793 $tar->extract($file['index'], TMP_DIR . $file['filename']);
794 }
795 }
796 }
797 $tar->close();
798
799 // create cache folders
800 @mkdir(TMP_DIR . 'setup/lang/cache/', 0777);
801 @chmod(TMP_DIR . 'setup/lang/cache/', 0777);
802
803 @mkdir(TMP_DIR . 'setup/template/compiled/', 0777);
804 @chmod(TMP_DIR . 'setup/template/compiled/', 0777);
805 }
806
807 if (!class_exists('wcf\system\WCFSetup')) {
808 throw new SystemException("Can not find class 'WCFSetup'");
809 }
810
811 // start setup
812 new \wcf\system\WCFSetup();