519 lines
26 KiB
PHP
519 lines
26 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".
|
||
|
*
|
||
|
********************************************************************************/
|
||
|
|
||
|
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 © '. 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 © '. 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.
|
||
|
?>
|