Subversion Repositories oidplus

Rev

Rev 1109 | Go to most recent revision | Blame | Last modification | View Log | RSS feed

#!/usr/bin/env php
<?php

/*
 * OIDplus 2.0
 * Copyright 2019 - 2022 Daniel Marschall, ViaThinkSoft
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

// This script will be called at the ViaThinkSoft server side

// Generate keypair with:
//      openssl genpkey -algorithm RSA -out private.pem -pkeyopt rsa_keygen_bits:8192
//      openssl rsa -pubout -in private.pem -out public.pem

$argc = $_SERVER['argc']; // to please Eclipse for PHP
$argv = $_SERVER['argv']; // to please Eclipse for PHP

if (PHP_SAPI != 'cli') {
        fwrite(STDERR, "This file can only be invoked in CLI mode.\n");
        die();
}

if (DIRECTORY_SEPARATOR != '/') {
        echo "This script can only run on Unix like systems\n";
        exit(2);
}

if ($argc != 4) {
        echo "Usage: ".$argv[0]." <targetpath> <privkey> <force(1|0)>\n";
        exit(2);
}

$output_dir = $argv[1];
$priv_key = $argv[2];
$force = $argv[3];

if (!is_dir($output_dir)) {
        echo "Path $output_dir does not exist!\n";
        exit(1);
}

if (!is_file($priv_key)) {
        echo "Private key file $priv_key does not exist!\n";
        exit(1);
}

if (($force != '1') && ($force != '0')) {
        echo "Argument 'force' must be 0 or 1\n";
        exit(1);
}

$outscript = '';
$func_idx = 0;


// First, write the change scripts

$out = array();
$ec = -1;
exec('svn info https://svn.viathinksoft.com/svn/oidplus/trunk/ | grep "Revision:" | cut -d " " -f 2', $out, $ec);
if ($ec != 0) die("SVN Info failed!!!\n");
$max_svn = implode("", $out);

for ($i=2; $i<=$max_svn; $i++) {
        echo "SVN revision $i / $max_svn\r";

        $outfile = $output_dir."/update_".($i-1)."_to_$i.txt";
        if (!$force && is_file($outfile)) continue;

        $outdir_old = "/tmp/oidplus_svntmp2_".($i-1)."/";
        if ($outdir_old && is_dir($outdir_old)) exec("rm -rf $outdir_old", $out, $ec);
        exec("svn co https://svn.viathinksoft.com/svn/oidplus/trunk/@".($i-1)." $outdir_old", $out, $ec);
        if ($ec != 0) die("Checkout of SVN Rev ".($i-1)." failed!!!\n");
        hotfix_dir($i-1, $outdir_old);

        $outdir_new = "/tmp/oidplus_svntmp2_$i/";
        if ($outdir_new && is_dir($outdir_new)) exec("rm -rf $outdir_new", $out, $ec);
        exec("svn co https://svn.viathinksoft.com/svn/oidplus/trunk/@$i $outdir_new", $out, $ec);
        if ($ec != 0) die("Checkout of SVN Rev ".($i)." failed!!!\n");
        hotfix_dir($i, $outdir_new);

        $outscript  = "<?php\n";
        $outscript .= "\n";
        $outscript .= "/*\n";
        $outscript .= " * OIDplus 2.0\n";
        $outscript .= " * Copyright 2019 - ".date('Y')." Daniel Marschall, ViaThinkSoft\n";
        $outscript .= " *\n";
        $outscript .= " * Licensed under the Apache License, Version 2.0 (the \"License\");\n";
        $outscript .= " * you may not use this file except in compliance with the License.\n";
        $outscript .= " * You may obtain a copy of the License at\n";
        $outscript .= " *\n";
        $outscript .= " *     http://www.apache.org/licenses/LICENSE-2.0\n";
        $outscript .= " *\n";
        $outscript .= " * Unless required by applicable law or agreed to in writing, software\n";
        $outscript .= " * distributed under the License is distributed on an \"AS IS\" BASIS,\n";
        $outscript .= " * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n";
        $outscript .= " * See the License for the specific language governing permissions and\n";
        $outscript .= " * limitations under the License.\n";
        $outscript .= " */\n";
        $outscript .= "\n";
        $outscript .= "function info(\$str) { echo \"INFO: \$str\\n\"; }\n";
        $outscript .= "function warn(\$str) { echo \"WARNING: \$str\\n\"; }\n";
        $outscript .= "function err(\$str) { die(\"FATAL ERROR: \$str\\n\"); }\n";
        $outscript .= "\n";
        $outscript .= "@set_time_limit(0);\n";
        $outscript .= "\n";
        $outscript .= "@header('Content-Type: text/plain');\n";
        $outscript .= "\n";
        $outscript .= "chdir(__DIR__);\n";
        if ($i >= 662) {
                $outscript .= "if (trim(@file_get_contents('.version.php')) !== '<?php // Revision ".($i-1)."') {\n";
                $outscript .= "\terr('This update can only be applied to OIDplus version svn-".($i-1)."!');\n";
                $outscript .= "}\n";
        } else {
                $outscript .= "if (trim(@file_get_contents('oidplus_version.txt')) !== 'Revision ".($i-1)."') {\n";
                $outscript .= "\terr('This update can only be applied to OIDplus version svn-".($i-1)."!');\n";
                $outscript .= "}\n";
        }
        $outscript .= "\n";
        /*
        if ($i >= 99999) {
                ... once we require PHP 7.1, we add the requirement here
                ... also if we require fancy new PHP modules, we must add it here
                ... the checks avoid that someone breaks their OIDplus installation if they update
        } else
        */if ($i >= 2) {
                // Rev 2+ requires PHP 7.0.0
                $outscript .= "if (version_compare(PHP_VERSION, '7.0.0') < 0) {\n";
                $outscript .= "\terr('You need PHP Version 7.0 to update to this version');\n";
                $outscript .= "}\n";
        }
        $outscript .= "\n";
        //$outscript .= "info('Update to OIDplus version svn-$i running...');\n";
        //$outscript .= "\n";
        getDirContents($outdir_old, $outdir_new);
        $outscript .= "\n";
        if ($i >= 661) {
                $outscript .= "file_put_contents('.version.php', \"<?php // Revision $i\\n\");\n";
                $outscript .= "if (trim(@file_get_contents('.version.php')) !== '<?php // Revision $i') err('Could not write to .version.php!');\n";
                if ($i == 661) {
                        $outscript .= "@unlink('oidplus_version.txt');\n";
                        $outscript .= "if (is_file('oidplus_version.txt')) err('Could not delete oidplus_version.txt! Please delete it manually');\n";
                }
        } else {
                $outscript .= "file_put_contents('oidplus_version.txt', \"Revision $i\\n\");\n";
                $outscript .= "if (trim(@file_get_contents('oidplus_version.txt')) !== 'Revision $i') err('Could not write to oidplus_version.txt!');\n";
        }
        $outscript .= "\n";
        $outscript .= "\n";
        $outscript .= "info('Update to OIDplus version svn-$i done!');\n";
        $outscript .= "\n";
        $outscript .= "unlink(__FILE__);\n";
        $outscript .= "\n";

        // Now add digital signature

        if (strpos($outscript, '<?php') === false) {
                echo "Not a PHP file\n"; // Should not happen
                continue;
        }

        $naked = preg_replace('@<\?php /\* <ViaThinkSoftSignature>(.+)</ViaThinkSoftSignature> \*/ \?>\n@ismU', '', $outscript);

        $hash = hash("sha256", $naked.basename($outfile));

        $pkeyid = openssl_pkey_get_private('file://'.$priv_key);
        openssl_sign($hash, $signature, $pkeyid, OPENSSL_ALGO_SHA256);
        openssl_free_key($pkeyid);

        if (!$signature) {
                echo "ERROR: Signature failed\n";
                continue;
        }

        $sign_line = '<?php /* <ViaThinkSoftSignature>'."\n".split_equal_length(base64_encode($signature),65).'</ViaThinkSoftSignature> */ ?>';

        // We have to put the signature at the beginning, because we don't know if the end of the file lacks a PHP closing tag
        if (substr($outscript,0,2) === '#!') {
                // Preserve shebang
                $shebang_pos = strpos($naked, "\n");
                $shebang = substr($naked, 0, $shebang_pos);
                $rest = substr($naked, $shebang_pos+1);
                $outscript = $shebang."\n".$sign_line."\n".$rest;
        } else {
                $outscript = $sign_line."\n".$naked;
        }

        // Write the file

        file_put_contents($outfile, $outscript);

        $ec = -1;
        $out = array();
        exec('php -l '.escapeshellarg($outfile), $out, $ec);
        if ($ec != 0) {
                fwrite(STDERR, "STOP! $outfile PHP syntax error!\n");
                unlink($outfile);
                break;
        }
        file_put_contents($outfile.'.gz', gzencode($outscript));

        // Delete temp dirs

        $ec = -1;
        $out = array();
        if ($outdir_old && is_dir($outdir_old)) exec("rm -rf $outdir_old", $out, $ec);
        if ($outdir_new && is_dir($outdir_new)) exec("rm -rf $outdir_new", $out, $ec);
}
echo "\n";


