409 lines
16 KiB
PHP
409 lines
16 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".
|
||
|
*
|
||
|
********************************************************************************/
|
||
|
|
||
|
|
||
|
/**
|
||
|
* @package Other\SystemDiagnostic
|
||
|
*/
|
||
|
class SystemDiagnostic {
|
||
|
|
||
|
private $cli_mode = false;
|
||
|
private $progress_bar_obj = null;
|
||
|
private $api_message_id = null;
|
||
|
private $curl_last_progress = null; //Progress of last time CURL reported.
|
||
|
private $curl_progress_start = null; //Time CURL transfer started.
|
||
|
|
||
|
/**
|
||
|
* @param $toggle
|
||
|
* @return bool
|
||
|
*/
|
||
|
static function setSystemDiagnostic( $toggle ) {
|
||
|
$install_obj = new Install();
|
||
|
$install_obj->writeConfigFile( [ 'debug' => [ 'enable' => $toggle, 'enable_log' => $toggle, 'verbosity' => 10 ] ] );
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @param $c_obj
|
||
|
* @param $cli_mode
|
||
|
* @return bool
|
||
|
*/
|
||
|
function uploadSystemDiagnostic( $c_obj, $cli_mode ) {
|
||
|
$install_obj = new Install();
|
||
|
if ( $install_obj->checkDiskSpace() !== 0 ) {
|
||
|
Debug::Text( 'ERROR: Not enough disk space.', __FILE__, __LINE__, __METHOD__, 10 );
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
$this->cli_mode = $cli_mode;
|
||
|
|
||
|
//Set script execution time to 24 hours. Users may have slow upload speed and large log files.
|
||
|
set_time_limit( 86400 );
|
||
|
|
||
|
global $config_vars;
|
||
|
if ( !isset( $config_vars['cache']['dir'] ) ) { //Just in case the cache directory is not set.
|
||
|
$config_vars['cache']['dir'] = Environment::getBasePath();
|
||
|
}
|
||
|
$temp_dir = $config_vars['cache']['dir'] . DIRECTORY_SEPARATOR . 'system_diagnostics' . DIRECTORY_SEPARATOR;
|
||
|
//Check if tempdir is writable.
|
||
|
if ( $install_obj->checkWritableCacheDirectory() !== 0 ) {
|
||
|
Debug::Text( 'ERROR: Cache directory is not writable.', __FILE__, __LINE__, __METHOD__, 10 );
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
//Create lock file so that this function cannot be ran more than once simultaneously.
|
||
|
$lock_file = new LockFile( $config_vars['cache']['dir'] . DIRECTORY_SEPARATOR . 'system_diagnostic.lock' );
|
||
|
|
||
|
if ( $lock_file->exists() == false ) {
|
||
|
if ( $lock_file->create() == true ) {
|
||
|
Debug::text( 'System diagnostic lock file created. Starting process of gathering system logs.', __FILE__, __LINE__, __METHOD__, 10 );
|
||
|
|
||
|
$this->getProgressBarObject()->start( $this->getAPIMessageID(), 104, null, TTi18n::getText( 'Starting Upload Process...' ) ); //104 = 4 steps plus 100 for upload progress.
|
||
|
|
||
|
$this->cleanUpTempDir( $temp_dir, false );
|
||
|
|
||
|
$registration_key = SystemSettingFactory::getSystemSettingValueByKey( 'registration_key' );
|
||
|
$zip_name = Misc::sanitizeFileName( $c_obj->getName() . '-' . date( 'Ymd-Hmi' ) .'-'. ( ( $registration_key != '' ) ? $registration_key : '0000000000000000000000000000000000000000' ) );
|
||
|
$zip_file = $temp_dir . $zip_name . '.zip';
|
||
|
|
||
|
if ( OPERATING_SYSTEM === 'WIN' ) {
|
||
|
$apache_log_name = Environment::getBasePath() . '..' . DIRECTORY_SEPARATOR . 'apache2'. DIRECTORY_SEPARATOR . 'log' . DIRECTORY_SEPARATOR .'error.log';
|
||
|
} else {
|
||
|
$apache_log_name = '/var/log/apache2/error.log';
|
||
|
}
|
||
|
|
||
|
if ( OPERATING_SYSTEM === 'WIN' ) {
|
||
|
$installer_log_name = Environment::getBasePath() . '..' . DIRECTORY_SEPARATOR .'install.log';
|
||
|
} else {
|
||
|
$installer_log_name = null;
|
||
|
}
|
||
|
|
||
|
if ( OPERATING_SYSTEM === 'WIN' ) {
|
||
|
$sql_log_name = Environment::getBasePath() . '..' . DIRECTORY_SEPARATOR .'upgrade_sql_error_timetrex.log';
|
||
|
} else {
|
||
|
$sql_log_name = null;
|
||
|
}
|
||
|
|
||
|
$timetrex_log_name = $config_vars['path']['log'] . DIRECTORY_SEPARATOR . 'timetrex.log';
|
||
|
|
||
|
//Using file_exists instead of is_dir as file_exists checks for both files and directories incase of name collision.
|
||
|
if ( !file_exists( $temp_dir ) ) {
|
||
|
mkdir( $temp_dir );
|
||
|
}
|
||
|
|
||
|
if ( $this->cli_mode === true ) {
|
||
|
echo "Collecting Log Files...\n";
|
||
|
} else {
|
||
|
$this->getProgressBarObject()->set( $this->getAPIMessageID(), 1, TTi18n::getText( 'Collecting Log Files...' ) );
|
||
|
}
|
||
|
|
||
|
$zip = new ZipArchive;
|
||
|
if ( $zip->open( $zip_file, ZipArchive::CREATE ) === true ) {
|
||
|
//Create phpInfo() log.
|
||
|
ob_start();
|
||
|
phpinfo();
|
||
|
$php_info = ob_get_contents();
|
||
|
ob_end_clean();
|
||
|
//Remove HTML formatting.
|
||
|
$php_info = strip_tags( $php_info );
|
||
|
//Remove CSS styling from top of output.
|
||
|
$php_info = strstr( $php_info, 'PHP Version' );
|
||
|
$zip->addFromString( 'php_info.log', $php_info );
|
||
|
|
||
|
global $db;
|
||
|
|
||
|
//Create system_info.log
|
||
|
$system_info = 'Operating System: '. php_uname() . PHP_EOL;
|
||
|
$system_info .= 'PostgreSQL: ' . Debug::varDump( $db->ServerInfo( true ) ) . PHP_EOL;
|
||
|
$system_info .= 'Total Disk Space: ' . round( disk_total_space( dirname( __FILE__ ) ) / 1024 / 1024 / 1024 ) . 'gb Free Space: ' . round( disk_free_space( dirname( __FILE__ ) ) / 1024 / 1024 / 1024 ) . 'gb' . PHP_EOL;
|
||
|
$zip->addFromString( 'system_info.log', $system_info );
|
||
|
|
||
|
//Zip apache log files.
|
||
|
if ( file_exists( $apache_log_name ) ) {
|
||
|
$zip->addFile( $apache_log_name, basename( $apache_log_name ) );
|
||
|
} else {
|
||
|
Debug::Text( 'Unable to locate Apache log files. May be caused by permission issues, or it doesn\'t exist: ' . $apache_log_name, __FILE__, __LINE__, __METHOD__, 10 );
|
||
|
}
|
||
|
|
||
|
if ( file_exists( $installer_log_name ) ) {
|
||
|
$zip->addFile( $installer_log_name, basename( $installer_log_name ) );
|
||
|
} else {
|
||
|
Debug::Text( 'Unable to locate Windows Installer log files. May be caused by permission issues, or it doesn\'t exist: ' . $installer_log_name, __FILE__, __LINE__, __METHOD__, 10 );
|
||
|
}
|
||
|
|
||
|
if ( file_exists( $sql_log_name ) ) {
|
||
|
$zip->addFile( $sql_log_name, basename( $sql_log_name ) );
|
||
|
} else {
|
||
|
Debug::Text( 'Unable to locate SQL error log files. May be caused by permission issues, or it doesn\'t exist: ' . $sql_log_name, __FILE__, __LINE__, __METHOD__, 10 );
|
||
|
}
|
||
|
|
||
|
//Include all TimeTrex files.
|
||
|
$root_path = realpath( Environment::getBasePath() );
|
||
|
$files = new RecursiveIteratorIterator( new RecursiveDirectoryIterator( $root_path ), RecursiveIteratorIterator::LEAVES_ONLY );
|
||
|
foreach ( $files as $name => $file ) {
|
||
|
// Skip directories (they would be added automatically)
|
||
|
if ( !$file->isDir() ) {
|
||
|
// Add current file to archive under the 'timetrex' directory.
|
||
|
$zip->addFile( $file->getRealPath(), str_replace( '\\', '/', 'timetrex'. DIRECTORY_SEPARATOR . substr( $file->getRealPath(), strlen( $root_path ) + 1 ) ) ); //Replace all backslashes from Windows with forward slashes as recommended in: https://www.php.net/manual/en/ziparchive.addfile.php
|
||
|
}
|
||
|
}
|
||
|
unset( $files, $file_path, $relative_path, $root_path );
|
||
|
|
||
|
//Zip TimeTrex log files -- Do this last so we can get as many log lines into it as possible.
|
||
|
if ( file_exists( $timetrex_log_name ) ) {
|
||
|
Debug::Text( ' Writing debug buffer to log prior to uploading...', __FILE__, __LINE__, __METHOD__, 10 );
|
||
|
Debug::writeToLog(); //Write all previous debug lines to log before uploading it. **Keep this here, even though its in TTShutdown() as well**
|
||
|
|
||
|
$zip->addFile( $timetrex_log_name, basename( $timetrex_log_name ) );
|
||
|
} else {
|
||
|
Debug::Text( 'Unable to locate TimeTrex log files. May be caused by permission issues.' . $timetrex_log_name, __FILE__, __LINE__, __METHOD__, 10 );
|
||
|
}
|
||
|
|
||
|
if ( $this->cli_mode === true ) {
|
||
|
echo "Compressing Logs...\n";
|
||
|
} else {
|
||
|
$this->getProgressBarObject()->set( $this->getAPIMessageID(), 2, TTi18n::getText( 'Compressing Logs...' ) );
|
||
|
}
|
||
|
|
||
|
$retval = $zip->close();
|
||
|
|
||
|
if ( $retval === true && file_exists( $zip_file ) ) {
|
||
|
|
||
|
if ( $this->cli_mode === true ) {
|
||
|
echo "Securely Uploading Logs...\n";
|
||
|
} else {
|
||
|
$this->getProgressBarObject()->set( $this->getAPIMessageID(), 3, TTi18n::getText( 'Securely Uploading Logs...' ) );
|
||
|
}
|
||
|
|
||
|
$fp = fopen( $zip_file, 'r' );
|
||
|
|
||
|
$curl_handler = curl_init();
|
||
|
curl_setopt( $curl_handler, CURLOPT_URL, 'https://nextcloud.timetrex.com/s/6NePJBkdqGeLaDM' );
|
||
|
curl_setopt( $curl_handler, CURLOPT_SSL_VERIFYPEER, false ); //False to avoid curl error - "unable to get local issuer certificate."
|
||
|
curl_setopt( $curl_handler, CURLOPT_RETURNTRANSFER, true );
|
||
|
$curl_retval = curl_exec( $curl_handler );
|
||
|
|
||
|
$doc = new DOMDocument();
|
||
|
//LIBXML_NOERROR to ignore HTML errors on the page we are loading.
|
||
|
$doc->loadHTML( $curl_retval, LIBXML_NOERROR );
|
||
|
$head = $doc->getElementsByTagName( 'head' );
|
||
|
if ( isset( $head[0] ) ) {
|
||
|
$request_token = $head[0]->getAttribute( 'data-requesttoken' );
|
||
|
$basic_authorization_token = base64_encode( $doc->getElementById( 'sharingToken' )->getAttribute( 'value' ) . ':' );
|
||
|
}
|
||
|
|
||
|
curl_setopt( $curl_handler, CURLOPT_URL, 'https://nextcloud.timetrex.com/public.php/webdav/' . $zip_name . '.zip' );
|
||
|
curl_setopt( $curl_handler, CURLOPT_HTTPHEADER, [
|
||
|
'requesttoken: ' . $request_token,
|
||
|
'authorization: Basic ' . $basic_authorization_token,
|
||
|
] );
|
||
|
curl_setopt( $curl_handler, CURLOPT_PUT, true );
|
||
|
curl_setopt( $curl_handler, CURLOPT_INFILESIZE, filesize( $zip_file ) );
|
||
|
curl_setopt( $curl_handler, CURLOPT_INFILE, $fp );
|
||
|
curl_setopt( $curl_handler, CURLOPT_NOPROGRESS, false );
|
||
|
curl_setopt( $curl_handler, CURLOPT_PROGRESSFUNCTION, [ $this, 'updateUploadProgress' ] );
|
||
|
curl_setopt( $curl_handler, CURLOPT_TIMEOUT, 0 );
|
||
|
curl_setopt( $curl_handler, CURLINFO_HEADER_OUT, true );
|
||
|
|
||
|
$this->curl_progress_start = microtime( true );
|
||
|
$curl_retval = curl_exec( $curl_handler );
|
||
|
Debug::Text( 'CURL Return Response: ' . Debug::varDump( $curl_retval ), __FILE__, __LINE__, __METHOD__, 10 );
|
||
|
|
||
|
if ( $this->cli_mode === true ) {
|
||
|
echo 'Finished Uploading Logs...'."\n";
|
||
|
} else {
|
||
|
$this->getProgressBarObject()->set( $this->getAPIMessageID(), 104, TTi18n::getText( 'Finished Uploading Logs...' ) );
|
||
|
}
|
||
|
|
||
|
if ( curl_errno( $curl_handler ) ) {
|
||
|
$error_msg = curl_error( $curl_handler );
|
||
|
Debug::Text( 'CURL ERROR: ' . $error_msg, __FILE__, __LINE__, __METHOD__, 10 );
|
||
|
} else {
|
||
|
Debug::Text( 'CURL Upload success.', __FILE__, __LINE__, __METHOD__, 10 );
|
||
|
}
|
||
|
curl_close( $curl_handler );
|
||
|
|
||
|
$this->cleanUpTempDir( $temp_dir, true );
|
||
|
}
|
||
|
} else {
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
$lock_file->delete();
|
||
|
} else {
|
||
|
Debug::text( 'Skipping... uploading system diagnostics. Lock file exists...', __FILE__, __LINE__, __METHOD__, 10 );
|
||
|
if ( $this->cli_mode === true ) {
|
||
|
echo "NOTICE: Lock file exists, diagnostics already being collected elsewhere, skipping...\n";
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
|
||
|
/**
|
||
|
* @param $temp_dir
|
||
|
* @param $remove_temp_dir
|
||
|
* @return bool
|
||
|
*/
|
||
|
function cleanUpTempDir( $temp_dir, $remove_temp_dir ) {
|
||
|
Debug::Text( 'Cleaning up Temp Dir: ' . $temp_dir, __FILE__, __LINE__, __METHOD__, 10 );
|
||
|
if ( is_dir( $temp_dir ) ) {
|
||
|
if ( $this->cli_mode === true ) {
|
||
|
echo "Cleaning Up...\n";
|
||
|
} else {
|
||
|
$this->getProgressBarObject()->set( $this->getAPIMessageID(), 3, TTi18n::getText( 'Cleaning Up' ) );
|
||
|
}
|
||
|
|
||
|
$files = new RecursiveIteratorIterator( new RecursiveDirectoryIterator( $temp_dir, RecursiveDirectoryIterator::SKIP_DOTS ), RecursiveIteratorIterator::CHILD_FIRST );
|
||
|
foreach ( $files as $fileinfo ) {
|
||
|
if ( $fileinfo->isDir() === true ) {
|
||
|
@rmdir( $fileinfo->getRealPath() );
|
||
|
} else {
|
||
|
Misc::unlink( $fileinfo->getRealPath() );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ( $remove_temp_dir === true ) {
|
||
|
//Since windows deleting files is async, its likely rare that the directory can be deleted, even after a sleeping for 10 seconds.
|
||
|
@rmdir( $temp_dir );
|
||
|
}
|
||
|
|
||
|
clearstatcache(); //Clear any stat cache when done.
|
||
|
Debug::Text( 'Done cleaning up Temp Dir: ' . $temp_dir, __FILE__, __LINE__, __METHOD__, 10 );
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Updates API or CLI with upload progress as a percent.
|
||
|
*/
|
||
|
function updateUploadProgress( $curl_handler, $download_file_size, $downloaded, $upload_file_size, $uploaded ) {
|
||
|
//Debug::text( ' Download File Size: '. $download_file_size .' Downloaded: '. $downloaded .' Upload File Size: '. $upload_file_size .' Uploaded: '. $uploaded, __FILE__, __LINE__, __METHOD__, 10 );
|
||
|
//echo ' Download File Size: '. $download_file_size .' Downloaded: '. $downloaded .' Upload File Size: '. $upload_file_size .' Uploaded: '. $uploaded ."\n";
|
||
|
if ( $upload_file_size > 0 ) { //Prevent division by 0.
|
||
|
$elapsed_upload_time = ( microtime( true ) - $this->curl_progress_start );
|
||
|
if ( $elapsed_upload_time == 0 ) {
|
||
|
$bytes_per_second = $uploaded;
|
||
|
} else {
|
||
|
$bytes_per_second = ( $uploaded / $elapsed_upload_time );
|
||
|
}
|
||
|
$kilobytes_per_second = round( $bytes_per_second / 1024, 2 );
|
||
|
|
||
|
$progress = round( ( $uploaded / $upload_file_size ) * 100 );
|
||
|
|
||
|
if ( $this->curl_last_progress === null || $progress !== $this->curl_last_progress ) { //Only update progress when it changes.
|
||
|
if ( $this->cli_mode === true ) {
|
||
|
if ( $progress % 10 == 0 ) {
|
||
|
echo 'Securely Uploading Logs... ' . $progress . '% - ' . $kilobytes_per_second . "KB/s\n";
|
||
|
}
|
||
|
} else {
|
||
|
//Add 3 to the progress because there are 3 steps before it.
|
||
|
$this->getProgressBarObject()->set( $this->getAPIMessageID(), ( floor( $progress ) + 3 ), TTi18n::getText( 'Securely Uploading Logs...' ) . ' ' . $progress . '% - '. $kilobytes_per_second .'KB/s' );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
$this->curl_last_progress = $progress;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @param object $obj
|
||
|
* @return bool
|
||
|
*/
|
||
|
function setProgressBarObject( $obj ) {
|
||
|
if ( is_object( $obj ) ) {
|
||
|
$this->progress_bar_obj = $obj;
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @return null|ProgressBar
|
||
|
*/
|
||
|
function getProgressBarObject() {
|
||
|
if ( !is_object( $this->progress_bar_obj ) ) {
|
||
|
$this->progress_bar_obj = new ProgressBar();
|
||
|
}
|
||
|
|
||
|
return $this->progress_bar_obj;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns the API messageID for each individual call.
|
||
|
* @return bool|null
|
||
|
*/
|
||
|
function getAPIMessageID() {
|
||
|
if ( $this->api_message_id != null ) {
|
||
|
return $this->api_message_id;
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @param string $id UUID
|
||
|
* @return bool
|
||
|
*/
|
||
|
function setAPIMessageID( $id ) {
|
||
|
Debug::Text( 'API Message ID: ' . $id, __FILE__, __LINE__, __METHOD__, 10 );
|
||
|
if ( $id != '' ) {
|
||
|
$this->api_message_id = $id;
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
?>
|