TimeTrex Community Edition v16.2.0

This commit is contained in:
2022-12-13 07:10:06 +01:00
commit 472f000c1b
6810 changed files with 2636142 additions and 0 deletions

2
includes/.htaccess Normal file
View File

@@ -0,0 +1,2 @@
Order allow,deny
Deny from all

319
includes/API.inc.php Normal file
View File

@@ -0,0 +1,319 @@
<?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".
*
********************************************************************************/
define( 'TIMETREX_API', true );
forceNoCacheHeaders(); //Send headers to disable caching.
/**
* Returns if the method should always be a unauthenticated API call.
* @return bool
*/
function isUnauthenticatedMethod( $method ) {
if ( in_array( strtolower( $method ), [ 'isloggedin', 'sendcsrftokencookie', 'senderrorreport', 'getprogressbar', 'ping' ] ) ) {
return true;
}
return false;
}
/**
* Returns valid classes when unauthenticated.
* @return array
*/
function getUnauthenticatedAPIClasses() {
return [ 'APIAuthentication', 'APIJobApplicantPortal', 'APIRecruitmentAuthentication', 'APIJobVacancyPortal', 'APIDocumentPortal', 'APIClientStationUnAuthenticated', 'APIAuthenticationPlugin', 'APIClientStationUnAuthenticatedPlugin', 'APICompanyPortal', 'APIProgressBar', 'APIInstall' ];
}
/**
* @return array
*/
function getAuthenticatedPortalAPIClasses() {
//Since we have isWhiteListedAPICall() now that ensures only child methods can be called from the API, we just need to whitelist the classes and don't need to bother with methods.
return [
'APICompanyPortal', 'APICurrencyPortal', 'APIDocumentPortal', 'APIEthnicGroupPortal', 'APIJobVacancyPortal', 'APIQualificationPortal', 'APIRecruitmentAuthentication'
];
}
/**
* @return array
*/
function getAuthenticatedPortalAPIMethods() {
return [
'getJobApplicant', 'getJobApplicantEducation', 'setJobApplicantEducation', 'getJobApplicantEmployment', 'setJobApplicantEmployment', 'getJobApplicantLanguage', 'setJobApplicantLanguage', 'getJobApplicantLicense', 'setJobApplicantLicense', 'getJobApplicantLocation', 'setJobApplicantLocation', 'getJobApplicantMembership', 'setJobApplicantMembership',
'getJobApplicantReference', 'setJobApplicantReference', 'getJobApplicantSkill', 'setJobApplicantSkill', 'getJobApplication', 'setJobApplication', 'getAttachment', 'addAttachment', 'uploadAttachment',
];
}
/**
* Make sure the remote API calls can only call the child methods of a class, and not the inherited parent methods, unless they are whitelisted in APIFactory.class.php
* @param object $obj Object
* @param string $method Method
* @return bool
*/
function isWhiteListedAPICall( $obj, $method ) {
if ( !is_object( $obj ) ) {
Debug::text( 'ERROR: Object not specified!', __FILE__, __LINE__, __METHOD__, 10 );
return false;
}
if ( empty( $method ) ) {
Debug::text( 'ERROR: Method is empty! Object: ' . get_class( $obj ), __FILE__, __LINE__, __METHOD__, 10 );
return false;
}
$api_factory_whitelisted_methods = [ 'getOptions', 'getOptionsBatch', 'exportRecords' ]; //From APIFactory.class.php
//Handle blacklisted methods.
// Functions that are blacklisted, or start with _*() should never be called remotely.
if ( in_array( $method, [ '__construct' ] ) === true || strpos( $method, '_' ) === 0 ) {
Debug::text( 'ERROR: Method is not part of the child class object! Method: ' . $method . ' Object: ' . get_class( $obj ), __FILE__, __LINE__, __METHOD__, 10 );
return false;
}
$obj_methods = get_class_methods( $obj );
$obj_parents = class_parents( $obj );
if ( is_array( $obj_parents ) ) {
$obj_parent_methods = [];
foreach ( $obj_parents as $obj_parent ) {
if ( $obj_parent === 'APIFactory' ) {
$obj_parent_methods = get_class_methods( $obj_parent );
}
}
$child_class_methods = array_diff( $obj_methods, $obj_parent_methods );
} else {
$child_class_methods = $obj_methods;
}
$valid_class_methods = array_merge( $child_class_methods, $api_factory_whitelisted_methods );
if ( in_array( strtolower( $method ), array_map( 'strtolower', $valid_class_methods ), true ) === true ) { //Must be case insensitive, since the remote user might call 'login' rather than 'Login'.
return true;
}
Debug::text( 'ERROR: Method is not part of the child class object! Method: ' . $method . ' Object: ' . get_class( $obj ), __FILE__, __LINE__, __METHOD__, 10 );
return false;
}
/**
* Get Cookie/Post/Get variable in that order.
* @param $var_name
* @param null $default_value
* @return mixed|null
*/
function getCookiePostGetVariable( $var_name, $default_value = null ) {
if ( isset( $_COOKIE[$var_name] ) && $_COOKIE[$var_name] != '' ) {
$retval = $_COOKIE[$var_name];
} else if ( isset( $_POST[$var_name] ) && $_POST[$var_name] != '' ) {
$retval = $_POST[$var_name];
} else if ( isset( $_GET[$var_name] ) && $_GET[$var_name] != '' ) {
$retval = $_GET[$var_name];
} else {
$retval = $default_value;
}
return $retval;
}
/**
* Returns session ID from _COOKIE, _POST, then _GET.
* @param int $authentication_type_id
* @return bool|string
*/
function getSessionID( $authentication_type_id = 800 ) {
//FIXME: Work-around for bug in Mobile app v3.0.86 that uses old SessionIDs in the Cookie, but correct ones on the URL.
if ( isset( $_COOKIE['SessionID'] ) && isset( $_GET['SessionID'] ) && $_COOKIE['SessionID'] != $_GET['SessionID'] ) {
//Debug::Arr( array($_COOKIE, $_POST, $_GET), 'Input Data:', __FILE__, __LINE__, __METHOD__, 10);
Debug::Text( 'WARNING: Two different SessionIDs sent, COOKIE: ' . $_COOKIE['SessionID'] . ' GET: ' . $_GET['SessionID'], __FILE__, __LINE__, __METHOD__, 10 );
if ( isset( $_SERVER['REQUEST_URI'] ) && ( stripos( $_SERVER['REQUEST_URI'], 'APIClientStationUnAuthenticated' ) !== false || stripos( $_SERVER['REQUEST_URI'], '/api/report/api.php' ) !== false ) ) {
Debug::Text( 'Using GET Session ID...', __FILE__, __LINE__, __METHOD__, 10 );
unset( $_COOKIE['SessionID'] );
}
}
$authentication = new Authentication();
$session_name = $authentication->getName( $authentication_type_id );
$session_id = getCookiePostGetVariable( $session_name, false );
if ( is_string( $session_id ) == false ) {
$session_id = false;
}
return $session_id;
}
/**
* Returns Station ID from _COOKIE, _POST, then _GET.
* @return bool|mixed
*/
function getStationID() {
$station_id = getCookiePostGetVariable( 'StationID', false );
//Check to see if there is a "sticky" user agent based Station ID defined.
if ( isset( $_SERVER['HTTP_USER_AGENT'] ) && $_SERVER['HTTP_USER_AGENT'] != '' && stripos( $_SERVER['HTTP_USER_AGENT'], 'StationID:' ) !== false ) {
if ( preg_match( '/StationID:\s?([a-zA-Z0-9]{30,64})/i', $_SERVER['HTTP_USER_AGENT'], $matches ) > 0 ) {
if ( isset( $matches[1] ) ) {
Debug::Text( ' Found StationID in user agent, forcing to that instead!', __FILE__, __LINE__, __METHOD__, 10 );
$station_id = $matches[1];
}
}
}
if ( is_string( $station_id ) == false ) {
$station_id = false;
}
return $station_id;
}
/**
* Handle temporarily overriding user preferences based on Cookie/Post/Get variables.
* This is useful for ensuring there is always consistent date/time formats and timezones when accessing the API for multiple users.
* @param $user_obj UserFactory
* @return bool
*/
function handleOverridePreferences( $user_obj ) {
$user_pref_obj = $user_obj->getUserPreferenceObject(); /** @var UserPreferenceFactory $user_pref_obj */
//Allow for BASE64 encoding of the JSON string, as flutter only allows RFC 6265 cookie values, which JSON is not.
$raw_override_cookie = getCookiePostGetVariable( 'OverrideUserPreference' );
if ( $raw_override_cookie != '' && strpos( $raw_override_cookie, '{' ) !== 0 ) {
$raw_override_cookie = base64_decode( $raw_override_cookie );
}
if ( $raw_override_cookie != '' ) {
$override_preferences = json_decode( $raw_override_cookie, true );
if ( is_array( $override_preferences ) && count( $override_preferences ) > 0 ) {
//If a user_id is specified, pull the timezone for that user and default to it, rather than the UI having to do a lookup itself and passing it through.
if ( isset( $override_preferences['user_id'] ) && TTUUID::isUUID( $override_preferences['user_id'] ) && $user_obj->getId() != $override_preferences['user_id'] ) {
$uplf = TTnew( 'UserPreferenceListFactory' ); /** @var UserPreferenceListFactory $uplf */
$uplf->getByUserID( $override_preferences['user_id'] ); //Cached
if ( $uplf->getRecordCount() > 0 ) {
$override_preferences = array_merge( $uplf->getCurrent()->getObjectAsArray( [ 'time_zone' => true ] ), $override_preferences );
}
//If switching to another users timezone, default to appending the timezone on the end of each timestamp unless otherwise specified.
if ( !isset( $override_preferences['time_format'] ) && strpos( $user_pref_obj->getTimeFormat(), 'T' ) === false ) {
$override_preferences['time_format'] = $user_pref_obj->getTimeFormat() . ' T';
}
}
Debug::Arr( $override_preferences, 'Overridden Preferences: ', __FILE__, __LINE__, __METHOD__, 10 );
$user_pref_obj->setObjectFromArray( $override_preferences );
}
}
$user_pref_obj->setDateTimePreferences();
Debug::text( 'Locale Cookie: ' . TTi18n::getLocaleCookie(), __FILE__, __LINE__, __METHOD__, 10 );
//If override preferences specifies a language, do not save the users preferences, just use it dynamically instead.
if ( !isset( $override_preferences['language'] ) && TTi18n::getLocaleCookie() != '' && $user_pref_obj->getLanguage() !== TTi18n::getLanguageFromLocale( TTi18n::getLocaleCookie() ) ) {
Debug::text( 'Changing User Preference Language to match cookie...', __FILE__, __LINE__, __METHOD__, 10 );
$user_pref_obj->setLanguage( TTi18n::getLanguageFromLocale( TTi18n::getLocaleCookie() ) );
if ( $user_pref_obj->isValid() ) {
$user_pref_obj->Save( false );
}
} else {
Debug::text( 'User Preference Language matches cookie!', __FILE__, __LINE__, __METHOD__, 10 );
}
if ( isset( $_GET['language'] ) && $_GET['language'] != '' ) {
TTi18n::setLocale( $_GET['language'] ); //Sets master locale
} else {
TTi18n::setLanguage( $user_pref_obj->getLanguage() );
TTi18n::setCountry( $user_obj->getCountry() );
TTi18n::setLocale(); //Sets master locale
}
TTi18n::setLocaleCookie(); //Make sure locale cookie is set so APIGlobal.js.php can read it.
return $user_pref_obj;
}
/**
* @return bool|string
*/
function getJSONError() {
$retval = false;
if ( function_exists( 'json_last_error' ) ) { //Handle PHP v5.3 and older.
switch ( json_last_error() ) {
case JSON_ERROR_NONE:
break;
case JSON_ERROR_DEPTH:
$retval = 'Maximum stack depth exceeded';
break;
case JSON_ERROR_STATE_MISMATCH:
$retval = 'Underflow or the modes mismatch';
break;
case JSON_ERROR_CTRL_CHAR:
$retval = 'Unexpected control character found';
break;
case JSON_ERROR_SYNTAX:
$retval = 'Syntax error, malformed JSON';
break;
case JSON_ERROR_UTF8:
$retval = 'Malformed UTF-8 characters, possibly incorrectly encoded';
break;
default:
$retval = 'Unknown error';
break;
}
}
return $retval;
}
//Make sure cron job information is always logged.
//Don't do this until log rotation is implemented.
/*
Debug::setEnable( TRUE );
Debug::setBufferOutput( TRUE );
Debug::setEnableLog( TRUE );
if ( Debug::getVerbosity() <= 1 ) {
Debug::setVerbosity( 1 );
}
*/
?>