// Now write the release messages (required by software update and vnag)

$ec = -1;
$out = array();
exec('svn log https://svn.viathinksoft.com/svn/oidplus/trunk --xml', $out, $ec);
if ($ec != 0) {
        fwrite(STDERR, "SVN Log failed\n");
} else {
        $str = implode("\n",$out);

        $xml = simplexml_load_string($str);

        $out = array();

        foreach ($xml as $a) {

                $out[(int)$a->attributes()->revision] = array(

                        'date' => date('Y-m-d H:i:s',strtotime((string)$a->date)),
                        'author' => (string)$a->author,
                        'msg' => trim((string)$a->msg),

                );

        }

        ksort($out);

        // TODO: We should also digitally sign these files?
        file_put_contents($output_dir.'/releases.ser', serialize($out));
        file_put_contents($output_dir.'/releases.ser.gz', gzencode(serialize($out)));
}

# ------------------------------------------------------------------------------------------

/**
 * @param string $dir_old
 * @param string $dir_new
 * @param string|null $basepath_old
 * @param string|null $basepath_new
 * @return void
 */
function getDirContents_del(string $dir_old, string $dir_new, string $basepath_old=null, string $basepath_new=null) {
        global $outscript;

        if (is_null($basepath_old)) $basepath_old = $dir_old;
        $basepath_old = my_realpath($basepath_old) . DIRECTORY_SEPARATOR;
        if ($basepath_old == '/') {
                fwrite(STDERR, 'ARG');
                die();
        }

        $dir_old = my_realpath($dir_old) . DIRECTORY_SEPARATOR;
        $dir_new = my_realpath($dir_new) . DIRECTORY_SEPARATOR;
        $files_old = my_scandir($dir_old);
        //$files_new = my_scandir($dir_new);

        foreach ($files_old as $file_old) {
                if ($file_old === '.') continue;
                if ($file_old === '..') continue;
                if ($file_old === '.svn') continue;
                if ($file_old === '.git') continue;

                $path_old = my_realpath($dir_old . DIRECTORY_SEPARATOR . $file_old);
                $path_new = my_realpath($dir_new . DIRECTORY_SEPARATOR . $file_old);

                $xpath_old = substr($path_old, strlen($basepath_old));

                if (is_dir($path_old)) {
                        getDirContents_del($path_old, $path_new, $basepath_old, $basepath_new);
                }

                // Note: We don't warn if a file-to-be-deleted has vanished. It would not be necessary to warn about it
                if (is_dir($path_old) && !is_dir($path_new)) {
                        $outscript .= "// Dir deleted: $xpath_old\n";
                        $outscript .= "@rmdir('$xpath_old');\n";
                        $outscript .= "if (is_dir('$xpath_old')) {\n";
                        $outscript .= "\twarn('Directory could not be deleted (was not empty?): $xpath_old');\n";
                        $outscript .= "}\n";
                        $outscript .= "\n";
                } else if (is_file($path_old) && !is_file($path_new)) {
                        $outscript .= "// File deleted: $xpath_old\n";
                        $outscript .= "@unlink('$xpath_old');\n";
                        $outscript .= "if (is_file('$xpath_old')) {\n";
                        $outscript .= "\twarn('File could not be deleted: $xpath_old');\n";
                        $outscript .= "}\n";
                        $outscript .= "\n";
                }
        }
}

