- New ESI data import for wormhole type data from _ESI_, closed #852 - New ESI data import static wormholes, closed #852 - Improved performance for character authorization (PHP). Reduced number of _SQL_ queries. - Improved HTTP cache header for `api/map/initData`, 'api/user/getEveServerStatus' ajax requests
657 lines
22 KiB
PHP
657 lines
22 KiB
PHP
<?php
|
||
/**
|
||
* Created by PhpStorm.
|
||
* User: Exodus
|
||
* Date: 28.05.2016
|
||
* Time: 16:05
|
||
*/
|
||
|
||
namespace lib;
|
||
|
||
|
||
use lib\db\Pool;
|
||
use lib\api\CcpClient;
|
||
use lib\api\GitHubClient;
|
||
use lib\api\SsoClient;
|
||
use lib\socket\AbstractSocket;
|
||
use lib\socket\SocketInterface;
|
||
use lib\socket\TcpSocket;
|
||
|
||
class Config extends \Prefab {
|
||
|
||
/**
|
||
* prefix for custom Pathfinder env vars
|
||
*/
|
||
const PREFIX_KEY = 'PF';
|
||
|
||
/**
|
||
* delimiter for custom Pathfinder env vars
|
||
*/
|
||
const ARRAY_DELIMITER = '-';
|
||
|
||
/**
|
||
* Hive key for all Pathfinder config vars (*.ini files)
|
||
*/
|
||
const HIVE_KEY_PATHFINDER = 'PATHFINDER';
|
||
|
||
/**
|
||
* Hive key for all environment config vars (*.ini files)
|
||
*/
|
||
const HIVE_KEY_ENVIRONMENT = 'ENVIRONMENT';
|
||
|
||
/**
|
||
* Hive key for Socket validation check
|
||
*/
|
||
const CACHE_KEY_SOCKET_VALID = 'CACHED_SOCKET_VALID';
|
||
|
||
/**
|
||
* Cache time for Socket validation check
|
||
*/
|
||
const CACHE_TTL_SOCKET_VALID = 60;
|
||
|
||
// ================================================================================================================
|
||
// Redis
|
||
// ================================================================================================================
|
||
|
||
/**
|
||
* Redis connect timeout (seconds)
|
||
*/
|
||
const REDIS_OPT_TIMEOUT = 2;
|
||
|
||
/**
|
||
* Redis read timeout (seconds)
|
||
*/
|
||
const REDIS_OPT_READ_TIMEOUT = 10;
|
||
|
||
/**
|
||
* redis retry interval (milliseconds)
|
||
*/
|
||
const REDIS_OPT_RETRY_INTERVAL = 200;
|
||
|
||
// ================================================================================================================
|
||
// EVE downtime
|
||
// ================================================================================================================
|
||
|
||
/**
|
||
* SSO downtime length (estimation), minutes
|
||
*/
|
||
const DOWNTIME_LENGTH = 8;
|
||
|
||
/**
|
||
* SSO downtime buffer length extends downtime length, minutes
|
||
*/
|
||
const DOWNTIME_BUFFER = 1;
|
||
|
||
// ================================================================================================================
|
||
// ESI API id´s
|
||
// ================================================================================================================
|
||
|
||
/**
|
||
* ESI categoryId or 'Structure's
|
||
*/
|
||
const ESI_CATEGORY_STRUCTURE_ID = 65;
|
||
|
||
/**
|
||
* ESI categoryId or 'Ship's
|
||
*/
|
||
const ESI_CATEGORY_SHIP_ID = 6;
|
||
|
||
/**
|
||
* ESI groupId or 'Wormhole's
|
||
*/
|
||
const ESI_GROUP_WORMHOLE_ID = 988;
|
||
|
||
/**
|
||
* ESI dogmaAttributeId for 'scanWormholeStrength's
|
||
*/
|
||
const ESI_DOGMA_ATTRIBUTE_SCANWHSTRENGTH_ID = 1908;
|
||
|
||
/**
|
||
* error message for missing Composer dependency class
|
||
*/
|
||
const ERROR_CLASS_NOT_EXISTS_COMPOSER = 'Class "%s" not found. → Check installed Composer packages';
|
||
|
||
/**
|
||
* error message for missing Composer dependency method
|
||
*/
|
||
const ERROR_METHOD_NOT_EXISTS_COMPOSER = 'Method "%s()" not found in class "%s". → Check installed Composer packages';
|
||
|
||
|
||
/**
|
||
* environment config keys that should be parsed as array
|
||
* -> use "," as delimiter in config files/data
|
||
*/
|
||
const ARRAY_KEYS = ['CCP_ESI_SCOPES', 'CCP_ESI_SCOPES_ADMIN'];
|
||
|
||
/**
|
||
* custom HTTP status codes
|
||
*/
|
||
const
|
||
HTTP_422='Unprocessable Entity';
|
||
|
||
/**
|
||
* all environment data
|
||
* @var array
|
||
*/
|
||
private $serverConfigData = [];
|
||
|
||
/**
|
||
* Config constructor.
|
||
* @param \Base $f3
|
||
*/
|
||
public function __construct(\Base $f3){
|
||
// set server data
|
||
// -> CGI params (Nginx)
|
||
// -> .htaccess (Apache)
|
||
$this->setServerData();
|
||
// set environment data
|
||
$this->setAllEnvironmentData($f3);
|
||
// set hive configuration variables
|
||
// -> overwrites default configuration
|
||
$this->setHiveVariables($f3);
|
||
|
||
// set global getter for \DateTimeZone
|
||
$f3->set('getTimeZone', function() use ($f3) : \DateTimeZone {
|
||
return new \DateTimeZone( $f3->get('TZ') );
|
||
});
|
||
|
||
// set global getter for new \DateTime
|
||
$f3->set('getDateTime', function(string $time = 'now', ?\DateTimeZone $timeZone = null) use ($f3) : \DateTime {
|
||
$timeZone = $timeZone ? : $f3->get('getTimeZone')();
|
||
return new \DateTime($time, $timeZone);
|
||
});
|
||
|
||
// database connection pool -----------------------------------------------------------------------------------
|
||
$f3->set(Pool::POOL_NAME, Pool::instance(
|
||
function(string $alias) use ($f3) : array {
|
||
// get DB config by alias for new connections
|
||
return self::getDatabaseConfig($f3, $alias);
|
||
},
|
||
function(string $schema) use ($f3) : array {
|
||
// get DB requirement vars from requirements.ini
|
||
return self::getRequiredDbVars($f3, $schema);
|
||
}
|
||
));
|
||
|
||
// lazy init Web Api clients ----------------------------------------------------------------------------------
|
||
$f3->set(SsoClient::CLIENT_NAME, SsoClient::instance());
|
||
$f3->set(CcpClient::CLIENT_NAME, CcpClient::instance());
|
||
$f3->set(GitHubClient::CLIENT_NAME, GitHubClient::instance());
|
||
|
||
// Socket connectors ------------------------------------------------------------------------------------------
|
||
$f3->set(TcpSocket::SOCKET_NAME, function(array $options = ['timeout' => 1]) : SocketInterface {
|
||
return AbstractSocket::factory(TcpSocket::class, self::getSocketUri(), $options);
|
||
});
|
||
}
|
||
|
||
/**
|
||
* get environment configuration data
|
||
* @param \Base $f3
|
||
* @return array|null
|
||
*/
|
||
protected function getAllEnvironmentData(\Base $f3){
|
||
if(!$f3->exists(self::HIVE_KEY_ENVIRONMENT, $environmentData)){
|
||
$environmentData = $this->setAllEnvironmentData($f3);
|
||
}
|
||
|
||
return $environmentData;
|
||
}
|
||
|
||
/**
|
||
* set/overwrite some global framework variables original set in config.ini
|
||
* -> can be overwritten in environments.ini OR ENV-Vars
|
||
* -> see: https://github.com/exodus4d/pathfinder/issues/175
|
||
* that depend on environment settings
|
||
* @param \Base $f3
|
||
*/
|
||
protected function setHiveVariables(\Base $f3){
|
||
// hive keys that can be overwritten
|
||
$hiveKeys = ['BASE', 'URL', 'DEBUG', 'CACHE'];
|
||
|
||
foreach($hiveKeys as $key){
|
||
if( !is_null( $var = self::getEnvironmentData($key)) ){
|
||
$f3->set($key,$var);
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* set all environment configuration data
|
||
* @param \Base $f3
|
||
* @return array|mixed|null
|
||
*/
|
||
protected function setAllEnvironmentData(\Base $f3){
|
||
$environmentData = null;
|
||
|
||
if( !empty($this->serverConfigData['ENV']) ){
|
||
// get environment config from $_SERVER data
|
||
$environmentData = (array)$this->serverConfigData['ENV'];
|
||
|
||
// some environment variables should be parsed as array
|
||
array_walk($environmentData, function(&$item, $key){
|
||
$item = (in_array($key, self::ARRAY_KEYS)) ? explode(',', $item) : $item;
|
||
});
|
||
|
||
$environmentData['ENVIRONMENT_CONFIG'] = 'PHP: environment variables';
|
||
}else{
|
||
// get environment data from *.ini file config
|
||
$customConfDir = $f3->get('CONF');
|
||
|
||
// check "custom" ini dir, of not found check default ini dir
|
||
foreach($customConfDir as $type => $path){
|
||
$envConfFile = $path . 'environment.ini';
|
||
$f3->config($envConfFile, true);
|
||
|
||
if(
|
||
$f3->exists(self::HIVE_KEY_ENVIRONMENT) &&
|
||
($environment = $f3->get(self::HIVE_KEY_ENVIRONMENT . '.SERVER')) &&
|
||
($environmentData = $f3->get(self::HIVE_KEY_ENVIRONMENT . '.' . $environment))
|
||
){
|
||
$environmentData['ENVIRONMENT_CONFIG'] = 'Config: ' . $envConfFile;
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
if( !is_null($environmentData) ){
|
||
ksort($environmentData);
|
||
$f3->set(self::HIVE_KEY_ENVIRONMENT, $environmentData);
|
||
}
|
||
|
||
return $environmentData;
|
||
}
|
||
|
||
/**
|
||
* get/extract all server data passed to PHP
|
||
* this can be done by either:
|
||
* OS Environment variables:
|
||
* -> add to /etc/environment
|
||
* OR:
|
||
* Nginx (server config):
|
||
* -> FastCGI syntax
|
||
* fastcgi_param PF-ENV-DEBUG 3;
|
||
*/
|
||
protected function setServerData(){
|
||
$data = [];
|
||
foreach($_SERVER as $key => $value){
|
||
if(strpos($key, self::PREFIX_KEY . self::ARRAY_DELIMITER) === 0){
|
||
$path = explode( self::ARRAY_DELIMITER, $key);
|
||
// remove prefix
|
||
array_shift($path);
|
||
|
||
$tmp = &$data;
|
||
foreach($path as $segment){
|
||
$tmp[$segment] = (array)$tmp[$segment];
|
||
$tmp = &$tmp[$segment];
|
||
}
|
||
|
||
// type cast values
|
||
// (e.g. '1.2' => (float); '4' => (int),...)
|
||
$tmp = is_numeric($value) ? $value + 0 : $value;
|
||
}
|
||
}
|
||
|
||
$this->serverConfigData = $data;
|
||
}
|
||
|
||
/**
|
||
* get a environment variable by hive key
|
||
* @param $key
|
||
* @return string|null
|
||
*/
|
||
static function getEnvironmentData($key){
|
||
$hiveKey = self::HIVE_KEY_ENVIRONMENT . '.' . $key;
|
||
\Base::instance()->exists($hiveKey, $data);
|
||
return $data;
|
||
}
|
||
|
||
/**
|
||
* get database config values
|
||
* @param \Base $f3
|
||
* @param string $alias
|
||
* @return array
|
||
*/
|
||
static function getDatabaseConfig(\Base $f3, string $alias) : array {
|
||
$alias = strtoupper($alias);
|
||
|
||
$config = [
|
||
'ALIAS' => $alias,
|
||
'SCHEME' => 'mysql',
|
||
'HOST' => 'localhost',
|
||
'PORT' => 3306,
|
||
'SOCKET' => null,
|
||
'NAME' => self::getEnvironmentData('DB_' . $alias . '_NAME'),
|
||
'USER' => self::getEnvironmentData('DB_' . $alias . '_USER'),
|
||
'PASS' => self::getEnvironmentData('DB_' . $alias . '_PASS')
|
||
];
|
||
|
||
$pdoReg = '/^(?<SCHEME>[[:alpha:]]+):((host=(?<HOST>[a-zA-Z0-9-_\.]*))|(unix_socket=(?<SOCKET>[a-zA-Z0-9\/]*\.sock)))((;dbname=(?<NAME>\w*))|(;port=(?<PORT>\d*))){0,2}/';
|
||
if(preg_match($pdoReg, self::getEnvironmentData('DB_' . $alias . '_DNS'), $matches)){
|
||
// remove unnamed matches
|
||
$matches = array_intersect_key($matches, $config);
|
||
// remove empty matches
|
||
$matches = array_filter($matches);
|
||
// merge matches with default config
|
||
$config = array_merge($config, $matches);
|
||
}
|
||
|
||
// connect options --------------------------------------------------------------------------------------------
|
||
$options = [
|
||
\PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION,
|
||
\PDO::ATTR_TIMEOUT => $f3->get('REQUIREMENTS.MYSQL.PDO_TIMEOUT')
|
||
];
|
||
|
||
if($config['SCHEME'] == 'mysql'){
|
||
$options[\PDO::MYSQL_ATTR_COMPRESS] = true;
|
||
$options[\PDO::MYSQL_ATTR_INIT_COMMAND] = implode(',', [
|
||
"SET NAMES " . strtolower(str_replace('-','', $f3->ENCODING)),
|
||
"@@session.time_zone = '+00:00'",
|
||
"@@session.default_storage_engine = " . self::getRequiredDbVars($f3, $config['SCHEME'])['DEFAULT_STORAGE_ENGINE']
|
||
]);
|
||
}
|
||
|
||
if(self::getPathfinderData('experiments.persistent_db_connections')){
|
||
$options[\PDO::ATTR_PERSISTENT] = true;
|
||
}
|
||
|
||
$config['OPTIONS'] = $options;
|
||
|
||
return $config;
|
||
}
|
||
|
||
/**
|
||
* get required MySQL variables from requirements.ini
|
||
* @param \Base $f3
|
||
* @param string $schema
|
||
* @return array
|
||
*/
|
||
static function getRequiredDbVars(\Base $f3, string $schema) : array {
|
||
return $f3->exists('REQUIREMENTS[' . strtoupper($schema) . '][VARS]', $vars) ? $vars : [];
|
||
}
|
||
|
||
/**
|
||
* get SMTP config values
|
||
* @return \stdClass
|
||
*/
|
||
static function getSMTPConfig(): \stdClass{
|
||
$config = new \stdClass();
|
||
$config->host = self::getEnvironmentData('SMTP_HOST');
|
||
$config->port = self::getEnvironmentData('SMTP_PORT');
|
||
$config->scheme = self::getEnvironmentData('SMTP_SCHEME');
|
||
$config->username = self::getEnvironmentData('SMTP_USER');
|
||
$config->password = self::getEnvironmentData('SMTP_PASS');
|
||
$config->from = [
|
||
self::getEnvironmentData('SMTP_FROM') => self::getPathfinderData('name')
|
||
];
|
||
return $config;
|
||
}
|
||
|
||
/**
|
||
* validates an SMTP config
|
||
* @param \stdClass $config
|
||
* @return bool
|
||
*/
|
||
static function isValidSMTPConfig(\stdClass $config): bool {
|
||
// validate email from either an configured array or plain string
|
||
$validateMailConfig = function($mailConf = null): bool {
|
||
$email = null;
|
||
if(is_array($mailConf)){
|
||
reset($mailConf);
|
||
$email = key($mailConf);
|
||
}elseif(is_string($mailConf)){
|
||
$email = $mailConf;
|
||
}
|
||
return \Audit::instance()->email($email);
|
||
};
|
||
|
||
return (
|
||
!empty($config->host) &&
|
||
!empty($config->username) &&
|
||
$validateMailConfig($config->from) &&
|
||
$validateMailConfig($config->to)
|
||
);
|
||
}
|
||
|
||
/**
|
||
* get email for notifications by hive key
|
||
* @param $key
|
||
* @return mixed
|
||
*/
|
||
static function getNotificationMail($key){
|
||
return self::getPathfinderData('notification' . ($key ? '.' . $key : ''));
|
||
}
|
||
|
||
/**
|
||
* get map default config values for map types (private/corp/ally)
|
||
* -> read from pathfinder.ini
|
||
* @param string $mapType
|
||
* @return mixed
|
||
*/
|
||
static function getMapsDefaultConfig($mapType = ''){
|
||
if( $mapConfig = self::getPathfinderData('map' . ($mapType ? '.' . $mapType : '')) ){
|
||
$mapConfig = Util::arrayChangeKeyCaseRecursive($mapConfig);
|
||
}
|
||
|
||
return $mapConfig;
|
||
}
|
||
|
||
/**
|
||
* get custom $message for a a HTTP $status
|
||
* -> use this in addition to the very general Base::HTTP_XXX labels
|
||
* @param int $status
|
||
* @return string
|
||
*/
|
||
static function getMessageFromHTTPStatus(int $status): string {
|
||
switch($status){
|
||
case 403:
|
||
$message = 'Access denied: User not found'; break;
|
||
default:
|
||
$message = '';
|
||
}
|
||
return $message;
|
||
}
|
||
|
||
/**
|
||
* use this function to "validate" the socket connection.
|
||
* The result will be CACHED for a few seconds!
|
||
* This function is intended to pre-check a Socket connection if it MIGHT exists.
|
||
* No data will be send to the Socket, this function just validates if a socket is available
|
||
* -> see pingDomain()
|
||
* @param string $uri
|
||
* @return bool
|
||
*/
|
||
static function validSocketConnect(string $uri) : bool{
|
||
$valid = false;
|
||
$f3 = \Base::instance();
|
||
|
||
if( !$f3->exists(self::CACHE_KEY_SOCKET_VALID, $valid) ){
|
||
if( $socketUrl = self::getSocketUri() ){
|
||
// get socket URI parts -> not elegant...
|
||
$domain = parse_url( $socketUrl, PHP_URL_SCHEME) . '://' . parse_url( $socketUrl, PHP_URL_HOST);
|
||
$port = parse_url( $socketUrl, PHP_URL_PORT);
|
||
// check connection -> get ms
|
||
$status = self::pingDomain($domain, $port);
|
||
if($status >= 0){
|
||
// connection OK
|
||
$valid = true;
|
||
}else{
|
||
// connection error/timeout
|
||
$valid = false;
|
||
}
|
||
}else{
|
||
// requirements check failed or URL not valid
|
||
$valid = false;
|
||
}
|
||
|
||
$f3->set(self::CACHE_KEY_SOCKET_VALID, $valid, self::CACHE_TTL_SOCKET_VALID);
|
||
}
|
||
|
||
return $valid;
|
||
}
|
||
|
||
/**
|
||
* get response time for a host in ms or -1 on error/timeout
|
||
* @param string $domain
|
||
* @param int $port
|
||
* @param int $timeout
|
||
* @return int
|
||
*/
|
||
static function pingDomain(string $domain, int $port, $timeout = 1) : int {
|
||
$startTime = microtime(true);
|
||
$file = @fsockopen ($domain, $port, $errno, $errstr, $timeout);
|
||
$stopTime = microtime(true);
|
||
|
||
if (!$file){
|
||
// Site is down
|
||
$status = -1;
|
||
}else {
|
||
fclose($file);
|
||
$status = ($stopTime - $startTime) * 1000;
|
||
$status = floor($status);
|
||
}
|
||
return $status;
|
||
}
|
||
|
||
/**
|
||
* get URI for TCP socket
|
||
* @return bool|string
|
||
*/
|
||
static function getSocketUri(){
|
||
$uri = false;
|
||
|
||
if(
|
||
( $ip = self::getEnvironmentData('SOCKET_HOST') ) &&
|
||
( $port = self::getEnvironmentData('SOCKET_PORT') )
|
||
){
|
||
$uri = 'tcp://' . $ip . ':' . $port;
|
||
}
|
||
return $uri;
|
||
}
|
||
|
||
/**
|
||
* @param string $key
|
||
* @return null|mixed
|
||
*/
|
||
static function getPathfinderData($key = ''){
|
||
$hiveKey = self::HIVE_KEY_PATHFINDER . ($key ? '.' . strtoupper($key) : '');
|
||
if( !\Base::instance()->exists($hiveKey, $data) ){
|
||
$data = null;
|
||
}
|
||
return $data;
|
||
}
|
||
|
||
/**
|
||
* get HTTP status message by HTTP return code
|
||
* -> either from F3 or from self::Config constants
|
||
* @param int $code
|
||
* @return string
|
||
*/
|
||
static function getHttpStatusByCode(int $code) : string {
|
||
if(empty($status = @constant('Base::HTTP_' . $code))){
|
||
$status = @constant('self::HTTP_' . $code);
|
||
}
|
||
return $status;
|
||
}
|
||
|
||
/**
|
||
* parse [D]ata [S]ource [N]ame string from *.ini into $conf parts
|
||
* -> $dsn = redis=localhost:6379:2
|
||
* $conf = ['type' => 'redis', 'host' => 'localhost', 'port' => 6379, 'db' => 2]
|
||
* -> some $conf values might be NULL if not found in $dsn!
|
||
* -> some missing values become defaults
|
||
* @param string $dsn
|
||
* @param array|null $conf
|
||
* @return bool
|
||
*/
|
||
static function parseDSN(string $dsn, ?array &$conf = []) : bool {
|
||
// reset reference
|
||
if($matches = (bool)preg_match('/^(\w+)\h*=\h*(.+)/', strtolower(trim($dsn)), $parts)){
|
||
$conf['type'] = $parts[1];
|
||
if($conf['type'] == 'redis'){
|
||
list($conf['host'], $conf['port'], $conf['db']) = explode(':', $parts[2]) + [1 => 6379, 2 => null];
|
||
}elseif($conf['type'] == 'folder'){
|
||
$conf['folder'] = $parts[2];
|
||
}
|
||
// int cast numeric values
|
||
$conf = array_map(function($val){
|
||
return is_numeric($val) ? intval($val) : $val;
|
||
}, $conf);
|
||
}
|
||
return $matches;
|
||
}
|
||
|
||
/**
|
||
* check if a given DateTime() is within downTime range: downtime + 10m
|
||
* -> can be used for prevent logging errors during downTime
|
||
* @param \DateTime|null $dateCheck
|
||
* @return bool
|
||
*/
|
||
static function inDownTimeRange(\DateTime $dateCheck = null) : bool {
|
||
$inRange = false;
|
||
// default daily downtime 00:00am
|
||
$downTimeParts = [0, 0];
|
||
if( !empty($downTime = (string)self::getEnvironmentData('CCP_SSO_DOWNTIME')) ){
|
||
$parts = array_map('intval', explode(':', $downTime));
|
||
if(count($parts) === 2){
|
||
// well formatted DOWNTIME found in config files
|
||
$downTimeParts = $parts;
|
||
}
|
||
}
|
||
|
||
try{
|
||
// downTime Range is 10m
|
||
$downtimeLength = self::DOWNTIME_LENGTH + (2 * self::DOWNTIME_BUFFER);
|
||
$timezone = \Base::instance()->get('getTimeZone')();
|
||
|
||
// if not set -> use current time
|
||
$dateCheck = is_null($dateCheck) ? new \DateTime('now', $timezone) : $dateCheck;
|
||
$dateDowntimeStart = new \DateTime('now', $timezone);
|
||
$dateDowntimeStart->setTime($downTimeParts[0],$downTimeParts[1]);
|
||
$dateDowntimeStart->sub(new \DateInterval('PT' . self::DOWNTIME_BUFFER . 'M'));
|
||
|
||
$dateDowntimeEnd = clone $dateDowntimeStart;
|
||
$dateDowntimeEnd->add(new \DateInterval('PT' . $downtimeLength . 'M'));
|
||
|
||
$dateRange = new DateRange($dateDowntimeStart, $dateDowntimeEnd);
|
||
$inRange = $dateRange->inRange($dateCheck);
|
||
}catch(\Exception $e){
|
||
$f3 = \Base::instance();
|
||
$f3->error(500, $e->getMessage(), $e->getTrace());
|
||
}
|
||
|
||
return $inRange;
|
||
}
|
||
|
||
/**
|
||
* format timeInterval in seconds into human readable string
|
||
* @param int $seconds
|
||
* @return string
|
||
* @throws \Exception
|
||
*/
|
||
static function formatTimeInterval(int $seconds = 0) : string {
|
||
$dtF = new \DateTime('@0');
|
||
$dtT = new \DateTime("@" . $seconds);
|
||
$diff = $dtF->diff($dtT);
|
||
|
||
$format = ($d = $diff->format('%d')) ? $d . 'd ' : '';
|
||
$format .= ($h = $diff->format('%h')) ? $h . 'h ' : '';
|
||
$format .= ($i = $diff->format('%i')) ? $i . 'm ' : '';
|
||
$format .= ($s = $diff->format('%s')) ? $s . 's' : '';
|
||
return $format;
|
||
}
|
||
|
||
static function ttlLeft($fromExists, int $ttlMax) : int {
|
||
$ttlMax = max($ttlMax, 0);
|
||
if($fromExists){
|
||
// == true || array
|
||
if(is_array($fromExists)){
|
||
return max(min((int)ceil(round(array_sum($fromExists) - microtime(true), 4)), $ttlMax), 0);
|
||
}else{
|
||
return 0;
|
||
}
|
||
}else{
|
||
// == false
|
||
return $ttlMax;
|
||
}
|
||
}
|
||
} |