107
includes/CLI.inc.php Normal file
View File

@@ -0,0 +1,107 @@
<?php /** @noinspection PhpUndefinedVariableInspection */
/*********************************************************************************
*
* 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".
*
********************************************************************************/
//Allow only CLI PHP binaries to call maint scripts. To avoid a remote party from running them from hitting a URL.
if ( PHP_SAPI != 'cli' ) {
echo "This script can only be called from the Command Line. (" . PHP_SAPI . ")\n";
exit( 1 );
}
//There appears to be cases where ARGC/ARGV may not be set, so check those too. Fixes: PHP ERROR - NOTICE(8): Undefined variable: argc File: C:\TimeTrex\timetrex\tools\unattended_install.php Line: 31
if ( !isset( $argc ) || !isset( $argv ) ) {
echo "This script can only be called from the Command Line. (args)\n";
exit( 1 );
}
if ( version_compare( PHP_VERSION, '7.0.0', '<' ) == 1 ) {
echo "You are currently using PHP v" . PHP_VERSION . " TimeTrex requires PHP v7 or greater!\n";
exit( 1 );
}
//Allow CLI scripts to run much longer. ie: Purging database could takes hours.
ini_set( 'max_execution_time', 86000 ); //Just less than 24hrs, so scripts that run daily can't build up.
$install_obj = new Install();
//Make sure CLI tools are not being run as root, otherwise show error message and attempt to down-grade users.
if ( Misc::isCurrentOSUserRoot() == true ) {
fwrite( STDERR, 'ERROR: Running as \'root\' forbidden! To avoid permission conflicts, must run as the web-server user instead.' . "\n" );
fwrite( STDERR, ' Example: su www-data -c "' . ( ( isset( $config_vars['path']['php_cli'] ) ) ? $config_vars['path']['php_cli'] : 'php' ) . ' ' . implode( ' ', ( ( isset( $argv ) ) ? $argv : [] ) ) . '"' . "\n" );
Debug::Text( 'WARNING: Running as OS user \'root\' forbidden!', __FILE__, __LINE__, __METHOD__, 10 );
//Before we down-grade user privileges, check to make sure we can read/write all necessary files.
$install_obj->checkFilePermissions();
if ( Misc::setProcessUID( Misc::findWebServerOSUser() ) != true ) {
//Debug::Display(); //Handled in TTShutdown now.
//Debug::writeToLog(); //Handled in TTShutdown now.
exit( 1 );
}
}
//Check post install requirements, because PHP CLI usually uses a different php.ini file.
if ( $install_obj->checkAllRequirements( true ) == 1 ) {
$failed_requirements = $install_obj->getFailedRequirements( true );
unset( $failed_requirements[0] );
echo "----WARNING----WARNING----WARNING-----\n";
echo "--------------------------------------\n";
echo "Minimum PHP Requirements are NOT met!!\n";
echo "--------------------------------------\n";
echo "Failed Requirements: " . implode( ',', (array)$failed_requirements ) . " \n";
echo "--------------------------------------\n";
echo "PHP INI: " . $install_obj->getPHPConfigFile() . " \n";
echo "Process Owner: " . $install_obj->getWebServerUser() . " \n";
echo "--------------------------------------\n\n\n";
}
unset( $install_obj );
TTi18n::chooseBestLocale(); //Make sure a locale is set, specifically when generating PDFs.
//Uncomment the below block to force debug logging with maintenance jobs.
/*
Debug::setEnable( TRUE );
Debug::setBufferOutput( TRUE );
Debug::setEnableLog( TRUE );
if ( Debug::getVerbosity() <= 1 ) {
Debug::setVerbosity( 1 );
}
*/
?>

78
includes/Cache.inc.php Normal file
View File

@@ -0,0 +1,78 @@
<?php /** @noinspection PhpUndefinedVariableInspection */
/*********************************************************************************
*
* 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".
*
********************************************************************************/
require_once( Environment::getBasePath() . 'vendor' . DIRECTORY_SEPARATOR . 'pear' . DIRECTORY_SEPARATOR . 'cache_lite' . DIRECTORY_SEPARATOR . 'Cache' . DIRECTORY_SEPARATOR . 'Lite.php' );
//If caching is disabled, still do memory caching, otherwise permission checks cause the page to take 2+ seconds to load.
if ( !isset( $config_vars['cache']['enable'] ) || $config_vars['cache']['enable'] == false ) {
$config_vars['cache']['only_memory_cache_enable'] = true;
} else {
$config_vars['cache']['only_memory_cache_enable'] = false;
}
if ( !isset( $config_vars['cache']['dir'] ) ) {
$config_vars['cache']['dir'] = null;
}
$cache_options = [
'caching' => true,
'cacheDir' => $config_vars['cache']['dir'] . DIRECTORY_SEPARATOR,
'lifeTime' => 86400, //604800, //One day, cache should be cleared when the data is modified
'fileLocking' => true,
'writeControl' => true,
'readControl' => true,
'memoryCaching' => true,
'onlyMemoryCaching' => $config_vars['cache']['only_memory_cache_enable'],
'automaticSerialization' => true,
'hashedDirectoryLevel' => 1,
'fileNameProtection' => false,
'redisHost' => ( isset( $config_vars['cache']['redis_host'] ) ) ? $config_vars['cache']['redis_host'] : '',
'redisDB' => ( isset( $config_vars['cache']['redis_db'] ) ) ? $config_vars['cache']['redis_db'] : '',
];
if ( isset( $config_vars['cache']['redis_host'] ) && $config_vars['cache']['redis_host'] != '' ) {
require_once( Environment::getBasePath() . 'classes' . DIRECTORY_SEPARATOR . 'modules' . DIRECTORY_SEPARATOR . 'other' . DIRECTORY_SEPARATOR . 'Redis_Cache_Lite.class.php' );
$cache = $ADODB_CACHE = new Redis_Cache_Lite( $cache_options );
} else {
$cache = new Cache_Lite( $cache_options );
}
?>

1282
includes/ClassMap.inc.php Normal file

File diff suppressed because it is too large Load Diff

198
includes/Database.inc.php Normal file
View File

