319 lines
13 KiB
PHP
319 lines
13 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".
|
||
|
*
|
||
|
********************************************************************************/
|
||
|
|
||
|
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 );
|
||
|
}
|
||
|
*/
|
||
|
?>
|