TimeTrex/tools/unattended_upgrade.php

782 lines
42 KiB
PHP

<?php
/*********************************************************************************
*
* TimeTrex is a Workforce Management program developed by
* TimeTrex Software Inc. Copyright (C) 2003 - 2021 TimeTrex Software Inc.
*
* This program is free software; you can redistribute it and/or modify it under
* the terms of the GNU Affero General Public License version 3 as published by
* the Free Software Foundation with the addition of the following permission
* added to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED
* WORK IN WHICH THE COPYRIGHT IS OWNED BY TIMETREX, TIMETREX DISCLAIMS THE
* WARRANTY OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
* details.
*
*
* You should have received a copy of the GNU Affero General Public License along
* with this program; if not, see http://www.gnu.org/licenses or write to the Free
* Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301 USA.
*
*
* You can contact TimeTrex headquarters at Unit 22 - 2475 Dobbin Rd. Suite
* #292 West Kelowna, BC V4T 2E9, Canada or at email address info@timetrex.com.
*
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License version 3.
*
*
* In accordance with Section 7(b) of the GNU Affero General Public License
* version 3, these Appropriate Legal Notices must retain the display of the
* "Powered by TimeTrex" logo. If the display of the logo is not reasonably
* feasible for technical reasons, the Appropriate Legal Notices must display
* the words "Powered by TimeTrex".
*
********************************************************************************/
//This must go above include for global.inc.php
if ( isset( $argv ) && in_array( '--config', $argv ) ) {
$_SERVER['TT_CONFIG_FILE'] = trim( $argv[( array_search( '--config', $argv ) + 1 )] );
}
//If requirements only check is enabled, do not connect to the database just in case the database isnt setup yet or setup incorrectly.
if ( isset( $argv ) && in_array( '--requirements_only', $argv ) ) {
$disable_database_connection = true;
}
require_once( dirname( __FILE__ ) . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'includes' . DIRECTORY_SEPARATOR . 'global.inc.php' );
//Always enable debug logging during upgrade.
Debug::setEnable( true );
Debug::setBufferOutput( true );
Debug::setEnableLog( true );
Debug::setVerbosity( 10 );
ignore_user_abort( true );
ini_set( 'default_socket_timeout', 5 );
ini_set( 'allow_url_fopen', 1 );
ini_set( 'max_execution_time', 0 );
ini_set( 'memory_limit', '2048M' ); //Just in case.
require_once( dirname( __FILE__ ) . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'includes' . DIRECTORY_SEPARATOR . 'CLI.inc.php' );
//Since we aren't including database.inc.php, force the timezone to be set to avoid WARNING(2): getdate(): It is not safe to rely on the system's timezone settings. You are *required* to use the date.timezone setting or the date_default_timezone_set()
//Sometimes scripts won't make a database connection
if ( !isset( $config_vars['other']['system_timezone'] ) || ( isset( $config_vars['other']['system_timezone'] ) && $config_vars['other']['system_timezone'] == '' ) ) {
$config_vars['other']['system_timezone'] = @date( 'e' );
}
if ( $config_vars['other']['system_timezone'] == '' ) {
$config_vars['other']['system_timezone'] = 'GMT';
}
TTDate::setTimeZone( $config_vars['other']['system_timezone'], false, false ); //Don't force SQL to be executed here, as an optimization to avoid DB connections when calling things like getProgressBar()
//Re-initialize install object with new config file.
$install_obj = new Install();
if ( isset( $config_vars['other']['primary_company_id'] ) ) {
$company_id = $config_vars['other']['primary_company_id'];
} else {
$company_id = null;
}
//The installer already checks the cache directory to make sure its writable, so use that as the upgrade staging directory.
//The cache dir does get cleaned once per week though, but if an upgrade failed that may be helpful.
if ( !isset( $config_vars['cache']['dir'] ) ) { //Just in case the cache directory is not set.
$config_vars['cache']['dir'] = Environment::getBasePath();
}
$upgrade_staging_dir = $config_vars['cache']['dir'] . DIRECTORY_SEPARATOR . 'upgrade_staging' . DIRECTORY_SEPARATOR;
$upgrade_staging_latest_dir = $upgrade_staging_dir . DIRECTORY_SEPARATOR . 'latest_version';
$upgrade_file_name = $config_vars['cache']['dir'] . DIRECTORY_SEPARATOR . 'UPGRADE.ZIP';
$php_cli = $config_vars['path']['php_cli'];
function moveUpgradeFiles( $upgrade_staging_latest_dir ) {
$latest_file_list = Misc::getFileList( $upgrade_staging_latest_dir, null, true );
if ( is_array( $latest_file_list ) && count( $latest_file_list ) > 0 ) {
foreach ( $latest_file_list as $latest_file ) {
$new_file = str_replace( $upgrade_staging_latest_dir, Environment::getBasePath(), $latest_file );
//Check if directory exists.
if ( !is_dir( dirname( $new_file ) ) ) {
Debug::Text( 'Creating new directory: ' . dirname( $new_file ), __FILE__, __LINE__, __METHOD__, 10 );
if ( @mkdir( dirname( $new_file ), 0755, true ) == false ) { //Read+Write+Execute for owner, Read/Execute for all others.
Debug::Text( 'ERROR: FAILED TO CREATE DIRECTORY: ' . $new_file, __FILE__, __LINE__, __METHOD__, 10 );
}
}
Debug::Text( 'Moving: ' . $latest_file . ' To: ' . $new_file, __FILE__, __LINE__, __METHOD__, 10 );
if ( Misc::rename( $latest_file, $new_file ) == false ) {
Debug::Text( 'ERROR: FAILED TO MOVE: ' . $latest_file . ' To: ' . $new_file, __FILE__, __LINE__, __METHOD__, 10 );
}
}
} else {
Debug::Text( 'No files to move... Are we running --stage2 perhaps?', __FILE__, __LINE__, __METHOD__, 10 );
}
clearstatcache();
return true;
}
function setAutoUpgradeFailed( $value = 1 ) {
//When upgrading from pre-UUID versions, its possible if a failure occurs before the schema version upgrade has fully completed,
// setSystemSetting() will then fail with a PHP fatal error saying it can't find class TTUUID, preventing the error log from being captured.
if ( class_exists( 'TTUUID' ) == true ) {
SystemSettingFactory::setSystemSetting( 'auto_upgrade_failed', $value );
if ( $value == 1 ) {
// Auto upgrade failed
if ( DEMO_MODE == false && PRODUCTION == true ) {
global $db;
//Make sure the notification table exists so when upgrading from versions without notifications, to versions with notifications,
// if the system requirements check or schema fails we don't trigger a SQL exception attempting to send a notification.
$install_obj = new Install();
$install_obj->setDatabaseConnection( $db ); //Default connection
if ( $install_obj->checkTableExists( 'notification' ) == true ) {
Debug::Text( ' Notification table exists, sending notifications about the upgrade failure...', __FILE__, __LINE__, __METHOD__, 10 );
$notification_data = [
'object_id' => TTUUID::getNotExistID( 1110 ),
'object_type_id' => 0,
'type_id' => 'system',
'title_short' => TTi18n::getText( 'Automatic upgrade failed' ),
'body_short' => TTi18n::getText( 'WARNING: %1 automatic upgrade has failed due to a system error! Please contact your %1 administrator immediately to re-run the %1 installer to correct the issue.', APPLICATION_NAME ),
];
Notification::sendNotificationToAllusers( 80, true, true, $notification_data, 604800 ); //ZZZ REMOVE ME
return true;
} else {
Debug::Text( 'ERROR: Notification table does not exist, unable to send notifications...', __FILE__, __LINE__, __METHOD__, 10 );
}
}
Debug::Text( 'ERROR: AutoUpgrade Failed, setting failed flag...', __FILE__, __LINE__, __METHOD__, 10 );
} else {
Debug::Text( 'AutoUpgrade Success, clearing failed flag...', __FILE__, __LINE__, __METHOD__, 10 );
//Clear other messages that likely aren't valid anymore.
SystemSettingFactory::setSystemSetting( 'valid_install_requirements', 1 );
SystemSettingFactory::setSystemSetting( 'new_version', 0 );
}
}
return true;
}
function CLIExit( $code = 0, $delete_lock_file = true ) {
Debug::Display();
Debug::writeToLog();
if ( $delete_lock_file == true ) {
global $lock_file;
if ( is_object( $lock_file ) ) {
$lock_file->delete();
}
}
exit( $code );
}
Debug::Text( 'Version: ' . APPLICATION_VERSION . ' (PHP: v' . phpversion() . ') Edition: ' . getTTProductEdition() . ' Production: ' . (int)PRODUCTION . ' Server: ' . ( isset( $_SERVER['SERVER_ADDR'] ) ? $_SERVER['SERVER_ADDR'] : 'N/A' ) . ' Database: Type: ' . ( isset( $config_vars['database']['type'] ) ? $config_vars['database']['type'] : 'N/A' ) . ' Name: ' . ( isset( $config_vars['database']['database_name'] ) ? $config_vars['database']['database_name'] : 'N/A' ) . ' Config: ' . CONFIG_FILE . ' Demo Mode: ' . (int)DEMO_MODE, __FILE__, __LINE__, __METHOD__, 10 );
//Force flush after each output line.
ob_implicit_flush( true );
ob_end_flush();
if ( isset( $argv[1] ) && in_array( $argv[1], [ '--help', '-help', '-h', '-?' ] ) ) {
$help_output = "Usage: unattended_upgrade.php\n";
$help_output .= " [--config] = Config file to use.\n";
$help_output .= " [--schema_only] = Run a schema upgrade only.\n";
$help_output .= " [--pre_requirements_update] = Run a pre system requirements update.\n";
$help_output .= " [--requirements_only] = Run a system requirements check only.\n";
$help_output .= " [-f] = Force upgrade even if INSTALL mode is disabled.\n";
echo $help_output;
} else {
//Create lock file so the same clock isn't being synchronized more then once at a time.
// Use arguments in lock file name, so each argument or separate run has a separte lock file.
$lock_file_name = $config_vars['cache']['dir'] . DIRECTORY_SEPARATOR . 'UnAttended_Upgrade_' . crc32( serialize( $argv ) ); //hash the arguments so we always use a different lock file name when using different arguments.
$lock_file = new LockFile( $lock_file_name . '.lock' );
$lock_file->max_lock_file_age = ( 3600 * 3 ); //3 hrs.
Debug::text( 'Lock File: ' . $lock_file->getFileName(), __FILE__, __LINE__, __METHOD__, 10 );
if ( $lock_file->exists() == true ) {
Debug::text( 'Lock File already exists, exiting...', __FILE__, __LINE__, __METHOD__, 10 );
echo 'Upgrade is already running, please wait for it to finish...' . "\n";
CLIExit( 253, false ); //Don't delete lock file.
} else {
if ( $lock_file->create() == false ) {
Debug::text( 'Unable to create lock file, likely already exists, exiting...', __FILE__, __LINE__, __METHOD__, 10 );
}
//Continue trying to run even if we can't create the lock file.
}
unset( $lock_file_name );
$last_arg = ( count( $argv ) - 1 );
if ( in_array( '-f', $argv ) ) {
$force = true;
} else {
$force = false;
}
//Full force mode, forces upgrade even if the file downloaded is the same version.
//Primarily should be used when UPGRADE.ZIP already exists.
if ( in_array( '-ff', $argv ) ) {
$force = true;
$full_force = true;
} else {
$full_force = false;
}
if ( isset( $argv ) && in_array( '--upgrade_file', $argv ) ) { //Allow forcing a specific file to use for upgrading instead of downloading one.
$manual_upgrade_file_name = trim( $argv[( array_search( '--upgrade_file', $argv ) + 1 )] );
}
if ( in_array( '--pre_requirements_update', $argv ) ) {
Debug::Text( 'Running pre-requirements update only...', __FILE__, __LINE__, __METHOD__, 10 );
if ( $force == true || version_compare( APPLICATION_VERSION, '10.0.0', '>=' ) == true ) {
//Cant enable INTL/ZIP extensions, as they won't load on some stack installs...
// Debug::Text(' Running: v10.0.0 Pre-Requirements Update...', __FILE__, __LINE__, __METHOD__, 10);
//
// //Check if stack installer was used, if so, attempt to enable INTL extension.
// if ( PHP_OS == 'WINNT' ) {
// $full_stack_file = dirname(__FILE__) . DIRECTORY_SEPARATOR .'..'. DIRECTORY_SEPARATOR .'..'. DIRECTORY_SEPARATOR .'..'. DIRECTORY_SEPARATOR .'..'. DIRECTORY_SEPARATOR .'start.bat';
// Debug::Text(' Checking if this is a full stack install or not: '. $full_stack_file, __FILE__, __LINE__, __METHOD__, 10);
// if ( !file_exists( $full_stack_file ) ) {
// $full_stack_file = dirname(__FILE__) . DIRECTORY_SEPARATOR .'..'. DIRECTORY_SEPARATOR .'..'. DIRECTORY_SEPARATOR .'start.bat';
// Debug::Text(' bChecking if this is a full stack install or not: '. $full_stack_file, __FILE__, __LINE__, __METHOD__, 10);
// if ( !file_exists( $full_stack_file ) ) {
// Debug::Text(' This is NOT a full stack install... Exiting...', __FILE__, __LINE__, __METHOD__, 10);
// CLIExit(0); //Exit success as it may not be a stack install.
// }
// }
//
// $stack_install_dir = realpath( dirname( $full_stack_file ) );
// Debug::Text(' Checking full stack install directory: '. $stack_install_dir, __FILE__, __LINE__, __METHOD__, 10);
// if ( file_exists( $stack_install_dir ) ) {
// Debug::Text(' Found full stack install directory: '. $stack_install_dir, __FILE__, __LINE__, __METHOD__, 10);
// $php_ini_file = $stack_install_dir. DIRECTORY_SEPARATOR .'php'. DIRECTORY_SEPARATOR .'php.ini';
// if ( file_exists( $php_ini_file ) ) {
// Debug::Text( ' PHP INI file found: '. $php_ini_file, __FILE__, __LINE__, __METHOD__, 10 );
// $php_ini_contents = file_get_contents( $php_ini_file );
//
// //Enable PHP MCRYPT extension. Seems like it was compiled under a wrong version of PHP though, so don't do this.
// //$php_ini_contents = preg_replace('/^;extension=php_mcrypt\.dll/mi', 'extension=php_mcrypt.dll', $php_ini_contents, -1, $replacement_count );
// //Debug::Text( ' PHP.INI replacements: MCRYPT: '. $replacement_count, __FILE__, __LINE__, __METHOD__, 10 );
//
// //Enable PHP OPENSSL extension.
// $php_ini_contents = preg_replace('/^;extension=php_openssl\.dll/mi', 'extension=php_openssl.dll', $php_ini_contents, -1, $replacement_count );
// Debug::Text( ' PHP.INI replacements: OPENSSL: '. $replacement_count, __FILE__, __LINE__, __METHOD__, 10 );
//
// //Enable PHP ZIP and INTL extensions. Seems like ZIP was compiled under a different version of PHP, so don't enable it.
// //$php_ini_contents = preg_replace('/^;extension=php_zip\.dll/mi', 'extension=php_zip.dll'. PHP_EOL .'extension=php_intl.dll', $php_ini_contents, -1, $replacement_count );
// if ( preg_match( '/^extension=php_intl\.dll/mi', $php_ini_contents ) === 0 ) { //Don't replace it if it already is enabled.
// $php_ini_contents = preg_replace( '/^;extension=php_zip\.dll/mi', ';extension=php_zip.dll' . PHP_EOL . 'extension=php_intl.dll', $php_ini_contents, -1, $replacement_count );
// Debug::Text( ' PHP.INI replacements: ZIP/INTL: ' . $replacement_count, __FILE__, __LINE__, __METHOD__, 10 );
// }
//
// if ( $php_ini_contents !== NULL ) {
// Debug::Arr( $php_ini_contents, ' Writing out new PHP.INI contents: ', __FILE__, __LINE__, __METHOD__, 10 );
// file_put_contents( $php_ini_file, $php_ini_contents );
//
// system( $stack_install_dir . DIRECTORY_SEPARATOR .'restart.bat', $exit_code );
// Debug::Text( ' Restarting TimeTrex services... Exit Code: '. $exit_code, __FILE__, __LINE__, __METHOD__, 10 );
// CLIExit(0);
// } else {
// Debug::Text( ' PHP INI modification failed, or none to make!', __FILE__, __LINE__, __METHOD__, 10 );
// CLIExit(0);
// }
// }
// }
// }
}
CLIExit( 0 );
}
if ( in_array( '--requirements_only', $argv ) ) {
Debug::Text( 'Checking requirements only...', __FILE__, __LINE__, __METHOD__, 10 );
$exclude_requirements = [ 'php_cli_requirements', 'base_url', 'clean_cache' ];
if ( in_array( '--web_installer', $argv ) ) {
Debug::Text( ' Launched from web installer...', __FILE__, __LINE__, __METHOD__, 10 );
//When run from the web_installer most requirements are already checked, so exclude the slow ones.
$exclude_requirements[] = 'disk_space';
$exclude_requirements[] = 'file_checksums';
$exclude_requirements[] = 'file_permissions';
$exclude_requirements[] = 'clean_cache';
}
$install_obj->cleanCacheDirectory();
if ( $install_obj->checkAllRequirements( false, $exclude_requirements ) == 0 ) {
echo 'Requirements all pass successfully!' . "\n";
CLIExit( 0 );
//CLIExit(1); //Test failed system requirement check...
} else {
echo 'Failed Requirements: ' . implode( ',', $install_obj->getFailedRequirements( false, $exclude_requirements ) ) . "\n";
CLIExit( 1 );
}
unset( $exclude_requirements );
}
if ( $force == true ) {
echo "Force Mode enabled...\n";
//Force installer_enabled to TRUE so we don't have to manually modify the config file with scripts.
$config_vars['other']['installer_enabled'] = true;
}
$install_obj = new Install(); //Re-initialize install object with new config options set above. (force)
if ( in_array( '--schema_only', $argv ) ) {
if ( $install_obj->isInstallMode() == false ) {
echo "ERROR: Install mode is not enabled in the timetrex.ini.php file!\n";
CLIExit( 1 );
} else {
$install_obj->cleanCacheDirectory();
if ( $install_obj->checkAllRequirements( true ) == 0 ) {
$install_obj->setDatabaseConnection( $db ); //Default connection
//Make sure at least one company exists in the database, this only works for upgrades, not initial installs.
if ( $install_obj->checkDatabaseExists( $config_vars['database']['database_name'] ) == true ) {
if ( $install_obj->checkTableExists( 'company' ) == true ) {
//Check to see if the DB schema is before the UUID upgrade (schema 1070 or older) and set the $PRIMARY_KEY_IS_UUID accordingly.
// THIS IS in tools/unattended_install.php, tools/unattended_upgrade.php, includes/database.inc.php as well.
// Must go after we check that the database and company table exist, otherwise it will just cause a SQL exception.
if ( (int)SystemSettingFactory::getSystemSettingValueByKey( 'schema_version_group_A' ) < 1100 ) {
Debug::Text( 'Setting PRIMARY_KEY_IS_UUID to FALSE due to pre-UUID schema version: ' . SystemSettingFactory::getSystemSettingValueByKey( 'schema_version_group_A' ), __FILE__, __LINE__, __METHOD__, 1 );
$PRIMARY_KEY_IS_UUID = false;
}
//Table could be created, but check to make sure a company actually exists too.
$clf = TTnew( 'CompanyListFactory' );
$clf->getAll();
if ( $clf->getRecordCount() >= 1 ) {
$install_obj->setIsUpgrade( true );
} else {
//No company exists, send them to the create company page.
$install_obj->setIsUpgrade( false );
}
} else {
$install_obj->setIsUpgrade( false );
}
}
if ( $install_obj->getIsUpgrade() == true ) {
if ( $install_obj->checkDatabaseExists( $config_vars['database']['database_name'] ) == true ) {
//Create SQL, always try to install every schema version, as
//installSchema() will check if its already been installed or not.
$install_obj->setDatabaseDriver( $config_vars['database']['type'] );
$retval = $install_obj->createSchemaRange( null, null ); //All schema versions
if ( $retval == true ) {
$install_obj->setVersions();
//Clear all cache. -- This is already done in createSchemaRange() too. But setVersions() could create additional cache files we want to clear.
$install_obj->cleanCacheDirectory();
$cache->clean();
echo "Upgrade successfull!\n";
CLIExit( 0 );
} else {
Debug::Text( 'ERROR: Unable to upgrade database schema.', __FILE__, __LINE__, __METHOD__, 10 );
echo "ERROR: Unable to upgrade database schema!\n";
}
} else {
Debug::Text( 'ERROR: Database does not exist.', __FILE__, __LINE__, __METHOD__, 10 );
echo "ERROR: Database does not exists!\n";
}
} else {
echo "ERROR: No company exists for upgrading!\n";
}
} else {
echo "ERROR: System requirements are not satisfied, or a new version exists!\n";
}
}
CLIExit( 1 );
}
//Upgrade Stage2
if ( in_array( '--stage2', $argv ) ) {
/*
Steps to do full upgrade:
- Check if new version is available, send FORCE flag to help update some clients sooner if required.
- If new version exists:
- Enable logging (in memory), don't modify config file.
- Check existing system requirements/checksums to make sure no files have been changed and system requirements are still met.
This should also check permissions to make sure the files are all writable by the user who is running the script.
- Download new version .ZIP file, extract to 'upgrade_staging' directory.
- Run system requirement check for new version in staging directory, to make sure we can upgrade to that version.
- (?)Force a database backup if possible.
- Copy main directory to 'upgrade_rollback' directory.
- Move staging directory over top of main directory
- Run schema upgrade.
- Done.
*/
Debug::Text( 'AutoUpgrade Stage2... Version: ' . APPLICATION_VERSION, __FILE__, __LINE__, __METHOD__, 10 );
if ( $force == false && ( PRODUCTION == false || DEPLOYMENT_ON_DEMAND == true ) ) { //Allow FORCE=TRUE to override this.
echo "ERROR: Not doing full upgrade when PRODUCTION mode is disabled, or in ONDEMAND mode... Use FORCE argument to override.\n";
CLIExit( 1 );
}
$config_vars['other']['installer_enabled'] = true;
echo "Performing any necessary corrections from previous version...\n";
//From v7.3.1 to 7.3.2 some files weren't getting copied if they were new in this version and created a new directory.
//So do the copy again in stage2 just in case.
moveUpgradeFiles( $upgrade_staging_latest_dir );
echo "Upgrading database schema...\n";
//Don't check file_checksums, as the script is run from the old version and therefore the checksum version match will fail everytime.
//They should have been checked above anyways, so in theory this shouldn't matter.
@file_get_contents( 'http://www.timetrex.com/' . URLBuilder::getURL( [ 'v' => $install_obj->getFullApplicationVersion(), 'page' => 'unattended_upgrade_stage2_requirements' ], 'pre_install.php' ), 'r' );
if ( $install_obj->checkAllRequirements( false, [ 'file_checksums', 'php_cli_requirements', 'base_url', 'clean_cache' ] ) == 0 ) {
$install_obj->setDatabaseConnection( $db ); //Default connection
//Make sure at least one company exists in the database, this only works for upgrades, not initial installs.
if ( $install_obj->checkDatabaseExists( $config_vars['database']['database_name'] ) == true ) {
if ( $install_obj->checkTableExists( 'company' ) == true ) {
//Table could be created, but check to make sure a company actually exists too.
$clf = TTnew( 'CompanyListFactory' );
$clf->getAll();
if ( $clf->getRecordCount() >= 1 ) {
$install_obj->setIsUpgrade( true );
} else {
//No company exists, send them to the create company page.
$install_obj->setIsUpgrade( false );
}
} else {
$install_obj->setIsUpgrade( false );
}
}
if ( $install_obj->getIsUpgrade() == true ) {
if ( $install_obj->checkDatabaseExists( $config_vars['database']['database_name'] ) == true ) {
Debug::Text( 'Upgrading schema now...', __FILE__, __LINE__, __METHOD__, 10 );
//Create SQL, always try to install every schema version, as
//installSchema() will check if its already been installed or not.
$install_obj->setDatabaseDriver( $config_vars['database']['type'] );
$install_obj->createSchemaRange( null, null ); //All schema versions
$install_obj->setVersions();
Debug::Text( 'Upgrading database schema successful!', __FILE__, __LINE__, __METHOD__, 10 );
echo "Upgrading database schema successful!\n";
@file_get_contents( 'http://www.timetrex.com/' . URLBuilder::getURL( [ 'v' => $install_obj->getFullApplicationVersion(), 'page' => 'unattended_upgrade_new_schema' ], 'pre_install.php' ), 'r' );
Debug::Text( 'Cleaning up temporary files...', __FILE__, __LINE__, __METHOD__, 10 );
echo "Cleaning up temporary files...\n";
//Send version data before and after upgrade.
$ttsc = new TimeTrexSoapClient();
$ttsc->sendCompanyData( $company_id, true );
$ttsc->sendCompanyVersionData( $company_id );
//Attempt to update license file if necessary.
$license = new TTLicense();
$license->getLicenseFile( false );
//Clear all cache.
$install_obj->cleanCacheDirectory();
$cache->clean();
Misc::cleanDir( $upgrade_staging_dir, true, true, true );
@unlink( $upgrade_file_name );
Debug::Text( 'Stage 2 Successfull!', __FILE__, __LINE__, __METHOD__, 10 );
echo "Stage 2 Successfull!\n";
@file_get_contents( 'http://www.timetrex.com/' . URLBuilder::getURL( [ 'v' => $install_obj->getFullApplicationVersion(), 'page' => 'unattended_upgrade_done' ], 'pre_install.php' ), 'r' );
//Make sure we disable the installer even if an error has occurred.
//Since v7.3.0 had a bug where the installer never disabled, force it disabled here for at least one version just in case.
//Even though we have switched to using the variable only, and this isn't needed anymore.
$data['other']['installer_enabled'] = 'FALSE';
$data['other']['default_interface'] = 'html5';
$install_obj->writeConfigFile( $data );
CLIExit( 0 );
} else {
Debug::Text( 'ERROR: Database does not exist.', __FILE__, __LINE__, __METHOD__, 10 );
echo "ERROR: Database does not exists!\n";
}
} else {
Debug::Text( 'ERROR: No company exists for upgrading', __FILE__, __LINE__, __METHOD__, 10 );
echo "ERROR: No company exists for upgrading!\n";
}
} else {
Debug::Text( 'ERROR: New system requirements are not satisfied!', __FILE__, __LINE__, __METHOD__, 10 );
echo "ERROR: New system requirements are not satisfied!\n";
}
CLIExit( 1 );
}
//Stage 1, Full upgrade, including downloading the file.
if ( in_array( '--schema_only', $argv ) == false && in_array( '--stage2', $argv ) == false ) {
if ( $force == false && ( PRODUCTION == false || DEPLOYMENT_ON_DEMAND == true ) ) { //Allow FORCE=TRUE to override this.
echo "ERROR: Not doing full upgrade when PRODUCTION mode is disabled, or in ONDEMAND mode... Use FORCE argument to override.\n";
CLIExit( 1 );
}
Debug::Text( 'New version available, check current system requirements...', __FILE__, __LINE__, __METHOD__, 10 );
if ( disk_free_space( Environment::getBasePath() ) < ( 1000 * 1024000 ) ) { //1000MB
Debug::Text( 'Disk space available: ' . disk_free_space( Environment::getBasePath() ), __FILE__, __LINE__, __METHOD__, 10 );
echo "Less than 1000MB of disk space available, unable to perform upgrade...\n";
CLIExit( 1 );
}
//No need to write install file, as it just adds potential for problems if it doesn't get disabled again.
$config_vars['other']['installer_enabled'] = true;
Debug::Text( 'Checking if new version is available, current version: ' . APPLICATION_VERSION . ' Force: ' . (int)$full_force, __FILE__, __LINE__, __METHOD__, 10 );
$ttsc = new TimeTrexSoapClient();
if ( $full_force === true || $ttsc->isNewVersionReadyForUpgrade( $force ) === true ) {
Debug::Text( 'New version available, or force used...', __FILE__, __LINE__, __METHOD__, 10 );
@file_get_contents( 'http://www.timetrex.com/pre_install.php?v=' . $install_obj->getFullApplicationVersion() . '&os=' . PHP_OS . '&php_version=' . PHP_VERSION . '&web_server=' . urlencode( substr( $_SERVER['SERVER_SOFTWARE'], 0, 20 ) ) . '&page=unattended_upgrade', 'r' );
$install_obj->cleanCacheDirectory();
if ( $install_obj->checkAllRequirements( false, [ 'file_checksums', 'php_cli_requirements', 'base_url', 'clean_cache' ] ) == 0 ) {
Debug::Text( 'New version available, collecting data to download...', __FILE__, __LINE__, __METHOD__, 10 );
echo "New version available, collecting data to download...\n";
//Send version data before and after upgrade.
$ttsc->sendCompanyData( $company_id, true );
$ttsc->sendCompanyUserLocationData( $company_id );
$ttsc->sendCompanyUserCountData( $company_id );
$ttsc->sendCompanyVersionData( $company_id );
for ( $i = 0; $i < 3; $i++ ) {
$file_url = $ttsc->getUpgradeFileURL( $force );
Debug::Arr( $file_url, 'File Upgrade URL: ', __FILE__, __LINE__, __METHOD__, 10 );
if ( !is_soap_fault( $file_url ) && $file_url === false ) {
//Skip retries in case the .ZIP file is already downloaded for testing.
Debug::Text( 'Upgrade URL not available from server, either already running latest version or not ready to upgrade yet, skip retries: ' . $i, __FILE__, __LINE__, __METHOD__, 10 );
break;
}
if ( !is_soap_fault( $file_url ) && $file_url !== false && $file_url != '' ) {
$file_url_size = Misc::getRemoteHTTPFileSize( $file_url );
if ( $file_url_size > 0 ) {
Debug::Text( 'Got File Upgrade URL and size, breaking retry loop...' . $i, __FILE__, __LINE__, __METHOD__, 10 );
break;
} else {
Debug::Text( 'Unable to get remote File Upgrade URL size, retrying: ' . $i, __FILE__, __LINE__, __METHOD__, 10 );
}
}
echo " Unable to obtain File Upgrade URL, retrying in 2 minutes: " . $i . "\n";
Debug::Text( 'Unable to obtain File Upgrade URL, retrying: ' . $i, __FILE__, __LINE__, __METHOD__, 10 );
sleep( 120 );
}
if ( file_exists( $upgrade_file_name ) || ( !is_soap_fault( $file_url ) && $file_url !== false && $file_url != '' ) ) {
@file_get_contents( 'http://www.timetrex.com/' . URLBuilder::getURL( [ 'v' => $install_obj->getFullApplicationVersion(), 'page' => 'unattended_upgrade_download' ], 'pre_install.php' ), 'r' );
if ( isset( $manual_upgrade_file_name ) && $manual_upgrade_file_name != '' && file_exists( $manual_upgrade_file_name ) && filesize( $manual_upgrade_file_name ) > 0 ) {
Debug::Text( 'Using manual upgrade file: ' . $manual_upgrade_file_name . ' Current Size: ' . filesize( $manual_upgrade_file_name ), __FILE__, __LINE__, __METHOD__, 10 );
echo 'Using Manual Upgrade File: ' . $manual_upgrade_file_name . "...\n";
$upgrade_file_name = $manual_upgrade_file_name;
} else {
if ( file_exists( $upgrade_file_name ) == false || ( isset( $file_url_size ) && filesize( $upgrade_file_name ) != $file_url_size ) ) {
Debug::Text( 'Attempting to download latest version...', __FILE__, __LINE__, __METHOD__, 10 );
echo "Attempting to download latest version...\n";
sleep( 5 ); //Sleep for 5 seconds so it can be cancelled easy if needed.
//$bytes_downloaded = @file_put_contents( $upgrade_file_name, fopen( $file_url, 'r') );
$bytes_downloaded = Misc::downloadHTTPFile( $file_url, $upgrade_file_name );
Debug::Text( 'Downloaded file: ' . $upgrade_file_name . ' Size: ' . @filesize( $upgrade_file_name ) . ' Bytes downloaded: ' . $bytes_downloaded . ' Remote Size: ' . $file_url_size, __FILE__, __LINE__, __METHOD__, 10 );
if ( $bytes_downloaded != $file_url_size || @filesize( $upgrade_file_name ) <= 0 ) {
Debug::Text( 'ERROR: File did not download correctly...', __FILE__, __LINE__, __METHOD__, 10 );
echo 'ERROR: File did not download correctly...' . "\n";
setAutoUpgradeFailed();
CLIExit( 1 );
} else {
echo 'Downloaded file: ' . $upgrade_file_name . ' Size: ' . filesize( $upgrade_file_name ) . "\n";
}
} else {
Debug::Text( 'Upgrade file already exists... Current Size: ' . filesize( $upgrade_file_name ) . ' Remote Size: ' . ( isset( $file_url_size ) ? $file_url_size : 'N/A' ), __FILE__, __LINE__, __METHOD__, 10 );
echo "Upgrade file already exists...\n";
}
}
if ( file_exists( $upgrade_file_name ) && filesize( $upgrade_file_name ) > 0 ) {
Debug::Text( 'Cleaning staging directory: ' . $upgrade_staging_dir, __FILE__, __LINE__, __METHOD__, 10 );
echo 'Cleaning staging directory: ' . $upgrade_staging_dir . "\n";
Misc::cleanDir( $upgrade_staging_dir, true, true, true );
sleep( 15 ); //Apparently unlink() is async on windows, so wait some random time to hopefully let the operations complete.
Debug::Text( 'Unzipping UPGRADE.ZIP', __FILE__, __LINE__, __METHOD__, 10 );
echo "Unzipping UPGRADE.ZIP\n";
$zip = new ZipArchive;
$zip_result = $zip->open( $upgrade_file_name );
if ( $zip_result === true ) {
@file_get_contents( 'http://www.timetrex.com/' . URLBuilder::getURL( [ 'v' => $install_obj->getFullApplicationVersion(), 'page' => 'unattended_upgrade_unzip' ], 'pre_install.php' ), 'r' );
//Hide errors from this, like failed streams, or file already exists warnings and such. Don't think there is anything we can do about them anyways.
//ie: PHP ERROR - WARNING(2): ZipArchive::extractTo(): File exists
// PHP ERROR - WARNING(2): ZipArchive::extractTo(): Unable to open stream
@$zip->extractTo( $upgrade_staging_dir );
$zip->close();
sleep( 15 ); //Maybe this will help prevent access denied (code: 5) errors on windows?
clearstatcache();
Debug::Text( 'Unzipping UPGRADE.ZIP done...', __FILE__, __LINE__, __METHOD__, 10 );
echo "Unzipping UPGRADE.ZIP done...\n";
} else {
Debug::Text( 'ERROR: Unzipping UPGRADE.ZIP failed... Result: '. $zip_result, __FILE__, __LINE__, __METHOD__, 10 );
echo "ERROR: Unzipping UPGRADE.ZIP failed...\n";
}
unset( $zip_result, $zip );
//Rename whatever directory that is in the staging dir to
if ( file_exists( $upgrade_staging_dir ) ) {
if ( $handle = opendir( $upgrade_staging_dir ) ) {
while ( ( $entry = readdir( $handle ) ) !== false ) {
if ( $entry != '.' && $entry != '..' && $entry != 'latest_version' ) { //In case the rename occurred and for some reason those files can't be cleared/deleted, ignore it.
$upgrade_staging_extract_dir = $upgrade_staging_dir . DIRECTORY_SEPARATOR . $entry;
break;
}
}
closedir( $handle );
}
if ( isset( $upgrade_staging_extract_dir ) ) {
@file_get_contents( 'http://www.timetrex.com/' . URLBuilder::getURL( [ 'v' => $install_obj->getFullApplicationVersion(), 'page' => 'unattended_upgrade_rename_dir' ], 'pre_install.php' ), 'r' );
//Make sure the latest directory does not exist before renaming the unzipped directory to it. This may help with some Access Denied errors on Windows.
Misc::cleanDir( $upgrade_staging_latest_dir, true, true, true );
Debug::Text( 'Upgrade Staging Extract Dir: ' . $upgrade_staging_extract_dir . ' Renaming to: ' . $upgrade_staging_latest_dir, __FILE__, __LINE__, __METHOD__, 10 );
if ( Misc::rename( $upgrade_staging_extract_dir, $upgrade_staging_latest_dir ) == false ) {
Debug::Text( 'ERROR: Unable to rename: ' . $upgrade_staging_extract_dir . ' to: ' . $upgrade_staging_latest_dir, __FILE__, __LINE__, __METHOD__, 10 );
echo 'ERROR: Unable to rename: ' . $upgrade_staging_extract_dir . ' to: ' . $upgrade_staging_latest_dir . "\n";
}
clearstatcache();
} else {
Debug::Text( 'ERROR: UPGRADE.ZIP extract directory does not exist...', __FILE__, __LINE__, __METHOD__, 10 );
}
unset( $handle, $entry, $upgrade_staging_extract_dir );
} else {
Debug::Text( 'ERROR: Upgrade staging directory does not exist, cannot continue...', __FILE__, __LINE__, __METHOD__, 10 );
echo "ERROR: Upgrade staging directory does not exist, cannot continue...\n";
}
if ( isset( $upgrade_staging_latest_dir ) ) {
//Check system requirements of new version.
$latest_unattended_upgrade_tool = $upgrade_staging_latest_dir . DIRECTORY_SEPARATOR . 'tools' . DIRECTORY_SEPARATOR . 'unattended_upgrade.php';
if ( file_exists( $latest_unattended_upgrade_tool ) ) {
if ( is_executable( $php_cli ) ) {
$command = '"' . $php_cli . '" -d opcache.enable_cli=0 "' . $latest_unattended_upgrade_tool . '" --config "' . CONFIG_FILE . '" --pre_requirements_update'; //Make each part is quoted in case there are spaces in the paths.
system( $command, $exit_code );
Debug::Text( 'Running pre-requirements update... Command: ' . $command . ' Exit Code: ' . $exit_code, __FILE__, __LINE__, __METHOD__, 10 );
if ( $exit_code == 0 ) {
Debug::Text( 'New version pre-requirements met...', __FILE__, __LINE__, __METHOD__, 10 );
@file_get_contents( 'http://www.timetrex.com/' . URLBuilder::getURL( [ 'v' => $install_obj->getFullApplicationVersion(), 'page' => 'unattended_upgrade_pre_requirements' ], 'pre_install.php' ), 'r' );
$command = '"' . $php_cli . '" -d opcache.enable_cli=0 "' . $latest_unattended_upgrade_tool . '" --config "' . CONFIG_FILE . '" --requirements_only'; //Make each part is quoted in case there are spaces in the paths.
system( $command, $exit_code );
Debug::Text( 'Checking new version system requirements... Command: ' . $command . ' Exit Code: ' . $exit_code, __FILE__, __LINE__, __METHOD__, 10 );
if ( $exit_code == 0 ) {
Debug::Text( 'New version system requirements met...', __FILE__, __LINE__, __METHOD__, 10 );
@file_get_contents( 'http://www.timetrex.com/' . URLBuilder::getURL( [ 'v' => $install_obj->getFullApplicationVersion(), 'page' => 'unattended_upgrade_new_requirements' ], 'pre_install.php' ), 'r' );
moveUpgradeFiles( $upgrade_staging_latest_dir );
@file_get_contents( 'http://www.timetrex.com/' . URLBuilder::getURL( [ 'v' => $install_obj->getFullApplicationVersion(), 'page' => 'unattended_upgrade_launch_stage2' ], 'pre_install.php' ), 'r' );
$global_class_map['TTUUID'] = 'core/TTUUID.class.php'; //Need to manually map the TTUUID class as it may be required by autoloaded classes in this process.
//Clear OPCACHE to help try to avoid calling ourself with opcached files from the old version.
if ( function_exists( 'opcache_reset' ) ) {
opcache_reset();
}
//Run separate process to finish stage2 of installer so it can be run with the new scripts.
//This allows us more flexibility if an error occurs to finish the install or have the latest version correct problems.
echo "Launching Stage 2...\n";
sleep( 5 );
$command = '"' . $php_cli . '" -d opcache.enable_cli=0 "' . __FILE__ . '" --config "' . CONFIG_FILE . '" --stage2'; //Disable opcache on CLI
//Pass along force argument if it was originally supplied.
if ( $full_force == true ) {
$command .= ' -ff';
} else if ( $force == true ) {
$command .= ' -f';
}
Debug::Text( 'Stage2 Command: ' . $command, __FILE__, __LINE__, __METHOD__, 10 );
system( $command, $exit_code );
if ( $exit_code == 0 ) {
Debug::Text( 'Stage2 success!', __FILE__, __LINE__, __METHOD__, 10 );
echo "Upgrade successfull!\n";
//Since the --stage2 SQL upgrade is performed in a different process, we have to manually turn UUIDs on for this process before calling any other SQL query like setAutoUpgradeFailed( 0 )
global $PRIMARY_KEY_IS_UUID;
$PRIMARY_KEY_IS_UUID = true;
setAutoUpgradeFailed( 0 ); //Clear auto_upgrade_failed setting if it isn't already.
Install::sendNotification();
CLIExit( 0 );
} else {
Debug::Text( 'Stage2 failed... Exit Code: ' . $exit_code, __FILE__, __LINE__, __METHOD__, 10 );
setAutoUpgradeFailed();
}
} else {
Debug::Text( 'ERROR: New version system requirements not met...', __FILE__, __LINE__, __METHOD__, 10 );
echo "ERROR: New version system requirements not met...\n";
setAutoUpgradeFailed();
}
} else {
Debug::Text( 'ERROR: Pre-Requirements Update failed...', __FILE__, __LINE__, __METHOD__, 10 );
echo "ERROR: Pre-Requirements Update failed...\n";
setAutoUpgradeFailed();
}
} else {
Debug::text( 'ERROR: PHP CLI is not executable: ' . $php_cli, __FILE__, __LINE__, __METHOD__, 10 );
echo "ERROR: PHP CLI is not executable: " . $php_cli . "\n";
setAutoUpgradeFailed();
}
} else {
Debug::Text( 'ERROR: UNATTENDED UPGRADE tool in new version does not exist: ' . $latest_unattended_upgrade_tool, __FILE__, __LINE__, __METHOD__, 10 );
echo "ERROR: UNATTENDED UPGRADE tool in new version does not exist: " . $latest_unattended_upgrade_tool . "\n";
setAutoUpgradeFailed();
}
} else {
Debug::Text( 'ERROR: Upgrade staging latest directory does not exist, cannot continue...', __FILE__, __LINE__, __METHOD__, 10 );
echo "ERROR: Upgrade staging latest directory does not exist, cannot continue...\n";
setAutoUpgradeFailed();
}
} else {
Debug::Text( 'ERROR: UPGRADE.ZIP does not exist or is 0 bytes...', __FILE__, __LINE__, __METHOD__, 10 );
echo "ERROR: UPGRADE.ZIP does not exist or is 0 bytes...\n";
setAutoUpgradeFailed();
}
} else {
Debug::Text( 'Upgrade File URL not available...', __FILE__, __LINE__, __METHOD__, 10 );
echo "ERROR: Unable to download upgrade file at this time, please try again later...\n";
setAutoUpgradeFailed();
}
} else {
$failed_requirements = $install_obj->getFailedRequirements( false, [ 'file_checksums', 'php_cli_requirements', 'base_url', 'clean_cache' ] );
Debug::Text( 'ERROR: Current system requirements check failed...', __FILE__, __LINE__, __METHOD__, 10 );
Debug::Text( ' Failed Requirements: ' . implode( ',', $failed_requirements ), __FILE__, __LINE__, __METHOD__, 10 );
echo "ERROR: Current system requirements check failed...\n";
echo ' Failed Requirements: ' . implode( ',', $failed_requirements ) . "\n";
unset( $failed_requirements );
setAutoUpgradeFailed();
}
} else {
Debug::Text( 'Already running latest version: ' . APPLICATION_VERSION, __FILE__, __LINE__, __METHOD__, 10 );
echo "Already running latest version: " . APPLICATION_VERSION . "\n";
setAutoUpgradeFailed( 0 ); //Clear auto_upgrade_failed setting if it isn't already.
}
CLIExit( 1 );
}
}
CLIExit( 1 );
?>