@@ -0,0 +1,198 @@
<?php /** @noinspection PhpUndefinedVariableInspection */
/** @noinspection PhpUndefinedFunctionInspection */
/** @noinspection ALL */
/*********************************************************************************
*
* 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".
*
********************************************************************************/
define( 'ADODB_DEFAULT_METATYPE', 'C' ); //Default to "C" or char type, which should allow most advanced data types such as geometry/ENUMS/JSON to work without further modifications.
require_once( Environment::getBasePath() . 'classes' . DIRECTORY_SEPARATOR . 'adodb' . DIRECTORY_SEPARATOR . 'adodb.inc.php' );
require_once( Environment::getBasePath() . 'classes' . DIRECTORY_SEPARATOR . 'adodb' . DIRECTORY_SEPARATOR . 'adodb-exceptions.inc.php' );
$PRIMARY_KEY_IS_UUID = true;
$COMPANY_GENERIC_MAP_DELETED_COLUMN = true; //Schema 1126A added "deleted" column to company_generic_map table, so we have to not use it in SELECT queries prior to 1126A upgrade.
//Use overloading to abstract $db and have calls directly to ADODB
if ( !isset( $disable_database_connection ) ) {
if ( isset( $config_vars['database']['type'] ) && isset( $config_vars['database']['host'] ) && isset( $config_vars['database']['user'] ) && isset( $config_vars['database']['password'] ) && isset( $config_vars['database']['database_name'] ) ) {
try {
if ( isset( $config_vars['cache']['dir'] ) && $config_vars['cache']['dir'] != '' ) {
$ADODB_CACHE_DIR = $config_vars['cache']['dir'] . DIRECTORY_SEPARATOR;
}
if ( Debug::getVerbosity() >= 12 ) {
$ADODB_OUTP = 'ADODBDebug';
function ADODBDebug( $msg, $newline = true ) {
Debug::Text( html_entity_decode( strip_tags( $msg ) ), __FILE__, __LINE__, __METHOD__, 11 );
return true;
}
}
if ( !isset( $config_vars['database']['persistent_connections'] ) ) {
$config_vars['database']['persistent_connections'] = false;
}
//Always force the database type that starts with "postgres" (ie: postgres7, postgres8, ...) to be just 'postgres'.
if ( strpos( $config_vars['database']['type'], 'postgres' ) !== false ) {
$config_vars['database']['type'] = 'postgres';
}
$ADODB_GETONE_EOF = false; //Make sure GetOne returns FALSE rather then NULL.
if ( strpos( $config_vars['database']['host'], ',' ) !== false ) {
require_once( Environment::getBasePath() . 'classes' . DIRECTORY_SEPARATOR . 'adodb' . DIRECTORY_SEPARATOR . 'adodb-loadbalancer.inc.php' );
$db = new ADOdbLoadBalancer();
//Use comma separated database hosts, assuming the first is always the master, the rest are slaves.
//Anything after the # is the weight. Username/password/database is assumed to be the same across all connections.
//ie: 127.0.0.1:5433#10,127.0.0.2:5433#100,127.0.0.3:5433#120
$db_hosts = Misc::parseDatabaseHostString( $config_vars['database']['host'] );
foreach ( $db_hosts as $db_host_arr ) {
Debug::Text( 'Adding DB Connection: Host: ' . $db_host_arr[0] . ' Type: ' . $db_host_arr[1] . ' Weight: ' . $db_host_arr[2], __FILE__, __LINE__, __METHOD__, 1 );
$db_connection_obj = new ADOdbLoadBalancerConnection( $config_vars['database']['type'], $db_host_arr[1], $db_host_arr[2], (bool)$config_vars['database']['persistent_connections'], $db_host_arr[0], $config_vars['database']['user'], $config_vars['database']['password'], $config_vars['database']['database_name'] );
$db_connection_obj->setConnectionTestCallback( function( $connection_obj, $adodb_obj ) use ( $config_vars ) {
if ( $connection_obj->type == 'readonly' ) {
//When connecting to a slave database, make sure the replication delay never exceeds our threshold, otherwise discard the connection and try a different host.
$maximum_replication_delay = 60; //Seconds
if ( isset($config_vars['database']['maximum_replication_delay']) && is_numeric( $config_vars['database']['maximum_replication_delay'] ) ) {
$maximum_replication_delay = (float)$config_vars['database']['maximum_replication_delay'];
}
$result = (float)$adodb_obj->GetOne( 'SELECT EXTRACT(epoch FROM ( now() - CASE WHEN pg_last_xact_replay_timestamp() IS NULL THEN now() ELSE pg_last_xact_replay_timestamp() END ) ) AS replication_delay' );
//Debug::Text( ' Database Replication Delay: '. $result .' Host: '. $connection_obj->host, __FILE__, __LINE__, __METHOD__, 10 );
if ( $result <= $maximum_replication_delay ) {
return true;
} else {
Debug::Text( 'ERROR: Database Connection is invalid, ignoring! Host: '. $connection_obj->host .' Replication Delay: '. $result, __FILE__, __LINE__, __METHOD__, 1 );
return false;
}
}
return true; //Always return true for master connections.
} );
$db_connection_obj->getADODbObject()->SetFetchMode( ADODB_FETCH_ASSOC );
$db_connection_obj->getADODbObject()->noBlobs = true; //Optimization to tell ADODB to not bother checking for blobs in any result set.
$db_connection_obj->getADODbObject()->fmtTimeStamp = "'Y-m-d H:i:s'";
if ( Debug::getVerbosity() >= 12 ) {
//Use 1 instead of TRUE, so it only outputs some debugging and not things like backtraces for every cache read/write.
//Set to 99 to get all debug output.
$db_connection_obj->getADODbObject()->debug = 1;
}
if ( isset( $config_vars['database']['disable_row_count'] ) && $config_vars['database']['disable_row_count'] == true ) {
//Dont count rows for pagination, much faster. However two queries must be run to tell if we are at the last page or not.
$db_connection_obj->getADODbObject()->pageExecuteCountRows = false;
}
$db->addConnection( $db_connection_obj );
}
unset( $db_hosts, $db_host_arr, $db_connection_obj );
$db->setSessionInitSQL( 'SET datestyle = \'ISO\'' ); //Needed for ADODB to properly parse dates, as we removed it from ADODB as an optimization so it can be delayed until the first query is executed.
//$db->setSessionInitSQL( 'SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL REPEATABLE READ' ); //This is required to properly handle simultaneous recalculations of timesheets/pay stubs. We moved this to trigger via setTransactionMode() only for certain operations instead though.
} else {
//To enable PDO support. Type: pdo_pgsql
//$dsn = $config_vars['database']['type'].'://'.$config_vars['database']['user'].':'.$config_vars['database']['password'].'@'.$config_vars['database']['host'].'/'.$config_vars['database']['database_name'].'?persist';
//$db = ADONewConnection( $dsn );
$db = ADONewConnection( $config_vars['database']['type'] );
/** @noinspection PhpUndefinedConstantInspection */
$db->SetFetchMode( ADODB_FETCH_ASSOC );
if ( isset( $config_vars['database']['persistent_connections'] ) && $config_vars['database']['persistent_connections'] == true ) {
$db->PConnect( $config_vars['database']['host'], $config_vars['database']['user'], $config_vars['database']['password'], $config_vars['database']['database_name'] );
} else {
$db->Connect( $config_vars['database']['host'], $config_vars['database']['user'], $config_vars['database']['password'], $config_vars['database']['database_name'] );
}
$db->noBlobs = true; //Optimization to tell ADODB to not bother checking for blobs in any result set.
//Use long timezone format because PostgreSQL 8.1 doesn't support some short names, like SGT,IST
//Using "e" for the timezone fixes the Asia/Calcutta & IST bug where the two were getting confused.
//We set the session timezone in PostgreSQL, so 'e' shouldn't be required anymore.
//$db->fmtTimeStamp = "'Y-m-d H:i:s e'";
$db->fmtTimeStamp = "'Y-m-d H:i:s'";
if ( Debug::getVerbosity() >= 12 ) {
//Use 1 instead of TRUE, so it only outputs some debugging and not things like backtraces for every cache read/write.
//Set to 99 to get all debug output.
$db->debug = 1;
}
$db->Execute( 'SET datestyle = \'ISO\'' ); //Needed for ADODB to properly parse dates, as we removed it from ADODB as an optimization so it can be delayed until the first query is executed.
//$db->Execute( 'SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL REPEATABLE READ' ); //This is required to properly handle simultaneous recalculations of timesheets/pay stubs. We moved this to trigger via setTransactionMode() only for certain operations instead though.
if ( isset( $config_vars['database']['disable_row_count'] ) && $config_vars['database']['disable_row_count'] == true ) {
//Dont count rows for pagination, much faster. However two queries must be run to tell if we are at the last page or not.
$db->pageExecuteCountRows = false;
}
}
} catch ( Exception $e ) {
Debug::Text( 'Error connecting to the database!', __FILE__, __LINE__, __METHOD__, 1 );
throw new DBError( $e );
}
if ( ( isset( $config_vars['other']['installer_enabled'] ) && $config_vars['other']['installer_enabled'] == true ) || ( isset( $_SERVER['SCRIPT_FILENAME'] ) && stripos( $_SERVER['SCRIPT_FILENAME'], 'unattended_upgrade' ) !== false ) ) { //Make sure we always check the schema versions when run from unattended_upgrade, just in case the upgrade failed and we need to determine if we are in UUID mode or not.
//Make sure that during initial installation we confirm that the database/table actually exists, otherwise this can throw a fatal SQL error.
$install_obj = new Install();
$install_obj->setDatabaseConnection( $db ); //Default connection
if ( $install_obj->checkSystemSettingTableExists() == 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.
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;
}
}
}
} else {
Debug::Text( 'Database config options are not set... Unable to connect to database.', __FILE__, __LINE__, __METHOD__, 1 );
throw new DBError( new Exception );
}
}
//Set timezone to system local timezone by default. This is so we sync up all timezones in the database and PHP.
//This fixes timezone bugs mainly in maintenance scripts. We used to default this to just GMT, but that can cause additional problems in threaded environments.
//This must be run AFTER the database connection has been made to work properly.
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' );
}
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()
?>

14
includes/Footer.inc.php Normal file
View File

@@ -0,0 +1,14 @@
<div id="bottomContainer" class="bottom-container">
<div class="footer">
<div class="container">
<div class="row">
<div class="col-md-12 col-sm-12 col-xs-12">
<p class="footer-copyright"><?php echo COPYRIGHT_NOTICE ?></p>
</div>
</div>
</div>
</div>
</div>
<div id="overlay" class=""></div>
</body>
</html>

190
includes/Header.inc.php Normal file
View File

