580 lines
20 KiB
PHP
580 lines
20 KiB
PHP
<?php declare(strict_types=1);
|
|
|
|
namespace App;
|
|
|
|
error_reporting(error_level: E_ALL);
|
|
|
|
use DirectoryIterator;
|
|
use Exception;
|
|
use JetBrains\PhpStorm\NoReturn;
|
|
use PDO;
|
|
use PharData;
|
|
|
|
|
|
const SUPPORTED_RELEASE_MAJOR = 3;
|
|
const SUPPORTED_RELEASE_MINOR = 2;
|
|
|
|
/**
|
|
*
|
|
*/
|
|
class UpdateController
|
|
{
|
|
/**
|
|
* @var true
|
|
*/
|
|
private bool $dryRun = false;
|
|
private string $installedVersion = '';
|
|
|
|
function parseOpts(): array
|
|
{
|
|
$shortOpts = 'h::d::';
|
|
|
|
$longOpts = [
|
|
'help',
|
|
'dry-run',
|
|
];
|
|
|
|
return getopt(
|
|
short_options: $shortOpts,
|
|
long_options: $longOpts
|
|
);
|
|
}
|
|
|
|
#[NoReturn]
|
|
function handleUpdate(): void
|
|
{
|
|
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);
|
|
}
|
|
|
|
if (!$this->dryRun) {
|
|
include $phpBBRootPath . 'config.php';
|
|
|
|
/** @var String $dbhost */
|
|
/** @var String $dbport */
|
|
/** @var String $dbname */
|
|
/** @var String $dbuser */
|
|
/** @var String $dbpasswd */
|
|
$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 = $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);
|
|
}
|
|
}
|
|
|
|
echo "Checking for the current version …" . PHP_EOL;
|
|
$json = file_get_contents(filename: 'https://version.phpbb.com/phpbb/versions.json');
|
|
$versions = json_decode(json: $json, associative: true);
|
|
|
|
$stableVersions = $versions['stable'];
|
|
|
|
// Get the highest stable version
|
|
$highestStableVersion = null;
|
|
foreach ($stableVersions as $version => $details) {
|
|
if ($highestStableVersion === null || version_compare(version1: $version, version2: $highestStableVersion, operator: '>')) {
|
|
$highestStableVersion = $version;
|
|
}
|
|
}
|
|
|
|
echo "Highest Stable Version: $highestStableVersion" . PHP_EOL;
|
|
if (!$this->dryRun) {
|
|
echo "Installed Version: $installedVersion" . PHP_EOL;
|
|
}
|
|
|
|
[$major, $minor, $patch] = explode(separator: '.', string: $stableVersions[$highestStableVersion]['current']);
|
|
echo "Latest stable release: $major.$minor.$patch" . PHP_EOL;
|
|
$availableUpdate = $stableVersions[$highestStableVersion]['current'];
|
|
// 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. Skipping download.' . PHP_EOL;
|
|
}
|
|
|
|
// TODO check SHA256?
|
|
|
|
// check for available language files
|
|
|
|
$useLangDeDu = false;
|
|
if (file_exists(filename: $phpBBRootPath . 'language/de')) {
|
|
$useLangDeDu = true;
|
|
// https://downloads.phpbb.de/pakete/deutsch/3.3/3.3.10/phpBB_lang_de-3.3.10.tar.bz2
|
|
$languageFile = "phpBB_lang_de-$availableUpdate.tar.bz2";
|
|
echo "language file: $languageFile";
|
|
$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";
|
|
if (file_exists(filename: $filePath)) {
|
|
$phpBBtbz = file_get_contents(filename: $filePath);
|
|
file_put_contents(filename: $langDeDuTarget, data: $phpBBtbz);
|
|
} else {
|
|
echo "Language file $languageFile does not exist." . PHP_EOL;
|
|
$useLangDeDu = false;
|
|
}
|
|
} else {
|
|
echo 'Language file ' . $languageFile . ' already exists' . PHP_EOL;
|
|
}
|
|
} else {
|
|
echo 'Language Deutsch "Du" ist not installed, skipping' . PHP_EOL;
|
|
}
|
|
|
|
$useLangDeSie = false;
|
|
if (file_exists(filename: $phpBBRootPath . 'language/de_x_sie')) {
|
|
$useLangDeSie = true;
|
|
$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";
|
|
if (file_exists(filename: $filePath)) {
|
|
$phpBBtbz = file_get_contents(filename: $filePath);
|
|
file_put_contents(filename: $langDeSieTarget, data: $phpBBtbz);
|
|
} else {
|
|
echo 'Language file ' . $languageFile . ' does not exist' . PHP_EOL;
|
|
$useLangDeSie = false;
|
|
}
|
|
} else {
|
|
echo 'Language file ' . $languageFile . ' already exists' . PHP_EOL;
|
|
}
|
|
} else {
|
|
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);
|
|
}
|
|
|
|
if ($this->dryRun) {
|
|
echo 'Dry run, exiting.' . PHP_EOL;
|
|
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 = $pdo->prepare(query: $sql);
|
|
$statement->bindParam(param: 'disable_message', var: $disableMsg);
|
|
|
|
if ($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 = $pdo->prepare(query: $sql);
|
|
if ($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 = $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 = $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 = $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 = $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 ..
|
|
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 = $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 = $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 = $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) {
|
|
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
|
|
}
|
|
|
|
public function printHelp(): void
|
|
{
|
|
echo "Usage: php update.php [options]", PHP_EOL;
|
|
echo "Options:", PHP_EOL;
|
|
echo "-h --help Print this help", PHP_EOL;
|
|
echo "-d --dry-run Just check for downloadable files, don't connect to database.", PHP_EOL;
|
|
}
|
|
|
|
public function setDryRun(): void
|
|
{
|
|
$this->dryRun = true;
|
|
}
|
|
|
|
}
|