/**
 * @param string $dir_old
 * @param string $dir_new
 * @param string|null $basepath_old
 * @param string|null $basepath_new
 * @return void
 * @throws Exception
 */
function getDirContents_diff(string $dir_old, string $dir_new, string $basepath_old=null, string $basepath_new=null) {
        global $outscript;

        if (is_null($basepath_old)) $basepath_old = $dir_old;
        $basepath_old = my_realpath($basepath_old) . DIRECTORY_SEPARATOR;
        if ($basepath_old == '/') {
                fwrite(STDERR, 'ARG');
                die();
        }

        $dir_old = my_realpath($dir_old) . DIRECTORY_SEPARATOR;
        $dir_new = my_realpath($dir_new) . DIRECTORY_SEPARATOR;
        $files_old = my_scandir($dir_old);
        $files_new = my_scandir($dir_new);

        foreach ($files_old as $file_old) {
                if ($file_old === '.') continue;
                if ($file_old === '..') continue;
                if ($file_old === '.svn') continue;
                if ($file_old === '.git') continue;

                $path_old = my_realpath($dir_old . DIRECTORY_SEPARATOR . $file_old);
                $path_new = my_realpath($dir_new . DIRECTORY_SEPARATOR . $file_old);

                $xpath_old = substr($path_old, strlen($basepath_old));

                if (is_file($path_old) && is_file($path_new)) {
                        if (file_get_contents($path_old) != file_get_contents($path_new)) {
                                $outscript .= "// Files different: $xpath_old\n";

                                global $func_idx;
                                $func_idx++;
                                $outscript .= "function writefile_".$func_idx."() {\n";
                                special_save_file($xpath_old, $path_new, $outscript, "\t@");
                                $outscript .= "\t@touch('$xpath_old',".filemtime($path_new).");\n";
                                $outscript .= "}\n";

                                $outscript .= "if (!is_file('$xpath_old')) {\n";
                                $outscript .= "\twarn('File has vanished! Will re-create it: $xpath_old');\n";
                                $outscript .= "\twritefile_".$func_idx."();\n";
                                $outscript .= "\tif (!is_file('$xpath_old')) {\n";
                                $outscript .= "\t\twarn('File cannot be created (not existing): $xpath_old');\n";
                                $outscript .= "\t} else if (sha1_file('$xpath_old') != '".sha1_file($path_new)."') {\n";
                                $outscript .= "\t\twarn('File cannot be created (checksum mismatch): $xpath_old');\n";
                                $outscript .= "\t} else if ((DIRECTORY_SEPARATOR === '/') && !@chmod('$xpath_old', 0".sprintf('%o', fileperms($path_new) & 0777).")) {\n";
                                $outscript .= "\t\twarn('Could not change file permissions of ".$xpath_old."');\n";
                                $outscript .= "\t}\n";

                                $outscript .= "} else {\n";

                                $outscript .= "\tif (@sha1_file('$xpath_old') !== '".sha1_file($path_new)."') {\n"; // it is possible that the file is already updated (e.g. by a manual hotfix)
                                $outscript .= "\t\tif (@sha1_file('$xpath_old') !== '".sha1_file($path_old)."') {\n";
                                $outscript .= "\t\t\twarn('File was modified. Will overwrite the changes now: $xpath_old');\n";
                                $outscript .= "\t\t\t\$tmp = pathinfo('$xpath_old');\n";
                                $outscript .= "\t\t\t\$backup_name = \$tmp['dirname'].DIRECTORY_SEPARATOR.\$tmp['filename'].'.'.date('Ymdhis',@filemtime('$xpath_old')).(isset(\$tmp['extension']) ? '.'.\$tmp['extension'] : '');\n";
                                $outscript .= "\t\t\twarn('Creating a backup as '.\$backup_name);\n";
                                $outscript .= "\t\t\tif (!@copy('$xpath_old', \$backup_name)) {\n";
                                $outscript .= "\t\t\t\twarn('Creation of backup failed');\n";
                                $outscript .= "\t\t\t}\n";
                                $outscript .= "\t\t}\n";
                                $outscript .= "\t\twritefile_".$func_idx."();\n";
                                $outscript .= "\t\tif (@sha1_file('$xpath_old') !== '".sha1_file($path_new)."') {\n";
                                $outscript .= "\t\t\twarn('File cannot be written (checksum mismatch): $xpath_old');\n";
                                $outscript .= "\t\t}\n";
                                $outscript .= "\t}\n";

                                $outscript .= "}\n";
                                $outscript .= "\n";
                        }
                        if ((fileperms($path_old) & 0777) != (fileperms($path_new) & 0777)) {
                                $outscript .= "// Different file chmod: $xpath_old\n";
                                $outscript .= "if ((DIRECTORY_SEPARATOR === '/') && !@chmod('$xpath_old', 0".sprintf('%o', fileperms($path_new) & 0777).")) {\n";
                                $outscript .= "\twarn('Could not change file permissions of ".$xpath_old."');\n";
                                $outscript .= "}\n";
                                $outscript .= "\n";
                        }
                } else if (is_dir($path_old) && is_dir($path_new)) {
                        /*
                        $outscript .= "// Verify that directory exists: $xpath_old\n";
                        $outscript .= "if (!is_dir('$xpath_old')) {\n";
                        $outscript .= "\twarn('Directory has vanished! Will re-create it: $xpath_old');\n";
                        $outscript .= "\t@mkdir('$xpath_old');\n";
                        $outscript .= "\tif (!is_dir('$xpath_old')) {\n";
                        $outscript .= "\t\twarn('Directory could not be created: $xpath_old');\n";
                        $outscript .= "\t}\n";
                        $outscript .= "}\n";
                        $outscript .= "\n";
                        */

                        if ((fileperms($path_old) & 0777) != (fileperms($path_new) & 0777)) {
                                $outscript .= "// Different dir chmod: $xpath_old\n";
                                $outscript .= "if ((DIRECTORY_SEPARATOR === '/') && !@chmod('$xpath_old', 0".sprintf('%o', fileperms($path_new) & 0777).")) {\n";
                                $outscript .= "\twarn('Could not change dir permissions of ".$xpath_old."');\n";
                                $outscript .= "}\n";
                                $outscript .= "\n";
                        }
                }

                if (is_dir($path_old)) {
                        getDirContents_diff($path_old, $path_new, $basepath_old, $basepath_new);
                }
        }
}