@@ -0,0 +1,190 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title><?php echo ( ( isset( $META_TITLE ) && $META_TITLE != '' ) ? $META_TITLE . ' | ' : '' ) . APPLICATION_NAME ?></title>
<base href="<?php echo $BASE_URL; ?>">
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<meta name="Description" content="Workforce Management Software for tracking employee time and attendance, employee time clock software, employee scheduling software and payroll software all in a single package. Also calculate complex over time and premium time business policies and can identify labor costs attributed to branches and departments. Managers can now track and monitor their workforce easily."/>
<script src="../html5/global/Debug.js?v=<?php echo APPLICATION_BUILD ?>"></script>
<script src="../html5/global/CookieSetting.js?v=<?php echo APPLICATION_BUILD ?>"></script>
<script src="../html5/framework/jquery.min.js?v=<?php echo APPLICATION_BUILD ?>"></script>
<!--
TODO
Various files have had paths changed or been commented out to avoid exceptions and restore appearance and functionality for
ForgotPassword.php, ConfirmEmail.php and DownForMaintenance.php. Eventually these files should go through
webpack the same as quick punch, recruitment and rest of the application.
-->
<?php
//require_once( '../../../includes/API.inc.php' );
//TTi18n::chooseBestLocale();
//$api_auth = TTNew( 'APIAuthentication' ); /** @var APIAuthentication $api_auth */
?>
<!--<script src="../html5/global/APIGlobal.js.php?disable_db=1&v=<?php //echo APPLICATION_BUILD ?>"></script>-->
<!--<script>
var APIGlobal = function() {};
APIGlobal.pre_login_data = <?php //echo json_encode( $api_auth->getPreLoginData() );?>//; //Convert getPreLoginData() array to JS.
</script>-->
<!--<script src="../../node_modules/underscore/underscore-min.js?v=--><?php //echo APPLICATION_BUILD ?><!--"></script>-->
<!--<script src="../../node_modules/backbone/backbone-min.js?v=--><?php //echo APPLICATION_BUILD ?><!--"></script>-->
<!--<script src="../html5/global/Global.js?v=--><?php //echo APPLICATION_BUILD ?><!--"></script>-->
<script src="../../node_modules/bootstrap/dist/js/bootstrap.bundle.js?v=<?php echo APPLICATION_BUILD ?>"></script>
<link rel="stylesheet" type="text/css" href="../../node_modules/bootstrap/dist/css/bootstrap.min.css?v=<?php echo APPLICATION_BUILD ?>">
<style rel="stylesheet" type="text/css">
body {
min-height: 100%;
height: auto;
width: 100%;
position: absolute;
}
.footer {
/*min-height: 68px;*/
height: 68px;
width: 100%;
padding: 14px 0 14px;
background-color: #262626;
text-align: center;
padding-top: 24px;
margin-top: 40px;
position: absolute;
bottom: 0;
left: 0;
}
.footer .footer-menu a:hover {
text-decoration: none;
cursor: pointer;
}
.footer .footer-copyright {
color: #787878;
margin: 0;
}
.company-logo img {
max-height: 51px;
}
.navbar-DownForMaintenance {
margin: 5px -15px 5px -15px;
}
.navbar-DownForMaintenance .company-logo img {
max-height: 51px;
}
#contentBox {
background: #fff;
margin: 0 auto;
position: relative;
left: 0;
padding: 0;
/*width: 600px;*/
max-width: 768px;
/*padding: 15px;*/
}
#contentBox-DownForMaintenance, #contentBox-ConfirmEmail, #contentBox-ForgotPassword {
background: #fff;
margin: 0 auto;
position: relative;
left: 0;
/*padding: 20px;*/
/*width: 600px;*/
border: 1px solid #779bbe;
text-align: center;
}
.textTitle2 {
color: #036;
font-size: 16px;
font-weight: bold;
padding: 0;
padding-left: 10px;
margin: 0;
}
#contentBox-ForgotPassword .form-control-static {
text-align: left;
}
#contentBox-DownForMaintenance .textTitle2, #contentBox-ConfirmEmail .textTitle2, #contentBox-ForgotPassword .textTitle2 {
padding-left: 0;
margin: 0;
height: 60px;
background: rgb(49, 84, 130);
line-height: 60px;
color: #fff;
}
/*#contentBox-ForgotPassword .textTitle2 {*/
/*padding-left: 0;*/
/*margin: 0;*/
/*height: 60px;*/
/*background: rgb(49,84,130);*/
/*line-height: 60px;*/
/*color: #fff;*/
/*margin-bottom: 15px;*/
/*}*/
#contentBox-ForgotPassword .form-horizontal {
margin: 15px;
}
#contentBox-ForgotPassword label {
color: rgb(49, 84, 130);
}
@media (max-width: 767px) {
#contentBox-ForgotPassword label {
text-align: left;
}
}
#contentBox-ForgotPassword .form-control {
border-color: rgb(49, 84, 130);;
}
#contentBox-ForgotPassword .button {
background: rgb(49, 84, 130);
color: #FFFFFF;
}
#rowWarning {
margin: 15px 30px;
padding: 5px;
border: 0px solid #c30;
background: #FFFF00;
font-weight: bold;
}
#contentBox-DownForMaintenance #rowWarning, #contentBox-ConfirmEmail #rowWarning, #contentBox-ForgotPassword #rowWarning {
margin: 0;
padding-top: 20px;
padding-bottom: 20px;
background: #FFFFFF;
/*line-height: 60px;*/
/*height: 60px;*/
}
#rowError {
margin: 15px 30px;
padding: 5px;
border: 0px solid #c30;
background: #FF0000;
font-weight: bold;
}
</style>
</head>
<body>
<nav class="navbar navbar-default">
<div class="container">
<div class="navbar-header">
<a tabindex="-1" class="company-logo" href="https://www.timetrex.com" title="Workforce Management Software"><img src="<?php echo Environment::getBaseURL(); ?>send_file.php?api=1&object_type=primary_company_logo" alt="Workforce Management Software"></a>
</div>
</div>
</nav>

140
includes/Interface.inc.php Normal file
View File

@@ -0,0 +1,140 @@
<?php /** @noinspection PhpUndefinedVariableInspection */
/*********************************************************************************
*
* 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".
*
********************************************************************************/
//
//Primary purpose of this include now is to just setup the $current_user, $current_user_prefs and $current_company globals when accessing pages outside of the API, such as send_file.php and upload_file.php.
//
forceNoCacheHeaders(); //Send headers to disable caching.
//Skip this step if disable_database_connection is enabled or the user is going through the installer still
$clf = new CompanyListFactory();
if ( ( !isset( $disable_database_connection ) || ( isset( $disable_database_connection ) && $disable_database_connection != true ) )
&& ( !isset( $config_vars['other']['installer_enabled'] ) || ( isset( $config_vars['other']['installer_enabled'] ) && $config_vars['other']['installer_enabled'] != true ) ) ) {
//Get all system settings, so they can be used even if the user isn't logged in, such as the login page.
try {
$sslf = new SystemSettingListFactory();
$system_settings = $sslf->getAllArray();
unset( $sslf );
//Get primary company data needs to be used when user isn't logged in as well.
$clf->getByID( PRIMARY_COMPANY_ID );
if ( $clf->getRecordCount() == 1 ) {
$primary_company = $clf->getCurrent();
}
} catch ( Exception $e ) {
//Database not initialized, or some error, redirect to Install page.
throw new DBError( $e, 'DBInitialize' );
}
}
$permission = new Permission();
$authentication = new Authentication();
if ( isset( $authenticate ) && $authenticate === false ) {
Debug::text( 'Bypassing Authentication', __FILE__, __LINE__, __METHOD__, 10 );
TTi18n::chooseBestLocale();
} else {
if ( $authentication->Check( null, [ 'USER_NAME', 'USER_NAME_MULTI_FACTOR' ] ) === true ) {
$profiler->startTimer( 'Interface.inc - Post-Authentication' );
/*
* Get default interface data here. Things like User info, Company info etc...
*/
$current_user = $authentication->getObject();
Debug::text( 'User Authenticated: ' . $current_user->getUserName() . ' Created Date: ' . $authentication->getCreatedDate(), __FILE__, __LINE__, __METHOD__, 10 );
if ( isset( $primary_company ) && PRIMARY_COMPANY_ID == $current_user->getCompany() ) {
$current_company = $primary_company;
} else {
$current_company = $clf->getByID( $current_user->getCompany() )->getCurrent();
}
$current_user_prefs = $current_user->getUserPreferenceObject();
//If user doesnt have any preferences set, we need to bootstrap the preference object.
if ( $current_user_prefs->getUser() == '' ) {
$current_user_prefs->setUser( $current_user->getId() );
}
/*
* Check locale cookie, if it varies from UserPreference Language,
* change user preferences to match. This could cause some unexpected behavior
* as the change is happening behind the scenes, but if we don't change
* the user prefs then they could login for weeks/months as a different
* language from their preferences, therefore making the user preference
* setting almost useless. Causing issues when printing pay stubs and in each
* users language.
*/
Debug::text( 'Locale Cookie: ' . TTi18n::getLocaleCookie(), __FILE__, __LINE__, __METHOD__, 10 );
if ( $current_user_prefs->isNew() == false && TTi18n::getLocaleCookie() != '' && $current_user_prefs->getLanguage() !== TTi18n::getLanguageFromLocale( TTi18n::getLocaleCookie() ) ) {
Debug::text( 'Changing User Preference Language to match cookie...', __FILE__, __LINE__, __METHOD__, 10 );
$current_user_prefs->setLanguage( TTi18n::getLanguageFromLocale( TTi18n::getLocaleCookie() ) );
if ( $current_user_prefs->isValid() ) {
$current_user_prefs->Save( false );
}
} else {
Debug::text( 'User Preference Language matches cookie!', __FILE__, __LINE__, __METHOD__, 10 );
}
if ( isset( $_GET['language'] ) && $_GET['language'] != '' ) {
TTi18n::setLocale( $_GET['language'] ); //Sets master locale
} else {
TTi18n::setLanguage( $current_user_prefs->getLanguage() );
TTi18n::setCountry( $current_user->getCountry() );
TTi18n::setLocale(); //Sets master locale
}
//Debug::text('Current Company: '. $current_company->getName(), __FILE__, __LINE__, __METHOD__, 10);
$profiler->stopTimer( 'Interface.inc - Post-Authentication' );
} else {
Debug::text( 'User NOT Authenticated!', __FILE__, __LINE__, __METHOD__, 10 );
Redirect::Page( URLBuilder::getURL( null, Environment::GetBaseURL() . 'html5/' ) );
//exit;
}
}
unset( $clf );
$profiler->startTimer( 'Main' );
?>

