517 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
			
		
		
	
	
			517 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
| <?php declare(strict_types=1);
 | |
| 
 | |
| namespace App;
 | |
| 
 | |
| error_reporting(error_level: E_ALL);
 | |
| 
 | |
| use DirectoryIterator;
 | |
| use Exception;
 | |
| use PDO;
 | |
| use PharData;
 | |
| 
 | |
| 
 | |
| const SUPPORTED_RELEASE_MAJOR = 3;
 | |
| const SUPPORTED_RELEASE_MINOR = 2;
 | |
| 
 | |
| /**
 | |
|  *
 | |
|  */
 | |
| class UpdateController
 | |
| {
 | |
| 	private $pdo;
 | |
| 	
 | |
| 	
 | |
| 	function handleUpdate()
 | |
| 	{
 | |
| 		define(constant_name: "PHPBB_ROOT_PATH", value: dirname(path: __DIR__, levels: 2));
 | |
| 		
 | |
| 		$phpBBRootPath = PHPBB_ROOT_PATH . '/';
 | |
| 		$phpEx = substr(string: strrchr(haystack: __FILE__, needle: '.'), offset: 1);
 | |
| 		
 | |
| 		
 | |
| 		// sanity checks
 | |
| 		
 | |
| 		if (strtoupper(string: substr(string: PHP_OS, offset: 0, length: 3)) === 'WIN') {
 | |
| 			echo 'This program must not be used on Windows installations.' . PHP_EOL;
 | |
| 			exit(1);
 | |
| 		}
 | |
| 		
 | |
| 		if (PHP_VERSION_ID < 80100) {
 | |
| 			echo 'You need at least php version 8.1.0';
 | |
| 			exit(1);
 | |
| 		}
 | |
| 		
 | |
| 		if (php_sapi_name() !== 'cli') {
 | |
| 			echo 'This program must be run from the command line.' . PHP_EOL;
 | |
| 			exit(1);
 | |
| 		}
 | |
| 		
 | |
| 		if (!extension_loaded(extension: 'bz2')) {
 | |
| 			echo 'You need to install/enable the bz2 PHP extension' . PHP_EOL;
 | |
| 			exit(1);
 | |
| 		}
 | |
| 		
 | |
| 		include $phpBBRootPath . 'config.php';
 | |
| 		
 | |
| 		/** @var String $dbhost */
 | |
| 		/** @var String $dbport */
 | |
| 		/** @var String $dbname */
 | |
| 		/** @var String $dbuser */
 | |
| 		/** @var String $dbpasswd */
 | |
| 		$this->pdo = new PDO(
 | |
| 			dsn     : "mysql:host=$dbhost;port=$dbport;charset=utf8mb4;dbname=$dbname",
 | |
| 			username: $dbuser,
 | |
| 			password: $dbpasswd
 | |
| 		);
 | |
| 		
 | |
| 		
 | |
| 		/** @var String $table_prefix */
 | |
| 		$sql = "SELECT config_value FROM ${table_prefix}config WHERE config_name = 'version'";
 | |
| 		$statement = $this->pdo->prepare(query: $sql);
 | |
| 		$statement->execute();
 | |
| 		$result = $statement->fetch();
 | |
| 		
 | |
| 		$installedVersion = $result['config_value'];
 | |
| 		print("Installed version: phpBB $installedVersion" . PHP_EOL);
 | |
| 		
 | |
| 		// phpBB has major, minor and maintenance version scheme
 | |
| 		[$major, $minor, $patch] = explode(separator: '.', string: $installedVersion);
 | |
| 		if ((intval(value: $major) != SUPPORTED_RELEASE_MAJOR) || (intval(value: $minor) < SUPPORTED_RELEASE_MINOR)) {
 | |
| 			echo 'This script only supports phpBB ' . SUPPORTED_RELEASE_MAJOR . '.' . SUPPORTED_RELEASE_MINOR . ' and above branch.', PHP_EOL;
 | |
| 			exit(1);
 | |
| 		}
 | |
| 		
 | |
| 		$json = file_get_contents(filename: 'https://version.phpbb.com/phpbb/versions.json');
 | |
| 		$versions = json_decode(json: $json);
 | |
| 		
 | |
| 		$stableVersions = $versions->stable;
 | |
| 		$availableUpdate = $stableVersions->{$major . '.' . $minor}->{'current'};
 | |
| 		
 | |
| 		echo 'Current Version: ' . $availableUpdate . PHP_EOL;
 | |
| 		
 | |
| 		// check for existing update
 | |
| 		if (!file_exists(filename: 'dist')) {
 | |
| 			echo "'dist' folder is missing, create a new one …'";
 | |
| 			mkdir(directory: 'dist');
 | |
| 		}
 | |
| 		
 | |
| 		$currentFile = "phpBB-$availableUpdate.tar.bz2";
 | |
| 		
 | |
| 		$phpBBTarget = "dist/$currentFile";
 | |
| 		
 | |
| 		if (!file_exists(filename: $phpBBTarget)) {
 | |
| 			echo "Downloading $currentFile" . PHP_EOL;
 | |
| 			$filePath = "https://download.phpbb.com/pub/release/$major.$minor/$availableUpdate/$currentFile";
 | |
| 			$phpBBtbz = file_get_contents(filename: $filePath);
 | |
| 			file_put_contents(filename: $phpBBTarget, data: $phpBBtbz);
 | |
| 		} else {
 | |
| 			echo $currentFile . ' already exists' . PHP_EOL;
 | |
| 		}
 | |
| 		
 | |
| 		// TODO check SHA256?
 | |
| 		
 | |
| 		// check for available language files
 | |
| 		
 | |
| 		if (file_exists(filename: $phpBBRootPath . 'language/de')) {
 | |
| 			$useLangDeDu = true;
 | |
| 			// https://downloads.phpbb.de/pakete/deutsch/3.3/3.3.7/phpBB_lang_de-3.3.7.tar.bz2
 | |
| 			$languageFile = "phpBB_lang_de-$availableUpdate.tar.bz2";
 | |
| 			$langDeDuTarget = "dist/$languageFile";
 | |
| 			
 | |
| 			if (!file_exists(filename: $langDeDuTarget)) {
 | |
| 				echo "Downloading language $languageFile" . PHP_EOL;
 | |
| 				$filePath = "https://downloads.phpbb.de/pakete/deutsch/$major.$minor/$availableUpdate/$languageFile";
 | |
| 				$phpBBtbz = file_get_contents(filename: $filePath);
 | |
| 				file_put_contents(filename: $langDeDuTarget, data: $phpBBtbz);
 | |
| 			} else {
 | |
| 				echo 'Language file ' . $languageFile . ' already exists' . PHP_EOL;
 | |
| 			}
 | |
| 		} else {
 | |
| 			$useLangDeDu = false;
 | |
| 			echo 'Language Deutsch "Du" ist not installed, skipping' . PHP_EOL;
 | |
| 		}
 | |
| 		
 | |
| 		if (file_exists(filename: $phpBBRootPath . 'language/de_x_sie')) {
 | |
| 			$useLangDeSie = true;
 | |
| 			//https://downloads.phpbb.de/pakete/deutsch/3.3/3.3.7/phpBB_lang_de_x_sie-3.3.7.tar.bz2
 | |
| 			$languageFile = "phpBB_lang_de_x_sie-$availableUpdate.tar.bz2";
 | |
| 			$langDeSieTarget = "dist/$languageFile";
 | |
| 			
 | |
| 			if (!file_exists(filename: $langDeSieTarget)) {
 | |
| 				echo "Downloading language $languageFile" . PHP_EOL;
 | |
| 				$filePath = "https://downloads.phpbb.de/pakete/deutsch/$major.$minor/$availableUpdate/$languageFile";
 | |
| 				$phpBBtbz = file_get_contents(filename: $filePath);
 | |
| 				file_put_contents(filename: $langDeSieTarget, data: $phpBBtbz);
 | |
| 			} else {
 | |
| 				echo 'Language file ' . $languageFile . ' already exists' . PHP_EOL;
 | |
| 			}
 | |
| 		} else {
 | |
| 			$useLangDeSie = false;
 | |
| 			echo 'Language Deutsch "Sie" ist not installed, skipping' . PHP_EOL;
 | |
| 		}
 | |
| 		
 | |
| 		if (!$this->confirm(message: 'Do you want to proceed with the update now?')) {
 | |
| 			exit(0);
 | |
| 		}
 | |
| 		
 | |
| 		// ok, start update
 | |
| 		$now = date(format: 'd.m.Y H:i');
 | |
| 		$disableMsg = "Software-update at $now, the forum ist down due to maintenance. We'll be back soon.";
 | |
| 		$sql = "UPDATE ${table_prefix}config SET config_value = :disable_message WHERE config_name = 'board_disable_msg'";
 | |
| 		$statement = $this->pdo->prepare(query: $sql);
 | |
| 		$statement->bindParam(param: 'disable_message', var: $disableMsg);
 | |
| 		
 | |
| 		if ($result = $statement->execute()) {
 | |
| 			echo "Disable Message set …", PHP_EOL;
 | |
| 		} else {
 | |
| 			echo 'There was an error talking to the DB.' . PHP_EOL;
 | |
| 			echo 'Failed SQL-statement: ' . $sql . PHP_EOL;
 | |
| 			exit(1);
 | |
| 		}
 | |
| 		
 | |
| 		$sql = "UPDATE ${table_prefix}config SET config_value = '1' WHERE config_name = 'board_disable'";
 | |
| 		$statement = $this->pdo->prepare(query: $sql);
 | |
| 		if ($result = $statement->execute()) {
 | |
| 			echo "Board disabled …", PHP_EOL;
 | |
| 		} else {
 | |
| 			echo 'There was an error talking to the DB.' . PHP_EOL;
 | |
| 			echo 'Failed SQL-statement: ' . $sql . PHP_EOL;
 | |
| 			exit(1);
 | |
| 		}
 | |
| 		
 | |
| 		$extensionsFile = 'extensions.txt';
 | |
| 		
 | |
| 		if (file_exists(filename: $extensionsFile)) {
 | |
| 			echo 'Extensions state already stored. Remove extensions.txt if you wish to recreate it.' . PHP_EOL;
 | |
| 		} else {
 | |
| 			// check for enabled extensions
 | |
| 			$sql = "SELECT ext_name FROM ${table_prefix}ext WHERE ext_active = '1'";
 | |
| 			$statement = $this->pdo->prepare(query: $sql);
 | |
| 			if ($statement->execute()) {
 | |
| 				$result = $statement->fetchAll();
 | |
| 				
 | |
| 				if (count(value: $result) > 0) {
 | |
| 					$extensions = json_encode(value: $result);
 | |
| 					
 | |
| 					// safe enabled extensions
 | |
| 					$oFile = fopen(filename: 'extensions.txt', mode: 'w');
 | |
| 					fputs(stream: $oFile, data: $extensions);
 | |
| 					fclose(stream: $oFile);
 | |
| 					echo 'Stored extensions state';
 | |
| 					
 | |
| 					// disable all extensions
 | |
| 					$sql = "UPDATE ${table_prefix}ext SET ext_active = '0' WHERE ext_active = '1'";
 | |
| 					$statement = $this->pdo->prepare(query: $sql);
 | |
| 					if ($statement->execute()) {
 | |
| 						echo 'Disabled all extensions';
 | |
| 					}
 | |
| 				}
 | |
| 				
 | |
| 			}
 | |
| 		}
 | |
| 		
 | |
| 		$stylesFile =  'styles.txt';
 | |
| 		if (file_exists(filename: $stylesFile)) {
 | |
| 			echo 'Styles state already stored. Remove styles.txt if you wish to recreate it' . PHP_EOL;
 | |
| 		} else {
 | |
| 			// check for enabled style
 | |
| 			$sql = "SELECT style_name FROM ${table_prefix}styles WHERE style_active = '1'";
 | |
| 			$statement = $this->pdo->prepare(query: $sql);
 | |
| 			if ($statement->execute()) {
 | |
| 				$result = $statement->fetchAll();
 | |
| 				
 | |
| 				if (count(value: $result) > 0) {
 | |
| 					$styles = json_encode(value: $result);
 | |
| 					
 | |
| 					// safe enabled styles
 | |
| 					$oFile = fopen(filename: 'styles.txt', mode: 'w');
 | |
| 					fputs(stream: $oFile, data: $styles);
 | |
| 					fclose(stream: $oFile);
 | |
| 					echo 'Stored styles state.' . PHP_EOL;
 | |
| 					
 | |
| 					// disable all styles except prosilver
 | |
| 					$sql = "UPDATE ${table_prefix}styles SET style_active = '0' WHERE NOT style_name = 'prosilver'";
 | |
| 					$statement = $this->pdo->prepare(query: $sql);
 | |
| 					if ($statement->execute()) {
 | |
| 						echo 'Disabled all styles except prosilver.' . PHP_EOL;
 | |
| 					}
 | |
| 				}
 | |
| 				
 | |
| 			}
 | |
| 		}
 | |
| 		
 | |
| 		// update phpBB
 | |
| 		$data = new PharData(filename: $phpBBTarget);
 | |
| 		try {
 | |
| 			unset($data['phpBB3/config.php']);
 | |
| 			unset($data['phpBB3/.htaccess']);
 | |
| 		} catch (Exception $e) {
 | |
| 			echo 'error: ', $e;
 | |
| 		}
 | |
| 		
 | |
| 		// remove all old files
 | |
| 		$excludes = [
 | |
| 			'config.php',
 | |
| 			'.htaccess',
 | |
| 			'.htpasswd',
 | |
| 			'images',
 | |
| 			'files',
 | |
| 			'ext',
 | |
| 			'styles',
 | |
| 			'store',
 | |
| 			'updates',
 | |
| 			'mobiquo'];
 | |
| 		// this will fuck up nearly all modified boards, leave the files alone by default.
 | |
| 		// It will also destroy any API-keys and whatever you might have in your document root.
 | |
| 		//         deleteDirectory($phpbb_root_path, $excludes);
 | |
| 		
 | |
| 		
 | |
| 		try {
 | |
| 			$data->extractTo(directory: $phpBBRootPath);
 | |
| 			
 | |
| 			$fromDir = $phpBBRootPath . 'phpBB3/';
 | |
| 			$toDir = $phpBBRootPath;
 | |
| 			
 | |
| 			$this->copyDirectory(source: $fromDir, target: $toDir);
 | |
| 			$this->deleteDirectory(dir: $fromDir);
 | |
| 		} catch (Exception $e) {
 | |
| 			print("Error while extracting $data: $e");
 | |
| 			exit(1);
 | |
| 		}
 | |
| 		echo 'Moved the update in place.', PHP_EOL;
 | |
| 		
 | |
| 		$fileOwner = fileowner(filename: $phpBBRootPath);
 | |
| 		$fileGroup = filegroup(filename: $phpBBRootPath);
 | |
| 		$fileOwnerName = posix_getpwuid(user_id: $fileOwner)['name'];
 | |
| 		$fileGroupName = posix_getgrgid(group_id: $fileGroup)['name'];
 | |
| 		
 | |
| 		
 | |
| 		echo 'Check file owner', PHP_EOL;
 | |
| 		print("You might need to perform 'chown -R $fileOwnerName:$fileGroupName $phpBBRootPath'" . PHP_EOL);
 | |
| 		
 | |
| 		echo 'prepare config.yml.', PHP_EOL;
 | |
| 		$oFile = fopen(filename: $phpBBRootPath . '/update-config.yml', mode: 'w');
 | |
| 		fputs(stream: $oFile, data: 'updater:' . PHP_EOL . "        type: db_only" . PHP_EOL);
 | |
| 		fclose(stream: $oFile);
 | |
| 		
 | |
| 		$command = <<<EOC
 | |
| 		cd ..
 | |
| 		pwd
 | |
| 		php install/phpbbcli.php update update-config.yml
 | |
| 		EOC;
 | |
| 		
 | |
| 		system(command: $command, result_code: $resultCode);
 | |
| 		
 | |
| 		if ($resultCode != 0) {
 | |
| 			echo 'There was an error updating the database: ' . $resultCode . PHP_EOL;
 | |
| 			exit(1);
 | |
| 		} else {
 | |
| 			echo 'The database has been updated' . PHP_EOL;
 | |
| 		}
 | |
| 		
 | |
| 		$installDir = $phpBBRootPath . '/install';
 | |
| 		if (is_dir(filename: $installDir)) {
 | |
| 			$this->deleteDirectory(dir: $installDir);
 | |
| 		}
 | |
| 		
 | |
| 		
 | |
| 		// update langDeDu
 | |
| 		if ($useLangDeDu) {
 | |
| 			$data = new PharData(filename: $langDeDuTarget);
 | |
| 			
 | |
| 			try {
 | |
| 				$data->extractTo(directory: $phpBBRootPath, overwrite: true);
 | |
| 			} catch (Exception $e) {
 | |
| 				print("Error while extracting $langDeDuTarget: $e");
 | |
| 				exit(1);
 | |
| 			}
 | |
| 		}
 | |
| 		
 | |
| 		// update langDeSie
 | |
| 		if ($useLangDeSie) {
 | |
| 			$data = new PharData(filename: $langDeSieTarget);
 | |
| 			
 | |
| 			try {
 | |
| 				$data->extractTo(directory: $phpBBRootPath, overwrite: true);
 | |
| 			} catch (Exception $e) {
 | |
| 				print("Error while extracting $langDeSieTarget: $e");
 | |
| 				exit(1);
 | |
| 			}
 | |
| 		}
 | |
| 		
 | |
| 		
 | |
| 		
 | |
| 		$sql = "UPDATE ${table_prefix}config SET config_value = '0' WHERE config_name = 'board_disable'";
 | |
| 		$statement = $this->pdo->prepare(query: $sql);
 | |
| 		$statement->execute();
 | |
| 		
 | |
| 		echo "Board reenabled …", PHP_EOL;
 | |
| 		
 | |
| 		
 | |
| 		if (file_exists(filename: $extensionsFile)) {
 | |
| 			$iFile = fopen(filename: $extensionsFile, mode: 'r');
 | |
| 			$extensions = json_decode(json: fgets(stream: $iFile), associative: true);
 | |
| 			
 | |
| 			echo 'Enable extensions: ';
 | |
| 			
 | |
| 			foreach ($extensions as $extension) {
 | |
| 				$ext = $extension['ext_name'];
 | |
| 				$sql = "UPDATE ${table_prefix}ext SET ext_active = '1' WHERE ext_name = '$ext'";
 | |
| 				$statement = $this->pdo->prepare(query: $sql);
 | |
| 				$statement->execute();
 | |
| 				echo '.';
 | |
| 			}
 | |
| 			echo 'done.', PHP_EOL;
 | |
| 		} else {
 | |
| 			echo 'There are no saved extension information available.', PHP_EOL;
 | |
| 		}
 | |
| 		
 | |
| 		if (file_exists(filename: $stylesFile)) {
 | |
| 			$iFile = fopen(filename: $stylesFile, mode: 'r');
 | |
| 			$styles = json_decode(json: fgets(stream: $iFile), associative: true);
 | |
| 			
 | |
| 			echo 'Enable styles: ';
 | |
| 			
 | |
| 			foreach ($styles as $style) {
 | |
| 				$style = $style['style_name'];
 | |
| 				$sql = "UPDATE ${table_prefix}styles SET style_active = '1' WHERE style_name = '$style'";
 | |
| 				$statement = $this->pdo->prepare(query: $sql);
 | |
| 				$statement->execute();
 | |
| 				echo '.';
 | |
| 			}
 | |
| 			echo 'done.', PHP_EOL;
 | |
| 		} else {
 | |
| 			echo 'There are no saved extension information available.', PHP_EOL;
 | |
| 		}
 | |
| 
 | |
| 
 | |
| 		// clear cache
 | |
| 		$dataGlobalCache = $phpBBRootPath . '/cache/data_global.' . $phpEx;
 | |
| 		
 | |
| 		if (file_exists(filename: $dataGlobalCache)) {
 | |
| 			unlink(filename: $dataGlobalCache);
 | |
| 			echo "Cache cleared …";
 | |
| 		}
 | |
| 		
 | |
| 		echo "Your board should now be up and running." . PHP_EOL;
 | |
| 	}
 | |
| 	
 | |
| 	
 | |
| 	/**
 | |
| 	 * @param String   $message
 | |
| 	 * @param string[] $options
 | |
| 	 * @param string   $default
 | |
| 	 *
 | |
| 	 * @return bool
 | |
| 	 */
 | |
| 	function confirm(string $message = 'Are you sure? ', array $options = ['y', 'n'], string $default = 'n'): bool
 | |
| 	{
 | |
| 		// first $options means true, any other false
 | |
| 		
 | |
| 		echo $message, ' (';
 | |
| 		$first = true;
 | |
| 		foreach ($options as $option) {
 | |
| 			// mark default
 | |
| 			if ($option == $default) {
 | |
| 				$option = strtoupper(string: $option);
 | |
| 			}
 | |
| 			if ($first) {
 | |
| 				echo $option;
 | |
| 				$first = false;
 | |
| 			} else {
 | |
| 				echo '/', $option;
 | |
| 			}
 | |
| 		}
 | |
| 		echo '): ';
 | |
| 		
 | |
| 		$handle = fopen(filename: "php://stdin", mode: 'r');
 | |
| 		$line = trim(string: fgetc(stream: $handle));
 | |
| 		fclose(stream: $handle);
 | |
| 		
 | |
| 		if ($line == '') {
 | |
| 			// enter
 | |
| 			$line = $default;
 | |
| 		}
 | |
| 		
 | |
| 		if ($line == $options[0]) {
 | |
| 			$result = true;
 | |
| 		} else {
 | |
| 			$result = false;
 | |
| 		}
 | |
| 		
 | |
| 		return $result;
 | |
| 	}
 | |
| 	
 | |
| 	
 | |
| 	/**
 | |
| 	 * @param       $dir
 | |
| 	 * @param array $excludes
 | |
| 	 *
 | |
| 	 * @return false|void
 | |
| 	 */
 | |
| 	function deleteDirectory($dir, array $excludes = [])
 | |
| 	{
 | |
| 		if (!file_exists(filename: $dir)) {
 | |
| 			return false;
 | |
| 		}
 | |
| 		$dir = rtrim(string: $dir, characters: '/') . '/';
 | |
| 		static $skip = false;
 | |
| 		
 | |
| 		$entries = glob(pattern: $dir . '{,.}[!.,!..]*', flags: GLOB_MARK | GLOB_BRACE);
 | |
| 		
 | |
| 		foreach ($entries as $entry) {
 | |
| 			if (!in_array(needle: basename(path: $entry), haystack: $excludes)) {
 | |
| 				if (is_dir(filename: $entry)) {
 | |
| 					$this->deleteDirectory(dir: $entry);
 | |
| 				} else {
 | |
| 					unlink(filename: $entry);
 | |
| 				}
 | |
| 			} else {
 | |
| 				$skip = true;
 | |
| 			}
 | |
| 		}
 | |
| 		if ($skip == false) {
 | |
| 			rmdir(directory: $dir);
 | |
| 		}
 | |
| 	}
 | |
| 	
 | |
| 	
 | |
| 	/**
 | |
| 	 * @param $source
 | |
| 	 * @param $target
 | |
| 	 *
 | |
| 	 * @return bool|void
 | |
| 	 */
 | |
| 	function copyDirectory($source, $target)
 | |
| 	{
 | |
| 		if (!file_exists(filename: $source)) {
 | |
| 			die("missing source: $source");
 | |
| 		}
 | |
| 		
 | |
| 		if (is_file(filename: $source)) {
 | |
| 			if (copy(from: $source, to: $target)) {
 | |
| 				return true;
 | |
| 			} else {
 | |
| 				return false;
 | |
| 			}
 | |
| 		}
 | |
| 		
 | |
| 		if (is_dir(filename: $source)) {
 | |
| 			if (!file_exists(filename: $target)) {
 | |
| 				mkdir(directory: $target);
 | |
| 			}
 | |
| 			$source = rtrim(string: $source, characters: '/') . '/';
 | |
| 			$target = rtrim(string: $target, characters: '/') . '/';
 | |
| 			$dir = new DirectoryIterator(directory: $source);
 | |
| 			
 | |
| 			foreach ($dir as $entry) {
 | |
| 				if (!$entry->isDot()) {
 | |
| 					$this->copyDirectory(source: "$source$entry", target: "$target$entry");
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 		// ignore links, not part of phpBB arch
 | |
| 	}
 | |
| 	
 | |
| }
 |