/**
 * @param string $dir_old
 * @param string $dir_new
 * @param string|null $basepath_old
 * @param string|null $basepath_new
 * @return void
 * @throws Exception
 */
function getDirContents_add(string $dir_old, string $dir_new, string $basepath_old=null, string $basepath_new=null) {
        global $outscript;

        if (is_null($basepath_new)) $basepath_new = $dir_new;
        $basepath_new = my_realpath($basepath_new) . DIRECTORY_SEPARATOR;
        if ($basepath_new == '/') {
                fwrite(STDERR, 'ARG');
                die();
        }

        $dir_old = my_realpath($dir_old) . DIRECTORY_SEPARATOR;
        $dir_new = my_realpath($dir_new) . DIRECTORY_SEPARATOR;
        //$files_old = my_scandir($dir_old);
        $files_new = my_scandir($dir_new);

        foreach ($files_new as $file_new) {
                if ($file_new === '.') continue;
                if ($file_new === '..') continue;
                if ($file_new === '.svn') continue;
                if ($file_new === '.git') continue;

                $path_old = my_realpath($dir_old . DIRECTORY_SEPARATOR . $file_new);
                $path_new = my_realpath($dir_new . DIRECTORY_SEPARATOR . $file_new);

                $xpath_new = substr($path_new, strlen($basepath_new));

                if (is_dir($path_new) && !is_dir($path_old)) {
                        // Note: We are not warning if the dir was already created by the user
                        $outscript .= "// Dir added: $xpath_new\n";
                        $outscript .= "@mkdir('$xpath_new');\n";
                        $outscript .= "if (!is_dir('$xpath_new')) {\n";
                        $outscript .= "\twarn('Directory could not be created: $xpath_new');\n";
                        $outscript .= "} else if ((DIRECTORY_SEPARATOR === '/') && !@chmod('$xpath_new', 0".sprintf('%o', fileperms($path_new) & 0777).")) {\n";
                        $outscript .= "\twarn('Could not change directory permissions of ".$xpath_new."');\n";
                        $outscript .= "}\n";
                        $outscript .= "\n";

                        // we create it locally, so that the recursive code still works
                        mkdir($dir_old . DIRECTORY_SEPARATOR . $file_new);
                        $path_old = my_realpath($dir_old . DIRECTORY_SEPARATOR . $file_new);

                } else if (is_file($path_new) && !is_file($path_old)) {
                        $outscript .= "// File added: $xpath_new\n";

                        global $func_idx;
                        $func_idx++;
                        $outscript .= "function writefile_".$func_idx."() {\n";
                        special_save_file($xpath_new, $path_new, $outscript, "\t@");
                        $outscript .= "\t@touch('$xpath_new',".filemtime($path_new).");\n";
                        $outscript .= "}\n";

                        // Note: We will not warn if the file was created and is exactly the file we want
                        $outscript .= "if (is_file('$xpath_new') && (sha1_file('$xpath_new') != '".sha1_file($path_new)."')) {\n";
                        $outscript .= "\twarn('File was created by someone else. Will overwrite the changes now: $xpath_new');\n";
                        $outscript .= "\t\$tmp = pathinfo('$xpath_new');\n";
                        $outscript .= "\t\$backup_name = \$tmp['dirname'].DIRECTORY_SEPARATOR.\$tmp['filename'].'.'.date('Ymdhis',@filemtime('$xpath_new')).(isset(\$tmp['extension']) ? '.'.\$tmp['extension'] : '');\n";
                        $outscript .= "\twarn('Creating a backup as '.\$backup_name);\n";
                        $outscript .= "\tif (!@copy('$xpath_new', \$backup_name)) {\n";
                        $outscript .= "\t\twarn('Creation of backup failed');\n";
                        $outscript .= "\t}\n";
                        $outscript .= "}\n";

                        $outscript .= "writefile_".$func_idx."();\n";
                        $outscript .= "if (!is_file('$xpath_new')) {\n";
                        $outscript .= "\twarn('File cannot be created (not existing): $xpath_new');\n";
                        $outscript .= "} else if (sha1_file('$xpath_new') != '".sha1_file($path_new)."') {\n";
                        $outscript .= "\twarn('File cannot be created (checksum mismatch): $xpath_new');\n";
                        $outscript .= "} else if ((DIRECTORY_SEPARATOR === '/') && !@chmod('$xpath_new', 0".sprintf('%o', fileperms($path_new) & 0777).")) {\n";
                        $outscript .= "\twarn('Could not change file permissions of ".$xpath_new."');\n";
                        $outscript .= "}\n";
                        $outscript .= "\n";
                }

                if (is_dir($path_new)) {
                        getDirContents_add($path_old, $path_new, $basepath_old, $basepath_new);
                }
        }
}