199
includes/TableMap.inc.php Normal file
View File

@@ -0,0 +1,199 @@
<?php
global $global_table_map;
$global_table_map = [
'accrual' => 'AccrualFactory',
'accrual_balance' => 'AccrualBalanceFactory',
'client_payment' => 'ClientPaymentFactory',
'client' => 'ClientFactory',
'client_balance' => 'ClientBalanceFactory',
'client_group' => 'ClientGroupFactory',
'client_contact' => 'ClientContactFactory',
'company_generic_map' => 'CompanyGenericMapFactory',
'company_generic_tag' => 'CompanyGenericTagFactory',
'company_generic_tag_map' => 'CompanyGenericTagMapFactory',
'recruitment_portal_field_map' => 'PortalConfigFieldMapFactory',
'branch' => 'BranchFactory',
'company_deduction_pay_stub_entry_account' => 'CompanyDeductionPayStubEntryAccountFactory',
'company_user_count' => 'CompanyUserCountFactory',
'company_deduction' => 'CompanyDeductionFactory',
'wage_group' => 'WageGroupFactory',
'company' => 'CompanyFactory',
'company_setting' => 'CompanySettingFactory',
'station_user' => 'StationUserFactory',
'system_setting' => 'SystemSettingFactory',
'user_date_total' => 'UserDateTotalFactory',
'currency' => 'CurrencyFactory',
'currency_rate' => 'CurrencyRateFactory',
'system_log_detail' => 'LogDetailFactory',
'station_exclude_user' => 'StationExcludeUserFactory',
'station_department' => 'StationDepartmentFactory',
'station_include_user' => 'StationIncludeUserFactory',
'exception' => 'ExceptionFactory',
'user_date' => 'UserDateFactory',
'permission' => 'PermissionFactory',
'station' => 'StationFactory',
'station_user_group' => 'StationUserGroupFactory',
'permission_control' => 'PermissionControlFactory',
'permission_user' => 'PermissionUserFactory',
'other_field' => 'OtherFieldFactory',
'custom_field_control' => 'CustomFieldFactory',
'station_branch' => 'StationBranchFactory',
'authorizations' => 'AuthorizationFactory',
'system_log' => 'LogFactory',
'cron' => 'CronJobFactory',
'department' => 'DepartmentFactory',
'document' => 'DocumentFactory',
'document_group' => 'DocumentGroupFactory',
'document_revision' => 'DocumentRevisionFactory',
'document_attachment' => 'DocumentAttachmentFactory',
'help_group' => 'HelpGroupFactory',
'help_group_control' => 'HelpGroupControlFactory',
'help' => 'HelpFactory',
'hierarchy_control' => 'HierarchyControlFactory',
'hierarchy_user' => 'HierarchyUserFactory',
'hierarchy_level' => 'HierarchyLevelFactory',
'hierarchy_share' => 'HierarchyShareFactory',
'hierarchy_object_type' => 'HierarchyObjectTypeFactory',
'hierarchy' => 'HierarchyFactory',
'recurring_holiday' => 'RecurringHolidayFactory',
'holidays' => 'HolidayFactory',
'invoice_config' => 'InvoiceConfigFactory',
'payment_gateway_credit_card_type' => 'PaymentGatewayCreditCardTypeFactory',
'invoice_district' => 'InvoiceDistrictFactory',
'payment_gateway' => 'PaymentGatewayFactory',
'invoice' => 'InvoiceFactory',
'invoice_transaction' => 'TransactionFactory',
'payment_gateway_currency' => 'PaymentGatewayCurrencyFactory',
'shipping_policy_object' => 'ShippingPolicyObjectFactory',
'area_policy_location' => 'AreaPolicyLocationFactory',
'tax_policy' => 'TaxPolicyFactory',
'shipping_policy' => 'ShippingPolicyFactory',
'area_policy' => 'AreaPolicyFactory',
'tax_policy_object' => 'TaxPolicyObjectFactory',
'job' => 'JobFactory',
'job_user_allow' => 'JobUserAllowFactory',
'job_group' => 'JobGroupFactory',
'job_item_amendment' => 'JobItemAmendmentFactory',
'job_item_group' => 'JobItemGroupFactory',
'job_item_allow' => 'JobItemAllowFactory',
'job_item' => 'JobItemFactory',
'message' => 'MessageFactory',
'message_recipient' => 'MessageRecipientFactory',
'message_sender' => 'MessageSenderFactory',
'message_control' => 'MessageControlFactory',
'notification' => 'NotificationFactory',
'income_tax_compare' => 'IncomeTaxCompare',
'pay_period_time_sheet_verify' => 'PayPeriodTimeSheetVerifyFactory',
'pay_period' => 'PayPeriodFactory',
'pay_period_schedule' => 'PayPeriodScheduleFactory',
'pay_period_schedule_user' => 'PayPeriodScheduleUserFactory',
'pay_stub_entry' => 'PayStubEntryFactory',
'pay_stub' => 'PayStubFactory',
'government_document' => 'GovernmentDocumentFactory',
'pay_stub_entry_account_link' => 'PayStubEntryAccountLinkFactory',
'pay_stub_entry_account' => 'PayStubEntryAccountFactory',
'recurring_ps_amendment' => 'RecurringPayStubAmendmentFactory',
'recurring_ps_amendment_user' => 'RecurringPayStubAmendmentUserFactory',
'pay_stub_amendment' => 'PayStubAmendmentFactory',
'pay_stub_transaction' => 'PayStubTransactionFactory',
'payroll_remittance_agency_event' => 'PayrollRemittanceAgencyEventFactory',
'exception_policy' => 'ExceptionPolicyFactory',
'premium_policy_department' => 'PremiumPolicyDepartmentFactory',
'round_interval_policy' => 'RoundIntervalPolicyFactory',
'holiday_policy_recurring_holiday' => 'HolidayPolicyRecurringHolidayFactory',
'meal_policy' => 'MealPolicyFactory',
'premium_policy' => 'PremiumPolicyFactory',
'pay_code' => 'PayCodeFactory',
'pay_formula_policy' => 'PayFormulaPolicyFactory',
'contributing_pay_code_policy' => 'ContributingPayCodePolicyFactory',
'contributing_shift_policy' => 'ContributingShiftPolicyFactory',
'regular_time_policy' => 'RegularTimePolicyFactory',
'over_time_policy' => 'OverTimePolicyFactory',
'schedule_policy' => 'SchedulePolicyFactory',
'break_policy' => 'BreakPolicyFactory',
'premium_policy_job_group' => 'PremiumPolicyJobGroupFactory',
'premium_policy_job_item_group' => 'PremiumPolicyJobItemGroupFactory',
'holiday_policy' => 'HolidayPolicyFactory',
'exception_policy_control' => 'ExceptionPolicyControlFactory',
'premium_policy_branch' => 'PremiumPolicyBranchFactory',
'policy_group' => 'PolicyGroupFactory',
'accrual_policy' => 'AccrualPolicyFactory',
'accrual_policy_account' => 'AccrualPolicyAccountFactory',
'accrual_policy_milestone' => 'AccrualPolicyMilestoneFactory',
'accrual_policy_user_modifier' => 'AccrualPolicyUserModifierFactory',
'policy_group_user' => 'PolicyGroupUserFactory',
'premium_policy_job' => 'PremiumPolicyJobFactory',
'policy_group_round_interval_policy' => 'PolicyGroupRoundIntervalPolicyFactory',
'policy_group_accrual_policy' => 'PolicyGroupAccrualPolicyFactory',
'premium_policy_job_item' => 'PremiumPolicyJobItemFactory',
'absence_policy' => 'AbsencePolicyFactory',
'policy_group_over_time_policy' => 'PolicyGroupOverTimePolicyFactory',
'policy_group_premium_policy' => 'PolicyGroupPremiumPolicyFactory',
'product_tax_policy_product' => 'ProductTaxPolicyProductFactory',
'product_price' => 'ProductPriceFactory',
'product_group' => 'ProductGroupFactory',
'product' => 'ProductFactory',
'punch_control' => 'PunchControlFactory',
'punch' => 'PunchFactory',
'report_schedule' => 'ReportScheduleFactory',
'report_custom_column' => 'ReportCustomColumnFactory',
'request' => 'RequestFactory',
'request_schedule' => 'RequestScheduleFactory',
'recurring_schedule_control' => 'RecurringScheduleControlFactory',
'schedule' => 'ScheduleFactory',
'recurring_schedule' => 'RecurringScheduleFactory',
'recurring_schedule_user' => 'RecurringScheduleUserFactory', //This table has been removed.
'recurring_schedule_template_control' => 'RecurringScheduleTemplateControlFactory',
'recurring_schedule_template' => 'RecurringScheduleTemplateFactory',
'user_identification' => 'UserIdentificationFactory',
'user_generic_data' => 'UserGenericDataFactory',
'user_preference' => 'UserPreferenceFactory',
'user_preference_notification' => 'UserPreferenceNotificationFactory',
'ui_kit' => 'UIKitSampleFactory',
'user_title' => 'UserTitleFactory',
'user_default_company_deduction' => 'UserDefaultCompanyDeductionFactory',
'user_wage' => 'UserWageFactory',
'user_default' => 'UserDefaultFactory',
'users' => 'UserFactory',
'user_generic_status' => 'UserGenericStatusFactory',
'user_deduction' => 'UserDeductionFactory',
'user_report_data' => 'UserReportDataFactory',
'bank_account' => 'BankAccountFactory',
'qualification' => 'QualificationFactory',
'qualification_group' => 'QualificationGroupFactory',
'user_skill' => 'UserSkillFactory',
'user_education' => 'UserEducationFactory',
'user_license' => 'UserLicenseFactory',
'user_language' => 'UserLanguageFactory',
'user_membership' => 'UserMembershipFactory',
'user_group' => 'UserGroupFactory',
'kpi' => 'KPIFactory',
'kpi_group' => 'KPIGroupFactory',
'user_review_control' => 'UserReviewControlFactory',
'user_review' => 'UserReviewFactory',
'user_contact' => 'UserContactFactory',
'job_vacancy' => 'JobVacancyFactory',
'job_applicant' => 'JobApplicantFactory',
'expense_policy' => 'ExpensePolicyFactory',
'user_expense' => 'UserExpenseFactory',
'user_setting' => 'UserSettingFactory',
'roe' => 'ROEFactory',
'job_application' => 'JobApplicationFactory',
'job_applicant_location' => 'JobApplicantLocationFactory',
'job_applicant_employment' => 'JobApplicantEmploymentFactory',
'job_applicant_reference' => 'JobApplicantReferenceFactory',
'job_applicant_skill' => 'JobApplicantSkillFactory',
'job_applicant_education' => 'JobApplicantEducationFactory',
'job_applicant_license' => 'JobApplicantLicenseFactory',
'job_applicant_language' => 'JobApplicantLanguageFactory',
'job_applicant_membership' => 'JobApplicantMembershipFactory',
'recruitment_portal_config' => 'RecruitmentPortalConfigFactory',
'ethnic_group' => 'EthnicGroupFactory',
'legal_entity' => 'LegalEntityFactory',
'payroll_remittance_agency' => 'PayrollRemittanceAgencyFactory',
'remittance_source_account' => 'RemittanceSourceAccountFactory',
'remittance_destination_account' => 'RemittanceDestinationAccountFactory',
'geo_fence' => 'GEOFenceFactory',
'idempotent_request' => 'IdempotentRequestFactory',
];
?>

518
includes/global.inc.php Normal file
View File

@@ -0,0 +1,518 @@
<?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".
*
********************************************************************************/
date_default_timezone_set( 'GMT' ); //Default timezone to UTC until we can at least determine or set another one. This should also prevent: PHP ERROR - 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() function
//Other than when using the installer, disable URL-aware fopen wrappers like: http:// just as a fail safe. All user input must still be sanitized of course.
ini_set( 'allow_url_fopen', 0 );
//BUG in PHP 5.2.2 that causes $HTTP_RAW_POST_DATA not to be set. Work around it.
//This is deprecated in PHP v5.6 and removed in PHP v7, so switch to just always populating it.
$HTTP_RAW_POST_DATA = file_get_contents( 'php://input' );
if ( !isset( $_SERVER['HTTP_HOST'] ) ) {
$_SERVER['HTTP_HOST'] = 'localhost';
}
ob_start(); //Take care of GZIP in Apache
if ( ini_get( 'max_execution_time' ) < 1800 ) {
ini_set( 'max_execution_time', 1800 );
}
define( 'APPLICATION_VERSION', '16.2.0' );
define( 'APPLICATION_VERSION_DATE', 1667372400 ); //Release date of version. CMD: php -r 'echo "\n". strtotime("02-Nov-2022")."\n\n";'
if ( strtoupper( substr( PHP_OS, 0, 3 ) ) == 'WIN' ) {
define( 'OPERATING_SYSTEM', 'WIN' );
} else {
define( 'OPERATING_SYSTEM', 'LINUX' );
}
/*
Find Config file.
Can use the following line in .htaccess or Apache virtual host definition to define a config file outside the document root.
SetEnv TT_CONFIG_FILE /etc/timetrex/timetrex.ini.php
Or from the CLI:
export TT_CONFIG_FILE=/etc/timetrex/timetrex.ini.php
*/
if ( isset( $_SERVER['TT_CONFIG_FILE'] ) && $_SERVER['TT_CONFIG_FILE'] != '' ) {
define( 'CONFIG_FILE', $_SERVER['TT_CONFIG_FILE'] );
} else {
define( 'CONFIG_FILE', dirname( __FILE__ ) . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'timetrex.ini.php' );
}
/*
Config file outside webroot.
*/
if ( file_exists( CONFIG_FILE ) ) {
if ( isset( $config_vars ) ) { //Allow config_var to be overwritten before global.inc.php is loaded. Used in QueueWorker.php for forcing a persistent connection.
$config_vars = array_merge_recursive( parse_ini_file( CONFIG_FILE, true ), $config_vars );
} else {
$config_vars = parse_ini_file( CONFIG_FILE, true );
}
if ( $config_vars === false ) {
echo "ERROR: Config file (" . CONFIG_FILE . ") contains a syntax error! If your passwords contain special characters you need to wrap them in double quotes, ie:<br>\n password = \"test!1!me\"\n";
exit( 1 );
}
} else {
echo "ERROR: Config file (" . CONFIG_FILE . ") does not exist or is not readable!\n";
exit( 1 );
}
if ( defined( 'PRODUCTION' ) == false ) {
if ( isset( $config_vars['debug']['production'] ) && $config_vars['debug']['production'] == 1 ) {
define( 'PRODUCTION', true );
} else {
define( 'PRODUCTION', false );
}
}
if ( defined( 'DEMO_MODE' ) == false ) {
if ( isset( $config_vars['other']['demo_mode'] ) && $config_vars['other']['demo_mode'] == 1 ) {
define( 'DEMO_MODE', true );
} else {
define( 'DEMO_MODE', false );
}
}
//**REMOVING OR CHANGING THIS APPLICATION NAME AND ORGANIZATION URL IS IN STRICT VIOLATION OF THE LICENSE AND COPYRIGHT AGREEMENT**//
define( 'APPLICATION_NAME', ( PRODUCTION == false && DEMO_MODE == false ) ? 'TimeTrex-Debug' : 'TimeTrex' );
define( 'ORGANIZATION_NAME', 'TimeTrex' );
define( 'ORGANIZATION_URL', 'www.TimeTrex.com' );
if ( isset( $config_vars['other']['deployment_on_demand'] ) && $config_vars['other']['deployment_on_demand'] == 1 ) {
define( 'DEPLOYMENT_ON_DEMAND', true );
} else {
define( 'DEPLOYMENT_ON_DEMAND', false );
}
if ( isset( $config_vars['other']['primary_company_id'] ) && $config_vars['other']['primary_company_id'] != '' ) {
define( 'PRIMARY_COMPANY_ID', (string)$config_vars['other']['primary_company_id'] );
} else {
define( 'PRIMARY_COMPANY_ID', false );
}
if ( PRODUCTION == true ) {
define( 'APPLICATION_BUILD', APPLICATION_VERSION . '-' . date( 'Ymd', APPLICATION_VERSION_DATE ) . '-' . date( 'His', filemtime( __FILE__ ) ) );
} else {
define( 'APPLICATION_BUILD', APPLICATION_VERSION . '-' . date( 'Ymd' ) ); //Keep as static as possible as if this changes during JS debugging it will wipe out any temporary break points.
}
//Windows doesn't define LC_MESSAGES, so lets do it manually here.
if ( defined( 'LC_MESSAGES' ) == false ) {
define( 'LC_MESSAGES', 6 );
}
if ( PRODUCTION == TRUE ) {
error_reporting( E_ALL & ~E_DEPRECATED & ~E_STRICT ); //Disable DEPRECATED & STRICT notices when in production.
}
/**
* Converts human readable size to bytes.
* @param int|float|string $size Human readable size, ie: 1K, 1M, 1G
* @return int
*/
function convertHumanSizeToBytes( $size ) {
$retval = (int)str_ireplace( [ 'G', 'M', 'K' ], [ '000000000', '000000', '000' ], $size );
//Debug::text('Input Size: '. $size .' Bytes: '. $retval, __FILE__, __LINE__, __METHOD__, 9);
return $retval;
}
//If memory limit is set below the minimum required, just bump it up to that minimum. If its higher, keep the higher value.
$memory_limit = convertHumanSizeToBytes( ini_get( 'memory_limit' ) );
if ( $memory_limit >= 0 && $memory_limit < 512000000 ) { //Use * 1000 rather than * 1024 for easier parsing of G, M, K -- Make sure we consider -1 as the limit.
ini_set( 'memory_limit', '512000000' );
}
unset( $memory_limit );
//IIS 5 doesn't seem to set REQUEST_URI, so attempt to build one on our own
//This also appears to fix CGI mode.
//Inspired by: http://neosmart.net/blog/2006/100-apache-compliant-request_uri-for-iis-and-windows/
if ( !isset( $_SERVER['REQUEST_URI'] ) ) {
if ( isset( $_SERVER['SCRIPT_NAME'] ) ) {
$_SERVER['REQUEST_URI'] = $_SERVER['SCRIPT_NAME'];
} else if ( isset( $_SERVER['PHP_SELF'] ) ) {
$_SERVER['REQUEST_URI'] = $_SERVER['PHP_SELF'];
}
if ( isset( $_SERVER['QUERY_STRING'] ) && $_SERVER['QUERY_STRING'] != '' ) {
$_SERVER['REQUEST_URI'] .= '?' . $_SERVER['QUERY_STRING'];
}
}
//HTTP Basic authentication doesn't work properly with CGI/FCGI unless we decode it this way.
if ( isset( $_SERVER['HTTP_AUTHORIZATION'] ) && $_SERVER['HTTP_AUTHORIZATION'] != '' && stripos( php_sapi_name(), 'cgi' ) !== false ) {
//<IfModule mod_rewrite.c>
//RewriteEngine on
//RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization},L]
//</IfModule>
//Or this instead:
//SetEnvIf Authorization "(.*)" HTTP_AUTHORIZATION=$1
[ $_SERVER['PHP_AUTH_USER'], $_SERVER['PHP_AUTH_PW'] ] = array_pad( explode( ':', base64_decode( substr( $_SERVER['HTTP_AUTHORIZATION'], 6 ) ) ), 2, null );
}
require_once( dirname( __FILE__ ) . DIRECTORY_SEPARATOR . 'ClassMap.inc.php' );
/**
* @param $name
* @return bool
*/
function TTAutoload( $name ) {
global $global_class_map, $profiler; //$config_vars needs to be here, otherwise TTPDF can't access the cache_dir.
//if ( isset( $profiler ) ) {
// $profiler->startTimer( '__autoload' );
//}
if ( isset( $global_class_map[$name] ) ) {
$file_name = Environment::getBasePath() . DIRECTORY_SEPARATOR . 'classes' . DIRECTORY_SEPARATOR . 'modules' . DIRECTORY_SEPARATOR . $global_class_map[$name];
} else {
//If the class name contains "plugin", try to load classes directly from the plugins directory.
if ( $name == 'PEAR' ) {
return false; //Skip trying to load PEAR class as it fails anyways.
} else if ( strpos( $name, 'Plugin' ) === false ) {
$file_name = $name . '.class.php';
} else {
$file_name = Environment::getBasePath() . DIRECTORY_SEPARATOR . 'classes' . DIRECTORY_SEPARATOR . 'modules' . DIRECTORY_SEPARATOR . 'plugins' . DIRECTORY_SEPARATOR . str_replace( 'Plugin', '', $name ) . '.plugin.php';
}
}
//Use include_once() instead of require_once so the installer doesn't Fatal Error without displaying anything.
//include_once() is redundant in __autoload.
//Debug::Text('Autoloading Class: '. $name .' File: '. $file_name, __FILE__, __LINE__, __METHOD__,10);
//Debug::Arr(Debug::BackTrace(), 'Backtrace: ', __FILE__, __LINE__, __METHOD__,10);
//Remove the following @ symbol to help in debugging parse errors.
if ( file_exists( $file_name ) === true ) {
include( $file_name );
} else {
return false; //File doesn't exist, could be external library or just incorrect name.
}
//if ( isset( $profiler ) ) {
// $profiler->stopTimer( '__autoload' );
//}
return true;
}
spl_autoload_register( 'TTAutoload' ); //Registers the autoloader mainly for use with PHPUnit
/**
* The basis for the plugin system, instantiate all classes through this, allowing the class to be overloaded on the fly by a class in the plugin directory.
* ie: $uf = TTNew( 'UserFactory' ); OR $uf = TTNew( 'UserFactory', $arg1, $arg2, $arg3 );
* @param $class_name
* @return string
*/
function TTgetPluginClassName( $class_name ) {
global $config_vars;
//Check if the plugin system is enabled in the config.
if ( isset( $config_vars['other']['enable_plugins'] ) && $config_vars['other']['enable_plugins'] == 1 ) {
//Classes must be alpha numeric, otherwise exit out early if someone is trying to pass in crazy class names as a potential attack.
// This also must ensure that no class name contains '../../' so an attacker can't attempt to access files outside the plugin directory.
// Some TimeClock classes have underscores in the name, so we need to use the actual PHP regex for class names as stated here: https://www.php.net/manual/en/language.oop5.basic.php
if ( preg_match( '/^[a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*$/', $class_name ) !== 1 ) { //Was using: ctype_alnum( $class_name ) == false -- But that doesn't allow underscores and is less than 10% faster than a complex regex.
Debug::Text( 'WARNING: Class name contains invalid characters: ' . $class_name, __FILE__, __LINE__, __METHOD__, 1 );
return false;
}
$plugin_class_name = $class_name . 'Plugin';
//This improves performance greatly for classes with no plugins.
//But it may cause problems if the original class was somehow loaded before the plugin.
//However the plugin wouldn't apply to it anyways in that case.
//
//Due to a bug that would cause the plugin to not be properly loaded if both the Factory and ListFactory were loaded in the same script
//we need to always reload the plugin class if the current class relates to it.
$is_class_exists = class_exists( $class_name, false );
if ( $is_class_exists == false || ( $is_class_exists == true && stripos( $plugin_class_name, $class_name ) !== false ) ) {
if ( class_exists( $plugin_class_name, false ) == false ) {
//Class file needs to be loaded.
$plugin_directory = Environment::getBasePath() . 'classes' . DIRECTORY_SEPARATOR . 'modules' . DIRECTORY_SEPARATOR . 'plugins';
$plugin_class_file_name = $plugin_directory . DIRECTORY_SEPARATOR . $class_name . '.plugin.php';
//Debug::Text('Plugin System enabled! Looking for class: '. $class_name .' in file: '. $plugin_class_file_name, __FILE__, __LINE__, __METHOD__,10);
if ( file_exists( $plugin_class_file_name ) ) {
@include_once( $plugin_class_file_name );
$class_name = $plugin_class_name;
Debug::Text( 'Found Plugin: ' . $plugin_class_file_name . ' Class: ' . $class_name, __FILE__, __LINE__, __METHOD__, 10 );
}
} else {
//Class file is already loaded.
$class_name = $plugin_class_name;
}
}
//else {
//Debug::Text('Plugin not found...', __FILE__, __LINE__, __METHOD__, 10);
//}
}
//else {
//Debug::Text('Plugins disabled...', __FILE__, __LINE__, __METHOD__, 10);
//}
return $class_name;
}
/**
* Instantiates a new object using plugins if they exist.
* @template Object
* @param class-string<Object> $class_name
* @return Object
* @throws ReflectionException
*/
function TTnew( $class_name ) { //Unlimited arguments are supported.
$class_name = TTgetPluginClassName( $class_name );
if ( func_num_args() > 1 ) {
$params = func_get_args();
array_shift( $params ); //Eliminate the class name argument.
$reflection_class = new ReflectionClass( $class_name );
return $reflection_class->newInstanceArgs( $params );
} else {
return new $class_name();
}
}
//Force no caching of file.
function forceNoCacheHeaders() {
//CSP headers break many things at this stage, unless "unsafe" is used for almost everything.
header( 'Content-Security-Policy: frame-ancestors \'self\'; default-src * \'unsafe-inline\'; script-src \'unsafe-eval\' \'unsafe-inline\' \'self\' *.timetrex.com *.google-analytics.com *.googletagmanager.com *.doubleclick.net *.googleapis.com *.gstatic.com *.google.com; img-src \'self\' map.timetrex.com:3128 *.mapbox.com *.timetrex.com *.google-analytics.com *.googletagmanager.com *.doubleclick.net *.googleapis.com *.gstatic.com *.google.com data: blob:' );
//Help prevent XSS or frame clickjacking.
header( 'X-XSS-Protection: 1; mode=block' );
header( 'X-Frame-Options: SAMEORIGIN' );
//Reduce MIME-TYPE security risks.
header( 'X-Content-Type-Options: nosniff' );
//Turn caching off.
header( 'Expires: Mon, 26 Jul 1997 05:00:00 GMT' );
header( 'Last-Modified: ' . gmdate( 'D, d M Y H:i:s' ) . ' GMT' );
header( 'Cache-Control: no-store' );
header( 'Vary: *' ); //Required for Safari to not cache APIGlobal.js.php during a refresh (after logged in). Without this, a refresh would redirect the user to the login screen even if they were logged in.
//Only when force_ssl is enabled and the user is using SSL, include the STS header.
global $config_vars;
if ( isset( $config_vars['other']['force_ssl'] ) && ( $config_vars['other']['force_ssl'] == true ) && Misc::isSSL( true ) == true ) {
header( 'Strict-Transport-Security: max-age=31536000; includeSubdomains' );
}
}
/**
* Function to force browsers to cache certain files.
* @param string $file_name
* @param int $mtime
* @param string $etag
* @return bool
*/
function forceCacheHeaders( $file_name = null, $mtime = null, $etag = null ) {
if ( $file_name == '' ) {
$file_name = $_SERVER['SCRIPT_FILENAME'];
}
if ( $mtime == '' ) {
$file_modified_time = filemtime( $file_name );
} else {
$file_modified_time = $mtime;
}
if ( $etag != '' ) {
$etag = trim( $etag );
}
//Help prevent XSS or frame clickjacking.
header( 'X-XSS-Protection: 1; mode=block' );
header( 'X-Frame-Options: SAMEORIGIN' );
//For some reason even with must-revalidate the browsers won't check ETag every page load.
//So some pages may get cached for an hour or two regardless of ETag changes.
header( 'Cache-Control: must-revalidate, max-age=0' );
header( 'Cache-Control: private', false );
header( 'Expires: Mon, 26 Jul 1997 05:00:00 GMT' );
//Check eTag first, then last modified time.
if ( ( isset( $_SERVER['HTTP_IF_NONE_MATCH'] ) && trim( $_SERVER['HTTP_IF_NONE_MATCH'] ) == $etag )
|| ( !isset( $_SERVER['HTTP_IF_NONE_MATCH'] )
&& isset( $_SERVER['HTTP_IF_MODIFIED_SINCE'] )
&& strtotime( $_SERVER['HTTP_IF_MODIFIED_SINCE'] ) == $file_modified_time ) ) {
//Cached page, send 304 code and exit.
header( 'HTTP/1.1 304 Not Modified' );
//Header('Connection: close'); //This closes keep-alive connections to close, shouldn't be needed and just slows things down.
ob_clean();
exit; //File is cached, don't continue.
} else {
//Not cached page, add headers to assist caching.
if ( $etag != '' ) {
header( 'ETag: ' . $etag );
}
header( 'Last-Modified: ' . gmdate( 'D, d M Y H:i:s', $file_modified_time ) . ' GMT' );
}
return true;
}
//See Authentication::checkValidCSRFToken() for more comments on how this is checked.
function sendCSRFTokenCookie() {
$csrf_token = sha1( TTUUID::generateUUID() );
//NOTE: If the user went to the secure HTTPS version of the domain and set this cookie, then tried to go to the non-secure HTTP version without first clearing cookies,
// the browser likely won't allow the non-secure cookie to overwrite the secure version of the cookie. You can see this error message in the developer tools, network tab, HEADERS RESPONSE section.
setcookie( 'CSRF-Token', $csrf_token . '-' . sha1( $csrf_token . TTPassword::getPasswordSalt() ), ( time() + 157680000 ), Environment::getCookieBaseURL(), '', Misc::isSSL( true ), false ); //Must not be HTTP only, as javascript needs to read this. Really should send the "SameSite=strict" flag, however PHP v7.3 and older handle this in different ways: https://stackoverflow.com/questions/39750906/php-setcookie-samesite-strict
return true;
}
/* @formatter:off */
define('TT_PRODUCT_COMMUNITY', 10 ); define('TT_PRODUCT_PROFESSIONAL', 15 ); define('TT_PRODUCT_CORPORATE', 20 ); define('TT_PRODUCT_ENTERPRISE', 25 );
function getTTProductEdition() { global $TT_PRODUCT_EDITION; if ( isset($TT_PRODUCT_EDITION) && $TT_PRODUCT_EDITION > 0 ) { return $TT_PRODUCT_EDITION; } elseif ( file_exists( Environment::getBasePath() .'classes'. DIRECTORY_SEPARATOR .'modules'. DIRECTORY_SEPARATOR .'expense'. DIRECTORY_SEPARATOR .'UserExpenseFactory.class.php') ) { $TT_PRODUCT_EDITION = TT_PRODUCT_ENTERPRISE; return $TT_PRODUCT_EDITION; } elseif ( file_exists( Environment::getBasePath() .'classes'. DIRECTORY_SEPARATOR .'modules'. DIRECTORY_SEPARATOR .'job'. DIRECTORY_SEPARATOR .'JobFactory.class.php') ) { $TT_PRODUCT_EDITION = TT_PRODUCT_CORPORATE; return $TT_PRODUCT_EDITION; } elseif ( file_exists( Environment::getBasePath() .'classes'. DIRECTORY_SEPARATOR .'modules'. DIRECTORY_SEPARATOR .'time_clock'. DIRECTORY_SEPARATOR .'TimeClock.class.php') ) { $TT_PRODUCT_EDITION = TT_PRODUCT_PROFESSIONAL; return $TT_PRODUCT_EDITION; } return TT_PRODUCT_COMMUNITY; }
function getTTProductEditionName() { switch( getTTProductEdition() ) { case 15: $retval = 'Professional'; break; case 20: $retval = 'Corporate'; break; case 25: $retval = 'Enterprise'; break; default: $retval = 'Community'; break; } return $retval; }
/* @formatter:on */
function TTsaveRequestMetrics() {
global $config_vars;
if ( function_exists( 'memory_get_usage' ) ) {
$memory_usage = memory_get_usage();
} else {
$memory_usage = 0;
}
file_put_contents( $config_vars['other']['request_metrics_log'], ( ( microtime( true ) - $_SERVER['REQUEST_TIME_FLOAT'] ) * 1000 ) . ' ' . $memory_usage . "\n", FILE_APPEND ); //Write each response in MS to log for tracking performance
}
//This has to be first, always.
require_once( dirname( __FILE__ ) . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'classes' . DIRECTORY_SEPARATOR . 'modules' . DIRECTORY_SEPARATOR . 'core' . DIRECTORY_SEPARATOR . 'Environment.class.php' );
require_once( dirname( __FILE__ ) . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'classes' . DIRECTORY_SEPARATOR . 'modules' . DIRECTORY_SEPARATOR . 'core' . DIRECTORY_SEPARATOR . 'Profiler.class.php' );
$profiler = new Profiler( true );
set_include_path(
'.' . PATH_SEPARATOR .
Environment::getBasePath() . 'classes' . DIRECTORY_SEPARATOR . 'modules' . DIRECTORY_SEPARATOR . 'core' .
PATH_SEPARATOR . Environment::getBasePath() . 'classes' .
PATH_SEPARATOR . Environment::getBasePath() . 'classes' . DIRECTORY_SEPARATOR . 'plugins' .
//PATH_SEPARATOR . get_include_path() . //Don't include system include path, as it can cause conflicts with other packages bundled with TimeTrex. However the bundled PEAR.php must check for class_exists('PEAR') to prevent conflicts with PHPUnit.
PATH_SEPARATOR . Environment::getBasePath() . 'classes' . DIRECTORY_SEPARATOR . 'pear' .
PATH_SEPARATOR . Environment::getBasePath() . 'vendor' . DIRECTORY_SEPARATOR . 'pear' . DIRECTORY_SEPARATOR . 'pear' ); //Put PEAR path at the end so system installed PEAR is used first, this prevents require_once() from including PEAR from two directories, which causes a fatal error.
require_once( Environment::getBasePath() . 'classes' . DIRECTORY_SEPARATOR . 'modules' . DIRECTORY_SEPARATOR . 'core' . DIRECTORY_SEPARATOR . 'Exception.class.php' );
require_once( Environment::getBasePath() . 'classes' . DIRECTORY_SEPARATOR . 'modules' . DIRECTORY_SEPARATOR . 'core' . DIRECTORY_SEPARATOR . 'Debug.class.php' );
require_once( Environment::getBasePath() . 'vendor' . DIRECTORY_SEPARATOR . 'autoload.php' ); //Composer autoloader.
/* @formatter:off */ /* REMOVING OR CHANGING THIS COPYRIGHT NOTICE IS IN STRICT VIOLATION OF THE LICENSE AND COPYRIGHT AGREEMENT -- Please don't steal from hard working volunteers. */ if ( getTTProductEdition() == TT_PRODUCT_COMMUNITY ) { define( 'COPYRIGHT_NOTICE', 'Copyright &copy; '. date('Y') .' <a href="https://'. ORGANIZATION_URL .'" class="footerLink">'. ORGANIZATION_NAME .'</a>. The Program is free software provided AS IS, without warranty. Licensed under <a href="http://www.fsf.org/licensing/licenses/agpl-3.0.html" class="footerLink" target="_blank">AGPLv3.</a>' ); } else { define( 'COPYRIGHT_NOTICE', 'Copyright &copy; '. date('Y') .' <a href="https://'. ORGANIZATION_URL .'" class="footerLink">'. ORGANIZATION_NAME .'</a>.' ); } /* @formatter:on */
Debug::setEnable( (bool)$config_vars['debug']['enable'] );
Debug::setEnableDisplay( (bool)$config_vars['debug']['enable_display'] );
Debug::setBufferOutput( (bool)$config_vars['debug']['buffer_output'] );
Debug::setEnableLog( (bool)$config_vars['debug']['enable_log'] );
Debug::setVerbosity( (int)$config_vars['debug']['verbosity'] );
if ( Debug::getEnable() == true && Debug::getEnableDisplay() == true ) {
ini_set( 'display_errors', 1 );
ini_set( 'display_startup_errors', 1 );
} else {
ini_set( 'display_errors', 0 );
ini_set( 'display_startup_errors', 0 );
}
if ( function_exists('TTShutdown') == false ) { //Could be created from other include files outside of this project.
//Function that is called on shutdown from register_shutdown_function().
function TTShutdown() {
//Operations to be handled *before* the request response is sent back to the user/browser.
ignore_user_abort( true );
if ( function_exists( 'fastcgi_finish_request' ) ) {
fastcgi_finish_request(); //Send data to user, so they aren't waiting for the below code to finish.
}
//Operations to be handled *after* the request response is sent back to the user/browser.
// Can queue up longer running operations to be run here if needed, without delaying the user.
Debug::text( 'Server Response Time: ' . ( microtime( true ) - $_SERVER['REQUEST_TIME_FLOAT'] ), __FILE__, __LINE__, __METHOD__, 10 );
Debug::showSQLProfile();
global $config_vars;
if ( PHP_SAPI != 'cli' && isset( $config_vars['other']['request_metrics_log'] ) && $config_vars['other']['request_metrics_log'] != '' ) {
TTsaveRequestMetrics();
}
Debug::Text( 'Shutting down completely...', __FILE__, __LINE__, __METHOD__, 10 );
Debug::Shutdown();
Debug::writeToLog();
Debug::Display(); //This only does anything if display is actually turned on.
return true;
}
}
//Register PHP error handling functions as early as possible.
register_shutdown_function( 'TTShutdown' );
set_error_handler( [ 'Debug', 'ErrorHandler' ] );
Debug::Text( 'URI: ' . ( isset( $_SERVER['REQUEST_URI'] ) ? $_SERVER['REQUEST_URI'] : 'N/A' ) . ' IP Address: ' . Misc::getRemoteIPAddress(), __FILE__, __LINE__, __METHOD__, 10 );
Debug::Text( 'USER-AGENT: ' . ( isset( $_SERVER['HTTP_USER_AGENT'] ) ? $_SERVER['HTTP_USER_AGENT'] : 'N/A' ), __FILE__, __LINE__, __METHOD__, 10 );
Debug::Text( 'Version: ' . APPLICATION_VERSION . ' (PHP: v' . phpversion() . ' ['. PHP_INT_SIZE .']) Edition: ' . getTTProductEdition() . ' Production: ' . (int)PRODUCTION . ' Server: ' . ( isset( $_SERVER['SERVER_ADDR'] ) ? $_SERVER['SERVER_ADDR'] : 'N/A' ) . ' OS: ' . OPERATING_SYSTEM . ' 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 );
if ( function_exists( 'bcscale' ) ) {
bcscale( 10 );
}
if ( PHP_SAPI != 'cli' ) {
//Make sure we are using SSL if required.
if ( ( isset( $config_vars['other']['force_ssl'] ) && $config_vars['other']['force_ssl'] == true ) && Misc::isSSL( true ) == false && isset( $_SERVER['HTTP_HOST'] ) && isset( $_SERVER['REQUEST_URI'] ) && !isset( $disable_https ) && php_sapi_name() != 'cli' ) {
//Prevent HTML tags from being included in the URL prior to redirecting to the HTTPS version.
//This helps avoid potential XSS attacks, even though its simply a redirect and gets handled properly afterwards. ie: http://demo.timetrex.com/?<script>test</script>
Redirect::Page( 'https://' . $_SERVER['HTTP_HOST'] . strip_tags( html_entity_decode( $_SERVER['REQUEST_URI'] ) ) );
exit;
}
if ( PRODUCTION == true ) {
$origin_url = ( Misc::isSSL( true ) == true ) ? 'https://' . Misc::getHostName( false ) : 'http://' . Misc::getHostName( false );
} else {
$origin_url = '*';
}
header( 'Access-Control-Allow-Origin: ' . $origin_url );
header( 'Access-Control-Allow-Headers: Content-Type, REQUEST_URI_FRAGMENT' );
unset( $origin_url );
}
require_once( 'Database.inc.php' );
require_once( 'Cache.inc.php' ); //Put cache after Database so we can handle our own DB caching.
?>