/**
 * @param string $dir_old
 * @param string $dir_new
 * @return void
 * @throws Exception
 */
function getDirContents(string $dir_old, string $dir_new) {
        global $func_idx;
        $func_idx = 0;
        getDirContents_add($dir_old, $dir_new);
        getDirContents_diff($dir_old, $dir_new);
        getDirContents_del($dir_old, $dir_new);
}

/**
 * @param int $rev
 * @param string $dir
 * @return void
 */
function hotfix_dir(int $rev, string $dir) {
        if ($rev == 699) {
                // Fix syntax error that lead to a stalled update!
                $file = $dir.'/plugins/viathinksoft/adminPages/900_software_update/OIDplusPageAdminSoftwareUpdate.class.php';
                $cont = file_get_contents($file);
                $cont = str_replace("urlencode('oidplus:system_file_check',OIDplus::getEditionInfo()['downloadpage']))",
                                    "urlencode('oidplus:system_file_check'),OIDplus::getEditionInfo()['downloadpage'])",
                                    $cont);
                file_put_contents($file, $cont);

                // Fix syntax error that lead to a stalled update!
                $file = $dir.'/plugins/viathinksoft/adminPages/901_vnag_version_check/vnag.php';
                $cont = file_get_contents($file);
                $cont = str_replace("\t\tOIDplus::getEditionInfo()", "", $cont);
                file_put_contents($file, $cont);
        }
        if ($rev == 830) {
                // Fix bug that caused system ID to get lost
                $file = $dir.'/includes/classes/OIDplus.class.php';
                $cont = file_get_contents($file);
                $cont = str_replace("if ((\$passphrase === false) || !is_privatekey_encrypted(\$privKey)) {",
                                    "if ((\$passphrase === false) || !is_privatekey_encrypted(OIDplus::config()->getValue('oidplus_private_key'))) {",
                                    $cont);
                file_put_contents($file, $cont);
        }
        if ($rev == 856) {
                // Fix runtime error that lead to a stalled update!
                $file = $dir.'/includes/classes/OIDplus.class.php';
                $cont = file_get_contents($file);
                $cont = str_replace('$this->recanonizeObjects();', '', $cont);
                file_put_contents($file, $cont);
        }
        if ($rev == 1108) {
                // Fix runtime error that lead to a stalled update!
                $file = $dir.'/vendor/danielmarschall/php_utils/vts_crypt.inc.php';
                $cont = file_get_contents($file);
                $cont = str_replace('echo "OK, password $password\n";', '', $cont);
                file_put_contents($file, $cont);
        }
}

/**
 * @param string $data
 * @param int $width
 * @return string
 */
function split_equal_length(string $data, int $width=65): string {
        $res = '';
        for ($i=0; $i<strlen($data); $i+=$width) {
                $res .= substr($data, $i, $width)."\n";
        }
        return $res;
}

/**
 * @param string $out_file
 * @param string $in_file
 * @param string $res
 * @param string $line_prefix
 * @param int $width
 * @return void
 * @throws Exception
 */
function special_save_file(string $out_file, string $in_file, string &$res, string $line_prefix, int $width=50) {
        $handle = @fopen($in_file, "rb");
        if (!$handle) {
                throw new Exception("Cannot open file $in_file");
        }
        $res .= $line_prefix."\$fp = fopen('$out_file', 'w');\n";

        while (!feof($handle)) {
                // important: must be a multiple of 3, otherwise we have base64 paddings!
                $buffer = fread($handle, $width*3);
                $base64 = base64_encode($buffer);
                $res .= $line_prefix."fwrite(\$fp, base64_decode('".$base64."'));\n";
        }

        $res .= $line_prefix."fclose(\$fp);\n";
        fclose($handle);
}

/**
 * @param string $name
 * @return mixed|string
 */
function my_realpath(string $name) {
        $ret = realpath($name);
        return ($ret === false) ? $name : $ret;
}

/**
 * @param string $dir
 * @return array
 */
function my_scandir(string $dir): array {
        $ret = @scandir($dir);
        if ($ret === false) return array();
        return $ret;
}